demo
打开来,是一个FlareOn的3D模型:
看似没什么东西,所以我们要从程序里头找点跟这个模型相关的东西出来,有可能是一些色调和背景融为一体的东西,也有可能是内容删减啥的,etc……
但是且慢,我们先介绍一种逃课打法——NinjaRipper。这个东西可以直接dump出程序中的模型文件,其高版本在永劫无间的模型吧里经常出现。这边建议使用1.7.1的老版本,高版本提出来的东西要看起来比较麻烦。
你还需要下载Noesis,并且使用NinjaRipper相关的演示插件才能正常显示。正常来讲你会在dump出来的东西中得到flag:
但是,逃课打法要用,正儿八经的做法也要用,这样才算得上健全。
我们先来了解一下在题目使用的 DirectX 9 架构下的模型文件,具体内容可以参考mesh文件加载相关和DirectX开发相关,这里简单说明一下,一个DirectX模型文件当中的基本图元,也就是组成一个模型的基本单元都是三角形,而在导入模型的时候,都是先导入各个三角形的顶点坐标,然后当即开始绘制,这可能也是早期游戏模型大多都十分尖锐的原因。
而相关的模型参数其实涉及了大量的数学运算,最主要的就是坐标系和邻接矩阵的概念,所以,如果我们能找到模型的位置偏移相关的坐标数值,也就能找到模型的位置。
问题来了,这东西在程序中的哪里呢?我们把目光方向DirectX的D3DXMatrixTranslation函数,这玩意可以即刻生成一个模型对坐标系的平移量。具体格式如下:
D3DXMATRIX* D3DXMatrixTranslation(
_Inout_ D3DXMATRIX *pOut, // 存储矩阵的变量
_In_ FLOAT x, // 三维坐标
_In_ FLOAT y,
_In_ FLOAT z
);
估计就是这个导致了flag模型飞出窗口。于是乎我们直接IDA,启……什么也没有?
似乎是文件头格式的问题,那我们只能x32动调看内存了。不管它加了什么壳,运行到一定程度断住,转到正确地址时,总会把符号表给加载干净,我们在其中发现了一个诡异的Direct3DX库,其中就有我们需要的函数:
下断点找到调用它的位置:
导致flag消失的罪魁祸首找到了,出题人设置了一个巨大的x轴偏移,改成0便是:
但是实际运行起来,flag的模型还有飞速转动的问题。这时候我们就要找找另一个函数:D3DXMatrixRotationY,这玩意可以控制模型旋转,而这个demo在经过几次动调之后可以确定,它是通过暴力循环加载模型来实现旋转的效果(这也解释了它性能为什么这么低),因此,我们只需要把这里下个断点,问题迎刃而解:
(虽然有点不大清晰就是了)
DnsChess
看上去只是个很简单的下棋而已,谁知道跑了一下就不动了——
算了,如果这玩意其实是一个联机软件,那先看一下流量包里的交互吧:
似乎也只是本机跟远程AI的交互,但是每一个操作对应的Answers似乎都来自另外的IP。我们总结起来看一下(如下是我手搓的部分结果):
车 c3->c6 127.150.96.223
马 g1->f3 127.252.212.90
卒 c2->c4 127.215.177.38
马 c7->d5 127.118.118.207
象 f1->e2 127.89.38.84
车 a1->g1 127.109.155.97
……
看着像一个只剩半截的棋谱,并且开头怎样还不清不楚,毕竟谁家好人车一开始从C3走。这下不得不逆了。先来看一眼UI:
qword_D118 = gtk_target_entry_new("x-chessblaster/move", 1LL, 0LL);
qword_D108 = gtk_target_list_new(qword_D118, 1LL);
v3 = gtk_application_new("com.flare-on.chessblaster", 0LL);
g_signal_connect_data(v3, "activate", sub_3AB0, 0LL, 0LL, 0LL);
type = g_application_get_type();
v5 = g_type_check_instance_cast(v3, type);
v6 = g_application_run(v5, a1, a2);
g_object_unref(v3);
return v6;
我们可以得知,前端使用的是GTK toolkit架构,并且,从sub_3AB0
里得知,主要行为跟so库里的getAiName
、getAiGreeting
、getNextMove
有关系。
其中最主要的是getNextMove
,我们发现这玩意似乎在远程端读取某个IP上的操作码并进行识别:
strcat(dest, ".game-of-thrones.flare-on.com");
v9 = gethostbyname(dest);
if ( !v9 )
return 2LL;
v10 = *v9->h_addr_list;
if ( *v10 != 127 || (v10[3] & 1) != 0 || a1 != (v10[2] & 0xF) ) // 确认读取的ip开头是否为127,且结尾是否为偶数
return 2LL;
sleep(1u);
// 对操作进行某种不知名的二对一异或解密
byte_4060[2 * a1] = v10[1] ^ byte_2020[2 * a1];
byte_4060[2 * a1 + 1] = v10[1] ^ byte_2020[2 * a1 + 1];
// 解密结果存入byte_4060后在a1=14时输出
*(_DWORD *)a5 = (unsigned __int8)v10[2] >> 4;
*(_DWORD *)(a5 + 4) = (unsigned __int8)v10[3] >> 1;
strcpy((char *)(a5 + 8), off_4120[a1]);
return (unsigned __int8)v10[3] >> 7;
a1的作用在这里应该类似于循环变量,或者步数计数器之类的。跟到输出会发现,似乎拿4位IP的第二位解密出来的结果会逐个输出并存入byte_4060
,并在最后一起输出,而byte_4060
的末尾刚好就是@flare-on.com,也就是flag格式:
虽然不知道这么一解密会发生什么,但这么一看,原包中似乎有成堆的不合规IP,我们筛选过后只剩下这么多:
1 马 g1->f3 127.252.212.90
2 卒 c2->c4 127.215.177.38
3 象 f1->e2 127.89.38.84
4 象 c1->f4 127.217.37.102
5 象 c6->a8 127.49.59.14
6 卒 e2->e4 127.182.147.24
7 卒 e5->e6 127.200.76.108
8 后 d1->h5 127.99.253.122
9 象 f3->c6 127.25.74.92
10 象 f4->g3 127.108.24.10
11 卒 e4->e5 127.34.217.88
12 后 h5->f7 127.141.14.174
13 象 e2->f3 127.230.231.104
14 马 b1->c3 127.159.162.42
15 卒 d2->d4 127.53.176.56
看着清楚多了,不过要按顺序排布清楚似乎也不简单。不过,根据正常的国象逻辑,不管对方怎么走,我们总得先动能动的子,直到字符串产生能看的结果,比方说第一步肯定是先走第二行的卒。
能行!第一步是d2的卒的话,我们按照逻辑和解码的结果,一个个排下去……且慢,如果这玩意才15个步数,那最后的全排列就是15的阶乘,甚至使用深搜还能剪枝,我们直接写一个简单的爆破脚本:
#include <bits/stdc++.h>
using namespace std;
char ID_sec_list[] = {252, 215, 89, 217, 49, 182, 200, 99, 25, 108, 34, 141, 230, 159, 53};
bool ID_usable[15];
char data[] = {121, 90, 184, 188, 236, 211, 223, 221, 153, 165,
182, 172, 21, 54, 133, 141, 9, 8, 119, 82,
77, 113, 84, 125, 167, 167, 8, 22, 253, 215};
char word[35];
int check(int add, char c)
{
char c1 = data[add*2] ^ c, c2 = data[add*2+1] ^ c;
if(c1 > 32 && c1 < 128 && c2 > 32 && c2 < 128)
return 1;
return 0;
}
void dfs(int stp)
{
if (stp == 15){
printf("%s@flare-on.com\n",word);
return;
}
for(int i=0;i<15;i++){
if(ID_usable[i] == true && check(stp, ID_sec_list[i])){
ID_usable[i] = false;
word[stp*2] = data[stp*2] ^ ID_sec_list[i];
word[stp*2+1] = data[stp*2+1] ^ ID_sec_list[i];
dfs(stp+1);
word[stp*2] = word[stp*2+1] = 0;
ID_usable[i] = true;
}
}
}
int main()
{
freopen("output.txt","w",stdout);
memset(word, 0, sizeof(word));
memset(ID_usable, true, sizeof(ID_usable));
dfs(0);
return 0;
}
然后从中找到一个跟刚才的结果对的上,又看得过去的,就是flag了: