背景介绍
原文:https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-iii
另外需要说明一下,原文只是一个系列中的一篇文章:
学习这篇之前,最好先把前面的都搞明白,本文的细节,只针对Part III using GOT overwrite and GOT dereference这篇。
1. 漏洞程序
准备工作:
程序说明:
2. 问题分解
面对一个复杂的问题时,通常需要自上而下、以大化小、分而治之,直接上图:
换个方式来说明最终目标的话,就是要让vuln执行这样一段代码:
并且仅仅是通过构造输入数据达到这个目的,而不是修改vlun.c源码,重新编译出新的vlun可执行文件。
为了方便描述,先假设可以构造出如下堆栈布局:
这显然可以使vuln按照上述的期望进行执行,但问题是我们无法向栈内构造这样的数据,原因如下:
不过,这些问题都可以化解:
对于execve(),可以通过GOT overwrite的方法执行,它依据的原理是,vuln进程执行到攻击者构造的逻辑时,GOT[getuid]这个内存单元,存储的一定已经是getuid()函数的加载地址,另外,execve与getuid加载地址的偏移,和它们在libc.so动态库文件中的偏移,是一样的,这样,通过输入数据构造逻辑,将这个偏移加到GOT[getuid],并触发getuid@PLT执行,即可执行execve()。GOT的原理,32位elf格式中的10种重定位类型同样介绍的很清楚。
上述这些,明显还没有将问题彻底分解,它们都依赖"通过输入数据构造的逻辑",比如,具体如何实现:GOT[getuid]+=(execve-getuid)?
由于gcc编译时没有加"-z execstack"选项,即栈所在的内存区间,没有可执行权限,无法使用往栈里构造shellcode的方法,这种情况下可以使用ROP的方法,从代码中寻找一些以"ret"指令结束的代码片段,拼凑出需要的完整逻辑,还是直接上图:
目标②:ebx = GOT[getuid]-0x5d5b04c4
GOT[getuid]运行时地址,根据vuln可执行文件就可以得到:
从而可以事先计算出ebx的静态值:ebx = 0xaaa99b40,因此,作者找一个gadget: "popl %ebx; ret;",这样,通过往栈里构造0xaaa99b40和这个gadget的地址即可达到目的;
目标④:-0xe0(%ebx,%esi,4) = 0x0804861c
作者发现,如果上述的call指令,调用_fini函数,可以达到不修改eax的目的,也就是要让-0xe0(%ebx,%esi,4)这个地址值(注意它前面有*号),存储的是_fini函数地址0x0804861c:
通过这样执行objdump命令,可以发现0x8049f3c这个地址处,存的是0x0804861c,从而又将目标转换成:-0xe0(%ebx,%esi,4) = 0x0804861c,这可以由目标⑦实现。另外,_fini函数中又会执行call指令,不过call的目标地址是确定的,由目标⑥保证再次call到的函数不修改eax即可;
3. 堆栈布局结果
不管是写一段程序,还是学习一段程序,先有一个概括性的图,往往能事半功倍,所以我给栈的布局,画了一张高清大图:
根据上述的问题分解过程,再去理解为什么要这样布局,难度就不大了,接下来可以顺着这个布局,看一下具体的执行过程:
gadget7
指向代码片段"movb $0x00000001, 0x0804A028; addl $0x04, %esp; popl %ebx; popl %ebp; ret;",并且gadget7所在位置,是main()函数返回地址的存储位置,这是通过写溢出覆盖的,那么,main()函数返回时,就会执行这段指令。使用这个代码片段,其实是有点“迫不得已”的,真正需要的其实只是"movb $0x00000001, 0x0804A028; ret;",但是没能在vuln中直接找到。
gadget7执行后:
另外,"addl $0x04, %esp; popl %ebx; popl %ebp;"使esp向上移动了4*3个字节,所以利用脚本在构造输入数据时,需要在gadget7后面安排4*3字节的dummy数据,保证gadget6距离gadget7 4*3字节,进而保证gadget7中的"ret"指令,能继续执行到gadget6,后续还有很多小的代码片段,都是这样连续在一起执行的,从而完成一个完整的逻辑,这也正是ROP的原理,以及gadget为什么要以"ret"指令结束的原因。
gadget2
gadget4~7,相当于迂回实现了gadget3的作用,与gadget2一起,服务于gadget1,gadget2执行结果:
但是,由于上方正好遇到服务于gadget4的0xfffff530,所以特地让gadget2指向代码片段"popl %ebx; popl %ebp; ret;",保证esp能够跳过0xfffff530的存储位置,到达gadget1的存储位置。
不过,到这个时候,离最终目标,还有另外一半路程:触发setuid(0)和execve()函数的执行。
根据上半程的"经验"+栈的详细布局图,不难看出,后续逻辑,是找了另外一块地盘,按照上图布局,构造了一个迁移栈(两个布局中的①~⑨标号,是一一对应的"逻辑-结果"),保证"/bin/sh"地址、{ "/bin/sh"地址, NULL }数组地址,都是固定的,从而绕过ASLR保护机制。
最后说明2点:
为了迁移到新栈帧,溢出栈的末尾布局如下:
leave指令等于:
4. 利用脚本执行结果演示
/
/
vuln.c
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
;
}
/
/
vuln.c
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
;
}
sudo gcc
-
fno
-
stack
-
protector
-
o vuln vuln.c
sudo chown root vuln
sudo chgrp root vuln
sudo chmod
+
s vuln
sudo sh
-
c
'echo 2 > /proc/sys/kernel/randomize_va_space'
sudo gcc
-
fno
-
stack
-
protector
-
o vuln vuln.c
sudo chown root vuln
sudo chgrp root vuln
sudo chmod
+
s vuln
sudo sh
-
c
'echo 2 > /proc/sys/kernel/randomize_va_space'
char
*
p
=
"/bin/sh"
;
char
*
*
argv
=
{ p, NULL };
setuid(
0
);
execve(p, argv, NULL);
char
*
p
=
"/bin/sh"
;
char
*
*
argv
=
{ p, NULL };
setuid(
0
);
execve(p, argv, NULL);
ldd vuln
objdump
-
S
/
lib
/
i386
-
linux
-
gnu
/
libc.so.
6
| grep
"getuid"
objdump
-
S
/
lib
/
i386
-
linux
-
gnu
/
libc.so.
6
| grep
"execve"
ldd vuln
objdump
-
S
/
lib
/
i386
-
linux
-
gnu
/
libc.so.
6
| grep
"getuid"
objdump
-
S
/
lib
/
i386
-
linux
-
gnu
/
libc.so.
6
| grep
"execve"
objdump
-
R vuln
objdump
-
sj .dynamic vuln | grep
'1c860408'
objdump
-
sj .dynamic vuln | grep
'1c860408'
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-9-18 12:45
被jmpcall编辑
,原因: 添加附件