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
//WNDCLASSEX类的原型
typedef struct WNDCLASSEX {
UINT cbSize; //WNDCLASSEX 的大小。我们可以用sizeof(WNDCLASSEX)来获得准确的值。
UINT style; //从这个窗口类派生的窗口具有的风格。您可以用“or”操作符来把几个风格或到一起。
WNDPROC lpfnWndProc; //窗口处理函数的指针。
int cbClsExtra; //指定紧跟在窗口类结构后的附加字节数。
int cbWndExtra; //指定紧跟在窗口实例的附加字节数。如果一个应用程序在资源中用CLASS伪指令注册一个对话框类时,则必须把这个成员设成DLGWINDOWEXTRA。
HINSTANCE hInstance; //本模块的实例句柄。
HICON hIcon; //图标的句柄。
HCURSOR hCursor; //光标的句柄。
HBRUSH hbrBackground; //背景画刷的句柄。
LPCTSTR lpszMenuName; //指向菜单的指针。
LPCTSTR lpszClassName; //指向类名称的指针。
HICON hIconSm; //和窗口类关联的小图标。如果该值为NULL。则把hIcon中的图标转换成大小合适的小图标。
} 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

关于glVertexAttribPointerhttps://blog.csdn.net/chenhanxuan1999/article/details/106182908

然后是加载纹理

关于glTexParameterihttps://blog.csdn.net/haiping1224746757/article/details/107251168

最后是设置projection、view、model三个变换矩阵并绘制界面

其中程序先设置好设置projection和view,而后使用循环设置model以及glDrawArrays

关于glGetUniformLocationhttps://blog.csdn.net/flycatdeng/article/details/82667229

关于glDrawArrayshttps://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 np
import matplotlib.pyplot as plt
import struct
xline = []
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')
# 设置X轴标签
plt.xlabel('X')
# 设置Y轴标签
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; // ecx
int v2; // esi
float v3; // xmm0_4
float v4; // xmm7_4
float v5; // xmm4_4
float v6; // xmm0_4
float v7; // xmm2_4
float v8; // xmm7_4
float v9; // xmm6_4
float v10; // xmm5_4
float v11; // xmm4_4
int result; // eax
float v13; // [esp+8h] [ebp-18h]
float v14; // [esp+Ch] [ebp-14h]
float v15; // [esp+Ch] [ebp-14h]
float v16; // [esp+Ch] [ebp-14h]
float v17; // [esp+10h] [ebp-10h]
float v18; // [esp+10h] [ebp-10h]
float v19; // [esp+14h] [ebp-Ch]
float v20; // [esp+14h] [ebp-Ch]
float v21; // [esp+18h] [ebp-8h]
int v22; // [esp+1Ch] [ebp-4h] BYREF

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
//#include "pch.h"
#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) //https://blog.csdn.net/hgy413/article/details/7875760
#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]; // the dll that used to inject
char* ProgramPath = new char[MAX_PATH]; // the program that needs to be injected
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差不多,但是这里不会影响正方体的贴图