论文阅读笔记: Understanding Linux Malware

今天阅读的论文是2018年S&P会议上一篇综述了Linux平台恶意软件对抗技术的论文: Understanding Linux Malware.

由于windows平台用户基数庞大, 所以过去的二十年来基本都是对win平台的恶意软件分析更多, 但是对linux平台的恶意分析只能通过寥寥的分析文章来窥见. 但是随着近几年的物联网嵌入式设备的崛起, linux平台也变得愈发重要起来. 而该论文就是对收集到的10,548个恶意样本进行分析, 总结了linux平台上恶意软件的对抗技术.

遇到的挑战

linux恶意样本有着以下挑战:

  1. 多样性: 不仅是平台架构的多样性, 还有目标的多样性(比如可以攻击摄像头, 路由器, 打印机等设备), 执行环境的多样性(需要底层执行环境或依赖库, 比如嵌入式常用的uClibc, musl-glibc, eglibc等), 系统的多样性(不同的二进制为不同的系统编译, 当静态编译时, 就会因为系统直接的差异而变得无法运行),
  2. 静态链接带来的麻烦: 静态链接能保证一定的可移植性, 但是文件体积会过于庞大而对逆向带来困难. 并且当内核ABI与期望的内核不一致时, 静态编译文件可能会在运行时崩溃.
  3. 分析环境: 沙箱需要尽可能模拟恶意样本的运行环境, 同时也要考虑到应用执行的身份和权限. 因为很多恶意软件在编写时就考虑了以root身份运行时的行为, 但是如果给了root权限, 又有可能使得恶意样本有了修改沙箱的权利并使得观察变得复杂.
  4. 缺乏历史资料: 缺乏分析, 缺乏数据集, 缺乏一个全面的报告.

分析流

作者收集了16年至17年VirusTotal上的Linux恶意样本, 并构建了如下的分析流对恶意样本进行大规模分析.

pipeline

元数据分析

使用提取到到元数据来筛选掉与分析无关的文件, 比如共享库文件, 核心转储, 损坏的文件及其他文件, 并可以用于识别异常的文件结构. 异常的文件结构能在保证样本正常运行的前提下阻止对文件的分析, 并使得现有的工具无法进行正确地处理.

最后从VirusTotal输出的报告里提取样本的AV标签, 并输入给AVClass对样本分类.

静态分析

静态分析包括两个部分: 二进制代码分析和保护壳检测.

二进制代码分析依靠大量的IDA脚本来提取指标: 函数总数量, 函数的尺寸及其循环复杂程度, 整体覆盖范围, 重叠指令和其他汇编技巧, 系统调用以及直接/间接分支指令的数量. 此外还有一些聚合指标, 比如操作码的分布, 不同代码/数据段的熵值等用于统计或集成到某个分析组件里.

其次就是要将ELF文件头中提取的信息与二进制代码分析结合起来, 以识别是否加壳.

动态分析

动态分析分为两块, 一块是在虚拟机中运行样本5分钟, 另一块则是进行保护壳分析并尝试脱壳. 对于虚拟机来说, x86平台则基于kvm进行硬件模拟(速度更快), 而其他架构则用qemu进行模拟. 样本的分发有另外的程序控制, 并对各个虚拟机做好了快照以供分析后恢复. 虚拟机在制作时也会预装基础的软件和依赖库.

插装则使用SystemTap来实现内核探针(kprobes)和用户探针(uprobes). 虽然文档中宣称SystemTap支持各种不同的体系结构, 但除x86外的其他结构还是需要进行适当的补丁修改才行. 作者还额外设计来SystemTap探针来收集系统调用信息, 并重新编译了glibc用于尽可能地收集有关字符串和内存操作函数的uprobes.

分析结束时, 沙箱会输出报告, 其中包含对系统调用和用户空间函数的完整跟踪信息, 并且可以根据输出的信息来确认恶意样本是否尝试测试了其用户权限或者做了某些权限不足而失败的操作, 这样系统会重新以root身份再次运行分析样本.

