腾讯游戏安全技术竞赛决赛第一题writeup

腾讯游戏安全技术竞赛决赛第一题writeup

题目描述

题目是用OpenGL ES 3.0编写的一个程序,用textures/container.jpg作为贴图,去渲染。目的是去寻找绘制出的flag,由于绘制区域限制,移动不到绘制区域。

做题过程

之前没有接触过openGL,相关函数一个个找,浏览了《opengl es 3.0 编程指南》。

贴图绑定:

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
sub_30604(v23);
dword_AA108 = v23;
_aeabi_memcpy8(&v48, dword_A1AA0, 720);
glGenVertexArrays(1, &v38);
glGenBuffers(1, &v39);
glBindVertexArray(v38);
glBindBuffer(0x8892, v39);
glBufferData(0x8892, 720, &v48, 35044);
glVertexAttribPointer(0, 3, 0x1406, 0, 20, 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, 5126, 0, 20, 12);
glEnableVertexAttribArray(1);
glGenTextures(1, &textures);
glBindTexture(0xDE1u, textures);
glTexParameteri(0xDE1u, 0x2802u, 10497);
glTexParameteri(0xDE1u, 0x2803u, 10497);
glTexParameteri(0xDE1u, 0x2801u, 9729);
glTexParameteri(0xDE1u, 0x2800u, 9729);
pixels = (const GLvoid *)sub_31578(v42, v43, &v35, &v34, &v33, 0);
if ( pixels )
{
glTexImage2D(0xDE1u, 0, 6407, *(GLsizei *)&v35, v34, 0, 0x1907u, 0x1401u, pixels);
glGenerateMipmap(3553);
}
j_j_free(pixels);
....

textures/container.jpg作为贴图纹理,dword_A1AA0为顶点数组(36个坐标),得到的结果就是一个立方体(以三角形为单位渲染,6 2 3=36)。

在程序中看到一个箭头上布满了箱子,就是以图片纹理渲染出的箱子来布满箭头。

找到的资料都是单个图像渲染,没找到怎么设置贴图目标。

找屏幕显示的地方,sub_30098里看到屏幕清除的函数,下面是一大堆浮点运算。该不会是逆浮点运算过程吧??
找了一下数据来源:dword_AA0FC
在so的init里找到了函数,里面是对着色器代码的复制,还有就是dword_AA0FC的赋值,数据来源就是dword_9E8D8。一堆数据,分析之后是浮点数,联系之前顶点坐标也是浮点数组,那么这也是个坐标数组。

一番搜索,glDrawElements和glDrawArrays是绘制函数,在绘制前,调用glUniformMatrix4fv,通过uniform上传至GPU,来绘制最终的图像。调试中发现传入的指针指向的是空数组。不清楚为什么。从代码来看就是将,dword_AA0FC传入的数据,每个循环处理x和y坐标。刚刚那个座标是一个三维坐标。

没仔细研究一大段的浮点运算具体是干什么的,猜测是三维到二维的映射计算。

把dword_9E8D8数据抠出来。用matplotlib绘制出来。哇!惊喜,这个坐标数组就是绘制图像的坐标数组。

由于旧的题里出现了flag的图片,还是给了不少启示的,所以这题做的还是有点侥幸的。

OpenGL相关

有关OpenGL的知识,需要了解一下几个概念。
这个系列文章写的不错:http://www.cnblogs.com/lijihong/p/5365677.html

vertex(顶点)

在设置贴图映射和绘制对象时会用到顶点。

例如在贴图映射中,会指定贴图所需要映射方式的一个顶点数组。如下是一个浮点数组。

VBO(顶点缓冲对象)

顶点缓冲对象VBO是在显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,可以直接从VBO中取出顶点的各类属性数据,由于VBO在显存而不是在内存中,不需要从CPU传输数据,处理效率更高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void CreateVertexBuffer()
{
// 创建含有一个顶点的顶点数组
Vector3f Vertices[1];
// 将点置于屏幕中央
Vertices[0] = Vector3f(0.0f, 0.0f, 0.0f);

// 创建缓冲器
glGenBuffers(1, &VBO);
// 绑定GL_ARRAY_BUFFER缓冲器
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 绑定顶点数据
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}

Shader(着色器)

图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。

在so中的init区段初始化了着色器代码。
0-w500

1-w500

sub_30604(v23);函数中完成了对着色器的创建和编译绑定。

1
2
3
4
5
6
GLuint ShaderProgram = glCreateProgram(); 
GLuint ShaderObj = glCreateShader(ShaderType);
glShaderSource(ShaderObj, 1, p, Lengths);
glCompileShader(ShaderObj);
glAttachShader(ShaderProgram, ShaderObj);
glUseProgram(ShaderProgram);

详细解释参考:https://blog.csdn.net/cordova/article/details/52495077

图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。

摘自:https://www.zhihu.com/question/29163054

贴图纹理

为了实现纹理贴图我们需要做三件事:
将一张贴图加载到OpenGL中
提供纹理坐标和顶点(将纹理对应匹配到顶点上)
并使用纹理坐标从纹理中进行取样操作取得像素颜色

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
sub_2F79C(*(_DWORD *)(*(_DWORD *)(v10 + 12) + 32), "textures/container.jpg", &v42);
...
sub_30604(v23);
dword_AA108 = v23;
_aeabi_memcpy8(&v48, dword_A1AA0, 720);
glGenVertexArrays(1, &v38);
glGenBuffers(1, &v39);
glBindVertexArray(v38);
glBindBuffer(0x8892, v39);
glBufferData(0x8892, 720, &v48, 35044);
glVertexAttribPointer(0, 3, 0x1406, 0, 20, 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, 5126, 0, 20, 12);
glEnableVertexAttribArray(1);
glGenTextures(1, &textures);
glBindTexture(0xDE1u, textures);
glTexParameteri(0xDE1u, 0x2802u, 10497);
glTexParameteri(0xDE1u, 0x2803u, 10497);
glTexParameteri(0xDE1u, 0x2801u, 9729);
glTexParameteri(0xDE1u, 0x2800u, 9729);
pixels = (const GLvoid *)sub_31578(v42, v43, &v35, &v34, &v33, 0);
if ( pixels )
{
glTexImage2D(0xDE1u, 0, 6407, *(GLsizei *)&v35, v34, 0, 0x1907u, 0x1401u, pixels);
glGenerateMipmap(3553);
}
j_j_free(pixels);
....

textures/container.jpg作为贴图纹理,dword_A1AA0为顶点数组(顶点数组是顶点中的截图),得到的结果就是一个立方体(以三角形为单位渲染,6 2 3=36)。

函数详解参考:https://blog.csdn.net/cordova/article/details/52825859

帧缓冲区

glUniform函数

uniform修饰符可以指定一个在应用中设置好的变量,它不会在图元处理的过程中发生变化,且在所有的着色阶段之间都是共享的——着色器中的全局变量。

详细参考:https://www.cnblogs.com/android-blogs/p/5454692.html

sub_30098(int a1)函数中对顶点的x、y坐标处理后会调用到sub_30EEC(int *a1, int a2, int a3),这个函数里就包含了glGetUniformLocation(v5, v4);j_glUniformMatrix4fv(v6, 1, 0, v3);