简介
反恶意软件扫描接口 (AMSI) 是对动态恶意脚本执行进行深入检查的 Microsoft Windows 组件。AMSI 可以被不同的防病毒软件调用,提供对PowerShell 脚本、JavaScript 和 VBA 宏等恶意脚本的扫描。Practical Security Analytics提出了一种通过Hook .Net Api绕过AMSI的新技术,本文主要对此技术进行分析与复现。
01 背景
反恶意软件扫描接口 (AMSI) 的开发旨在为 Windows 环境中执行恶意脚本提供安全防护支持。目前,主流对抗AMSI的技术主要有编码混淆、内存补丁、强制AMSI初始化失败、amsi.dll劫持、修改注册表项等。其中,内存补丁通过Hook windows API AmsiScanBuffer() 函数,使其始终返回AMSI_RESULT_CLEAN值以达到绕过的目的。而目前Hook的实现多针对于asmi.dl中的AmsiScanBuffer函数,实际上,powershell是.Net程序集,在执行命令或脚本之前,powershell会调用AmsiUtils.ScanCotent静态方法,该方法将命令发送到amsi接口进行检查,如下图所示。AmsiUtils是一个内部类,位于System.Management.Automation.dll中。如果修补ScanCotent方法,就可以阻止对amsi.dll中 AmsiScanBuffer 的调用,从而实现asmi绕过。
AmsiUtils.AmsiNativeMethods.AMSI_RESULT result = AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_CLEAN;
fixed (char* chPtr = content) {
IntPtr buffer = new IntPtr((void*)chPtr);
hresult = AmsiUtils.AmsiNativeMethods.AmsiScanBuffer(AmsiUtils.amsiContext, buffer, (uint)(content.Length * 2), sourceMetadata, AmsiUtils.amsiSession, ref result);
}
02 技术分析
该技术的核心是Hook .Net API。由于.Net程序集运行在CLR环境中,因此.Net API Hook 与常规windows API Hook不一样。
.Net API Hook 的步骤有6步:
确定要hook的目标函数
定义具有相同函数签名的函数
利用反射找到函数
编译目标函数和自定义函数
找到目标函数和自定义函数在内存中的地址
修补目标函数,该步骤与Hook windows API一致
可以看到,与windows hook相比,重点在于编译函数和定位函数地址,这是由.Net的特性决定的。.NET 编写的代码经编译后,会得到一个由通用中间语言(CIL)构成的二进制文件,CIL构成的二进制文件会交由CLR进行接管, 只有在运行时才会由CLR进行编译(JIT),生成可以直接运行的native code。
我们可以通过RuntimeMethodHandle.GetFunctionPointer()方法获取编译后的native code地址。但在.Net中,如果函数没有实际被调用,那么该函数可能不会被编译,也就无法定位到函数地址。幸运的是,RuntimeHelpers类可以手动触发JIT编译。如下代码所示。
//反射找到函数
MethodInfo ori = typeof(CmdletInfo).Assembly.GetType("System.Management.Automation.A"+"ms"+"i"+"Ut"+"ils").GetMethod("ScanC"+"ontent", BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo rep = typeof(Program).GetMethod("ScacnConte"+"ntStub", BindingFlags.NonPublic | BindingFlags.Static);
//JIT 编译函数
RuntimeHelpers.PrepareMethod(ori.MethodHandle);
RuntimeHelpers.PrepareMethod(rep.MethodHandle);
//获取函数地址
IntPtr originalSite = original.MethodHandle.GetFunctionPointer();
IntPtr replacementSite = replacement.MethodHandle.GetFunctionPointer();
//修补目标函数
.....
这里多说一句,未编译前MethodHandle.GetFunctionPointer()获得的地址指向PreJitStub块,大小为5个字节,该块的作用是调用JIT进行编译。
另外,在自定义函数这一步,由于函数总是返回1,因此c#编译器认为函数不是完整的函数调用,从而优化整个函数代码,无法hook。可以通过向函数添加属性来告诉编译器不要优化或内联该方法。
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private static int ScanContentStub(string content, string metadata) {
return 1; //AMSI_RESULT_NOTDETECTED
}
03 利用
为了在 PowerShell 中安装Hook,需要把程序集加载到powershell中,作者提出了两种方法,一是使用Add-Type动态编译c#代码并加载到session中;二是将已编译的程序集作为 Base64 嵌入脚本中,并以反射方式将该 DLL加载到内存中;还有其他的利用思路,比如未托管代码使用CLR Hosting技术来加载Hook程序集,未托管代码可以做持久化,注入到启动项当中。
该技术针对.Net API进行Hook,可以较好规避杀软,但需要对代码中的一些静态特征做混淆,如"System.Management.Automation.Amsiutils"字符串。
04 总结
本文介绍了利用Hook .Net API进行amsi绕过的一种新技术,对其中关键技术点进行了分析,并简要介绍了Hook .Net API的其他利用思路。
附录 参考链接
[1] https://practicalsecurityanalytics.com/new-amsi-bypass-using-clr-hooking/
[2] https://learn.microsoft.com/en-us/archive/msdn-magazine/2005/may/net-framework-internals-how-the-clr-creates-runtime-objects
绿盟科技天元实验室专注于新型实战化攻防对抗技术研究。
研究目标包括:漏洞利用技术、防御绕过技术、攻击隐匿技术、攻击持久化技术等蓝军技术,以及攻击技战术、攻击框架的研究。涵盖Web安全、终端安全、AD安全、云安全等多个技术领域的攻击技术研究,以及工业互联网、车联网等业务场景的攻击技术研究。通过研究攻击对抗技术,从攻击视角提供识别风险的方法和手段,为威胁对抗提供决策支撑。
M01N Team公众号
聚焦高级攻防对抗热点技术
绿盟科技蓝军技术研究战队
官方攻防交流群
网络安全一手资讯
攻防技术答疑解惑
扫码加好友即可拉群