目录
2.6 ECFS(extended core file snapshot,扩展核心文件快照)
Linux下二进制文件遵循一个统一的格式布局规范,这个二进制规范使得二进制在不同系统中可以拷贝、共享和运行,以及支持跨平台的交叉编译。二进制文件规范的制定,考虑了不同的硬件平台、各类操作系统和编译器的支持。实际上,有了该规范,才会产生操作系统内核的实现支持,才会有编译器的编译、链接器的链接、动态加载的支持。
二进制分析,是信息安全行业逆向工程的技术。在本系列文章中,我将根据参考一系列教材,介绍Linux下二进制相关的知识、实践相关的二进制分析工具。同时,从安全的角度,给出各类针对二进制进行漏洞、病毒的恶意攻击方法和防御方法,包括但不限于:二进制保护、进程追踪方法、病毒技术、二进制取证分析等等。
本系列文章将耗费接近半年的时间来完成,并在随后的时间里,继续添加一系列实践类的篇章。技术文章创作不易,希望对自己和读者都有所收益。欢迎关注和收藏,记得一键四连(关注、评论、点赞、收藏) 。
调试工具,不详述。做C/C++开发的小伙伴,都比较熟悉它。基于ptrace实现,实际有更多使用功能。后文会介绍到。
objdump
objdump可以对ELF二进制进行解析(反编译)的工具,对未被篡改过的二进制文件能进行信息dump。显示二进制中的相关信息。objdump依赖于ELF的Section Header,因此无法进行控制流分析,且如果没有正确的ELF Section Header,objdump无法正常工作。
ELF二进制中的Section,将在后面的章节根据设计规范详细介绍。
常使用的功能如下:
- #include
- #include
-
- 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
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
Assume input file is in format -O --output-target
Create an output file in format
上述命令将二进制文件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)
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 ...]
strace是系统调用的追踪工具,基于ptrace实现。strace在一个循环中使用PTRACE_SYSCALL来显示运行程序中所使用到的系统调用。在调试二进制运行,可以收集二进制运行机制。
1)追踪命令执行过程中的系统调用
例如,追踪ls命令:
2) 追踪特定进程系统调用
# strace -p 12100 -o 12100.strace
ltrace(library trace)是一个库追踪的工具,比较简洁。可以用来解析共享库,即一个程序的链接信息,并打印出用到的库函数。
该文件可以输出进程的映像,展现每个内存映射。内容包括:可执行文件、共享库、栈、堆、VDSO等信息。可以用这些信息快速解析一个进程的地址空间的分布情况。后文详细介绍。
Linux内核的动态核心文件,是以ELF核心文件的形式展示的原生内存转储,gdb可以对/proc/kcore来对内核进行调试和分析。第9章节将详细介绍。
该文件存在于几乎所有的Linux发行版中,包含了整个内核的所有符号。对内核黑客来说,非常重要。
与System.map类似,但区别是kallsyms是内核所属的/proc的一个入口,可动态更新。安装了新的LKM(Linux Kernel Module)之后,符号会自动添加到kallsyms中。其中包含了内核中的绝大多数的符号,通过配置内核编译选项CONFIG_KALLSYMS_ALL甚至可以包含内核中的全部符号。
Linux系统中,固定分配给设备以及内核代码段、数据段等的系统物理内存 地址。可以查看内核的code/text段、data段和bss段的地址范围如下:
- # cat /proc/iomem | grep Kernel
- ea8400000-ea9200e30 : Kernel code
- ea9200e31-eaa051cff : Kernel data
- eaa321000-eaa7fffff : Kernel bss
Extended Core File Snapshot(ECFS),是一项特殊的核心转储技术,专门为进程映像设计的高级取证分析。该软件代码参考:https://github.com/elfmaster/ecf
第8章节将详细介绍ECFS及其使用方法,可以用它进行高级内存取证分析。
动态链接/加载,是在程序链接、运行过程中的基本功能。在Linux中,有许多可以代替动态链接的方法,可以被黑客利用,从而攻击或分析运行中的应用程序。下面给出两种方法,后文会详细给出操作实践:
该环境变量可以设置一个指定库的路径,运行时动态链接时可以比其它库有更高的链接优先级。这就允许预加载库中的函数和符号,能够覆盖掉后续链接库中的函数和符号。利用它的技术,可以通过重定向共享库函数来进行运行时修复,或者绕过反调试代码,以及用作用户级的rootkit。
该环境变量能够通知程序加载器来展示程序运行时的辅助向量。辅助向量是放在程序栈(通过内核的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
链接器脚本的使用,可以极大提高分析的能力。**链接器脚本是由链接器linker进行解释,把程序划分成相应的节、内存和符号。默认的连机器脚本可以使用ld --verbose查看
ld链接程序有自己解释的一套语言,当有文件输入时,如可重定位的目标文件、共享库或头文件,ld链接器会用自己的语言来决定输出文件(如可执行文件)的组织方式。
例如:如果输出的是一个ELF可执行文件,链接器脚本能够决定该输出文件的布局,以及每个段中包含哪些Sections。
再如:.bss Section总是存放在data段的尾部,这也是由链接器脚本决定的。
对于编译链接过程的深入了解是很重要的,gcc依赖于链接器和其它程序来完成编译任务,在某些情况下,能够控制可执行文件的布局相当重要(制作病毒)。ld命令语言是一门非常深入的语言,非常值得学习和深究。
同时,在对可执行文件进行反编译时,普通段地址或者文件的其它部分有时会被修改,这就表明引入了一个自定义的链接器脚本。gcc通过使用-T选项来指定链接器脚本。 第5章节将详细介绍相应的链接器脚本的使用实践。
关于作者:
犇叔,浙江大学计算机科学与技术专业,研究生毕业,而立有余。先后在华为、阿里巴巴和字节跳动,从事技术研发工作,资深研发专家。主要研究领域包括虚拟化、分布式技术和存储系统(包括CPU与计算、GPU异构计算、分布式块存储、分布式数据库等领域)、高性能RDMA网络协议和数据中心应用、Linux内核等方向。
专业方向爱好:数学、科学技术应用
关注犇叔,期望为您带来更多科研领域的知识和产业应用。
内容坚持原创,坚持干货有料。坚持长期创作,关注犇叔不迷路