Skip to content

Latest commit

 

History

History
940 lines (762 loc) · 55.2 KB

File metadata and controls

940 lines (762 loc) · 55.2 KB

八、扩展核心文件快照技术

扩展核心文件快照(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 就是要使程序的运行时分析比以前更容易。 整个过程被封装在一个单一的文件中,并以这样的方式组织,以定位和访问对检测异常和感染至关重要的数据和代码,可以通过有序和有效的手段实现。 这主要是通过解析节头来访问有用的数据,如符号表、动态链接数据和与取证相关的结构。

ECFS 入门

在撰写本章时,完整的 ECFS 项目和源代码可以在http://github.com/elfmaster/ecfs上找到。 一旦您用 git 克隆了存储库,您应该按照 README 文件中的描述编译并安装软件。

目前,ECFS 有两种使用模式:

  • 将 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二进制将调用ecfs32ecfs64。 我们写入 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 快照工具发挥作用的地方。

不杀死进程的 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 文件。

libecfs -用于解析 ECFS 文件的库

ECFS 文件格式非常容易用传统的 ELF 实用程序(如readelf)解析,但是要构建自定义的解析工具,我强烈建议您使用 libecfs 库。 这个库是专门为轻松解析 ECFS 核心文件而设计的。 在本章后面,当我们研究设计先进的恶意软件分析工具来检测受感染的进程时,它将被稍微详细地演示。

libecfs 还用于正在进行的readecfs实用工具的开发,这是一种用于解析 ECFS 文件的工具,与常见的readelf实用工具非常相似。 注意,libecfs 包含在 GitHub 存储库的 ECFS 包中。

readecfs

本章的其余部分将使用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 检查受感染的进程

在之前,我们通过一个现实世界的例子来展示 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 快照

现在,如果我们通过使用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_INJECTEDSHT_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 将其标记为共享库的原因。

使用 readecfs 提取寄生代码

我们在 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 对象重建一个最小的节头表。

Azazel userland rootkit 分析

第七章,进程内存取证,归与阿撒泻勒的 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,opendirfopen

重建 host2 进程的 section 头表

让我们看看主机 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部分的相应重定位条目表示

用 ECFS 验证 PLT/GOT

理解并系统地验证 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()函数的用法。

用于 PLT/GOT 验证的 readecfs 输出

- 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应该有一个匹配gotshlibpltval的地址。 但是,我们可以看到,符号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 参考指南

ECFS 文件格式既简单又复杂! ELF 文件格式通常很复杂,而 ECFS 从结构的角度继承了这些复杂性。 在令牌的另一方面,如果您知道流程映像具有哪些特定特性以及要查找什么,那么 ECFS 可以帮助您非常容易地导航流程映像。

在前面的小节中,我们给出了一些使用 ECFS 的实际示例,这些示例演示了它的许多主要特性。 然而,有一个简单而直接的引用来说明这些特征是什么也很重要,比如存在哪些自定义部分以及它们的确切含义。 在本节中,我们将提供 ECFS 快照文件的参考。

ECFS 符号表重构

ECFS 处理程序使用对 ELF 二进制格式的高级理解,甚至使用 dwarf 调试格式(特别是动态段和GNU_EH_FRAME段)来完全重建程序的符号表。 即使原始的二进制文件已经被剥离并且没有节头,ECFS 处理程序也足够智能,可以重新构建符号表。

我个人从来没有遇到过符号表重构完全失败的情况。 它通常重新构造所有或大部分符号表项。 可以使用实用程序readelfreadecfs访问符号表。 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 节标题

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 文件作为常规的核心文件

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 及其使用方法

libecfs API 是将 ECFS 支持集成到恶意软件分析和 Linux 反向工程工具中的关键组件。 关于这个图书馆的文档太多了,无法在本书的一章里一一介绍。 我建议你使用手册,它仍在随着项目的发展而发展:

https://github.com/elfmaster/ecfs/blob/master/Documentation/libecfs_manual.txt

使用 ECFS 处理巫术

您是否曾经希望能够在 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

扩展核心文件快照技术 ECFS 仍然是相对较新的技术。 我在 23 号战备会议上展示了它(https://www.defcon.org/html/defcon-23/dc-23-speakers.html#O%27Neill),这个消息仍在传播。 希望一个社区会不断发展,更多的人会开始在他们的日常取证工作和工具中采用 ECFS。 尽管如此,在这一点上,有几个现有的 ECFS 资源:

GitHub 官方页面:https://github.com/elfmaster/ecfs

总结

在本章中,我们介绍了 ECFS 快照技术和快照格式的基础知识。 我们使用几个真实的实例对 ECFS 进行了试验,甚至编写了一个工具来检测共享库注入和使用 libecfs C 库的 PLT/GOT 钩子。 在下一章中,我们将跳出用户世界,探索 Linux 内核,vmlinux 的布局,以及内核 rootkit 和取证技术的组合。*