linux下恶意软件的七种反分析技术

  • 7 类主流的 Linux 恶意软件反分析/检测躲避技术
    • 反调试(Anti-Debug): 软件调试是恶意软件分析的常⽤⼿段之⼀,但恶意软件可以通过识别调试器特征,实现⾃⾝恶意⾏为的隐藏,或导致调试失败,从⽽规避分析与检测
    • 反内存转储(Anti-Memory-Dump):内存转储是恶意软件取证分析的重要⼿段,但恶意软件可通过更改代码数据结构实现⾃⾝恶意⾏为的隐藏或导致内存转储失败,从⽽规避分析检测
    • 反模拟执⾏(Anti-Emulation):⼆进制程序模拟执⾏是恶意软件深度分析的常⽤技术之⼀,但恶意软件可以针对常⽤模拟器(如 QEMU)的某些指纹特征,实现⾃⾝恶意⾏为的隐藏,或导致模拟执⾏失败,从⽽规避分析检测
    • 反代码插桩(Anti-Instrumentation):⼆进制程序代码插桩(DBI)技术通常采⽤ JIT 编译器,实现运⾏时代码插桩,是重要的恶意软件分析与检测⼿段,但恶意软件也可以通过比较插桩前后差异(如特定寄存器)探测到执⾏环境异常,实现⾃⾝恶意⾏为隐藏或导致插桩执⾏失败,从⽽规避分析检测
    • 代码注入(Code Injection):恶意软件可将⾃⾝注入合法进程实现攻击⾏为隐藏,从⽽规避分析检测,实现⽆文件攻击和攻击持久化等⽬标
    • 迟滞代码(Stalling Code):恶意软件通过迟滞恶意⾏为导致动态分析检测沙箱超时,从⽽规避检测
    • 运⾏环境测量(Runtime Measurement):通过测量特定⾏为(如指令、API 调⽤等)执⾏时间,探测当前执⾏环境中是否存在检测探针(例如:采⽤ API hook,VM/Emulator 的检测⽅法)

反代码插桩(Anti-Instrumentation)

代码插桩(Code Instrumentation) 是一种在程序代码中插入额外指令或代码片段的技术。这通常是为了收集执行时的信息、监控程序行为、调试代码或进行性能分析。
⼆进制程序代码插桩(DBI)技术通常采⽤ JIT 编译器,实现运⾏
反代码插桩,是重要的恶意软件分析与检测⼿段,但恶意软件也可以通过比较插桩前后差异(如特定寄存器)探测到执⾏环境异常,实现⾃⾝恶意⾏为隐藏或导致插桩执⾏失败,从⽽规避分析检测

代码加密和压缩

使用加密和压缩技术对恶意代码进行加密和压缩,使代码难以解析和识别,从而阻止了动态检测

插桩检测(Instrumentation detection)

恶意软件检查程序的代码部分是否已被修改。如果修改,则更改程序的执行并异常退出

抵制调试(Resist debugging)

“Resist debugging”(抵抗调试)是一种在软件中采取措施以防止被调试的技术。当程序被调试时,调试器(debugger)可以访问程序的内部状态、内存内容和执行路径,这为逆向工程和分析提供了便利。因此,一些软件开发者或软件保护方案可能会采取措施,以抵抗调试,增加对逆向工程的难度。
这些技术的目标是增加逆向工程的难度,以保护软件免受未经授权的分析和修改。然而,反调试技术并不是绝对的,有经验的逆向工程师通常能够找到方法来绕过这些保护机制

真实的恶意软件示例Mirai:

  • 简介:
    Mirai 是一种恶意软件,最初在2016年首次被发现。它专门针对物联网(IoT)设备,例如网络摄像头、路由器和智能家居设备。Mirai 的主要目标是通过感染这些设备来创建一个庞大的僵尸网络,该网络可以被用于发起分布式拒绝服务攻击(DDoS 攻击)。
    它发布后被攻击者开源,可以在Github上直接访问源代码
  • 示例
    这是几个C语言中用于信号处理的函数,用来定义程序接收到特定信号时的行为
    sigemptyset(&sigs);
    sigaddset($set, SIGINT);
    sigprocmask(SIG_BLOCK, &sigs, NULL);
    signal(SIGCHLD, SIG_IGN);
    signal(SIGTRAP, &anti_gdb_entry);
    
    1. sigemptyset(&sigs);:
      作用: 该函数用于初始化一个信号集,将指定的信号集 sigs 清空,即将其中的所有信号都设置为未阻塞。
      参数: &sigs 是指向信号集的指针。
    2. sigaddset($set, SIGINT);:
      作用: 该函数用于向信号集中添加一个特定的信号,此处是将 SIGINT(中断信号)添加到 $set 信号集中。
      参数: $set 是一个信号集。
    3. sigprocmask(SIG_BLOCK, &sigs, NULL);:
      作用: 此函数用于修改当前进程的信号屏蔽集。在这里,它将 sigs 中的信号添加到当前进程的信号屏蔽集中,即将这些信号设置为被阻塞,使得它们在阻塞状态下不会被处理。
      参数: SIG_BLOCK 表示将信号集添加到当前信号屏蔽集,&sigs 是要添加的信号集,最后一个参数为 NULL 表示不保存原来的信号屏蔽集。
      4. signal(SIGCHLD, SIG_IGN);:
      作用: 该函数用于指定对信号 SIGCHLD(子进程状态改变信号)的处理方式。在这里,通过设置为 SIG_IGN,表示忽略 SIGCHLD 信号,即子进程终止时不会创建僵尸进程。
      参数: SIGCHLD 是要设置处理方式的信号,SIG_IGN 表示忽略。
    4. signal(SIGTRAP, &anti_gdb_entry);:
      作用: 该函数用于指定对信号 SIGTRAP(调试中断信号)的处理方式,这里将其设置为调用 anti_gdb_entry 函数。
      参数: SIGTRAP 是要设置处理方式的信号,&anti_gdb_entry 是处理该信号时调用的函数。

使用sigemptyset()和sigaddset()分别清除和添加信号集,并将SIGINT信号添加到信号集。使用sigprocmask()函数来阻止信号集,以防止程序运行时其他进程或信号处理程序将其发送到此进程。使用signal()函数设置信号处理程序,设置要忽略的SIGCHLD信号,并设置要由反gdb-entry()函数处理的SIGTRAP信号。CPU将在硬件断点处触发中断信号(SIGTRAP),并暂停程序的执行。

Mirai使用了一系列技术,通过信号攻击来防止被调试器检测和分析

  1. 修改中断信号处理功能
    Mirai 修改了中断信号(如SIGTRAP)的处理方式,将其指向一个自定义的函数 anti_gdb_entry()。这样,当调试器(如 GDB)尝试对被感染的程序进行调试时,会调用这个自定义的函数。
  2. 检查调试符号表和调试信息表
    在 anti_gdb_entry() 方法中,Mirai 进行检查以确认是否存在调试符号表和调试信息表。这是为了识别是否有调试工具正在尝试分析程序。
  3. 使用特殊指令检查调试器状态
    Mirai 使用特殊指令检查调试器的运行状态。这可能涉及检查调试器标志或执行特殊的指令以确定是否在调试模式下运行。
  4. 修改CPU寄存器中的指令指针
    如果检测到调试器正在运行,Mirai 将修改 CPU 寄存器中的指令指针,使其指向下一条指令的地址。这样,当程序在调试器中停止时,Mirai 能够通过跳过断点等措施继续执行,防止被调试器检测到。
  5. 输出大量无用信息
    当 GDB 连接到被感染的程序并停止其运行时,Mirai 会通过输出大量的空格字符和 ANSI 转义序列到控制台,使 GDB 输出窗口充满大量无用的信息。这增加了分析者在调试过程中的困难,使输出窗口难以阅读。

DBI工具检测

Dynamic Binary Instrumentation (DBI) 工具用于在运行时分析和修改二进制程序的行为。这类工具常用于调试、性能分析、安全研究等领域。然而,一些恶意软件或反分析技术可能会尝试检测 DBI 工具的存在,以防止被分析。

反调试

许多恶意代码使用反调试技术来识别它们是否正在被调试或禁用调试器。恶意代码编写者意识到,分析人员经常使用调试器来观察恶意代码的操作,因此他们使用反调试技术来尽可能延长恶意代码的分析时间。为了防止调试器进行分析,当恶意代码意识到它正在被调试时,它可能会更改正常执行路径或修改自己的程序以崩溃,从而增加调试时间和复杂性。

插入"int3"

"int3"是一种用于在x86架构上触发调试中断的汇编指令。它实际上是一条中断指令,它引发的是中断号为3的中断。在Intel x86体系结构中,中断号3通常用于调试目的。
int3 指令的作用是在程序执行过程中插入一个软中断,这可以被调试器(如GDB)捕获。一旦中断被触发,操作系统或调试器会停止程序的执行,并将控制权传递给调试器,以便进行相应的调试操作。
当调试器想要在某一点停止,即设置断点或断点时,它将使用“int3”(0xcc)。在旧版本的gdb中,我们可以通过识别这个字符来判断它是否处于调试状态,但在新版本中它无法工作。但是我们可以尝试在代码中插入“int3”字符来破坏程序。
在一些恶意软件分析和逆向工程的情境中,int3 指令有时被用来干扰调试工具的运行,例如通过检测调试器的存在或动态修改代码来绕过调试。
在这个例子中,我们捕获SigTrap信号,这样程序在正常操作过程中就不会崩溃,并启动一个“int3”,它以类似于断点的方式在调试器中触发SigTrap

// Insert ’int3 ’
#include 
#include 
#include 

// 信号处理器,对 SIGTRAP 信号的处理函数
void handler(int signo) {}

// 在函数中插入 int3 指令的函数
void PassByInt3() {
    // 设置对 SIGTRAP 信号的处理函数为 handler
    signal(SIGTRAP, handler);
    
    // 在汇编中插入 nop 和 int3 指令
    asm("nop\n\t" "int3\n\t");
    
    // 这条语句将在 int3 执行成功后被打印
    printf("Code executes successfully\n");
}

int main(int argc, char *argv[]) {
    // 调用函数插入 int3 指令
    PassByInt3();
    
    return 0;
}

使用ptrace()

ptrace()系统调用用于进程跟踪。它为父进程提供了观察和控制其子进程执行的能力,并允许父进程检查和替换子进程内核映像(包括寄存器)的值。许多调试工具都是基于此功能进行调试的。但是,每个进程只能调用一个ptrace()。只要我们试图在代码中跟踪自己,就会出现错误

进程识别

交互进程有一个标识符,称为会话的“Leader”进程,即SID。我们可以通过父进程(PPID)和引导进程(SID)之间的差异来判断执行应用程序时是否存在中间程序,也就是我们的调试工具。通过关注getsid()、getppid()、getpgid()和getpgrp()的值,我们可以检测调试器当前是否正在运行。

参数和环境变量

从bash-shell解释文档中,我们可以看到环境变量“-”的值将包含已运行的每个应用程序的全名。在正常环境中,此变量的值应等于/path/argv[0]。但是,在调试器中,这个变量的值经常被修改,因此我们可以通过比较这两个值来知道它当前是否处于调试环境中

运行时检测Runtime detection

当程序处于调试状态时,由于调试期间的断点、内存检查和其他操作,运行时间通常比正常时间长得多。因此,如果程序运行时间过长,可能是由于正在调试。具体来说,当程序启动时,通过警报设置计时器,当计时器到达时,程序终止。但是,对于一些调试器(如gdb),我们可以设置gdb处理信号的方式。如果我们选择忽略SIGALRM而不是将其传递给程序,则不会执行alarmHandler。我们需要考虑其他实时检测运行时的方法。

进程插入

使用Ptrace

Ptrace是Unix系列系统的系统调用之一。它的主要功能是跟踪过程。对于目标进程,执行流量控制,读取和写入用户寄存器值,以及读取和修改内存。这个特性非常适合编写实现和远程代码注入。大多数病毒都会使用它来实现自用空间注入、rip位置直接注入以及文本和数据段之间的间隙注入。

使用LD PRELOAD

LD PRELOAD是Linux/Unix系统的一个环境变量。它会影响程序的运行时链接器。它允许在程序运行之前定义要首先加载的动态链接库。该函数主要用于在不同的动态链接库中选择性地加载相同的函数。通过这个环境变量,我们可以在主程序与其动态链接库之间加载其他动态链接库,甚至可以覆盖正常的函数库

使用Proc文件系统

Proc是一个虚拟文件系统,安装在Linux系统中的/Proc目录上。Proc具有多种功能,包括允许用户访问内核信息或使用它进行故障排除。其中一个非常有用的功能是能够以文本流的形式访问进程信息,这在Linux中变得更加独特。在Proc中,有两个伪文件,即Proc/pid/maps和Proc/pid/mem。

proc/pid/mem提供了进程使用的完整内存空间的稀疏架构,并结合从maps文件获得的偏移量,mem文件可以用于读取和写入进程的内存空间。如果偏移量不正确,或者从起始位置按顺序读取文件,则会返回读写错误,因为这相当于读取无法访问的未分配内存。

通过这两个文件,可以定位目标进程中的函数。使用这些函数地址可以替换当前堆栈上的正常返回地址,并完成进程注入。

失速代码

特点

  1. 指令序列在分析环境中的运行速度比在真实(本地)主机上慢得多
  2. 他整个执行必须花费不可忽略的时间

在这里,不可忽略的必须与分配给程序自动分析的总时间联系起来

运行时间测量

恶意软件的运行时度量是指在程序运行时收集信息的行为。这些信息可能包括系统配置、进程信息、文件系统结构、网络连接等。这些信息可以帮助恶意软件适应不同的环境,或者帮助它们在感染计算机后进行更多攻击。通过编译语言插入,我们可以获得CPU品牌、供应商、版本和Hypervisor模式状态等信息。

反仿真 anti-emulation

许多恶意代码使用反仿真技术来阻止研究人员在沙盒中检查程序。反仿真技术包括硬件检查、环境检查和时间分析三种方法

硬件检测

从字面上看,硬件检查就是检查模拟器不支持的硬件命令和行为。这是因为虚拟机/模拟器对某些指令的实现与物理机不同。首先,官方arm手册对某些指令的返回值有所保留,未定义的返回值由处理器制造商自行返回。第二种是,qemu等模拟器在执行特殊指令时可能与arm手册不一致,包括arm自己的fvp试剂盒,该试剂盒也与手册不一致。因此,通过检查指令的特性,它不仅有助于识别它是否在模拟器中运行,而且有助于确定它在哪个模拟器中运行。

环境检测

环境检查是恶意软件检测系统环境中是否存在模拟器相关内容的地方。通常模拟器中的环境变量会与物理机器不同,例如硬盘、NIC信息和制造商。

时序分析

时序分析是检测程序是否在模拟器中执行的常用方法。在物理机器上,一些与加密、解密、媒体编码和解码相关的指令由硬件加速。然而,在模拟器中,指令不是由硬件加速的,而是由CPU计算的,这导致了执行时间的指数差。通过这种方式,恶意软件可以很容易地检测它是否在模拟器中执行。

反内存转储 Anti-Memory-Dump

“反内存转储”(Anti-Memory Dumping)是指一类技术和手段,旨在防止或干扰对程序内存的转储操作。在计算机安全领域,内存转储是指将程序的内存内容写入文件,通常是为了进行分析、调试或逆向工程。反内存转储技术旨在对抗这种操作,使得攻击者难以获取敏感信息或分析程序的内部结构

反内存转储是用于防止恶意代码分析的常用方法。通常,恶意软件实际上无法阻止我们转储物理内存,但恶意软件可以对代码进行模糊处理并加密,只有在代码执行时才能解密。恶意软件通常会虚拟化指令并使用虚拟指令解释器。重新配置寄存器和指令,以重新模拟CPU的本机指令,并形成一个虚拟指令集,该虚拟指令集依赖解释器将它们解释为本机指令以供执行,解释器只处理当前获取的指令。

你可能感兴趣的:(系统安全,linux,服务器)