13.4 DirectX内部劫持绘制

news2025/1/12 13:20:49

相对于外部绘图技术的不稳定性,内部绘制则显得更加流程与稳定,在Dx9环境中,函数EndScene是在绘制3D场景后,用于完成将最终的图像渲染到屏幕的一系列操作的函数。它会将缓冲区中的图像清空,设置视口和其他渲染状态,执行顶点和像素着色器,最后在后台缓冲区中生成一张完整的渲染图像,然后将其呈现到屏幕上,完成一次绘制操作。

EndSceneIDirect3DDevice943个函数,我们通过对该函数进行挂钩,并将该函数绘制之前的流程劫持到自身进程内的MyEndScene函数内做图形的增加工作,当我们增加好所需功能后再将该函数指向原来的函数入口,此时EndScene函数再次渲染则会出现我们所新增的功能,利用这种方式即可实现屏幕图形绘制效果,至于笔者是如何确定该函数是第43个的,读者可以在IDirect3DDevice9上面右键查看定义,至此即可看到函数所在位置;

13.4.1 封装Hook劫持功能

首先要实现劫持需要封装钩子函数,如下代码片段则是一个简单通用的钩子结构体的封装,该结构体在此处其实是当作类来使用了,其中读者只需要调用JmpCode()函数则可自动将需要跳转的内存地址与JMP指令相结合,当有了跳转指令的机器码后,则我们只需要通过VirtualProtect设置内存属性为可写,并通过调用memcpy函数即可实现对特定内存的地址替换功能,如下代码中hook()函数用于挂钩,unhook()函数则用于摘除,代码比较通用读者可应用于任何一个领域。

// ---------------------------------------------------------------------------------
// 挂钩摘钩结构体
// ---------------------------------------------------------------------------------

#pragma pack(push)
#pragma pack(1)
#ifndef _WIN64
struct JmpCode
{
private:
    const BYTE jmp;
    DWORD address;

public:
    JmpCode(DWORD srcAddr, DWORD dstAddr) : jmp(0xE9)
    {
        setAddress(srcAddr, dstAddr);
    }

    void setAddress(DWORD srcAddr, DWORD dstAddr)
    {
        address = dstAddr - srcAddr - sizeof(JmpCode);
    }
};
#else
struct JmpCode
{
private:
    BYTE jmp[6];
    uintptr_t address;

public:
    JmpCode(uintptr_t srcAddr, uintptr_t dstAddr)
    {
        static const BYTE JMP[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
        memcpy(jmp, JMP, sizeof(jmp));
        setAddress(srcAddr, dstAddr);
    }

    void setAddress(uintptr_t srcAddr, uintptr_t dstAddr)
    {
        address = dstAddr;
    }
};
#endif
#pragma pack(pop)

// ---------------------------------------------------------------------------------
// Hook挂钩与摘够函数
// ---------------------------------------------------------------------------------

// 开始Hook
int hook(void* originalFunction, void* hookFunction, BYTE* oldCode)
{
    JmpCode code((uintptr_t)originalFunction, (uintptr_t)hookFunction);
    DWORD oldProtect, oldProtect2;

    // 设置内存保护方式为可读写
    if (VirtualProtect(originalFunction, sizeof(code), PAGE_EXECUTE_READWRITE, &oldProtect))
    {
        memcpy(oldCode, originalFunction, sizeof(code));
        memcpy(originalFunction, &code, sizeof(code));

        // 恢复内存保护方式
        if (VirtualProtect(originalFunction, sizeof(code), oldProtect, &oldProtect2))
        {
            return 1;
        }
    }
    return 0;
}

// 取消Hook
int unhook(void* originalFunction, BYTE* oldCode)
{
    DWORD oldProtect, oldProtect2;

    // 设置保护方式为可读写
    if (VirtualProtect(originalFunction, sizeof(JmpCode), PAGE_EXECUTE_READWRITE, &oldProtect))
    {
        memcpy(originalFunction, oldCode, sizeof(JmpCode));

        // 恢复内存保护方式
        if (VirtualProtect(originalFunction, sizeof(JmpCode), oldProtect, &oldProtect2))
        {
            return 1;
        }
    }
    return 0;
}

13.4.2 定制MyEndScene

接着就是自定义绘图部分,此处第一个DrawBox绘图函数我们仅仅提供一个方框的绘制,如果需要更多绘制技巧读者可自行尝试实现,这里我们重点看一下MyEndScene函数,该函数是我们的自定义函数,当进程绘图函数被挂钩后,所有调用原函数的请求都会被路由到此函数内,进入此函数内首先通过g_font == NULL判断函数是不是第一次被调用如果是第一次被调用则对当前模块的字体绘制设备等进行初始化,而如果不是第一次绘制则自动流转到else片段内,此块区域内则是我们自己自由发挥的位置,如下代码中我们仅仅是绘制了一段话,并绘制出了两个方框,并没有做其他功能扩展。

void* endSceneAddr = NULL;
BYTE endSceneOldCode[sizeof(JmpCode)];

ID3DXFont* g_font = NULL;
ID3DXLine* d3dLine = NULL;

// ---------------------------------------------------------------------------------
// 绘图函数
// ---------------------------------------------------------------------------------

// 绘制方框
void DrawBox(float x, float y, float width, float height, float w, D3DCOLOR color)
{
    D3DXVECTOR2 points[5];
    points[0] = D3DXVECTOR2(x, y);
    points[1] = D3DXVECTOR2(x + width, y);
    points[2] = D3DXVECTOR2(x + width, y + height);
    points[3] = D3DXVECTOR2(x, y + height);
    points[4] = D3DXVECTOR2(x, y);
    d3dLine->SetWidth(w);
    d3dLine->Draw(points, 5, color);
}

// ---------------------------------------------------------------------------------
// Hook处理函数
// ---------------------------------------------------------------------------------

// 该函数是劫持后的转向函数,这里面可以增加功能
HRESULT STDMETHODCALLTYPE MyEndScene(IDirect3DDevice9* thiz)
{
    // 如果是第一次则初始化绘图库
    if (g_font == NULL)
    {
        // 初始化字体
        D3DXCreateFontA(thiz, 12, 0, FW_HEAVY, 1, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "宋体", &g_font);

        // 线条初始化线条
        D3DXCreateLine(thiz, &d3dLine);

        // 临时摘除钩子
        unhook(endSceneAddr, endSceneOldCode);

        // 设置绘图设备
        HRESULT hr = thiz->EndScene();

        // 继续挂钩
        hook(endSceneAddr, MyEndScene, endSceneOldCode);
        return hr;
    }

    // 不是第一次则直接绘图
    else
    {
        // 自定义绘制流程
        // ---------------------------------------------------------------
        static RECT rect = { 0, 0, 200, 200 };

        // 屏幕写字
        g_font->DrawText(NULL, L"Inject D3D hook Success ...", -1, &rect, DT_TOP | DT_LEFT, D3DCOLOR_XRGB(255, 0, 0));

        // 屏幕绘制方框
        DrawBox(25, 30, 60, 120, 2, D3DCOLOR_XRGB(255, 0, 255));
        DrawBox(45, 60, 35, 70, 2, D3DCOLOR_XRGB(255, 69, 0));

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

        // 恢复钩子
        unhook(endSceneAddr, endSceneOldCode);

        // 执行原函数
        HRESULT hr = thiz->EndScene();

        // 挂钩
        hook(endSceneAddr, MyEndScene, endSceneOldCode);
        return hr;
    }
}

13.4.3 初始化与绘制图形

继续向下则是initHookThread函数,该函数内我们自行创建了一个具有空类名的隐藏窗口,并通过调用Direct3DCreate9实现了对Dx9引擎的初始化,通过调用(*(void***)device)[42]的方式我们即可获取到当前内存中endSceneAddr的原始地址,有了这个地址则直接对其进行Hook替换,此时当有新的请求访问该函数时则会自动路由到MyEndSceneAddr函数内。

// 初始化Hook线程
DWORD WINAPI initHookThread(LPVOID dllMainThread)
{
    WaitForSingleObject(dllMainThread, INFINITE);
    CloseHandle(dllMainThread);

    WNDCLASSEX wc = {};
    wc.cbSize = sizeof(wc);
    wc.style = CS_OWNDC;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpfnWndProc = DefWindowProc;
    wc.lpszClassName = _T("LySharkWindow");

    // 注册窗口类
    if (RegisterClassEx(&wc) == 0)
    {
        return 0;
    }

    // 创建窗口
    HWND hwnd = CreateWindowEx(0, wc.lpszClassName, _T(""), WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, NULL, NULL, wc.hInstance, NULL);
    if (hwnd == NULL)
    {
        return 0;
    }

    // 初始化D3D
    IDirect3D9* d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
    if (d3d9 == NULL)
    {
        DestroyWindow(hwnd);
        return 0;
    }

    D3DPRESENT_PARAMETERS pp = {};
    pp.Windowed = TRUE;
    pp.SwapEffect = D3DSWAPEFFECT_COPY;

    // 创建设备
    IDirect3DDevice9* device;
    if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &pp, &device)))
    {
        d3d9->Release();
        DestroyWindow(hwnd);
        return 0;
    }

    // 开始劫持 EndScene
    // EndScene是IDirect3DDevice9第43个函数
    endSceneAddr = (*(void***)device)[42];

    // 开始挂钩
    hook(endSceneAddr, MyEndScene, endSceneOldCode);

    // 释放
    d3d9->Release();
    device->Release();
    DestroyWindow(hwnd);
    return 0;
}

