0x00 题 题目说明: 下载ctf1.exe (md5:63b2b6e27bc8e95cf81e5be8e5081265)。 运行ctf1.exe,依据程序提示,找到flag。
0x01 解 OpenGL做的三维图形显示程序
这里考的应该是解锁视角限制
打开程序只能看到一个箭头指向右上角
然后鼠标被限制了,没办法移动太多
比较相似的程序:https://blog.csdn.net/szqsdq/article/details/79584409
关于OpenGL的摄像机可以看这个:https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
对OpenGL不熟,可以先试试解除对鼠标的限制
这里会涉及到Windows消息机制:https://blog.csdn.net/liulianglin/article/details/14449577
可以先去找窗口注册的函数,那里有WndProc函数,负责处理各种消息,当然也包括鼠标消息
在IDA的structures视图中找到WNDCLASSEXW
结构体,然后x查询交叉引用,就可以找到对它进行赋值的地方
关于WNDCLASSEXW可以看这个:https://blog.csdn.net/nullccc/article/details/81188355
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct WNDCLASSEX {UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX;
我们不需要关心它被赋了什么值,我们需要知道的是他在什么函数中被赋的值
因此一直向上找,可以找到这个有窗口名字的地方
这里应该就和解题有关了
其实这个函数再向上就可以到WinMain中
WinMain中的操作并不多,根据WP,这里是从内存中加载了两幅图,而最后return的这个是OpenGL绘图,也就是需要分析处理的函数
回到这个函数,我们需要找到它对鼠标做出的限制
对鼠标做出限制一般体现在x,y两个坐标上,而且会对正和负都进行判断限制,因此目标就是找到小于窗口大小的,对正负进行判断的位置
一个个找,可以发现在sub_967120中有这么一段
看来这个就是对鼠标的限制了,试着将其patch掉
nop掉之后就可以看到右上角有啥了
非常抽象
看来除了搞定鼠标还需要其他的
现在有两个操作可以获得flag:
1.获取这些箱子的位置信息,然后重新进行绘制
2.更改摄像机位置,使摄像机能看到全貌
先来看1
1.获取这些箱子的位置信息,然后重新进行绘制 比较相似的程序:https://blog.csdn.net/szqsdq/article/details/79584409
参考程序将未知函数命名
首先是设置顶点数据
一个立方体有6个面,每个面有2个三角形,所以需要2 * 3 * 6 = 36个顶点
关于VAO和VBO:https://blog.csdn.net/qq_32974399/article/details/103956589
关于glVertexAttribPointer
:https://blog.csdn.net/chenhanxuan1999/article/details/106182908
然后是加载纹理
关于glTexParameteri
:https://blog.csdn.net/haiping1224746757/article/details/107251168
最后是设置projection、view、model三个变换矩阵并绘制界面
其中程序先设置好设置projection和view,而后使用循环设置model以及glDrawArrays
关于glGetUniformLocation
:https://blog.csdn.net/flycatdeng/article/details/82667229
关于glDrawArrays
:https://blog.csdn.net/frank06504/article/details/117523329
由于判断出了glDrawArrays
,所以可以判定这个函数所在的循环就是绘制正方体的循环
我们只需要取位置,而VBO在保存的时候保存的是顶点的所有数据,不只是位置数据,参考:http://www.funwoow.com/opengl-vao-vbo/
在初始化阶段,VBO是不知道它所存储的是什么数据,而是在渲染阶段(精确说是 glVertexAttribPointer 函数)才确定数据作用类型(顶点位置、float类型、从偏移量0处开始采集数据、2个float算一个采集步长等等)。到真正绘制(glDrawArray/glDrawElement)的时候才从VBO里读取需要的数据进入渲染流水线。
因此找位置还是得从循环中下手
先找到glDrawArrays
的位置
动调下个断点
断下来之后走几步到下面的跳转,去到循环开头
根据汇编,ecx所存的地址就是位置数据的地址了
至于大小,看看循环的判断条件就知道了
0xBF1 = 3057
由于每次渲染会取xyz三个float,也就是说循环的增量是i+=3,3057/3 = 1019
即共有1019个立方体
把这些数据dump下来(4 * BF1 = 2FC4)
然后写个脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import numpy as npimport matplotlib.pyplot as pltimport structxline = [] yline = [] zline = [] with open ('./location' , 'rb' ) as f: data = f.read() data = struct.unpack('f' * (len (data) // 4 ), data) for i in range (len (data)): xline.append(data[i]) yline.append(data[i+1 ]) zline.append(data[i+2 ]) i = i + 3 if i == len (data): break fig = plt.figure() ax1 = fig.add_subplot(111 ) ax1.set_title('ANSWER' ) plt.xlabel('X' ) plt.ylabel('Y' ) plt.xlim((-25 , 100 )) plt.ylim((-25 , 25 )) for i in range (len (xline)): ax1.scatter(xline[i], yline[i], c='r' , marker='.' ) plt.legend('x1' ) plt.show()
最后结果:
好,这是1,让我们看看2
2.更改摄像机位置,使摄像机能看到全貌 参考:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1420772
这里涉及到一个MVP矩阵:https://blog.csdn.net/weixin_51327051/article/details/120016477
要解锁自由视角,那么就要找到控制camera的地方,即涉及view矩阵的地方
核心设置view矩阵的函数在这一个部分
里面长这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 int __cdecl set_view_matrix (float *camera) { int v1; int v2; float v3; float v4; float v5; float v6; float v7; float v8; float v9; float v10; float v11; int result; float v13; float v14; float v15; float v16; float v17; float v18; float v19; float v20; float v21; int v22; v2 = v1; v19 = *camera - *(float *)&x0; v17 = camera[1 ] - *(float *)&y0; v14 = camera[2 ] - *(float *)&z0; v3 = *(double *)_libm_sse2_sqrt_precise().m128_u64; v4 = (float )(1.0 / v3) * v14; v20 = (float )(1.0 / v3) * v19; v5 = (float )(*(float *)&dword_2C4CB8 * v4) - (float )(*(float *)&dword_2C4CC0 * v20); v18 = (float )(1.0 / v3) * v17; v13 = v4; v21 = (float )(*(float *)&dword_2C4CC0 * v18) - (float )(*(float *)&dword_2C4CBC * v4); v15 = (float )(*(float *)&dword_2C4CBC * v20) - (float )(*(float *)&dword_2C4CB8 * v18); v6 = *(double *)_libm_sse2_sqrt_precise().m128_u64; v7 = (float )(1.0 / v6) * v21; v8 = v5 * (float )(1.0 / v6); v16 = v15 * (float )(1.0 / v6); v9 = (float )(v13 * v8) - (float )(v18 * v16); v22 = 1065353216 ; v10 = (float )(v20 * v16) - (float )(v13 * v7); v11 = (float )(v18 * v7) - (float )(v20 * v8); sub_269640(&v22); *(float *)(v2 + 32 ) = v16; result = v2; *(float *)v2 = v7; *(float *)(v2 + 8 ) = -v20; *(float *)(v2 + 16 ) = v8; *(_DWORD *)(v2 + 24 ) = LODWORD(v18) ^ 0x80000000 ; *(float *)(v2 + 4 ) = v9; *(float *)(v2 + 40 ) = -v13; *(float *)(v2 + 20 ) = v10; *(float *)(v2 + 36 ) = v11; *(float *)(v2 + 48 ) = -(float )((float )((float )(*(float *)&y0 * v8) + (float )(*(float *)&x0 * v7)) + (float )(*(float *)&z0 * v16)); *(float *)(v2 + 52 ) = -(float )((float )((float )(*(float *)&y0 * v10) + (float )(*(float *)&x0 * v9)) + (float )(*(float *)&z0 * v11)); *(float *)(v2 + 56 ) = (float )((float )(*(float *)&y0 * v18) + (float )(*(float *)&x0 * v20)) + (float )(*(float *)&z0 * v13); return result; }
一开始几句应该是在计算相机到目标点的向量,并单位化,然后通过叉积求上向量和右向量,类似于 lookUp(vec3f camera, vec3f object, vec3f up) 函数
目标点存储在4CC4,4CC8和4CCC中
内容为0,0,3
修改这三个值可以更改摄像机位置,从而观看到图像全貌
这里我设置成了50,20,90(注意浮点数转字节)
即42480000,41A00000,42B40000
更改之后需要让程序运行几次,让数据被读入函数中更改才能看到结果
当然除此之外还可以动动手为程序加一个摄像头移动的功能
参考:https://bbs.kanxue.com/thread-267002.htm
安装一下detours:https://www.cnblogs.com/bonelee/p/17011264.html
opengl环境的配置:https://zhuanlan.zhihu.com/p/486459964
重点在于glfw的安装
还需要安装一个glm:https://github.com/g-truc/glm/tree/0.9.8.0
glm的安装方式和路径可以参考环境配置,以便让所有项目都能读到glm
dll的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 #include <Windows.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include <glm/gtc/type_ptr.hpp> #include "detours.h" #pragma comment (lib,"detours.lib" ) #define NAKED __declspec(naked) #define STACK_FRAME_BEGIN #define STACK_FRAME_END #define RVA_glfwGetKey 0x19920 #define RVA_Get_p_windows_HookPoint 0x06542 #define RVA_Key_HookPoint 0x06BDB #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; VOID NAKED Get_p_windows () { STACK_FRAME_BEGIN __asm { mov p_windows, eax } STACK_FRAME_END __asm { push p_windows_HookPoint ret } } 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)); } 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 ; } 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 } } 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; } VOID __declspec(dllexport) test () { OutputDebugString (L"__declspec(dllexport) test() \r\n" ); } 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; }
可以使用setdll注入,参考:https://blog.csdn.net/DOwnstairs/article/details/123753532
效果如下:
此时用wasd可以操控摄像机
不过用DetourSetDll程序设置程序import注入Dll,结果发生了正方体纹理显示不正常的问题,这个是因为修改了文件头,破坏了最初的拿图片数据的结构,可以使用Detours的DetourCreateProcessWithDll函数将Dll注入到程序中实现Hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #undef UNICODE #include <cstdio> #include <windows.h> #include "detours.h" int main () { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory (&si, sizeof (STARTUPINFO)); ZeroMemory (&pi, sizeof (PROCESS_INFORMATION)); si.cb = sizeof (STARTUPINFO); char * DirPath = new char [MAX_PATH]; char * DLLPath = new char [MAX_PATH]; char * ProgramPath = new char [MAX_PATH]; GetCurrentDirectory (MAX_PATH, DirPath); sprintf_s (DLLPath, MAX_PATH, "%s\\DLL32.dll" , DirPath); sprintf_s (ProgramPath, MAX_PATH, "%s\\ctf3.exe" , DirPath); printf ("%s\n" , DirPath); printf ("%s\n" , DLLPath); printf ("%s\n" , ProgramPath); DetourCreateProcessWithDll (NULL , ProgramPath, NULL , NULL , FALSE, CREATE_DEFAULT_ERROR_MODE, NULL , NULL , &si, &pi, DLLPath, NULL ); delete [] DirPath; delete [] DLLPath; return 0 ; }
启动程序后就可以进行操控了,和直接用setdll差不多,但是这里不会影响正方体的贴图