当然沙箱还为样本提供了部分的网络访问权限并记录网络流量. 而在脱壳方面, 作者基于unicorn开发了一款模拟执行的工具, 能够运行样本进行脱壳.

对抗技术

ELF文件头畸变

篡改ELF文件头的某些字段和结构是攻击者对抗工具分析的第一步.

内核在将ELF文件载入内存前就需要获取一些字段的信息, 比如e_ident(用于标识文件类型), e_type(用于指定对象类别), e_machine(指定机器架构).

攻击者经常篡改ELF文件头, 产出异常文件(但仍遵循ELF规范)以及无效文件(但仍然可以正确被系统运行)

  1. 异常ELF: 移除了ELF的部分信息, 或者对ELF某些标识字段进行伪造和误导
  2. 无效ELF: 损坏节区信息, 通常是文件头的e_shoff e_shnum e_shentsize字段

持久化驻留

通过修改感染主机上的配置, 让恶意样本能重复运行. 技术大体分为以下4类:

  1. 子系统初始化. 修改系统的rc脚本, 使得在系统的启动初始化过程中重新运行. 这非常依赖于system-v初始化系统, 并且这需要root权限.
  2. 定时执行. 依赖cron在某个时间间隔内重新运行. 也需要一定权限.
  3. 文件感染和替换. 感染主机上已存在的应用程序.
  4. 感染用户文件. 比如~/.bashrc, 但是除了受感染的用户会受影响外, 其他用户不受此影响.

伪装良性程序

用看起来无害或者容易误认为良性程序的名字来掩盖嫌疑.

请求特权

恶意软件会请求root权限, 并且在特权和非特权下的行为表现会大相径庭.

加壳与多态

与windows多样的保护壳相反, linux下的加壳工具非常少, 并且实用性也很差, 仅有UPX有得到运用.

进程交互

  1. 多进程: 主要用于守护和ddos攻击/僵尸网络
  2. 执行shell命令: 使用如sed, cp, chmod之类的命令用于持久化, 使用rm来删除痕迹等等, 使用iptables打开网络端口
  3. 进程注入: 通过将代码注入到正在运行的进程中以改变行为, 使得样本更加难以调试, 或者hook有趣的功能以窃取信息. 有以下三种技术可以写入另一个程序的内存:
    • 请求PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_POKEUSER的ptrace系统调用
    • PTRACE_ATTACH请求, 然后对/proc/<TARGET_PID>/mem进行读写
    • 调用process_vm_writev

信息收集

收集信息用于检测沙箱的存在, 或者控制样本执行.

  1. proc和sysfs文件系统. proc和sysfs虚拟文件系统分别包含有关进程, 系统, 硬件配置等运行时系统信息, 以及有关内核子系统, 硬件设备和内核驱动的信息.
  2. 配置文件. 用于实现持久化, 或者是跟网络香港的配置文件, 以及跟账户密码相关的配置文件.

逃逸

逃逸的目的在于隐藏恶意行为, 使得长期不被发现.

  1. 沙箱特征检测. 通过检测沙箱特征, 获取root特权以检测环境等方式.
  2. 进程枚举: 不过linux下等进程枚举通常是用于测试当前主机是否已被感染, 或者确定要杀死等目标进程(比如av, 其他干扰挖坑的程序)
  3. 反调试: 常见的反调试技术都是基于ptrace系统调用. ptrace能让调试器去调试程序, 但是ptrace只能调试一个程序, 因此可以通过ptrace来检测是否有调试正在运行, 或者调试自身以进行保护.
  4. 反执行: 有的恶意软件会将自己的文件名与硬编码字符串进行比较, 因为沙箱通常会在分析前重命名文件.
  5. 停顿: 延迟样本执行恶意行为的时间. 因为沙箱通常只会运行几分钟. 不过linux下貌似更多用sleep来协调子进程或网络通信

依赖库

动态链接可以复用本地代码, 而静态链接可以更方便移植. linux更多依赖于glibc和uclibc. 然后还有就是其他的各种依赖库

libraries