有了上述代码基础,接着读者只需要增加一个DLL头,在入口处通过DuplicateHandle得到当前线程的线程ID,并调用CreateThread创建新线程,此时劫持也就正式生效了。

// dll入口
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        HANDLE curThread;

        // 获取当前线程ID
        if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &curThread, SYNCHRONIZE, FALSE, 0))
        {
            return FALSE;
        }

        // DllMain中不能使用COM组件 所以要在另一个线程初始化
        CloseHandle(CreateThread(NULL, 0, initHookThread, curThread, 0, NULL));
        break;

    case DLL_PROCESS_DETACH:
        if (endSceneAddr != NULL)
        {
            unhook(endSceneAddr, endSceneOldCode);
        }
        break;
    }
    return TRUE;
}

至此,读者可使用任意一款注入软件将编译好的hook.dll文件注入到目标进程内,此时会发现窗体上新增加了一行文字和两个方框,至此绘制实现;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/ca456002.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1132200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Win10系统备份的方法和步骤

在Win10电脑中,用户为了避免丢失重要的数据而无法找回,提前备份这些重要数据是特别有必要的。但是,许多新手用户不知道Win10系统备份的详细操作方法,接下来小编给大家介绍关于备份Win10系统的简单方法与步骤。 Win10系统备份的方法…

d3中svg标签里插入图片不显示的解决方案

1. 使用背景 在d3中的svg元素中插入图片&#xff0c;图片未正常显示。 2. 使用image标签 首先&#xff0c;在svg中加载图片用的image标签&#xff0c;而不是img标签。支持的图片格式可以是 JPEG、PNG、SVG.。 该元素<img> 一样&#xff0c;会解析渲染为图片。 <im…

vue2进阶学习知识汇总

目录 1.组件之处理边界情况 1.1 子组件访问根组件数据 1.2 子组件访问父组件数据 1.3 父组件访问子组件 1.4 依赖注入 1.5 程序化的事件侦听器 1.6 递归组件 1.7 内联模板 1.8 X-Template 1.9 强制更新 1.10 v-once 2.过渡效果与状态 2.1 过渡效果 2.1.1 单元素/…

VMware Workstation Pro下载安装

VMware Workstation Pro下载安装 下载 VMware Workstation Pro 1. 进入官网下载页面 VMware Workstation Pro下载页面&#xff1a;https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evaluation.html 2. 选择对应操作系统平台下载 VMware Workstation Pr…

2023年清洁电器行业数据分析:洗地机市场规模持续倍增,进入赛点

洗地机作为清洁电器领域的明星品类&#xff0c;正在成为继扫地机器人之后拉动清洁电器市场大盘的又一核心动力。 在清洁电器领域&#xff0c;扫地机器人、洗地机和吸尘器是三大热门品类。截至今年9月份&#xff0c;根据鲸参谋平台的数据显示&#xff0c;吸尘器的规模继续大幅下…

基于Android 10系统的瑞芯微RK3399K烧写镜像实录

基于Android 10系统的瑞芯微RK3399K烧写镜像实录 1. 前言2. 官网及相关资料3. 烧写固件所需软件4. 直接烧写单一固件步骤5. 固件文件6. Windows下烧写准备6.1 安装 RK USB 驱动6.2 连接设备 7. 烧写固件7.1 烧写统一固件 update.img7.2 烧写分区映像 8. Linux下烧写8.1 upgrade…

Vue组件自定义事件父子

Vue组件父子之间自定义事件 父组件使用props传递数据给子组件&#xff0c;子组件怎么跟父组件通信呢&#xff1f;这时&#xff0c;Vue的自定义事件就派上用场了 示例代码 <div id"zjw1"><span>CQNU--重庆ZJW--同学</span><input placeholder…

数据安全小课堂开讲啦!看这里!

数据安全小课堂开讲啦&#xff01;看这里&#xff01; 1、什么是数据&#xff1f; 《数据安全法》第三条明确&#xff0c;本法所称的数据&#xff0c;就是指任何以电子或者其他方式对信息的记录。小到个人使用手机、电脑等电子产品时浏览的网页、下载的应用、存储的文件&…

【等保小课堂】等保测评后还要花很多钱做等保整改吗?

我国正在严格执行等保政策&#xff0c;过等保企业也越来越多。但大家对于等保政策还不是很了解&#xff0c;有小伙伴在问&#xff0c;等保测评后还要花很多钱做等保整改吗&#xff1f; 等保测评后还要花很多钱做等保整改吗&#xff1f; 【回答】&#xff1a;不一定。等保测评后…

Web前端接入Microsoft Azure AI文本翻译

Azure 文本翻译是 Azure AI 翻译服务的一项基于云的 REST API 功能。 文本翻译 API 支持实时快速准确地进行源到目标文本翻译。 文本翻译软件开发工具包 (SDK) 是一组库和工具&#xff0c;可用于轻松地将文本翻译 REST API 功能集成到应用程序中。 文本翻译 SDK 可跨 C#/.NET、…

一篇文章教会你搭建Hive分布式集群

目录 ​编辑 一、环境描述 二、安装mysql 2.1 卸载mysql 2.1.1 列出安装的mysql 2.1.2 卸载mysql 2.1.3 删除mysql文件目录 2.1.3.1 查看mysql 目录 2.1.3.2 依次删除 2.2.1 下载安装源 2.2.2 安装源rpm 2.2.3 加入rpm密钥 2.2.4 执行安装 2.2.5 设置开机自启动 …

STM32 定时器配置不当导致误差(精度)偏大的问题发现与解决

通用定时器TIM2/3/4/5&#xff0c;PWM输出1Khz的波形 一开始初始化代码如下&#xff1a; void MX_TIM2_Init(void)//1kHz {TIM_ClockConfigTypeDef sClockSourceConfig {0};TIM_MasterConfigTypeDef sMasterConfig {0};TIM_OC_InitTypeDef sConfigOC {0};htim2.Instance T…

Git简明教程

1.Git的定位 在我们自己开发项目的过程中&#xff0c;经常会遇到这样的情况&#xff0c;为了防止代码丢失&#xff0c;或者新变更的代码影响到原有的代码功能&#xff0c;为了在失误后能恢复到原来的版本&#xff0c;不得不复制出一个副本,比如&#xff1a;“坦克大战1.0”“坦…

十个最常用的计算机视觉数据集

如今&#xff0c;人工智能和机器学习领域中最振奋人心的一个分支是计算机视觉&#xff08;Computer Vision&#xff0c;简称CV&#xff09;。CV应用于多种场景&#xff0c;以改善我们的日常生活&#xff0c;并推进科学技术研究。其中包括&#xff1a; 自动驾驶自动生成图像描述…

一、W5100S/W5500+RP2040树莓派Pico<静态配置网络信息>

文章目录 1. 前言2. 相关网络信息2.1 简介2.2 优点2.3 应用 3. WIZnet以太网芯片4. 静态IP网络设置示例讲解以及使用4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 编译烧录 5. 注意事项6. 相关链接 1. 前言 从本章开始我们将用WIZnet的W5100S/W5500以太网芯片结合RP…

听GPT 讲Rust源代码--library/std(3)

rust标准库std中的src目录主要包含以下内容和模块: alloc:内存分配相关函数,比如alloc::boxed::Box、alloc::string::String等。 ascii:ASCII相关工具函数。 char:字符相关类型和函数,如Char、char等。 cmp:比较相关trait和函数,如Ord、Eq、PartialOrd等。 env:环境变量相关功能…

机架式服务器介绍

大家都知道服务器分为机架式服务器、刀片式服务器、塔式服务器三类&#xff0c;今天小编就分别讲一讲这三种服务器&#xff0c;第一篇先来讲一讲机架式服务器的介绍。 机架式服务器定义&#xff1a;机架式服务器是安装在标准机柜中的服务器&#xff0c;一般采用19英寸的标准尺寸…

5年测开经验,领导却说:写的测试文档还不如应届生

如果有人问“测试人员最重要的能力是什么” &#xff0c;有人会说“自动化技术”&#xff0c;有人会说“代码能力”&#xff0c;还有人会说“沟通能力”。 以上的各种能力确实挺重要的&#xff0c;但是在我看来&#xff0c;有一种能力长期遭到人们的低估&#xff0c;那就是“文…

TypeScript学习笔记 | 泛型 - 泛型的应用 - 泛型工具类 - extends - infer用法

文章目录 TypeScript学习笔记 | 泛型 - 泛型的应用什么是泛型类型兼容鸭子类型类型运算「&」和「&#xff5c;」 泛型的定义泛型的使用泛型接口与泛型类 extends用法泛型用法条件判断 用于类型的条件判断应用&#xff1a;Exclude<T,U>排除 / Extract 提取 infer关键词…

【23真题】这套适合考211的同学练手!考察复杂梅森!

今天分享的是23年五邑大学801的信号与系统试题及解析。 本套试卷难度分析&#xff1a;本套试题难度中等偏下&#xff0c;考察的题量较少&#xff0c;但是涉及到的知识点是非常全面的&#xff0c;考察的知识大多都是三大性质的定义以及运用&#xff0c;还考察了复杂梅森公式的运…