Linux | 浅学一下,进程内存取证分析
We don't use the word 'intelligence' with software. We regard that as a naive idea. We say that it’s 'complex.' Which means that we don't
always understand what it’s doing. —— Orson Scott Card, Ender's Shadow
浅学一下,Linux进程内存取证思路,参考O'Neill(ELFmaster)。
1. 进程内存布局
取证时可以通过分析该文件,获得特定文件的位置或者进程中的内存映射。
下面使用dmesg程序进程的内存布局示例:
1.1 可执行文件内存映射
1-3行为可执行文件的内存映射,显示了文件路径。
第一行为text段,权限为可读、可执行。
第二行为data段第一部分,因为使用了RELRO(只读重定位)保护,因此只标记为只读。
第三行为data端的剩余部分,权限为可写。
00400000-00408000 r-xp 00000000 fd:00 134786044 /usr/bin/dmesg
00607000-00608000 r--p 00007000 fd:00 134786044 /usr/bin/dmesg
00608000-0060b000 rw-p 00008000 fd:00 134786044 /usr/bin/dmesg
1.2 程序堆区
在ASLR之前,堆区位于data段后面,在ASLR出现之后,堆区是内存中随机映射的,只不过在maps文件中显示在data段之后。
01eca000-01eeb000 rw-p 00000000 00:00 0 [heap]
当调用 malloc()请求的内存块大小超过 MMAP_THRESHOLD 时, 会创建匿名内存段。这种类型的匿名内存段不会被标上[heap]标签。
MMAP_THRESHOLD:默认值为128KB,在32位系统上最大值为512KB,64位系统上的最大值为32MB,由于默认开启mmap分配阈值动态调整,该字段的值会动态修改,但不会超过最大值。
1.3 共享库映射
共享库 libc-2.17.so 的内存映射。可以看到位于 text 段和data 段之间的内存映射没有权限标志。这样做的目的就是占用 text 段和 data段之间的内存空间,从而无法创建任意的内存映射。
7f38fc1f8000-7f38fc3bc000 r-xp 00000000 fd:00 202460984 /usr/lib64/libc-2.17.so
7f38fc3bc000-7f38fc5bb000 ---p 001c4000 fd:00 202460984 /usr/lib64/libc-2.17.so
7f38fc5bb000-7f38fc5bf000 r--p 001c3000 fd:00 202460984 /usr/lib64/libc-2.17.so
7f38fc5bf000-7f38fc5c1000 rw-p 001c7000 fd:00 202460984 /usr/lib64/libc-2.17.so
7f38fc5c6000-7f38fc5e8000 r-xp 00000000 fd:00 201326730 /usr/lib64/ld-2.17.so
7f38fc7d6000-7f38fc7dd000 r--s 00000000 fd:00 201328831 /usr/lib64/gconv/gconv-modules.cache
7f38fc7dd000-7f38fc7e0000 rw-p 00000000 00:00 0
7f38fc7e5000-7f38fc7e7000 rw-p 00000000 00:00 0
7f38fc7e7000-7f38fc7e8000 r--p 00021000 fd:00 201326730 /usr/lib64/ld-2.17.so
7f38fc7e8000-7f38fc7e9000 rw-p 00022000 fd:00 201326730 /usr/lib64/ld-2.17.so
7f38fc7e9000-7f38fc7ea000 rw-p 00000000 00:00 0
1.4 栈、VDSO 和 vsyscall
在映射文件的末尾是栈段、VDSO(虚拟动态共享目标文件)和vsyscall:
7ffdefd0f000-7ffdefd30000 rw-p 00000000 00:00 0 [stack]
7ffdefd56000-7ffdefd58000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
glibc使用VDSO来调用一些常用到的系统调用。VDSO通过执行用户层特定的系统调用来进行加速执行。在x86_64位系统上,vsyscall页已经被弃用了,不过在32位系统上,vsyscall的功能跟VDSO相同。进程布局如下:
2. 进程注入技术
(1)注入相关
共享目标文件注入(ET_DYN):这种方式是通过ptrace()系统调用和一段使用了mmap()或者__libc_dlopen_mode()函数来加载共享库文件的shellcode实现的。共享目标文件本身可能不是一个真正的共享目标文件。有可能是一个PIE可执行文件
重定位目标文件注入(ET_REL):将一个重定位目标文件注入到进程中以便于进行热补丁。可以使用ptrace系统调用将shellcode注入到进程中,利用shellcode将目标文件映射到内存中。
PIC代码注入:通常使用ptrace将shellcode注入到进程中。注入shellcode是往进程中注入更加复杂的代码(ET_DYN和ET_REL文件)的第一个阶段。
(2)劫持可执行文件
内联函数钩子(Inline Hook):攻击者会使用一个jmp指令替换函数代码中前5-7个字节,这个jmp跳转指令能够将控制转向一个恶意的函数。可以通过扫描所有函数的初始字节代码来检测内联函数钩子。
3. 共享库注入取证
Jynx2 rootkit 是基于 LD_PRELOAD 技术的用户级 Rootkit。本节使用GDB和Linux环境变量来检查被感染的进程。
注:共享目标文件、共享库、DLL、ET_DYN是同义词。
编写demo,调用fopen函数,即加载了glibc库中的函数。
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *fp;
char buff[256];
fp = fopen("test.txt", "r");
fgets(buff, 256, fp);
printf("\n%s\n", buff);
fclose(fp);
getchar();
return 0;
}
3.1 分析进程的地址空间
重点关注下面的信息,/XxJynx/jynx2.so明显不是标准的共享库的路径,意味着可能进行了共享库的注入,这种情况下需要首先对LD_PRELOAD环境变量进行检查。
7efc1408b000-7efc14090000 rw-p 00000000 00:00 0
7efc14090000-7efc14096000 r-xp 00000000 fd:00 134354322 /XxJynx/jynx2.so
7efc14096000-7efc14295000 ---p 00006000 fd:00 134354322 /XxJynx/jynx2.so
7efc14295000-7efc14296000 r--p 00005000 fd:00 134354322 /XxJynx/jynx2.so
7efc14296000-7efc14297000 rw-p 00006000 fd:00 134354322 /XxJynx/jynx2.so
7efc14297000-7efc142b9000 r-xp 00000000 fd:00 201326730 /usr/lib64/ld-2.17.so
7efc144a8000-7efc144b1000 rw-p 00000000 00:00 0
7efc144b5000-7efc144b8000 rw-p 00000000 00:00 0
7efc144b8000-7efc144b9000 r--p 00021000 fd:00 201326730 /usr/lib64/ld-2.17.so
7efc144b9000-7efc144ba000 rw-p 00022000 fd:00 201326730 /usr/lib64/ld-2.17.so
7efc144ba000-7efc144bb000 rw-p 00000000 00:00 0
7fff25bdb000-7fff25bfc000 rw-p 00000000 00:00 0 [stack]
7fff25bfe000-7fff25c00000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
3.2 查找栈中的LD_PRELOAD
栈顶 栈底
7fff25bdb000-7fff25bfc000 rw-p 00000000 00:00 0 [stack]
gdb -q attach `pidof demo`
x/2048s (0x7fff25bfc000 - 2048)
并没有发现LD_PRELOAD环境变量被修改,故可以检查PLT/GOT(PLT的全局偏移表)中函数劫持的情况。
实际上,Jynx2将编译成的恶意动态链接库复制在LD_PRELOAD的默认配置文件/etc/ld.so.preload中,以此实现全局劫持,并未修改环境变量,所以无法找到。
3.3 分析PLT/GOT钩子
检查PLT/GOT(PLT的全局偏移表)中函数劫持
在检查位于 ELF 中的.got.plt 节(在可执行文件的 data 段)的 PLT/GOT之前,先看一下./demo 程序中的哪个函数设置了 PLT/GOT 相关的重定位。全局偏移表的重定位条目是<ARCH>_JUMP_SLOT 类型的。可以看到demo中调用了几个常见的 glibc 函数。可能有一部分或者全部的函数被恶意的共享库 jynx2.so 劫持。
(gdb) x/gx 0x601048
0x601048 <fopen .plt>: 0x00007efc140933da
#/proc/PID/maps
7efc14090000-7efc14096000 r-xp 00000000 fd:00 134354322 /XxJynx/jynx2.so
3.4 共享库注入的其他方法
使用 LD_PRELOAD 对恶意的共享库进行预加载,很容易就能识别出可疑的共享库。
实际上,有许多其他形式的恶意软件会通过 ptrace()或者使用了 mmap()/__libc_dlopen_mode()的 shellcode 注入共享库。
判断是否进行了共享库注入的其他方法,需要先了解其他的共享库注入。
共享库远程注入:将共享库注入到已经存在的进程中。注入共享库后,需要通过PLT/GOT重定向、函数蹦床(function trampoline)等将控制流重定向到共享库。
利用LD_PRELOAD:在程序执行时加载共享库,可以通过设置LD_PRELOAD环境变量,将我们想要的共享库放在其他共享库之前加载。不过对已存在进程没有影响。
/* Taken from Saruman's launcher.c */
__PAYLOAD_KEYWORDS__ void * dlopen_load_exec(const char *path,void *dlopen_addr)
{
void * (*libc_dlopen_mode)(const char *, int) = dlopen_addr;
void *handle;
handle = libc_dlopen_mode(path,
__RTLD_DLOPEN|RTLD_NOW|RTLD_GLOBAL);
__RETURN_VALUE__(handle);
__BREAKPOINT__;
}
dlopen()也可以装载 PIE 可执行文件。这就意味着可以将一个完整的程序注入到进程中并执行。事实上,可以在单个进程中运行任意程序。这是一项很好的反取证分析技术,在使用线程注入时,可以让注入的线程并发地执行。
ELFmaster设计了一款名为 Saruman 的工具,就可以将程序注入到进程中。工具中使用了两种注入方法:open()/mmap()方法,使用手动重定位或者使用__libc_dlopen_mode()方法。
saruman:http://www.bitlackeys.org/#saruman
3.5 总结共享库注入取证思路
1.从/proc/PID/maps文件中获取共享目标文件路径的列表。
2.检查可执行文件中是否存在与查看的共享库相对应的有效DT_NEEDED条目。如果存在,则说明是合法的共享库。在验证了给定共享库的合法性之后,可以检查共享库的动态段,并枚举动态段中的DT_NEEDED条目。所有与之对应的共享库都可以被标记为合法的。这就是传递共享目标文加载的概念。
3.查看进程对应的实际可执行程序的PLT/GOT。如果使用了任何的dlopen调用,则继续对代码进行分析,找到所有的dlopen调用。可以对dlopen中传递的参数进行静态检查。例如:
void *handle = dlopen("somelib.so", RTLD_NOW);
字符串会作为静态常量存储于二进制文件的.rodata节中。因此,可以检查.rodata节中是否存在想要验证的共享库文件的路径。
4.如果映射文件中的共享目标文件路径没有对应的DT_NEEDED条目,也没有任何的dlopen调用,那么这个共享目标文件可能是通过LD_PRELOAD预加载进来的。或者是通过其他方式注入的。此时,就可以认为该共享目标文件是异常的。
3.6 检测PLT/GOT钩子工具
以下工具本质上都是使用上述的思路实现的。
Linux VMA Voodoo:检测多种类型的进程内存感染,目前只能在 32位的系统上工作。在 http://www.bitlackeys.org/#vmavudu 查看 VMA Voodoo 的相关内容。
ECFS(扩展核心文件快照): 这项技术最初是作为 Linux 中的进程内存取证分析工具的本地快照格式而设计的,现在已经演变成一项更加复杂的技术,详情见 https://github.com/elfmaster/ecfs。
Volatility plt_hook:Volatility 软件主要面向整个系统的内存分析。GeorgWicherski 在 2013 年设计了一个插件, 专门用于检测进程中的 PLT/GOT感染。该插件使用的启发方法跟之前讨论的非常类似。此功能已经与 Volatility 的 源 码 合 并 了,详情见 https://github.com/volatilityfoundation/volatility。
reference
《Learning Binary Analysis》elfmaster-ONeill