pwn学习之dl_resolve学习篇

一:首先来了解一下linux下常见的攻击缓解机制:
这里写图片描述
CANARY:(金丝雀值,指的是矿工曾利用金丝雀来确认是否有气体泄漏,如果金丝雀因为气体泄漏而中毒死亡,可以给矿工预警),类似于windows GS技术,当栈溢出发生时,canary值将在已保存的指令指针被重写前先改变。系统检测这个值是否改变,栈溢出发生了,保存的指令指针可能也被修改了,因此不能安全返回,函数会调用__stack_chk_fail函数。这个函数会丢出一个错误然后退出进程。缺点:仅保护了sip,未保护应用变量,覆写GOT绕过。
FORTIFY:Compile Time Buffer Checks,确定函数执行栈的大小,以避免缓冲区溢出攻击。
NX:No Execute.现代处理器支持一种称为NX的特性使得系统控制各部分的执行的内存。即程序栈不可执行。
PIE:随机化加载程序的内存地址。
RELRO:RELocation Read-Only (RELRO) 重定位只读,它能够保护库函数的调用不受攻击者重定向的影响。
——————–
二.ELF动态装载机制详解:
从栈上执行shellcode到NX,从return2libc到ASLR, 从ROP泄露内存地址RELRO,攻与守的对抗技术在不断升级,进而催生出一种有效的针对没有libc文件和system函数的情况下实施ROP攻击的方法——return to dl-resolve。
一个应用由elf二进制文件和数个动态库构成,它们都是elf格式, ELF符号(函数或全局变量)使用elf_ sym结构体描述,[st_name]为相对.dynstr段开始的偏移,[st _info]为导出后函数的虚拟地址(没导出时为null)elf解析流程如图:
pwn学习之dl_resolve学习篇_第1张图片
.rel.plt结构体定义:

typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Word;
typedef struct
{
  Elf32_Addr    r_offset;               /* Address */
  Elf32_Word    r_info;                 /* Relocation type and symbol index */
} Elf32_Rel;
#define ELF32_R_SYM(val)                ((val) >> 8)
#define ELF32_R_TYPE(val)               ((val) & 0xff)

.dynsym结构体定义:

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;

我们readelf -d 查看JUMPREL(.rel.plt)段的地址如图:

# readelf -d pwn1

Dynamic section at offset 0xf14 contains 24 entries:
  标记        类型                         名称/值
 0x00000001 (NEEDED)                     共享库:[libc.so.6]
 0x0000000c (INIT)                       0x804837c
 0x0000000d (FINI)                       0x80486e4
 0x00000019 (INIT_ARRAY)                 0x8049f08
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x8049f0c
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x80481ac
 0x00000005 (STRTAB)                     0x8048280
 0x00000006 (SYMTAB)                     0x80481d0
 0x0000000a (STRSZ)                      116 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x804a000
 0x00000002 (PLTRELSZ)                   64 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x804833c
 0x00000011 (REL)                        0x804832c
 0x00000012 (RELSZ)                      16 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x804830c
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x80482f4
 0x00000000 (NULL)                       0x0
  readelf -S 查看rel节的信息:
# readelf -S pwn1
共有 30 个节头,从偏移量 0x1160 开始:

节头:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000024 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481d0 0001d0 0000b0 10   A  6   1  4
  [ 6] .dynstr           STRTAB          08048280 000280 000074 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          080482f4 0002f4 000016 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0804830c 00030c 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             0804832c 00032c 000010 08   A  5   0  4
  [10] .rel.plt          REL             0804833c 00033c 000040 08   A  5  12  4
  [11] .init             PROGBITS        0804837c 00037c 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080483a0 0003a0 000090 04  AX  0   0 16
  [13] .text             PROGBITS        08048430 000430 0002b2 00  AX  0   0 16
  [14] .fini             PROGBITS        080486e4 0006e4 000014 00  AX  0   0  4
  [15] .rodata           PROGBITS        080486f8 0006f8 000019 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        08048714 000714 00002c 00   A  0   0  4
  [17] .eh_frame         PROGBITS        08048740 000740 0000c0 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 00002c 04  WA  0   0  4
  [24] .data             PROGBITS        0804a02c 00102c 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a040 001034 000120 00  WA  0   0 32
...
  readelf -r 查看程序的链接库函数:
readelf -r pwn1

重定位节 '.rel.dyn' 位于偏移量 0x32c 含有 2 个条目:
 Offset     Info    Type            Sym.Value  Sym. Name
08049ffc  00000206 R_386_GLOB_DAT    00000000   __gmon_start__
0804a040  00000905 R_386_COPY        0804a040   stdout

