原文

https://www.wietzebeukema.nl/blog/windows-command-line-obfuscation#/

引言:

出于兼容性或易用性的原因,同一命令行可以被不同的windows应用用不同的方法表达出来。 由于变量的数量不同,命令行的参数被表达的不一致,使得检测特定命令变得更加困难。 这篇文章展示了 40 多个常用的内置 Windows 应用程序如何容易受到命令行混淆形式的攻击,并提供了一种用于分析其他可执行文件的工具。

命令行参数:

事实证明,当谈到计算机时,这句 16 世纪的名言仍然非常适用。
image.png
毕竟,在大多数操作系统上,进程都有一个“命令行”组件,它允许(启动的)父进程将信息传递给子进程。 这个新创建的进程可以访问命令行,它可能会根据在命令行中找到的内容来更改其进程流。 这个概念是“计算机”的核心:它能够执行一组指令、程序、接受各种输入。
尽管它在计算中发挥着重要作用,人们对于如何称呼这些命令行的部分并没有达成一致。 有些人认为命令行参数、参数、选项、标志、开关是相同的,但对有些人来说它们具有不同的含义。 这篇文章中将使用以下术语:
windows command-line obfuscation - 图2
整个行是命令行,它由命令行参数(由空格分隔)组成。 尽管它们都是参数,但它们具有不同的作用:例如,第一个参数通常是被调用的进程,或者,如果放在命令提示符中,这将是命令。 在上面的图片中,命令的后面跟着更多的参数。 更具体地说,前三个是命令行选项,通常以特殊字符开头。 在这三个选项中,前两个是开关,因为它们不需要进一步的输入,而第三个是一个标志,因为它后面跟着更多的参数,这些参数是这个参数的“输入”。
选项是用作命令行选项前缀的字符。 在 Windows 系统中,这通常是正斜杠 (/),在类 Unix 系统中,主要使用连字符 (-)。 这不是惯例,而是规则:因为命令行是由正在执行的程序解析的,所以开发人员可以完全自由地定义应该以什么格式传递参数。
达成约定是一件很难的事。 计算机的历史告诉我们,如果没有标准化,事情往往会以混乱告终——这当然适用于命令行。

兼容性

命令行解析实例缺乏标准化导致了许多用户的困惑:很容易混淆不同的实例,导致执行不成功以及产生挫败感。为了帮助用户,一些程序被设计为可以接受多种约定。一个常见的例子是同时接受正斜杠和连字符作为选项字符,这将在下一节中更详细地讨论。
另一个令人迷惑的事情是存在不同字符编码。这导致了旧程序(通常只接受 ASCII)和新程序(现在通常接受 UTF-8 或 Unicode)之间的各种兼容性问题。一些程序试图通过过滤掉某些字符或将特定字符转换为 ASCII 等价物来修复这些不兼容性。
这导致了一些程序将各种不同的命令行参数视为同一个参数的情况。您可以将此类实例称为同义命令行,因为尽管传递给进程的数据不同,但它们的执行和结果是相同的。

检测的难题

尽管能够以不同的方式表达相同的命令可能对某些用户有所帮助,但它往往会使检测和预防工作变得更加困难。大多数威胁检测软件(AV、EDR 等)都会监控进程执行,并查找可能指示恶意使用的命令行参数。由于攻击者希望不被发现,他们可能会利用命令行的灵活性来逃避检测。他们尝试的范围从小调整到绕过基于关键字的检测,再到全面的命令行混淆以隐藏原始命令。
命令行级别混淆的一个很好的例子是 Daniel Bohannon 的出色工作,DOSfuscation [1, 2],它特别关注 Windows 命令行提示符 CMD。即使执行的 CMD “理解”并执行了混淆的命令,它对人类来说可能看起来难以理解,监控软件也可能被蒙在鼓里。
同义命令行的现象超越了 DOSfuscation,因为它适用于更多的程序,而不仅仅是 CMD。这里的关键区别在于,您不仅可以欺骗命令行提示符,还可以欺骗正在执行的程序本身。正如稍后将显示的,虽然 DOS 混淆工作最终可能以未混淆的形式被记录在检测软件使用的记录器中,同义命令行参数不会。

同义命令行:方法

我们下面将展示五种不同的可以造成同义命令行的方法。

