LD_PRELOAD

LD_PRELOAD是启动时在进程中加载共享库的最简单,最流行的方法。该环境变量可以配置有指向共享库的路径,该路径要在任何其他共享对象之前加载。

对于文中大多数案例,我们将使用此处列出的GitHub中可用的示例。让我们使用sample-target作为目标流程并使用sample-library作为我们将要注入的共享库。

我们可以使用ldd工具检查加载到进程中的共享库。如果我们使用ldd执行样本目标二进制文件,则可以看到该信息。

Linux-vdso.so.1是一个虚拟的动态共享对象,内核在每个进程中自动将其映射到地址空间。根据不同的体系架构,它可以具有其他名称。

Libc.so.6是样本目标需要运行的动态库之一,而ld-linux.so.2负责查找和加载共享库。通过使用readelf,我们可以看到如何在样本目标ELF文件中定义它。

现在,让我们设置LD_PRELOAD环境变量以通过执行来加载我们的库。

1
export LD_PRELOAD = /home/ubuntu/linux-inject/sample-library.so; ldd /home/ubuntu/linux-inject/sample-target

我们可以看到现在正在加载示例库。通过设置LD_DEBUG环境变量,我们还可以获得更多详细信息。

1
export DEBUG = filename

通过Osquery搜索恶意LD_PRELOAD用法的一种简单方法是查询process_envs表并查找设置了LD_PRELOAD环境变量的进程。

1
SELECT process_envs.pid as source_process_id, process_envs.key as environment_variable_key, process_envs.value as environment_variable_value, processes.name as source_process, processes.path as file_path, processes.cmdline as source_process_commandline, processes.cwd as current_working_directory, 'T1055' as event_attack_id, 'Process Injection' as event_attack_technique, 'Defense Evasion, Privilege Escalation' as event_attack_tactic FROM process_envs join processes USING (pid) WHERE key = 'LD_PRELOAD';

由于某些监视和安全软件出于良性目的使用LD_PRELOAD,因此您必须使用LD_PRELOAD创建环境中已知进程的基准。

我们在使用LD_PRELOAD时遇到的一些良性示例包括以下内容。

从攻击者的角度来看,使用LD_PRELOAD会带来一些不便-主要是需要重新启动要向其中注入代码的过程才能正常工作。后面会介绍不需要此限制的其他技术。

除了LD_PRELOAD,攻击者还可以使用其他类似的技术来获得相同的结果。例如,通过设置LD_LIBRARY_PATH环境变量,可以指定一个目录,装载程序将在该目录中首先尝试找到所需的库,因此攻击者可以创建libc.so的修改版本或其他所需的共享库,并加载恶意代码。进入过程。

最后,建议监视/etc/ld.so.conf和/etc/ld.so.conf.d/*.conf的更改,因为它们可以用于相同的目的。可以使用Osquery的FIM功能,在file_paths配置中添加“ /etc/ld.so.conf”和“ /etc/ld.so.conf.d/%%”。

Ptrace 注入

Linux的注入工具可被用于通过使用ptrace的,类似于Windows中的公知的DLL注射技术来共享库加载到运行的进程。

让我们看一下它如何工作的。首先,我们使用ptrace()附加到目标进程,并注入加载库的代码。然后,加载程序代码使用malloc()分配内存,将共享库的路径复制到缓冲区,然后调用__libc_dlopen_mode()来加载共享库。

1
./inject -n sample-target sample-library.so

失败了!发生了什么?

它的源头是一个称为Yamaha的Linux安全模块,该模块为特定的内核功能(例如ptrace)实现了自主访问控制(DAC)。你可以通过查看/proc/sys/kernel/yama/ptrace_scope或使用systemctl来检查当前状态。

正如你可以在看文档,当ptrace_scope设置为1,只有一个父进程可以调试(这是在Ubuntu 18.04.2默认值)。设置为3时,无法使用ptrace调试任何进程,并且需要重新启动才能更改该值。

从防御者的角度来看,这实际上很棒,因为这意味着攻击者必须在使用ptrace之前修改ptrace_scope的值。

我们可以利用此优势,并利用Osquery的system_controls表查询ptrace_scope的当前配置值。

1
osquery> select * from system_controls WHERE name =='kernel.yama.ptrace_scope';

您还可以在Osquery中利用以下计划的查询来监视ptrace_scope的更改。

1
 detection_ptrace_scope_changed”:{
2
3
       platform”:“ linux”,
4
5
       description”:“检测对kernel.yama.ptrace_scope的更改”,
6
7
       query”:“从system_controls中选择名称,current_value,config_value
8
9
  WHERE name =='kernel.yama.ptrace_scope';“,
10
11
      “时间间隔”:3600,
12
13
      “已删除”:false
14
15
}

我们还可以寻找ptrace_scope的当前值已从/etc/sysctl.conf中的原始值修改过的系统。

1
SELECT name, subsystem, current_value, config_value from system_controls WHERE name == 'kernel.yama.ptrace_scope' AND current_value != config_value;

或者,我们可以简单地检查始终允许使用ptrace的系统,并将其标记为潜在的安全问题。

1
SELECT name, subsystem, current_value, config_value from system_controls WHERE name == 'kernel.yama.ptrace_scope' AND current_value = 0;

更重要的是,当ptrace被阻止时,会记录一条Syslog消息,该消息可用于检测使用ptrace的失败尝试。

1
Jun 10 20:59:24 ip-172-31-32-145 kernel: [955105.055910] ptrace attach of "./sample-target"[13134] was attempted by "./inject -n sample-target sample-library.so"[13148]

如果您已经在安全性堆栈中收集Syslog消息,建议您在系统中多次尝试使用ptrace时发出警报。

在Osquery中,以下查询可用于监视此特定的syslog消息。

1
SELECT * from syslog WHERE tag ='kernel'AND message like'%ptrace attach%';

好的,现在让我们回到linux-inject工具。为了使ptrace起作用,攻击者需要将ptrace_scope设置为0。

1
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

linux注入ptrace工作

让我们使用Osquery的process_memory_map表查看进程内存。

1
SELECT process_memory_map.*, pid as mpid from process_memory_map WHERE pid in (select PID from processes where name LIKE '%sample-target');

在其他公共内存区域中,我们可以看到一些我们已经熟悉的共享库(libc,ld等)。除此之外,还存在注入的库sample-library.so。

我们知道我们正在寻找具有执行许可权的内存区域,我们也可以丢弃原始图像和标记为伪的区域。

1
SELECT count(distinct(process_memory_map.pid)) from process_memory_map LEFT JOIN processes USING (pid) WHERE process_memory_map.path LIKE '/%' and process_memory_map.pseudo != 1 AND process_memory_map.path != processes.path AND process_memory_map.permissions LIKE '%x%';

该查询仍然过于广泛。让我们检查一下共享库最常见的路径是什么。

1
SELECT split(process_memory_map.path, '/', 0) AS folder, count(*) as cnt from process_memory_map LEFT JOIN processes USING (pid) WHERE process_memory_map.path LIKE '/%' AND process_memory_map.pseudo != 1 AND process_memory_map.path != processes.path AND process_memory_map.permissions LIKE '%x%' GROUP by folder order by cnt desc;

这就说得通了; 我们可以创建一个忽略公共路径的查询。这将帮助我们寻找从非标准位置加载的共享库。

1
SELECT process_memory_map.*, pid as mpid from process_memory_map LEFT JOIN processes USING (pid) WHERE process_memory_map.path LIKE '/%' and process_memory_map.pseudo != 1 AND process_memory_map.path NOT LIKE '/lib/%' AND process_memory_map.path NOT LIKE '/usr/lib%' AND process_memory_map.path != processes.path AND process_memory_map.permissions LIKE '%x%';

在许多情况下,攻击者在加载文件后会从磁盘上删除文件,从而使分析更加困难并避免检测。如果删除注入的.so文件,则可以使用以下查询进行验证,利用该查询来检测何时从磁盘删除了共享库。

1
SELECT process_memory_map.pid, process_memory_map.start, process_memory_map.end, process_memory_map.permissions, process_memory_map.offset, process_memory_map.path from process_memory_map LEFT join file USING (path) where pseudo != 1 AND process_memory_map.path NOT LIKE '/lib/%' AND process_memory_map.path NOT LIKE '/usr/lib%' AND process_memory_map.permissions LIKE '%x%' AND filename IS NULL and process_memory_map.inode !=0 AND process_memory_map.permissions = 'r-xp';

在检测目标进程中注入的代码方面,我们可以使用以下Yara规则。

ReflectiveSOInjection

有趣的是,当我在Virustotal中搜索与这些模式匹配的ELF文件时,我很快发现流行的Pupy RAT实际上在Linux客户端中使用Linux注入

ReflectiveSOInjection是基于linux-inject的工具。主要区别在于共享对象的注入方式。在linux-inject中,shellcode使用__libc_dlopen_mode加载共享对象。ReflectiveSOInjection 将共享库映射到内存,然后强制主程序调用ReflectiveLoader导出。所述ReflectiveLoader负责解析功能,负载所需的库和节目段映射到存储器中。

让我们在与linux-inject一起使用的相同样本目标上使用ReflectiveSOInjection。

如果我们看一下内存映射,我们可以看到有一个新的内存部分标记为rwxp且具有空路径。

此方法的优点是注入的共享对象不必在磁盘上。我们可以使用以下查询来搜寻此活动。

1
SELECT processes.name, process_memory_map.*, pid as mpid from process_memory_map join processes USING (pid) WHERE process_memory_map.permissions = 'rwxp' AND process_memory_map.path = '';

作为额外检测,如果攻击者是懒惰的并且没有修改ReflectiveSOInjection代码,则默认情况下,该代码正在注入的共享库中查找名称为“ ReflectiveLoader”的导出。因此,我们可以编写一个简单的Yara签名来检测具有该导出功能的磁盘上的共享库。

1
import "elf"
2
3
rule ReflectiveLoader : LinuxMalware {
4
                  meta:
5
                                    author = "AlienVault Labs"
6
                                    description = "Detects a shared object with the name of an export used by ReflectiveLoader"
7
                                    reference = "https://github.com/infosecguerrilla/ReflectiveSOInjection"
8
                  condition:
9
                                    uint32(0) == 0x464c457f and
10
                                    elf.type == elf.ET_DYN and
11
                                    for any i in (0..elf.symtab_entries): (
12
                                                      elf.symtab[i].name == "ReflectiveLoader"
13
                                    ) 
14
}

GDB调试

我们要探索的最后一种方法是使用Gnu Project Debugger GDB加载共享对象。从攻击者的角度来看,GDB可能已经安装在目标系统中,并且噪音不如自带工具。使用GDB方法的优点是我们可以利用它。

在幕后,这种方法几乎与linux-inject完全相同。GDB使用ptrace附加到进程,然后调用我们熟悉的相同的__libc_dlopen_mode()函数来加载共享对象。

使用ptrace的gdb

结果与linux-inject相同,我们可以使用相同的查询来寻找。

总之,我们分析了攻击者可以通过多种方式将共享对象注入正在运行的进程中,并且共享了不同的Osquery查询和检测想法,蓝队可以使用这些查询和检测想法来在其环境中寻找这种行为。

总结

监控方法归纳成两种,一种是上文归纳的监控主机文件和map的变化:

  • 进程LD_PRELOAD变量监控
  • 监控文件/etc/ld.so.*/etc/ld.so.conf.d/*完整性变化
  • 检测/proc/[pid]/maps中permissions为rwxp并且path为空
  • 检测/proc/[pid]/maps中path中so文件路径为非常规的路径(或者有deleted标志),dpkg -S [so_path]或者rpm -qf [so_path]可以判断是否是系统的包,需要注意的是LD_PRELOAD/etc/ld.so.*定义的白名单可能会导致误报。

第二种方法可通过监控内核ptrace事件,当然pdb调试时候也会产生相应的事件,但是生产环境就不应该有调试的动作,所以正常情况不是误报,如果有特殊情况再特殊对待。

参考:

https://www.trustedsec.com/blog/linux-hows-my-memory/

https://cybersecurity.att.com/blogs/labs-research/hunting-for-linux-library-injection-with-osquery

https://xz.aliyun.com/t/6883