cm1

jadx打开,里面有两个main函数,发现用了一个函数对文件进行了加密

image-20250804170802834

加密函数里面长这样,好丑我滴妈

image-20250804170802834

我在网上看到有人用jeb反汇编可以,所以特地下载了一个jeb,可以发现里面的逻辑是1024一组,然后和key异或(⊙o⊙)!

image-20250804182304648

1
2
3
4
5
6
7
with open("ooo", "rb") as f:
content = list(f.read())
key = b"vn2022"
for i in range(len(content)):
content[i] ^= key[(i % 1024) % 6]
with open("one", "wb") as f:
f.write(bytes(content))

这个是dex文件,打开后在haha函数里面发现是xxtea加密,没有魔改,加密数组和密钥也都有

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

void encrypt(unsigned int * v, int n, unsigned int * key) {
unsigned int rounds = (52 / n) + 6;
unsigned int sum = 0, y = 0;
unsigned int z = v[n - 1];
unsigned int p;
while (rounds > 0) {
sum -= 1640531527;
unsigned int e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) {
y = v[p + 1];
v[p] += ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
z = v[p];
}
y = v[0];
v[n - 1] += ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
z = v[n - 1];
rounds--;
}
}

void decrypt(unsigned int* v, int n, unsigned int* key)
{
unsigned int rounds = (52 / n) + 6;
unsigned int sum =0 - (1640531527 * rounds), z = 0;
unsigned int p;
unsigned int y = v[0];
while (rounds > 0) {
unsigned int e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = v[p - 1];
v[p] -= ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
y = v[p];
}
z = v[n - 1];
v[0] -= ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
y = v[0];
sum += 1640531527;
rounds--;
}
}

int main() {
char c[] = { 68, 39, -92, 108, -82, -18, 72, -55, 74, -56, 38, 11, 60, 84, 97, -40, 87, 71, 99, -82, 120, 104, 47, -71, -58, -57, 0, 33, 42, 38, -44, -39, -60, 113, -2, 92, -75, 118, -77, 50, -121, 43, 32, -106 };
unsigned int key[] = { 1349530696, 1314283353, 558257219, 1333153569 };
decrypt((unsigned int *)c, 11, key);
return 0;
}

反汇编dex

DEX文件是Android平台上的可执行文件格式,存放着APK的编译后的字节码。

①转换成jar放到JD-GUI

②直接放到jadx

login

做完感觉收获满满的一道题o( ̄▽ ̄)ブ

首先我们拿到文件,里面有两个elf,执行一下会一开始没什么头脑,后来知道是soket技术

在网上找了资料稍微看了一下,其实学过计网了大概都能懂

Socket 编程详解(含 TCP 和 UDP 示例) - kyle_7Qc - 博客园

image-20250805212408385

