Elf文件格式与hook

12 minute read

文件格式介绍

示例文件 main.c

#include <unistd.h>
#include <stdio.h>

int main(int argc, char* argv[]){
  printf("this is elf test");
  return 0;
}
gcc -c main.c -o main.o
gcc main.c -o main.out
gcc -fPIC -shared main.c -o main.so
  • .o:c/.c++文件编译出的obj文件
  • .so:动态库文件
  • .out:可执行程序

ELF Header

readelf -h main.so

ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x530
Start of program headers: 64 (bytes into file)
Start of section headers: 6104 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 7
Size of section headers: 64 (bytes)
Number of section headers: 28
Section header string table index: 27

Section Header Table

readelf --sections main.o

There are 13 section headers, starting at offset 0x2e0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000027 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000230
0000000000000030 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000067
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000067
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 00000067
0000000000000011 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000078
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000a4
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000a8
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000260
0000000000000018 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 000000e0
0000000000000120 0000000000000018 11 9 8
[11] .strtab STRTAB 0000000000000000 00000200
000000000000002a 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000278
0000000000000061 0000000000000000 0 0 1
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)

Program Header Table

readelf -l main.out


Elf file type is DYN (Shared object file)
Entry point 0x540
There are 9 program headers, starting at offset 64

Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R 0x8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000860 0x0000000000000860 R E 0x200000
LOAD 0x0000000000000db8 0x0000000000200db8 0x0000000000200db8
0x0000000000000258 0x0000000000000260 RW 0x200000
DYNAMIC 0x0000000000000dc8 0x0000000000200dc8 0x0000000000200dc8
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x0000000000000254 0x0000000000000254 0x0000000000000254
0x0000000000000044 0x0000000000000044 R 0x4
GNU_EH_FRAME 0x0000000000000718 0x0000000000000718 0x0000000000000718
0x000000000000003c 0x000000000000003c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000000db8 0x0000000000200db8 0x0000000000200db8
0x0000000000000248 0x0000000000000248 R 0x1

Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn
.rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .dynamic .got .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .dynamic .got
  • .dynstr:保存了所有的字符串常量信息。
  • .dynsym:保存了符号(symbol)的信息(符号的类型、起始地址、大小、符号名称在 .dynstr 中的索引编号等)。函数也是一种符号。
  • .text:程序代码经过编译后生成的机器指令。
  • .dynamic:供动态链接器使用的各项信息,记录了当前 ELF 的外部依赖,以及其他各个重要 section 的起始位置等信息。
  • .got:Global Offset Table。用于记录外部调用的入口地址。动态链接器(linker)执行重定位(relocate)操作时,这里会被填入真实的外部调用的绝对地址。
  • .plt:Procedure Linkage Table。外部调用的跳板,主要用于支持 lazy binding 方式的外部调用重定位。(Android 目前只有 MIPS 架构支持 lazy binding)
  • .rel.plt:对外部函数直接调用的重定位信息。
  • .rel.dyn:除 .rel.plt 以外的重定位信息。(比如通过全局函数指针来调用外部函数)

Python解析脚本

read_elf.py

GOT & PLT & REL的作用和关系

基本概念

  • 装载:将程序从磁盘文件形态加载到进程的内存中
  • 重定位:寻找本程序中使用的函数/变量的地址
  • 动态链接:装载+重定位的整个过程
  • 链接器:用于完成链接动作的程序,Android下固定为”/system/bin/linker”

在编译时,如果本程序中使用了其他动态库中的函数或变量,编译器无法知晓他们在内存中的地址,所以编译器只能在调用处留下标记,告诉链接器这些地方需要重定位到真正的内存地址。在运行时,链接器遍历这些需要重定位的地方,找到真正的内存地址。

REL(重定位表)的作用

本程序装载进内存时,通过自己的rel表项告诉链接器,哪些地方需要重定位

GOT(全局偏移表)的作用

用来存放链接器找到的 函数/变量地址

在Linux下,GOT被拆分成”.got”和”.got.plt”2个表。其中”.got”用来保存全局变量引用的地址,”.got.plt”用来保存函数引用的地址。此外,”.got.plt”的前三项保留,用于存放特殊的数据结构地址:第一项保存的是”.dynamic”动态节区的地址;第二项保存的是本模块ID,指向已经加载的共享库的链表地址(前面提到加载的共享库会形成一个链表);第三项保存的是_dlruntime resolve函数的地址(用于查找指定模块下的特定方法).

PLT(过程链接表)的作用

读取函数对应GOT表中的值,即函数的内存地址

PLT[0]内容固定,跳转到GOT[2]即_dlruntime resolve函数,查找特定模块下的指定函数,并填充到GOT表。而其他PLT普通表项则相当于一个函数的桩函数(stub),通过引用GOT表中函数的绝对地址,来把控制转移到实际的函数

Android Native Hook

导入表hook

之前提到,在第一次调用函数时,PLT会执行_dlruntime resolve,将函数地址存在GOT表,后续调用就直接调用GOT表中的地址。所以导入表hook的原理时替换GOT表中外部函数地址。较为有名的开源库:xHook

导入表hook的特点:

  1. 即时生效
  2. 只能修改当前应用进程下的GOT表中外部符号地址

导出表hook

.dynsym(符号表)有说明该符号为导入符号还是导出符号。在动态链接时,如果发现符号为外部符号,会去解析相应的依赖库,获取地址。所以,导出表hook的原理就是修改要hook的函数所在so中的符号表中的值,并对其进行替换,其他库在调用时就会调用替换后的函数。

导出表hook的特点

  1. 不能即时生效,需要将修改过的函数地址重新导入GOT表
  2. 对所有用到该函数的动态库有效
  3. 只支持hook符号表导出的函数

inline hook

Inline hook其实就是直接修改函数指令,对代码的调用流程进行替换,从而把程序执行流程引向用户需要的功能代码中去,它的基本流程如下所示:

  1. 在想要Hook的目标代码处备份下面的几条指令,然后插入跳转指令,把程序流程转移到一个stub段上去。
  2. 在stub代码段上先把所有寄存器的状态保存好,并调用用户自定义的Hook功能函数,然后把所有寄存器的状态恢复并跳转到备份代码处。
  3. 在备份代码处把当初备份的那几条指令都执行一下,然后跳转到当初备份代码位置的下面接着执行程序。

inline hook的特点:

  1. 汇编级,不仅能处理导入导出函数,能覆盖动态库内的全部可执行代码
  2. 灵活性强,单次或批量均可

参考

ELF文件结构详解

聊聊Linux动态链接中的PLT和GOT

安卓SO中GOT REL PLT 作用与关系

Android PLT hook 概述

Android Native Hook技术路线概述

ELF Hook总结与代码实现

Updated: