继续踩partial overwrite和stack smash的坑
前置知识
Stack Smash
在程序加了 canary 保护之后,如果我们读取的 buffer 覆盖了对应的值时,程序就会报错,而一般来说我们并不会关心报错信息。而 stack smash 技巧则就是利用打印这一信息的程序来得到我们想要的内容。这是因为在程序启动 canary 保护之后,如果发现 canary 被修改的话,程序就会执行 __stack_chk_fail
函数来打印 argv[0] 指针所指向的字符串,故而我们可以通过覆盖argv[0]的内容来泄露内存中的字符串
Partial Overwrite
在开启了随机化(ASLR,PIE)后, 无论高位的地址如何变化,低 12 位的页内偏移始终是固定的, 也就是说如果我们能更改低位的偏移, 就可以在一定程度上控制程序的执行流, 绕过 PIE 保护
这种技巧不止在栈上有效, 在堆上也是一种有效的绕过地址随机化的手段
环境
- Ubuntu 18.04
- glibc 2.27
Stack Smash
这里以2015 年 32C3 CTF readme为例
在ctf-wiki上给出的exp以及讲解已经很详细了 https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/fancy-rop-zh/ ,我这里主要记录一下坑点
在我复现exp时,发现无论如何,打印出的信息都是
1 | '*** stack smashing detected ***: <unknown> terminated\n' |
一开始我以为是我的操作错误,但是在调试的过程中,却发现貌似与我的操作无关,而是glibc的行为与ctf-wiki上的有所差别
于是老样子,我去查看了glbc的源代码,发现了__fortify_fail_abort
函数是如下的形式
1 | void |
这与ctf-wiki上给出的版本有着很大的区别
1 | void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg) |
可以看到多了一个参数,而且在我调试的过程中,第一个参数永远为0xor edi,edi
,所以我们来看看上层函数是如何传参的
1 | void |
可以看到,在__stack_chk_fail
函数中直接就指定了need_backtrace
为false
,故而无论如何修改argv[0]
的指针,都不会输出argv[0]
指针指向的字符串,而是会一直输出unknown
另一个坑点在于,如果exp的程序在远端,那么类似如下的报错信息是直接输出在远端的tty
上而不是stderr
1 | '*** stack smashing detected ***: xxxxxx terminated\n' |
与这里有关的代码如下
1 | void |
可以从代码中看出,如果希望将此报错信息输出至stderr
,需要设置环境变量LIBC_FATAL_STDERR_=1
而ctf-wiki上所说的
没有出现无法看见报错信息这里我们直接就得到了 flag,没有出现网上说的得不到 flag 的情况
实际上就是环境提供者已经设置好了此环境变量
相关文章: https://github.com/ctfs/write-ups-2015/tree/master/32c3-ctf-2015/pwn/readme-200
Partial Overwrite
同样的,记录几个坑点
这里以安恒杯 2018 年 7 月月赛的 babypie 为例
ctf-wiki上也讲的比较清楚了 https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/fancy-rop-zh/
在复现的时候,老问题又出现了,system函数起不来
于是我尝试调试(16分之一的概率我试了好久…脸是真的黑)
发现了还是老问题,rsp没有对齐
不清楚为啥的可以看我写的这篇 https://ph4ntonn.github.io/bypass-Canary.html
同时注意这里不可以在syscall前对齐,因为system函数校验canary的时候,是如下代码
1 | 0x7f9c8d1b81f7 <do_system+679> mov rcx, QWORD PTR [rsp+0x178] |
所以必须在call之前就直接对齐
但是这里我并没有找到很好的办法可以在exp中对齐rsp
在Ubuntu 16.04
,glibc 2.23
下可以复现成功
附上exp
1 | from pwn import * |
成功率看脸~