在当下的红队攻防演练活动里,WebShell 仍旧是攻击者实现内网初始渗透落地、横向拓展渗透范围以及维持系统权限的关键手段之一。不过,随着主流杀毒软件不断更新针对传统 WebShell 的签名库,特别是对像 Process.Start 这类关键 API 的监控力度持续加大,红队面临着新的挑战——如何绕过杀毒软件的检测。
01. 剖析 Emit 技术
在此背景下,今天为大家介绍一种利用 System.Reflection.Emit 所具备的动态代码生成能力,达成免杀执行命令的方法。该方法借助 IL 动态拼装技术来执行 “cmd.exe /c whoami” 命令,在 WebShell 的应用场景中顺利完成命令执行操作,进而绕过绝大多数基于关键字符串特征匹配的防御机制。
在 .NET 框架的丰富功能体系中,System.Reflection.Emit 犹如一颗璀璨的明珠,它为开发者提供了一套强大且灵活的 API 集合,专门用于在程序运行期间动态地生成和执行代码。这一特性赋予了开发者极大的自由度,使得他们能够在程序运行时按需构建程序集、模块、类型以及方法,并通过中间语言(IL)指令来精准定义这些代码的具体行为。
1.1 深入理解IL指令
在 .NET 的编译与执行流程中,C#、VB.NET 等高级语言所编写的代码并不会直接转换为机器码,而是会经历一个中间阶段——编译成中间语言(IL)。IL 是一种面向对象的低级语言,其风格与汇编语言颇为相似。它作为 .NET 生态中的关键一环,由 .NET 公共语言运行库(CLR)中的即时(JIT)编译器在程序运行时动态地翻译成原生机器码,从而确保程序能够在不同的硬件平台上顺利执行。
从这个角度来看,IL 堪称 .NET 的汇编层。对于安全研究人员和攻击者来说,IL 就像是一扇观察程序运行细节的窗户,透过它能够洞察程序在底层的执行逻辑。而对于红队而言,IL 更是通过 System.Reflection.Emit 构造恶意行为的绝佳“注射点”。通过巧妙地操控 IL 指令,红队能够在目标系统中植入隐蔽的恶意代码,实现各种攻击目标。
IL 采用的是栈式指令集模型,这与传统的 x86 架构有着显著的区别。在 x86 架构中,运算主要依赖于寄存器,而 IL 则摒弃了这种方式,所有的操作都基于“压栈 - 弹栈”的机制来执行。具体来说,当需要使用某个数据时,先将其压入栈中;当需要对该数据进行操作时,再从栈中弹出并进行相应的处理。每条 IL 指令都精确地控制着一次操作,例如加载常量、调用方法等。而且,所有的方法都必须以 Ret 指令作为结尾,这标志着方法的执行结束并将控制权返回给调用者。
为了更好地理解 IL 指令,我们来看一个具体的例子。假设我们有以下一段简单的 .NET 代码:
Process.Start("cmd.exe","/c whoami");
这段代码的功能是启动一个命令提示符窗口并执行 whoami
命令,用于显示当前用户的身份信息。当这段代码被编译成 IL 形式时,大致如下:
ldstr &nb