BUUCTF-PWN-前五页简单回顾

学 pwn 到现在快三个月了,在 BUU 上做了前五页共160题,不能把刷过的题的技巧都给忘了,再做一遍还不是得心应手的题,同时堆题很灵活,要多总结才能举一反三。
现在写下刚开始学的时候栈和格式化字符串值得注意的点,堆的要单独一篇发
 
1.为什么 32 位的ROP 是 p32(system) + p32(0) + p32(binsh)
首先要了解 C 语言函数调用栈的过程
如 main 函数中进入 backdoor 函数执行 system('/bin/sh');
main 函数
0
backdoor 函数
0
call 指令会把当前 eip 的值压入栈中,也就是 ret ,然后进行 backdoor 参数,再把 ebp 压栈,就成了我们平时在 IDA 中看到的栈布局
同样的,执行到 call _system 指令时,需要将当前 eip 的值压入栈中,也就是 ret ,payload 中的 p32(system) + p32(0) + p32(binsh) 的 p32(0) 就是为了充当 ret ,也就是执行完 system 的返回地址
当然,也可以直接利用 call _system 构造 payload ,如 p32(call_system) + p32(binsh) ,这样就不需要我们去伪造 ret 了,因为 call _system 指令会自动将当前 eip 的值压入栈作为 ret
 
2.为什么 p64(pop_rdi_ret) + p64(binsh) + p64(system) 需要用到 pop_rdi_ret 指令
这里直接用gdb调试下,执行 pop rdi 前
0
执行 pop rdi 后,可以看到 /bin/sh 被放入了 rdi 寄存器
执行 ret 前,可以看到这里的 esp 执行了 system@plt
0
执行 ret 后
0
最后可以看到成功进入了 system 函数
由上可见,pop rdi 是用来控制 system 的参数为 /bin/sh 的, ret 则是帮助我们继续执行 system 函数
 
3.格式化字符串中 addr%n$n 和 addr%n$s 为什么可以实现任意地址写和任意地址读。
printf 函数执行的时候调用的参数实际上是存在着该参数数据的地址,而存在着该参数数据的地址也在栈上,并且是与 printf 的参数有一定偏移,因此我们就可以确定该参数数据的地址是 pintf 函数的第几个参数,这也是我们要调试出偏移的目的。并且因为参数数据是可控的,我们可以写入参数数据为某一内存地址,利用 %n$n/s 来将参数数据作为地址调用,那么就可以实现任意地址写和任意地址读。
 
4.为什么要栈对齐和为什么 ret 指令能对齐栈
ubuntu18及以上在调用system函数的时候会先进行一个检测,如果此时的栈没有16字节对齐的话,就会强行把程序crash掉,所以需要栈对齐。(https://www.cnblogs.com/Rookle/p/12871878.html
以64位程序为例,ret 指令是将 rsp 执行的八个字节送入 rip ,然后 rsp + 8
所以如果 payload 是
b'a'*offset + p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)
这里的 ret 相当于 ret pop_rdi_ret (看第二个问题),也就是继续执行 pop_rdi_ret ,和正常执行
b'a'*offset + p64(pop_rdi_ret) + p64(binsh) + p64(system)
没什么区别,但是 esp 被抬高了 0x8 ,这样执行到 system 时 esp 就是 0x10 对齐的了
 
5.栈迁移为什么要用到 leave 和 ret 指令(以 32 位程序为例)
leave => mov esp,ebp ; pop ebp
ret => pop eip
而我们能够通过栈溢出控制 ebp 的值间接控制 esp 的值,再通过 ret 指令控制 eip 劫持程序执行
这里看下 leave 指令的执行
leave 指令执行前
执行 leave 后
相当于执行了 mov esp,ebp ; pop ebp
mov esp,ebp 将 esp 劫持到了我们输入数据的开头, pop ebp 则是将我们数据开头的前四个字节作为 ebp 的值,也就是 aaaa
接下来是 ret 指令,将 eip 劫持为 system@plt
可以看到执行后程序进行了 system 函数,成功攻击
 
6.关于在 cmcc_simplerop 这一题中,当 payload 为
payload = b'a'*0x20 + p32(eax) + p32(0xb) + p32(edx_ecx_ebx) + p32(0)*2 + p32(next(elf.search(b'sh\x00'))) + p32(int_0x80)
为什么会攻击失败,因为 execve 调用参数时会将参数代表的文件作为二进制文件读
所以如果为 execve('sh', 0, 0) 会报错
 
然后是思维导图

格式化字符串

 

 堆

posted @ 2022-11-30 20:40  xshhc  阅读(450)  评论(0编辑  收藏  举报