这篇文章介绍了一种又快又猥琐的方法,可以在Windows Scripting Host 中禁用AMSI,而不需要管理员权限或者修改注册表密钥/系统状态(比如Defender之类AV都会监控这些修改)。我在最新的Windows10 1803型号上做了测试,仅供参考。
注:AMSI是微软的反恶意软件扫描接口。
我注意到DotNetToJScript 的默认脚本文件不能工作了,因为Windows Defender会阻止它,感谢所有将我的工具标记成恶意软件的朋友。
如果你仔细查看屏幕截图你会发现图中显示“Affected items:”是以amsi为前缀的。这表明检测不是基于文件而是基于 Antimalware Scan Interface的行为。当然你可以通过重新编写脚本来解决这个问题(它以一种很奇怪的方式在scriptlet中工作),但我永远都不会这样做。因为我从来没有为了使用“Sharpshooter“而编写它。我之前有一个绕过AMSI的想法,现在正好可以测试一下。
在看到MDsec最近关于PowerShell中AMSI的新bypass方法以及Tal Liberman在BlackHat Asis的演讲后,我受到了一些启发,准备再次挖掘下这种技术。但是我没在其它地方看到关于这种技术的描述,但是我还是决定写出来,因为如果我错了肯定会有人能纠正我的错误。
Windows脚本宿主如何加载AMSI
AMSI是作为COM服务器被实现的,用于通过内部通道与已经安装的安全产品进行通信。先前对AMSI的攻击是Matt Nelson通过劫持COM注册表完成的。脚本主机不支持直接调用COM对象,而是通过AMSI.DLL中导出的方法调用,我们可以通过在Process Monitor中设置适当的过滤器来观察被加载的情况。
我们可以使用Process Monitor的堆栈跟踪功能来查找负责加载的AMSI.DLL的代码。它实际上是脚本引擎的一部分,例如Jscript或者VBScript,而不是WSH的核心部分。代码的基础知识如下:
HRESULT COleScript::Initialize() {
hAmsiModule = LoadLibraryExW(L"amsi.dll",
nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (hAmsiModule) { ①
// Get initialization functions.
FARPROC pAmsiInit = GetProcAddress(hAmsiModule, "AmsiInitialize");
pAmsiScanString = GetProcAddress(hAmsiModule, "AmsiScanString");
if (pAmsiInit){
if (pAmsiScanString && FAILED(pAmsiInit(&hAmsiContext))) ②
hAmsiContext = nullptr;
}
}
bInit = TRUE; ③
return bInit;
}
基于这段代码我们能看到它在加载了AMSI DLL①后调用了AmsiInitialize ②方法来获取上下文句柄。这段代码的有趣之处是无论AMSI是否初始化它都会返回成功③。有三种情况会导致这段代码失败从而永远不会初始化AMSI,阻止加载AMSI.DLL,使AMSI.DLL不包含诸如AmsiInitialize之类的方法,或让AmsiInitializeto失败。
Tal Liberman在他的PPT第56页中提到你可以使用应用程序将AMSI复制到另一个目录,它将尝试从该目录加载AMSI.DLL。由于这个AMSI可能是一些不相关的DLL,它们不能导出AmsiInitialize ,那么加载将成功,其实是失败的。然而这个方法在有标志LOAD_LIBRARY_SEARCH_SYSTEM32时候不会生效,这意味着LoadLibraryEx 始终尝试首先从SYSTEM32 加载。AmsiInitialize 的失败显然是一个问题,但是我们很难阻止这个代码从 SYSTEM32 加载AMSI.DLL,那我们怎么办呢?我们当然可以预加载另一个AMSI.DLL。
劫持AMSI.DLL
我们如何以最少的代价加载替换AMSI.DLL?可能不是所有人都意识到LoadLibrary将在已加载DLL中查找所有请求加载DLL的名字,以便不会加载相同的DLL两次。这不仅适用于DLL的名称,还适用于主可执行文件。因此,如果我们能够让库加载器相信我们的主可执行文件就是叫做AMSI.DLL,那么加载器将返回它的上下文句柄。无法找到导出的AmsiInitialize会导致AMSI无法初始化但会继续执行而不是检查它。
如何在不修改进程内存的情况下将主可执行文件的名称更改为?很简单,我们将WSCRIPT.EXE复制到另一个目录,但是将其命名为AMSI.DLL然后运行它。我们如何运行AMSI.DLL,它不是需要SOMETHING.EXE吗?
在windows上有两种主要的进程执行方法,ShellExecute或者CreateProcess。调用ShellExecute时候,API会查找扩展的处理程序,比如.EXE或者.DLL,并根据结果执行特定操作。通常.EXE将重定向到只调用CreateProcess,其中.DLL会尝试将其加载到已注册的视图中,在默认的系统上则不会。
但是,CreateProcess不关心文件具有什么扩展名,只要它是基于其PE头的可执行文件即可。(实际上你可以使用native API调用DLL,但是我们无权在WSH访问)。因此,只要我们能够调用CreateProcess创建AMSI.DLL(WSCRIPT.EXE的副本),就会执行。要做到这一点,我们可以使用WScript.Shell的Execmethod直接调用CreateProcess。
var obj = new ActiveXObject("WScript.Shell");
obj.Exec("amsi.dll dotnettojscript.js");
这样一个名为AMSI.DLL的进程就运行了。当Jscript或者VBScript尝试加载AMSI.DLL时,它将获得主可执行文件的句柄,AMSI不再工作。据我所知,这个简短的script并没有被AMSI本身检测到,所以运行你想运行的“真正的”代码是安全的。
总结一下这个攻击:
1. 启动存根脚本,将WSCRIPT.EXE复制到已知的位置,并且命名为AMSI.DLL。这是位于不同位置的同一目录签名可执行文件,因此可能绕过纯粹基于签名的检测。
2. 在存根脚本中使用“真实”脚本执行新创建的AMSI.DLL
3. 额,就这么多了。
AFAIK不适用于PowerShell,因为它似乎破坏了代码中的一些重要东西,这使得PS无法运行。我不知道这是不是设计好的。不过,我知道这是一种绕过AMSI很蠢的方法,但它只是表明,恶意软件很容易在对检测平台进行修改时,这种自检功能很少能工作的很好。
审核人:yiwang 编辑:边边