Windows 命令行混淆

  • A+
所属分类:安全闲碎
Windows 命令行混淆

开卷有益 · 不求甚解

Windows 命令行混淆

前言

许多 Windows 应用程序有多种方式可以表达相同的命令行,通常是出于兼容性或易用性的原因。结果,命令行参数的实现不一致,由于变化的数量,使得检测特定命令变得更加困难。这篇博文展示了 40 多个常用的内置 Windows 应用程序如何容易受到命令行混淆形式的影响,并提供了一种用于分析其他可执行文件的工具。


命令行参数

想要被服从的人必须知道如何指挥。

马基雅维利(李维三世的论述,第二十二章)

事实证明,当谈到计算机时,这句 16 世纪的名言仍然非常适用。毕竟,在大多数操作系统上,进程都有一个“命令行”组件,它允许(启动)父进程将信息传递给子进程。这个新创建的进程可以访问命令行,这可能会根据在命令行上找到的内容更改其进程流。这个概念是构成“计算机”的核心:它能够执行一组指令、程序,接受各种输入。

尽管它在计算中发挥着基本作用,但对于命令行上可以找到的各个部分的称呼似乎没有达成一致。有些人认为命令行参数、参数、选项、标志、开关是一回事,有些人则有不同的含义。这篇文章将使用以下术语:

Windows 命令行混淆
img

整个行是命令行,它由命令行参数(由空格分隔)组成。虽然都是参数,但它们有不同的作用:例如,第一个参数通常是被调用的进程- 或者,如果您在命令提示符中,这将是command。在上面的例子中,后面跟着更多的参数。更具体地说,前三个是命令行选项,通常以特殊字符开头。在这三个选项中,前两个是*开关,因为它们不需要进一步的输入,而第三个是一个标志,*因为它后面是进一步的参数,这些参数是这个参数的“输入”。

选项炭是用作命令行选项的前缀的字符。在 Windows 上,这通常是正斜杠 ( /),在类 Unix 系统-上,主要使用连字符 ( )。这是约定俗成而不是规则:因为命令行是由正在执行的程序解析的,开发人员可以完全自由地定义应该以什么格式传递参数。

公约很难。计算历史告诉我们,如果没有标准化,事情往往会以混乱结束——这当然适用于命令行。

兼容性

命令行解析实践缺乏标准化导致用户混淆:很容易混淆不同的实践,导致执行不成功和很多挫折。为了帮助用户,一些程序被设计为接受多种约定。一个常见的例子是接受正斜杠和连字符作为选项字符,这将在下一节中更详细地讨论。

另一个混淆源是可用的不同字符编码。这导致了旧程序(通常只接受 ASCII)和新程序(现在通常接受 UTF-8Unicode)之间的各种兼容性问题。一些程序试图通过过滤掉某些字符或将特定字符转换为 ASCII 等价物来解决这些不兼容性。

这导致了一些程序将各种不同的命令行参数视为一个相同的情况。您可以将此类实例称为同义命令行,因为尽管传递给流程的数据不同,但它们的执行和结果是相同的。

检测问题

虽然能够以不同的方式表达相同的命令可能对某些用户有所帮助,但它往往会使检测和预防工作更加困难。大多数威胁检测软件(AV、EDR 等)将监控进程执行,并寻找可能表示恶意使用的命令行参数。由于攻击者希望不被发现,他们可能会利用命令行的灵活性来逃避检测。这可能包括从绕过基于关键字的检测的小调整到全面的命令行混淆以隐藏原始命令。

命令行级别混淆的一个很好的例子是 Daniel Bohannon 的优秀作品 DOSfuscation [ 1 , 2 ],它特别关注 Windows 命令行提示符 CMD。即使正在执行的 CMD '理解'并执行混淆的命令,它对人类来说可能看起来难以理解,并且监控软件也很可能被蒙在鼓里。

同义命令行的现象超出了 DOSfuscation,因为它适用于更多程序,而不仅仅是 CMD。这里的主要区别在于,您不仅会愚弄命令行提示符,还会愚弄正在执行的程序本身。正如稍后将展示的,虽然 DOSfuscation 工作可能会在检测软件使用的记录器中以未混淆的形式结束,但同义的命令行参数不会。

同义命令行:方法

为了看到这一点,我们现在将仔细研究可能导致同义命令行的五种不同方法。

(1) 选项字符替换

考虑 Windows 可执行文件ping。由于该程序是原始 Unix 版本的移植,因此帮助页面建议命令行选项应使用连字符作为选项字符,例如ping -n 0 127.0.0.1. 这与大多数其他使用正斜杠的 Windows 原生命令行工具不一致。大概是为了帮助困惑的用户,该程序还接受正斜杠作为选项 char:ping /n 0 127.0.0.1也能工作。

