当前位置: 首页 > news >正文

Linux二进制ELF程序查找symbol过程分析

文章目录

    • 0.相关section的简介
      • .got
      • .plt
      • .got.plt
      • .plt.got
    • 1.关于Partial RELRO下的外部函数延迟/惰性(lazily)调用的过程分析
      • 1.1例子
      • 1.2静态编译结果初步分析
      • 1.3GOTPLT_ADDR是什么地址?jmp到该地址将会完成那些功能?
      • 1.4对比Partial Full下的符号查找过程
      • 1.5总结一下
    • 2.参考
      • [Runtime Dynamic Linking](http://users.eecs.northwestern.edu/~kch479/docs/notes/linking.html)(害怕万一这个外网网站访问不到了,copy下来(^__^) ):

Linux二进制ELF程序查找symbol过程分析涉及到的段有.got/.got.plt/.plt/.plt.got,今天我们将一起深入探讨其原理和过程。

0.相关section的简介

.got

称为全局偏移表,链接器填充外部符号实际地址的表。全局偏移表(或GOT)是程序内部的一个部分,其中包含动态链接的函数的地址。

.got的前三项存放着特殊的地址引用:

  • GOT[0]:保存.dynamic段的地址,动态链接器利用该地址提取动态链接相关的信息。 GOT[0] is the address of the program’s .dynamic segment. This segment holds a lot of pointers to other parts of the ELF. It basically serves as a guide for the dynamic linker to navigate the ELF.

  • GOT[1]:保存动态链接程序管理的数据结构的指针。该数据结构是与程序链接的每个共享库的符号表相对应的节点的链接列表。GOT[1] is the pointer to a data structure that the dynamic linker manages. This data structure is a linked list of nodes corresponding to the symbol tables for each shared library linked with the program. When a symbol is to be resolved by the linker, this list is traversed to find the appropriate symbol. Using the LD_PRELOAD environment variable basically ensures that your preload library will be the first node on this list.

  • GOT[2]:存放了指向动态链接器_dl_runtime_resolve函数的地址,该函数用来解析共享库函数的实际符号地址。GOT[2] is the address osf the symbol resolution function within the dynamic linker. In ld.so, it contains the address of the function named _dl_runtime_resolve, which is basically an assembly stub that does some register/stack setup and calls into a C function called dl_fixup(). dl_fixup is the workhorse that actually resolves the symbol in question. Once the symbol’s address is found, the program’s GOT entry for it must be patched. This is also the job of dl_fixup(). Once dl_fixup() patches the correct GOT entry, the next time the function is called, it will again jump to the PLT entry, but this time the indirect jump there will go to the symbol’s address instead of the following instruction.

.plt

称为过程链接表,通过这个表程序将跳转到符号的正确地址,或者跳转到.got.plt段中寻找正确的符号地址。The PLT holds an entry for each external function reference.

.got.plt

.plt.got。它包含返回.plt去触发查找的地址,或者是一个经过查找后填充的正确符号地址。

注:

如果Partial RELRO下产生的程序,.got.plt段中一开始并未填充正确的符号地址。在程序运行过程中,当发现.got.plt段中符号地址未填充时,将使用动态链接器_dl_runtime_resolve函数来解析共享库函数的实际符号地址,并填充到.got.plt段中相应的位置。当程序第二次使用该符号地址时,由于.got.plt段中已经有值了,将直接跳转到正确的符号地址。在Partial RELRO下.got.plt段是可读/写的。

.plt.got

暂且不知道其用处。

1.关于Partial RELRO下的外部函数延迟/惰性(lazily)调用的过程分析

1.1例子

一小段C代码

#include <stdio.h>
#include <stdlib.h>int main(int argc, char **argv) {puts("Hello world1!");puts("Hello world2!");exit(0);
}

编译构建

$ gcc -no-pie -g -o plt plt.c

1.2静态编译结果初步分析

使用以下命令查看Section Header

$ readelf -W -S plt

得到如下结果:

There are 34 section headers, starting at offset 0x21c0:Section Headers:[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al[12] .plt              PROGBITS        0000000000400420 000420 000030 10  AX  0   0 16[13] .text             PROGBITS        0000000000400450 000450 000192 00  AX  0   0 16[21] .got              PROGBITS        0000000000600ff0 000ff0 000010 08  WA  0   0  8[22] .got.plt          PROGBITS        0000000000601000 001000 000028 08  WA  0   0  8
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),l (large), p (processor specific)

总共有34个section,如上所示。我仅保留了我们接下来讨论将重点讨论的几个section,.plt/.text/.got/.got.plt

查看plt的反汇编代码,在命令行中使用了-M intel选项来指定使用intel汇编格式而不是AT&T。

main函数的反汇编结果如下:

0000000000400537 <main>:400537:   55                      push   rbp 400538:   48 89 e5                mov    rbp,rsp40053b:   48 83 ec 10             sub    rsp,0x1040053f:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi400542:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi400546:   48 8d 3d a7 00 00 00    lea    rdi,[rip+0xa7]        # 4005f4 <_IO_stdin_used+0x4>40054d:   e8 de fe ff ff          call   400430 <puts@plt>400552:   48 8d 3d a9 00 00 00    lea    rdi,[rip+0xa9]        # 400602 <_IO_stdin_used+0x12>400559:   e8 d2 fe ff ff          call   400430 <puts@plt>40055e:   bf 00 00 00 00          mov    edi,0x0400563:   e8 d8 fe ff ff          call   400440 <exit@plt>400568:   0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]40056f:   00

如上所示,在0x40054d调用了0x400430 <puts@plt>,当rip到0x400430时,其将要执行的汇编代码如下:

0000000000400430 <puts@plt>:400430:   ff 25 e2 0b 20 00       jmp    QWORD PTR [rip+0x200be2]        # 601018 <puts@GLIBC_2.2.5>400436:   68 00 00 00 00          push   0x040043b:   e9 e0 ff ff ff          jmp    400420 <.plt>

执行的指令是jmp QWORD PTR [rip+0x200be2],其中rip+0x200be2=0x601018,地址0x601018指向哪里呢?

当再一次回头看一下之前查看的Section Header信息,你会发现0x601018正好落在.got.plt段中,如下:

Section Headers:[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al[22] .got.plt          PROGBITS        0000000000601000 001000 000028 08  WA  0   0  8
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),l (large), p (processor specific)

根据我们现有的知识可知:

  1. 如果ELF程序是Full RELRO的,那么.got.plt中将在链接时,填充上正确的符号地址。
  2. 如果ELF程序是Partial RELRO的,那么.got.plt中填充的是返回.plt去触发查找的地址,将使用动态链接器_dl_runtime_resolve函数来解析共享库函数的实际符号地址,并填充到.got.plt段中相应的位置。

从Section Headers表中可以发现.got.plt的Flg是WA.got.plt具有可写权限,说明ELF程序是Partial RELRO的。同时再利用checksec工具来看一下ELF程序是Partial RELRO,应证我们得到的结论。

cmp@U1804:~/work_dir/how_to_exploit/ELF/test2$ gdb plt 
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
...略...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from plt...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$ 

结果如上RELRO : Partial

回到当前将要执行的指令jmp QWORD PTR [rip+0x200be2],其中rip+0x200be2=0x601018,地址0x601018指向.got.plt段中的一个地址,0x601018指向的内存区域中存放的数据我们暂且称之为GOTPLT_ADDR。

GOTPLT_ADDR是什么地址?通过jmp到GOTPLT_ADDR,将会完成那些功能?

接下来将一一解惑。

1.3GOTPLT_ADDR是什么地址?jmp到该地址将会完成那些功能?

使用gdb调试,使用以下命令在0x40054d指令处下断点:

gdb-peda$ b *0x40054d

运行到0x40054d处,调试结果如下:

[----------------------------------registers-----------------------------------]
RAX: 0x400537 (<main>:	push   rbp)
RBX: 0x0 
RCX: 0x400570 (<__libc_csu_init>:	push   r15)
RDX: 0x7fffffffdec8 --> 0x7fffffffe26d ("CLUTTER_IM_MODULE=xim")
RSI: 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
RDI: 0x4005f4 ("Hello world1!")
RBP: 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
RSP: 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
RIP: 0x40054d (<main+22>:	call   0x400430 <puts@plt>)
R8 : 0x7ffff7dd0d80 --> 0x0 
R9 : 0x7ffff7dd0d80 --> 0x0 
R10: 0x2 
R11: 0x7 
R12: 0x400450 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x40053f <main+8>:	mov    DWORD PTR [rbp-0x4],edi0x400542 <main+11>:	mov    QWORD PTR [rbp-0x10],rsi0x400546 <main+15>:	lea    rdi,[rip+0xa7]        # 0x4005f4
=> 0x40054d <main+22>:	call   0x400430 <puts@plt>0x400552 <main+27>:	lea    rdi,[rip+0xa9]        # 0x4006020x400559 <main+34>:	call   0x400430 <puts@plt>0x40055e <main+39>:	mov    edi,0x00x400563 <main+44>:	call   0x400440 <exit@plt>
Guessed arguments:
arg[0]: 0x4005f4 ("Hello world1!")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
0008| 0x7fffffffddc8 --> 0x100000000 
0016| 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
0024| 0x7fffffffddd8 --> 0x7ffff7a05b97 (<__libc_start_main+231>:	mov    edi,eax)
0032| 0x7fffffffdde0 --> 0x1 
0040| 0x7fffffffdde8 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
0048| 0x7fffffffddf0 --> 0x100008000 
0056| 0x7fffffffddf8 --> 0x400537 (<main>:	push   rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, valueBreakpoint 1, 0x000000000040054d in main (argc=0x1, argv=0x7fffffffdeb8) at plt.c:5
5	  puts("Hello world1!");
gdb-peda$ 

使用gdb的si命令单指令调试,将跳转到0x400430 <puts@plt>处,结果如下:

[----------------------------------registers-----------------------------------]
RAX: 0x400537 (<main>:	push   rbp)
RBX: 0x0 
RCX: 0x400570 (<__libc_csu_init>:	push   r15)
RDX: 0x7fffffffdec8 --> 0x7fffffffe26d ("CLUTTER_IM_MODULE=xim")
RSI: 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
RDI: 0x4005f4 ("Hello world1!")
RBP: 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
RSP: 0x7fffffffddb8 --> 0x400552 (<main+27>:	lea    rdi,[rip+0xa9]        # 0x400602)
RIP: 0x400430 (<puts@plt>:	jmp    QWORD PTR [rip+0x200be2]        # 0x601018)
R8 : 0x7ffff7dd0d80 --> 0x0 
R9 : 0x7ffff7dd0d80 --> 0x0 
R10: 0x2 
R11: 0x7 
R12: 0x400450 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x400421:	xor    eax,0x200be20x400426:	jmp    QWORD PTR [rip+0x200be4]        # 0x6010100x40042c:	nop    DWORD PTR [rax+0x0]
=> 0x400430 <puts@plt>:	jmp    QWORD PTR [rip+0x200be2]        # 0x601018| 0x400436 <puts@plt+6>:	push   0x0| 0x40043b <puts@plt+11>:	jmp    0x400420| 0x400440 <exit@plt>:	jmp    QWORD PTR [rip+0x200bda]        # 0x601020| 0x400446 <exit@plt+6>:	push   0x1|->   0x400436 <puts@plt+6>:	push   0x00x40043b <puts@plt+11>:	jmp    0x4004200x400440 <exit@plt>:	jmp    QWORD PTR [rip+0x200bda]        # 0x6010200x400446 <exit@plt+6>:	push   0x1JUMP is taken
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddb8 --> 0x400552 (<main+27>:	lea    rdi,[rip+0xa9]        # 0x400602)
0008| 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
0016| 0x7fffffffddc8 --> 0x100000000 
0024| 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
0032| 0x7fffffffddd8 --> 0x7ffff7a05b97 (<__libc_start_main+231>:	mov    edi,eax)
0040| 0x7fffffffdde0 --> 0x1 
0048| 0x7fffffffdde8 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
0056| 0x7fffffffddf0 --> 0x100008000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400430 in puts@plt ()
gdb-peda$ 

注:

细心的人会发现以下的一个问题。jmp [rip+0x200be2][rip+0x200be2]应该等于0x601012 。使用gdb打印其结果,如下:

gdb-peda$ p $rip
$1 = (void (*)()) 0x400430 <puts@plt>
gdb-peda$ p $rip+0x200be2
$2 = (void (*)()) 0x601012

为什么 jmp [rip+0x200be2]实际跳转到了0x601018呢?

使用以下命令查看.got.plt段中0x601018位置存放的GOTPLT_ADDR是什么?

gdb-peda$ x 0x601018
0x601018:	0x0000000000400436

jmp QWORD PTR [rip+0x200be2]指令将变成jmp 0x400436,并沿着以下命令执行到0x40043b <puts@plt+11>: jmp 0x400420

 | 0x400436 <puts@plt+6>:	push   0x0| 0x40043b <puts@plt+11>:	jmp    0x400420

jmp 0x400420命令执行后将跳转到.plt段的开头,将要执行的指令如下:

Disassembly of section .plt:0000000000400420 <.plt>:400420:   ff 35 e2 0b 20 00       push   QWORD PTR [rip+0x200be2]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>400426:   ff 25 e4 0b 20 00       jmp    QWORD PTR [rip+0x200be4]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>40042c:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]

push QWORD PTR [rip+0x200be2] # 601008命令存入栈的是什么呢?有什么作用?

使用gdb打印0x601008存放的数据,如果如下:

gdb-peda$ x 0x601008
0x601008:	0x00007ffff7ffe170
gdb-peda$ x 0x00007ffff7ffe170
0x7ffff7ffe170:	0x0000000000000000

根据Runtime Dynamic Linking文章中所说:“The next instruction pushes some info on the stack (the PLT offset) and jumps to the very first entry into the PLT, which calls into the dynamic linker’s resolution function (_dl_runtime_resolve for ld.so)”。push进栈的是一个存放在.got.plt段中表示外部函数在.plt中的偏移值的指针。

本例中中put@plt.plt中的便宜为0。故0x7ffff7ffe170指向的数据为0。

查看进程映射信息:

gdb-peda$ i proc mappings
process 6427
Mapped address spaces:Start Addr           End Addr       Size     Offset objfile
..........0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0 

查看/proc/6427/maps中该区域的权限有rw-p

7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0

接着看这一条指令400426: ff 25 e4 0b 20 00 jmp QWORD PTR [rip+0x200be4] # 601010。指令将首先取出.got.plt段中0x601010处的数据。使用gdb打印0x601010处的数据,如下:

gdb-peda$ x 0x601010
0x601010:	0x00007ffff7dec7a0

通过查看进程映射信息,可以看到0x7ffff7dec7a0位于/lib/x86_64-linux-gnu/ld-2.27.so动态链接库中。

gdb-peda$ i proc mappings
process 6427
Mapped address spaces:Start Addr           End Addr       Size     Offset objfile0x7ffff7dd5000     0x7ffff7dfc000    0x27000        0x0 /lib/x86_64-linux-gnu/ld-2.27.so

使用gdb反汇编0x7ffff7dec7a0处的指令,如下:

gdb-peda$ disas 0x00007ffff7dec7a0
Dump of assembler code for function _dl_runtime_resolve_xsavec:0x00007ffff7dec7a0 <+0>:	push   rbx0x00007ffff7dec7a1 <+1>:	mov    rbx,rsp0x00007ffff7dec7a4 <+4>:	and    rsp,0xffffffffffffffc0
.....0x00007ffff7dec815 <+117>:	call   0x7ffff7de4e40 <_dl_fixup> ; 调用dl_fixup函数完成对未定义符号的修复操作。
.....0x00007ffff7dec84e <+174>:	mov    rbx,QWORD PTR [rsp]0x00007ffff7dec852 <+178>:	add    rsp,0x180x00007ffff7dec856 <+182>:	bnd jmp r11
End of assembler dump.

汇编结果如上所示,这段是_dl_runtime_resolve_xsavec函数的反汇编。该函数的主要作用是:调用dl_fixup函数完成对未定义符号的修复操作。

使用gdb命令完成当前puts@plt的调用,将返回到main函数。当前rip指向0x400552 (<main+27>: lea rdi,[rip+0xa9] # 0x400602),如下所示:

[----------------------------------registers-----------------------------------]
RAX: 0xe 
RBX: 0x0 
RCX: 0x7ffff7af4264 (<__GI___libc_write+20>:	cmp    rax,0xfffffffffffff000)
RDX: 0x7ffff7dd18c0 --> 0x0 
RSI: 0x602260 ("Hello world1!\n")
RDI: 0x1 
RBP: 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
RSP: 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
RIP: 0x400552 (<main+27>:	lea    rdi,[rip+0xa9]        # 0x400602)
R8 : 0x0 
R9 : 0x0 
R10: 0x602010 --> 0x0 
R11: 0x246 
R12: 0x400450 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x400542 <main+11>:	mov    QWORD PTR [rbp-0x10],rsi0x400546 <main+15>:	lea    rdi,[rip+0xa7]        # 0x4005f40x40054d <main+22>:	call   0x400430 <puts@plt>
=> 0x400552 <main+27>:	lea    rdi,[rip+0xa9]        # 0x4006020x400559 <main+34>:	call   0x400430 <puts@plt>0x40055e <main+39>:	mov    edi,0x00x400563 <main+44>:	call   0x400440 <exit@plt>0x400568:	nop    DWORD PTR [rax+rax*1+0x0]
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
0008| 0x7fffffffddc8 --> 0x100000000 
0016| 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
0024| 0x7fffffffddd8 --> 0x7ffff7a05b97 (<__libc_start_main+231>:	mov    edi,eax)
0032| 0x7fffffffdde0 --> 0x1 
0040| 0x7fffffffdde8 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
0048| 0x7fffffffddf0 --> 0x100008000 
0056| 0x7fffffffddf8 --> 0x400537 (<main>:	push   rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
main (argc=0x1, argv=0x7fffffffdeb8) at plt.c:6
6	  puts("Hello world2!");

继续使用si命令,rip将指向0x400559 <main+34>: call 0x400430 <puts@plt>,即将再次调用puts@plt函数。

继续使用si命令,rip将指向0x400430 <puts@plt>: jmp QWORD PTR [rip+0x200be2] # 0x601018。如下所示:

[----------------------------------registers-----------------------------------]
RAX: 0xe 
RBX: 0x0 
RCX: 0x7ffff7af4264 (<__GI___libc_write+20>:	cmp    rax,0xfffffffffffff000)
RDX: 0x7ffff7dd18c0 --> 0x0 
RSI: 0x602260 ("Hello world1!\n")
RDI: 0x400602 ("Hello world2!")
RBP: 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
RSP: 0x7fffffffddb8 --> 0x40055e (<main+39>:	mov    edi,0x0)
RIP: 0x400430 (<puts@plt>:	jmp    QWORD PTR [rip+0x200be2]        # 0x601018)
R8 : 0x0 
R9 : 0x0 
R10: 0x602010 --> 0x0 
R11: 0x246 
R12: 0x400450 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x400421:	xor    eax,0x200be20x400426:	jmp    QWORD PTR [rip+0x200be4]        # 0x6010100x40042c:	nop    DWORD PTR [rax+0x0]
=> 0x400430 <puts@plt>:	jmp    QWORD PTR [rip+0x200be2]        # 0x601018| 0x400436 <puts@plt+6>:	push   0x0| 0x40043b <puts@plt+11>:	jmp    0x400420| 0x400440 <exit@plt>:	jmp    QWORD PTR [rip+0x200bda]        # 0x601020| 0x400446 <exit@plt+6>:	push   0x1|->   0x7ffff7a64a30 <_IO_puts>:	push   r130x7ffff7a64a32 <_IO_puts+2>:	push   r120x7ffff7a64a34 <_IO_puts+4>:	mov    r12,rdi0x7ffff7a64a37 <_IO_puts+7>:	push   rbpJUMP is taken
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddb8 --> 0x40055e (<main+39>:	mov    edi,0x0)
0008| 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
0016| 0x7fffffffddc8 --> 0x100000000 
0024| 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
0032| 0x7fffffffddd8 --> 0x7ffff7a05b97 (<__libc_start_main+231>:	mov    edi,eax)
0040| 0x7fffffffdde0 --> 0x1 
0048| 0x7fffffffdde8 --> 0x7fffffffdeb8 --> 0x7fffffffe23d ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt")
0056| 0x7fffffffddf0 --> 0x100008000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400430 in puts@plt ()

使用gdb查看.got.plt段中0x601018处的值,如下所示:

gdb-peda$ x 0x601018
0x601018:	0x00007ffff7a64a30

反汇编0x00007ffff7a64a30处的代码,可以发现此处已经是_IO_puts函数的反汇编代码,这是_IO_puts函数的地址。说明经过第一次调用puts@plt后,puts正确的函数地址已经通过动态链接器中_dl_runtime_resolve_xsavec函数解析并且填充到了.got.plt段中。

gdb-peda$ disas 0x00007ffff7a64a30
Dump of assembler code for function _IO_puts:0x00007ffff7a64a30 <+0>:	push   r130x00007ffff7a64a32 <+2>:	push   r120x00007ffff7a64a34 <+4>:	mov    r12,rdi
......0x00007ffff7a64c21 <+497>:	add    rsp,0x800x00007ffff7a64c28 <+504>:	mov    rdi,rsi0x00007ffff7a64c2b <+507>:	call   0x7ffff7a05e70 <_Unwind_Resume>
End of assembler dump.
gdb-peda$

到此外部函数的延迟调用时,外部函数符号的解析过程已经完成。

1.4对比Partial Full下的符号查找过程

同样使用1.1中的小程序,使用如下命令进行编译

$ gcc -z relro -z now -no-pie -g -o plt_full plt.c

使用checksec工具查看一下Partial Full模式是开启状态。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : FULL

使用一下命令查看Section Header信息:

There are 33 section headers, starting at offset 0x2178:Section Headers:[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al[12] .plt              PROGBITS        0000000000400420 000420 000030 10  AX  0   0 16[13] .text             PROGBITS        0000000000400450 000450 000192 00  AX  0   0 16[21] .got              PROGBITS        0000000000600fc8 000fc8 000038 08  WA  0   0  8
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),l (large), p (processor specific)

结果如下:

There are 33 section headers, starting at offset 0x2178:Section Headers:[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al[12] .plt              PROGBITS        0000000000400420 000420 000030 10  AX  0   0 16[13] .text             PROGBITS        0000000000400450 000450 000192 00  AX  0   0 16[21] .got              PROGBITS        0000000000600fc8 000fc8 000038 08  WA  0   0  8
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),l (large), p (processor specific)

main函数的反汇编结果如下:

0000000000400537 <main>:400537:   55                      push   rbp400538:   48 89 e5                mov    rbp,rsp40053b:   48 83 ec 10             sub    rsp,0x1040053f:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi400542:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi400546:   48 8d 3d a7 00 00 00    lea    rdi,[rip+0xa7]        # 4005f4 <_IO_stdin_used+0x4>40054d:   e8 de fe ff ff          call   400430 <puts@plt>400552:   48 8d 3d a9 00 00 00    lea    rdi,[rip+0xa9]        # 400602 <_IO_stdin_used+0x12>400559:   e8 d2 fe ff ff          call   400430 <puts@plt>40055e:   bf 00 00 00 00          mov    edi,0x0400563:   e8 d8 fe ff ff          call   400440 <exit@plt>400568:   0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]40056f:   00

gdb调试运行plt_full,在0x40054d处下断点。程序运行到断点处的调试结果如下图所示:

[----------------------------------registers-----------------------------------]
RAX: 0x400537 (<main>:	push   rbp)
RBX: 0x0 
RCX: 0x400570 (<__libc_csu_init>:	push   r15)
RDX: 0x7fffffffdec8 --> 0x7fffffffe268 ("CLUTTER_IM_MODULE=xim")
RSI: 0x7fffffffdeb8 --> 0x7fffffffe233 ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt_full")
RDI: 0x4005f4 ("Hello world1!")
RBP: 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
RSP: 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe233 ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt_full")
RIP: 0x40054d (<main+22>:	call   0x400430 <puts@plt>)
R8 : 0x7ffff7dd0d80 --> 0x0 
R9 : 0x7ffff7dd0d80 --> 0x0 
R10: 0x2 
R11: 0x7 
R12: 0x400450 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x40053f <main+8>:	mov    DWORD PTR [rbp-0x4],edi0x400542 <main+11>:	mov    QWORD PTR [rbp-0x10],rsi0x400546 <main+15>:	lea    rdi,[rip+0xa7]        # 0x4005f4
=> 0x40054d <main+22>:	call   0x400430 <puts@plt>0x400552 <main+27>:	lea    rdi,[rip+0xa9]        # 0x4006020x400559 <main+34>:	call   0x400430 <puts@plt>0x40055e <main+39>:	mov    edi,0x00x400563 <main+44>:	call   0x400440 <exit@plt>
Guessed arguments:
arg[0]: 0x4005f4 ("Hello world1!")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe233 ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt_full")
0008| 0x7fffffffddc8 --> 0x100000000 
0016| 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
0024| 0x7fffffffddd8 --> 0x7ffff7a05b97 (<__libc_start_main+231>:	mov    edi,eax)
0032| 0x7fffffffdde0 --> 0x1 
0040| 0x7fffffffdde8 --> 0x7fffffffdeb8 --> 0x7fffffffe233 ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt_full")
0048| 0x7fffffffddf0 --> 0x100008000 
0056| 0x7fffffffddf8 --> 0x400537 (<main>:	push   rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x000000000040054d	5	  puts("Hello world1!");
gdb-peda$

使用gdb命令si进入puts@plt,如果如下:

[----------------------------------registers-----------------------------------]
RAX: 0x400537 (<main>:	push   rbp)
RBX: 0x0 
RCX: 0x400570 (<__libc_csu_init>:	push   r15)
RDX: 0x7fffffffdec8 --> 0x7fffffffe268 ("CLUTTER_IM_MODULE=xim")
RSI: 0x7fffffffdeb8 --> 0x7fffffffe233 ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt_full")
RDI: 0x4005f4 ("Hello world1!")
RBP: 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
RSP: 0x7fffffffddb8 --> 0x400552 (<main+27>:	lea    rdi,[rip+0xa9]        # 0x400602)
RIP: 0x400430 (<puts@plt>:	jmp    QWORD PTR [rip+0x200baa]        # 0x600fe0)
R8 : 0x7ffff7dd0d80 --> 0x0 
R9 : 0x7ffff7dd0d80 --> 0x0 
R10: 0x2 
R11: 0x7 
R12: 0x400450 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x400421:	xor    eax,0x200baa0x400426:	jmp    QWORD PTR [rip+0x200bac]        # 0x600fd80x40042c:	nop    DWORD PTR [rax+0x0]
=> 0x400430 <puts@plt>:	jmp    QWORD PTR [rip+0x200baa]        # 0x600fe0| 0x400436 <puts@plt+6>:	push   0x0| 0x40043b <puts@plt+11>:	jmp    0x400420| 0x400440 <exit@plt>:	jmp    QWORD PTR [rip+0x200ba2]        # 0x600fe8| 0x400446 <exit@plt+6>:	push   0x1|->   0x7ffff7a64a30 <_IO_puts>:	push   r130x7ffff7a64a32 <_IO_puts+2>:	push   r120x7ffff7a64a34 <_IO_puts+4>:	mov    r12,rdi0x7ffff7a64a37 <_IO_puts+7>:	push   rbpJUMP is taken
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddb8 --> 0x400552 (<main+27>:	lea    rdi,[rip+0xa9]        # 0x400602)
0008| 0x7fffffffddc0 --> 0x7fffffffdeb8 --> 0x7fffffffe233 ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt_full")
0016| 0x7fffffffddc8 --> 0x100000000 
0024| 0x7fffffffddd0 --> 0x400570 (<__libc_csu_init>:	push   r15)
0032| 0x7fffffffddd8 --> 0x7ffff7a05b97 (<__libc_start_main+231>:	mov    edi,eax)
0040| 0x7fffffffdde0 --> 0x1 
0048| 0x7fffffffdde8 --> 0x7fffffffdeb8 --> 0x7fffffffe233 ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt_full")
0056| 0x7fffffffddf0 --> 0x100008000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400430 in puts@plt ()

查看rip指向的指令0x400430 <puts@plt>: jmp QWORD PTR [rip+0x200baa] # 0x600fe0,将要跳转到0x600fe0中存储的数据地址。

注意:

回过头看Section Header中的信息发现:0x600fe0地址在.got段中。而Partial RELRO模式下的.got.plt段却消失了。

查看0x600fe0处的数据

gdb-peda$ x 0x600fe0
0x600fe0:	0x00007ffff7a64a30

反汇编0x00007ffff7a64a30处的代码,可以发现已经是_IO_puts函数的代码了。

gdb-peda$ disas 0x00007ffff7a64a30
Dump of assembler code for function _IO_puts:0x00007ffff7a64a30 <+0>:	push   r130x00007ffff7a64a32 <+2>:	push   r120x00007ffff7a64a34 <+4>:	mov    r12,rdi
..........0x00007ffff7a64c28 <+504>:	mov    rdi,rsi0x00007ffff7a64c2b <+507>:	call   0x7ffff7a05e70 <_Unwind_Resume>
End of assembler dump.
gdb-peda$

疑问???

以上例子,位于.got段中0x600fe0地址处的正确的_IO_puts函数地址是编译生成的?还是在加载程序的时候由动态链接器填写进去的呢?

查看Section Header.got段的信息,如下:

There are 33 section headers, starting at offset 0x2178:Section Headers:[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al[21] .got              PROGBITS        0000000000600fc8 000fc8 000038 08  WA  0   0  8
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),l (large), p (processor specific)

.got段的Flg为WA,该段是可写的。

其次读取程序静态时的.got段的数据,如下图所示:

$ readelf -x 21 plt_fullHex dump of section '.got':NOTE: This section has relocations against it, but these have NOT been applied to this dump.0x00600fc8 d80d6000 00000000 00000000 00000000 ..`.............0x00600fd8 00000000 00000000 36044000 00000000 ........6.@.....0x00600fe8 46044000 00000000 00000000 00000000 F.@.............0x00600ff8 00000000 00000000                   ........

由于ELF是小端字节序(little endian),可以发现0x600fe0地址处的数据是0x0000000036044000,并不是正确的_IO_puts函数地址0x00007ffff7a64a30

然后调试程序,在_start程序入口处下断点。结果如下:

[----------------------------------registers-----------------------------------]
RAX: 0x1c 
RBX: 0x0 
RCX: 0x4 
RDX: 0x7ffff7de59f0 (<_dl_fini>:	push   rbp)
RSI: 0x7ffff7ffe700 --> 0x0 
RDI: 0x8 
RBP: 0x0 
RSP: 0x7fffffffdeb0 --> 0x1 
RIP: 0x400450 (<_start>:	xor    ebp,ebp)
R8 : 0x2 
R9 : 0x0 
R10: 0x2 
R11: 0x7 
R12: 0x400450 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x400440 <exit@plt>:	jmp    QWORD PTR [rip+0x200ba2]        # 0x600fe80x400446 <exit@plt+6>:	push   0x10x40044b <exit@plt+11>:	jmp    0x400420
=> 0x400450 <_start>:	xor    ebp,ebp0x400452 <_start+2>:	mov    r9,rdx0x400455 <_start+5>:	pop    rsi0x400456 <_start+6>:	mov    rdx,rsp0x400459 <_start+9>:	and    rsp,0xfffffffffffffff0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdeb0 --> 0x1 
0008| 0x7fffffffdeb8 --> 0x7fffffffe233 ("/home/cmp/work_dir/how_to_exploit/ELF/test2/plt_full")
0016| 0x7fffffffdec0 --> 0x0 
0024| 0x7fffffffdec8 --> 0x7fffffffe268 ("CLUTTER_IM_MODULE=xim")
0032| 0x7fffffffded0 --> 0x7fffffffe27e ("LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...)
0040| 0x7fffffffded8 --> 0x7fffffffe86a ("LC_MEASUREMENT=zh_CN.UTF-8")
0048| 0x7fffffffdee0 --> 0x7fffffffe885 ("LESSCLOSE=/usr/bin/lesspipe %s %s")
0056| 0x7fffffffdee8 --> 0x7fffffffe8a7 ("LC_PAPER=zh_CN.UTF-8")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, valueBreakpoint 2, 0x0000000000400450 in _start ()

使用如下命令查看.got段中的数据,如下:

gdb-peda$ x /14wx 0x00600fc8
0x600fc8:	0x00600dd8	0x00000000	0x00000000	0x00000000
0x600fd8:	0x00000000	0x00000000	0xf7a64a30	0x00007fff
0x600fe8:	0xf7a271d0	0x00007fff	0xf7a05ab0	0x00007fff
0x600ff8:	0x00000000	0x00000000

由于ELF是小端字节序(little endian),0x600fe0地址处的数据是0x00007ffff7a64a30,在程序入口处已经是正确的_IO_puts函数地址0x00007ffff7a64a30

Full RELRO模式下,ELF文件中不会出现.got.plt段。所有的外部函数的符号,均在程序载入时被动态链接器解释后填充到.got段(Flg为WA,具有可写权限)中的相应位置。

1.5总结一下

  1. 在开启Partial RELRO时,外部函数的符号地址并不会事先填入.got.plt段中。
  2. 当第一次调用外部函数时,将利用碰床机制,通过.got.plt使rip,再次跳转.plt的头部触发动态链接器_dl_runtime_resolve_xsavec解析符号地址。
  3. 当完成解析后,正确的符号地址将填入.got.plt对应的位置中。
  4. 再次调用该外部函数时,由于.got.plt中已经正确的地址,而不需要重复解析了。
  5. Full RELRO模式下,ELF文件中不会出现.got.plt段。所有的外部函数的符号,均在程序载入时被动态链接器解释后填充到.got段(Flg为WA,具有可写权限)中的相应位置。

注:

  1. 延迟符号解析可以避免对没有调用的函数进行昂贵的查找。可以在编译选项中添加-z relro -z lazy开启Partial RELRO。已经编译好的Partial RELRO程序,您也可以通过设置LD_BIND_NOW环境变量来强制链接器在程序启动时对所有符号进行解析。
  2. 在编译选项中添加-z relro -z now开启Full RELRO。

2.参考

Runtime Dynamic Linking(害怕万一这个外网网站访问不到了,copy下来(__) ):

Dynamically linked binaries (usually) resolve external function calls lazily through what’s called the Procedure Linkage Table (PLT). The PLT holds an entry for each external function reference. When the function, say printf, is first called, it jumps to a known offset within the PLT corresponding to that function. This location contains a few instructions. The first performs an indirect jump into an entry of the Global Offset Table (GOT). At first, this entry contains the address of the instruction following the previous jump. This method is commonly known as trampolining. The next instruction pushes some info on the stack (the PLT offset) and jumps to the very first entry into the PLT, which calls into the dynamic linker’s resolution function (_dl_runtime_resolve for ld.so).

This call is simply another indirect jump into the GOT. The first three entries of the GOT are reserved, and are filled in by the dynamic linker on program startup. GOT[0] is the address of the program’s .dynamic segment. This segment holds a lot of pointers to other parts of the ELF. It basically serves as a guide for the dynamic linker to navigate the ELF.

GOT[1] is the pointer to a data structure that the dynamic linker manages. This data structure is a linked list of nodes corresponding to the symbol tables for each shared library linked with the program. When a symbol is to be resolved by the linker, this list is traversed to find the appropriate symbol. Using the LD_PRELOAD environment variable basically ensures that your preload library will be the first node on this list.

Finally, GOT[2] is the address osf the symbol resolution function within the dynamic linker. In ld.so, it contains the address of the function named _dl_runtime_resolve, which is basically an assembly stub that does some register/stack setup and calls into a C function called dl_fixup(). dl_fixup is the workhorse that actually resolves the symbol in question. Once the symbol’s address is found, the program’s GOT entry for it must be patched. This is also the job of dl_fixup(). Once dl_fixup() patches the correct GOT entry, the next time the function is called, it will again jump to the PLT entry, but this time the indirect jump there will go to the symbol’s address instead of the following instruction.

This method of lazy symbol resolution avoids costly lookups for functions that aren’t even called. You can force the linker to eagerly resolve symbols on program startup by setting the LD_BIND_NOW environment variable.

http://www.xdnf.cn/news/800389.html

相关文章:

  • 终端里面常用的转义字符串
  • 销毁机密文件你还在用删的吗?文件粉碎了解一下哈!(附自制工具下载)
  • xmind8 Pro序列号_xmind8pro序列号
  • java笔试题总结与大家分享
  • 光立方原理讲解_初中物理光现象知识点汇总大全
  • 9.100ASK_V853-PRO开发板支持E907小核开发
  • C语言入门--VC++6.0
  • PHP的socket详解
  • 浅析事务脚本和领域模型
  • 网站安全检测:推荐 8 款免费的 Web 安全测试工具
  • 代理IP网速慢,经常卡顿?如何有效解决!
  • 2024年最全黑客入门破解网络密码常用九个方法_网页password解密教程(1),50家大厂面试万字精华总结
  • 优秀网站源码、编程源码下载网站
  • 分析Apache Log4j2 远程代码执行漏洞_log4j2漏洞2,2024年最新网络安全性能优化最佳实践
  • Raspberry Pi 4 Model B(4G RAM)WIndows 10 安装(SD挂载USB驱动)
  • 电驴资源站
  • 最新免费wap网址大全
  • Webroot AntiVirus with AntiSpyware V6.1简介与下载
  • 架设NOD32升级服务器
  • 震荡波病毒简介
  • ***必备工具
  • hack工具大全
  • DirectX11笔记(二)--Direct3D初始化1之基本概念
  • 初入lamp兄弟连
  • Windows系统丢失oleaut32.dll文件导致程序错误问题
  • 个人网站设计作品html,经典网页设计:25个优秀的个人网站设计欣赏_html/css_WEB-ITnose...
  • Timer定时器每天的固定时间执行
  • Linux设备驱动---OMAP3630 Linux I2C总线驱动分析(2)
  • Windows系统丢失d3d10warp.dll文件导致程序无法运行问题
  • 点讯输入法S60数字键通用版V6.0(官方签名正式版)