事实证明,当谈到计算机时,这句 16 世纪的名言仍然非常适用。毕竟,在大多数操作系统上,进程都有一个“命令行”组件,它允许(启动)父进程将信息传递给子进程。这个新创建的进程可以访问命令行,这可能会根据在命令行上找到的内容更改其进程流。这个概念是构成“计算机”的核心:它能够执行一组指令、程序,接受各种输入。尽管它在计算中发挥着基本作用,但对于命令行上可以找到的各个部分的称呼似乎没有达成一致。有些人认为命令行参数、参数、选项、标志、开关是一回事,有些人则有不同的含义。这篇文章将使用以下术语:整个行是命令行,它由命令行参数(用空格分隔)组成。虽然都是参数,但它们有不同的作用:例如,第一个参数通常是被调用的进程 - 或者,如果您在命令提示符中,这将是命令。在上面的例子中,后面跟着更多的参数。更具体地说,前三个是命令行选项,通常以特殊字符开头。在这三个选项中,前两个是开关,因为它们不需要进一步的输入,而第三个是一个标志,因为它后面是进一步的参数,这些参数是这个参数的“输入”。选项 char 是用作命令行选项前缀的字符。在 Windows 上,这通常是正斜杠 (/),在类 Unix 系统上,主要使用连字符 (-)。这是惯例而非规则:因为命令行由正在执行的程序解析,开发人员可以完全自由地定义应该以什么格式传递参数。公约很难。计算历史告诉我们,如果没有标准化,事情往往会以混乱结束——这当然适用于命令行。命令行解析实践缺乏标准化导致用户混淆:很容易混淆不同的实践,导致执行不成功和很多挫折。为了帮助用户,一些程序被设计为接受多种约定。一个常见的例子是接受正斜杠和连字符作为选项字符,这将在下一节中更详细地讨论。另一个混淆源是可用的不同字符编码。这导致了旧程序(通常只接受 ASCII)和新程序(现在通常接受 UTF-8 或 Unicode)之间的各种兼容性问题。一些程序试图通过过滤掉某些字符或将特定字符转换为 ASCII 等价物来解决这些不兼容性。
这导致了某些程序将各种不同的命令行参数视为一个相同的情况。您可以将此类实例称为同义命令行,因为尽管传递给流程的数据不同,但它们的执行和结果是相同的。虽然能够以不同的方式表达相同的命令可能对某些用户有所帮助,但它往往会使检测和预防工作更加困难。大多数威胁检测软件(AV、EDR 等)将监控进程执行,并寻找可能表示恶意使用的命令行参数。由于攻击者希望不被发现,他们可能会利用命令行的灵活性来逃避检测。这可能包括从绕过基于关键字的检测的小调整到全面的命令行混淆以隐藏原始命令。命令行级别混淆的一个很好的例子是 Daniel Bohannon 的优秀作品 DOSfuscation [1, 2],它特别关注 Windows 命令行提示符 CMD。即使正在执行的 CMD '理解'并执行混淆的命令,它对人类来说可能看起来难以理解,并且监控软件也很可能被蒙在鼓里。同义命令行的现象超出了 DOSfuscation,因为它适用于更多程序,而不仅仅是 CMD。这里的主要区别在于,您不仅会愚弄命令行提示符,还会愚弄正在执行的程序本身。正如稍后将展示的,虽然 DOSfuscation 工作可能会在检测软件使用的记录器中以未混淆的形式结束,但同义的命令行参数不会。为了看到这一点,我们现在将仔细研究可能导致同义命令行的五种不同方法。考虑 Windows 可执行文件 ping。由于该程序是原始 Unix 版本的移植,帮助页面建议命令行选项应使用连字符作为选项字符,例如 ping -n 0 127.0.0.1。这与大多数其他使用正斜杠的 Windows 原生命令行工具不一致。大概是为了帮助困惑的用户,该程序还接受正斜杠作为选项字符:ping /n 0 127.0.0.1 也可以工作。大多数使用连字符的内置 Windows 可执行文件也接受正斜杠,但反之则不然。例如,命令 find /i keyword 将显示包含单词“keyword”的所有文件,而 find -i keyword 将导致错误。
尽管正斜杠和连字符是最常见的可用选项,但有些程序支持更多的选项字符。 certutil 恰好接受连字符、斜线和斜线的大多数 Unicode 表示,例如除法斜线 (0x2215) 和分数斜线 (0x2044) [3]。正如我们将在其他变体中看到的那样,可执行文件很少记录这些替代命令行选项,这意味着您会偶然、“蛮力”或通过逆向工程找到它们。另一种方法是用类似的字符替换命令行中的其他字符(即除了选项字符之外)。尤其是当您考虑整个 Unicode 范围时,在 ASCII 范围内也发现了许多字母变体,某些进程可能接受这些变体。 Unicode 包含一个间距修饰字母范围 (0x02B0 - 0x02FF) [4],其中包括 ˪、ˣ 和 ˢ 等字符。一些命令行解析器将它们识别为字母并将它们分别转换回 l、x 和 s。一个例子是 reg,它将 reg export HKCU out.reg 和 reg eˣport HKCU out.reg 视为相等。事实证明,有更多 Unicode 范围包含某些程序接受的字符。同样,有时可以在命令行中插入额外的字符,这些字符将被执行程序忽略。例如,某些可执行文件可能会删除不可打印的字符,同时也可能会过滤掉某些可打印的字符。例如,Windows 事件日志工具 wevtutil 似乎接受在随机位置插入某些范围内的 Unicode 字符的命令行。因此,执行 wevtutil gli hardwareevents 和 wevtutil gࢯli hardwareevents 将产生完全相同的输出,尽管后者在第一个参数的中间包含一个阿拉伯字母。
由于命令行提示的标准输入有时不支持可用于此技术的字符(例如,因为它们不可打印),您可能必须使用字节表示法插入字符。从截图中可以看出,在这种情况下,字符被正确地传递给了进程。在保持流程完整的同时操纵命令行的另一种方法是插入引号。尽管这听起来像是先前技术的一个子集,但这里的要求是引号成对出现。您可能熟悉在参数周围加上引号的概念。以 dir "c:\windows\" 为例,由于缺少空格,它实际上与 dir c:\windows\ 相同。大多数程序都接受这个约定。鲜为人知的是,大多数程序在任意位置接受引号:命令 dir c:\"win"d""ow"s" 也可以使用。只要每个参数的引号数是偶数并且后面的引号不超过两个,大多数程序似乎都接受这一点。值得注意的是,在命令提示符中使用引号可能很棘手,因为它们通常在将引号传递给底层程序之前自己处理引号。例如,在 cmd 中解决此问题的一种方法是将每个引号加倍,因此要获得如上所示的等效执行,您必须运行 netsh ad""vfi""rewall show currentprofile state。插入和替换字符后,我们还需要尝试删除字符。一些应用程序允许为其他冗长的命令行选项提供“速记”,从而更容易输入它们。这是基于 Unix 的工具中的一个众所周知的概念(例如 grep -i 关键字与 grep --ignore-case 关键字),但在 Windows 上则不然。然而,一些程序接受缩短的版本。一些程序采用与 Unix 类似的方法并接受单字母版本(例如 cmdkey /l 与 cmdkey /list),有些程序接受其他缩写版本(例如 wevtutil gli 与 wevtutil get-loginfo),而其他程序则采用“通配符方法” '。这方面的一个例子是 PowerShell,它的许多关键字允许您在关键字 [5] 的末尾省略一个或多个字符。成功执行 powershell /encodedcommand ZQBjAGgAbwAgACIASQB0ACAAdwBvAHIAawBzACEAIgA= 后跟 13 种不同速记的示例。
也许除了最短的变体 /e 之外,在我看来,接受这些速记方法对很少人有帮助,但它确实使事情变得更加复杂和不可预测。例如,PowerShell 只接受缩短的版本,前提是它不会导致另一个命令之间的歧义。出于这个原因,关键字/noprofile 的最短变体是/nop,因为/no 会与例如/noexit 发生冲突。除了这种“通配符方法”之外,PowerShell 在某些情况下也接受首字母缩略词,因此尽管屏幕截图中没有显示,/ec 也可以用作 /encodedcommand 的简写。如前几节所述,处理命令行参数没有标准。有些程序非常严格,而有些程序会非常努力地将给出的任何内容变成它可以理解的内容。找出特定程序的命令行行为也是有问题的:帮助页面通常只指定传递参数的“首选”方式(方法 1 和 5),通常从不描述如何处理意外字符(方法 2-4 )。确定这一点的最准确方法似乎是逆向工程。但是,这是一项非常耗时的活动,尤其是当您考虑到某些程序的复杂性时。因此,更实用的方法是简单地使用不同的字符重复尝试所有绕过技术并比较结果。这种方法的概念验证实现 [6] 使我们能够快速分析所描述的五种行为中的哪一种。可以在 GitHub 项目页面上找到对 PoC 的更详细解释、其基本假设和警告。出于本文的目的,PoC 用于分析 40 多个经常用于攻击的内置 Windows 可执行文件。注:1) 使用 Windows 10 2004 版进行分析;2) 对于每个 Windows 可执行文件,仅使用一个特定的命令 - 其他命令可能会导致不同的结果;3) 用星号 (*) 表示的条目针对的是命令-line 参数接受每个变化。例如,在 nslookup -querytype=ALL (...) 的情况下,PoC 发现无论插入什么字符,都会返回预期的输出。事实证明,nslookup 会忽略命令行选项(在本例中为 -q)的第一个字母之后的所有字母,这意味着可能的排列数量几乎是无限的。每个测试的可执行文件的完整报告,包括测试的命令和找到的字符,可以在 GitHub [6] 上找到。
了解所有这些使我们能够退后一步,从威胁搜寻的角度审视命令行行为。如果你想用特定的命令行参数检测程序的执行,同义的命令行参数会带来问题。例如,编写 Sigma 规则 [7] 来检测指定 urlcache 和 f 参数的 certutil 执行可以表示为: 问题很明显:如果攻击者使用 -urlcache、/urᴸcache、/urlcache 或 /url"cach "e - 或者更糟的是,一个组合:-urᴸ"cach"e,该命令仍然有效,但上述规则不会影响行为。将所有变体添加到规则中不是一种选择,因为结合各种绕过技术将导致数千甚至数百万个排列。 certutil -f -urlcache -split https://wietze.github.io/robots.txt output.txt 混淆版本的成功执行示例。可能尝试的第一个潜在解决方案是使规则更通用。例如,解决选项字符问题,我们可以将选项字符排除在定义之外:虽然这似乎解决了选项字符问题,但它创建了一个新的问题。查找 urlcache 而不是 /urlcache 不太可能导致很多误报,但是,只查找 f 而不是 /f 可能会产生误报。第二种可能的解决方案是使用更巧妙的匹配选项。许多提供寻找流程执行功能的 EDR 工具往往仅限于简单的“字符串包含”式功能,如上述示例中所示。有些允许正则表达式,但这似乎仍然很少见。尽管 Sigma 支持正则表达式值,但在 30 个支持的后端中似乎只有 7 个实际实现了它。虽然这并不一定意味着其他 23 个平台不支持它,但至少表明它可能不是一条明显的下降路径。供应商可能对提供此选项犹豫不决的一个关键原因首先是性能:正常的编译正则表达式的复杂度为 O(n),但通过递归扩展,可以创建永远不会结束计算的正则表达式。 Sigma 项目也不鼓励使用正则表达式 [8]。
对于那些支持正则表达式的工具,可以解决选项字符问题(假设您将 [-/\\] 替换为所有选项字符的列表):正则表达式还可以帮助有效地捕获速记关键字。不幸的是,由于选项和排列的大小,字符插入和替换通常是不可行的。解决这些类型的混淆永远不会是无懈可击的。需要更具弹性的方法。为避免落入隧道视觉检测工程的陷阱,您可以采取一些措施来实现强大的威胁搜寻过程。首先,确保规则被合理地定义为“广泛”,捕捉所有可能的变化,同时考虑到误报的潜在增加。提供的 PoC [6] 可用于查找您的规则可能允许的变化。其次,标准化监控工具的输出也可能是有益的。如果您的数据在最终进入分析平台之前在管道中进行处理,请考虑添加一个单独的字段,将所有特殊字符转换为 ASCII 等效字符并去除所有非字母数字值(以去除引号和选项字符)。在此字段上运行您的规则可能会有更高的命中率。此外,使用数据分析来检测混淆尝试本身。查看包含在您的 IT 资产中使用率较低的字符的流程命令行,比较字符密度级别,甚至只是查找包含非 ASCII 字符的命令行参数并从那里开始工作。
还要确保您的重点比单独的流程字符串更广泛。在可能的情况下,关注命令行试图实现的实际行为,例如文件创建、注册表更改或网络连接。例如,与其试图捕获所有可能的恶意 wevtutil 命令,不如寻找写入意外位置的 wevtutil(系统信息发现?[9])或检查事件 ID 1102 的事件日志(指标删除?[10]) ])。最后,请记住,不可能在 100% 的时间内检测到 100% 的不良情况 - 但是,您检测到的越多,攻击者就越难以完全被忽视。因此,考虑到上述因素将使您更近一步。 “欲听从者,必知指挥”,懂指挥者,必知察。