在 Linux 系统应用程序中部署 Canary 漏洞缓解机制可有效防御栈溢出漏洞的攻击,然而在一定环境下,攻击者可利用该机制泄露内存信息,实现进一步的攻击。
0x01利用思路
1. Stack smash
Linux 系统中,为了防御栈溢出漏洞的利用,通常会部署 Canary 漏洞缓解措施。Wiki 中对 Canary 的解释如下:
Canaries or canary words are known values that are placed between a buffer and control data on the stack to monitor buffer overflows. When the buffer overflows, the first data to be corrupted will usually be the canary, and a failed verification of the canary data will therefore alert of an overflow, which can then be handled, for example, by invalidating the corrupted data.
下面简单描述下 Canary 的原理。对于栈溢出漏洞的利用,最简单的方法就是通过溢出数据修改栈中函数返回地址为目标内存地址,当函数返回时将会跳转到目标内存处执行指令,从而实现控制流劫持。为了防御这种利用方法,分配栈空间时在 EBP-4 的位置存放一个 Canary 值,函数返回之前会校验该值是否被修改,若检测到被修改则调用 __stack_chk_fail
函数抛出异常并结束进程。可见,要覆盖函数返回地址必须修改 Canary,从而可防御该攻击方法。gcc 编译器默认开启该缓解机制,编译时可用 -fno-stack-protector
选项关闭该机制。
libc 中 __stack_chk_fail
的源码如下,该函数调用 __fortify_fail
输出异常信息,其中包含 libc_argv[0] 指向的程序名。
若通过栈溢出漏洞可修改栈内存中 argv[0] 指针,那么触发 Stack smash 时可泄露内存信息。例如把 argv[0] 修改为 got 表项可泄露出内存中函数地址,为进一步利用提供条件。
2. environ
在 Linux 系统中,glibc 的环境指针 environ(environment pointer) 为程序运行时所需要的环境变量表的起始地址,环境表中的指针指向各环境变量字符串。从以下结果可知环境指针 environ 在栈空间的高地址处。因此,可通过 environ 指针泄露栈地址。
0x02 实例分析
下面通过调试 网鼎杯的 pwn-GUESS 的利用过程说明 Stack smash 利用方法。
1. 漏洞位置
程序首先将 flag 读入内存中的 buf,用户有 3 次猜测 flag 的机会。通过 gets() 读取用户输入时存在栈溢出漏洞。
2. 漏洞利用
首先查看程序开启的漏洞缓解机制,发现已开启 Canary 和 NX,未开启 PIE。
由于程序中 sub_400A11() 函数使用 fork 子进程的方式允许用户有 3 次猜测 flag 的机会,同时又将 flag 读入栈中,因此可利用 Stack smash 进行 3 次内存泄漏获得 flag。具体利用思路如下:
- 通过栈溢出漏洞覆盖 argv[0] 为
__libc_start_main
的 got 表项,触发 Stack smash 可泄露__libc_start_main
函数地址,利用给出的 libc 文件可计算得到 libc 基地址; - 计算出 environ 在内存中的地址,第二次利用栈溢出漏洞覆盖 argv[0] 为
environ
,泄露出 environ 的值,即指向环境变量的栈地址; - 根据栈内存中 flag 与 environ 值的偏移量计算出 flag 的栈地址,再次利用栈溢出漏洞覆盖 argv[0] 为 flag 的栈地址,从而可读取 flag 的值。
1)泄露 libc 基址
首先,从下图栈内存信息可知缓冲区 s2 地址为 0x7fffffffdb60, argv[0] 地址为 0x7fffffffdc88,从而可计算出 s2 与 argv[0] 间的偏移量为 0x128(0x7fffffffdc88-0x7fffffffdb60)。
因此可构造以下 payload 将 argv[0] 覆盖为 __libc_start_main
的 got 表项,可泄露出 __libc_start_main
函数在内存中的地址,从而计算出 libc 的基址。
泄露出 libc 基址为 0x7ffff7a0d000。
2)泄露 environ
构造以下 payload,第二次利用栈溢出将 argv[0] 覆盖为 environ
的地址,从而泄露出 environ
的值,该值为执行环境变量的栈地址。
泄露出 environ
的值为 0x7fffffffdcf8。
可在 gdb 中验证该值为正确的。
3)读取 flag
查看内存中 flag 的地址为 0x7fffffffdb90,计算该地址与泄露栈地址的偏移量为 0x168(0x7fffffffdcf8 - 0x7fffffffdb90)。
构造以下 payload,第三次利用栈溢出覆盖 argv[0] 为 flag 的内存地址,从而可读取内存中的 flag。
最终获取 flag 如下:
References:
[1] 浅析ROP之Stack Smash
[2] Environ