【RE】2025-12-20升级赛1z_re

整体思路很清晰但是写得我晕头转向的

题目:1z_re

分析

检查无壳。

主函数。读入一个buffer,期望buffer长度为19,取前四个四节塞到key里,进行一个encrypt得到Block。期望Block长度为24,对Block按照置换表perm进行置换,然后顺序进行异或加,得到v5。最后如果v5与target完全相等输出You win!

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
int result; // eax
char v5[24]; // [rsp+20h] [rbp-60h] BYREF
unsigned __int64 Block_len; // [rsp+38h] [rbp-48h] BYREF
unsigned __int8 Key[8]; // [rsp+40h] [rbp-40h] BYREF
__int64 v8; // [rsp+48h] [rbp-38h]
char Buffer[128]; // [rsp+50h] [rbp-30h] BYREF
unsigned __int64 v10; // [rsp+D0h] [rbp+50h]
_BYTE *Block; // [rsp+D8h] [rbp+58h]
unsigned __int64 l; // [rsp+E0h] [rbp+60h]
unsigned __int64 k; // [rsp+E8h] [rbp+68h]
char tmp; // [rsp+F7h] [rbp+77h]
unsigned __int64 j; // [rsp+F8h] [rbp+78h]
unsigned __int64 i; // [rsp+100h] [rbp+80h]
unsigned __int64 Buffer_len; // [rsp+108h] [rbp+88h]

_main();
v3 = __acrt_iob_func(0);
if ( !fgets(Buffer, 128, v3) )
return 0;
Buffer_len = strlen(Buffer);
if ( Buffer_len && Buffer[Buffer_len - 1] == 10 )
Buffer[--Buffer_len] = 0;
if ( Buffer_len == 19 ) // 长度19
{
v8 = 0i64;
*(_QWORD *)Key = (unsigned __int8)Buffer[0];
Key[1] = Buffer[1];
Key[2] = Buffer[2];
Key[3] = Buffer[3];
Block_len = 0i64;
Block = encrypt((const unsigned __int8 *)Buffer, 19ui64, Key, &Block_len);
if ( Block && Block_len == 24 )
{
for ( i = 0i64; i <= 23; ++i )
v5[i] = Block[main::perm[i]];
for ( j = 1i64; j <= 23; ++j )
{
tmp = v5[j];
v10 = j / 3;
for ( k = 0i64; k < v10; ++k )
tmp ^= v5[k];
v5[j] = tmp;
}
free(Block);
for ( l = 0i64; l <= 23; ++l )
{
if ( v5[l] != main::target[l] )
{
puts("error");
return 0;
}
}
puts("You win!");
getchar();
result = 0;
}
else
{
free(Block);
puts("error");
result = 0;
}
}
else
{
puts("error");
result = 0;
}
return result;
}

encrypt。输入char转int32得到Block,密钥key扩展成v5,xxtea_encrypt_uint32进行xxtea加密,再int32转回char返回

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
_BYTE *__fastcall encrypt(const unsigned __int8 *buffer, unsigned __int64 buffer_len, const unsigned __int8 *key, unsigned __int64 *Block_len)
{
_BYTE *result; // rax
unsigned int v5[4]; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 int32_len; // [rsp+30h] [rbp-20h] BYREF
_BYTE *v7; // [rsp+38h] [rbp-18h]
void *Block; // [rsp+40h] [rbp-10h]
int i; // [rsp+4Ch] [rbp-4h]

if ( buffer_len )
{
Block = str2long(buffer, buffer_len, &int32_len, 1);// int32 6个
if ( Block )
{
for ( i = 0; i <= 3; ++i )
v5[i] = (key[4 * i + 3] << 24) | (key[4 * i + 2] << 16) | *(unsigned __int16 *)&key[4 * i];
xxtea_encrypt_uint32((unsigned int *)Block, int32_len, v5);
v7 = long2str((const unsigned int *)Block, int32_len, Block_len, 0);// 变回字节长24
free(Block);
result = v7;
}
else
{
*Block_len = 0i64;
result = 0i64;
}
}
else
{
*Block_len = 0i64;
result = 0i64;
}
return result;
}

str2long。四个char合一块变成int32,因为flag长19字节所以需要(bytes_len >> 2) + ((bytes_len & 3) != 0)=5块,最后加了一块用来保留原始字节长度,一共6块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_DWORD *__fastcall str2long(const unsigned __int8 *bytes, unsigned __int64 bytes_len, unsigned __int64 *int32_len, int preserve_bytes_len)
{
_DWORD *res; // [rsp+20h] [rbp-20h]
size_t Count; // [rsp+28h] [rbp-18h]
unsigned __int64 v7; // [rsp+30h] [rbp-10h]
unsigned __int64 i; // [rsp+38h] [rbp-8h]

v7 = (bytes_len >> 2) + ((bytes_len & 3) != 0);// 要几个4b的块 5
Count = (preserve_bytes_len != 0) + v7; // 加一个数保留字节流长度
res = calloc(Count, 4ui64);
if ( !res )
return 0i64;
for ( i = 0i64; i < bytes_len; ++i )
res[i >> 2] |= bytes[i] << (8 * (i & 3));
if ( preserve_bytes_len )
res[v7] = bytes_len;
*int32_len = Count;
return res;
}

long2str。把int32拆成四个char,所以最后是24个char

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
_BYTE *__fastcall long2str(const unsigned int *Block, __int64 int32_len, unsigned __int64 *bytes_len, int flag)
{
_BYTE *result; // rax
_BYTE *bytes; // [rsp+28h] [rbp-28h]
unsigned int v6; // [rsp+34h] [rbp-1Ch]
unsigned __int64 Size; // [rsp+38h] [rbp-18h]
unsigned __int64 i; // [rsp+40h] [rbp-10h]
unsigned __int64 v9; // [rsp+48h] [rbp-8h]

Size = 4 * int32_len;
v9 = 4 * int32_len;
if ( flag )
{
if ( !int32_len )
{
*bytes_len = 0i64;
return 0i64;
}
v6 = Block[int32_len - 1];
if ( Size < v6 || v6 < Size - 3 )
{
*bytes_len = 0i64;
return 0i64;
}
v9 = v6;
}
bytes = malloc(Size);
if ( bytes )
{
for ( i = 0i64; i < Size; ++i )
bytes[i] = Block[i >> 2] >> (8 * (i & 3));
*bytes_len = v9;
result = bytes;
}
else
{
*bytes_len = 0i64;
result = 0i64;
}
return result;
}

xxtea_encrypt_uint32。标准的xxtea,delta是-= 0x61C88647也是标准的,轮数是0x34 / len + 6=14

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
bool __fastcall xxtea_encrypt_uint32(unsigned int *x, unsigned __int64 len, const unsigned int *a3)
{
int v3; // eax
bool ret_flag; // al
unsigned int *v5; // rax
unsigned int *v6; // rax
unsigned int v7; // [rsp+8h] [rbp-18h]
int rounds; // [rsp+Ch] [rbp-14h]
unsigned __int64 i; // [rsp+10h] [rbp-10h]
unsigned int v10; // [rsp+18h] [rbp-8h]
unsigned int v11; // [rsp+1Ch] [rbp-4h]

if ( len > 1 )
{
v11 = x[len - 1];
v10 = 0;
rounds = 0x34 / len + 6;
while ( 1 )
{
v3 = rounds--;
ret_flag = v3 != 0;
if ( !ret_flag )
break;
v10 -= 0x61C88647;
v7 = (v10 >> 2) & 3;
for ( i = 0i64; i < len - 1; ++i )
{
v5 = &x[i];
*v5 += ((x[i + 1] ^ v10) + (v11 ^ a3[i & 3 ^ v7])) ^ (((4 * x[i + 1]) ^ (v11 >> 5))
+ ((x[i + 1] >> 3) ^ (16 * v11)));
v11 = *v5;
}
v6 = &x[len - 1];
*v6 += ((*x ^ v10) + (v11 ^ a3[i & 3 ^ v7])) ^ (((4 * *x) ^ (v11 >> 5)) + ((*x >> 3) ^ (16 * v11)));
v11 = *v6;
}
}
return ret_flag;
}

逆向

加密:

输入19个字节,先str2long变成5个int32,加上一个“19”,共6块,然后xxxtea加密,再long2str,6块变24字节再用perm置换,异或,得到target

所以解密:

对24字节的target先异或,逆perm,然后str2long得到6块,然后xxxtea解密,再long2str得到24字节,前19字节就是flag

异或&逆置换

为了方便理解用new和old来区分一下,new表示已经经过运算发生了改变

1
2
3
4
5
6
7
8
9
10
for ( i = 0i64; i <= 23; ++i )
old[i] = ori[main::perm[i]];
for ( j = 1i64; j <= 23; ++j )
{
tmp = old[j];
v10 = j / 3;
for ( k = 0i64; k < v10; ++k )
tmp ^= new[k];
new[j] = tmp;
}

逆向就是:

1
2
3
4
5
6
7
8
9
10
for (int i = 1; i < n; i++){
uint8_t tmp = 0;
for (int j = 0; j < i / 3; j++){
tmp ^= new[j];
}
old[i] = new[i] ^ tmp;
}
for (int i = 0; i < n; i++) {
ori[i] = old[inv_perm[i]];
}

xxtea解密

抄板子了。不过有个Key的问题,只定义了8个但是用了16

定义:

1
2
3
unsigned __int8 Key[8]; // [rsp+40h] [rbp-40h] BYREF
__int64 v8; // [rsp+48h] [rbp-38h]
char Buffer[128]; // [rsp+50h] [rbp-30h] BYREF

初始化:

1
2
3
4
5
v8 = 0i64;
*(_QWORD *)Key = (unsigned __int8)Buffer[0];
Key[1] = Buffer[1];
Key[2] = Buffer[2];
Key[3] = Buffer[3];

后续用到key[0-15]:

1
2
3
for ( i = 0; i <= 3; ++i )
v5[i] = (key[4 * i + 3] << 24) | (key[4 * i + 2] << 16) | *(unsigned __int16 *)&key[4 * i];
xxtea_encrypt_uint32((unsigned int *)Block, int32_len, v5);

key[0-4]是buffer前4字节,猜是flag,后面就落到v8里了,可能是ida没识别出来吧,v8应该是跟key一块的。v8有初始化为0,所以后边都是0

str2long&long2str

直接魔改一下用

target和perm数组ida里粘

1
2
3
4
5
6
7
8
.rdata:0000000000405000 _ZZ4mainE4perm  dd 2, 0, 3, 1, 6, 4, 7, 5, 0Ah, 8, 0Bh, 9, 0Eh, 0Ch, 0Fh
.rdata:0000000000405000 ; DATA XREF: main+168↑o
.rdata:0000000000405000 dd 0Dh, 12h, 10h, 13h, 11h, 16h, 14h, 17h, 15h
.rdata:0000000000405060 ; _BYTE main::target[24]
.rdata:0000000000405060 _ZZ4mainE6target db 0EEh, 56h, 9Dh, 6Bh, 2, 63h, 78h, 75h, 5, 42h, 70h
.rdata:0000000000405060 ; DATA XREF: main+23F↑o
.rdata:0000000000405060 db 9Ch, 63h, 0D7h, 0DCh, 0D7h, 1Ah, 0B5h, 0EAh, 0Dh, 0C6h
.rdata:0000000000405060 db 8Dh, 97h, 5Dh

题解

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
170
171
172
173
174
175
176
177
178
#include <cstdio>
#include <stdint.h>
#include <cstdlib>
#include <cstring>
#define DELTA 0x9e3779b9

static inline uint32_t MX_calc(uint32_t z, uint32_t y, uint32_t sum, const uint32_t key[4], unsigned p, unsigned e) {
return (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z));
}
/* 解密:out[] = decrypt of in[] 需要知道加密时的轮数(或再算一遍) */
void xxtea_decrypt(const uint32_t *in, int n, int rounds, const uint32_t key[4], uint32_t *out)
{
uint32_t y, z, sum;
unsigned cycle;

sum = rounds * DELTA;
// sum = 0 - (rounds-1) * DELTA;
memcpy(out, in, sizeof(uint32_t) * n);
y = out[0];

while (rounds--) {
cycle = (sum >> 2) & 3;

for (int idx = n - 1; idx > 0; idx--) {
z = out[idx - 1];
out[idx] -= MX_calc(z, y, sum, key, idx, cycle);
y = out[idx];
}
z = out[n - 1];
out[0] -= MX_calc(z, y, sum, key, 0, cycle);
y = out[0];
sum -= DELTA;
}
}

uint32_t* __fastcall str2long(const unsigned __int8 *bytes, unsigned __int64 bytes_len, unsigned __int64 *int32_len, int preserve_bytes_len)
{
size_t Count; // [rsp+28h] [rbp-18h]
unsigned __int64 v7; // [rsp+30h] [rbp-10h]
unsigned __int64 i; // [rsp+38h] [rbp-8h]

v7 = (bytes_len >> 2) + ((bytes_len & 3) != 0);// 要几个4b的块
Count = (preserve_bytes_len != 0) + v7; // 加一个数保留字节流长度
uint32_t* res = static_cast<uint32_t*>(malloc(Count * sizeof(uint32_t)));
if ( !res )
return 0;
memset(res, 0, Count * sizeof(uint32_t));
for ( i = 0; i < bytes_len; ++i )
res[i >> 2] |= (uint32_t)bytes[i] << (8 * (i & 3));
if ( preserve_bytes_len )
res[v7] = bytes_len;
*int32_len = Count;
// printf("int32_len=%llu\n", *int32_len);
return res;
}

uint8_t* __fastcall long2str(const unsigned int *Block, __int64 int32_len, unsigned __int64* bytes_len, int flag)
{
uint8_t *result; // rax
unsigned int v6; // [rsp+34h] [rbp-1Ch]
unsigned __int64 Size; // [rsp+38h] [rbp-18h]
unsigned __int64 i; // [rsp+40h] [rbp-10h]
unsigned __int64 v9; // [rsp+48h] [rbp-8h]

Size = 4 * int32_len;
v9 = 4 * int32_len;

uint8_t* bytes = static_cast<uint8_t*>(malloc(Size * sizeof(uint8_t)));
if ( bytes )
{
*bytes_len = 24;
printf("int32_len=%llu, bytes_len=%llu\n", int32_len, *bytes_len);
for ( i = 0; i < Size; ++i )
bytes[i] = Block[i >> 2] >> (8 * (i & 3u));
result = bytes;
}
else
{
*bytes_len = 0;
result = 0;
}
return result;
}

void printX(uint32_t *data, int n){
for(int i=0;i<n;i++){
printf("0x%08X ",data[i]);
}
printf("\n");
}
void print(uint32_t *data, int n){
for(int i=0;i<n;i++){
printf("%d ",data[i]);
}
printf("\n");
}

int main()
{
uint32_t plaintext[24];
// uint32_t key[4]= {0x67616c66,0x67616c66, 0, 0};
uint32_t key[4]= {0x67616c66, 0, 0, 0};

/* 置换表 */
static const unsigned int perm[24] = {
2, 0, 3, 1, 6, 4, 7, 5,
0xA, 8, 0xB, 9, 0xE, 0xC, 0xF, 0xD,
0x12, 0x10, 0x13, 0x11, 0x16, 0x14, 0x17, 0x15};

unsigned int inv_perm[24];
for ( int i = 0; i < 24; i++ )
{
inv_perm[perm[i]] = i;
// printf("%d ", inv_perm[perm[i]]);
}

/* 目标密文(24 字节) */
uint8_t target[24] = {
0xEE, 0x56, 0x9D, 0x6B, 0x02, 0x63, 0x78, 0x75,
0x05, 0x42, 0x70, 0x9C, 0x63, 0xD7, 0xDC, 0xD7,
0x1A, 0xB5, 0xEA, 0x0D, 0xC6, 0x8D, 0x97, 0x5D
};
uint8_t v5[24];
int n = 24;

v5[0] = target[0];
for (int i = 1; i < n; i++){
uint8_t tmp = 0;
for (int j = 0; j < i /3; j++){
tmp ^= target[j];
}
v5[i] = target[i] ^ tmp;
}
unsigned char Block[24];
printf("Block数据:");
for (int i = 0; i < n; i++) {
Block[i] = v5[inv_perm[i]];
printf("%02X ", Block[i]);
}

// perm
// for (int i = 0; i < n; ++i )
// v5[i] = Block[perm[i]];
// for (int j = 1; j < n; ++j )
// {
// int tmp = v5[j];
// int v10 = j / 3;
// for (int k = 0; k < v10; ++k )
// tmp ^= v5[k];
// v5[j] = tmp;
// // printf("%02X ", v5[j]);
// }
// printf("\n");
// printf("还原后的数据v5:");
// for (int i = 0; i < n; i++) {
// printf("%02X ", v5[i]);
// if ((i % 8) == 7) printf("\n");
// }
// printf("\n");


long long unsigned target_len;
uint32_t* ciphertext = str2long((const uint8_t*)Block, 24, &target_len, 0);
printf("密文数据:");
printX(ciphertext, target_len);

xxtea_decrypt(ciphertext, target_len, 6 + 52/target_len, key, plaintext);
printf("解密后的数据:");
printX(plaintext, target_len);

long long unsigned bytes_len = 0;
uint8_t* bytes = long2str(plaintext, target_len, &bytes_len, 0);
for (int i = 0; i < bytes_len; i++)
printf("%c", bytes[i]);

return 0;
}