这一篇会总结一下前面一笔带过的几个问题,否则那仅仅是一个理想环境下的漏洞利用,在真实环境下很难遇到这么好用的。
canary
canary 就是在栈溢出发生的高危区域的尾部插入一个值,当函数返回之时检测 canary 的值是否经过了改变,以此来判断是否发生栈溢出。
使用 gcc -S -fno-stack-protector -o 1.s main.c
和 gcc -S -o 2.s main.c
分别生成两个汇编文件,然后 diff 结果如下。
35,37d34
< movq %fs:40, %rax
< movq %rax, -8(%rbp)
< xorl %eax, %eax
88,92d84
< movq -8(%rbp), %rdx
< xorq %fs:40, %rdx
< je .L8
< call __stack_chk_fail
< .L8:
实际调试也很清楚的展示了这一点
[----------------------------------registers-----------------------------------]
RAX: 0x1a5ab696c6c08700
RBP: 0x7fffffffe340 --> 0x400810 (<__libc_csu_init>: push r15)
...
[-------------------------------------code-------------------------------------]
0x400721 <main+11>: mov QWORD PTR [rbp-0x60],rsi
0x400725 <main+15>: mov rax,QWORD PTR fs:0x28
0x40072e <main+24>: mov QWORD PTR [rbp-0x8],rax
=> 0x400732 <main+28>: xor eax,eax
...
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400732 9 int main(int argc, char **argv) {
gdb-peda$ x 0x7fffffffe338
0x7fffffffe338: 0x1a5ab696c6c08700
在部分情况下,__stack_chk_fail
也可以来漏洞利用,以后会提到。
ASLR
在第二篇文章中硬编码了 system
函数的地址,在第三篇文章中硬编码了 student
结构体的地址,但是一个是指令地址,一个是栈上数据的地址,在多次运行的时候,这两个地址是否会变化呢?
#include <stdio.h>
int main() {
int a = 100;
printf("%p", &a);
return 0;
}
使用 gcc -g -o aslr aslr.c
编译,然后 cat /proc/sys/kernel/randomize_va_space
是默认值 2
,发现每次输出都是不一样的,说明堆栈的地址会每次都变化。
➜ new ./aslr
0x7ffe9fbb2374 ➜ new ./aslr
0x7ffd2d1286b4 ➜ new ./aslr
0x7ffc697f8af4
但是如果在 GDB 中多次运行,然后 p &a
会发现地址是固定不变的。
然后使用 echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
禁用 ASLR,再次运行,发现直接运行地址也是固定了的。
➜ new ./aslr
0x7fffffffe3a4 ➜ new ./aslr
0x7fffffffe3a4 ➜ new ./aslr
0x7fffffffe3a4
ASLR 会随机化堆栈地址和共享库的地址,这样的话,我们就无法在 payload 中硬编码 student
的地址,也就是 shellcode 的地址了。因为 GDB 在调试的时候会默认禁用 ASLR,所以我们的 payload 在 GDB 中还是可以运行的。
由于函数参数、环境变量等很多因素会影响栈地址,即使在关闭 ASLR 的情况下,直接运行的栈地址和 GDB 中的栈地址也不一定是一样的,运行时就会 segmentation fault
。
这时候可以使用 GDB attach 到那个进程上。
GDB attach
先运行 vuln
进程,然后运行 sudo gdb --pid $(pidof vuln)
attach 到指定的进程上。
gdb-peda$ p &student
No symbol "student" in current context.
因为目前的栈 frame 不对,要切换到 main 函数所在的 frame。
gdb-peda$ bt
#0 0x00007ffff7b04230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
#1 0x00007ffff7a875e8 in _IO_new_file_underflow (fp=0x7ffff7dd18e0 <_IO_2_1_stdin_>) at fileops.c:592
#2 0x00007ffff7a8860e in __GI__IO_default_uflow (fp=0x7ffff7dd18e0 <_IO_2_1_stdin_>) at genops.c:413
#3 0x00007ffff7a69260 in _IO_vfscanf_internal (s=<optimized out>, format=<optimized out>, argptr=argptr@entry=0x7fffffffe278, errp=errp@entry=0x0)
at vfscanf.c:634
#4 0x00007ffff7a785df in __isoc99_scanf (format=<optimized out>) at isoc99_scanf.c:37
#5 0x00000000004006ed in main (argc=0x1, argv=0x7fffffffe498) at main.c:13
#6 0x00007ffff7a2d830 in __libc_start_main (main=0x4006b6 <main>, argc=0x1, argv=0x7fffffffe498, init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7fffffffe488) at ../csu/libc-start.c:291
#7 0x00000000004005e9 in _start ()
gdb-peda$ up 5
#5 0x00000000004006ed in main (argc=0x1, argv=0x7fffffffe498) at main.c:13
13 scanf("%d", &student.birth);
gdb-peda$ p &student
$1 = (struct Student *) 0x7fffffffe360
确定了 student
直接运行时候的地址,直接修改 payload 即可。
最后记得 echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
再打开 ASLR。
NX
在第三篇文章中已经说过,所以那个 shellcode 能成功运行是禁用了两大安全机制的结果 (捂脸