BUUCTF 刮开有奖1题解
这个题目是我目前为止做过的最难的逆向题目了,相当的恶心。不过适应了也还好。不说了,拿到程序后丢进ida,发现是一个 32 位程序,先进WinMain函数,然后F5反汇编。最终我们拿到了这样的一个程序:
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
DialogBoxParamA(hInstance, (LPCSTR)0x67, 0, DialogFunc, 0);
return 0;
}
其中DialogFunc就是这个窗口的内部程序的程序过程。所以双击函数名进入。然后大致浏览,可以得到这个是该程序的主要逻辑部分。
可以发现,函数sub_4010F0
对一个数组进行了预处理,同时还有一个函数sub_401000
对输入进行了加密。
通过判断条件
if ( String == v7 + 34
&& v19 == v11
&& 4 * v20 - 141 == 3 * v9
&& v21 / 4 == 2 * (v14 / 9)
&& !strcmp(v4, "ak1w")
&& !strcmp(v5, "V1Ax") )
{
MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
}
我们可以知道输入的字符串前半部分对部分预处理结果进行对比判断,然后将输入的后半部分编码和几个已知的字符串进行对比判断。
所以接下来我们的重点是看看这两个函数究竟干了什么。
函数 sub_4010F0
这个函数由于是预处理,我们可以直接将这段伪代码转换成能执行的 C 语言代码,运行求解即可。转换后的代码:
int sub_4010F0(int a1, int a2, int a3)
{
int result; // eax
int i; // esi
int v5; // ecx
int v6; // edx
result = a3;
for (i = a2; i <= a3; a2 = i)
{
v5 = 4 * i;
v6 = *(int *)(4 * i + a1);
if (a2 < result && i < result)
{
do
{
if (v6 > *(int *)(a1 + 4 * result))
{
if (i >= result)
break;
++i;
*(int *)(v5 + a1) = *(int *)(a1 + 4 * result);
if (i >= result)
break;
while (*(int *)(a1 + 4 * i) <= v6)
{
if (++i >= result)
goto LABEL_13;
}
if (i >= result)
break;
v5 = 4 * i;
*(int *)(a1 + 4 * result) = *(int *)(4 * i + a1);
}
--result;
} while (i < result);
}
LABEL_13:
*(int *)(a1 + 4 * result) = v6;
sub_4010F0(a1, a2, i - 1);
result = a3;
++i;
}
return result;
}
至于转换得到方法,其实也很简单,就是将一些不属于 C 的变量类型换掉就可以了,这个网上有不少博客都有总结,可以自行百度。
这个程序因为是 32 位程序,所以这个函数只能在32位环境下才能正常运行,要不然会因为64位指针类型和32位指针类型占用的空间大小不一样,可能导致运行出错或是无法编译。
64位系统要如何编译32位程序可以看下面这个博客
https://blog.csdn.net/x356982611/article/details/79056089
运行后用 debug 查看结果如下:
然后根据判断条件,就得到了开头的几个输入。
没准这个函数就是排序函数呢,不过这么大串代码不会真的有人去读吧
函数 sub_401000
接下来我们就来分析这个函数。这个函数因为是对输入加密,所以比较复杂,但是这个函数的代码也是相当复杂,如果我们对这个函数的程序一行一行去读,估计也是读不出来什么的。所以我们按照上面所说的,将函数的伪代码转换成能运行的C代码。
char *sub_401000(int a1, int a2)
{
int v2; // eax
int v3; // esi
size_t v4; // ebx
char *v5; // eax
char *v6; // edi
int v7; // eax
char *v8; // ebx
int v9; // edi
signed int v10; // edx
int v11; // edi
signed int v12; // eax
signed int v13; // esi
char *result; // eax
char *v15; // [esp+Ch] [ebp-10h]
char *v16; // [esp+10h] [ebp-Ch]
int v17; // [esp+14h] [ebp-8h]
int v18; // [esp+18h] [ebp-4h]
v2 = a2 / 3;
v3 = 0;
if (a2 % 3 > 0)
++v2;
v4 = 4 * v2 + 1;
v5 = (char *)malloc(v4);
v6 = v5;
v15 = v5;
if (!v5)
exit(0);
memset(v5, 0, v4);
v7 = a2;
v8 = v6;
v16 = v6;
if (a2 > 0)
{
while (1)
{
v9 = 0;
v10 = 0;
v18 = 0;
do
{
if (v3 >= v7)
break;
++v10;
v9 = *(unsigned char *)(v3++ + a1) | (v9 << 8);
} while (v10 < 3);
v11 = v9 << 8 * (3 - v10);
v12 = 0;
v17 = v3;
v13 = 18;
do
{
if (v10 >= v12)
{
*((char *)&v18 + v12) = (v11 >> v13) & 0x3F;
v8 = v16;
}
else
{
*((char *)&v18 + v12) = 64;
}
*v8++ = byte_407830[*((char *)&v18 + v12)];
v13 -= 6;
++v12;
v16 = v8;
} while (v13 > -6);
v3 = v17;
if (v17 >= a2)
break;
v7 = a2;
}
v6 = v15;
}
result = v6;
*v8 = 0;
return result;
}
然后我们将部分数据代入这个函数,然后得到输出,然后丢到一些自动判断加密算法的程序,看看是不是一些已知的加密或者编码算法。比如说这个网站就可以
然后我们知道这个加密算法是 base64 编码算法。接下来的步骤就简单了,因为base64可逆,只需要将伪代码中的"ak1w"
,"V1Ax"
字符串解码就可以得到了剩下的几个输入了。
话说为啥这个题目我死活找不到输入在哪里啊,是我 win API 编程还不太熟悉吗?