扩展核心文件快照(ECFS)技术是一种插入到 Linux 核心处理程序的软件,它可以创建专门设计的进程内存快照,专门针对进程内存取证。 大多数人都不知道如何解析进程映像,更不用说如何检查异常了。 即使对专家来说,查看进程图像并检测感染或恶意软件也是一项艰巨的任务。
在 ECFS 之前,除了使用核心文件之外,没有真正的进程映像快照标准,这些核心文件可以使用大多数 Linux 发行版附带的gcore脚本按需创建。 正如前一章所简要讨论的,常规的核心文件对于过程取证分析并不是特别有用。 这就是 ECFS 核心文件出现的原因——提供一种文件格式,它可以描述进程映像的每一个细微差别,以便能够有效地分析、轻松导航并轻松地与恶意软件分析和进程取证工具集成。
在本章中,我们将讨论 ECFS 的基础知识,以及如何使用 ECFS 核心文件和libecfsAPI 来快速设计恶意软件分析和取证工具。
2011 年,我为美国国防部高级研究计划局的一份合同创建了一个名为 LinuxVMA Monitor(http://www.bitlackeys.org/#vmavudu)的软件原型。 这个软件被设计用来查看实时进程内存或进程内存的原始快照。 它能够检测各种运行时感染,包括共享库注入,PLT/GOT 劫持,以及其他表明运行时恶意软件的异常。
最近,我考虑过重新编写这个软件,使其达到更完善的状态,我觉得为进程内存提供一个本地快照格式将是一个非常好的特性。 这是开发 ECFS 的最初灵感,虽然我现在取消了恢复 Linux VMA Monitor 软件的计划,但我仍在继续扩展和开发 ECFS 软件,因为它对许多其他人的项目有很大价值。 它甚至被整合到 Lotan 产品中,这是一个通过分析崩溃转储(http://www.leviathansecurity.com/lotan)来检测利用尝试的软件。
ECFS 就是要使程序的运行时分析比以前更容易。 整个过程被封装在一个单一的文件中,并以这样的方式组织,以定位和访问对检测异常和感染至关重要的数据和代码,可以通过有序和有效的手段实现。 这主要是通过解析节头来访问有用的数据,如符号表、动态链接数据和与取证相关的结构。
在撰写本章时,完整的 ECFS 项目和源代码可以在http://github.com/elfmaster/ecfs上找到。 一旦您用 git 克隆了存储库,您应该按照 README 文件中的描述编译并安装软件。
目前,ECFS 有两种使用模式:
- 将 ECFS 插入核心处理程序
- 不终止进程的 ECFS 快照
在本章中,术语 ECFS 文件、ECFS 快照和 ECFS 核心文件可以互换使用。
第一件事是将 ECFS 核心处理程序插入 Linux 内核。 make
安装将为您完成这一任务,但它必须在每次重启后完成,或者存储在init
脚本中。 手动设置 ECFS 核心处理程序的方法是通过修改/proc/sys/kernel/core_pattern
文件。
这是用来激活 ECFS 核心处理程序的命令:
echo '|/opt/ecfs/bin/ecfs_handler -t -e %e -p %p -o \ /opt/ecfs/cores/%e.%p' > /proc/sys/kernel/core_pattern
注意,设置了-t
选项。 这对取证来说非常重要,不应该关掉它。 这个选项告诉 ECFS 为任何可执行库或共享库映射捕获整个文本段。 在传统的核心文件中,文本图像被截断为 4k。 在本章的后面,我们还将研究-h
选项(启发式),它可以被设置为启用扩展启发式,以便检测共享库注入。
根据进程是 64 位还是 32 位,ecfs_handler
二进制将调用ecfs32
或ecfs64
。 我们写入 procfscore_pattern
项的行前面的管道符号(|
)告诉内核将它生成的核心文件管道到 ECFS 核心处理程序进程的标准输入中。 然后,ECFS 核心处理程序将传统的核心文件转换为高度定制的、引人注目的 ECFS 核心文件。 随时如果进程崩溃或传递一个信号,导致核心转储,如【T7 SIGSEGV】【显示】或SIGABRT【病人】,然后 ecf 核心处理器将会介入,仪器的核心文件创建自己的组特殊的程序创建一个 ECFS-style 核心转储。
以下是捕获sshd
的 ECFS 快照的示例:
$ kill -ABRT `pidof sshd`
$ ls -lh /opt/ecfs/cores
-rwxrwx--- 1 root root 8244638 Jul 24 13:36 sshd.1211
$
将 ECFS 作为默认的核心文件处理程序非常好,非常适合日常使用。 这是因为 ECFS 内核向后兼容传统的核心文件,并且可以与 GDB 等调试器一起使用。 然而,有时用户可能希望在不终止进程的情况下捕获 ECFS 快照。 这就是 ECFS 快照工具发挥作用的地方。
让我们考虑一个场景,其中有一个可疑的进程正在运行。 这是可疑的,因为它正在消耗大量的 CPU 和它有网络套接字打开,即使它是已知的不是任何类型的网络程序。 在这种情况下,最好让进程继续运行,这样潜在的攻击者就不会收到警报,但仍然有能力生成 ECFS 核心文件。 在这些情况下,应该使用ecfs_snapshot
实用程序。
ecfs_snapshot
实用程序最终使用 ptrace 系统调用,这意味着两件事:
- 它可能会花费更长的时间来捕捉进程
- 对于使用反调试技术来防止 ptrace 附加的进程,它可能无效
如果这两个问题中的任何一个成为问题,您可能不得不考虑对作业使用 ECFS 核心处理程序,在这种情况下,您将不得不终止该进程。 然而,在大多数情况下,ecfs_snapshot
实用程序可以工作。
下面是一个用快照实用程序捕获 ECFS 快照的示例:
$ ecfs_snapshot -p `pidof host` -o host_snapshot
这将快照程序主机的进程,并创建一个名为host_snapshot
的 ECFS 快照。 在下面的小节中,我们将演示一些 ECFS 的实际用例,并查看带有各种实用程序的 ECFS 文件。
ECFS 文件格式非常容易用传统的 ELF 实用程序(如readelf
)解析,但是要构建自定义的解析工具,我强烈建议您使用 libecfs 库。 这个库是专门为轻松解析 ECFS 核心文件而设计的。 在本章后面,当我们研究设计先进的恶意软件分析工具来检测受感染的进程时,它将被稍微详细地演示。
libecfs 还用于正在进行的readecfs
实用工具的开发,这是一种用于解析 ECFS 文件的工具,与常见的readelf
实用工具非常相似。 注意,libecfs 包含在 GitHub 存储库的 ECFS 包中。
本章的其余部分将使用readecfs
实用程序,同时演示不同的 ECFS 特性。 以下是来自readecfs -h
的工具简介:
Usage: readecfs [-RAPSslphega] <ecfscore>
-a print all (equiv to -Sslphega)
-s print symbol table info
-l print shared library names
-p print ELF program headers
-S print ELF section headers
-h print ELF header
-g print PLTGOT info
-A print Auxiliary vector
-P print personality info
-e print ecfs specific (auiliary vector, process state, sockets, pipes, fd's, etc.)
-[View raw data from a section]
-R <ecfscore> <section>
-[Copy an ELF section into a file (Similar to objcopy)]
-O <ecfscore> .section <outfile>
-[Extract and decompress /proc/$pid from .procfs.tgz section into directory]
-X <ecfscore> <output_dir>
Examples:
readecfs -e <ecfscore>
readecfs -Ag <ecfscore>
readecfs -R <ecfscore> .stack
readecfs -R <ecfscore> .bss
readecfs -eR <ecfscore> .heap
readecfs -O <ecfscore> .vdso vdso_elf.so
readecfs -X <ecfscore> procfs_dir
在之前,我们通过一个现实世界的例子来展示 ECFS 的有效性,对于我们将从黑客的角度使用的感染方法有一点背景知识是很有帮助的。 对于黑客来说,将反取证技术整合到他们的工作流程中是非常有用的,这样他们的程序,尤其是那些充当后门的程序,就可以对未经训练的人保持隐藏。
其中一种方法是执行过程掩盖。 这是在现有进程中运行程序的行为,理想情况下是在已知的良性但持久的进程中,如 ftpd 或 sshd。 Sarumananti-forensics exec(http://www.bitlackeys.org/#saruman)允许攻击者将一个完整的、动态链接的 PIE 可执行文件注入到现有的进程地址空间中并运行它。
它使用线程注入技术,以便被注入的程序可以与主程序同时运行。 这种特殊的黑客技术是我在 2013 年提出和设计的,但我毫不怀疑,其他类似的工具在地下场景中存在的时间比这要长得多。 通常,这种类型的反法医技术不会被注意到,也很难被发现。
让我们看看通过使用 ECFS 技术分析这样一个过程,我们可以获得什么样的效率和准确性。
主机进程是一个良性进程,通常是 sshd 或 ftpd 之类的进程,如前所述。 为了便于示例,我们将使用一个简单而持久的程序 host; 它只是在无限循环中运行,在屏幕上打印一条消息。 然后我们会利用萨鲁曼反取证执行启动程序注入一个远程服务器后门。
在终端 1 中运行主机程序:
$ ./host
I am the host
I am the host
I am the host
在终端 2 中,将后门注入流程:
$ ./launcher `pidof host` ./server
[+] Thread injection succeeded, tid: 16187
[+] Saruman successfully injected program: ./server
[+] PT_DETACHED -> 16186
$
现在,如果我们通过使用ecfs_snapshot
实用程序或通过向核心转储发送进程信号来捕获进程的快照,我们就可以开始我们的检查了。
让我们来看看host.16186
快照的符号表分析:
readelf -s host.16186
Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00007fba3811e000 0 NOTYPE LOCAL DEFAULT UND
1: 00007fba3818de30 0 FUNC GLOBAL DEFAULT UND puts
2: 00007fba38209860 0 FUNC GLOBAL DEFAULT UND write
3: 00007fba3813fdd0 0 FUNC GLOBAL DEFAULT UND __libc_start_main
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 00007fba3818c4e0 0 FUNC GLOBAL DEFAULT UND fopen
Symbol table '.symtab' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000400470 96 FUNC GLOBAL DEFAULT 10 sub_400470
1: 00000000004004d0 42 FUNC GLOBAL DEFAULT 10 sub_4004d0
2: 00000000004005bd 50 FUNC GLOBAL DEFAULT 10 sub_4005bd
3: 00000000004005ef 69 FUNC GLOBAL DEFAULT 10 sub_4005ef
4: 0000000000400640 101 FUNC GLOBAL DEFAULT 10 sub_400640
5: 00000000004006b0 2 FUNC GLOBAL DEFAULT 10 sub_4006b0
readelf
命令允许我们查看符号表。 注意,对于.dynsym
中的动态符号和存储在.symtab
符号表中的局部函数的符号,都存在一个符号表。 ECFS 能够通过访问动态段并找到DT_SYMTAB
来重建动态符号表。
符号表有点复杂,但非常有价值。 ECFS 使用一种特殊的方法来解析包含矮化格式的帧描述条目的PT_GNU_EH_FRAME
片段; 它们用于异常处理。 该信息对于收集二进制文件中定义的每个函数的位置和大小非常有用。
在功能被混淆的情况下,IDA 等工具将无法识别二进制文件或核心文件中定义的每个功能,但是 ECFS 技术将会成功。 这是 ECFS 对逆向工程世界的主要影响之一——一种几乎万无一失的方法,可以定位和调整每个函数的大小,并生成一个符号表。 在host.16186
文件中,符号表被完全重构。 这很有用,因为它可以帮助我们检测是否有任何 PLT/GOT 钩子被用于重定向共享库函数,如果是这样,我们可以识别被劫持的函数的实际名称。
现在,让我们看看host.16186
快照的头分析部分。
我的版本readelf
稍加修改,以便它能够识别以下自定义类型:SHT_INJECTED
和SHT_PRELOADED
。 如果不修改 readelf,它只会显示与这些定义相关的数值。 查看include/ecfs.h
以获得定义,如果您愿意,可以将它们添加到readelf
源代码中:
$ readelf -S host.16186
There are 46 section headers, starting at offset 0x255464:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00002238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note NOTE 0000000000000000 000005f0
000000000000133c 0000000000000000 A 0 0 4
[ 3] .hash GNU_HASH 0000000000400298 00002298
000000000000001c 0000000000000000 A 0 0 4
[ 4] .dynsym DYNSYM 00000000004002b8 000022b8
0000000000000090 0000000000000018 A 5 0 8
[ 5] .dynstr STRTAB 0000000000400348 00002348
0000000000000049 0000000000000018 A 0 0 1
[ 6] .rela.dyn RELA 00000000004003c0 000023c0
0000000000000018 0000000000000018 A 4 0 8
[ 7] .rela.plt RELA 00000000004003d8 000023d8
0000000000000078 0000000000000018 A 4 0 8
[ 8] .init PROGBITS 0000000000400450 00002450
000000000000001a 0000000000000000 AX 0 0 8
[ 9] .plt PROGBITS 0000000000400470 00002470
0000000000000060 0000000000000010 AX 0 0 16
[10] ._TEXT PROGBITS 0000000000400000 00002000
0000000000001000 0000000000000000 AX 0 0 16
[11] .text PROGBITS 00000000004004d0 000024d0
00000000000001e2 0000000000000000 0 0 16
[12] .fini PROGBITS 00000000004006b4 000026b4
0000000000000009 0000000000000000 AX 0 0 16
[13] .eh_frame_hdr PROGBITS 00000000004006e8 000026e8
000000000000003c 0000000000000000 AX 0 0 4
[14] .eh_frame PROGBITS 0000000000400724 00002728
0000000000000114 0000000000000000 AX 0 0 8
[15] .ctors PROGBITS 0000000000600e10 00003e10
0000000000000008 0000000000000008 A 0 0 8
[16] .dtors PROGBITS 0000000000600e18 00003e18
0000000000000008 0000000000000008 A 0 0 8
[17] .dynamic DYNAMIC 0000000000600e28 00003e28
00000000000001d0 0000000000000010 WA 0 0 8
[18] .got.plt PROGBITS 0000000000601000 00004000
0000000000000048 0000000000000008 WA 0 0 8
[19] ._DATA PROGBITS 0000000000600000 00003000
0000000000001000 0000000000000000 WA 0 0 8
[20] .data PROGBITS 0000000000601040 00004040
0000000000000010 0000000000000000 WA 0 0 8
[21] .bss PROGBITS 0000000000601050 00004050
0000000000000008 0000000000000000 WA 0 0 8
[22] .heap PROGBITS 0000000000e9c000 00006000
0000000000021000 0000000000000000 WA 0 0 8
[23] .elf.dyn.0 INJECTED 00007fba37f1b000 00038000
0000000000001000 0000000000000000 AX 0 0 8
[24] libc-2.19.so.text SHLIB 00007fba3811e000 0003b000
00000000001bb000 0000000000000000 A 0 0 8
[25] libc-2.19.so.unde SHLIB 00007fba382d9000 001f6000
00000000001ff000 0000000000000000 A 0 0 8
[26] libc-2.19.so.relr SHLIB 00007fba384d8000 001f6000
0000000000004000 0000000000000000 A 0 0 8
[27] libc-2.19.so.data SHLIB 00007fba384dc000 001fa000
0000000000002000 0000000000000000 A 0 0 8
[28] ld-2.19.so.text SHLIB 00007fba384e3000 00201000
0000000000023000 0000000000000000 A 0 0 8
[29] ld-2.19.so.relro SHLIB 00007fba38705000 0022a000
0000000000001000 0000000000000000 A 0 0 8
[30] ld-2.19.so.data SHLIB 00007fba38706000 0022b000
0000000000001000 0000000000000000 A 0 0 8
[31] .procfs.tgz LOUSER+0 0000000000000000 00254388
00000000000010dc 0000000000000001 0 0 8
[32] .prstatus PROGBITS 0000000000000000 00253000
00000000000002a0 0000000000000150 0 0 8
[33] .fdinfo PROGBITS 0000000000000000 002532a0
0000000000000ac8 0000000000000228 0 0 4
[34] .siginfo PROGBITS 0000000000000000 00253d68
0000000000000080 0000000000000080 0 0 4
[35] .auxvector PROGBITS 0000000000000000 00253de8
0000000000000130 0000000000000008 0 0 8
[36] .exepath PROGBITS 0000000000000000 00253f18
000000000000001c 0000000000000008 0 0 1
[37] .personality PROGBITS 0000000000000000 00253f34
0000000000000004 0000000000000004 0 0 1
[38] .arglist PROGBITS 0000000000000000 00253f38
0000000000000050 0000000000000001 0 0 1
[39] .fpregset PROGBITS 0000000000000000 00253f88
0000000000000400 0000000000000200 0 0 8
[40] .stack PROGBITS 00007fff4447c000 0022d000
0000000000021000 0000000000000000 WA 0 0 8
[41] .vdso PROGBITS 00007fff444a9000 0024f000
0000000000002000 0000000000000000 WA 0 0 8
[42] .vsyscall PROGBITS ffffffffff600000 00251000
0000000000001000 0000000000000000 WA 0 0 8
[43] .symtab SYMTAB 0000000000000000 0025619d
0000000000000090 0000000000000018 44 0 4
[44] .strtab STRTAB 0000000000000000 0025622d
0000000000000042 0000000000000000 0 0 1
[45] .shstrtab STRTAB 0000000000000000 00255fe4
00000000000001b9 0000000000000000 0 0 1
我们对第 23 节特别感兴趣; 它被标记为一个可疑的 ELF 对象,注入的外延为:
[23] .elf.dyn.0 INJECTED 00007fba37f1b000 00038000
0000000000001000 0000000000000000 AX 0 0 8
当 ECFS 启发式检测到一个可疑的 ELF 对象,并且在其映射的共享库列表中找不到该特定对象时,它将以以下格式命名该节:
.elf.<type>.<count>
类型可以是以下四种之一:
ET_NONE
ET_EXEC
ET_DYN
ET_REL
在我们的示例中,它显然是ET_DYN
,表示为dyn
。 计数只是已找到的注入对象的索引。 在本例中,索引是0
,因为它是在这个特定进程中找到的第一个也是唯一一个注入的 ELF 对象。
类型INJECTED
明显表示该切片包含一个 ELF 对象,该 ELF 对象被确定为可疑或通过非自然手段注入。 在这个特定的情况下,进程感染了 Saruman(前面描述过),它注入了一个位置无关的可执行文件(PIE)。 PIE 可执行文件的类型为ET_DYN
,类似于共享库,这也是 ECFS 将其标记为共享库的原因。
我们在 ECFS 核心文件中发现了与寄生代码相关的部分,在本例中,寄生代码是注入的 PIE 可执行文件。 下一步是研究代码本身。 这可以通过以下方式之一:objdump
实用程序或更先进的反汇编程序,如 IDA pro 可以用来导航到章节.elf.dyn.0
,readecfs
实用程序也可以首先被用来提取寄生 ecf 核心文件代码:
$ readecfs -O host.16186 .elf.dyn.0 parasite_code.exe
- readecfs output for file host.16186
- Executable path (.exepath): /home/ryan/git/saruman/host
- Command line: ./host
[+] Copying section data from '.elf.dyn.0' into output file 'parasite_code.exe'
多亏了 ECFS,我们现在有了从进程图像中提取的寄生代码的单一副本。 如果没有 ECFS,识别这种特定的恶意软件并提取它将是一项极其乏味的任务。 现在我们可以将parasite_code.exe
作为一个单独的文件来检查,在 IDA 中打开它,然后依次类推:
root@elfmaster:~/ecfs/cores# readelf -l parasite_code.exe
readelf: Error: Unable to read in 0x40 bytes of section headers
readelf: Error: Unable to read in 0x780 bytes of section headers
Elf file type is DYN (Shared object file)
Entry point 0xdb0
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 E 8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000001934 0x0000000000001934 R E 200000
LOAD 0x0000000000001df0 0x0000000000201df0 0x0000000000201df0
0x0000000000000328 0x0000000000000330 RW 200000
DYNAMIC 0x0000000000001e08 0x0000000000201e08 0x0000000000201e08
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000000254 0x0000000000000254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000017e0 0x00000000000017e0 0x00000000000017e0
0x000000000000003c 0x000000000000003c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000001df0 0x0000000000201df0 0x0000000000201df0
0x0000000000000210 0x0000000000000210 R 1
readelf: Error: Unable to read in 0x1d0 bytes of dynamic section
注意,在前面的输出中,readelf
是。 这是,因为我们提取的寄生体没有它自己的 section 头表。 将来,readecfs
实用程序将能够为从整个 ECFS 核心文件中提取的映射 ELF 对象重建一个最小的节头表。
所第七章,进程内存取证,归与阿撒泻勒的 userland rootkit userland rootkit,感染过程通过LD_PRELOAD
,阿扎赛尔共享库的相关流程,并劫持各种libc
功能。 在第 7 章,进程内存取证中,我们使用 GDB 和readelf
来检查一个进程是否存在这种特定的 rootkit 感染。 现在让我们尝试使用 ECFS 方法来进行这种类型的过程内省。 以下是来自可执行主机 2 的进程的 ECFS 快照,该进程已被 Azazel rootkit 感染。
*### 重建 host2 进程的符号表
现在,这是经过进程重构的 host2 的符号表:
$ readelf -s host2.7254
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 00007f0a0d0ed070 0 FUNC GLOBAL DEFAULT UND unlink
2: 00007f0a0d06fe30 0 FUNC GLOBAL DEFAULT UND puts
3: 00007f0a0d0bcef0 0 FUNC GLOBAL DEFAULT UND opendir
4: 00007f0a0d021dd0 0 FUNC GLOBAL DEFAULT UND __libc_start_main
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fopen
Symbol table '.symtab' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000004004b0 112 FUNC GLOBAL DEFAULT 10 sub_4004b0
1: 0000000000400520 42 FUNC GLOBAL DEFAULT 10 sub_400520
2: 000000000040060d 68 FUNC GLOBAL DEFAULT 10 sub_40060d
3: 0000000000400660 101 FUNC GLOBAL DEFAULT 10 sub_400660
4: 00000000004006d0 2 FUNC GLOBAL DEFAULT 10 sub_4006d0
我们可以看到从 host2 前面的符号表是一个简单的程序,只有少数共享库调用(这是.dynsym
符号表所示):unlink
,puts
,opendir
和fopen
。
让我们看看主机 2 的 section 头表在进程重构时是什么样子:
$ readelf -S host2.7254
There are 65 section headers, starting at offset 0x27e1ee:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00002238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note NOTE 0000000000000000 00000900
000000000000105c 0000000000000000 A 0 0 4
[ 3] .hash GNU_HASH 0000000000400298 00002298
000000000000001c 0000000000000000 A 0 0 4
[ 4] .dynsym DYNSYM 00000000004002b8 000022b8
00000000000000a8 0000000000000018 A 5 0 8
[ 5] .dynstr STRTAB 0000000000400360 00002360
0000000000000052 0000000000000018 A 0 0 1
[ 6] .rela.dyn RELA 00000000004003e0 000023e0
0000000000000018 0000000000000018 A 4 0 8
[ 7] .rela.plt RELA 00000000004003f8 000023f8
0000000000000090 0000000000000018 A 4 0 8
[ 8] .init PROGBITS 0000000000400488 00002488
000000000000001a 0000000000000000 AX 0 0 8
[ 9] .plt PROGBITS 00000000004004b0 000024b0
0000000000000070 0000000000000010 AX 0 0 16
[10] ._TEXT PROGBITS 0000000000400000 00002000
0000000000001000 0000000000000000 AX 0 0 16
[11] .text PROGBITS 0000000000400520 00002520
00000000000001b2 0000000000000000 0 0 16
[12] .fini PROGBITS 00000000004006d4 000026d4
0000000000000009 0000000000000000 AX 0 0 16
[13] .eh_frame_hdr PROGBITS 0000000000400708 00002708
0000000000000034 0000000000000000 AX 0 0 4
[14] .eh_frame PROGBITS 000000000040073c 00002740
00000000000000f4 0000000000000000 AX 0 0 8
[15] .ctors PROGBITS 0000000000600e10 00003e10
0000000000000008 0000000000000008 A 0 0 8
[16] .dtors PROGBITS 0000000000600e18 00003e18
0000000000000008 0000000000000008 A 0 0 8
[17] .dynamic DYNAMIC 0000000000600e28 00003e28
00000000000001d0 0000000000000010 WA 0 0 8
[18] .got.plt PROGBITS 0000000000601000 00004000
0000000000000050 0000000000000008 WA 0 0 8
[19] ._DATA PROGBITS 0000000000600000 00003000
0000000000001000 0000000000000000 WA 0 0 8
[20] .data PROGBITS 0000000000601048 00004048
0000000000000010 0000000000000000 WA 0 0 8
[21] .bss PROGBITS 0000000000601058 00004058
0000000000000008 0000000000000000 WA 0 0 8
[22] .heap PROGBITS 0000000000602000 00005000
0000000000021000 0000000000000000 WA 0 0 8
[23] libaudit.so.1.0.0 SHLIB 0000003001000000 00026000
0000000000019000 0000000000000000 A 0 0 8
[24] libaudit.so.1.0.0 SHLIB 0000003001019000 0003f000
00000000001ff000 0000000000000000 A 0 0 8
[25] libaudit.so.1.0.0 SHLIB 0000003001218000 0003f000
0000000000001000 0000000000000000 A 0 0 8
[26] libaudit.so.1.0.0 SHLIB 0000003001219000 00040000
0000000000001000 0000000000000000 A 0 0 8
[27] libpam.so.0.83.1\. SHLIB 0000003003400000 00041000
000000000000d000 0000000000000000 A 0 0 8
[28] libpam.so.0.83.1\. SHLIB 000000300340d000 0004e000
00000000001ff000 0000000000000000 A 0 0 8
[29] libpam.so.0.83.1\. SHLIB 000000300360c000 0004e000
0000000000001000 0000000000000000 A 0 0 8
[30] libpam.so.0.83.1\. SHLIB 000000300360d000 0004f000
0000000000001000 0000000000000000 A 0 0 8
[31] libutil-2.19.so.t SHLIB 00007f0a0cbf9000 00050000
0000000000002000 0000000000000000 A 0 0 8
[32] libutil-2.19.so.u SHLIB 00007f0a0cbfb000 00052000
00000000001ff000 0000000000000000 A 0 0 8
[33] libutil-2.19.so.r SHLIB 00007f0a0cdfa000 00052000
0000000000001000 0000000000000000 A 0 0 8
[34] libutil-2.19.so.d SHLIB 00007f0a0cdfb000 00053000
0000000000001000 0000000000000000 A 0 0 8
[35] libdl-2.19.so.tex SHLIB 00007f0a0cdfc000 00054000
0000000000003000 0000000000000000 A 0 0 8
[36] libdl-2.19.so.und SHLIB 00007f0a0cdff000 00057000
00000000001ff000 0000000000000000 A 0 0 8
[37] libdl-2.19.so.rel SHLIB 00007f0a0cffe000 00057000
0000000000001000 0000000000000000 A 0 0 8
[38] libdl-2.19.so.dat SHLIB 00007f0a0cfff000 00058000
0000000000001000 0000000000000000 A 0 0 8
[39] libc-2.19.so.text SHLIB 00007f0a0d000000 00059000
00000000001bb000 0000000000000000 A 0 0 8
[40] libc-2.19.so.unde SHLIB 00007f0a0d1bb000 00214000
00000000001ff000 0000000000000000 A 0 0 8
[41] libc-2.19.so.relr SHLIB 00007f0a0d3ba000 00214000
0000000000004000 0000000000000000 A 0 0 8
[42] libc-2.19.so.data SHLIB 00007f0a0d3be000 00218000
0000000000002000 0000000000000000 A 0 0 8
[43] azazel.so.text PRELOADED 00007f0a0d3c5000 0021f000
0000000000008000 0000000000000000 A 0 0 8
[44] azazel.so.undef PRELOADED 00007f0a0d3cd000 00227000
00000000001ff000 0000000000000000 A 0 0 8
[45] azazel.so.relro PRELOADED 00007f0a0d5cc000 00227000
0000000000001000 0000000000000000 A 0 0 8
[46] azazel.so.data PRELOADED 00007f0a0d5cd000 00228000
0000000000001000 0000000000000000 A 0 0 8
[47] ld-2.19.so.text SHLIB 00007f0a0d5ce000 00229000
0000000000023000 0000000000000000 A 0 0 8
[48] ld-2.19.so.relro SHLIB 00007f0a0d7f0000 00254000
0000000000001000 0000000000000000 A 0 0 8
[49] ld-2.19.so.data SHLIB 00007f0a0d7f1000 00255000
0000000000001000 0000000000000000 A 0 0 8
[50] .procfs.tgz LOUSER+0 0000000000000000 0027d038
00000000000011b6 0000000000000001 0 0 8
[51] .prstatus PROGBITS 0000000000000000 0027c000
0000000000000150 0000000000000150 0 0 8
[52] .fdinfo PROGBITS 0000000000000000 0027c150
0000000000000ac8 0000000000000228 0 0 4
[53] .siginfo PROGBITS 0000000000000000 0027cc18
0000000000000080 0000000000000080 0 0 4
[54] .auxvector PROGBITS 0000000000000000 0027cc98
0000000000000130 0000000000000008 0 0 8
[55] .exepath PROGBITS 0000000000000000 0027cdc8
000000000000001c 0000000000000008 0 0 1
[56] .personality PROGBITS 0000000000000000 0027cde4
0000000000000004 0000000000000004 0 0 1
[57] .arglist PROGBITS 0000000000000000 0027cde8
0000000000000050 0000000000000001 0 0 1
[58] .fpregset PROGBITS 0000000000000000 0027ce38
0000000000000200 0000000000000200 0 0 8
[59] .stack PROGBITS 00007ffdb9161000 00257000
0000000000021000 0000000000000000 WA 0 0 8
[60] .vdso PROGBITS 00007ffdb918f000 00279000
0000000000002000 0000000000000000 WA 0 0 8
[61] .vsyscall PROGBITS ffffffffff600000 0027b000
0000000000001000 0000000000000000 WA 0 0 8
[62] .symtab SYMTAB 0000000000000000 0027f576
0000000000000078 0000000000000018 63 0 4
[63] .strtab STRTAB 0000000000000000 0027f5ee
0000000000000037 0000000000000000 0 0 1
[64] .shstrtab STRTAB 0000000000000000 0027f22e
0000000000000348 0000000000000000 0 0 1
ELF 第 43 到 46 节都是可疑的,因为它们被标记为PRELOADED
节类型,这表明它们是从一个用LD_PRELOAD
环境变量预加载的共享库的映射:
[43] azazel.so.text PRELOADED 00007f0a0d3c5000 0021f000
0000000000008000 0000000000000000 A 0 0 8
[44] azazel.so.undef PRELOADED 00007f0a0d3cd000 00227000
00000000001ff000 0000000000000000 A 0 0 8
[45] azazel.so.relro PRELOADED 00007f0a0d5cc000 00227000
0000000000001000 0000000000000000 A 0 0 8
[46] azazel.so.data PRELOADED 00007f0a0d5cd000 00228000
0000000000001000 0000000000000000 A 0 0 8
各种用户使用的 rootkit,如 Azazel,使用LD_PRELOAD
作为他们的注射手段。 下一步是查看 PLT/GOT(全局偏移表),检查它是否包含指向各自边界之外的函数的指针。
你可能还记得,在前面的章节中,GOT 包含了一个指针值表,它应该指向以下任意一个指针:
- 对应 PLT 表项中的 PLT 存根(还记得第二章中的延迟链接概念吗?,the ELF Binary Format)
- 如果链接器已经以某种方式(延迟或严格链接)解析了特定的 GOT 条目,那么它将指向共享库函数,该函数由来自可执行文件
.rela.plt
部分的相应重定位条目表示
理解并系统地验证 PLT/GOT 的完整性是一件乏味的手工工作。 幸运的是,有一种非常简单的方法可以使用 ECFS 实现这一点。 如果你更喜欢编写自己的工具,那么你应该使用专门为这个目的设计的libecfs
函数:
ssize_t get_pltgot_info(ecfs_elf_t *desc, pltgot_info_t **pginfo)
这个函数分配一个 struct 数组,每个元素对应于一个 PLT/GOT 条目。
命名为pltgot_info_t
的 C 结构有以下格式:
typedef struct pltgotinfo {
unsigned long got_site; // addr of the GOT entry itself
unsigned long got_entry_va; // pointer value stored in the GOT entry
unsigned long plt_entry_va; // the expected PLT address
unsigned long shl_entry_va; // the expected shared lib function addr
} pltgot_info_t;
使用此函数的示例可以在ecfs/libecfs/main/detect_plt_hooks.c
中找到。 这是一个用于检测共享库注入和 PLT/GOT 钩子的简单演示工具,为了清晰起见,将在本章后面给出说明和注释。 readecfs
实用程序还演示了在传递-g
标志时get_pltgot_info()
函数的用法。
- readecfs output for file host2.7254
- Executable path (.exepath): /home/user/git/azazel/host2
- Command line: ./host2
- Printing out GOT/PLT characteristics (pltgot_info_t):
gotsite gotvalue gotshlib pltval symbol
0x601018 0x7f0a0d3c8c81 0x7f0a0d0ed070 0x4004c6 unlink
0x601020 0x7f0a0d06fe30 0x7f0a0d06fe30 0x4004d6 puts
0x601028 0x7f0a0d3c8d77 0x7f0a0d0bcef0 0x4004e6 opendir
0x601030 0x7f0a0d021dd0 0x7f0a0d021dd0 0x4004f6 __libc_start_main
前面的输出是,很容易解析。 gotvalue
应该有一个匹配gotshlib
或pltval
的地址。 但是,我们可以看到,符号unlink
的第一个条目有一个地址0x7f0a0d3c8c81
。 这与预期的共享库函数或 PLT 值不匹配。
更多的调查表明,地址指向azazel.so
中的一个函数。 从前面的输出中,我们可以看到只有两个函数没有被篡改,它们是puts
和__libc_start_main
。 为了更深入地了解检测过程,让我们看一下一个工具的源代码,该工具将自动执行 PLT/GOT 验证作为其检测功能的一部分。 这个工具名为detect_plt_hooks
,是用 c 编写的。它利用 libecfs API 来加载和解析 ECFS 快照。
请注意,下面的代码大约有 50 行源代码,这是相当了不起的。 如果我们不使用 ECFS 或 libecfs,则需要大约 3000 行 C 代码来准确分析共享库注入和 PLT/GOT 钩子的进程映像。 我知道这一点,因为我做过,而且使用 libecfs 是迄今为止最轻松的编写此类工具的方法。
下面是使用detect_plt_hooks.c
的代码示例:
#include "../include/libecfs.h"
int main(int argc, char **argv)
{
ecfs_elf_t *desc;
ecfs_sym_t *dsyms;
char *progname;
int i;
char *libname;
long evil_addr = 0;
if (argc < 2) {
printf("Usage: %s <ecfs_file>\n", argv[0]);
exit(0);
}
/*
* Load the ECFS file and creates descriptor
*/
desc = load_ecfs_file(argv[1]);
/*
* Get the original program name
*/
progname = get_exe_path(desc);
printf("Performing analysis on '%s' which corresponds to executable: %s\n", argv[1], progname);
/*
* Look for any sections that are marked as INJECTED
* or PRELOADED, indicating shared library injection
* or ELF object injection.
*/
for (i = 0; i < desc->ehdr->e_shnum; i++) {
if (desc->shdr[i].sh_type == SHT_INJECTED) {
libname = strdup(&desc->shstrtab[desc->shdr[i].sh_name]);
printf("[!] Found malicously injected ET_DYN (Dynamic ELF): %s - base: %lx\n", libname, desc->shdr[i].sh_addr);
} else
if (desc->shdr[i].sh_type == SHT_PRELOADED) {
libname = strdup(&desc->shstrtab[desc->shdr[i].sh_name]);
printf("[!] Found a preloaded shared library (LD_PRELOAD): %s - base: %lx\n", libname, desc->shdr[i].sh_addr);
}
}
/*
* Load and validate the PLT/GOT to make sure that each
* GOT entry points to its proper respective location
* in either the PLT, or the correct shared lib function.
*/
pltgot_info_t *pltgot;
int gotcount = get_pltgot_info(desc, &pltgot);
for (i = 0; i < gotcount; i++) {
if (pltgot[i].got_entry_va != pltgot[i].shl_entry_va &&
pltgot[i].got_entry_va != pltgot[i].plt_entry_va &&
pltgot[i].shl_entry_va != 0) {
printf("[!] Found PLT/GOT hook: A function is pointing at %lx instead of %lx\n",
pltgot[i].got_entry_va, evil_addr = pltgot[i].shl_entry_va);
/*
* Load the dynamic symbol table to print the
* hijacked function by name.
*/
int symcount = get_dynamic_symbols(desc, &dsyms);
for (i = 0; i < symcount; i++) {
if (dsyms[i].symval == evil_addr) {
printf("[!] %lx corresponds to hijacked function: %s\n", dsyms[i].symval, &dsyms[i].strtab[dsyms[i].nameoffset]);
break;
}
}
}
}
return 0;
}
ECFS 文件格式既简单又复杂! ELF 文件格式通常很复杂,而 ECFS 从结构的角度继承了这些复杂性。 在令牌的另一方面,如果您知道流程映像具有哪些特定特性以及要查找什么,那么 ECFS 可以帮助您非常容易地导航流程映像。
在前面的小节中,我们给出了一些使用 ECFS 的实际示例,这些示例演示了它的许多主要特性。 然而,有一个简单而直接的引用来说明这些特征是什么也很重要,比如存在哪些自定义部分以及它们的确切含义。 在本节中,我们将提供 ECFS 快照文件的参考。
ECFS 处理程序使用对 ELF 二进制格式的高级理解,甚至使用 dwarf 调试格式(特别是动态段和GNU_EH_FRAME
段)来完全重建程序的符号表。 即使原始的二进制文件已经被剥离并且没有节头,ECFS 处理程序也足够智能,可以重新构建符号表。
我个人从来没有遇到过符号表重构完全失败的情况。 它通常重新构造所有或大部分符号表项。 可以使用实用程序readelf
或readecfs
访问符号表。 libecfs API 还有几个功能:
int get_dynamic_symbols(ecfs_elf_t *desc, ecfs_sym_t **syms)
int get_local_symbols(ecfs_elf_t *desc, ecfs_sym_t **syms)
一个函数获取动态符号表,另一个函数分别获取局部符号表.dynsym
和.symtab
。
下面是与readelf
的阅读符号表:
$ readelf -s host.6758
Symbol table '.dynsym' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00007f3dfd48b000 0 NOTYPE LOCAL DEFAULT UND
1: 00007f3dfd4f9730 0 FUNC GLOBAL DEFAULT UND fputs
2: 00007f3dfd4acdd0 0 FUNC GLOBAL DEFAULT UND __libc_start_main
3: 00007f3dfd4f9220 0 FUNC GLOBAL DEFAULT UND fgets
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 00007f3dfd4f94e0 0 FUNC GLOBAL DEFAULT UND fopen
6: 00007f3dfd54bd00 0 FUNC GLOBAL DEFAULT UND sleep
7: 00007f3dfd84a870 8 OBJECT GLOBAL DEFAULT 25 stdout
Symbol table '.symtab' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000004004f0 112 FUNC GLOBAL DEFAULT 10 sub_4004f0
1: 0000000000400560 42 FUNC GLOBAL DEFAULT 10 sub_400560
2: 000000000040064d 138 FUNC GLOBAL DEFAULT 10 sub_40064d
3: 00000000004006e0 101 FUNC GLOBAL DEFAULT 10 sub_4006e0
4: 0000000000400750 2 FUNC GLOBAL DEFAULT 10 sub_400750
ECFS 处理程序重新构造程序可能具有的原始段头的大部分。 它还增加了相当多的新的部分和部分类型,可以非常有用的法医分析。 节头由名称和类型标识,并包含数据或代码。
解析段头非常容易,因此它们对于创建进程内存映像的映射非常有用。 通过部分头导航整个过程布局比只有程序头(比如常规的核心文件)要容易得多,后者甚至没有字符串名称。 程序头文件用来描述内存段,而段头文件则用来给出给定段的每个部分的上下文。 节头有助于为反向工程提供更高的分辨率。
|节标题
|
描述
|
| --- | --- |
| ._TEXT
| 这指向文本段(而不是.text
部分)。 这使得无需解析程序头即可定位文本段。 |
| ._DATA
| 这个指向数据段(而不是.data
段)。 这使得无需解析程序头即可定位数据段。 |
| .stack
| 根据线程数,它指向几个可能的堆栈段中的一个。 如果没有一个名为.stack
的部分,就很难知道进程的实际堆栈在哪里。 您必须查看%rsp
寄存器的值,然后查看哪些程序头段包含与堆栈指针值匹配的地址范围。 |
| .heap
| 与.stack
部分类似,该部分指向堆段,这也使得堆的识别更加容易,特别是在 ASLR 将堆移动到随机位置的系统上。 在较旧的系统上,它总是从数据段扩展而来。 |
| .bss
| 这部分对于 ECFS 来说并不是新内容。 这里提到它的唯一原因是,对于可执行库或共享库,.bss
部分不包含任何内容,因为未初始化的数据不会占用磁盘空间。 然而,ECFS 表示内存,并且直到运行时才真正创建.bss
部分。 ECFS 文件有一个.bss
部分,它实际反映了进程正在使用的未初始化的数据变量。 |
| .vdso
| 这个指向[vdso]段,它被映射到每个包含某些glibc
系统调用包装器调用实际系统调用所必需的代码的 Linux 进程。 |
| .vsyscall
| 与.vdso
代码相似,.vsyscall
页包含仅调用少量虚拟系统调用的代码。 它被保留下来是为了向后兼容。 在逆向工程中知道这个位置可能是有用的。 |
| .procfs.tgz
| 这个部分包含 ECFS 处理程序捕获的进程/proc/$pid
的整个目录结构和文件。 如果您是一名热心的法医分析人员或程序员,那么您可能已经知道proc
文件系统中包含的信息有多么有用。 一个进程在/proc/$pid
中有超过 300 个文件。 |
| .prstatus
| 这个节包含一个结构体elf_prstatus
的数组。 与进程状态和寄存器有关的重要信息存储在以下结构中:
struct elf_prstatus
{
struct elf_siginfo pr_info; /* Info associated with signal. */
short int pr_cursig; /* Current signal. */
unsigned long int pr_sigpend; /* Set of pending signals. */
unsigned long int pr_sighold; /* Set of held signals. */
__pid_t pr_pid;
__pid_t pr_ppid;
__pid_t pr_pgrp;
__pid_t pr_sid;
struct timeval pr_utime; /* User time. */
struct timeval pr_stime; /* System time. */
struct timeval pr_cutime; /* Cumulative user time. */
struct timeval pr_cstime; /* Cumulative system time. */
elf_gregset_t pr_reg; /* GP registers. */
int pr_fpvalid; /* True if math copro being used. */
};
|
| .fdinfo
| 本节包含描述文件描述符、套接字和用于进程打开文件、网络连接和进程间通信的管道的 ECFS 自定义数据。 头文件ecfs.h
定义了fdinfo_t
类型:
typedef struct fdinfo {
int fd;
char path[MAX_PATH];
loff_t pos;
unsigned int perms;
struct {
struct in_addr src_addr;
struct in_addr dst_addr;
uint16_t src_port;
uint16_t dst_port;
} socket;
char net;
} fd_info_t;
readecfs
实用程序很好地解析和显示了文件描述符信息,如查看 sshd 的 ECFS 快照时所示:
[fd: 0:0] perms: 8002 path: /dev/null
[fd: 1:0] perms: 8002 path: /dev/null
[fd: 2:0] perms: 8002 path: /dev/null
[fd: 3:0] perms: 802 path: socket:[10161]
PROTOCOL: TCP
SRC: 0.0.0.0:22
DST: 0.0.0.0:0
[fd: 4:0] perms: 802 path: socket:[10163]
PROTOCOL: TCP
SRC: 0.0.0.0:22
DST: 0.0.0.0:0
|
| .siginfo
| 这一节包含特定于信号的信息,例如什么信号终止了进程,或者拍摄快照之前的最后一个信号代码是什么。 siginfo_t struct
存储在这个部分中。 该结构的格式见/usr/include/bits/siginfo.h
。 |
| .auxvector
| 它包含了来自堆栈底部的实际辅助向量(最高内存地址)。 辅助向量由内核在运行时设置,它包含在运行时传递给动态连接器的信息。 这些信息可能在许多方面对高级法医分析人员有价值。 |
| .exepath
| 它保存了为该进程调用的原始可执行路径的字符串,即/usr/sbin/sshd
。 |
| .personality
| 此包含人格信息,即 ECFS 人格信息。 一个 8 字节的无符号整数可以设置任意数量的个性标志:
#define ELF_STATIC (1 << 1) // if it's statically linked (instead of dynamically)
#define ELF_PIE (1 << 2) // if it's a PIE executable
#define ELF_LOCSYM (1 << 3) // was a .symtab symbol table created by ecfs?
#define ELF_HEURISTICS (1 << 4) // were detection heuristics used by ecfs?
#define ELF_STRIPPED_SHDRS (1 << 8) // did the binary have section headers?
|
| .arglist
| 包含本节中作为数组存储的原始'char **argv'
。 |
ECFS 核心文件格式本质上是向后兼容常规 Linux 核心文件,因此可以用传统的方式作为核心文件使用 GDB 进行调试。
然而,ECFS 文件的 ELF 文件头的e_type
(ELF 类型)被设置为ET_NONE
而不是ET_CORE
。 这是因为核心文件预计不会对部分头但是 ecf 文件有部分标题,并确保他们承认某些公用事业如objdump
,objcopy
,等等,我们将它们标记为核心文件以外的文件。 在 ECFS 文件中切换 ELF 类型的最快方法是使用 ECFS 软件套件附带的et_flip
实用程序。
下面是一个在 ECFS 核心文件中使用 GDB 的例子:
$ gdb -q /usr/sbin/sshd sshd.1195
Reading symbols from /usr/sbin/sshd...(no debugging symbols found)...done.
"/opt/ecfs/cores/sshd.1195" is not a core dump: File format not recognized
(gdb) quit
下面是将 ELF 文件类型更改为ET_CORE
并再次尝试的示例:
$ et_flip sshd.1195
$ gdb -q /usr/sbin/sshd sshd.1195
Reading symbols from /usr/sbin/sshd...(no debugging symbols found)...done.
[New LWP 1195]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `/usr/sbin/sshd -D'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00007ff4066b8d83 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb)
libecfs API 是将 ECFS 支持集成到恶意软件分析和 Linux 反向工程工具中的关键组件。 关于这个图书馆的文档太多了,无法在本书的一章里一一介绍。 我建议你使用手册,它仍在随着项目的发展而发展:
https://github.com/elfmaster/ecfs/blob/master/Documentation/libecfs_manual.txt
您是否曾经希望能够在 Linux 中暂停和恢复进程? 在设计 ECFS 之后,很快就发现它们包含了关于进程及其状态的足够信息,可以将它们重新启动到内存中,以便在上次停止的地方开始执行。 该特性有许多可能的用例,需要进行更多的研究和开发。
目前,ECFS 快照执行的实现是基本的,只能处理简单的流程。 在撰写本章时,它可以恢复文件流,但不能恢复套接字或管道,并且只能处理单线程进程。 执行 ECFS 快照的软件可以在 GitHub 上的https://github.com/elfmaster/ecfs_exec找到。
下面是一个快照执行的例子:
$ ./print_passfile
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
– interrupted by snapshot -
现在我们有了 ECFS 快照文件 print_passfile.6627 (其中 6627 是进程 ID)。 我们将使用 ecfs_exec 来执行这个快照,它应该从它停止的地方开始:
$ ecfs_exec ./print_passfile.6627
[+] Using entry point: 7f79a0473f20
[+] Using stack vaddr: 7fff8c752738
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
syslog:x:101:104::/home/syslog:/bin/false
messagebus:x:102:106::/var/run/dbus:/bin/false
usbmux:x:103:46:usbmux daemon,,,:/home/usbmux:/bin/false
dnsmasq:x:104:65534:dnsmasq,,,:/var/lib/misc:/bin/false
avahi-autoipd:x:105:113:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
kernoops:x:106:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false
saned:x:108:115::/home/saned:/bin/false
whoopsie:x:109:116::/nonexistent:/bin/false
speech-dispatcher:x:110:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/sh
avahi:x:111:117:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
lightdm:x:112:118:Light Display Manager:/var/lib/lightdm:/bin/false
colord:x:113:121:colord colour management daemon,,,:/var/lib/colord:/bin/false
hplip:x:114:7:HPLIP system user,,,:/var/run/hplip:/bin/false
pulse:x:115:122:PulseAudio daemon,,,:/var/run/pulse:/bin/false
statd:x:116:65534::/var/lib/nfs:/bin/false
guest-ieu5xg:x:117:126:Guest,,,:/tmp/guest-ieu5xg:/bin/bash
sshd:x:118:65534::/var/run/sshd:/usr/sbin/nologin
gdm:x:119:128:Gnome Display Manager:/var/lib/gdm:/bin/false
这是一个非常简单的演示ecfs_exec
是如何工作的。 它使用来自.fdinfo
节的文件描述符信息来学习文件描述符编号、文件路径和文件偏移量。 它还使用.prstatus
和.fpregset
节来学习寄存器状态,以便从停止的地方继续执行。
扩展核心文件快照技术 ECFS 仍然是相对较新的技术。 我在 23 号战备会议上展示了它(https://www.defcon.org/html/defcon-23/dc-23-speakers.html#O%27Neill),这个消息仍在传播。 希望一个社区会不断发展,更多的人会开始在他们的日常取证工作和工具中采用 ECFS。 尽管如此,在这一点上,有几个现有的 ECFS 资源:
GitHub 官方页面:https://github.com/elfmaster/ecfs
- 原白皮书(过时):http://www.leviathansecurity.com/white-papers/extending-the-elf-core-format-for-forensics-snapshots
- 一篇来自 POC || GTFO 0x7 的文章:创新与核心文件,https://speakerdeck.com/ange/poc-gtfo-issue-0x07-1
在本章中,我们介绍了 ECFS 快照技术和快照格式的基础知识。 我们使用几个真实的实例对 ECFS 进行了试验,甚至编写了一个工具来检测共享库注入和使用 libecfs C 库的 PLT/GOT 钩子。 在下一章中,我们将跳出用户世界,探索 Linux 内核,vmlinux 的布局,以及内核 rootkit 和取证技术的组合。*