这一篇会总结一下前面一笔带过的几个问题,否则那仅仅是一个理想环境下的漏洞利用,在真实环境下很难遇到这么好用的。

canary

canary 就是在栈溢出发生的高危区域的尾部插入一个值,当函数返回之时检测 canary 的值是否经过了改变,以此来判断是否发生栈溢出。

使用 gcc -S -fno-stack-protector -o 1.s main.cgcc -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 能成功运行是禁用了两大安全机制的结果 (捂脸

参考