February 5, 2022

时空飞行

0x00 日常查壳

无壳64位

image-20220205104052072

0x01 分析主函数

首先简单看下程序行为,就是去找到日期和符来歌

image-20220205104207525

于是拖进ida一看

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4[24]; // [rsp+20h] [rbp-A0h]
char flag[32]; // [rsp+80h] [rbp-40h] BYREF
char date[20]; // [rsp+A0h] [rbp-20h] BYREF
int v7; // [rsp+B4h] [rbp-Ch]
int j; // [rsp+B8h] [rbp-8h]
int i; // [rsp+BCh] [rbp-4h]

_main(argc, argv, envp);
puts("Come on! Time travel starts now!\n");
LinkStart();
printf("请放入日期:");
scanf("%s", date);
SetDate((__int64)dates, (unsigned int *)date);// 原型是SM4密钥扩展 去掉了S盒代换
for ( i = 0; i <= 3; ++i )
{
if ( endate[i] != dates[i + 28] ) // 如果不相等就会退出 那么里面的代码不用关心
{
i = 2;
v7 = 0;
while ( i <= 3 )
{
for ( j = 1; j <= 4; ++j )
*((_BYTE *)&C + 6 * i + j) = date[v7++];
++i;
}
Door();
printf(asc_405100);
exit(0);
}
}
PartSuccessful(date);
printf("请唱出符来歌:");
scanf("%s", flag);
SingFlag(flags, flag); // 原型是AES密钥扩展 去掉了S盒代换
for ( i = 0; i <= 5; ++i )
{
v4[4 * i] = (unsigned __int8)flags[i + 60];
v4[4 * i + 1] = (unsigned __int8)BYTE1(flags[i + 60]);
v4[4 * i + 2] = (unsigned __int8)BYTE2(flags[i + 60]);
v4[4 * i + 3] = HIBYTE(flags[i + 60]);
}
for ( i = 1; i <= 23; ++i )
v4[i - 1] ^= (v4[i - 1] % 0x12u + v4[i] + 5) ^ 0x41;// 这边的异或没法直接逆 需要DFS穷举所有异或可能性来解
for ( i = 0; i <= 23; ++i )
{
if ( v4[i] != enflag[i] ) // 如果不相等就会退出 那么里面的代码不用关心
{
for ( i = 0; i <= 5; ++i )
{
for ( j = 0; j <= 5; ++j )
*((_BYTE *)&C + 6 * i + j) = 14;
}
Door();
printf(asc_405138);
exit(0);
}
}
EndSuccessful(flag);
return 0;
}

0x02 SetDate

简单分析一下SetDate函数

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
__int64 __fastcall SetDate(__int64 dates, unsigned int *date)
{
__int64 result; // rax
int v3; // ebx
int k[36]; // [rsp+20h] [rbp-60h]
unsigned int v5; // [rsp+B0h] [rbp+30h]
unsigned int v6; // [rsp+B4h] [rbp+34h]
unsigned int v7; // [rsp+B8h] [rbp+38h]
unsigned int v8; // [rsp+BCh] [rbp+3Ch]
int i; // [rsp+CCh] [rbp+4Ch]
// 也就是date16位差分为4 4 4 4分别大端序放入
v5 = _byteswap_ulong(*date); // 动调可以得知 这个是每次取4个字节大端序放到v5 v6 v7 v8
v6 = _byteswap_ulong(date[1]);
v7 = _byteswap_ulong(date[2]);
v8 = _byteswap_ulong(date[3]);
k[0] = v5 ^ 0xA3B1BAC6; // 每个异或常量
k[1] = v6 ^ 0x56AA3350;
k[2] = v7 ^ 0x677D9197;
result = v8 ^ 0xB27022DC;
k[3] = v8 ^ 0xB27022DC;
for ( i = 0; i <= 31; ++i ) // 然后下面一段异或是完全可逆的 只要实现了CalcRoundKey里的操作即可
{
v3 = k[i];
k[i + 4] = v3 ^ CalcRoundKey(k[i + 3] ^ k[i + 2] ^ (unsigned int)k[i + 1] ^ CK[i]);
result = (unsigned int)k[i + 4];
*(_DWORD *)(dates + 4i64 * i) = result;
}
return result;
}

异或等式

关键还是异或那 写个等式

1
2
3
4
5
6
7
8
9
k[5] = k[0] ^ CalcRoundKey( k[3] ^ k[2] ^ k[1] ^ CK[0] )

k[6] = k[1] ^ CalcRoundKey( k[4] ^ k[3] ^ k[2] ^ CK[1] )

...

k[34] = k[30] ^ CalcRoundKey( k[33] ^ k[32] ^ k[31] ^ CK[30] )

k[35] = k[31] ^ CalcRoundKey( k[34] ^ k[33] ^ k[32] ^ CK[31] )

那么现在我们有了CK常量和加密后的k[35] k[32] k[33] k[34](也就是dates[28] [29] [30] [31])

不用在意CalcRoundKey怎么操作,只用关心这只是个值

那么逆回去的就是

1
2
3
k[31] = k[35] ^ CalcRoundKey( k[34] ^ k[33] ^ k[32] ^ CK[31] ) //拿回了k[31]

k[30] = k[31] ^ CalcRoundKey( k[33] ^ k[32] ^ k[31] ^ CK[30] )

于是可以写个等式

k[i] = k[i + 4] ^ CalcRoundKey( k[i + 3] ^ k[i + 2] ^ k[i + 1] ^ CK[i] )

CalcRoundKey

就是数本身异或左移13位和右移9位(右移9位也可以理解成左移23位)

1
2
3
4
__int64 __fastcall CalcRoundKey(int a1)
{
return a1 ^ (unsigned int)(__ROL4__(a1, 13) ^ __ROR4__(a1, 9));
}

GetDate!

于是只要写好函数直接开逆

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
#include <stdio.h>												

/**
* 作用: 参数 x 左移参数 n 位
*/
#define SHL(x, n) ( ((x) & 0xFFFFFFFF) << n )

/**
* 作用: 参数 x 逻辑左移参数 n 位
*/
#define ROTL(x, n) ( SHL((x), n) | ((x) >> (32 - n)) )

/**
* 密钥用常量
*/
static const unsigned long FK[4] = {0xa3b1bac6,0x56aa3350,0x677d9197,0xb27022dc};

/**
* 密钥用常量
*/
static const unsigned long CK[32] =
{
0x00070e15,0x1c232a31,0x383f464d,0x545b6269,
0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,
0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,
0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,
0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,
0x30373e45,0x4c535a61,0x686f767d,0x848b9299,
0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,
0x10171e25,0x2c333a41,0x484f565d,0x646b7279
};

unsigned int CalcRoundKey(unsigned int ka);
void SetKey(unsigned int SK[32], unsigned char key[16]);

int main(void)
{
unsigned int t[36];
unsigned int k[] = { 0xFD07C452, 0xEC90A488, 0x68D33CD1, 0x96F64587 };
int i, j;

for ( i = 32; i < 36; i++ )
{
t[i] = k[i - 32];
// printf("0x%X, ", t[i]);
}

for ( i = 35; i >= 4; i-- )
t[i - 4] = t[i] ^ ( CalcRoundKey(t[i - 3] ^ t[i - 2] ^ t[i - 1] ^ CK[i - 4]) );

for ( i = 0; i < 4; i++ )
{
// printf("0x%X, ", t[i] ^ FK[i]);
t[i] ^= FK[i];
}

printf("Date:");
unsigned char * p = (unsigned char *)t;
for ( i = 1; i <= 2; i++ )
for ( j = 1; j <= 4; j++ )
printf("%c", p[i * 4 - j]);

return 0;
}

unsigned int CalcRoundKey(unsigned int ka)
{
unsigned int retval = 0;
int i;

retval = ka ^ (ROTL(ka, 13) ^ ROTL(ka, 23));

return retval;
}

GetPart!

image-20220205114241182

0x03 SingFlag

拿到了正确的日期 于是可以去找符来歌

image-20220205114627234

从这里是可以得知我们要的符来歌是24位

image-20220205115050791

异或等式

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
void __fastcall SingFlag(__int64 flags, __int64 flag)
{
int v2; // ebx
unsigned int v3; // [rsp+28h] [rbp-58h]
int i; // [rsp+2Ch] [rbp-54h]
int v5; // [rsp+2Ch] [rbp-54h]

for ( i = 0; i <= 5; ++i )
*(_DWORD *)(4i64 * i + flags) = GetWordFromStr((char *)(flag + 4 * i));// 把字符串转成4字节整形 循环六次
v5 = 6;
v3 = 0;
while ( v5 <= 65 ) // 一共66轮
{
if ( v5 % 6 )
{
*(_DWORD *)(4i64 * v5 + flags) = *(_DWORD *)(4i64 * v5 - 24 + flags) ^ *(_DWORD *)(4i64 * v5 - 4 + flags);
}
else // 6的倍数进行一个T操作
{
v2 = *(_DWORD *)(4i64 * v5 - 24 + flags);
*(_DWORD *)(4i64 * v5 + flags) = T(*(unsigned int *)(4i64 * v5 - 4 + flags), v3++) ^ v2;
}
++v5;
}
}

其实还是一个异或等式

1
2
3
4
5
6
7
8
9
10
11
flags[6] = flags[0] ^ T( flags[5], 0 )

flags[7] = flags[1] ^ flags[6]

flags[8] = flags[2] ^ flags[7]

...

flags[64] = flags[58] ^ flags[63]

flags[65] = flags[59] ^ flags[64]

那么到最后是有flags[60] flags[61] flags[62] flags[63] flags[64] flags[65]

同理写出等式

1
2
3
i != 6: flags[i] = flags[i + 6] ^ flags[i + 5] 

i % 6 == 0: flags[i] = flags[i + 6] ^ T( flags[i + 5], j )

(j 为轮次 第一次为0 第二次为1以此类推)

T运算

实现相应操作即可构成数值

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall T(unsigned int a1, int a2)
{
char v3[28]; // [rsp+20h] [rbp-20h] BYREF
unsigned int v4; // [rsp+3Ch] [rbp-4h]

SplitIntToArray(a1, v3); // 分割成数组
LeftLoop4Int(v3, 1i64); // 行左移
v4 = MergeArrayToInt(v3); // 变回整形
return v4 ^ Rcon[a2]; // 异或轮常量
}

0x04 DFS

已经知道上面怎么逆了,现在就是拿回这六个数值(flags[60] flags[61] flags[62] flags[63] flags[64] flags[65])

这边就是把六个值顺序放回到t数组

例如 flags[60] = 0x12345678

t[0] = 78, t[1] = 56, t[2] = 34, t[3] = 12

1
2
3
4
5
6
7
8
9
for ( i = 0; i <= 5; ++i )
{
t[4 * i] = (unsigned __int8)flags[i + 60];
t[4 * i + 1] = (unsigned __int8)BYTE1(flags[i + 60]);
t[4 * i + 2] = (unsigned __int8)BYTE2(flags[i + 60]);
t[4 * i + 3] = HIBYTE(flags[i + 60]);
}
for ( i = 1; i <= 23; ++i )
t[i - 1] ^= (t[i - 1] % 0x12u + t[i] + 5) ^ 0x41;// 这边的异或没法直接逆 需要DFS穷举所有异或可能性来解

于是就这是个破坏性的异或,我们无法直接靠爆破逆回去,因为值分叉了,需要穷举所有异或的可能性解决

(最后一个值t[23]是没有变的,突破口就在这)

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
void DFS(unsigned char * flag, int deep)
{
int i;
if ( deep == 0 ) //如果恢复了整条链子
{
for ( i = 0; i < 24; i++ )
{
flags[x][i] = flag[i]; //存入flag集
// printf("0x%X, ", flag[i]);
}
x++;
// puts("\n");
}
else
{ //这里的核心就是 每当恢复了一条链子递归回溯又到这个循环 就会继续遍历其他的可能性
for ( i = 0; i < 0xFF; i++ ) //尝试每一个数据
{
if ( ((i ^ 0x41) ^ (i % 0x12 + flag[deep] + 0x05)) == enc[deep - 1] ) //i为我们构造的值
{
flag[deep - 1] = i; //恢复一条就放到其中的一条链子
// printf("0x%X, ", flag[deep - 1]);
DFS(flag, deep - 1); //继续恢复
}
}
}

}

通过输入正确的日期 我们可以拿到真正的密文串(我把恢复数值放到PartSuccessful里面)

image-20220205142914739

0x05 GetFlag

于是与逆符来歌合并一下

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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include <stdio.h>

static const int Rcon[10] =
{
0x01000000, 0x02000000,
0x04000000, 0x08000000,
0x10000000, 0x20000000,
0x40000000, 0x80000000,
0x1b000000, 0x36000000
};

int GetIntFromChar(char c);
int GetWordFromStr(char * str);
int MergeArrayToInt(int array[4]);
void SplitIntToArray(int num, int array[4]);
void LeftLoop4Int(int array[4], int step);
int T(int num, int round);

void DFS(unsigned char * flag, int deep);

unsigned char flags[30][24]; //已经控制数据在30个以内 你们多设点也可以
unsigned int enc[24] = { 0x25, 0x15, 0xDF, 0xA2, 0xC0, 0x93, 0xAD, 0x14, 0x46, 0xC5, 0xF, 0x2E, 0x9A, 0xEB, 0x30, 0xF8, 0x20, 0xE9, 0xCB, 0x88, 0xC6, 0xBE, 0x8D, 0xE3 };
int x;

int main(void)
{
int i, j, c;
unsigned int t[66];

unsigned char flag[24];
flag[23] = 0xE3;
DFS(flag, 23); //找到所有可能性放到flags

unsigned int * pi = (unsigned int *)flags; //已无符号整形来访问 宽度为24 于4字节访问一次 所以为6
// for ( i = 0; i < 30 * 6; i++ )
// {
// printf("0x%X, ", pi[i]);
// if ( (i + 1) % 6 == 0 )
// printf("\n");
// }

for ( c = 0; c <= 30 * 6; c += 6 )
{
for ( i = 60; i < 66; i++ ) //填好数据
t[i] = pi[c + i - 60];
for ( i = 59, j = 9; i >= 0; i-- ) //利用等式逆回去
{
if ( i % 6 == 0 )
{
t[i] = t[i + 6] ^ T(t[i + 5], j);
j--;
}
else
t[i] = t[i + 6] ^ t[i + 5];
// printf("t[%d] = 0x%X, ",i, t[i]);
}
unsigned char * p = (unsigned char *)t;
for ( i = 0; i < 24; i += 4 )
printf("%c%c%c%c", p[i + 3], p[i + 2], p[i + 1], p[i]);//注意小端序
printf("\n");
}

return 0;
}

void DFS(unsigned char * flag, int deep)
{
int i;
if ( deep == 0 )
{
for ( i = 0; i < 24; i++ )
{
flags[x][i] = flag[i];
// printf("0x%X, ", flag[i]);
}
x++;
// puts("\n");
}
else
{
for ( i = 0; i < 0xFF; i++ )
{
if ( ((i ^ 0x41) ^ (i % 0x12 + flag[deep] + 0x05)) == enc[deep - 1] )
{
flag[deep - 1] = i;
// printf("0x%X, ", flag[deep - 1]);
DFS(flag, deep - 1);
}
}
}

}

int GetIntFromChar(char c)
{
int result = (int) c;

return result & 0x000000FF;
}

int GetWordFromStr(char * str)
{
int one = GetIntFromChar(str[0]);
one = one << 24;

int two = GetIntFromChar(str[1]);
two = two << 16;

int three = GetIntFromChar(str[2]);
three = three << 8;

int four = GetIntFromChar(str[3]);

return one | two | three | four;
}

int MergeArrayToInt(int array[4])
{
int one = array[0] << 24;
int two = array[1] << 16;
int three = array[2] << 8;
int four = array[3];

return one | two | three | four;
}

void SplitIntToArray(int num, int array[4])
{
int one = num >> 24;
array[0] = one & 0x000000FF;

int two = num >> 16;
array[1] = two & 0x000000FF;

int three = num >> 8;
array[2] = three & 0x000000FF;

array[3] = num & 0x000000FF;
}

void LeftLoop4Int(int array[4], int step)
{
int tmp[4];
int i;

for ( i = 0; i < 4; i++ )
tmp[i] = array[i];

int index = step;
for ( i = 0; i < 4; i++ )
{
array[i] = tmp[index];
index++;
index = index % 4;
}
}

int T(int num, int round)
{
int NumArray[4];

SplitIntToArray(num, NumArray);

LeftLoop4Int(NumArray, 1);

int result = MergeArrayToInt(NumArray);

return result ^ Rcon[round];
}

Get符来歌

image-20220205145237180

结束了?还没有,我们继续输入得到这串符来歌

image-20220205150045708

1
Flag: VNCTF{TimeFl20211205ightMachine}
DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

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