重定位节 '.rel.plt' 位于偏移量 0x33c 含有 8 个条目:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a00c  00000107 R_386_JUMP_SLOT   00000000   read
0804a010  00000207 R_386_JUMP_SLOT   00000000   __gmon_start__
0804a014  00000307 R_386_JUMP_SLOT   00000000   strchr
0804a018  00000407 R_386_JUMP_SLOT   00000000   __libc_start_main
0804a01c  00000507 R_386_JUMP_SLOT   00000000   write
0804a020  00000607 R_386_JUMP_SLOT   00000000   setvbuf
0804a024  00000707 R_386_JUMP_SLOT   00000000   atoi
0804a028  00000807 R_386_JUMP_SLOT   00000000   shutdown

readelf -s 查看.dynsym(运行时所需)和.symtab(编译时的符号信息)节区信息:

# readelf -s pwn1

Symbol table '.dynsym' contains 11 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 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND strchr@GLIBC_2.0 (2)
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND write@GLIBC_2.0 (2)
     6: 00000000     0 FUNC    GLOBAL DEFAULT  UND setvbuf@GLIBC_2.0 (2)
     7: 00000000     0 FUNC    GLOBAL DEFAULT  UND atoi@GLIBC_2.0 (2)
     8: 00000000     0 FUNC    GLOBAL DEFAULT  UND shutdown@GLIBC_2.0 (2)
     9: 0804a040     4 OBJECT  GLOBAL DEFAULT   25 stdout@GLIBC_2.0 (2)
    10: 080486fc     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used

Symbol table '.symtab' contains 74 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 08048154     0 SECTION LOCAL  DEFAULT    1 
...

下面看下重定位过程:.symtab(编译时符号地址)+RELSZ对应于.dynsym(运行时符号地址)的偏移,.dynstr中的st_name的地址对应于STRTAB+.dynsym中的偏移:

gdb-peda$ x/x 0x80481d0(SYMTAB)+16
0x80481e0:  0x0000001a
gdb-peda$ x/s 0x8048280(STRTAB)+0x1a
0x804829a:  "read"

查看.rel.plt的read函数汇编:

gdb-peda$ x/5i read
   0x80483b0 <read@plt>:    jmp    DWORD PTR ds:0x804a00c
   0x80483b6 <read@plt+6>:  push   0x0
   0x80483bb <read@plt+11>: jmp    0x80483a0
   0x80483c0 <__gmon_start__@plt>:  jmp    DWORD PTR ds:0x804a010
   0x80483c6 <__gmon_start__@plt+6>:    push   0x8
gdb-peda$ x/wx 0x804a00c
0x804a00c <read@got.plt>:   0x080483b6

由于延迟绑定的机制,第一次调用read函数时寻找真正位置进行绑定。当我们第一次调用read时,其对应的GOT表里并没有存放read的真实地址,而是下一条指令的地址。第二、三行,把reloc_arg=0x0作为参数推入栈中,跳到0x80483a0继续执行。跟随:

gdb-peda$ x/10i 0x80483a0
   0x80483a0:   push   DWORD PTR ds:0x804a004
   0x80483a6:   jmp    DWORD PTR ds:0x804a008

0x80483a0把(link map = *(GOT+4))压栈,*(GOT+8)保存着 _dl runtime _resolve函数的地址,该函数将真实read地址写入GOT,随后将控制权转交read函数

_dl_runtime_resolve(link_map, rel_offset);

根据rel_offset找到rel _entry:

Elf32_Rel * rel_entry = JMPREL + rel_offset;

由rel_entry->r_info定位符号信息

Elf32_Sym *sym_entry = SYMTAB[ELF32_R_SYM(rel_entry->r_info)];

再定位符号信息中的符号名:

char *sym_name = STRTAB + sym_entry->st_name;

根据得到的符号名搜索动态库,找到地址后填充至.got.plt对应位置。
三.攻击过程分析
逆向思维:
1.控制*sym_name(改为system)需要控制*sym_entry;
2.再前面就是控制*rel_entry;
正向思维:
1.通过将eip覆盖为.rel.plt地址,传递一个可控的rel _offset,使rel_entry落在可控区域;
2.伪造rel_entry使sym_entry落在可控区域,伪造sym_entry使sym_name落在可控区域;
3.伪造sym_name为‘system’

参考文献:
1. http://www.freebuf.com/author/fubeerf
2.https://fedoraproject.org/wiki/Security_Features?rd=Security/Features
3. http://www.inforsec.org/wp/?p=389
4. http://rk700.github.io/article/2015/08/09/return-to-dl-resolve
5. http://drops.wooyun.org/binary/14360

你可能感兴趣的:(pwn,dl-resolve,逆向,逆向)