理解完他的一个流程程序就很清晰了,这个是check服务端主函数里面最重要的一个

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
unsigned __int64 __fastcall sub_405F6A(unsigned int a1, __int64 a2, __int64 a3)
{
__int64 v3; // rax
__int64 v4; // rax
__int64 v6; // rax
__int64 v7; // rax
unsigned __int64 result; // rax
int v10; // [rsp+28h] [rbp-898h]
int v11; // [rsp+28h] [rbp-898h]
int v12; // [rsp+28h] [rbp-898h]
int v13; // [rsp+2Ch] [rbp-894h]
char v14[32]; // [rsp+30h] [rbp-890h] BYREF
char v15[32]; // [rsp+50h] [rbp-870h] BYREF
char v16[48]; // [rsp+70h] [rbp-850h] BYREF
char v17[64]; // [rsp+A0h] [rbp-820h] BYREF
__int64 password[126]; // [rsp+E0h] [rbp-7E0h] BYREF
__int64 token[125]; // [rsp+4D0h] [rbp-3F0h] BYREF
unsigned __int64 v20; // [rsp+8B8h] [rbp-8h]

v20 = __readfsqword(0x28u);
memset(password, 0, 1000);
memset(token, 0, sizeof(token));
strcpy(v14, "Please enter the token: ");
strcpy(v15, "Please input the password: ");
strcpy(v17, "Login successful.\nNow, you can enter flag to verify: ");
strcpy(v16, "Forbidden, wrong token or password.\n");
sys_sendto(a1, "1", 1LL, 0LL);
v3 = strlen(v14);
sys_sendto(a1, v14, v3, 0LL);
v10 = sys_recvfrom(a1, token, 1000LL, 0LL);
if ( v10 >= 0 )
{
*((_BYTE *)token + v10) = 0;
sys_sendto(a1, "1", 1LL, 0LL);
v4 = strlen(v15);
sys_sendto(a1, v15, v4, 0LL);
v11 = sys_recvfrom(a1, password, 1000LL, 0LL);
if ( v11 >= 0 )
{
*((_BYTE *)password + v11) = 0;
if ( (unsigned int)RSA(token) && (unsigned int)sub_405BDE(password) )
{
sys_sendto(a1, "2", 1LL, 0LL);
v6 = strlen(v17);
sys_sendto(a1, v17, v6, 0LL);
v12 = sys_recvfrom(a1, byte_60B6C0, 1000LL, 0LL);
if ( v12 >= 0 )
{
byte_60B6C0[v12] = 0;
sub_4058DB(byte_60B6C0);
v13 = clone();
if ( v13 <= 0 )
{
if ( !v13 )
{
sub_406444(a1, a2, a3, token, password);
exit(0LL);
}
exit(0LL);
}
sys_close(a1);
}
}
else
{
sys_sendto(a1, "0", 1LL, 0LL);
v7 = strlen(v16);
sys_sendto(a1, v16, v7, 0LL);
}
}
}
sys_close(a1);
result = __readfsqword(0x28u) ^ v20;
if ( result )
sub_55CC60();
return result;
}

如果动调的话,到accept那运行客户端文件就行,好像服务器端会监听23946端口,所以我的客户端在23946运行

然后动调的话,我换成了12345端口,但是一直无法准确的动调到内部的函数::>_<::不会调

image-20250802103650805

随后先进行静态分析,理一下逻辑

先解输入的token,这个是RSA,特征值0x10001,还挺好认的

常规操作,n用网站求出p、q,然后求出token

factordb.com

1
2
3
4
5
6
7
8
9
10
11
import gmpy2
p=98197216341757567488149177586991336976901080454854408243068885480633972200382596026756300968618883148721598031574296054706280190113587145906781375704611841087782526897314537785060868780928063942914187241017272444601926795083433477673935377466676026146695321415853502288291409333200661670651818749836420808033
q=133639826298015917901017908376475546339925646165363264658181838203059432536492968144231040597990919971381628901127402671873954769629458944972912180415794436700950304720548263026421362847590283353425105178540468631051824814390421486132775876582962969734956410033443729557703719598998956317920674659744121941513
e=0x10001
n=p*q
c=b'By reading we enrich the mind, by conversation we polish it.'
c = int.from_bytes(c, 'little')
print(c)
d=gmpy2.invert(e,(p-1)*(q-1))
m=pow(c,d,n)
print(m)

下一个是password,里面是一个矩阵的运算,没听过hill算法

感觉比较难想出来的事这边的rand函数,去看了一下rand函数的源码,记住了::>_<::

[原创]linux C rand(),srand()函数算法-编程技术-看雪-安全社区|安全招聘|kanxue.com

image-20250802101535838

剩下的即是不知道是hill算法也没关系,逆起来也不难,先把矩阵提出来

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

int main() {
uint64_t v6[] = {
0xA34C0F7A2E25DB71LL,
0x7AA91B6E29AA226ALL,
0x56AA0EF0802F278ALL,
0x9AF6F2A9005859F7LL,
};
for (int i = 0; i < 4; i++) {
uint8_t *bytes = (uint8_t *)&v6[i];
for (int j = 0; j < 8; j += 2) {
printf("%d %d ", bytes[j], bytes[j+1]);
}
printf("\n");
}
return 0;
}

在Linux下拿到随机数,因为windows下和Linux下随机数初始化不同

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
for(int i=0;i<48;i++){
printf("%d, ",rand()%255);
}
return 0;
}

image-20250731171100345

然后写个脚本

得到的矩阵逆矩阵和随机数矩阵相乘

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
import numpy as np
import galois

GF = galois.GF(257)

x = GF([
[113, 219, 37, 46, 122, 15],
[76, 163, 106, 34, 170, 41],
[110, 27, 169, 122, 138, 39],
[47, 128, 240, 14, 170, 86],
[247, 89, 88, 0, 169, 242],
[246, 154, 78, 28, 72, 201]
])

enc = GF([
[163, 151, 162, 85, 83, 190],
[241, 252, 249, 121, 107, 82],
[20, 19, 233, 226, 45, 81],
[142, 31, 86, 8, 87, 39],
[167, 5, 212, 208, 82, 130],
[119, 117, 27, 153, 74, 237]
])

# flag = x^{-1} * en
x_inv = np.linalg.inv(x)
flag = x_inv @ enc
print(flag)
hex_str = ''.join([f"{int(x):02x}" for x in flag.flatten()])
print(hex_str)

最后得到的字符串就是password

接下来输入之后就是flag了

里面是个AES加密,我一直以为AES不会魔改内部魔改这么狠但是这道就( ⊙ o ⊙ )啊!

AES加解密算法C实现 - 陌默安 - 博客园

首先是s盒加密解密的s盒交换了

然后密钥扩展的时候异或的轮常量改变了

接着是前面有一个明文块被异或了,搜了一下发现是类似于CBC模式,有一个iv

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
unsigned __int64 __fastcall sub_4055C0(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
unsigned __int64 result; // rax
int i; // [rsp+28h] [rbp-38h]
int j; // [rsp+28h] [rbp-38h]
int m; // [rsp+28h] [rbp-38h]
int k; // [rsp+2Ch] [rbp-34h]
__int64 v10[5]; // [rsp+30h] [rbp-30h] BYREF
unsigned __int64 v11; // [rsp+58h] [rbp-8h]
__int64 savedregs; // [rsp+60h] [rbp+0h] BYREF

v11 = __readfsqword(0x28u);
v10[0] = 0LL;
v10[1] = 0LL;
sub_404C1A(a1, a2, a4); // 明文块的前置操作
sub_40554A(a1, (__int64)v10, a2); // 转置
addroundkey(a1, (__int64)v10, a1);
for ( i = 1; ; ++i )
{
SubBytes(a1, (__int64)v10);
ShiftRows(a1, (__int64)v10);
if ( i == 10 )
break;
MixCloumns(a1, (__int64)v10); // 1字节8位的的计算
addroundkey(a1, (__int64)v10, 16 * i + a1);
}
addroundkey(a1, (__int64)v10, a1 + 160);
for ( j = 0; j <= 3; ++j )
{
for ( k = 0; k <= 3; ++k ) // shiftRows
*((_BYTE *)&savedregs + 4 * j + k - 32) = *((_BYTE *)&savedregs + 4 * k + j - 48);
}
for ( m = 0; m <= 15; ++m )
sub_4FC580(a3 + 2 * m, (__int64)"%02x", *((unsigned __int8 *)&v10[2] + m));
result = __readfsqword(0x28u) ^ v11;
if ( result )
sub_55CC60();
return result;
}

这里是真的不好看,有源码也没用,而且这里的数据类型都是_BYTE,一字节一字节计算,标准的是4字节

贴一下脚本

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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#include <stdio.h>
#include <string.h>
#include "aes.h"

//解密的s盒
unsigned char sBox[] =
{ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, /*0*/
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, /*1*/
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, /*2*/
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, /*3*/
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, /*4*/
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, /*5*/
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, /*6*/
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, /*7*/
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, /*8*/
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, /*9*/
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, /*a*/
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, /*b*/
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, /*c*/
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, /*d*/
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, /*e*/
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16 /*f*/
};
//生成轮密钥的轮常量
const unsigned char rcon[11] =
{
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
};
//列混淆用的矩阵
static const int deColM[4][4] =
{
0xE, 0xB, 0xD, 0x9,
0x9, 0xE, 0xB, 0xD,
0xD, 0x9, 0xE, 0xB,
0xB, 0xD, 0x9, 0xE
};

static unsigned char W[44];

void ExtendKey(unsigned char * key);
void AddRoundKey(unsigned char (*stateMatrix)[4], unsigned char * key);
void DeShiftRows(unsigned char (*stateMatrix)[4]);
void DeSubBytes(unsigned char (*stateMatrix)[4]);
void DeMixColumns(unsigned char (*stateMatrix)[4]);
void XorIv(unsigned char * plainText, unsigned char * iv);
void AESDecode(unsigned char * enc, unsigned char * plainText, unsigned char * key);

int main(void)
{
unsigned char key[] = {50, 48, 7, 54, 106, 55, 120, 49, 72, 57, 66, 57, 20, 49, 213, 50};
unsigned char iv[] = {98, 54, 249, 56, 66, 48, 195, 49, 106, 53, 72, 56, 52, 53, 84, 52, 41, 52, 81, 54, 21, 57, 210, 56, 210, 57, 32, 49, 185, 50, 46, 48};
unsigned char enc[] = {254, 249, 231, 62, 246, 161, 35, 204, 87, 97, 193, 21, 119, 251, 156, 187, 202, 47, 177, 232, 79, 217, 7, 216, 12, 107, 234, 207, 232, 66, 162, 250};
unsigned char plainText[32] = { 0 };
int i;

for ( i = 0; i < 32; i += 16)
{
AESDecode(enc + i, plainText + i, key);
XorIv(plainText + i, iv + i);
}
for ( i = 0; i < 32; i++ )
printf("%c", plainText[i]);

return 0;
}

void XorIv(unsigned char * plainText, unsigned char * iv)
{
int i;

for ( i = 0; i < 16 ; i++ )
plainText[i] ^= iv[i];
}

void GetStateMatrix(unsigned char (*stateMatrix)[4], unsigned char * enc)
{
int i, j;

for ( i = 0; i < 4; i++ )
for ( j = 0; j < 4; j++ )
stateMatrix[j][i] = enc[i * 4 + j];
}

void PutStateMatrix(unsigned char * plainText, unsigned char (*stateMatrix)[4])
{
int i, j;

for ( i = 0; i < 4; i++ )
for ( j = 0; j < 4; j++ )
plainText[i * 4 + j] = stateMatrix[j][i];
}

void AESDecode(unsigned char * enc, unsigned char * plainText, unsigned char * key)
{
int i, j;
ExtendKey(key);

unsigned char stateMatrix[4][4] = { 0 };
GetStateMatrix(stateMatrix, enc);

AddRoundKey(stateMatrix, W + i * 16);
for ( i=10; ; i-- )
{
DeShiftRows(stateMatrix);
DeSubBytes(stateMatrix);
AddRoundKey(stateMatrix, W + i * 16);
if ( i == 0 )
break;
DeMixColumns(stateMatrix);
}

PutStateMatrix(plainText, stateMatrix);
}

static int GFMul2(int s)
{
int result = s << 1;
int a7 = result & 0x00000100; //判断位移后的那位是否为 1

if ( a7 != 0 )
{
result = result & 0x000000FF;
result = result ^ 0x1B; //矩阵乘法的特殊性
}

return result;
}

static int GFMul3(int s)
{
return GFMul2(s) ^ s;
}

static int GFMul4(int s)
{
return GFMul2(GFMul2(s));
}

static int GFMul8(int s)
{
return GFMul2(GFMul4(s));
}

static int GFMul9(int s)
{
return GFMul8(s) ^ s;
}

static int GFMul11(int s)
{
return GFMul9(s) ^ GFMul2(s);
}

static int GFMul12(int s)
{
return GFMul8(s) ^ GFMul4(s);
}

static int GFMul13(int s)
{
return GFMul12(s) ^ s;
}

static int GFMul14(int s)
{
return GFMul12(s) ^ GFMul2(s);
}


/**
* GF上的二元运算
*/
static int GFMul(int n, int s)
{
int result;

if ( n == 1 )
result = s;
else if ( n == 2 )
result = GFMul2(s);
else if ( n == 3 )
result = GFMul3(s);
else if ( n == 9 )
result = GFMul9(s);
else if ( n == 0xB )
result = GFMul11(s);
else if ( n == 0xD )
result = GFMul13(s);
else if ( n == 0xE )
result = GFMul14(s);

return result;
}

void DeMixColumns(unsigned char (*stateMatrix)[4])
{
unsigned char tmpArray[4][4];
int i, j;

for ( i = 0; i < 4; i++ )
for ( j = 0; j < 4; j++ )
tmpArray[i][j] = stateMatrix[i][j];

for ( i = 0; i < 4; i++ )
for ( j = 0; j < 4; j++ )
stateMatrix[i][j] = GFMul(deColM[i][0], (int)tmpArray[0][j]) ^
GFMul(deColM[i][1], (int)tmpArray[1][j]) ^
GFMul(deColM[i][2], (int)tmpArray[2][j]) ^
GFMul(deColM[i][3], (int)tmpArray[3][j]);
}

void DeSubBytes(unsigned char (*stateMatrix)[4])
{
int i, j;

for ( i = 0; i < 4; i++ )
for ( j = 0; j < 4; j++ )
stateMatrix[i][j] = sBox[stateMatrix[i][j]];
}

void DeShiftRows(unsigned char (*stateMatrix)[4])
{
int i, j, count, t;

for ( i = 1; i <= 3; i++ )
{
count = 0;
while ( count++ < i )
{
t = stateMatrix[i][3];
for ( j = 3; j >= 1; j-- )
stateMatrix[i][j] = stateMatrix[i][j - 1];
stateMatrix[i][0] = t;
}
}
}

void AddRoundKey(unsigned char (*stateMatrix)[4], unsigned char * key)
{
int i, j;

for ( i = 0; i <= 3; i++ )
for ( j = 0; j <= 3; j++ )
stateMatrix[i][j] ^= key[4 * j + i];
}

void ExtendKey(unsigned char * key)
{
unsigned char tmp[16];
int i, j;

for ( i = 0; i <= 3; i++ )
{
W[4 * i] = key[4 * i];
W[4 * i + 1] = key[4 * i + 1];
W[4 * i + 2] = key[4 * i + 2];
W[4 * i + 3] = key[4 * i + 3];
}

for ( j = 4; j < 44; j++ )
{
unsigned char v9 = W[4 * (j - 1)];
unsigned char v10 = W[4 * (j - 1) + 1];
unsigned char v11 = W[4 * (j - 1) + 2];
unsigned char v12 = W[4 * (j - 1) + 3];
if ( (j & 3) == 0 )
{
v10 = sBox[v11];
v11 = sBox[v12];
v12 = sBox[W[4 * (j - 1)]];
v9 = sBox[W[4 * (j - 1) + 1]] ^ rcon[j >> 2];
}
W[(4 * j)] = v9 ^ W[4 * (j - 4)];
W[(4 * j) + 1] = v10 ^ W[4 * (j - 4) + 1];
W[(4 * j) + 2] = v11 ^ W[4 * (j - 4) + 2];
W[(4 * j) + 3] = v12 ^ W[4 * (j - 4) + 3];
}
}

ECB和CBC

ECB 模式是最简单、最基础的加密模式,它不需要 IV。

ECB 模式将明文分割成一个个独立的 16 字节(128 位)数据块。然后,它使用相同的密钥独立地加密每一个数据块。

CBC 模式是解决 ECB 模式缺陷而设计的,它通过“链式”操作来引入随机性,所以必须使用 IV

在 CBC 模式中,每个明文块在加密前,会先与“前一个密文块”进行异或(XOR)运算。

Ci=EK(Pi⊕Ci−1)

  • Pi 是第 i 个明文块。
  • Ci−1 是第 i−1 个密文块。
  • EK 是使用密钥 K 的加密函数。
  • Ci 是第 i 个密文块。

CBC 模式的“链式”依赖关系带来了一个问题:第一个明文块 (P1) 没有“前一个密文块” (C0) 可以进行异或运算

为了解决这个问题,CBC 模式引入了一个初始向量 (IV)。IV 是一个与数据块大小相同的随机或伪随机值,它只在加密第一个明文块时使用。

C1=EK(P1⊕IV)

  • IV 必须是随机且不可预测的。
  • IV 不需要保密,但必须在解密时可用,通常会随密文一起发送。

这种设计确保了即使使用相同的密钥加密相同的明文,只要 IV 不同,最终得到的密文也会完全不同,从而隐藏了数据模式,大大增强了安全性

rand源码

image-20250802112937726

crackme

一开始在这下了断点,不行,有反调试,但是没找到是哪个函数

image-20250804161209348

后来知道在wrong上面有一个字符串,ZwSetInformationThread和NtSetInformationThread一样是用于反调试的函数

image-20250804161307982

把0x11修改成0x12,然后再次动调输入

主函数大体是这个意思

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
CWnd *v3; // ecx
const void *v4; // eax
const void *v5; // eax
int v7; // [esp+10h] [ebp-238h]
unsigned int Size; // [esp+18h] [ebp-230h]
CWnd *v9; // [esp+1Ch] [ebp-22Ch]
size_t pdwDataLen; // [esp+20h] [ebp-228h] BYREF
void *Buf1; // [esp+24h] [ebp-224h] BYREF
const void *v12; // [esp+28h] [ebp-220h] BYREF
BYTE *v13; // [esp+2Ch] [ebp-21Ch] BYREF
size_t dwDataLen; // [esp+30h] [ebp-218h] BYREF
size_t v15; // [esp+34h] [ebp-214h] BYREF
DWORD v16; // [esp+38h] [ebp-210h] BYREF
BYTE flag[260]; // [esp+3Ch] [ebp-20Ch] BYREF
char key[260]; // [esp+140h] [ebp-108h] BYREF

v9 = v3;
CWnd::UpdateData(v3, 1);
memset(key, 0, sizeof(key));
memset(flag, 0, sizeof(flag));
Size = sub_323E70(v9 + 216);
pdwDataLen = sub_323E70(v9 + 212);
dwDataLen = 0;
v15 = 0;
v16 = 0;
v4 = sub_322590(Size);
memmove(key, v4, Size);
v5 = sub_322590(pdwDataLen);
memmove(flag, v5, pdwDataLen);
if ( Size != 8 && pdwDataLen != 32 )
return printfwrong(v9);
sub_323510(key, Size >> 1, 0x8003u, &Buf1, &dwDataLen);// key的前4位md5
sub_323510(&key[4], Size >> 1, 0x8004u, &v12, &v15);// key后四位sha
sub_323510(key, Size, 0x8003u, &v13, &v16); // key的md5
v7 = memcmp(Buf1, v9 + 220, dwDataLen);
if ( memcmp(v12, v9 + 480, v15) )
return printfwrong(v9);
sub_3236E0(v13, v16, flag, &pdwDataLen, 0x104u);
if ( !memcmp(flag, v9 + 740, pdwDataLen) )
return sub_323840(v9, 0, 0, 0, 0, v7, 0);
else
return printfwrong(v9);
}

sub_323510和sub_3236E0里面都是系统函数查一查会发现是有关哈希加密的

ALG_ID (Wincrypt.h) - Win32 apps | Microsoft Learn

0x0000660e是AES-128

0x00008003是MD5

0x00008004是SHA

动调拿到key的值,然后在线网站解,key是NocTuRne,再去拿密文

image-20250804161907990

拿到密文,调用Windows Cryptoapi进行解密

这里要注意key要转成md5

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
#include<stdio.h>
#include <windows.h>
#include <wincrypt.h>
bool __stdcall sub_3236E0(BYTE *pbData, DWORD dwDataLen, BYTE *a3, DWORD *pdwDataLen)
{
BOOL v6; // [esp+4h] [ebp-18h]
HCRYPTKEY phKey; // [esp+Ch] [ebp-10h] BYREF
HCRYPTPROV phProv; // [esp+10h] [ebp-Ch] BYREF
HCRYPTHASH phHash; // [esp+14h] [ebp-8h] BYREF

phProv = 0;
phHash = 0;
phKey = 0;
v6 = CryptAcquireContextA(&phProv, 0, 0, 0x18u, 0xF0000000);
if ( v6 )
{
v6 = CryptCreateHash(phProv, 0x8003u, 0, 0, &phHash);
if ( v6 )
{
v6 = CryptHashData(phHash, pbData, dwDataLen, 0);
if ( v6 )
{
v6 = CryptDeriveKey(phProv, 0x660Eu, phHash, 1u, &phKey);
if ( v6 ){
v6 = CryptDecrypt(phKey, 0, 1, 0, a3, pdwDataLen);
printf("%s",a3);
}
}
}
}
if ( phKey )
CryptDestroyKey(phKey);
if ( phHash )
CryptDestroyHash(phHash);
if ( phProv )
CryptReleaseContext(phProv, 0);
return v6;
}
//key,0x10,enarry,0x20,0x104
int main(){
BYTE input[]={ 0x5B, 0x9C, 0xEE, 0xB2, 0x3B, 0xB7, 0xD7, 0x34, 0xF3, 0x1B,
0x75, 0x14, 0xC6, 0xB2, 0x1F, 0xE8, 0xDE, 0x33, 0x44, 0x74,
0x75, 0x1B, 0x47, 0x6A, 0xD4, 0x37, 0x51, 0x88, 0xFC, 0x67,
0xE6, 0x60};
BYTE key[]={0x5c,0x53,0xa4,0xa4,0x1d,0x52,0x43,0x7a,0x9f,0xa1,0xe9,0xc2,0x6c,0xa5,0x90,0x90};
DWORD keylen = 0x10;
DWORD len_input=0x20;
sub_3236E0(key, keylen, input, &len_input);

return 0;
}

ZwSetInformationThread

ZwSetInformationThread 是 Windows 内核中的一个 系统服务函数,属于 NT Native API(通过 ntdll.dll 导出)。它与 NtSetInformationThread 功能完全相同,但通常在内核模式(如驱动程序)中使用 Zw 前缀的版本,而在用户模式(如普通应用程序)中使用 Nt 前缀的版本。

ZwSetInformationThread用于修改线程的底层属性,常见的 ThreadInformationClass 参数包括:

信息类型(值) 作用
ThreadHideFromDebugger (0x11) 隐藏线程,使其对调试器不可见(反调试技术)
ThreadBreakOnTermination (0x1D) 线程终止时触发异常(用于监控线程生命周期)
ThreadPriority 设置线程优先级
ThreadAffinityMask 设置线程的 CPU 亲和性(绑定到特定核心)
ThreadImpersonationToken 设置线程的模拟令牌(用于权限提升/降级)

win32api

CryptAcquireContext函数获取CSP句柄

CryptCreateHash函数创建HASH对象,HASH算法设置

​ 0x0000660e是AES-128

​ 0x00008003是MD5

​ 0x00008004是SHA

CryptHashData函数计算用户传入数据的MD5值

CryptDeriveKey函数来派生密钥

FakePica

apk有壳,用BlackDex进行脱壳,然后共享文件夹拿到四个dex文件

我是四个文件都看了,在最后一个文件cookie_8836564.dex中MainActivity找到逻辑

里面是一个AES

image-20250805211138150

这个byte的最大值是127,写个脚本

嗯这次正好是CBC模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Cipher import AES  

password = [-40, 26, 95, -49, -40, -123, 72, -90, -100, -41, 122, -4, 25, -101, -58, 116]
email = [-114, 95, -37, 127, -110, 113, 41, 74, 40, 73, 19, 124, -57, -88, 39, -116, -16, -75, -3, -45, -73, -6, -104, -6, -78, 121, 110, 74, -90, -47, -28, -28]


def decrypt(text):
iv = b"0102030405060708"
key = b"picapicapicapica"
mode = AES.MODE_CBC

cryptor = AES.new(key, mode, iv)
plain_text = cryptor.decrypt(text)
print(bytes.decode(plain_text))


if __name__ == '__main__':
text = bytes([x & 0xff for x in password])
decrypt(text)
text = bytes([x & 0xff for x in email])
decrypt(text)

apk脱壳

没有任何技巧,脱完壳之后把存储的dex文件放到共享文件夹里

总结

login很有意思的一道题目O(∩_∩)O~~,但是就是调试不尽人意不知道怎么弄呢,下次如果有机会的话我也要试一试soket,写一下,看看能不能怎么写能让他对接准确跑起来

两道安卓题,学到了不少因为我做安卓题挺少的╮(╯▽╰)╭