本文将分析的样本是OSX.TinyShell的变种:TinyTim。早在2018年,我就发现有攻击者正在使用Tiny SHell的改良版。这个后门(Tiny SHell,是开放源码的)的运行方式类似于SSH的可疑版本。虽然我已经有一段时间没有遇到新的示例了,但是我完全相信攻击者仍然在使用它。
目前,尚未有专门的文章讨论讨论有关该恶意软件的技术细节。这可能是因为实际上,它相对于其开源形式几乎没有什么变化,但是这些修改的确使我们能够通过多种方式检测其独特性。由于该恶意软件已被恶意行为者修改,因此称其为Tiny SHell似乎并不准确。因此,我将这个特定的修改版本称为“TinyTim”(因为我开始尝试在假期前后撰写此博客文章,并将发布拖到现在为止)。
本文中使用的样本可以在VirusTotal上找到(SHA256:8029e7b12742d67fe13fcd53953e6b03ca4fa09b1d5755f8f8289eac08366efc)。
在VirusTotal页面上,你会注意到许多反病毒扫描程序将其标记为OSX.Keydnap,但我不知道这种联系是如何产生的,因为我看不到它们之间的共同点。
发现过程
第一次观察表明,这个恶意软件是由开发人员签名的。我不知道这是一个合法的被盗的签名证书,还是它是由攻击者创建和拥有的。这很有趣,因为恶意软件通常仅用于攻击Gatekeeper的签名。正如我之前发现的那样,这个恶意软件是通过SSH使用受攻击的凭证在受害系统上释放的。也许攻击者是有意使用带符号的二进制代码来进行混合(因为macOS上的大多数二进制代码都是带符号的)。使用macOS的代码签名实用程序,我们可以转储恶意软件的代码签名信息:
如上所述,我们今天看到的这种变体是直接基于开源Tiny SHell后门的。Tiny SHell源代码进行的主要更改之一是添加了一个称为MyDecode的函数,攻击者使用此函数对二进制文件内的某些“敏感”字符串进行编码。如果我们想对这里发生的事情有个更好的了解,就必须在Hopper等反汇编程序中打开它。
在main函数内部,第一个检查显示TinyTim添加了一些基本的反调试函数。在开始时,我们看到ptrace与ptrace_deny_attach参数一起使用,如果程序在附加到调试器时执行,ptrace_deny_attach参数将立即关闭程序,我们必须记住这一点。
在检查调试器是否存在之后,它调用getuid,它返回执行程序的用户的用户ID。在这种情况下,恶意软件会检查根用户的UID是否为0。在这两种情况下,MyDecode函数最终运行在一个看起来像是乱码的字符串上。如果我们在左边的标签中选择_MyDecode并按下x,则可以很好地看到引用此函数的所有位置:
main调用11个,tshd_runshell调用2个,显然,这个恶意软件经常依赖于这个函数。如果我们把Hopper视图切换到伪代码,我们会看到此函数实际上是非常基本的:
这里要重点关注的关键项是r14 ^ r15,这是恶意软件常见的两个字节的简单XOR运算。我们看到r14和r15的值是传递给该函数的第二个和第三个参数的值。此处传递的第一个参数是攻击者想要取消屏蔽的字符串。如果回到主代码,我们可以看一下攻击者调用MyDecode时传递的值:
在前几个调用中,我们看到MyDecode使用XOR模式0x4 ^ 0x2对每个字符串进行解码。我们有一些选项可以将这些字符串转换回可读的文本。我们可以调试程序,也可以编写简单的脚本为我们解码字符串。或者,我们可以同时做!让我们从调试开始。
准备调试
在开始之前,我们必须采取以下步骤来准备这个可执行文件,以便它能够运行。
1. 通过chmod + x赋予TinyTim可执行文件权限;
2. 使用以下代码删除已撤销的签名:codesign --remove-signature;
3. 使用xattr -d com.apple.quarantine删除隔离位(假设已下载此恶意软件);
4. 删除前面讨论的ptrace调用,这是一种防调试技术,如果检测到调试器,它将关闭该程序。我们可以通过在ptrace调用上放置一个断点,然后跳过它来完成此操作,或者我们可以简单地NOP它,因此我们不必每次运行时都担心一个额外的断点。
攻击过程
现在,我们已经完成了所有的设置,我们可以在调试器中打开TinyTim并开始使用MyDecode函数。让我们在函数末尾的返回处放一个断点,然后启动调试器:
当在调试器中命中断点时,这意味着MyDecode函数刚刚完成运行。如果使用x / s $ rdx命令打印RDX寄存器,我们可以看到已解码的字符串:
在本例中,我们看到已解码的字符串是“/Users/%@/Library/Fonts/.cache”。请记住,我们是以基本用户的身份运行的,从我们在main中看到的情况来看,如果它作为根用户运行,将使用不同的路径(请参见第一个屏幕快照中的if/else语句)。我们可以继续“跳转到下一个断点”并打印每个字符串。结果并不令人惊讶:
0x10000c260: “/Users/%@/Library/Fonts/.cache” 0x7ffeefbffa40: “PROG_INFO” 0x7ffeefbffa50: “name_masq” 0x7ffeefbffa60: “CONN_INFO” 0x7ffeefbffb28: “domain” 0x7ffeefbffa70: “” 0x7ffeefbffa70: “next_time”
大多数安全分析人员都将上述字符串视为后门配置选项,这些选项可能是从“/Users/%@/Library/Fonts/.cache”文件中读取了这些选项。但是,由于没有在指定位置创建配置文件,因此没有成功读取这些配置。还要注意,其中一个已解码的字符串是空的。这有点奇怪,但我们稍后会再讨论它。让我们一起整理一些快速的python代码,这些代码也可以解开这些字符串,因为这样做不会造成任何攻击性。这没有什么复杂的,我们只需要遍历提供的字符串中的每个字符,并在其上运行XOR方案来获得已解码的字符。
现在我们无需使用调试器就可以轻松解码:
太棒了!我们现在可以获取存储在可执行文件中的各种字符串,并以纯文本的形式查看它们。继续,我们可以尝试创建一个配置文件来查看发生了什么,但是我们不知道配置文件的格式,接下来看看是否可以解决这个问题。
发现配置格式的关键实际上是在getProfileString函数中,该函数仅引用fopen,fgets,fseek和fclose函数。这些函数通常用于打开、关闭和移动文件的不同内容。
我们可以看到fopen打开了指定为arg0的文件,在本例中,arg0是恶意软件的配置文件。然后开始解析它。在文件的底部,我们看到sscanf正在使用某些特定的格式:
你可以使用sscanf函数在网上找一下,或者你可以使用我们应该已经使用过的方法,即使用GetProfileString函数,它可以准确地揭示我们所要查找的内容。
这里我们有一个函数,它是来自Windows的某种类型的端口,允许用户读取ini格式的配置文件。如果考虑一下我们前面看到的MyDecode函数的值,这是有意义的。这意味着所有大写的项都是lpAppName值,小写的项是lpKeyName值。当然,这在Mac上感觉有点不自然,因为这是Windows ini格式的一部分,但实际上它只是一个文本文件,这真的是一种格式吗?这意味着我们的配置文件应该如下:
这里使用的值当然是为我自己的测试而设置的,但是这种格式应该可以达到目的。一种简单的确认方法是在getProfileString底部附近的strcpy函数上放置一个断点,因为这个函数可能用于保存从配置文件中取出的字符串。一旦断点被命中,我们就可以使用x/s $RDI打印RDI寄存器(当函数被调用时,RDI应该总是保持arg0)来显示传递给strcpy函数的第一个参数,然后继续到下一个断点并重复。
使用正确的配置文件格式之后,我们将更接近于可操作的恶意软件。然而,仍有一些悬而未决的问题。让我们重新检查放置在myDecode函数上的断点,然后再次打印出每个解码后的值。你还记得吗,我们尝试打印的第六个字符串是空字符串,让我们看看是否有任何变化?
现在,解码后的字符串显示为749060607。请注意,该字符串在域字符串解码后立即解码。只需看一下就可以看出它与我们提供的localhost IP地址的长度相同-127.0.0.1。
如果我们使用我们编写的myDecode.py脚本并在127.0.0.1上运行它,那么我们是否可能得到749060607?
事实证明,我们在配置文件中使用的IP地址必须使用XOR方案进行编码。这对于攻击者而言是明智的。这样可以确保即使找到配置文件,也无法以纯文本方式找到命令和控制IP或域。如果他们使用的是已知的恶意IP地址,这还可以确保通过简单的YARA规则无法提取他们使用的C2。因此,如果我们希望看到与该恶意软件的成功连接,则必须确保首先对存储在配置文件中的IP或域进行了相应的编码。由于XOR是可逆的,并且我们已经知道所使用的方案,因此最终变得非常简单。我们可以通过在python myDecode脚本中翻转单个运算符来实现:
它以所需的掩码格式提供了127.0.0.1,可以在我们更新配置文件后正确解码:
现在你可能认为我们已经准备好连接回C2服务器了,然而,TinyTim还有另一个反调试技巧。如果我们再看一下伪代码中的main函数,我们会注意到connect函数的调用依赖于一个非匹配的字符串比较。
让我们在此strcmp函数上添加一个断点,并通过打印寄存器RDI和RSI(传递给strcmp的第一个和第二个参数)来查看正在比较的内容:在连接到指定的C2之前,要进行一次检查,以确保这不是试图连接到运行恶意软件的同一台计算机,这是恶意软件开发者的另一个聪明举动。有很多方法可以绕过这个问题。为了简单起见,我将在VM中启动微型Tiny SHell服务器,获取该VM的本地IP地址,使用XOR模式重新询问IP,并将其添加到配置文件中,这样问题就解决了。运行TinyTim现在将创建到我的VM上的Tiny SHelll服务器的连接,这样最后一个问题就解决了。
TinyTim想要一个密码,这是意料之中的,因为在其开源形式Tiny SHell中,用户需要输入密码。但是,因为我们没有在配置文件中看到指定的密码选项,所以我们知道它必须存储在可执行文件的某个地方。开源的Tiny SHell将该密码称为机密,在Hopper中,我们可以对秘密进行简单的搜索:
现在,我们会看到一个XREF指向一个有趣的字符串' lcc,./3,我们可以尝试使用此密码作为密码,但是与可执行文件中的其他所有字符串一样,实际上已经对其进行了编码,这很有可能会有所帮助。因此,我们将首先使用python脚本对其进行解码:
一直以来,我们的目标都是让恶意软件连接到我们的C2服务器,以查看是否可以通过任何方式对其进行进一步修改。事实证明,从此以后,这种恶意软件的行为就像开源的Tiny SHell。因此,主要的增加是编码字符串,增加了用于快速更改的配置文件以及少量的反调试技术。与其继续使用我们的反编译器,不如仅仅看一下Tiny SHell客户端源代码就更有意义了。
本文翻译自:https://objective-see.com/blog/blog_0x58.html如若转载,请注明原文地址: