13.4 DirectX内部劫持绘制
相对于外部绘图技术的不稳定性,内部绘制则显得更加流程与稳定,在Dx9环境中,函数EndScene是在绘制3D场景后,用于完成将最终的图像渲染到屏幕的一系列操作的函数。它会将缓冲区中的图像清空,设置视口和其他渲染状态,执行顶点和像素着色器,最后在后台缓冲区中生成一张完整的渲染图像,然后将其呈现到屏幕上,完成一次绘制操作。
而EndScene是IDirect3DDevice9第43个函数,我们通过对该函数进行挂钩,并将该函数绘制之前的流程劫持到自身进程内的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文件注入到目标进程内,此时会发现窗体上新增加了一行文字和两个方框,至此绘制实现;

13.4 DirectX内部劫持绘制的更多相关文章
- libgdx学习记录13——矩形CD进度条绘制
利用ShapeRenderer可进行矩形进度条的绘制,多变形的填充等操作. 这是根据角度获取矩形坐标的函数. public Vector2 GetPoint( float x, float y, fl ...
- 【转】使用Python matplotlib绘制股票走势图
转载出处 一.前言 matplotlib[1]是著名的python绘图库,它提供了一整套绘图API,十分适合交互式绘图.本人在工作过程中涉及到股票数据的处理如绘制K线等,因此将matplotlib的使 ...
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/16330267 在上一篇文章中,我带着大家一起剖析了一下LayoutInflater ...
- Direct2D开发:绘制网格
转载请注明出处:http://www.cnblogs.com/Ray1024 一.引言 最近在使用Direct2D进行绘制工作中,需要实现使用Direct2D绘制网格的功能.在网上查了很多资料,终于实 ...
- View (三) 视图绘制流程完全解析
相 信每个Android程序员都知道,我们每天的开发工作当中都在不停地跟View打交道,Android中的任何一个布局.任何一个控件其实都是直接或间 接继承自View的,如TextView.Butto ...
- Canvas入门(3):图像处理和绘制文字
来源:http://www.ido321.com/997.html 一.图像处理(非特别说明,所有结果均来自最新版Google) 在HTML 5中,不仅可以使用Canvas API绘制图形,也可以用于 ...
- 利用JFreeChart绘制股票K线图完整解决方案
http://blog.sina.com.cn/s/blog_4ad042e50100q7d9.html 利用JFreeChart绘制股票K线图完整解决方案 (2011-04-30 13:27:17) ...
- HTML5—canvas绘制图形(1)
1.canvas基础知识 canvas元素是HTML5中新增的一个重要的元素,专门用来绘制图形,不过canvas本身不具备画图的能力,在页面中放置了canvas元素,就相当于在页面中放置了一块矩形的“ ...
- [Direct2D开发] 绘制网格
转载请注明出处:http://www.cnblogs.com/Ray1024 一.引言 最近在使用Direct2D进行绘制工作中,需要实现使用Direct2D绘制网格的功能.在网上查了很多资料,终于实 ...
- Java Graphics 2D绘制图片 在Liunx上乱码
绘图的代码工具类 package com.gwzx.framework.captcha; import java.awt.Color; import java.awt.Font; import jav ...
随机推荐
- 【flask】flask请求上下文分析 threading.local对象 偏函数 flask1.1.4生命执行流程 wtforms
目录 上节回顾 今日内容 1 请求上下文分析(源码:request原理) 1.1 导出项目的依赖 1.2 函数和方法 1.3 threading.local对象 1.4 偏函数 1.5 flask 整 ...
- forms组件渲染标签 form表单展示信息 forms组件校验方式 form组件源码 modelform组件 django自定义中间件
目录 forms组件渲染标签 方式一:全自动渲染表单 as_p as_ul as_table 表单类的label标签 方式二:手动渲染 方式三:for循环表单对象(推荐) 查看源码 渲染标签的注意事项 ...
- Redis 缓存性能实践及总结
一.前言 在互联网应用中,缓存成为高并发架构的关键组件.这篇博客主要介绍缓存使用的典型场景.实操案例分析.Redis使用规范及常规 Redis 监控. 二.常见缓存对比 常见的缓存方案,有本地缓存,包 ...
- 使用acme.sh、acme-dns自动申请ssl证书
使用acme.acme-dns实现自动申请ssl证书并实现自动替换 有些dns没有dnsapi,所以用这种方式申请只需要添加一条dns解析即可完成 以下为linux系统操作 安装acme.sh 官方源 ...
- 拥抱开放,Serverless 时代的下一征程
Serverless 作为云计算的最佳实践和未来演进趋势,其全托管免运维的使用体验和按量付费的成本优势使得它在云原生时代备受推崇.Serverless 的使用场景也由事件驱动,数据处理等部分特定场景转 ...
- I/O多路复用与socket
前言 简单来讲I/O多路复用就是用一个进程来监听多个文件描述符(fd),我们将监听的fd通过系统调用注册到内核中,如果有一个或多个fd可读或可写,内核会通知应用程序来对这些fd做读写操作,select ...
- java进阶(7)--Object类-toString()/equals()/finalize()/hashCode()
一.object类介绍 object类这个老祖宗中的方法,所有子类通用,直接或间接继承. 学习常用方法即可 列表 prtected object clone() //对象克隆 ...
- 人人都会Kubernetes(二):使用KRM实现快速部署服务,并且通过域名发布
1. 上节回顾 上一小节<人人都会Kubernetes(一):告别手写K8s yaml,运维效率提升500%>介绍了KRM的一些常用功能,并且使用KRM的DEMO环境,无需安装就可以很方便 ...
- linux 开机默认进入命令行模式
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- CDC设计实例-02
CDC设计实例 加速器 假设要处理一项业务比如图像处理,有两种方向,第一种选择一些通用的处理器CPU\GPU\DSP等通用的处理器,第二种是将算法映射成IP,直接使用IP进行处理图像处理等专门的业务就 ...