二进制分析属于信息安全业界逆向工程中的一种技术,经过利用可执行的机器代码(二进制)来分析应用程序的控制结构和运行方式,它有助于信息安全从业人员更好地分析各类漏洞、病毒以及恶意软件,从而找到相应的解决方案。html
在本文中,你将会更加深刻地了解程序如何映射到磁盘并加载到内存中。程序执行的内部逻辑比较复杂,对于有抱负的二进制黑客、逆向工程师或者普通的程序员来讲,对二进制格式的理解将会是很是宝贵的知识财富。在Linux中,程序就是以ELF二进制的格式执行的。linux
像许多Linux反编译工程师同样,我也是先了解ELF的说明规范,而后把学到的内容以一种创造性的方式进行应用,经过这样的方式来进行ELF的学习。程序员
一个ELF文件能够被标记为如下几种类型之一。算法
ET_NONE
:未知类型。这个标记代表文件类型不肯定,或者还未定义。编程
ET_REL
:重定位文件。ELF类型标记为relocatable意味着该文件被标记为了一段可重定位的代码,有时也称为目标文件。可重定位目标文件一般是还未被连接到可执行程序的一段位置独立的代码(position
independent code)。在编译完代码以后一般能够看到一个.o
格式的文件,这种文件包含了建立可执行文件所须要的代码和数据。数组
ET_EXEC
:可执行文件。ELF类型为executable,代表这个文件被标记为可执行文件。这种类型的文件也称为程序,是一个进程开始执行的入口。安全
ET_DYN
:共享目标文件。ELF类型为dynamic,意味着该文件被标记为了一个动态的可连接的目标文件,也称为共享库。这类共享库会在程序运行时被装载并连接到程序的进程镜像中。数据结构
ET_CORE
:核心文件。在程序崩溃或者进程传递了一个SIGSEGV信号(分段违规)时,会在核心文件中记录整个进程的镜像信息。可使用GDB读取这类文件来辅助调试并查找程序崩溃的缘由。异步
使用readelf–h
命令查看ELF文件,能够看到原始的ELF文件头。ELF文件头从文件的0偏移量开始,是除了文件头以后剩余部分文件的一个映射。文件头主要标记了ELF类型、结构和程序开始执行的入口地址,并提供了其余ELF头(节头和程序头)的偏移量,稍后会细讲。一旦理解了节头和程序头的含义,就容易理解文件头了。经过查看Linux的ELF(5)手册,能够了解ELF头部的结构:ide
#define EI_NIDENT 16
typedef struct{
unsigned char e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
ElfN_Addr e_entry;
ElfN_Off e_phoff;
ElfN_Off e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
}ElfN_Ehdr;复制代码
在本文的后续内容中,咱们会用一个简单的C程序来展现如何利用上面结构中的字段映射一个ELF文件。咱们先继续介绍现存的其余类型的ELF头。
ELF程序头是对二进制文件中段的描述,是程序装载必需的一部分。段(segment)是在内核装载时被解析的,描述了磁盘上可执行文件的内存布局以及如何映射到内存中。能够经过引用原始ELF头中名为e_phoff
(程序头表偏移量)的偏移量来获得程序头表,如前面ElfN_Ehdr
结构中所示。
下面讨论5种常见的程序头类型。程序头描述了可执行文件(包括共享库)中的段及其类型(为哪一种类型的数据或代码而保留的段)。首先,咱们来看一下Elf32_Phdr
的结构,它构成了32位ELF可执行文件程序头表的一个程序头条目。
在本书的后续内容中有时还会引用Phdr的程序头结构。
下面是Elf32_Phdr
结构体:
typedef struct {
uint32_t p_type; (segment type)
Elf32_Off p_offset; (segment offset)
Elf32_Addr p_vaddr; (segment virtual address)
Elf32_Addr p_paddr; (segment physical address)
uint32_t p_filesz; (size of segment in the file)
uint32_t p_memsz; (size of segment in memory)
uint32_t p_flags; (segment flags, I.E execute|read|read)
uint32_t p_align; (segment alignment in memory)
} Elf32_Phdr;复制代码
一个可执行文件至少有一个PT_LOAD
类型的段。这类程序头描述的是可装载的段,也就是说,这种类型的段将被装载或者映射到内存中。
例如,一个须要动态连接的ELF可执行文件一般包含如下两个可装载的段(类型为PT_LOAD
):
存放程序代码的text段;
存放全局变量和动态连接信息的data段。
上面的两个段将会被映射到内存中,并根据p_align
中存放的值在内存中对齐。建议读者阅读一下Linux的ELF手册,以便理解Phdr结构体中全部变量的含义,这些变量描述了段在文件和内存中的布局。
程序头主要描述了程序执行时在内存中的布局。本章稍后会使用Phdr来讲明什么是程序头,以及如何在反编译软件中使用程序头。
一般将text段(也称代码段)的权限设置为PF_X | PF_R
(读和可执行)。
一般将data段的权限设置为PF_W | PF_R
(读和写)。
感染了千面人病毒(polymorphic virus)文件的text段或data段的权限可能会被修改,如经过在程序头的段标记(p_flags
)处增长PF_W
标记来修改text段的权限。
动态段是动态连接可执行文件所特有的,包含了动态连接器所必需的一些信息。在动态段中包含了一些标记值和指针,包括但不限于如下内容:
运行时须要连接的共享库列表;
全局偏移表(GOT)的地址
——ELF动态连接部分(2.6节)会讨论;
重定位条目的相关信息。
表2-1是完整的标记名列表。
表2-1
|
|
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
动态段包含了一些结构体,在这些结构体中存放着与动态连接相关的信息。d_tag
成员变量控制着d_un
的含义。
32位ELF文件的动态段结构体以下:
typedef struct{
Elf32_Sword d_tag;
union{
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
extern Elf32_Dyn _DYNAMIC[];复制代码
本文稍后会继续对动态连接进行更深刻的探讨。
PT_NOTE
类型的段可能保存了与特定供应商或者系统相关的附加信息。下面是标准ELF规范中对PT_NOTE
的定义:
有时供应商或者系统构建者须要在目标文件上标记特定的信息,以便于其余程序对一致性、兼容性等进行检查。SHT_NOTE
类型的节(section)和PT_NOTE
类型的程序头元素就能够用于这一目的。节或者程序头元素中的备注信息能够有任意数量的条目,每一个条目都是一个4字节的目标处理器格式的数组。下面的标签能够解释备注信息的组织结构,不过这些标签并非规范中的内容。
比较有意思的一点:事实上,这一段只保存了操做系统的规范信息,在可执行文件运行时是不须要这个段的(由于系统会假设一个可执行文件是本地的),这个段成了很容易被病毒感染的一个地方。因为篇幅限制,就不具体介绍了。更多NOTE段病毒感染相关的信息能够从vxheavens.com/
了解到。
lib/vhe06.html
PT_INTERP段只将位置和大小信息存放在一个以null为终止符的字符串中,是对程序解释器位置的描述。例如,/lib/linux-ld.so.2
通常是指动态连接器的位置,也即程序解释器的位置。
PT_PHDR段保存了程序头表自己的位置和大小。Phdr表保存了全部的Phdr对文件(以及内存镜像)中段的描述信息。
能够查阅ELF(5)手册或者ELF规范文档来查看全部的Phdr类型。咱们已经介绍了一些最经常使用的Phdr类型,其中一些对程序执行相当重要,有一些在反编译时会常常用到。
可使用readelf–l <filename>
命令查看文件的Phdr表:
Elf file type is EXEC (Executable file) Entry point 0x8049a30 There are 9 program headers, starting at offset 52 Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x1622c 0x1622c R E 0x1000
LOAD 0x016ef8 0x0805fef8 0x0805fef8 0x003c8 0x00fe8 RW 0x1000
DYNAMIC 0x016f0c 0x0805ff0c 0x0805ff0c 0x000e0 0x000e0 RW 0x4
NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x016104 0x0805e104 0x0805e104 0x0002c 0x0002c R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
GNU_RELRO 0x016ef8 0x0805fef8 0x0805fef8 0x00108 0x00108 R 0x1复制代码
从上面的片断中,能够看到可执行程序的入口点,还有刚刚讨论的不一样段的类型。注意看中间部分的PT_LOAD
段,从最左边的偏移量到最右边的权限标识和对齐标识。
text段是可读可执行的,data段是可读可写的,这两个段都有0x1000
(4096)的对齐标识,恰好是32位可执行文件一页的大小,该标识用于在程序装载时对齐。
本文摘自于《Linux二进制分析》
本书是目前为止惟一一本剖析Linux ELF工做机制的图书,共分为9章,其内容涵盖了Linux环境和相关工具、ELF二进制格式、Linux进程追踪、ELF病毒技术、Linux二进制保护、Linux中的ELF二进制取证分析、进程内存取证分析、扩展核心文件快照技术、Linux/proc/kcore分析等。
本书适合具备必定的Linux操做知识,且了解C语言编程技巧的信息安全从业人员阅读。
目 录
第1章 Linux环境和相关工具1
1.1 Linux工具1
1.1.1 GDB2
1.1.2 GNU binutils中的objdump2
1.1.3 GNU binutils中的objcopy3
1.1.4 strace3
1.1.5 ltrace4
1.1.6 基本的ltrace命令4
1.1.7 ftrace4
1.1.8 readelf4
1.1.9 ERESI——ELF反编译系统接口5
1.2 有用的设备和文件6
1.2.1 /proc//maps6
1.2.2 /proc/kcore6
1.2.3 /boot/System.map6
1.2.4 /proc/kallsyms7
1.2.5 /proc/iomem7
1.2.6 ECFS7
1.3 连接器相关环境指针7
1.3.1 LD_PRELOAD环境变量8
1.3.2 LD_SHOW_AUXV环境变量8
1.3.3 连接器脚本9
1.4 总结10
第2章 ELF二进制格式11
2.1 ELF文件类型12
2.2 ELF程序头14
2.2.1 PT_LOAD14
2.2.2 PT_DYNAMIC——动态段的Phdr15
2.2.3 PT_NOTE17
2.2.4 PT_INTERP17
2.2.5 PT_PHDR17
2.3 ELF节头18
2.3.1 .text节20
2.3.2 .rodata节20
2.3.3 .plt节21
2.3.4 .data节21
2.3.5 .bss节21
2.3.6 .got.plt节21
2.3.7 .dynsym节21
2.3.8 .dynstr节22
2.3.9 .rel.*节22
2.3.10 .hash节22
2.3.11 .symtab节22
2.3.12 .strtab节23
2.3.13 .shstrtab节23
2.3.14 .ctors和.dtors节23
2.4 ELF符号27
2.4.1 st_name28
2.4.2 st_value28
2.4.3 st_size28
2.4.4 st_other28
2.4.5 st_shndx29
2.4.6 st_info29
2.5 ELF重定位34
2.6 ELF动态连接43
2.6.1 辅助向量44
2.6.2 了解PLT/GOT46
2.6.3 重温动态段49
2.7 编码一个ELF解析器52
2.8 总结55
第3章 Linux进程追踪57
3.1 ptrace的重要性57
3.2 ptrace请求58
3.3 进程寄存器状态和标记60
3.4 基于ptrace的调试器示例61
3.5 ptrace调试器67
3.6 高级函数追踪软件75
3.7 ptrace和取证分析75
3.8 进程镜像重建77
3.8.1 重建进程到可执行文件的挑战78
3.8.2 重建可执行文件的挑战78
3.8.3 添加节头表79
3.8.4 重建过程算法79
3.8.5 在32位测试环境中使用Quenya重建进程81
3.9 使用ptrace进行代码注入83
3.10 简单的例子演示复杂的过程91
3.11 code_inject工具演示92
3.12 ptrace反调试技巧92
3.13 总结94
第4章 ELF病毒技术——Linux/UNIX病毒95
4.1 ELF病毒技术96
4.2 设计ELF病毒面临的挑战97
4.2.1 寄生代码必须是独立的97
4.2.2 字符串存储的复杂度99
4.2.3 寻找存放寄生代码的合理空间100
4.2.4 将执行控制流传给寄生代码100
4.3 ELF病毒寄生代码感染方法101
4.3.1 Silvio填充感染101
4.3.2 逆向text感染106
4.3.3 data段感染108
4.4 PT_NOTE到PT_LOAD转换感染110
4.5 感染控制流112
4.5.1 直接PLT感染113
4.5.2 函数蹦床(function trampolines)113
4.5.3 重写.ctors/.dtors函数指针114
4.5.4 GOT感染或PLT/GOT重定向115
4.5.5 感染数据结构115
4.5.6 函数指针重写115
4.6 进程内存病毒和rootkits——远程代码注入技术115
4.6.1 共享库注入116
4.6.2 text段代码注入120
4.6.3 可执行文件注入120
4.6.4 重定位代码注入——ET_REL注入120
4.7 ELF反调试和封装技术121
4.7.1 PTRACE_TRACEME技术121
4.7.2 SIGTRAP处理技术122
4.7.3 /proc/self/status技术122
4.7.4 代码混淆技术123
4.7.5 字符串表转换技术124
4.8 ELF病毒检测和杀毒124
4.9 总结126
第5章 Linux二进制保护127
5.1 ELF二进制加壳器127
5.2 存根机制和用户层执行128
5.3 保护器存根的其余用途133
5.4 现存的ELF二进制保护器133
5.4.1 DacryFile——Grugq于2001年发布134
5.4.2 Burneye——Scut于2002年发布134
5.4.3 Shiva——Neil Mehta和Shawn Clowes于2003年发布135
5.4.4 May's Veil——Ryan O'Neill于2014年发布136
5.5 下载Maya保护的二进制文件142
5.6 二进制保护中的反调试142
5.7 防模拟技术143
5.7.1 经过系统调用检测模拟144
5.7.2 检测模拟的CPU不一致144
5.7.3 检测特定指令之间的时延144
5.8 混淆方法145
5.9 保护控制流完整性145
5.9.1 基于ptrace的攻击145
5.9.2 基于安全漏洞的攻击146
5.10 其余资源147
5.11 总结147
第6章 Linux下的ELF二进制取证分析149
6.1 检测入口点修改技术150
6.2 检测其余形式的控制流劫持154
6.2.1 修改.ctors/.init_array节154
6.2.2 检测PLT/GOT钩子155
6.2.3 检测函数蹦床158
6.3 识别寄生代码特征159
6.4 检查动态段是否被DLL注入161
6.5 识别逆向text填充感染164
6.6 识别text段填充感染166
6.7 识别被保护的二进制文件170
6.8 IDA Pro175
6.9 总结175
第7章 进程内存取证分析177
7.1 进程内存布局178
7.1.1 可执行文件内存映射179
7.1.2 程序堆179
7.1.3 共享库映射180
7.1.4 栈、VDSO和vsyscall180
7.2 进程内存感染181
7.2.1 进程感染工具181
7.2.2 进程感染技术182
7.3 检测ET_DYN注入184
7.3.1 Azazel:用户级rootkit检测184
7.3.2 映射出进程的地址空间184
7.3.3 查找栈中的LD_PRELOAD187
7.3.4 检测PLT/GOT钩子188
7.3.5 ET_DYN注入内部原理190
7.3.6 操纵VDSO194
7.3.7 共享目标文件加载195
7.3.8 检测.so注入的启发方法196
7.3.9 检测PLT/GOT钩子的工具197
7.4 Linux ELF核心文件198
7.5 总结204
第8章 ECFS——扩展核心文件快照技术205
8.1 历史205
8.2 ECFS原理206
8.3 ECFS入门206
8.3.1 将ECFS嵌入到核心处理器中207
8.3.2 在不终止进程的状况下使用ECFS快照208
8.4 libecfs——解析ECFS文件的库208
8.5 readecfs工具209
8.6 使用ECFS检测被感染的进程210
8.6.1 感染主机进程210
8.6.2 捕获并分析ECFS快照211
8.6.3 使用readecfs提取寄生代码215
8.6.4 Azazel用户级rootkit分析216
8.7 ECFS参考指南224
8.7.1 ECFS符号表重建225
8.7.2 ECFS节头226
8.7.3 使用ECFS文件做为常规的核心文件229
8.7.4 libecfs API的使用229
8.8 使用ECFS恢复中断的进程230
8.9 了解更多ECFS相关内容231
8.10 总结232
第9章 Linux/proc/kcore分析233
9.1 Linux内核取证分析和rootkit233
9.2 没有符号的备份vmlinux234
9.3 探索/proc/kcore和GDB236
9.4 直接修改sys_call_table237
9.4.1 检测sys_call_table修改238
9.4.2 内核函数蹦床238
9.4.3 函数蹦床示例239
9.4.4 检测函数蹦床241
9.4.5 检测中断处理器修复243
9.5 Kprobe rootkit243
9.6 调试寄存器rootkit——DRR244
9.7 VFS层rootkit244
9.8 其余内核感染技术245
9.9 vmlinux和.altinstructions修补245
9.9.1 .altinstructions和.altinstr_replace246
9.9.2 arch/x86/include/asm/alternative.h代码片断246
9.9.3 使用textify验证内核代码完整性247
9.9.4 使用textify检查sys_call_table247
9.10 使用taskverse查看隐藏进程248
9.11 感染的LKM——内核驱动249
9.11.1 方法一:感染LKM文件——符号劫持249
9.11.2 方法二:感染LKM文件——函数劫持249
9.11.3 检测被感染的LKM250
9.12 /dev/kmem和/dev/mem250
9.12.1 /dev/mem251
9.12.2 FreeBSD /dev/kmem251
9.13 K-ecfs ——内核ECFS251
9.14 内核黑客工具252
9.14.1 通用的逆向工程和调试253
9.14.2 高级内核劫持/调试接口253
9.14.3 本章提到的论文253
9.15 总结254
本文禁止转载,如需转载,请联系公众号异步社区