选项(option)符号替代法

比如Windows 可执行 ping。因为该程序是原始 Unix 版本的一个端口,所以帮助页面建议命令行选项应该使用连字符作为选项字符,例如ping -n 0 127.0.0.1。这与大多数其他使用正斜杠的 Windows 原生命令行工具不一致。大概是为了帮助困惑的用户,该程序也接受正斜杠作为选项: ping /n 0 127.0.0.1。
大多数使用连字符的内置 Windows 可执行文件也接受正斜杠,但反之则不然。例如,命令 find /i 关键字将显示所有包含单词“关键字”的文件,而 find -i 关键字将导致错误。
尽管正斜杠和连字符是最常见的可用选项,但有些程序支持更多选项字符。 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 视为同一个命令。
image.png
事实证明,有更多的unicode编码可以被一些程序所接受。

字符插入法

类似地,有时可以在命令行中插入额外的字符,这些字符将被执行程序忽略。 例如,某些可执行文件可能会删除不可打印的字符,而某些可打印的字符也可能会被过滤掉。
例如,Windows 事件日志工具 wevtutil 似乎接受在随机位置插入特定范围的 Unicode 字符的命令行。 因此,执行 wevtutil gli hardwareevents 和 wevtutil gࢯli hardwareevents 将产生完全相同的输出,尽管后者在第一个参数的中间包含一个阿拉伯字母。
image.png
因为可用于此技术的字符有时在命令行提示符的标准输入中不受支持(例如,因为它们不可打印),您可能必须使用字节表示法(\u)插入字符。 从屏幕截图中可以看出,在这种情况下,字符被正确传递给进程。

引号插入法

另一种修改命令行却保持进程流完整性方法是插入引号。 尽管这听起来像是先前技术的一个子集,但这里的要求是引号成对出现
您可能熟悉在参数周围加上引号的概念。 以 dir “c:\windows\” 为例,由于缺少空格,它实际上与 dir c:\windows\ 相同。 大多数程序都接受这个约定。 鲜为人知的是,大多数程序在任意位置都接受引号:命令 dir c:\”win”d””ow”s” 也可以使用。 只要每个参数的引号数量是偶数并且后续引号不超过两个,大多数程序似乎都接受这一点。
image.png
值得注意的是,在命令提示符CMD中使用引号可能会很棘手,因为它们通常会在将引号传递给底层程序之前自己处理引号。 例如,在 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]。
image.png
也许除了最短的变体 /e 之外,在我看来,几乎没有程序通过接受这些简写而对使用者产生帮助,而它确实使事情变得更加复杂和不可预测。 例如,PowerShell 仅在不会导致另一个命令之间产生歧义的情况下才接受缩短的版本。 出于这个原因,关键字 /noprofile 的最短变体是 /nop,因为 /no 会与例如/noexit冲突。在这种“通配符方法”旁边,PowerShell 在某些情况下还接受首字母缩略词,因此尽管未在屏幕截图中显示,/ec 也可以用作 /encodedcommand 的简写。

找到可以使用混淆命令行的程序

如前几节所述,程序在处理命令行参数时是没有标准的。有些程序非常严格,而有些程序会非常努力地将用户输入的内容变成它可以理解的东西。找出特定程序的命令行行为也是有困难的:帮助页面通常只指定传递参数的“首选”方式(方法 1 和 5),并且通常从不描述如何处理意外字符(方法 2-4 )。
确定这一点的最准确方法似乎是逆向工程。但是,这是一项非常耗时的活动,尤其是考虑到某些程序的复杂性时。因此,更实用的方法是简单地用不同的字符重复尝试所有绕过技术并比较结果。
这种方法 [6] 的poc使我们能够快速分析所描述的五种行为中的哪一种适用。您可以在 GitHub 项目页面上找到关于 PoC 的更详细说明、其基本假设和注意事项。
出于本文的目的,PoC 这里被用来分析 40 多个经常被用于攻击的内置 Windows 可执行文件。

Executable Option Char substitution Character insertion Character substitution Quote insertion Shorthands
fltmc.exe ➡️ N/a ✔️
net.exe ➡️ N/a ✔️ (1) ✔️ ✔️
netsh.exe ➡️ N/a ✔️
route.exe ➡️ N/a ✔️ ✔️
sc.exe ➡️ N/a ✔️
vssadmin.exe ➡️ N/a ✔️
wevtutil.exe ➡️ N/a ✔️ (3,369) ✔️ ✔️
wmic.exe ➡️ N/a
cmdkey.exe ➡️ ✔️ (1) ✔️ (1) ✔️ ✔️
csc.exe ➡️ ✔️ (1) ✔️
ipconfig.exe ➡️ ✔️ (1) ✔️ (3,370) ✔️ ✔️
jsc.exe ➡️ ✔️ (1) ✔️ ✔️
mpcmdrun.exe ➡️ ✔️ (1) ✔️
msiexec.exe ➡️ ✔️ (1) N/a
ping.exe ➡️ ✔️ (1) ✔️ (1) ✔️ ✔️
regsrv32.exe ➡️ ✔️ (1) ✔️* ✔️* ✔️ ✔️*
robocopy.cmd ➡️ ✔️ (1)
vbc.exe ➡️ ✔️ (1) ✔️
winrm.cmd ➡️ ✔️ (1) ✔️ (1) ✔️
winrs.exe ➡️ ✔️ (1) ✔️
findstr.exe ➡️ ✔️ (11) ✔️ ✔️
schtasks.exe ➡️ ✔️ (2) ✔️ ✔️
systeminfo.exe ➡️ ✔️ (2) ✔️ ✔️
takeown.exe ➡️ ✔️ (2) ✔️ ✔️ N/a
taskkill.exe ➡️ ✔️ (2) ✔️ ✔️
tasklist.exe ➡️ ✔️ (2) ✔️
whoami.exe ➡️ ✔️ (2) ✔️
curl.exe ➡️ ✔️ (3) N/a
reg.exe ➡️ ✔️ (3) ✔️ (6) ✔️ ✔️
powershell.exe ➡️ ✔️ (4) ✔️ ✔️
certutil.exe ➡️ ✔️ (5) ✔️ (6) ✔️ ✔️
nslookup.exe ➡️ ✔️ (5) ✔️* ✔️* ✔️ ✔️*
arp.exe ➡️ ✔️ (8) ✔️ (3) ✔️ ✔️ N/a
netstat.exe ➡️ ✔️ (8) ✔️ ✔️ N/a
at.exe ➡️ ✔️ ✔️
bitsadmin.exe ➡️ ✔️
cacls.exe ➡️ ✔️ (3,087) ✔️ ✔️ ✔️
cmstp.exe ➡️ ✔️ (3) ✔️
forfiles.exe ➡️ ✔️ ✔️ N/a
icacls.exe ➡️ ✔️

一些备注:

  1. 这里使用的windows版本是Windows 10 version 2004;
  2. 对于每一个window可执行文件,只是用了一个特定的命令来测试,其他的命令也可能导致不同的结果;
  3. 用星号 (*) 表示的条目表示该参数接受任意一种变体。 例如,在 nslookup -querytype=ALL (…) 的情况下,PoC 发现无论插入什么字符都返回了预期的输出。 事实证明,nslookup 会忽略命令行选项(在本例中为 -q)的第一个字母之后的所有字母,这意味着可能的排列数量几乎是无限的。
  4. 您可以在 GitHub [6] 上找到每个经过测试的可执行文件的完整报告,包括经过测试的命令和找到的字符。

    检测的难题

    了解这一切后,我们可以后退一步,从威胁搜寻的角度看待命令行行为。
    如果您想检测具有特定命令行参数的程序的执行,那么同义命令行参数会带来问题。 例如,使用写sigma规则的形式来检测带有urlcache和f参数的certutil可以写成:
    1. detection:
    2. selection:
    3. Image: "*\\certutil.exe"
    4. CommandLine|contains|all:
    5. - "/urlcache"
    6. - "/f"
    7. condition: selection
    问题很明显:如果攻击者使用 -urlcache、/urᴸcache、/₞urlcache 或 /url”cach”e - 或者更糟糕的是,组合使用:-₞u₞rᴸ”c₞a₞ch”e,命令 仍然可以工作,但上述规则不会采取这种行为。 将所有变体添加到规则中不是一种好的选择,因为结合各种绕过技术将导致数千甚至数百万的排列。
    image.png
    人们可能尝试的第一个潜在解决方案是使规则更加通用。 例如,解决选项字符的问题,我们可以将选项字符排除在定义之外:
    1. detection:
    2. selection:
    3. Image: "*\\certutil.exe"
    4. CommandLine|contains|all:
    5. - "urlcache"
    6. - "f"
    7. condition: selection
    虽然这似乎可以解决选项字符问题,但它创建了一个新问题。查找 urlcache 而不是 /urlcache 不太可能导致许多误报,但是,仅查找 f 而不是 /f 可能会产生误报。
    第二个可能的解决方案是使用更聪明的匹配选项。许多提供寻找流程执行功能的 EDR 工具往往仅限于简单的“字符串包含”式功能,如上述示例中所表达的。有些允许正则表达式,但这似乎仍然很少见。尽管 Sigma 支持正则表达式值,但 30 个支持的后端中只有 7 个似乎实际实现了它。虽然这并不一定意味着其他 23 个平台不支持它,但至少表明它可能不是一个好方法。供应商可能不愿首先提供此选项的一个关键原因是性能:正常的编译正则表达式的复杂度为 O(n),但通过递归扩展,可以创建永远不会结束评估的正则表达式。 Sigma 项目也不鼓励使用正则表达式 [8]。
    对于那些支持正则表达式的工具,可以解决选项字符问题(假设您将 [-/\] 替换为所有选项字符的列表):
    1. detection:
    2. selection:
    3. Image: "*\\certutil.exe"
    4. CommandLine|re: "^(?=.*[-/\\]urlcache)(?=.*[-/\\]f).+.*"
    5. condition: selection
    正则表达式还可以帮助有效地捕获简写关键字。 不幸的是,由于选项和排列的大小,字符插入和替换通常是没有办法的。
    解决这些类型的混淆永远不会是无懈可击的。 我们需要更具弹性的方法。

    强壮且有弹性的检测

    为避免陷入隧道式检测工程的陷阱,您可以采取一些措施来实现强大的威胁搜寻过程。
    首先,确保规则被定义地尽可能的广泛,从而捕捉所有可能的变形,同时需要考虑到误报的潜在增加。我们提出的 PoC [6] 可用于查找您的规则可能允许的变化。
    其次,标准化您的监控工具的输出可能也是有益的。如果您的数据在最终进入分析平台之前在管道中进行处理,请考虑添加一个单独的字段,将所有特殊字符转换为 ASCII 等效字符并去除所有非字母数字值(以去除引号和选项字符)。这样您的规则可能会有更高的命中率。
    此外,使用数据分析来检测混淆尝试本身。查看包含在您的 IT 资产中流行率较低的字符的进程命令行,比较字符密度级别,或者甚至只是查找包含非 ASCII 字符的命令行参数并从那里开始工作。
    还要确保您的关注点比仅处理字符串更广泛。在可能的情况下,关注命令行试图实现的实际行为,例如文件创建、注册表更改或网络连接。例如,与其尝试捕获所有可能的恶意 wevtutil 命令,不如寻找 wevtutil 写入意外位置(系统信息发现?[9])或检查事件日志中的事件 ID 1102(指示器删除?[10 ])。
    最后,请记住,不可能在 100% 的时间里 100% 地检测到错误 - 但是,您检测的越多,就越难让攻击者完全不被注意。
    (hhh这里我是直接google翻译+校对的,目前还不咋想关注如何检测:)

    参考链接

    [1] https://github.com/danielbohannon/Invoke-DOSfuscation#/
    [2] https://www.fireeye.com/content/dam/fireeye-www/blog/pdfs/dosfuscation-report.pdf#/
    [3] https://carbonfibersecurity.com/articles/TaniumotherEDRHIDSbypass
    [4] https://www.unicode.org/charts/nameslist/n_02B0.html
    [5] https://www.trustedsec.com/blog/circumventing-encodedcommand-detection-powershell/
    [6] https://github.com/wietze/windows-command-line-obfuscation
    [7] https://github.com/SigmaHQ/sigma#/
    [8] https://github.com/Neo23x0/sigma/wiki/Rule-Creation-Guide#detection
    [9] https://attack.mitre.org/techniques/T1082/
    [10] https://attack.mitre.org/techniques/T1070/001/