8-BitQuest
菜单
[APT]

BPFdoor:利用BPF技术绕过防火墙的隐形Linux后门

· 3 分钟阅读 · @admin

BPFdoor:利用BPF技术绕过防火墙的隐形Linux后门 像素封面

  BPFDoor是专门为Linux和Solaris系统设计的后门程序,攻击者可以远程连接到linux shell以获得受害者设备的访问权限,背后所属的APT组织主要针对中东和亚洲的电信提供商以及政府、教育和物流部门的实体。   该后门滥用伯克利数据包过滤器(BPF)来监听网络和过滤网络数据包,这是BPFDoor名字的由来,也是2022年来被发现的第二个基于BPF进行攻击的恶意程序,第一个是由盘古实验室公布的NAS后门bvp47。   PWC研究人员将该恶意程序归因于来自中国的APT组织Red Menshen, 该APT组织为2021年被PWC命名,归因原因是该组织在后利用阶段使用了Gh0st后门变种以及Mimikatz和Metasploit等开源软件、攻击时间与中国地区工作时间吻合等原因。这种归因方式是信誉度比较低的,因为其他国家的APT组织有时也会使用Gh0st后门,比如朝鲜国家支持的APT组织Lazarus。

2.    样本详情

文件名 kdumpflush
Md5 156226c90974180cc4b5f9738e80f1f8
文件类型 ELF64

3.    流程图

4.    详细分析

  BPFDoor不受防火墙规则的约束,并持续监听来自ICMP、UDP和TCP端口的数据包。当接收到包含特定数据的TCP或UDP数据包时,密码验证通过后执行后门操作,比如创建远程shell、修改防火墙规则等等。   使用BPF数据包过滤的方式无需开启入站端口、也不使用出站端口,因此无法被防火墙阻止,并且可以响应来自网络上任意IP地址的命令,非常适合隐身操作。   并且大部分linux操作系统都内置BPF的功能,因此执行数据包过滤所需的CPU开销较小。   BPFDoor使用动态编译时编译后的文件很小,只有几十K;目前发现BPFDoor存在Linux和Solaris SPARC两种系统版本。

4.1 互斥锁机制

恶意程序在首次执行后会创建一个 0 字节的锁文件 /var/run/auditd.lock,用于标识恶意程序已经在运行中。当程序再次执行时会检测该文件是否存在,如果存在则直接退出,从而避免多个实例同时运行。

此外,恶意程序还会调用 getuid() 函数判断当前用户是否为 root 用户,如果不是则退出程序,因为后续的操作(如修改防火墙规则、创建原始套接字等)需要 root 权限才能执行。

在 4.2 节中,当文件 kdumpflush 执行并将自身进程名修改为硬编码的 Linux 守护进程名后,恶意程序会注册 SIGTERM 信号处理函数,确保在正常终止时删除锁文件 auditd.lock。但在非正常终止的情况下(如强制关机或系统崩溃),该文件可能会残留。

如果检测到 auditd.lock 文件已存在,程序将不会启动,因为该文件标识恶意程序可能已经在运行中。

当触发进程结束信号时,程序会判断当前进程 PID 是否为运行中的恶意程序的 PID,如果是则删除标记文件 auditd.lock。

4.2 进程伪装技术

恶意程序根据是否携带命令行参数来执行不同的操作:

第一阶段(无参数执行):

  • 将自身拷贝到目录 /var/lock 并重命名为 kdumpflush
  • 设置文件权限为 755
  • 执行 kdumpflush 并传入命令行参数 --init

需要注意的是,路径 /var/lock 通常用于保存锁定文件,标记某些程序正在使用特定的设备或文件,其他程序发现锁定文件时将不再尝试使用该资源。恶意软件选择这个目录是为了混淆真实意图。

第二阶段(携带 --init 参数执行):

  • 从 10 个硬编码的进程名中随机选择一个进行伪装
  • 调用 daemon() 函数以守护进程的方式在后台运行
  • 使用 chdir() 函数将当前工作目录修改为根目录 /

如下图所示,这些硬编码的进程名看起来都像常见的 Linux 系统守护进程。恶意程序会用其中一个来覆盖参数 argv[0] 的值:

进程伪装的具体实现如下:首先使用伪装进程名覆盖 argv[0],然后调用 prctl() 函数设置当前运行进程的程序名。当使用 ps 命令查询进程时,显示的将是伪装后的进程名。

4.3 时间戳篡改(Timestomping)

恶意程序将自身复制到路径 /dev/shm/kdmtmpflush 后,会修改该文件的时间戳为 1225394236,转换为北京时间为 2008-10-31 03:17:16。

修改文件时间戳的目的是为了:

  • 隐藏对文件的修改痕迹
  • 使文件看起来像是多年前就存在的系统文件
  • 降低安全人员在时间线分析中发现该文件的可能性

