GameSec initially search
@source: https://learnopengl.com/Getting-started/Camera
摄像机#
opengl 中通过把场景往相反方向移动来模拟出摄像机

glm::lookAt#
$$ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$$$ \text{LookAt}= \begin{bmatrix} R_x & R_y & R_z & 0\\ U_x & U_y & U_z & 0\\ D_x & D_y & D_z & 0\\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 0 & -P_x\\ 0 & 1 & 0 & -P_y\\ 0 & 0 & 1 & -P_z\\ 0 & 0 & 0 & 1 \end{bmatrix} $$glm::mat4 view = glm::lookAt(
cameraPos, // Camera position in world space
cameraTarget, // Target point the camera is looking at
cameraUp // “Up” direction for the camera
);
lookAt 构造 view 矩阵,需要三组向量,其中有这样的关系
glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);
右向量(公式中的 R)由上向量(U)和方向向量(D)正交得到
解释了 lookAt 的三个参数
控制 cameraTarget 在 (0, 0, 0),将 cameraPos 绕上轴(y轴;OpenGL 中 z 轴正方向从屏幕指出)旋转
float radius = 10.0f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
glm::mat4 view;
view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));
cameraDirection, cameraFront 在文档里有点混淆(可能是相反的关系?),暂时不做深究
自由移动#
为 GLFW 的键盘输入定义一个 processInput,添加检查的按键,按下按键 cameraPosition 更新,向前/向后移动把位置向量加上/减去方向向量即可,左右移动则先用叉乘创建右向量然后标准化沿着移动就可以
视角移动#
文档中提到的 cameraFront 被鼠标输入改变,两种欧拉角(其实总共有三种):
- 俯仰角 (Pitch) 控制抬头低头
- 偏航角 (Yaw) 控制左右转头
根据三角函数把两者转换成新的 front
总结#
关键值
- view -> 反推玩家观察姿态
- position
- front/direction -> 玩家当前视角方向
- up/right
- yaw/pitch
为了避免将来我自己看得一头雾水,把我想到的一些记录一下:
- 基本认为 front 和 direction 相反
- 由 right = normalize(cross(up, direction)) 得,right = normalize(cross(front, up))
渲染图元#
一个物体被画出来,涉及:
- CPU 准备顶点数据,写入 VBO
- 位置 position
- 法线 normal
- 纹理坐标 textcoord
- 顶点颜色
- CPU 准备索引数据,写入 EBO
- VAO 记录顶点属性布局
- CPU 激活 shader program
- vertex shader:处理顶点,做坐标变换
- 典型计算:
gl_Position = projection * view * model * vec4(aPos, 1.0);- 模型矩阵
model,把模型局部系变换到世界系 - 观察矩阵
view,把世界系变换到相机系 - 投影矩阵
projection做投影,准备后续光栅化流程
- 模型矩阵
- 典型计算:
- fragement shader:处理像素,决定最终颜色
- vertex shader:处理顶点,做坐标变换
- CPU 绑定纹理
- CPU 计算
model/view/projection - CPU 通过 uniform 把矩阵上传到 GPU
- 变换矩阵
model / view / projection - 光照参数
- 材质参数
- 这一步可以截获到 3D 结构
- 变换矩阵
- CPU 调用
glDrawElements - GPU 按当前
VAO/EBO/program/texture/uniform执行绘制
不难想到 glDrawElements 是绝佳的 hook 点,走到调用点时所有关键状态都已经设置好了
这个接口从数组中获取数据渲染图元。
游戏安全侧#
- 客户端游戏的图形层数据并不安全,坐标、血量、命中框、视角、骨骼矩阵,只要进入本地进程理论上都可以被攻击
- 渲染 API 是典型的攻击面,因为它处在『业务逻辑』和『最终显示』之间
- 一种方案是渲染层数据提取
- 不修改游戏逻辑
- 不涉及网络协议
- 只观察渲染调用现场
2021 腾讯游戏安全技术竞赛初赛 PC 端#
int __cdecl sub_4064D0()
{
glfwInit_wrapper();
glfwWindowHint_wrapper(139266, 3);
glfwWindowHint_wrapper(139267, 3);
glfwWindowHint_wrapper(139272, 204801);
Window_wrapper = glfwCreateWindow_wrapper(800, 600, "XDDDDDDDDD", 0, 0);
v1 = Window_wrapper;
if ( !Window_wrapper )
{
glfwTerminate_wrapper();
return -1;
}
glfwMakeContextCurrent_wrapper(Window_wrapper);
glfwSetFramebufferSizeCallback_wrapper(v1, framebuffer_size_callback);
glfwSetCursorPosCallback_wrapper(v1, mouse_cursor_callback);
glfwSetInputMode_wrapper(v1, 208897, 212995);
if ( !load_opengl_functions() )
return -1;
glEnable_ptr(2929);
build_shader_program(v31, v32);
v59[0] = xmmword_455340;
v59[1] = xmmword_456AA0;
v59[2] = xmmword_455470;
v59[3] = xmmword_4554D0;
v59[4] = xmmword_455560;
v59[5] = xmmword_455320;
v59[6] = xmmword_456AE0;
v59[7] = xmmword_456A90;
v59[8] = xmmword_455450;
v59[9] = xmmword_455300;
v59[10] = xmmword_455540;
v59[11] = xmmword_4554A0;
v59[12] = xmmword_455500;
v59[13] = xmmword_456A70;
v59[14] = xmmword_4552A0;
v59[15] = xmmword_455550;
v59[16] = xmmword_456A60;
v59[17] = xmmword_456AC0;
v59[18] = xmmword_456A80;
v59[19] = xmmword_455530;
v59[20] = xmmword_4552D0;
v59[21] = xmmword_4554B0;
v59[22] = xmmword_455470;
v59[23] = xmmword_455460;
v59[24] = xmmword_455560;
v59[25] = xmmword_455330;
v59[26] = xmmword_456AB0;
v59[27] = xmmword_456A40;
v59[28] = xmmword_455450;
v59[29] = xmmword_4552F0;
v59[30] = xmmword_455340;
v59[31] = xmmword_456AB0;
v59[32] = xmmword_456A50;
v59[33] = xmmword_455460;
v59[34] = xmmword_455300;
v59[35] = xmmword_4552D0;
v59[36] = xmmword_456AD0;
v59[37] = xmmword_4554F0;
v59[38] = xmmword_4554C0;
v59[39] = xmmword_455560;
v59[40] = xmmword_455540;
v59[41] = xmmword_455480;
v59[42] = xmmword_4554E0;
v59[43] = xmmword_456A30;
v59[44] = xmmword_455520;
glGenVertexArrays_ptr(1, &v58);
glGenBuffers_ptr(1, &v54);
glBindVertexArray_ptr(v58);
glBindBuffer_ptr(34962, v54);
glBufferData_ptr(34962, 720, v59, 35044);
glVertexAttribPointer_ptr(0, 3, 5126, 0, 20, 0);
glEnableVertexAttribArray_ptr(0);
glVertexAttribPointer_ptr(1, 2, 5126, 0, 20, 12);
glEnableVertexAttribArray_ptr(1);
glGenTextures_ptr(1, &v56);
glBindTexture_ptr(3553, v56);
glTexParameteri_ptr(3553, 10242, 10497);
glTexParameteri_ptr(3553, 10243, 10497);
glTexParameteri_ptr(3553, 10241, 9729);
glTexParameteri_ptr(3553, 10240, 9729);
dword_465FC4 = 1;
v3 = (void *)decode_image_from_memory(&v40, &v39, &v57);
v4 = v3;
if ( v3 )
{
glTexImage2D_ptr(3553, 0, 6407, LODWORD(v40), v39, 0, 6407, 5121, v3);
glGenerateMipmap_ptr(3553);
}
j___free_base(v4);
glGenTextures_ptr(1, &v55);
glBindTexture_ptr(3553, v55);
glTexParameteri_ptr(3553, 10242, 10497);
glTexParameteri_ptr(3553, 10243, 10497);
glTexParameteri_ptr(3553, 10241, 9729);
glTexParameteri_ptr(3553, 10240, 9729);
v5 = (void *)decode_image_from_memory(&v40, &v39, &v57);
v6 = v5;
if ( v5 )
{
glTexImage2D_ptr(3553, 0, 6407, LODWORD(v40), v39, 0, 6408, 5121, v5);
glGenerateMipmap_ptr(3553);
}
j___free_base(v6);
v7 = v38;
glUseProgram_ptr(v38);
v43 = 15;
v42 = 0;
LOBYTE(Block[0]) = 0;
std_string_assign_n("texture1", 8u);
v60 = 0;
v8 = Block;
if ( v43 >= 0x10 )
v8 = (void **)Block[0];
UniformLocation_ptr = glGetUniformLocation_ptr(v38, v8, 0);
glUniform1i_ptr(UniformLocation_ptr);
v60 = -1;
std_string_destroy(Block);
v43 = 15;
v42 = 0;
LOBYTE(Block[0]) = 0;
std_string_assign_n("texture2", 8u);
v60 = 1;
v10 = Block;
if ( v43 >= 0x10 )
v10 = (void **)Block[0];
v11 = glGetUniformLocation_ptr(v38, v10, 1);
glUniform1i_ptr(v11);
v60 = -1;
std_string_destroy(Block);
if ( !glfwWindowShouldClose_wrapper(v1) )
{
v12 = *(double *)_libm_sse2_tan_precise().m128_u64;
v38 = 0;
v40 = 1.0 / v12;
v57 = 1.0 / (float)(v12 * 1.3333334);
do
{
flt_465FBC = glfwGetTime_wrapper();
if ( glfwGetKey_wrapper(v1, 256) == 1 )
glfwSetWindowShouldClose_wrapper(v1, 1);
glClearColor_ptr(1045220557, 1050253722, 1050253722, 1065353216);
glClear_ptr(16640);
glActiveTexture_ptr(33984);
glBindTexture_ptr(3553, v56);
glActiveTexture_ptr(33985);
glBindTexture_ptr(3553, v55);
glUseProgram_ptr(v7);
mat4_identity(&v38);
v44 = v57;
v45 = v40;
v47 = -1082130432;
v46 = -1082113638;
v48 = -1102249656;
v43 = 15;
v42 = 0;
LOBYTE(Block[0]) = 0;
std_string_assign_n("projection", 0xAu);
v60 = 2;
v13 = Block;
if ( v43 >= 0x10 )
v13 = (void **)Block[0];
v14 = glGetUniformLocation_ptr(v7, v13, 1);
glUniformMatrix4fv_ptr(v14);
v60 = -1;
if ( v43 >= 0x10 )
{
v15 = Block[0];
if ( v43 + 1 >= 0x1000 )
{
if ( ((int)Block[0] & 0x1F) != 0
|| (v16 = (void *)*((_DWORD *)Block[0] - 1), v16 >= Block[0])
|| (unsigned int)((char *)Block[0] - (char *)v16) < 4
|| (unsigned int)((char *)Block[0] - (char *)v16) > 0x23 )
{
LABEL_52:
_invalid_parameter_noinfo_noreturn();
}
v15 = (void *)*((_DWORD *)Block[0] - 1);
}
j_j___free_base(v15);
}
v34[0] = *(float *)&dword_464CC4 + *(float *)&qword_464CAC;
v34[1] = *(float *)&dword_464CC8 + *((float *)&qword_464CAC + 1);
v34[2] = *(float *)&dword_464CCC + *(float *)&dword_464CB4;
build_view_matrix(v34);
v43 = 15;
v42 = 0;
LOBYTE(Block[0]) = 0;
std_string_assign_n("view", 4u);
v60 = 3;
v17 = Block;
if ( v43 >= 0x10 )
v17 = (void **)Block[0];
v18 = glGetUniformLocation_ptr(v7, v17, 1);
glUniformMatrix4fv_ptr(v18);
v60 = -1;
if ( v43 >= 0x10 )
{
v19 = Block[0];
if ( v43 + 1 >= 0x1000 )
{
if ( ((int)Block[0] & 0x1F) != 0 )
goto LABEL_52;
v20 = (void *)*((_DWORD *)Block[0] - 1);
if ( v20 >= Block[0]
|| (unsigned int)((char *)Block[0] - (char *)v20) < 4
|| (unsigned int)((char *)Block[0] - (char *)v20) > 0x23 )
{
goto LABEL_52;
}
v19 = (void *)*((_DWORD *)Block[0] - 1);
}
j_j___free_base(v19);
}
v43 = 15;
v42 = 0;
LOBYTE(Block[0]) = 0;
glBindVertexArray_ptr(v58);
v21 = 0;
v22 = dword_46601C;
if ( (dword_466020 - (int)dword_46601C) >> 2 )
{
v33[0] = 1065353216;
v33[1] = 1050253722;
v33[2] = 1056964608;
do
{
v23 = v22[v21 + 1];
v24 = v22[v21 + 2];
v53[0] = v22[v21];
v53[1] = v23;
v53[2] = v24;
mat4_zero_init(&v49);
v25 = (__int128 *)build_model_translation_matrix(v53);
v49 = *v25;
v50 = v25[1];
v51 = v25[2];
v52 = v25[3];
v26 = (__int128 *)build_model_scale_matrix(v33);
v37 = 15;
v36 = 0;
LOBYTE(v35) = 0;
v49 = *v26;
v50 = v26[1];
v51 = v26[2];
v52 = v26[3];
std_string_assign_n("model", 5u);
v60 = 4;
v27 = &v35;
if ( v37 >= 0x10 )
v27 = v35;
v28 = glGetUniformLocation_ptr(v7, v27, 1);
glUniformMatrix4fv_ptr(v28);
v60 = -1;
if ( v37 >= 0x10 )
{
v29 = v35;
if ( v37 + 1 >= 0x1000 )
{
if ( ((unsigned __int8)v35 & 0x1F) != 0 )
goto LABEL_52;
v30 = *((_DWORD *)v35 - 1);
if ( v30 >= (unsigned int)v35 || (char *)v35 - v30 < (char *)4 || (char *)v35 - v30 > (char *)0x23 )
goto LABEL_52;
v29 = (void *)*((_DWORD *)v35 - 1);
}
j_j___free_base(v29);
}
v37 = 15;
v36 = 0;
LOBYTE(v35) = 0;
glDrawArrays_ptr(4u, 0, 0x24u, 0);
v21 += 3;
v22 = dword_46601C;
}
while ( v21 < (dword_466020 - (int)dword_46601C) >> 2 );
}
glfwSwapBuffers_wrapper(v1);
glfwPollEvents_wrapper();
}
while ( !glfwWindowShouldClose_wrapper(v1) );
}
glDeleteVertexArrays_ptr(1, &v58);
glDeleteBuffers_ptr(1, &v54);
glfwTerminate_wrapper();
return 0;
}
渲染逻辑:
加载 shader
创建 VBO
加载两个纹理
设置 uniform sampler
texture1 -> 0
texture2 -> 1
每帧循环:
设置 projection 矩阵
设置 view 矩阵
for each object
设置 model 矩阵
glDrawElements(GL_TRIANGLES, 36)
下面注入的 dll 劫持了摄像机,用键盘输入改 cameraPosition,并实现
- W/S:沿 cameraFront 前后移动
- A/D:沿右向量 cross(cameraFront, cameraUp) 左右平移
- R/T:通过修改 fov,传入 projection 实现视野缩放
实现自由视角解锁
没有 hook glDrawElements,而是 hook glUniformMatrix4fv 的参数准备点
// @source: https://www.xuenixiang.com/thread-3160-1-1.html
// DllMain
#include "stdafx.h"
// 复用原程序中的 glfwGetKey_wrapper
#define RVA_glfwGetKey 0x19920
// 四个点,贴了附近的代码
// .text:0040653D call glfwCreateWindow_wrapper
// .text:00406542 mov ebx, eax
#define RVA_Get_p_windows_HookPoint 0x06542
// .text:00406BD8 sub esp, 10h
// .text:00406BDB mov [esp+474h+var_468], 3F800000h
// .text:00406BE3 mov [esp+474h+var_46C], 3E99999Ah
// .text:00406BEB mov [esp+474h+var_470], 3E99999Ah
// .text:00406BF3 mov [esp+474h+var_474], 3E4CCCCDh
// .text:00406BFA call glClearColor_ptr
// do
// {
// if ( glfwGetKey_wrapper(v1, 256) == 1 )
// glfwSetWindowShouldClose_wrapper(v1, 1);
// glClearColor_ptr(1045220557, 1050253722, 1050253722, 1065353216);
// ...
// }
#define RVA_Key_HookPoint 0x06BDB // 这里是接近渲染主循环每帧开头的地方,放消息钩子很合适
// std_string_assign_n("projection", 0xAu);
// v60 = 2;
// v13 = Block; // 覆盖这里
// if ( v43 >= 0x10 )
// v13 = (void **)Block[0];
// v14 = glGetUniformLocation_ptr(v7, v13, 1);
// glUniformMatrix4fv_ptr(v14);
#define RVA_Setprojection_HookPoint 0x06CDB
// 跟上面的差不多
#define RVA_SetView_HookPoint 0x06DEE
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
float fov = 50.0f;
typedef DWORD(__cdecl* pfnglfwGetKey)(DWORD p_windows, DWORD KeyValue);
pfnglfwGetKey m_glfwGetKey;
DWORD p_windows;
HMODULE hModule_EXE;
PVOID p_windows_HookPoint = NULL;
PVOID p_GetKey_HookPoint = NULL;
PVOID p_Setprojection = NULL;
PVOID p_Setview = NULL;
glm::mat4 projection;
glm::mat4 view;
// 拿到窗口句柄:目标不是找某个静态值而是主动复用原程序的输入系统,拿到窗口句柄是为了后面能够调用 glfwGetKey
VOID NAKED Get_p_windows()
{
STACK_FRAME_BEGIN
__asm {
mov p_windows, eax
}
STACK_FRAME_END
__asm {
push p_windows_HookPoint
ret
}
}
// 把渲染循环 hook 下来后插入对键盘的处理
VOID NAKED prcessInPut()
{
STACK_FRAME_BEGIN
if (m_glfwGetKey(p_windows, GLFW_KEY_W) == GLFW_PRESS)
{
cameraPos += cameraFront;
}
if (m_glfwGetKey(p_windows, GLFW_KEY_S) == GLFW_PRESS)
{
cameraPos -= cameraFront;
}
if (m_glfwGetKey(p_windows, GLFW_KEY_A) == GLFW_PRESS)
{
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp));
}
if (m_glfwGetKey(p_windows, GLFW_KEY_D) == GLFW_PRESS)
{
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp));
}
// R、V 拉视野
if (m_glfwGetKey(p_windows, GLFW_KEY_R) == GLFW_PRESS)
{
fov -= 1.0;
}
if (m_glfwGetKey(p_windows, GLFW_KEY_T) == GLFW_PRESS)
{
fov += 1.0f;
}
// 用改过的 fov 计算 projection,实现视野拉近/拉远
view = glm::lookAt(cameraPos, cameraFront + cameraPos, cameraUp);
projection = glm::perspective(glm::radians(fov), (float)800.0 / (float)600.0, 0.1f, 200.0f);
STACK_FRAME_END
__asm {
push p_GetKey_HookPoint
ret
}
}
// hook uniform 的传参,修改 projection
VOID NAKED Setprojection()
{
__asm {
mov ecx, offset projection
push p_Setprojection
ret
}
}
// 同上
VOID NAKED Setview()
{
__asm
{
mov ecx, offset view
push p_Setview
ret
}
}
BOOL DetourHook()
{
if (DetourTransactionBegin() == NO_ERROR)
{
DetourAttach(&p_windows_HookPoint, Get_p_windows);
DetourAttach(&p_GetKey_HookPoint, prcessInPut);
DetourAttach(&p_Setprojection, Setprojection);
DetourAttach(&p_Setview, Setview);
if (DetourTransactionCommit() == NO_ERROR)
{
return TRUE;
}
}
return FALSE;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hModule_EXE = GetModuleHandle(NULL);
m_glfwGetKey = (pfnglfwGetKey)((DWORD)hModule_EXE + RVA_glfwGetKey);
p_windows_HookPoint = (PVOID)((DWORD)hModule_EXE + RVA_Get_p_windows_HookPoint);
p_GetKey_HookPoint = (PVOID)((DWORD)hModule_EXE + RVA_Key_HookPoint);
p_Setprojection = (PVOID)((DWORD)hModule_EXE + RVA_Setprojection_HookPoint);
p_Setview = (PVOID)((DWORD)hModule_EXE + RVA_SetView_HookPoint);
DetourHook();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
小补充#
glBindTexture(GL_TEXTURE_2D, texture1)绑定第一张纹理。 纹理 ID、program ID 也常被外挂拿来辅助区分对象类别。glDrawArrays(GL_TRIANGLES, 0, 36)实际发出绘制命令。 攻击思路也相通,都是在 draw call 边界读上下文。
Read other posts