和两位学长一起做出来的,总算是能在团队赛中做出来题了,结束了长达一个月的白给生涯,不容易啊
思路
先拖到 ida 里进行静态分析
查看main函数,把变量定义和初始化删了,并将 cin
的东西命名为了 input
__int64 __fastcall main(int a1, char **a2, char **a3) {
std::operator<<<std::char_traits<char>>(&std::cout, ">> ", a3);
std::operator>><char,std::char_traits<char>>(&std::cin, input);
if ( strlen(input) == 64 ) {
v3 = input;
v4 = &unk_55A06D2D0020;
v5 = 0LL;
do {
v6 = hexstr2int(v3, 8);
v7 = v14;
sub_55A06D2CC408(&v14[v5], v6);
sub_55A06D2CC408(&v18[v5], 17);
v8 = v19;
sub_55A06D2CC408(&v19[v5], *v4);
++v3;
++v4;
v5 += 36LL;
} while ( v3 != v21 );
sub_55A06D2CCB30(v14, v18, v9);
sub_55A06D2CCB30(v15, v18, v10);
sub_55A06D2CCB30(v16, v18, v11);
sub_55A06D2CCB30(v17, v18, v12);
while ( !sub_55A06D2CCADC(v7, v8) ) {
v7 += 36;
v8 += 36;
if ( v7 == v18 ) {
__printf_chk(1LL, "flag{%s}\n", input);
return 0LL;
}
}
puts("error");
}
else {
puts("error");
}
return 0LL;
}
简单查看一下,发现输入长度为64,然后 do while
看起来是进行初始化,后面的四个连续函数应该是加密
同时确定这个应该是算法题,不涉及混淆之类的东西
于是查看一下 v6=
的那个函数
__int64 __fastcall hexstr2int(_BYTE *a1, int a2) {
v2 = strlen(a0123456789abcd) + 1; // 17
if ( a2 <= 0 )
return 0LL;
v3 = v2 - 1;
v4 = a1;
LODWORD(result) = 0;
while ( 1 ) {
if ( v3 <= 0 )
return 0LL;
for ( i = 0LL; a0123456789abcd[i] != *v4; ++i ) {// input是0-f
if ( i == v2 - 2 )
return 0LL;
}
if ( v3 <= i )
break;
result = (i + (v2 - 1) * result); // input转成int
if ( ++v4 == &a1[a2 - 1 + 1] )
return result;
}
return 0LL;
}
发现应该是将输入转换成了 int
,而且输入的字符必须得在 0123456789ABCDEF
中
然后看一眼重复出现三次的 sub_55A06D2CCB30
函数
__int64 __fastcall sub_55A06D2CC408(__int64 a1, int a2) {
*(a1 + 8) = 0;
*(a1 + 4) = a2;
v2 = 4LL;
while ( 1 ) {
v3 = v2 - 1;
if ( *(a1 + v2 + 3) )
break;
if ( !--v2 )
goto LABEL_4;
}
v3 = v2;
LABEL_4:
*a1 = v3;
return v3;
}
看起来就是简单的赋值,a1
的第一部分填写长度,第二部分存 a2
初始化部分应该可以先不管了,去看加密函数
unsigned __int64 __fastcall sub_55A06D2CCB30(__int64 a1, __int64 a2, __int64 a3) {
sub_55A06D2CCA13(v5, a1);
sub_55A06D2CCA13(v6, (a1 + 36));
sub_55A06D2CC408(v7, 0);
sub_55A06D2CC408(v8, 0x830A5376);
sub_55A06D2CC408(v9, 0x1D3D2ACF);
sub_55A06D2CC667(v10, v9, v8); // v10=delta=0x9e3779b9
sub_55A06D2CCA13(v11, a2);
sub_55A06D2CCA13(v12, (a2 + 36));
sub_55A06D2CCA13(v13, (a2 + 72));
sub_55A06D2CCA13(v14, (a2 + 108));
v3 = 32;
do {
sub_55A06D2CC2E9(v7, v7, v10); // sum+=delta
left_rot(v15, v6, 4);
sub_55A06D2CC2E9(v15, v15, v11); // v15+v11
sub_55A06D2CC2E9(v16, v6, v7); // sum+plain
right_rot(v17, v6, 5);
sub_55A06D2CC2E9(v17, v17, v12); // key+plain
sub_55A06D2CC667(v15, v15, v16);
sub_55A06D2CC667(v15, v15, v17);
sub_55A06D2CC2E9(v5, v5, v15);
left_rot(v18, v5, 4);
sub_55A06D2CC2E9(v18, v18, v13);
sub_55A06D2CC2E9(v19, v5, v7);
right_rot(v20, v5, 5);
sub_55A06D2CC2E9(v20, v20, v14);
sub_55A06D2CC667(v18, v18, v19);
sub_55A06D2CC667(v18, v18, v20);
sub_55A06D2CC2E9(v6, v6, v18);
--v3;
} while ( v3 );
sub_55A06D2CCA13(a1, v5);
sub_55A06D2CCA13((a1 + 36), v6);
return __readfsqword(0x28u) ^ v21;
}
一开始看到一大串 sub_
还以为是 AES 之类的(毕竟之前做过一道类似的),然后看了一下重要的参数
64bit明文,128bit密钥,32轮加密
看起来很像tea家族的算法,但不是很确定。因为懒得详细看每个函数了,所以动态调试一下,做个黑盒测试
调试之后发现 0x830A5376
和 0x1D3D2ACF
其实是用来算 delta
的,算出来 v10=0x9e3779b9
,这就肯定是tea家族了
另外,动调的时候发现这里应该都是用的结构体,所以每个小的加法都是单独的函数给出的,但好像用上的只有结构体的前八个字节,后面的字节有什么用还不太清楚
然后具体往下看,看到循环中的前几个分别是 sum+=delta
,v6<<4
,(v6<<4)+k0
,sum+p0
,v6>>5
,k1+p0
。和网上的三种 TEA 加密比对了一下,发现竟然就是最简单的 TEA 加密,一开始看这么多函数还以为是 XTEA 呢
脚本
密钥就是主函数中初始化的几个 0x11
,密文就是最后用来 cmp
的字符串(找结构体对应的那个字符数组就行),于是找了个脚本,直接解密
#include <stdio.h>
#include <stdint.h>
void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
uint32_t delta = 0x9e3779b9;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0; v[1] = v1;
}
int main() {
uint32_t v[2] = {0x79AE1A3B, 0X596080D3}, k[4] = {17, 17, 17, 17};
decrypt(v, k);
printf("%X%X", v[0], v[1]);
}
把所有密文全扔进去算结果,就能拿到 flag 了
总结
第一眼看到这个题就感觉能做出来,果然如此
深刻体会到了动调进行黑盒测试的重要性,可以大幅度加快做题速度,能猜出来就不要花时间看
其实这题有点可惜,本来能抢到三血的,结果忘了大小写的问题,脚本算出来后本地测试一直是 error
,最后还是大佬队友发现的,然后就过了