4.4 BPF 过滤器安装与激活

恶意程序完成前述所有准备工作后,进入 packet_loop() 函数,该函数负责实现 BPF 过滤器的激活和数据包解析。

恶意程序使用 AF_PACKET 地址族创建一个类型为 SOCK_RAW 的原始套接字,从而能够监听网络流量并应用 BPF 数据包过滤器。BPF 会根据 g_bpfcode 中定义的规则来允许或拒绝特定类型的数据通过 Socket,因此恶意程序只需要处理通过过滤器的数据包,而不需要处理所有的网络数据包,大大提高了效率。

恶意程序通过调用 setsockopt() 函数来安装 BPF 过滤器,过滤器以字节码的形式加载。字节码 g_bpfcode 如下图所示:

转换成 BPF 汇编指令如下。其中注释为功能描述,在 BPF 汇编中 A 寄存器作为累加器,X 为索引寄存器,内存引用的数据为监听到的网络数据包内容。

// 12为EtherType在Ethernet的偏移,加载该值到A
l0:  ldh [12]  
//判断EtherType是否为IPv4,是则跳转到l2否则跳转到l29
l1:  jeq #0x800, l2, l29 
//14为Ipv4在Ethernet的偏移,9为Protocol在Ipv4的偏移,加载该值到A
l2:   ldb [23]               
//如果Protocol等于UDP跳转到l4,否则跳转到l9。
l3:   jeq #0x11, l4, l9      
// 读取fragment偏移
l4:   ldh [20]               
// 如果fragment偏移不等于0跳转到l29
l5:   jset #0x1fff, l29, l6  
//获取ipv4头部长度保存到寄存器X
l6:   ldxb 4*([14]&0xf)      
//读取UDP 的datagram数据保存到A
l7:   ldh [x+22]             
//如果A等于0x7255跳转到l28,否则l29
l8:   jeq #0x7255, l28, l29  
//如果Protocol等于ICMP,跳转到l10,否则跳转到l17。
//该位置是由l13跳转过来的,因此A为ipv4 protocol的值
l9:   jeq #0x1, l10, l17     
// 读取fragment偏移
l10:  ldh [20]               
// 如果fragment偏移不等于0跳转到l29
l11:  jset #0x1fff, l29, l12 
// 读取IP层头部长度
l12:  ldxb 4*([14]&0xf)     
// 读取ICMP数据
l13:  ldh [x+22]            
// 判断ICMP第一个word值是否为0x7255,如果时跳转l15否则跳转l29
l14:  jeq #0x7255, l15, l29 
// 读取ICMP类型
l15:  ldb [x+14]    
//判断ICMP类型是否是ping,如果是跳转到l28,否则跳转l29。
l16:  jeq #0x8, l28, l29     
//如果protocol为TCP跳转到l18,否则l29。
l17:  jeq #0x6, l18, l29     
// 读取fragment偏移
l18:  ldh [20]               
// 如果fragment偏移不等于0跳转到l29
l19:  jset #0x1fff, l29, l20 
// 读取IP层头部长度
l20:  ldxb 4*([14]&0xf)      
// 读取TCP segment偏移
l21:  ldb [x+26]          
//计算TCP手机壳i【哦啊木哦
l22:  and #0xf0
//计算TCP头部长度            
l23:  rsh #2                 
l24:  add x 
l25:  tax        
// A = halfword from packet offset X+14 (14 is ethernet header, x is IPv4+TCP header, so this offset is first TCP payload byte)
l26:  ldh [x+14] 
//如果A==0x5293跳转到分支l28否则跳转分支l29
l27:  jeq #0x5293, l28, l29
// 命中返回非0值
l28:  ret #0xffff            
// 未命中返回0
l29:  ret #0 

  反汇编后的汇编代码可读性很差,使用伪代码来表示大致代码如下

if(EtherType != IPv4)
    return false;
if(数据包为IP层分片包的一部分)
    return false;
//验证magic字节
if(protocol==udp&& data[0:2]==0x7255)
    return true;
if(protocol==ICMP&&data[0:2]==0x7255 && ICMP类型==Echo请求)
    return true;
if(protocol==TCP&&data[0:2]==0x5293)
    return true;
return false;

  数据包通过BPF过滤器后使用结构体sniff_ethernet、sniff_tcp、sniff_udp和sniff_icmp来解析对应的数据包找到payload数据,payload使用结构体magic_packet,内存布局如下:

struct magic_packet{
   unsigned int    flag;
   in_addr_t       ip;//攻击者IP
   unsigned short  port;//攻击者使用的端口
   char   pass[14];//验证密码
};

  由于不同的协议magicPacket所处的位置不一样,需要解析协议找到payload部分。

4.5 防火墙绕过机制

4.5.1 密码验证流程

BPF 过滤器在内核网络栈的早期阶段接收或丢弃数据包,这一时机早于防火墙规则的应用。因此,即使防火墙正在运行,恶意程序仍然能够接收到包含特定 Magic 值的数据包。

如果防火墙只允许特定端口通过(例如 MySQL 的 3306、HTTPS 的 443 或 HTTP 的 80 等),攻击者仍然可以向这些端口发送包含特定数据的数据包来激活后门功能,实现端口复用攻击。

恶意程序收到包含特定数据的数据包后,从 Magic Packet 中提取攻击者的 IP 地址;如果未指定,则从数据包的 IP 协议层读取源 IP 地址。恶意程序随后调用 fork() 函数创建一个子进程用于与攻击者通信,并将该进程名伪装为 usr/libexec/postfix/master。

要成功执行后门命令,还需要通过密码验证。密码从接收到的 Magic Packet 中提取,该密码不仅用于数据包验证,还作为 RC4 算法加密通信流量的密钥。

恶意程序硬编码了两个密码的 MD5 哈希值。由于使用的是单向哈希算法,很难逆向破解出原始密码。密码验证流程如下:

  1. 从 Magic Packet 中获取密码
  2. 将密码拼接到盐值字符串 I5*AYbs@LdaWbsO 后面
  3. 计算拼接后字符串的 MD5 值
  4. 判断是否与内置的两个密码哈希匹配

恶意程序根据密码匹配结果执行不同的操作:

  • 密码匹配失败:发送一个值为 “1” 的 UDP 数据包,可能用于检测恶意程序是否存活
  • 匹配 pass2:建立本地 Bind Shell
  • 匹配 pass1:建立反向 Bind Shell,连接到 Magic Packet 中指定的 IP 和端口

2. BindShell

2.1 本地BindShell

  恶意程序从端口42391到43390一次选择一个端口用于创建本地bindShell,然后修改防火墙规则将来自攻击者的IP的所有流量从请求的端口重定向到选择的端口。   修改的防火墙规则如下,在创建bindshell之前添加防火墙规则用于接收和重定向来自攻击者的流量,在接收数据后删除添加的两条防火墙规则避免被发现。这种数据包重定向的方法比较隐蔽,也更难找到,因为它能够重定向许多使用常用端口的数据流量,比如ssh、https和mysql等应用使用的端口,用抓包软件抓包的流量看起来就像是合法的流量。

2.2 反向BindShell

  如果数据包magicPacket中的地址_ip2为不为空或0xFFFFFFFF,则向该地址发送ICMP数据包来转发恶意程序接收到的magicPacket和当前进程PID;否则就是恶意程序主动连接magicPacket中指定的攻击者IP和端口然后创建BindShell。  

2.3 创建BindShell

  当前进程是被伪装成”usr/libexec/postfix/master”来执行的,该进程fork出一个子进程用于执行shell操作,并且进程名伪装成”qmgr -l -t fifo -u”,设置了一些环境变量告知shell不要记录某些信息。   Linux恶意程序进程使用的shell一般都是通过管道来输入输出,对于一些需要编辑修改数据的shell命令就无法正常使用了,BPFdoor的方法就是创建伪终端来执行shell命令。   当要执行的命令字符中包含字符’\v’时执行修改伪终端窗口大小,然后再执行shell命令。分隔符’\v’前面四个字节指定要设置的窗口大小,后门字符为要执行的shell命令。   攻击者与受害者之间shell通信流量使用RC4算法加密,密钥由接收的magicPacket数据包中的字段pass指定。

6.    IOC

/var/run/auditd.lock Kdumpflush 156226c90974180cc4b5f9738e80f1f8

7.    ATT&CK矩阵

Tactic ID Name Description
Execution T1059.004 Command and Scripting Interpreter:Unix Shell 执行unix shell命令
T1204.002 User Execution: Malicious File 受害者执行恶意文件
Persistence T1205.001 Traffic Signaling :Port Knocking 使用PortKnocking技术来激活后门程序
Defense Evasion T1562.004 Impair Defenses:<br><br>Disable or Modify System Firewall 修改防火墙规则重定向通信流量数据
T1070.004 Indicator Removal on Host: File Deletion 删除锁定文件auditd.lock
T1070.006 Indicator Removal on Host: Timestomp 修改文件时间
T1036 Masquerading 进程伪装成linux daemon程序。

8. 参考

https://www.tcpdump.org/pcap.html https://www.kernel.org/doc/Documentation/networking/filter.txt https://www.sandflysecurity.com/blog/bpfdoor-an-evasive-linux-backdoor-technical-analysis/ https://www.countercraftsec.com/blog/a-step-by-step-bpfdoor-compromise/

更多文章