大多数使用连字符的内置 Windows 可执行文件也接受正斜杠,但反之则不然。find /i keyword例如,该命令将显示包含单词“keyword”的所有文件,同时find -i keyword会导致错误。

尽管正斜杠和连字符是最常见的可用选项,但有些程序支持更多的选项字符。certutil碰巧接受连字符、斜线和斜线的大多数 Unicode 表示,例如除法斜线 (0x2215) 和分数斜线 (0x2044) [ 3 ]。

正如我们将在其他变体中看到的那样,可执行文件很少记录这些替代命令行选项,这意味着您会偶然、“蛮力”或通过逆向工程找到它们。

(2) 字符替换

另一种方法是用类似的字符替换命令行中的其他字符(即除了选项字符之外)。尤其是当您考虑整个 Unicode 范围时,在 ASCII 范围内也发现了许多可能接受的字母变体。

Unicode 包含一个间距修饰符字母范围(0x02B0 - 0x02FF) [ 4 ],其中包括 ˪、ˣ 和 ˢ 等字符。一些命令行解析器将它们识别为字母并将它们分别转换回 l、x 和 s。这方面的一个例子是reg,它将reg export HKCU out.regreg eˣport HKCU out.reg视为平等。

Windows 命令行混淆
img

成功执行的一个例子reg eˣport HKCU out.reg

事实证明,有更多 Unicode 范围包含某些程序接受的字符。

(3) 字符插入

同样,有时可以在命令行中插入额外的字符,这些字符将被执行程序忽略。例如,某些可执行文件可能会删除不可打印的字符,同时也可能会过滤掉某些可打印的字符。

例如,Windows 事件日志工具wevtutil似乎接受在随机位置插入某些范围内的 Unicode 字符的命令行。因此执行wevtutil gli hardwareeventsandwevtutil gࢯli hardwareevents将产生完全相同的输出,尽管后者在第一个参数的中间包含一个阿拉伯字母。

Windows 命令行混淆
img

成功执行的一个例子wevtutil gࢯli hardwareevents

由于命令行提示的标准输入有时不支持可用于此技术的字符(例如,因为它们不可打印),您可能必须使用字节表示法插入字符。从截图中可以看出,在这种情况下,字符被正确地传递给了进程。

(4) 行情插入

在保持流程完整的同时操纵命令行的另一种方法是插入引号。尽管这听起来像是先前技术的一个子集,但这里的要求是引号成对出现。

您可能熟悉在参数周围加上引号的概念。就拿dir "c:windows"例如,这是有效的一样dir c:windows,由于缺乏空间。大多数程序都接受这个约定。鲜为人知的是,大多数程序在任意位置接受引号:该命令dir c:"win"d""ow"s"也可以工作。只要每个参数的引号数是偶数并且后面的引号不超过两个,大多数程序似乎都接受这一点。

Windows 命令行混淆
img

成功执行的一个例子netsh ad"vfi"rewall show currentprofile state

值得注意的是,在命令提示符中使用引号可能很棘手,因为它们通常在将引号传递给底层程序之前自己处理引号。cmd例如,解决此问题的一种方法是将每个引号加倍,因此要获得如上所示的等效执行,您必须运行netsh ad""vfi""rewall show currentprofile state.

(5) 速记

插入和替换字符后,我们还需要尝试删除字符。一些应用程序允许为其他冗长的命令行选项提供“速记”,从而更容易输入它们。

这是基于 Unix 的工具(例如grep -i keywordvs grep --ignore-case keyword)中的一个众所周知的概念,但在 Windows 上则不然。然而,一些程序接受缩短的版本。有些程序采用与 Unix 类似的方法并接受单字母版本(例如cmdkey /lvs cmdkey /list),有些程序接受其他缩写版本(例如wevtutil glivs wevtutil get-loginfo),而其他程序则采用“通配符方法”。这方面的一个例子是 PowerShell,它的许多关键字允许您在关键字 [ 5 ]的末尾省略一个或多个字符。

Windows 命令行混淆
img

成功执行powershell /encodedcommand ZQBjAGgAbwAgACIASQB0ACAAdwBvAHIAawBzACEAIgA=后跟 13 种不同速记的示例。

也许除了最短的变体之外,在/e我看来,接受这些速记方法对很少人有帮助,但它确实使事情变得更加复杂和不可预测。例如,PowerShell 只接受缩短的版本,前提是它不会导致另一个命令之间的歧义。出于这个原因,关键字/noprofile的最短变体是/nop, 因为/no会与 eg 发生冲突/noexit。除了这种“通配符方法”之外,PowerShell 在某些情况下也接受首字母缩略词,因此尽管屏幕截图中没有显示,/ec但也可以用作/encodedcommand.

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

