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


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,rsp
  40053b:   48 83 ec 10             sub    rsp,0x10
  40053f:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
  400542:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  400546:   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,0x0
  400563:   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   0x0
  40043b:   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],edi
   0x400542 <main+11>:	mov    QWORD PTR [rbp-0x10],rsi
   0x400546 <main+15>:	lea    rdi,[rip+0xa7]        # 0x4005f4
=> 0x40054d <main+22>:	call   0x400430 <puts@plt>
   0x400552 <main+27>:	lea    rdi,[rip+0xa9]        # 0x400602
   0x400559 <main+34>:	call   0x400430 <puts@plt>
   0x40055e <main+39>:	mov    edi,0x0
   0x400563 <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, value

Breakpoint 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,0x200be2
   0x400426:	jmp    QWORD PTR [rip+0x200be4]        # 0x601010
   0x40042c:	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   0x0
       0x40043b <puts@plt+11>:	jmp    0x400420
       0x400440 <exit@plt>:	jmp    QWORD PTR [rip+0x200bda]        # 0x601020
       0x400446 <exit@plt+6>:	push   0x1
                                                                  JUMP 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 objfile
      0x7ffff7dd5000     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   rbx
   0x00007ffff7dec7a1 <+1>:	mov    rbx,rsp
   0x00007ffff7dec7a4 <+4>:	and    rsp,0xffffffffffffffc0
.....
   0x00007ffff7dec815 <+117>:	call   0x7ffff7de4e40 <_dl_fixup> ; 调用dl_fixup函数完成对未定义符号的修复操作。
.....
   0x00007ffff7dec84e <+174>:	mov    rbx,QWORD PTR [rsp]
   0x00007ffff7dec852 <+178>:	add    rsp,0x18
   0x00007ffff7dec856 <+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],rsi
   0x400546 <main+15>:	lea    rdi,[rip+0xa7]        # 0x4005f4
   0x40054d <main+22>:	call   0x400430 <puts@plt>
=> 0x400552 <main+27>:	lea    rdi,[rip+0xa9]        # 0x400602
   0x400559 <main+34>:	call   0x400430 <puts@plt>
   0x40055e <main+39>:	mov    edi,0x0
   0x400563 <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,0x200be2
   0x400426:	jmp    QWORD PTR [rip+0x200be4]        # 0x601010
   0x40042c:	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   r13
       0x7ffff7a64a32 <_IO_puts+2>:	push   r12
       0x7ffff7a64a34 <_IO_puts+4>:	mov    r12,rdi
       0x7ffff7a64a37 <_IO_puts+7>:	push   rbp
                                                                  JUMP 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   r13
   0x00007ffff7a64a32 <+2>:	push   r12
   0x00007ffff7a64a34 <+4>:	mov    r12,rdi
......
   0x00007ffff7a64c21 <+497>:	add    rsp,0x80
   0x00007ffff7a64c28 <+504>:	mov    rdi,rsi
   0x00007ffff7a64c2b <+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   rbp
  400538:   48 89 e5                mov    rbp,rsp
  40053b:   48 83 ec 10             sub    rsp,0x10
  40053f:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
  400542:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  400546:   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,0x0
  400563:   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],edi
   0x400542 <main+11>:	mov    QWORD PTR [rbp-0x10],rsi
   0x400546 <main+15>:	lea    rdi,[rip+0xa7]        # 0x4005f4
=> 0x40054d <main+22>:	call   0x400430 <puts@plt>
   0x400552 <main+27>:	lea    rdi,[rip+0xa9]        # 0x400602
   0x400559 <main+34>:	call   0x400430 <puts@plt>
   0x40055e <main+39>:	mov    edi,0x0
   0x400563 <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,0x200baa
   0x400426:	jmp    QWORD PTR [rip+0x200bac]        # 0x600fd8
   0x40042c:	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   r13
       0x7ffff7a64a32 <_IO_puts+2>:	push   r12
       0x7ffff7a64a34 <_IO_puts+4>:	mov    r12,rdi
       0x7ffff7a64a37 <_IO_puts+7>:	push   rbp
                                                                  JUMP 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   r13
   0x00007ffff7a64a32 <+2>:	push   r12
   0x00007ffff7a64a34 <+4>:	mov    r12,rdi
..........
   0x00007ffff7a64c28 <+504>:	mov    rdi,rsi
   0x00007ffff7a64c2b <+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_full

Hex 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]        # 0x600fe8
   0x400446 <exit@plt+6>:	push   0x1
   0x40044b <exit@plt+11>:	jmp    0x400420
=> 0x400450 <_start>:	xor    ebp,ebp
   0x400452 <_start+2>:	mov    r9,rdx
   0x400455 <_start+5>:	pop    rsi
   0x400456 <_start+6>:	mov    rdx,rsp
   0x400459 <_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, value

Breakpoint 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.

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 《Linux二进制分析》是一本介绍分析Linux二进制文件的PDF文档。本书的目的是帮助读者了解Linux系统中二进制文件的结构和内容,以及如何进行分析和调试。这本书按照逻辑顺序分为多个章节,每个章节探讨了不同的主题。 第一章介绍了二进制文件的基础知识,包括二进制文件的类型、结构和常见的文件格式。通过学习这些基础知识,读者可以对Linux二进制文件有一个整体的了解。 第二章讨论了在Linux系统中进行二进制文件分析的工具和技术。这部分内容涵盖了命令行工具、调试器和反汇编器的使用方法,以及一些常见的二进制文件分析技术。 第三章以一个实际的案例为例,介绍了如何通过分析二进制文件来理解程序的功能和行为。这个案例涉及了静态分析和动态分析的技术,读者可以通过跟随案例学习如何应用这些技术。 第四章包含了一些高级的二进制文件分析技术,如符号动态执行、模糊测试和漏洞挖掘等。这些技术需要一定的专业知识和经验,但对于希望深入了解Linux二进制文件分析的读者来说,是非常有用的。 总的来说,这本《Linux二进制分析》PDF为读者提供了系统而全面的关于Linux二进制文件分析的知识。无论是初学者还是有经验的分析师,都可以从中获得有价值的信息和技巧。 ### 回答2: 《Linux二进制分析PDF》是一本介绍Linux二进制分析的电子书籍。通过对该书的阅读和学习,可以了解Linux操作系统中二进制文件的结构、特性和功能。 首先,该书会详细介绍Linux二进制文件的基本概念和核心组成部分。包括ELF(Executable and Linkable Format)格式的介绍,这是Linux系统中常见的二进制文件格式。通过了解ELF格式,可以深入理解可执行文件、共享库和目标文件的结构和功能。 其次,该书会介绍ELF文件的各个节(section)和段(segment)。节是ELF文件中存储数据和代码的逻辑块,而段是指逻辑上相关的节的集合。了解节和段的细节对于进行二进制分析非常重要,可以帮助我们理解程序的内部结构和运行机制。 此外,该书还会介绍动态链接(Dynamic Linking)和装载器(Loader)。动态链接是Linux中的一种链接方式,通过将共享库与可执行文件动态地链接在一起,提高了代码的复用性和运行效率。装载器是Linux系统中负责将可执行文件加载到内存并执行的组件。了解动态链接和装载器的工作原理,可以帮助我们理解程序运行的整个过程。 最后,该书还会介绍一些常见的二进制分析工具和技术。例如,调试器(Debugger)可以帮助我们追踪程序的执行过程查找bug。反汇编器(Disassembler)可以将二进制文件转换为可读的汇编代码。逆向工程技术可以帮助我们分析和修改二进制文件中的代码。 总之,《Linux二进制分析PDF》是一本系统介绍Linux二进制分析的书籍,通过阅读和学习这本书,我们可以了解Linux二进制文件的结构、特性和功能,以及进行二进制分析的常见工具和技术。这对于提高我们对Linux操作系统的理解和应用开发能力非常有帮助。 ### 回答3: "Linux二进制分析PDF" 是指一本关于Linux操作系统二进制分析的书籍或电子文档。 Linux二进制分析是指对Linux操作系统中的二进制文件进行深入研究和分析过程。这些二进制文件包括可执行文件、库文件、驱动程序和内核等。通过对这些文件逐个解析和分析,我们可以更好地理解它们的结构和功能,并且能够更好地进行调试、优化和开发工作。 "Linux二进制分析PDF" 这本书提供了关于如何进行Linux二进制分析的详细指导和实践经验。它可能包括以下方面的内容: 1. 二进制文件的格式:介绍二进制文件的基本结构和格式,如ELF(Executable and Linkable Format)等。讲解二进制文件中的各个段(section)和节(segment),以及文件头(header)和节头(section header)等信息。 2. 动态链接与装载:详细解释Linux中的动态链接和动态装载机制,如何解析和加载共享库,并将它们链接到可执行文件中。 3. 符号表与调试信息:讲解二进制文件中的符号表和调试信息,包括函数和变量的符号名、地址和类型等。以及如何利用这些信息进行调试和反汇编工作。 4. 反汇编和反编译:介绍如何将二进制文件转换成汇编代码,并进行反编译,还可以讲解一些常用的反汇编和反编译工具和技术。 5. 内核分析:讲解如何对Linux内核进行二进制分析和调试,包括内核模块、系统调用和驱动程序等。 通过学习和掌握这本书的内容,读者可以更深入地了解Linux系统的底层实现和机制,并且可以开展更高级、更复杂的开发和调试工作。当然,理解和运用这些知识需要具备一定的编程和计算机系统知识基础。希望这本"Linux二进制分析PDF"对想要深入学习Linux系统内部工作原理的读者有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值