January 13, 2022

NPUCTF2020-Baby Obfuscation

0x00 日常查壳

无壳64位

image-20220113171926664

0x01 五个F0X

分析主函数前,先自己代数字理解这五个函数,题目就能很看弄懂

F0X1

讲解一下几个函数,首先这个一开始我半天没反应过来,就是欧几里得算法 找最大公约数

可以参考这篇文章,讲的挺好

https://www.cxyxiaowu.com/995.html

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall F0X1(unsigned int a1, int a2)
{
__int64 result; // rax

if ( a2 )
result = Gcd(a2, (int)a1 % a2);
else
result = a1;
return result;
}

F0X2

只有当a1和a2都为1的时候返回1

1
2
3
4
_BOOL8 __fastcall F0X2(char a1, char a2)
{
return a1 == a2 && a1 != 1;
}

F0X3

与F0X3一样,都为1的时候返回1

1
2
3
4
5
6
7
8
9
__int64 __fastcall F0X3(bool a1, bool a2)
{
char v2; // bl
char v3; // al

v2 = F0X2(a2, a2);
v3 = F0X2(a1, a1);
return F0X2(v3, v2);
}

F0X4

这个很神奇,是相减的效果,理解这个整个题目就通了

先假设他们都是8位的

a1 = 0000 0010

a2 = 0000 0001

~a1 = 1111 1101

~a1 + a2 = 1111 1110

(a1 + a2) = 0000 0001

1
2
3
4
__int64 __fastcall F0X4(int a1, int a2)
{
return (unsigned int)~(~a1 + a2);
}

F0X5

这个效果就是Pow的效果 a1的a2次方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall F0X5(int a1, int a2)
{
unsigned int v4; // [rsp+Ch] [rbp-4h]

v4 = 1;
while ( a2 )
{
if ( (a2 & 1) != 0 )
v4 *= a1;
a1 *= a1;
a2 >>= 1;
}
return v4;
}

0x02 分析主函数

一点望去有点乱,但其实慢慢分析,能理解了题名的含义《宝宝都能做的混淆》。

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // ebx
int v5; // esi
int v6; // ebx
int v7; // ebx
int v8; // esi
int v9; // ebx
int v10; // ebx
int v11; // ebx
int v12; // esi
int v13; // eax
int v14; // ebx
int v15; // esi
int v16; // ebx
int v17; // eax
bool v18; // bl
int v19; // eax
int v20; // esi
int v21; // ebx
int v22; // ebx
int v23; // eax
int v24; // eax
int v25; // eax
int v26; // ebx
int data1[68]; // [rsp+20h] [rbp-60h] BYREF
char input[1008]; // [rsp+130h] [rbp+B0h] BYREF
int enflag[1008]; // [rsp+520h] [rbp+4A0h] BYREF
int num[4]; // [rsp+14E0h] [rbp+1460h]
int len; // [rsp+14F0h] [rbp+1470h]
int k; // [rsp+14F4h] [rbp+1474h]
int j; // [rsp+14F8h] [rbp+1478h]
int i; // [rsp+14FCh] [rbp+147Ch]

_main();
memset(enflag, 0, 0xFA0ui64);
enflag[1000] = 0;
memset(data1, 0, 0x100ui64);
data1[64] = 0;
for ( i = 0; i <= 64; ++i )
data1[i] = i + 1; // 初始化为1到65
num[0] = 2;
num[1] = 3;
num[2] = 4;
num[3] = 5;
enflag[1004] = 2;
enflag[1005] = 3;
enflag[1006] = 4;
enflag[1007] = 5;
puts("WHERE IS MY KEY!?");
scanf("%32s", input);
len = strlen(input);
v3 = Gcd(data1[j], data1[j]); // v3 = data1[j]
for ( j = v3 / data1[j]; j <= len; ++j )
{
v4 = (data1[j] + data1[j + 1]) * (data1[j] + data1[j + 1]);// 设[j]为a 设[j + 1]为b
if ( v4 >= (int)(Pow(2, 2) * data1[j] * data1[j + 1]) )// (a + b) ^ 2 >= 4ab 恒定成立
{
v5 = ~input[(int)Sub(j, 1)]; // input从下标为0开始取
v6 = Sub(j, 1);
enflag[j] = ~(v5 + num[v6 % (int)Pow(2, 2)]);// enflag[j] = input[i] - num[i % 4]
}
v7 = Gcd(data1[j], data1[j + 1]); // 相邻数最大公约数为1
if ( v7 > (int)Gcd(data1[j + 1], ~(~data1[j + 1] + data1[j])) )// gcd(b, a) > gcd(b, b - a) 等式不成立 代个数字就知道了
{
v8 = enflag[j];
v9 = Sub(j, 1);
enflag[j] = ~(~v8 + data1[v9 % (int)Pow(2, 2)]) * v8;
}
v10 = data1[j + 1];
v11 = Pow(2, 1) * v10;
v12 = data1[j];
v13 = Pow(2, 1);
v14 = Gcd(v12 * v13, v11); // v14 = gcd(a * 2, b * 2)
v15 = Pow(2, 1);
if ( v14 == v15 * (unsigned int)Gcd(data1[j], data1[j + 1]) )// 2 * gcd(a, b)== v14 恒定成立
{
v16 = Sub(j, 1);
enflag[j] ^= num[v16 % (int)Pow(2, 2)]; // enflag[i] ^= num[i % 4]
}
v17 = Pow(Num_3, data1[j]); // 3 ^ i
v18 = v17 < data1[j] + 1; // Pow(3, data[j]) < data[j] + 1恒不成立 v18恒等于 0
v19 = Pow(2, 4);
if ( (unsigned __int8)F0X3(v19 >= j, v18) ) // 也就是只要j <= 16的时候会进入这个判断
{
v20 = ~input[(int)Sub(j, 1)];
v21 = Sub(j, 1);
enflag[j] ^= ~(v20 + num[v21 % (int)Pow(2, 2)]);// enflag[j] ^= (input[i] - num[i % 4])
}
v22 = Pow(2, 3); // v22 = 8
v23 = Gcd(data1[j], data1[j]); // v23 = data1[j]
enflag[j] *= v22 + (unsigned int)Pow(2, v23 / data1[j]);// enflag[j] *= 10
}
v24 = Pow(2, 4);
if ( (unsigned int)Sub(v24, 1) != len ) // 判断循环次数 也就是长度为15
goto LABEL_23;
v25 = Gcd(data1[k], data1[k]);
for ( k = v25 / data1[k]; k <= len; ++k )
{
v26 = enflag[k];
if ( v26 == (int)Sub(A0X6[k], 1) / 10 )
++V0X2;
}
if ( V0X2 == len )
puts("\nPASS");
else
LABEL_23:
puts("\nDENIED");
return 0;
}

于是我们来理一下,flag到底经历了什么(那么真正进入的判断)

  1. 首先是这样放进了enflag里 enflag[j] = input[i] - num[i % 4]
  2. 再异或了num数组 enflag[i] ^= num[i % 4]
  3. 最后 enflag[j] *= 10

0x03 GetFlag

从判断的地方取出加密后的数据

image-20220113201401788

shift + E

image-20220113201518822

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>

int main(void)
{
int enflag[] = { 7801, 7801, 8501, 5901, 8001, 6401, 11501, 4601, 9801, 9601, 11701, 5301, 9701, 10801, 12501 };
int i, j;
int nums[] = { 2, 3, 4, 5 };

for ( i = 0; i < 15; i++ )
{
enflag[i] = (enflag[i] - 1) / 10;
enflag[i] /= 10;
enflag[i] ^= nums[i % 4];
enflag[i] += nums[i % 4];
printf("%c", enflag[i]);
}

return 0;
}

GetFlag!

image-20220113201454681

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.