目录
2.6 ECFS(extended core file snapshot,扩展核心文件快照)
Linux下二进制文件遵循一个统一的格式布局规范,这个二进制规范使得二进制在不同系统中可以拷贝、共享和运行,以及支持跨平台的交叉编译。二进制文件规范的制定,考虑了不同的硬件平台、各类操作系统和编译器的支持。实际上,有了该规范,才会产生操作系统内核的实现支持,才会有编译器的编译、链接器的链接、动态加载的支持。
二进制分析,是信息安全行业逆向工程的技术。在本系列文章中,我将根据参考一系列教材,介绍Linux下二进制相关的知识、实践相关的二进制分析工具。同时,从安全的角度,给出各类针对二进制进行漏洞、病毒的恶意攻击方法和防御方法,包括但不限于:二进制保护、进程追踪方法、病毒技术、二进制取证分析等等。
本系列文章将耗费接近半年的时间来完成,并在随后的时间里,继续添加一系列实践类的篇章。技术文章创作不易,希望对自己和读者都有所收益。欢迎关注和收藏,记得一键四连(关注、评论、点赞、收藏) 。
1. Linux工具
1.1 gdb调试器
调试工具,不详述。做C/C++开发的小伙伴,都比较熟悉它。基于ptrace实现,实际有更多使用功能。后文会介绍到。
1.2 binutils工具箱
-
objdump
objdump可以对ELF二进制进行解析(反编译)的工具,对未被篡改过的二进制文件能进行信息dump。显示二进制中的相关信息。objdump依赖于ELF的Section Header,因此无法进行控制流分析,且如果没有正确的ELF Section Header,objdump无法正常工作。
ELF二进制中的Section,将在后面的章节根据设计规范详细介绍。
常使用的功能如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello binar\n");
}
# gcc -o a a.c
1) 查看二进制中的所有符号(静态和动态符号表)
# objdump -tT a
2) 显示二进制中的Sections(header)
# objdump -h a
3)显示二进制中特定section的信息
配合-S可以尽可能显示源代码。
# objdump -j .data -s a
4) 显示二进制中所有section的数据或代码
# objdump -D a
5) 显示二进制中可执行代码section的部分
# objdump -d a
-
objcopy
objcopy是公认的正统分析和修改ELF二进制的工具,支持将.elf格式文件转换为.hex文件,支持将二进制转换成目标文件(.o文件)。用来分析和修改任意类型的ELF目标文件,可修改,也可将ELF Section复制到ELF二进制中。例如:将一个ELF目标文件复制到另一个文件中使用如下命令:
objcopy --only-section=.data <infile> <outfile>
1) 生成ELF二进制
# dd if=/dev/urandom of=foo.bin bs=1K count=1 oflag=direct
# objcopy -I binary -O elf64-x86-64 foo.bin foo.o
# objdump -h foo.o
foo.o: file format elf64-little
Sections:
Idx Name Size VMA LMA File off Algn
0 .data 00000400 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, DATA
# objdump -j .data -s foo.o > foo2.bin
Usage: objcopy [option(s)] in-file [out-file]
支持的目标有: elf64-x86-64 elf32-i386 elf32-iamcu elf32-x86-64 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big pe-x86-64 pe-bigobj-x86-64 pe-i386 plugin srec symbolsrec verilog tekhex binary ihex
选项:
-I --input-target <bfdname> Assume input file is in format <bfdname>
-O --output-target <bfdname> Create an output file in format <bfdname>
上述命令将二进制文件foo.bin,转换为foo.o的目标文件。可以看到foo.bin内容与foo.o的.data section的内容是一致的。 默认情况下,二进制数据放在目标文件的.data Section下。 可以通过--rename-section选项,更改到其它Section。如下:
# objcopy -I binary -O elf64-x86-64 \
--rename-section .data=.rodata,alloc,load,readonly foo.bin foo2.o
# objdump -h foo2.o
foo2.o: file format elf64-little
Sections:
Idx Name Size VMA LMA File off Algn
0 .rodata 00000400 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
2)通过添加Section嵌入数据到二进制对象中
例如,有如下的目标对象:
# echo 'int main() { puts("Hello world\n"); }' | gcc -x c - -c -o hello.o
通过objdump把foo.bin的二进制添加到.mydata的Section中,并编译成可执行文件:
# objcopy --add-section .mydata=foo.bin \
--set-section-flags .mydata=noload,readonly hello.o hello2.o
# gcc hello2.o -o hello
# ./hello
Hello world
# objdump -h hello | grep data
15 .rodata 00000011 00000000000006e0 00000000000006e0 000006e0 2**2
22 .data 00000010 0000000000201000 0000000000201000 00001000 2**3
25 .mydata 00000400 0000000000000000 0000000000000000 00001039 2**0
通过 objdump -sj .mydata hello命令可以看到二进制内容与foo.bin完全一致。
QA: objcopy --only-section=.mydata -O binary hello2.o hello.bin
-
readelf
readelf是用来解析ELF二进制文件的强大工具。在进行二进制分析(黑客攻击)时,与objcopy一样,是一个利器,可以收集目标文件相关的所有特定于ELF的数据,为后续的反编译准备信息。常用如下:
1)查询ELF文件头
# readelf -h hello
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: 7496 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
2)查询ELF符号表
3) 查询ELF二进制程序头
# readelf -l hello
Elf file type is DYN (Shared object file)
Entry point 0x530
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
0x0000000000000838 0x0000000000000838 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 0x00000000000006f4 0x00000000000006f4 0x00000000000006f4
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
4) -S查询Section headers表
5)-d查询动态Section部分
6)-r查询可重定位入口
7) -e查询ELF文件Header数据(等同于-l -h -S)
1.3 hexdump
hexdump将文件看作二进制,并以十六进制dump显示。
该工具主要能调整dump的位置,以及显示的格式。
# hexdump -h
hexdump: invalid option -- 'h'
usage: hexdump [-bcCdovx] [-e fmt] [-f fmt_file] [-n length]
[-s skip] [file ...]
hd [-bcdovx] [-e fmt] [-f fmt_file] [-n length]
[-s skip] [file ...]
1.4 strace
strace是系统调用的追踪工具,基于ptrace实现。strace在一个循环中使用PTRACE_SYSCALL来显示运行程序中所使用到的系统调用。在调试二进制运行,可以收集二进制运行机制。
1)追踪命令执行过程中的系统调用
例如,追踪ls命令:
2) 追踪特定进程系统调用
# strace -p 12100 -o 12100.strace
1.5 ltrace
ltrace(library trace)是一个库追踪的工具,比较简洁。可以用来解析共享库,即一个程序的链接信息,并打印出用到的库函数。
2. 设备和文件
2.1 /proc/${pid}/maps
该文件可以输出进程的映像,展现每个内存映射。内容包括:可执行文件、共享库、栈、堆、VDSO等信息。可以用这些信息快速解析一个进程的地址空间的分布情况。后文详细介绍。
2.2 /proc/kcore
Linux内核的动态核心文件,是以ELF核心文件的形式展示的原生内存转储,gdb可以对/proc/kcore来对内核进行调试和分析。第9章节将详细介绍。
2.3 /boot/System.map
该文件存在于几乎所有的Linux发行版中,包含了整个内核的所有符号。对内核黑客来说,非常重要。
2.4 /boot/kallsyms
与System.map类似,但区别是kallsyms是内核所属的/proc的一个入口,可动态更新。安装了新的LKM(Linux Kernel Module)之后,符号会自动添加到kallsyms中。其中包含了内核中的绝大多数的符号,通过配置内核编译选项CONFIG_KALLSYMS_ALL甚至可以包含内核中的全部符号。
2.5 /proc/iomem
Linux系统中,固定分配给设备以及内核代码段、数据段等的系统物理内存 地址。可以查看内核的code/text段、data段和bss段的地址范围如下:
# cat /proc/iomem | grep Kernel
ea8400000-ea9200e30 : Kernel code
ea9200e31-eaa051cff : Kernel data
eaa321000-eaa7fffff : Kernel bss
2.6 ECFS(extended core file snapshot,扩展核心文件快照)
Extended Core File Snapshot(ECFS),是一项特殊的核心转储技术,专门为进程映像设计的高级取证分析。该软件代码参考:https://github.com/elfmaster/ecf
第8章节将详细介绍ECFS及其使用方法,可以用它进行高级内存取证分析。
3. 链接器
动态链接/加载,是在程序链接、运行过程中的基本功能。在Linux中,有许多可以代替动态链接的方法,可以被黑客利用,从而攻击或分析运行中的应用程序。下面给出两种方法,后文会详细给出操作实践:
3.1 LD_PRELOAD环境变量
该环境变量可以设置一个指定库的路径,运行时动态链接时可以比其它库有更高的链接优先级。这就允许预加载库中的函数和符号,能够覆盖掉后续链接库中的函数和符号。利用它的技术,可以通过重定向共享库函数来进行运行时修复,或者绕过反调试代码,以及用作用户级的rootkit。
3.2 LD_SHOW_AUXV环境变量
该环境变量能够通知程序加载器来展示程序运行时的辅助向量。辅助向量是放在程序栈(通过内核的ELF常规加载方式)上的信息,其附带了传递给动态链接器的程序相关的特定信息。这些信息对于反编译和调试来说非常有用。例如,想要获取进程映像VDSO页的内存地址,就需要查询AT_SYSINFO。
第2章节将介绍辅助向量,如下带有LD_SHOW_AUXV辅助向量的例子:
# LD_SHOW_AUXV=1 whoami
AT_SYSINFO_EHDR: 0x7ffd7af17000
AT_HWCAP: bfebfbff
AT_PAGESZ: 4096
AT_CLKTCK: 100
AT_PHDR: 0x5615bf740040
AT_PHENT: 56
AT_PHNUM: 9
AT_BASE: 0x7f4338e2a000
AT_FLAGS: 0x0
AT_ENTRY: 0x5615bf741a10
AT_UID: 0
AT_EUID: 0
AT_GID: 0
AT_EGID: 0
AT_SECURE: 0
AT_RANDOM: 0x7ffd7ae83239
AT_HWCAP2: 0x0
AT_EXECFN: /usr/bin/whoami
AT_PLATFORM: x86_64
root
3.3 链接器脚本
链接器脚本的使用,可以极大提高分析的能力。**链接器脚本是由链接器linker进行解释,把程序划分成相应的节、内存和符号。默认的连机器脚本可以使用ld --verbose查看
ld链接程序有自己解释的一套语言,当有文件输入时,如可重定位的目标文件、共享库或头文件,ld链接器会用自己的语言来决定输出文件(如可执行文件)的组织方式。
例如:如果输出的是一个ELF可执行文件,链接器脚本能够决定该输出文件的布局,以及每个段中包含哪些Sections。
再如:.bss Section总是存放在data段的尾部,这也是由链接器脚本决定的。
对于编译链接过程的深入了解是很重要的,gcc依赖于链接器和其它程序来完成编译任务,在某些情况下,能够控制可执行文件的布局相当重要(制作病毒)。ld命令语言是一门非常深入的语言,非常值得学习和深究。
同时,在对可执行文件进行反编译时,普通段地址或者文件的其它部分有时会被修改,这就表明引入了一个自定义的链接器脚本。gcc通过使用-T选项来指定链接器脚本。 第5章节将详细介绍相应的链接器脚本的使用实践。
关于作者:
犇叔,浙江大学计算机科学与技术专业,研究生毕业,而立有余。先后在华为、阿里巴巴和字节跳动,从事技术研发工作,资深研发专家。主要研究领域包括虚拟化、分布式技术和存储系统(包括CPU与计算、GPU异构计算、分布式块存储、分布式数据库等领域)、高性能RDMA网络协议和数据中心应用、Linux内核等方向。
专业方向爱好:数学、科学技术应用
关注犇叔,期望为您带来更多科研领域的知识和产业应用。
内容坚持原创,坚持干货有料。坚持长期创作,关注犇叔不迷路