如前几节所述,处理命令行参数没有标准。有些程序非常严格,而有些程序会非常努力地将给出的任何内容变成它可以理解的内容。找出特定程序的命令行行为也是有问题的:帮助页面通常只指定传递参数的“首选”方式(方法 1 和 5),通常从不描述如何处理意外字符(方法 2-4 )。

确定这一点的最准确方法似乎是逆向工程。但是,这是一项非常耗时的活动,尤其是当您考虑到某些程序的复杂性时。因此,更实用的方法是简单地使用不同的字符重复尝试所有绕过技术并比较结果。

这种方法的概念验证实现 [ 6 ] 使我们能够快速分析所描述的五种行为中的哪一种。可以在 GitHub 项目页面上找到对 PoC 的更详细解释、其基本假设和警告。

出于本文的目的,PoC 用于分析 40 多个经常用于攻击的内置 Windows 可执行文件。

显示40个条目

搜索:

可执行 选项字符替换 字符插入 字符替换 引用插入 速记
arp.exe ➡️ ✔️ (8) ✔️ (3) ✔️ ✔️ 不适用
at.exe ➡️ ✔️ ✔️
bitsadmin.exe ➡️ ✔️
cacls.exe ➡️ ✔️ (3,087) ✔️ ✔️ ✔️
certutil.exe ➡️ ✔️ (5) ✔️ (6) ✔️ ✔️
cmdkey.exe ➡️ ✔️ (1) ✔️ (1) ✔️ ✔️
cmstp.exe ➡️ ✔️ (3) ✔️
csc.exe ➡️ ✔️ (1) ✔️
curl.exe ➡️ ✔️ (3) 不适用
findstr.exe ➡️ ✔️ (11) ✔️ ✔️
fltmc.exe ➡️ 不适用 ✔️
forfiles.exe ➡️ ✔️ ✔️ 不适用
icacls.exe ➡️ ✔️
ipconfig.exe ➡️ ✔️ (1) ✔️ (3,370) ✔️ ✔️
jsc.exe ➡️ ✔️ (1) ✔️ ✔️
mpcmdrun.exe ➡️ ✔️ (1) ✔️
msiexec.exe ➡️ ✔️ (1) 不适用
net.exe ➡️ 不适用 ✔️ (1) ✔️ ✔️
netsh.exe ➡️ 不适用 ✔️
netstat.exe ➡️ ✔️ (8) ✔️ ✔️ 不适用
nslookup.exe ➡️ ✔️ (5) ✔️* ✔️* ✔️ ✔️*
ping.exe ➡️ ✔️ (1) ✔️ (1) ✔️ ✔️
powershell.exe ➡️ ✔️ (4) ✔️ ✔️
reg.exe ➡️ ✔️ (3) ✔️ (6) ✔️ ✔️
regsrv32.exe ➡️ ✔️ (1) ✔️* ✔️* ✔️ ✔️*
robocopy.cmd ➡️ ✔️ (1)
route.exe ➡️ 不适用 ✔️ ✔️
sc.exe ➡️ 不适用 ✔️
schtasks.exe ➡️ ✔️ (2) ✔️ ✔️
systeminfo.exe ➡️ ✔️ (2) ✔️ ✔️
takeown.exe ➡️ ✔️ (2) ✔️ ✔️ 不适用
taskkill.exe ➡️ ✔️ (2) ✔️ ✔️
tasklist.exe ➡️ ✔️ (2) ✔️
vbc.exe ➡️ ✔️ (1) ✔️
vssadmin.exe ➡️ 不适用 ✔️
wevtutil.exe ➡️ 不适用 ✔️ (3,369) ✔️ ✔️
whoami.exe ➡️ ✔️ (2) ✔️
winrm.cmd ➡️ ✔️ (1) ✔️ (1) ✔️
winrs.exe ➡️ ✔️ (1) ✔️
wmic.exe ➡️ 不适用

:1) 分析使用的是Windows 10 version 2004;2) 对于每个 Windows 可执行文件,只使用了一个特定的命令 - 其他命令可能会导致不同的结果;3) 用星号 () 表示的条目针对接受每个*变体的命令行参数。在的情况下,nslookup -querytype=ALL (...)例如,在PoC发现,预期的输出,无论是插入什么字符的返回。事实证明,nslookup忽略命令行选项的第一个字母之后的所有字母(-q在本例中),这意味着可能的排列数量几乎是无限的。

每个测试的可执行文件的完整报告,包括测试的命令和找到的字符,可以在 GitHub [ 6 ]上找到。

检测问题

了解所有这些使我们能够退后一步,从威胁搜寻的角度审视命令行行为。

如果你想用特定的命令行参数检测程序的执行,同义的命令行参数会带来问题。例如,编写 Sigma 规则 [ 7 ] 来检测指定和参数的certutil执行可以表示为:urlcache``f

detection:
    selection:
        Image: "*\certutil.exe"
        CommandLine|contains|all:
            - "/urlcache"
            - "/f"
    condition: selection

问题是立即清除:如果一个攻击者使用-urlcache/urᴸcache/₞urlcache/url"cach"e-或更糟的是,一个组合:-₞u₞rᴸ"c₞a₞ch"e,该命令将仍然工作,但上面的规则不会拿起行为。将所有变体添加到规则中不是一种选择,因为结合各种绕过技术将导致数千甚至数百万个排列。

Windows 命令行混淆
img

成功执行混淆版本的示例certutil -f -urlcache -split https://wietze.github.io/robots.txt output.txt

可能尝试的第一个潜在解决方案是使规则更通用。例如,解决选项字符问题,我们可以将选项字符排除在定义之外:

detection:
    selection:
        Image: "*\certutil.exe"
        CommandLine|contains|all:
            - "urlcache"
            - "f"
    condition: selection

虽然这似乎解决了选项字符问题,但它创建了一个新问题。寻找urlcache而不是/urlcache不太可能导致许多误报,但是,寻找只是f代替/f很可能会产生误报。

第二种可能的解决方案是使用更巧妙的匹配选项。许多提供寻找流程执行功能的 EDR 工具往往仅限于简单的“字符串包含”式功能,如上述示例中所示。有些允许正则表达式,但这似乎仍然很少见。尽管 Sigma 支持正则表达式值,但在 30 个支持的后端中似乎只有 7 个实际实现了它。虽然这并不一定意味着其他 23 个平台不支持它,但至少表明它可能不是一条明显的下降路径。供应商可能不愿提供此选项的一个关键原因首先是性能:正常的编译正则表达式的复杂度为O(n),但通过递归扩展,可以创建永远不会结束计算的正则表达式。Sigma 项目也不鼓励使用正则表达式 [ 8 ]。

对于那些支持正则表达式的工具,可以解决选项字符问题(假设您替换[-/\]为所有选项字符的列表):

detection:
    selection:
        Image: "*\certutil.exe"
        CommandLine|re: "^(?=.*[-/\]urlcache)(?=.*[-/\]f).+.*"
    condition: selection

正则表达式还可以帮助有效地捕获速记关键字。不幸的是,由于选项和排列的大小,字符插入和替换通常是不可行的。

解决这些类型的混淆永远不会是无懈可击的。需要更具弹性的方法。

泛化能力好的检测

为避免落入隧道视觉检测工程的陷阱,您可以采取一些措施来实现强大的威胁搜寻过程。

首先,确保规则被合理地定义为“广泛”,捕捉所有可能的变化,同时考虑到误报的潜在增加。提供的 PoC [ 6 ] 可用于查找您的规则可能允许的变化。

其次,标准化监控工具的输出也可能是有益的。如果您的数据在最终进入分析平台之前在管道中进行处理,请考虑添加一个单独的字段,将所有特殊字符转换为 ASCII 等效字符并去除所有非字母数字值(以去除引号和选项字符)。在此字段上运行您的规则可能会有更高的命中率。

此外,使用数据分析来检测混淆尝试本身。查看包含在您的 IT 资产中使用率较低的字符的流程命令行,比较字符密度级别,甚至只是查找包含非 ASCII 字符的命令行参数并从那里开始工作。

还要确保您的重点比单独的流程字符串更广泛。在可能的情况下,关注命令行试图实现的实际行为,例如文件创建、注册表更改或网络连接。例如,与其试图捕获所有可能的恶意wevtutil命令,不如寻找wevtutil写入意外位置(系统信息发现?[ 9 ])或检查事件 ID 1102的事件日志(指标删除?[ 10 ]) .

结尾

最后,请记住,你不可能在100%的时间里发现100%的坏事--然而,你能发现的越多,你就越难让攻击者完全不被注意到。因此,考虑到上述情况将使你更近一步。

欲听从者,必知指挥”,懂指挥者,必知察。

译文申明

  • 文章来源为近期阅读文章,质量尚可的,大部分较新,但也可能有老文章。
  • 开卷有益,不求甚解,不需面面俱到,能学到一个小技巧就赚了。
  • 译文仅供参考,具体内容表达以及含义, 以原文为准 (译文来自自动翻译)
  • 如英文不错的,尽量阅读原文。(点击原文跳转)
  • 每日早读基本自动化发布,这是一项测试

Follow Me

微信/微博:red4blue

公众号/知乎:blueteams

Windows 命令行混淆



本文始发于微信公众号(甲方安全建设):Windows 命令行混淆

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: