检测恶意软件包及其如何混淆其恶意代码
恶意软件包系列(4 / 4):如何避免和检测恶意软件包,以及它们使用哪些混淆技术来隐藏恶意代码

哇!这是恶意软件包系列的最后一篇文章。离别是如此甜蜜的悲伤,我们希望博客一个,两个,三个提供关于恶意包在整个DevOps和DevSecOps管道中造成的破坏的见解。
在之前的帖子中:
- 我们解释了什么是软件供应链攻击了解了恶意软件包在其中扮演的关键角色。
- 我们介绍了感染方法攻击者用来传播恶意软件包的方法,以及JFrog安全研究团队是如何揭露它们的。
- 我们讨论了一次成功袭击的后果以及攻击者如何在恶意包中执行有效负载来满足他们的需求。我们使用JFrog Security披露的真实恶意软件包的有效负载来演示这一点。
现在让我们了解攻击者在创建恶意包时的其他更谨慎的兴趣:隐藏恶意代码,最后展示如何检测和阻止恶意包。
攻击者使用混淆技术来隐藏恶意包中的有效负载
除了成功地执行感染和有效负载外,恶意包的作者还希望避免检测到他们的恶意活动。
为了获得合理的攻击成功率,攻击者希望避免代码分析安全工具的检测,并使安全研究人员难以对其恶意包进行反向工程。实现这些目标的一种广泛的技术是使用代码混淆,这是修改可执行文件的过程,使其对黑客不再有用。但是,它仍然具有完整的功能。
我们将讨论几种代码混淆技术,包括现成的公共混淆器、自定义混淆技术和不可见的后门技术,后门技术本身不一定是一种混淆方法,而是一种不可见地更改源代码逻辑而不产生可视工件的技术。
公共混淆器示例:python-obfuscator库
2021年7月,JFrog安全研究人员发现一个名为noblesse的恶意包,它使用了一个公开流行的Python混淆器,简称Python混淆工具。此工具中使用的混淆机制是使用base64对Python代码进行简单编码,在运行时解码,编译并执行。
进口base64编解码器魔法= ' cHJpbnQ '爱=神“bVxuyoT”=“xvIHdvc”命运= zkxVFVc的快乐= ' \ x72 \ x6f \ x74 \ x31 \ x33 '信任= eval(‘\ x6d \ x61 \ x67 \ x69 \ x63”)+ eval(‘\ x63 \ x6f \ x64 \ x65 \ \ x64 \ x65 \表示就是x63 \ x73 \ x2e x63 \ x6f \ x64 \ x65 \ x28 \ x6c \ x6f \ x76 \ x65 \ x2c \ x20的\ x6a \ x6f \ x79 \ x29”)+ eval(‘\ x67 \ x6f \ x64) + eval(‘\ x63 \ x6f \ x64 \ x65 \ \表示就是x63 \ x73 \ x2e x64 \ x65 \ x63 \ x6f \ x64 \ x65 \ x28 \ x64 \ x65 \ x73 \ x74 \ x69 \ x6e \ x79 \ x2c \ x20的\ x6a \ x6f \ x79 \ x29 ') eval(编译(base64.b64decode (eval(‘\ x74 \ x72 \ x75 \ x73 \ x74 '))<字符串> ','执行'))
在上面的代码片段中,我们可以看到一个例子你好世界打印,这个工具会自动混淆。我们可以看到base64字符串的用法,以及它们的解码b64decode ()函数,以及编译()和eval ()执行已解码代码的调用。
这种混淆技巧可以骗过简单的静态分析工具,但骗不了更彻底的分析工具。例如,我们的自动恶意代码检测器知道这种简单的混淆技术,并标记被混淆的代码。
控制流平坦化模糊技术
控制流扁平化是一种技术,在这种技术中,代码的控制流结构被分解成彼此相邻的块,而不是它们原来嵌套的层次。
2021年12月,jfffrog安全研究人员发现一个名为discord-lofy的恶意软件包,它使用了几种混淆技术的组合。其中之一是控制流平坦化。的有效载荷在这个包是一个不和谐令牌抓取器,打字和木马感染的方法帮助传播了它。
我们可以通过发表的论文中的一个例子来了解这种技术通过控制流扁平化来混淆c++程序下面用一个简单的例子解释了这个方法:

看看图表左侧的原始代码;我们可以把它分成三个代码块。首先是变量初始化,然后是带有break条件的while循环,最后是while循环中的代码块。
您可以在应用模糊处理后看到右侧的代码。三个代码块被压平,一个开关箱被用来控制代码的流动。新添加的变量swVar保存执行的代码块的编号。此外,在每个代码块的末尾,它的值会改变,以指示应该运行的下一个代码块。
使用同形字符隐藏恶意代码
同形字符方法本身并不是一种混淆器,但它可以用来隐藏合法软件包中的恶意代码修改。这项技术发表在TrojanSource纸并演示了在不可见的情况下更改源代码的可能性。换句话说,代码的逻辑改变,例如包含一个漏洞或恶意代码,而不产生任何可视化工件。
在这种技术中,攻击者可以使用看起来像普通读者会忽略的标准ASCII拉丁字符的Unicode字符。但是,编译器或解释器会以不同的方式对待它们,因此代码的逻辑会发生变化。
供应链攻击者可以使用这种技术在流行的源代码存储库中植入不可见的后门。例如,攻击者可能会通过将字符串的一个字符更改为同音异义字来更改字符串文字检查或函数调用,以使其始终不可见地成功或失败。
在下面的代码片段示例中,这两个函数看起来完全相同。但是,底部的函数名使用西里尔字母H字符,这将被视为完全不同的函数名。程序后面的代码可以以难以区分的方式调用这两个函数中的任何一个。
void sayHello() {std::cout << "Hello, World!\n";}无效说Н嗨(){std:: cout < <“再见,世界! \ n”;}
使用双向控制字符隐藏恶意代码
介绍了另一种隐形方法TrojanSource纸是Unicode双向(或BiDi)控制字符。这些字符控制文本流(从左到右或从右到左)。当在源代码中使用BiDi控制字符时,Unicode编码可能会产生奇怪的工件,例如源代码行以一种方式显示,但编译器以另一种方式解析。
例如,看看下面的原始代码片段:
int main() {bool isAdmin = false;/* begin admins only */ if (isAdmin) {printf("You are a admin.\n");/* end adminonly */}返回0;}
对于读者来说,代码似乎不会打印“您是管理员”,因为isAdmin = false。但是,如下面的代码片段所示,假设Unicode BiDi控制字符插入到条件检查中的正确位置。在这种情况下,编译器可以将条件检查行解释为完整的注释,因此可以绕过整个检查。
当在条件检查中插入BiDi字符时:
int main() {bool isAdmin = false;' /* begin admins only if (isAdmin) */ {' printf("You are an admin.\n");/* end adminonly */}返回0;}
Anti-Debug技术
除了代码混淆之外,攻击者还通过检测调试工具作为恶意代码的一部分,使研究人员和自动化工具的分析过程变得更加困难。
首次,JFrog安全研究人员发现和披露一个名为cookiezlog的恶意Python包,它使用了这种反调试技术。在已知的混淆技术中PyArmor和代码压缩,发现该包使用了一个开源的Python反调试器Advanced-Anti-Debug。
此反调试器的函数之一称为check_processes ()它的目的是通过将活动进程列表与50多个已知工具列表进行比较,来查看调试器进程是否在系统上运行,其中包括:
- “idau64.exe”(IDA Pro反汇编器)
- “x64dbg.exe”(x64dbg调试器)
- “Windbg.exe”(WinDbg调试器)
- “Devenv.exe”(Visual Studio IDE)
- “Processhacker.exe”(过程的黑客)
PROCNAMES = ["ProcessHacker.exe", "httpdebuggerui.exe", "wireshark.exe", "fiddler.exe", "regedit.exe",…]如果PROCNAMES中的proc.name(): proc.kill()
如果这些进程中的任何一个正在运行,反调试代码将尝试通过psutil.Process.kill。阅读我们对这个反调试器的完整分析最新的博客。
既然我们已经了解了恶意包中使用的感染、有效负载、混淆和反调试技术的技术信息,那么最后让我们讨论在软件开发生命周期(SDLC)中检测恶意包的方法。
如何识别已知和未知的恶意软件包
检测已知恶意软件包
让我们从检测已知的恶意包开始。
为了全面了解项目中的恶意软件包,我们本质上需要列出项目的依赖项,并检测项目中安装的所有第三方软件版本。此过程的工件称为软件物料清单(SBOM),其中包含有关已安装的第三方软件的信息。
我们可以使用SBOM查询公共存储库,并检查我们使用的第三方软件包是否恶意。如果我们以PyPI或npm为例,这些存储库定义了用户可以在其中报告恶意包的进程。要检查已知的恶意包,最有效的方法是查询这些存储库。
不幸的是,在实现此过程时存在两个问题。第一个问题是许多存储库不保存历史数据。例如,在PyPI中,当恶意包被确认为恶意包时,将从存储库中删除,从而无法判断过去是否检测到某个包或其特定版本为恶意包。下面是一个恶意软件包的截图ecopwer我们去年披露的。到目前为止,在PyPI中搜索它时,没有这个包的证据:

npm中的跟踪稍微好一些。报告的恶意程序包将被替换为虚拟代码,并被标记为安全持有包,正如你在下面的截图中看到的,一个名为colors-art在npm:

虽然这对于跟踪恶意包很有用,但是对于跟踪合法包的特定恶意版本却没有用处,因为当所有版本被确认为恶意时,它们都会从存储库中删除。
第二个问题是,即使我们希望使用存储库的数据来扫描恶意包,常见的安全审计工具也会报告漏洞,但不会报告恶意包。例如,看看以下执行PyPI工具的结果pip-audit在恶意包之后ecopower是安装。该包无法扫描(如下所示),因为它被报告为恶意后从PyPI存储库中删除:

由于这两个问题以及开发人员通常必须执行我们刚刚在规模上描述的审计过程(例如,作为SDLC的一部分)的事实,我们本质上需要自动化该过程。这可以通过使用集成到我们的开发和CI/CD过程中的软件组合分析(SCA)工具来实现。选择一个安全工具是很重要的JFrog x光它在内部数据库中收集和存储恶意包的名称和版本,而不仅仅依赖于来自外部存储库(如npm和PyPI)的信息,这些存储库不保存我们所看到的历史数据。
检测未知恶意包
检测未知的恶意软件包在技术上被认为要困难得多,因为我们本质上是在处理未知的威胁,类似于在漏洞领域寻找零日漏洞。为了检测未知的恶意包,我们需要在恶意包被称为恶意包之前找到一种识别其特征的方法。
我们在开发JFrog Xray时采用的方法不仅仅是用最新的已知恶意包名称和版本更新Xray数据库。但是为了检测未知的恶意包,我们还开发并运行启发式扫描器来扫描公共存储库中的软件包代码并检测其中的异常。扫描器试图在我们在本系列博客中讨论的任何攻击阶段找到恶意活动的证据——在感染方法、有效载荷阶段,以及通过检测隐藏方法或混淆技术。
扫描器提供可能未知的恶意包警报的能力使它们成为我们发现、研究和披露的所有恶意包的基础。在这个博客系列中,我们彻底分析了使用这种技术发现的一些恶意软件包,以及我们发布的其他博客。
这是我们开发的扫描器清单。请记住,理论上可以为攻击的每个阶段开发扫描器,因此请尝试将此列表视为启发式技术的演示列表,如果您对搜索未知恶意包感兴趣,则可以考虑更多技术。
我们开发的扫描仪示例:
- 用于检测依赖关系混淆感染的方法我们开发了扫描器,可以在远程公共存储库中找到版本号高的包,并对可能的模拟发出警报。
- 用于检测下载和执行有效载荷,我们开发了扫描器,可以使用我们用不同语言监控的系统函数,找到下载二进制文件并执行它的代码模式。
- 用于检测敏感数据窃取者有效载荷,我们开发了扫描器,使用我们以不同语言监控的系统函数来查找访问文件系统中敏感位置的代码模式。
- 为了检测混淆技术,我们开发了能够对base64解码和其他公共混淆器的代码特征发出警报的扫描仪。
安全开发的最佳实践,以避免恶意包
我们已经接近这个博客系列的尾声了,但是在结束之前,我们将为您提供几个处理恶意包安全威胁的安全开发最佳实践:
- 处理恶意软件包威胁的最重要和最基本的方法是使用软件组合分析工具作为SDLC的一部分,如JFrog Xray,或其他软件组合分析工具。
- 定义策略和自动化操作,作为a的一部分DevSecOps的过程。如果在流程中发现恶意包,建议采用一种策略,破坏构建流程并警告该问题。
- 为了防止依赖混淆感染方法,我们希望避免自动获取高版本的恶意软件包,除非我们在发布的软件的新版本上执行DevOps和DevSecOps测试。要实现这一点,建议将构建系统配置为排除远程存储库对于内部包和使用严格版本每个构建的外部依赖项。
- 使用开源工具帮助检测恶意软件包并防止它们感染您的项目:
- Jfrog-npm-tools:开源工具JFrog开发并发布到社区,用于npm包的安全性。
- piproxy:为pip开发的一个小型代理服务器JFrog,它修改pip的行为,仅当包在任何内部存储库上都找不到时才安装外部包。这修复了pip中的依赖混淆问题。
- npm_domain_check: JFrog开发的一个工具,用于检测可以被劫持的npm依赖域的收购。
- 困惑此工具检查多个包管理系统中的依赖混淆漏洞。
- PyPI-scan:此工具检查名称的相似性,以查找输入相似的包。
- Pyrsia。JFrog去年宣布了一项新的开源计划,旨在创建一个安全的、分布式的点对点软件包存储库,为软件包提供完整性。该项目使用区块链技术建立开源包的来源链。阅读更多关于这一点pyrsia.io。
这篇文章结束了我们的恶意软件包博客系列,但这并不是告别。
注册JFrog 's即将到来的网络研讨会继续你的教育。