在前两篇文章中,我们以NSA组织“二次约会”间谍软件的1048/1060通信载荷样本作为案例,对其样本代码、会话劫持原理、网络通信模型进行了详细的剖析;同时我们还对所有公开的“二次约会”间谍软件的版本信息、远控指令进行了梳理及对比分析,我们发现NSA组织“二次约会”间谍软件支持在Linux、FreeBSD、Solaris、JunOS等各类操作系统上运行,适用范围较广。
由于前两篇文章中,我们研究分析的1048/1060字节通信载荷样本均是在linux系统中运行,因此我们接下来尝试对适用于freebsd平台“二次约会”样本进行研究分析:
通过对“二次约会”各版本样本的功能代码进行对比,发现随着样本的版本迭代,此系列样本存在以下几个方面的演变过程:
基于《NSA组织“二次约会”间谍软件通信模型剖析与对比》文章中“样本版本梳理及对比”章节内容,笔者发现:
通过对比分析,笔者发现:
通过对比分析,笔者发现:
通过对比分析,笔者发现:
通过对比分析,笔者发现:
rulewizard指令截图如下:
通过对比分析,笔者发现:
低版本样本代码截图如下:
高版本样本代码截图如下:
需要对适用于freebsd平台的“二次约会”样本进行研究,则需要构建freebsd操作系统环境。
通过对比分析所有公开的freebsd平台样本,发现其指令集架构为x86、x86_64,因此我们只需要安装对应的操作系统即可,相关文件信息如下:
通过文件信息可知,相关freebsd间谍软件对应的系统版本为4.4(2001年发布)、5.0(2003年发布)、5.4(2004年发布),因此,笔者准备尝试安装上述操作系统,通过搜索引擎查找发现可通过 http://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/ISO-IMAGES/4.4/ 网站下载安装低版本的freebsd系统;成功下载待安装镜像后,笔者开始尝试构建低版本freebsd系统,逻辑上安装操作系统应该很简单,但上述操作系统版本实在太老且无相关资料,因此,在操作系统构建过程中发现了不少问题,导致一直无法在操作系统中执行第三方程序。
鉴于无法安装适当的操作系统运行环境,便只能更换方案,安装其他版本的操作系统。
最终,笔者最后选择使用的是 https://mirrors.aliyun.com/freebsd/releases/i386/i386/ISO-IMAGES/12.4/?spm=a2c6h.25603864.0.0.7c632d0aqiXTjB 网站下的FreeBSD-12.4-RELEASE-i386-dvd1.iso镜像文件,基于此镜像文件,可成功安装操作系统和第三方包,第三方包包括:逆向分析所需的gdb、ssh等工具,以及间谍软件运行所需的库文件。
#更新软件包索引 pkg update #安装gdb pkg install gdb
在开始样本分析前,发现缺少"libc.so.4"、“libc.so.6”等链接库,相关截图如下:
经过分析,发现可执行如下命令处理:
#提示找不到“libc.so.x”文件
pkg install misc/compat4x
pkg install misc/compat5x
pkg install misc/compat6x
pkg install misc/compat7x
pkg install misc/compat8x
pkg install misc/compat9x
本次案例中,使用seconddate_ImplantStandalone_1.7.0.3_trunklien样本作为调试案例。
在尝试对freebsd平台seconddate_ImplantStandalone_1.7.0.3_trunklien样本进行调试时,发现运行样本后,进程列表中无此进程,并且使用控制端也无法正常连接;同时,运行样本后,也未发现任何报错情况,相关截图如下:
由于程序无法运行,因此推测可能是由以下原因引起:
尝试使用gdb调试,发现可以使用gdb开展正常调试活动,确定此样本是可以在此系统环境中运行的,相关截图如下:
在确定此样本是可以在此系统环境中运行后,于是,笔者开始正常的调试活动,但在调试过程中,又发现在进入核心功能代码前,无论怎么下断点,程序均会异常退出调试,相关运行情况如下:
root@freebsdOS:~ # gdb seconddate_ImplantStandalone_1.7.0.3_trunklien GNU gdb (GDB) 13.2 [GDB v13.2 for FreeBSD] Copyright (C) 2023 Free Software Foundation, Inc. ...... (gdb) set follow-fork-mode child (gdb) set disassembly-flavor intel (gdb) display /i $pc 1: x/i $pc <error: No registers.> (gdb) b *0x080495F4 #start函数 Breakpoint 1 at 0x80495f4 (gdb) r Starting program: /root/seconddate_ImplantStandalone_1.7.0.3_trunklien Breakpoint 1, 0x080495f4 in ?? () 1: x/i $pc => 0x80495f4: push ebp (gdb) b *0x0804992A Breakpoint 2 at 0x804992a (gdb) c Continuing. [Attaching after LWP 100128 of process 952 fork to child LWP 100126 of process 954] [New inferior 2 (process 954)] [Detaching after fork from parent process 952] [Inferior 1 (process 952) detached] [Attaching after LWP 100126 of process 954 fork to child LWP 100130 of process 955] [New inferior 3 (process 955)] [Detaching after fork from parent process 954] [Inferior 2 (process 954) detached] [Switching to LWP 100130 of process 955] Thread 3.1 hit Breakpoint 2, 0x0804992a in ?? () (gdb)
相关截图如下:
确定问题后,尝试对问题进行排查,对报错前的代码进行调试分析,发现在执行核心功能代码前,样本将执行部分初始化操作,例如开启子进程等行为,当样本调用fork函数开启子进程时,由于fork函数执行失败引发异常,相关引发异常截图如下:
样本执行过程中引发异常,我们就无法继续往下分析了吗?
当然不是,我们可以使用gdb修改EIP,强制程序绕过异常代码。
操作流程如下:
root@freebsdOS:~ # gdb seconddate_ImplantStandalone_1.7.0.3_trunklien GNU gdb (GDB) 13.2 [GDB v13.2 for FreeBSD] Copyright (C) 2023 Free Software Foundation, Inc. ...... (gdb) set follow-fork-mode child (gdb) set disassembly-flavor intel (gdb) display /i $pc 1: x/i $pc <error: No registers.> (gdb) b *0x080495F4 Breakpoint 1 at 0x80495f4 (gdb) r Starting program: /root/seconddate_ImplantStandalone_1.7.0.3_trunklien Breakpoint 1, 0x080495f4 in ?? () 1: x/i $pc => 0x80495f4: push ebp (gdb) b *0x08049906 Breakpoint 2 at 0x8049906 (gdb) c Continuing. Breakpoint 2, 0x08049906 in ?? () 1: x/i $pc => 0x8049906: call 0x8049988 (gdb) set $eip=0x0804990B #修改eip强制绕过fork函数 (gdb) ni 0x080498cb in ?? () 1: x/i $pc => 0x80498cb: mov eax,DWORD PTR [ebp-0x80] (gdb) b *0x0804992A #相同断点处,成功执行 Breakpoint 3 at 0x804992a (gdb) c Continuing. Breakpoint 3, 0x0804992a in ?? () 1: x/i $pc => 0x804992a: call 0x8049a80 (gdb)
继续调试样本,发现样本在sub_8049AC0函数代码中调用获取网卡信息的函数时,获取网卡信息的函数无法成功获取网卡信息,因此在未执行网络流量嗅探代码前即将退出程序,相关代码截图如下:
无法执行网络流量嗅探代码,就无法对后续远控指令及通信行为进行分析了吗?
不是
虽然我们无法执行网络流量嗅探代码,但我们可以对比前期linux平台样本分析情况,了解网络流量嗅探功能代码只是用于筛选符合自身特征的数据包,因此在网络流量嗅探代码后,肯定有处理数据包的函数,我们可以尝试在不破坏程序堆栈平衡的情况下,手动将数据包数据传递给相应处理函数。
为快速查找处理数据包的函数,我们可以根据网络通信函数或者数据包中的部分特征16进制进行搜索,最终发现sub_8051A64函数即为数据包处理函数,相关截图如下:
在经过多次尝试后,笔者最终构造如下gdb调试指令,可成功向数据包处理函数传递IP载荷数据,并跳转至数据包处理函数处有效执行功能代码,相关GDB调试指令如下:
#调试ip载荷 gdb seconddate_ImplantStandalone_1.7.0.3_trunklien set follow-fork-mode child set disassembly-flavor intel display /i $pc b *0x080495F4 r b *0x08049906 #异常代码处:fork函数 c set $eip=0x0804990B #修改eip强制绕过fork函数 ni b *0x08049A9B #异常代码处:网络流量嗅探代码 c set $ebx=0xffbfed00 set $eip=0x08050BC0 #强制跳转至数据包处理函数处 ni set {int}($ebx+0x0)=0x54000045 #ip数据包载荷 set {int}($ebx+0x4)=0x0040c8bd set {int}($ebx+0x8)=0x78fe1140 set {int}($ebx+0xc)=0x807ea8c0 set {int}($ebx+0x10)=0x867ea8c0 set {int}($ebx+0x14)=0x901faf83 set {int}($ebx+0x18)=0x76ba4000 set {int}($ebx+0x1c)=0x64160215 set {int}($ebx+0x20)=0x6266e34c set {int}($ebx+0x24)=0x9dccbdc8 set {int}($ebx+0x28)=0x14ba62a6 set {int}($ebx+0x2c)=0x2c7faca9 set {int}($ebx+0x30)=0xa15ff2c0 set {int}($ebx+0x34)=0x33f2afdd set {int}($ebx+0x38)=0xe19ccd63 set {int}($ebx+0x3c)=0x7cba4791 set {int}($ebx+0x40)=0x571d3a1b set {int}($ebx+0x44)=0x20231c7c set {int}($ebx+0x48)=0xcf13e8ba set {int}($ebx+0x4c)=0x4d63fb63 set {int}($ebx+0x50)=0xc1558d25 b *0x08051AE9 #udp数据载荷解密函数断点 b *0x08051AEE c x /10xw $esp
相关截图如下:
尝试编写辅助程序,主要用于将ip数据包载荷进行拆分,打印成待执行的GDB调试指令,相关代码如下:
package main import ( "bytes" "encoding/binary" "encoding/hex" "fmt" "math/bits" ) func main() { hex_data, _ := hex.DecodeString("45000054bdc840004011fe78c0a87e80c0a87e8683af1f900040ba76150216644ce36662c8bdcc9da662ba14a9ac7f2cc0f25fa1ddaff23363cd9ce19147ba7c1b3a1d577c1c2320bae813cf63fb634d258d55c1") for i := 0; i < len(hex_data); i = i + 4 { fmt.Printf("set {int}($ebx+0x%x)=0x", i) aa := bits.ReverseBytes32(uint32(BytesToInt(hex_data[i : i+4]))) fmt.Println(hex.EncodeToString(IntToBytes(int(aa)))) } } func BytesToInt(bys []byte) int { bytebuff := bytes.NewBuffer(bys) var data int32 binary.Read(bytebuff, binary.BigEndian, &data) return int(data) } func IntToBytes(n int) []byte { data := int32(n) bytebuf := bytes.NewBuffer([]byte{}) binary.Write(bytebuf, binary.BigEndian, data) return bytebuf.Bytes() }
由于1048/1060字节通信载荷样本的通信解密程序无法成功解密此样本的通信数据,因此需要再次对此样本的通信算法进行剖析。
结合上述GDB调试方法,即可对通信算法进行调试分析,通过分析,发现此样本的通信模型与1048/1060字节通信载荷样本的通信模型一致(详见《NSA组织“二次约会”间谍软件通信模型剖析与对比》文章的“1048/1060通信载荷间谍软件网络通信模型剖析”章节内容),由于此样本通信模型中调用的RC6算法有所修改,导致无法使用1048/1060字节通信载荷样本的通信解密程序进行解密。
当前样本RC6算法初始化函数代码截图如下:
1048/1060字节通信载荷样本RC6算法初始化函数代码截图如下:
通过GDB调试,提取RC6算法的相关rk值如下:
#rk (gdb) x /44xw 0x0808afe0 0x808afe0: 0xfe7781ed 0x1083ccbd 0x123c7586 0x416ea88e 0x808aff0: 0x719ebc9d 0xe156726f 0xaa296314 0x56c34347 0x808b000: 0x0546da99 0x6068ca02 0x5ed6e10d 0xb70a7ccd 0x808b010: 0xbeb39fea 0xe938ba48 0xaa26e4b3 0x412cfa47 0x808b020: 0x41ba94fe 0xcc545c84 0x2fe3f9ee 0x33ff0d31 0x808b030: 0x23fa1524 0x0342d7c9 0xe034b94e 0xf8a60592 0x808b040: 0xa3a6f79f 0x3b7a2cab 0xbf4edfc2 0x8d826d74 0x808b050: 0x9681895a 0x5cdea507 0xee6894a5 0xc5bd0392 0x808b060: 0x146e5cde 0x5ec69503 0x3d72ae19 0x85aa5f59 0x808b070: 0xb455a4e3 0xde118e7d 0xce760941 0x4acef362 0x808b080: 0x479922f4 0x94a073fe 0x569dae63 0x19285df3
基于RC6算法的差异点,使用Go语言重新编写RC6解密脚本,重构RC6初始化函数如下:
//重构https://github.com/dgryski/go-rc6/blob/master/rc6.go代码中的New(key []byte)函数 func New(key []byte) (cipher.Block, error) { skeytable := make([]uint32, 44) skeytable[0] = uint32(0xb7e15163) for i := 1; i < 44; i++ { skeytable[i] = skeytable[i-1] - uint32(0x61C88647) } if l := len(key); l != 16 { return nil, KeySizeError(l) } c := &rc6cipher{} const keyWords = 4 var L [keyWords]uint32 for i := 0; i < keyWords; i++ { L[i] = binary.LittleEndian.Uint32(key[:4]) key = key[4:] } copy(c.rk[:], skeytable) var A uint32 var B uint32 var i, j int for k := 0; k < 3*roundKeys; k++ { c.rk[i] = bits.RotateLeft32(c.rk[i]+(A+B), 3) A = c.rk[i] L[j] = bits.RotateLeft32(L[j]+(A+B), int(A+B)) B = L[j] i = (i + 1) % roundKeys j = (j + 1) % keyWords } return c, nil }
尝试对seconddate_Client_1.7.0.3_UNKNOWN控制端程序的ping指令数据包进行解密,发现可正常解密。
数据包截图如下:
#加密数据 7ee0b85f #0x7ee0b85f - 0x61E57CC6 = 0x1CFB3B99(第一层解密所需的异或值) e304c467 #数据包校验:0xe304c467 + 0x1CFB3B99 = 0 31d66c5e #用于校验第一层解密后的数据是否正确,第一层解密后的数据按4字节循环异或后的结果等于output[8:0xc] d08b0669 85a6c2be 9967379e73f4043a4c3b2fcec3b6ba9d71dbd5c7af757e1aeaab6e3b242370570d388056 #解密数据 7ee0b85f #0x7ee0b85f - 0x61E57CC6 = 0x1CFB3B99(第一层解密所需的异或值) e304c467 #数据包校验:0xe304c467 + 0x1CFB3B99 = 0 31d66c5e #用于校验第一层解密后的数据是否正确,第一层解密后的数据按4字节循环异或后的结果等于output[8:0xc] cc703df0995df927 #RC6算法随机iv值 9e1a833a #固定值 0833fa27 #序列号,每次指令交互后加1 00000001 00000009 #指令 0000000000000000000000000000000000000000
由于在调试环境中,网络流量嗅探相关的库函数无法正常运行,同时,样本处理远控指令后的响应发包也是调用的相同库中的函数代码,因此此样本暂无法正常发送响应数据包,相关代码截图如下:
虽然此样本无法发送响应数据包,但其实我们是可以找到发包内容的,所以我们可以自行构建程序发送响应数据包,至此,便能模拟实现此样本的所有通信行为,虽然环节过于繁琐复杂,但在无法正常运行木马程序的情况下,也算是一种较高效的问题解决思路。