学弟问了一个ctf-wiki上pwn入门题的知识点,本身没什么意思,但引发的一些小思考感觉还挺好玩的……当然也有可能我作为一个单纯的re狗,pwn题总是被队友秒光了所以没什么写shellcode或者ROP的机会,因此对shellcode的编写比较生疏叭~
题目下载链接 - sniperoj-pwn100-shellcode-x86-64
题目简介

很明显栈溢出,buf位于栈顶,栈空间为0x10个字节(可以从buf的位置==rsp+0==rbp-10h看出),所以栈分布为
| 内容 | 偏移 |
|---|---|
| buf | rsp |
| last_rbp | rsp+0x10 |
| ret_addr | rsp+0x18 |
| other | rsp+0x20 |
| buf_end | rsp+0x40 |
题目保护只有PIE,但是栈地址被刻意给出来了所以无所谓,因此这题本身很简单只需要把execve(’/bin/sh’)的shellcode放到buf里即可。
ret_addr是需要确定的,因此能放shellcode的地方分别有上下两部分,buf-last_rbp一共0x18即24个字节,other-buf_end一共0x20即32个字节。
学弟的问题就是网上很多WP说因为buf只有24个字节所以不能用shellcraft.sh()生成的44个字节的shellcode,然后最后给出的exp清一色全是长度为23的shellcode再加上
payload = 'a'*24 + p64(shellcode_addr) + shellcode
就让人很不能理解,明明这种方法是采用的后者32个字节的空间,跟24有啥关系尼?
这些payload是可以正常打通的,实际上任何小于32bytes的payload使用后边空间都是没问题的。
分析问题
至于为什么shellcode是23字节却不能使用前者,实际上是因为leave以及空间过于紧促。
首先我们看一下shellcode的组成:

注意到最大的栈需求是连续三次push,也就是说会把rsp抬高3*8=24字节。
而leave的时候实际上相当于
mov rsp, rbp
pop rbp
而上一个栈帧中rbp是指向last_rbp的,当赋值给rsp后,又进行了一次pop使得rsp-=8指向了ret_addr。
另一方面我们的shellcode是23bytes,即0x17bytes,恰好占用了buf和last_rbp的空间。
因此当shellcode执行的时候第一次push会覆盖ret_addr,第二次push就会覆盖last_rbp,而last_rbp现在是shellcode的末端,导致syscall指令无法执行。
绕过分析
那么如果想要修复它,就有两条思路:
- 降低栈的使用
- 降低rsp指针
刚开始我的思路是按1走,毕竟最终syscall只需要用到2个栈空间,虽然现在只有一个但是对于空闲的1bytes可以做一次pop来获取一个额外的栈空间。
但是看了一下shellcode就会发现这个顺序已经没有办法改变了:
execve的执行条件有如下几个:
rax==0x3brdx==rsi==0rdi==rsp*这里的rsp必须是最终的栈顶栈上分别存放'/bin//sh'和0
因为栈上的两个参数是不可省略的,而rdi又必须指向放好参数后的栈顶,在rsp是通过栈传给rdi的情况下必然要用到3个栈空间。
(rsp->rdi的过程是push rsp, pop rdi,仅需要2bytes,而正常赋值需要3bytes以上。)
于是现在有如下条件:
- 栈空间只有1个,空闲字节也只有1byte
- 传参需要2个栈空间
- rsp->rdi需要1个栈空间
- pop可以+1栈空间,但消耗1个字节
- rsp->rdi也可以用1个字节换1个栈空间
看起来似乎就差那1byte,怎么办呢?
搜索
不就差1byte嘛,这个shellcode不行,我们来康康还有没有更好的shellcode哇!
搜索了一下发现果然有22bytes的shellcode:

payload = "\x31\xF6\xF7\xE6\x50\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x54\x5F\xB0\x3B\x0F\x05"
这样就又省出了1byte,把push rsp + pop rdi换成mov rdi, rsp或者索性在开头多pop rsi一次都可以。
最终exp:
shellcode2 = "\x5e\x5e\x31\xF6\xF7\xE6\x50\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x54\x5F\xB0\x3B\x0F\x05"
payload = shellcode2+p64(shellcode_addr)
因地制宜
这个23bytes的shellcode真的没有办法了吗?
在搜索过程中发现有的shellcode使用了两字节的mov al, 59的指令,这样比起原来三字节的push 59, pop rax又能节省1bytes。
那本来为什么不用呢?因为这样的指令会使得原来的rax的高3字节保留下来。
可是我们这个程序有这样的顾虑吗?
注意到代码中:

return 0意味着什么?

ret前会为我们将rax清零!
因此这里我们也可以节省出1字节,同样的方法转换成栈空间,从而成功get shell~

本文探讨了一个CTF的栈溢出题目,解释了shellcode的限制和如何解决。分析了shellcode长度与栈空间的关系,以及如何通过选择不同长度的shellcode或调整指令来绕过限制。最后,展示了如何利用程序特性巧妙地节省栈空间,成功执行shellcode。
2276

被折叠的 条评论
为什么被折叠?



