13.4 DirectX内部劫持绘制

相对于外部绘图技术的不稳定性,内部绘制则显得更加流程与稳定,在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;}// 初始化D3DIDirect3D9* 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;// 获取当前线程IDif (!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.mzph.cn/news/118455.shtml

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

相关文章

was下log4j设置日志不输出问题

was下log4j设置日志不输出问题 WAS 也是用的 commons-logging 日志框架 commons-logging 确定 LogFactory 实现的顺序是 从应用的 META-INF/services/org.apache.commons.logging.LogFactory 中获得 LogFactory 实现从系统环境中获得 org.apache.commons.logging.LogFactory…

clip-path图片裁剪

CSS clip-path 属性 属性定义及使用说明 clip-path 属性使用裁剪方式创建元素的可显示区域。区域内的部分显示,区域外的隐藏。可以指定一些特定形状。 CSS3 剪贴路径(Clip-path)在线生成器 | 踏得网 语法:clip: clip-source|basi…

《 汇编语言的系统学习》一、编程语言、机器语言与汇编语言

目录 《 汇编语言的系统学习》1、编程语言1.1 语言1.2 程序 2 编程语言分类2.1 机器语言2.2 汇编语言2.3 高级程序语言2.3.1 编译型2.3.1 解释型 《 汇编语言的系统学习》 1、编程语言 1.1 语言 定义:一种系统的,人与人之间通过声音、符号等进行交流的…

2023/10/25

如果你越来越冷漠 你以为你成长了 但其实没有 长大应该是变得温柔 对全世界都温柔

CUDA学习笔记(十五)Stream and Event

Stream 一般来说,cuda c并行性表现在下面两个层面上: Kernel levelGrid level 到目前为止,我们讨论的一直是kernel level的,也就是一个kernel或者一个task由许多thread并行的执行在GPU上。Stream的概念是相对于后者来说的&…

JavaScript对象与原型

目录 对象的创建 原型与原型链 原型继承 总结 在JavaScript中,对象是非常重要的概念之一。它们允许我们以一种结构化的方式存储和组织数据,并提供了一种方便的方式来操作和访问这些数据。而对象的行为和属性则通过原型来定义。 对象的创建 在JavaS…

华为OD 走方格的方案数(100分)【java】A卷+B卷

华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应往年部分考题以及新出的题目。 我将持续更新最新题目 我精选了一部分题目免费分享给大家,可前往夸克网盘转存,请点击以下链接进入: 我用夸克网盘分享了「华为OD题库J…

Linux环境下Redis 集群部署

Linux环境下Redis 集群部署 1.单机Redis部署2.Redis 集群配置2.1 创建redis集群安装目录2.2 将redis单机部署目录下的redis.confi文件复制到每个目录下2.3 修改每个文件夹下的redis.conf2.4 修改完六个配置内容后开始启动2.5 启动完后查看进程2.6 建集群 1.单机Redis部署 Linu…

解决:vscode和jupyter远程连接无法创建、删除文件的问题(permission denied)

目录 问题:vscode和jupyter远程连接服务器无法创建、删除文件的问题原因:代码文件的权限不够解决方法:1.ls -l查看目录所在组,权限2.chown修改拥有者和所在组 问题:vscode和jupyter远程连接服务器无法创建、删除文件的…

linux 系统查询redis中指定key值(命令)

文章目录 Linux 系统中查询redis指定key值进入到redis目录下授权当前用户查询指定key值 Linux 系统中查询redis指定key值 进入到redis目录下 登上服务器之后直接输入 redis-cli 进入到127.0.0.1:6379>下 授权当前用户 在127.0.0.1:6379>下,输入如下指令 auth…

【计算机网络】文件传输协议FTP和SFTP

1. 介绍 SFTP(SSH文件传输协议)和FTP(文件传输协议)都是用于在计算机之间传输文件的网络协议。FTP和SFTP都位于OSI模型中的应用层。这两种协议用于文件传输和管理,是应用层协议,因此它们工作在OSI模型的最…

vueDay04——v-if else show

一、v-if的使用 我们可以像c语言一样去使用v-if结构 比如单用v-if&#xff0c;连用v-if v-else&#xff0c;或者是v-if v-else-if v-else 注意&#xff1a; 1.v-if v-else-if需要绑定值,而v-else不需要绑定值 2.if结构可以用在不同的标签类型之间 <div v-if"fir…

[Unity]给场景中的3D字体TextMesh增加描边方案一

取你的文本对象&#xff0c;简单地添加以下脚本: using UnityEngine; using System.Collections; using UnityEngine.UI;public class TextOutline : MonoBehaviour {public float pixelSize 1;public Color outlineColor Color.black;public bool resolutionDependant fal…

整理指定文件夹下的所有文件,以类树状图显示并生成对应超链接

最近在整理家里学习资料的时候&#xff0c;由于年代久远&#xff0c;找不到我想要找的文件&#xff0c;windows文件搜索速度感觉太慢。于是想要生成一份类似文件索引的东西来显示所有资料&#xff0c;让我可以快速的找到需要的资料路径 直接上代码 import os import datetim…

【LeetCode】每日一题最后一个单词的长度投票法求解多数元素异或操作符巧解只出现一次的数字整数反转

个人主页直达&#xff1a;小白不是程序媛 LeetCode系列专栏&#xff1a;LeetCode刷题掉发记 目录 LeetCode 58.最后一个单词的长度 LeetCode169.多数元素 LeetCode 136.出现一次的数字 LeetCode 7.整数反转 LeetCode 58.最后一个单词的长度 难度&#xff1a;简单 OJ链接…

软件兼容性测试对软件产品起到什么作用?CMA、CNAS软件测评中心分享

软件兼容性测试是指检查软件之间能否正确地进行交互和共享信息。随着用户对来自各种类型软件之间共享数据能力和充分利用空间同时执行多个程序能力的要求&#xff0c;测试软件之间能否协作变得越来越重要。软件兼容性测试工作的目标是保证软件按照用户期望的方式进行交互。 1、…

各品牌PLC存储器寻址的规则

在PLC编程时&#xff0c;字节或多字节的变量一般支持绝对地址寻址&#xff08;比如&#xff0c;IW0、MD4等&#xff09;。要想正确寻址&#xff0c;则必须要搞清楚寻址的规则。目前常见的规则有两种&#xff1a;字节寻址和字寻址。下图清晰地表达了两种规则的编号情况&#xff…

React介绍

React介绍 学习目标&#xff1a;了解React是什么以及它的特点 React是什么 React是一个来自FaceBook的专注于构建用户界面的javascript库&#xff0c;与Vue和Angular并称为前端三大框架&#xff0c;目前是世界范围内最流行的js前端框架   React英文文档&#xff08;https:/…

Ubuntu18.04创建用户缺少默认的下载、桌面目录

Ubuntu18.04创建用户缺少默认的下载、桌面目录 创建用户缺少默认的下载、桌面目录等默认目录 创建用户缺少默认的下载、桌面目录等默认目录 在存在桌面版本的ubuntu系统中&#xff0c;在终端使用命令创建用户&#xff0c;丹是 sudo useradd -m liyao -d /home/liyao -s /bin/b…

MySQL---JDBC编程

文章目录 什么是JDBC&#xff1f;JDBC的工作原理JDBC的使用添加依赖创建数据源DataSource创建数据库连接Connection创建操作命令Statement执行SQL指令释放资源 通过JDBC演示CRUD新增查询修改删除 什么是JDBC&#xff1f; JDBC&#xff1a;Java Database Connectivity&#xff…