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
public byte[] G(byte[] bArr) {
try
{
byte[] bArr2
=
new byte[
16
];
/
/
随机生成
16
个字节
new Random().nextBytes(bArr2);
/
/
实例化一个加密对象
Cipher cipher
=
Cipher.getInstance(
"AES/CBC/NoPadding"
);
/
/
传入密钥和随机数
cipher.init(
1
, new SecretKeySpec(
"6SvMO4msTk1OqA8n"
.getBytes(),
"AES"
), new IvParameterSpec(bArr2));
/
/
传入明文加密并得到返回的密文
byte[] doFinal
=
cipher.doFinal(bArr);
/
/
AES 加密后的数据长度是不变的,这里其实就是创建了一个长度
32
的字节数组
byte[] bArr3
=
new byte[doFinal.length
+
16
];
/
/
从最开始申请的随机数的下标
8
位开始拷贝,拷贝
8
个长度,放到 barr3 的起始位置
System.arraycopy(bArr2,
8
, bArr3,
0
,
8
);
/
/
把 AES 加密后的全部数据拷贝到 barr3,从barr3的下标
8
开始,其实就是接着上边结束位置
System.arraycopy(doFinal,
0
, bArr3,
8
, doFinal.length);
/
/
从最开始申请的随机数的下标
0
位开始拷贝,拷贝
8
个长度,从barr3的下标 doFinal.length
+
8
开始,其实就是接着上边结束位置
System.arraycopy(bArr2,
0
, bArr3, doFinal.length
+
8
,
8
);
/
/
小结一下,bArr3 的组成如下所示:
/
/
bArr3
=
bArr2[
8
-
15
]
+
aes(明文)
+
bArr2[
0
-
7
]
return
bArr3;
} catch (Exception e2) {
e2.printStackTrace();
return
null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private byte[] F(byte[] bArr) {
try
{
byte[] bArr2
=
new byte[
16
];
/
/
把密文的
0
-
7
位拷贝到bArr2的
8
-
15
位
System.arraycopy(bArr,
0
, bArr2,
8
,
8
);
/
/
把密文的末端
8
位拷贝到bArr2的
0
-
7
位
System.arraycopy(bArr, bArr.length
-
8
, bArr2,
0
,
8
);
/
/
实例化一个加密对象
Cipher cipher
=
Cipher.getInstance(
"AES/CBC/NoPadding"
);
/
/
设置模式为解密,同时传入密钥和随机数的密文
cipher.init(
2
, new SecretKeySpec(
"6SvMO4msTk1OqA8n"
.getBytes(),
"AES"
), new IvParameterSpec(bArr2));
/
/
申请
16
个字节的空间
byte[] bArr3
=
new byte[bArr.length
-
16
];
/
/
密文的下标
8
开始拷贝,拷贝
16
个,也就是将密文的[
7
-
23
]位拷贝到bArr3
System.arraycopy(bArr,
8
, bArr3,
0
, bArr.length
-
16
);
/
/
返回解密后的明文
return
cipher.doFinal(bArr3);
} catch (Exception e2) {
e2.printStackTrace();
return
null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
jbyteArray __cdecl Java_com_test_pac_demo_MainActivity_M1(JNIEnv
*
env, jobject a2, BYTE
*
bytes_)
{
jbyteArray v4;
/
/
esi
int
v6;
/
/
[esp
-
14h
] [ebp
-
1030h
]
unsigned
int
user_name_length;
/
/
[esp
+
0h
] [ebp
-
101Ch
]
jbyte
*
byte_user_name;
/
/
[esp
+
4h
] [ebp
-
1018h
]
char dest[
4096
];
/
/
[esp
+
8h
] [ebp
-
1014h
] BYREF
unsigned
int
v10;
/
/
[esp
+
1008h
] [ebp
-
14h
]
v10
=
__readgsdword(
0x14u
);
user_name_length
=
(
*
env)
-
>GetArrayLength(env, bytes_);
byte_user_name
=
(
*
env)
-
>GetByteArrayElements(env, bytes_,
0
);
memset(dest,
0
, sizeof(dest));
v6
=
sub_46660((
int
)byte_user_name, user_name_length, dest);
v4
=
(
*
env)
-
>NewByteArray(env, v6);
(
*
env)
-
>SetByteArrayRegion(env, v4,
0
, v6, dest);
(
*
env)
-
>ReleaseByteArrayElements(env, bytes_, byte_user_name,
0
);
return
v4;
}
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
int
__cdecl sub_46660(BYTE
*
byte_user_name, unsigned
int
user_name_length, char
*
dest)
{
......
canary
=
__readgsdword(
0x14u
);
/
/
开启canary保护
memset(&key_data[
32
],
0
,
32
);
v3
=
-
32
;
do
{
v4
=
lrand48();
/
/
产生一个正的长整型随机数
key_data[v3
+
+
+
64
]
=
v4
+
v4
/
255
;
/
/
为 key_data[
32
-
63
] 位赋值随机数
}
while
( v3 );
memset(key_data,
0
,
32
);
v5
=
-
32
;
do
{
v_eax
=
lrand48();
key_data[v5
+
+
+
32
]
=
v_eax
+
v_eax
/
255
;
/
/
为 key_data[
0
-
31
] 位赋值随机数
}
while
( v5 );
memset(
hash
,
0
, sizeof(
hash
));
hmac_ctx_hash(&key_data[
32
], key_data,
hash
);
/
/
参数
1
:data 参数
2
:key 参数
3
:dest
strcpy((char
*
)const_key,
"123456awxzcdfqwqt2wetbwerw"
);
memset(encrypt_data,
0
, sizeof(encrypt_data));
evp_encrypt((
int
)
hash
, (
int
)&
hash
[
32
],
12
, const_key,
27
, (
int
)byte_user_name, user_name_length, encrypt_data);
/
/
加密用户名
memset(encrypt_data2,
0
, sizeof(encrypt_data2));
*
(__m128i
*
)hash_temp
=
_mm_load_si128((const __m128i
*
)&
hash
[
48
]);
/
/
从
hash
的第
48
个字节开始加载
128bits
,也就是加载
16
个字节
__memcpy_chk((
int
)encrypt_data2, (
int
)encrypt_data, user_name_length
+
16
,
0x2800
);
if
( !(((
int
)(user_name_length
+
16
) <
0
) ^ __OFADD__(
16
, user_name_length) | (user_name_length
=
=
-
16
)) )
/
/
用户名长度为
16
,所以
1
^
0
|
0
=
1
/
/
__OFADD__ 测试两数相加后是否溢出
{
v_esi
=
0
;
v8
=
user_name_length
+
16
;
/
/
v8
=
32
if
( user_name_length >
=
4294967280
)
/
/
/
/
检查用户名长度是否大于
4294967280
goto LABEL_15;
if
( user_name_length
+
15
>
15
)
/
/
检查用户名长度是否大于
0
,此处跳转至LABEL15,感谢手下留情
goto LABEL_15;
v_esi
=
v8 &
0xFFFFFFF0
;
si128
=
_mm_load_si128((const __m128i
*
)(&(&off_1A25DC)[
-
11535
]
+
1
));
v10
=
_mm_load_si128((const __m128i
*
)(&off_1A25DC
-
92275
));
v11
=
_mm_load_si128((const __m128i
*
)(&(&off_1A25DC)[
-
11534
]
+
1
));
v12
=
_mm_load_si128((const __m128i
*
)(&off_1A25DC
-
92267
));
v13
=
0
;
v34
=
*
(__m128i
*
)hash_temp;
v31
=
*
(__m128i
*
)(&(&off_1A25DC)[
-
11533
]
+
1
);
v33
=
*
(__m128i
*
)(&off_1A25DC
-
92259
);
v32
=
_mm_load_si128((const __m128i
*
)(&(&off_1A25DC)[
-
11532
]
+
1
));
......
while
( v_esi !
=
v13 );
if
( v8 !
=
v_esi )
{
LABEL_15:
do
{
v28
=
(v_esi
+
1
) ^ hash_temp[v_esi &
0xF
];
/
/
hash48[
0
-
F] 分别与
1
进行异或,也就是说src每一位如果是
/
/
奇数:减一
/
/
偶数:不变
encrypt_data2[v_esi]
=
v28 ^ (((unsigned __int8)(encrypt_data[v_esi]
+
v28) >>
4
) | (
16
*
(encrypt_data[v_esi]
+
v28)));
/
/
/
/
encrypt_data[i]
+
v28 看成一个整体结果记为 res
/
/
res转成了无符号类型所以是逻辑右移,左侧用零补齐
/
/
res乘以
16
等价于逻辑左移
4
位
/
/
其实就是把res的高四位和低四位颠倒了一下
/
/
最后再和v28异或
}
while
( v8 !
=
+
+
v_esi );
/
/
循环
32
次
}
}
memset(s,
0
,
960u
);
*
(_OWORD
*
)hash_temp
=
*
(_OWORD
*
)
hash
;
*
(_OWORD
*
)&hash_temp[
16
]
=
*
(_OWORD
*
)&
hash
[
16
];
*
(_OWORD
*
)&hash_temp[
32
]
=
*
(_OWORD
*
)&
hash
[
32
];
*
(_OWORD
*
)&hash_temp[
48
]
=
*
(_OWORD
*
)&
hash
[
48
];
/
/
hash_temp[
0
-
63
]
=
hash
[
0
-
63
]
__memcpy_chk((
int
)s, (
int
)encrypt_data2, user_name_length
+
16
,
960
);
/
/
这里把密文进行了一次拷贝,但看起来后续没有用到
*
(_OWORD
*
)v39
=
0LL
;
return
encrypt_4(hash_temp, user_name_length
+
80
, (
int
)v39,
16
, dest);
/
/
第三次加密得到最终的dest
}
这个函数总的来讲就是不断的将求得的hash作为key再次求hash,在分析这个函数时遇到的问题主要在于作者使用了 HMAC_Update 并且还总是在参数中不该传0的传0,无法确定是否对上下文产生了影响,解决方法是花了一些时间在 VS 中搭了一下环境,写了一个简单的 demo 进行简单的测试。
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
unsigned
int
__cdecl hmac_ctx_hash(BYTE
*
data, BYTE
*
key, BYTE
*
res)
{
int
md;
/
/
esi
__int64 v4;
/
/
xmm1_8
__int64 v5;
/
/
xmm0_8
int
len
;
/
/
[esp
+
8h
] [ebp
-
154h
] BYREF
char v8;
/
/
[esp
+
Eh] [ebp
-
14Eh
] BYREF
char v9;
/
/
[esp
+
Fh] [ebp
-
14Dh
] BYREF
BYTE hash3[
32
];
/
/
[esp
+
10h
] [ebp
-
14Ch
] BYREF
BYTE hash2[
32
];
/
/
[esp
+
30h
] [ebp
-
12Ch
] BYREF
int
ctx[
52
];
/
/
[esp
+
50h
] [ebp
-
10Ch
] BYREF
BYTE hash1[
40
];
/
/
[esp
+
120h
] [ebp
-
3Ch
] BYREF
unsigned
int
canary;
/
/
[esp
+
148h
] [ebp
-
14h
]
canary
=
__readgsdword(
0x14u
);
memset(hash1,
0
,
32
);
HMAC_CTX_init((
int
)ctx);
/
/
初始化一个 HMAC_CTX
md
=
EVP_sha256();
/
/
返回 sha256 的 EVP_MD
HMAC_Init_ex((
int
)ctx, (
int
)key,
32
, md,
0
);
/
/
初始化HAMC_CTX上下文结构,key为秘钥,
len
为秘钥长度,md为计算
hash
的函数集合
HMAC_Update((
int
)ctx, (
int
)data,
0
);
/
/
向HMAC上下文输入字节流,加密并输出,传入长度为
0
/
/
经过 demo 验证,该条指令不会对上下文产生影响
len
=
0
;
HMAC_Final((
int
)ctx, (
int
)hash1, (
int
)&
len
);
/
/
生成最终的HMAC串,成功时
len
更新为HMAC的长度。
memset(hash2,
0
, sizeof(hash2));
v9
=
1
;
HMAC_CTX_init((
int
)ctx);
HMAC_Init_ex((
int
)ctx, (
int
)hash1,
32
, md,
0
);
/
/
把 hash1 作为 key
HMAC_Update((
int
)ctx, (
int
)data,
0
);
HMAC_Update((
int
)ctx, (
int
)&v9,
1
);
/
/
输入字节流
1
,长度为
1
len
=
0
;
HMAC_Final((
int
)ctx, (
int
)hash2, (
int
)&
len
);
/
/
得到第二次的
hash
memset(hash3,
0
, sizeof(hash3));
v8
=
2
;
HMAC_CTX_init((
int
)ctx);
HMAC_Init_ex((
int
)ctx, (
int
)hash1,
32
, md,
0
);
/
/
把第一次的
hash
作为 key 再次加密
HMAC_Update((
int
)ctx, (
int
)hash2,
32
);
/
/
输入字节流 hash2,长度为
32
HMAC_Update((
int
)ctx, (
int
)&v8,
1
);
/
/
输入字节流
2
,长度为
1
len
=
0
;
HMAC_Final((
int
)ctx, (
int
)hash3, (
int
)&
len
);
/
/
得到第三次的
hash
v4
=
*
(_QWORD
*
)&hash2[
8
];
/
/
v4
=
hash2[
8
-
15
]
*
(_QWORD
*
)res
=
*
(_QWORD
*
)hash2;
/
/
res[
0
-
7
]
=
hash2[
0
-
7
]
*
((_QWORD
*
)res
+
1
)
=
v4;
/
/
res[
8
-
15
]
=
hash2[
8
-
15
]
*
((_QWORD
*
)res
+
2
)
=
*
(_QWORD
*
)&hash2[
16
];
/
/
res[
16
-
23
]
=
hash2[
16
-
23
]
*
((_QWORD
*
)res
+
3
)
=
*
(_QWORD
*
)&hash2[
24
];
/
/
res[
24
-
31
]
=
hash2[
24
-
31
]
*
((_QWORD
*
)res
+
7
)
=
*
(_QWORD
*
)&hash3[
24
];
/
/
res[
56
-
63
]
=
hash3[
24
-
31
]
*
((_QWORD
*
)res
+
6
)
=
*
(_QWORD
*
)&hash3[
16
];
/
/
res[
48
-
55
]
=
hash3[
16
-
31
]
v5
=
*
(_QWORD
*
)hash3;
*
((_QWORD
*
)res
+
5
)
=
*
(_QWORD
*
)&hash3[
8
];
/
/
res[
40
-
47
]
=
hash3[
8
-
15
]
*
((_QWORD
*
)res
+
4
)
=
v5;
/
/
res[
32
-
39
]
=
hash3[
0
-
7
]
return
__readgsdword(
0x14u
);
}
这个函数的总的来说就是使用上一层传入的 hash 作为 key,加密用户名得到 encrypt_data,然后将 encrypt_data分段赋值给最终的 res_encrypt_data,分析这个函数的问题也还是在于作者使用了 EVP_EncryptUpdate 这个不熟悉的函数,并且还总是在参数中不该传0的传0,无法确定是否对上下文产生了影响,解决方法是花了一些时间在 VS 中搭了一下环境,测试了各种情况的结果。
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
int
__cdecl evp_encrypt(
int
hash
,
int
iv,
int
iv_len,
BYTE
*
const_key,
int
const_key_length,
int
byte_user_name,
int
user_name_length,
char
*
res_encrypt_data)
{
int
ctx;
/
/
esi
int
cipher;
/
/
eax
size_t v10;
/
/
eax
__int64 v11;
/
/
xmm1_8
int
v13;
/
/
[esp
+
0h
] [ebp
-
41Ch
] BYREF
size_t encrypt_data_len;
/
/
[esp
+
4h
] [ebp
-
418h
] BYREF
BYTE encrypt_data[
1024
];
/
/
[esp
+
8h
] [ebp
-
414h
] BYREF
unsigned
int
v16;
/
/
[esp
+
408h
] [ebp
-
14h
]
v16
=
__readgsdword(
0x14u
);
ctx
=
EVP_CIPHER_CTX_new();
/
/
创建加密上下文
cipher
=
EVP_aes_256_gcm();
/
/
选择一种加密算法
EVP_EncryptInit_ex(ctx, cipher,
0
,
0
,
0
);
/
/
初始化密码上下文ctx
EVP_CIPHER_CTX_ctrl(ctx,
9
, iv_len,
0
);
/
/
/
/
设置向量IV的长度
EVP_EncryptInit_ex(ctx,
0
,
0
,
hash
, iv);
/
/
使用
hash
作为key初始化ctx
EVP_EncryptUpdate(ctx,
0
, (
int
)&encrypt_data_len, (
int
)const_key, const_key_length);
/
/
加密 const_key,让人困惑的是输出地址为
0
,
/
/
无法确定是否会对上下文产生影响,经过 demo 测试最终确定加密结果被丢弃
EVP_EncryptUpdate(ctx, (
int
)encrypt_data, (
int
)&encrypt_data_len, byte_user_name, user_name_length);
/
/
加密用户名
memcpy(res_encrypt_data, encrypt_data, encrypt_data_len);
/
/
dest[
0
-
15
]
=
encrypt_data[
0
-
15
]
/
/
将第二次EncryptUpdate的结果拷贝到dest中,而且经过动态调试得知,encrypt_data_len
=
16
EVP_EncryptFinal_ex(ctx, (
int
)encrypt_data, (
int
)&v13);
/
/
块对齐
EVP_CIPHER_CTX_ctrl(ctx,
16
,
16
, (
int
)encrypt_data);
/
/
v10
=
encrypt_data_len;
v11
=
*
(_QWORD
*
)&encrypt_data[
8
];
/
/
v11
=
encrypt_data[
8
-
15
]
*
(_QWORD
*
)&res_encrypt_data[encrypt_data_len]
=
*
(_QWORD
*
)encrypt_data;
/
/
dest[
16
-
23
]
=
encrypt_data[
0
-
7
]
*
(_QWORD
*
)&res_encrypt_data[v10
+
8
]
=
v11;
/
/
dest[
24
-
31
]
=
v11
=
encrypt_data[
8
-
15
]
EVP_CIPHER_CTX_free(ctx);
/
/
释放上下文
return
encrypt_data_len
+
16
;
/
/
返回加密结果的长度
+
16
}
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
int
main()
{
unsigned char key[
32
]
=
{
0
};
unsigned char iv[
32
]
=
{
0
};
unsigned char encrypt_data[
32
]
=
{
0
};
unsigned char user_name[
16
]
=
{
'a'
,
'b'
,
'c'
,
'd'
,
'a'
,
'b'
,
'c'
,
'd'
,
'a'
,
'b'
,
'c'
,
'd'
,
'a'
,
'b'
,
'c'
,
'd'
};
const unsigned char
*
const_key
=
(const unsigned char
*
)
"123456awxzcdfqwqt2wetbwerw"
;
int
len
=
0
;
for
(
int
i
=
0
; i <
32
; i
+
+
)
{
key[i]
=
i;
iv[i]
=
i;
}
EVP_CIPHER_CTX
*
ctx
=
EVP_CIPHER_CTX_new();
const EVP_CIPHER
*
cipher
=
EVP_aes_256_gcm();
EVP_EncryptInit_ex(ctx, cipher,
0
, key, iv);
EVP_CIPHER_CTX_ctrl(ctx,
9
,
12
,
0
);
/
/
EVP_EncryptUpdate(ctx,
0
, &
len
, const_key,
27
);
/
/
该条语句注释未产生任何影响
len
=
0
;
EVP_EncryptUpdate(ctx, encrypt_data, &
len
, user_name,
16
);
len
=
0
;
EVP_EncryptFinal_ex(ctx, encrypt_data, &
len
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int
__cdecl Decrypt(
int
key,
int
a2,
int
a3,
int
a4,
int
a5,
int
a6,
int
a7,
int
a8,
int
a9, void
*
dest)
{
int
ctx;
/
/
esi
int
v11;
/
/
eax
int
v12;
/
/
edi
size_t v14;
/
/
[esp
+
4h
] [ebp
-
418h
] BYREF
char src[
1024
];
/
/
[esp
+
8h
] [ebp
-
414h
] BYREF
unsigned
int
v16;
/
/
[esp
+
408h
] [ebp
-
14h
]
v16
=
__readgsdword(
0x14u
);
ctx
=
EVP_CIPHER_CTX_new();
v11
=
EVP_aes_256_gcm();
EVP_DecryptInit_ex(ctx, v11,
0
,
0
,
0
);
/
/
大致浏览一下参数与和之前的基本一致,只是调用的是解密函数
EVP_CIPHER_CTX_ctrl(ctx,
9
, a3,
0
);
EVP_DecryptInit_ex(ctx,
0
,
0
, key, a2);
if
( a5 )
EVP_DecryptUpdate(ctx,
0
, &v14, a4, a5);
EVP_DecryptUpdate(ctx, src, &v14, a8, a9);
memcpy(dest, src, v14);
/
/
这里也是一样,解密后的数据放到最后一个参数地址中
EVP_CIPHER_CTX_ctrl(ctx,
17
, a7, a6);
v12
=
EVP_DecryptFinal_ex(ctx, src, &v14);
EVP_CIPHER_CTX_free(ctx);
return
v12;
}
分析完 check2 按钮后,发现相比较 generate2 缺少一个流程,那就是求用户名 hash 作为key,于是回溯了一下这个 key,通过追溯发现,这个 key 已经通过两个异或加密函数加密在了 password 中,只需要将其逆运算取出来即可使用。
在分析按钮 generate3 时很多数据静态分析时并不存在,所以这里还需要分析 _init 函数,该函数地址并没有被 IDA 分析出来,可以通过以下两个办法找到:
这个函数首先将 19ca9c + image_base 存到 buff1_60[0-3] 中,根据后面的分析结果这里存的是一个函数地址,之后在 buff1_60[28-43] 中填入了一些随机数,随后调用了 sub_48840,并传入了一个字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BYTE
*
__cdecl sub_48840(BYTE
*
buff1_16, void
*
src, size_t len_32)
{
v3
=
*
buff1_16;
v4
=
10
;
if
( (v3 &
1
) !
=
0
)
/
/
0
&
1
!
=
0
判断失败
v4
=
(
*
(_DWORD
*
)buff1_16 &
0xFFFFFFFE
)
-
1
;
if
( v4 >
=
len_32 )
/
/
10
>
=
32
判断失败
{
......
}
else
{
if
( (v3 &
1
) !
=
0
)
/
/
判断失败
v5
=
*
((_DWORD
*
)buff1_16
+
1
);
else
v5
=
v3 >>
1
;
/
/
v5
=
0
sub_488E0(buff1_16, v4, len_32
-
v4, v5,
0
, v5, len_32, src);
/
/
/
/
sub_488e0(buff1_16,
10
,
22
,
0
,
0
,
0
,
32
,
"3390fd362dfdda0030d5737632d3d213"
)
/
/
buff1_16[
0
-
3
] 存放着申请堆空间大小
+
1
,也就是
49
/
/
buff1_16[
4
-
7
] 存放着字符串
"3390fd362dfdda0030d5737632d3d213"
的长度,也就是
32
/
/
buff1_16[
8
-
11
] 存放着字符串
"3390fd362dfdda0030d5737632d3d213"
的指针
}
return
buff1_16;
}
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
/
/
sub_488e0(buff1_60_16,
10
,
22
,
0
,
0
,
0
,
32
,
"3390fd362dfdda0030d5737632d3d213"
)
BYTE
*
__cdecl sub_488E0(BYTE
*
buff1_60_16,unsigned
int
a2,unsigned
int
a3,
int
a4,size_t n,
int
a6,size_t a7,void
*
src)
{
if
(
0xFFFFFFEE
-
a2 < a3 )
/
/
这里注意a2的类型是无符号类型,
-
18
会转换成一个非常大的正数,所以判断失败
sub_48A20();
if
( (
*
buff1_16 &
1
) !
=
0
)
/
/
buff1_16
=
=
0
,判断失败
v8
=
(BYTE
*
)
*
((_DWORD
*
)buff1_16
+
2
);
else
v8
=
buff1_16
+
1
;
/
/
v8
=
buff1_17
v18
=
v8;
/
/
v18
=
buff1_17
v9
=
-
17
;
/
/
v9
=
-
17
if
( a2 <
=
0x7FFFFFE6
)
/
/
10
<
=
0x7ffffff6
,判断成功
{
v10
=
a2
+
a3;
/
/
v10
=
32
; a2
=
10
; a3
=
22
if
( a2
+
a3 <
2
*
a2 )
/
/
10
+
22
<
10
*
2
=
false, 判断失败
v10
=
2
*
a2;
v9
=
11
;
/
/
v9
=
11
if
( v10 >
=
0xB
)
/
/
32
>
=
0xb
,判断成功
v9
=
(v10
+
16
) &
0xFFFFFFF0
;
/
/
v9
=
48
}
v20
=
v9;
/
/
v20
=
48
buff_48
=
(char
*
)operator new(v9);
/
/
申请了
48
个字节的空间
buff_48_
=
buff_48;
if
( n )
memcpy(buff_48, v18, n);
buff_48__
=
buff_48_;
v13
=
a6;
/
/
v13
=
0
v14
=
a7;
/
/
v14
=
a7
=
32
if
( a7 )
{
memcpy(&buff_48_[n], src, a7);
/
/
buff_48[
0
-
31
]
=
"3390fd362dfdda0030d5737632d3d213"
v13
=
a6;
/
/
v13
=
0
v14
=
a7;
/
/
v14
=
32
}
v15
=
a4
-
v13;
/
/
v15
=
0
-
0
=
0
if
( a4
-
v13 !
=
n )
/
/
0
-
0
!
=
0
该判断失败
{
memcpy(&buff_48__[n
+
v14], &v18[n
+
a6], v15
-
n);
v14
=
a7;
}
if
( a2 !
=
10
)
/
/
10
!
=
10
判断失败
{
operator delete(v18);
v14
=
a7;
}
result
=
buff1_16;
*
((_DWORD
*
)buff1_16
+
2
)
=
buff_48__;
/
/
buff1_16[
8
-
11
]
=
buff_48;注意这里是把申请空间的地址放在了这里
/
/
buff1_16[
8
-
11
] 存放了一个堆空间的指针,该指针指向字符串
"3390fd362dfdda0030d5737632d3d213"
*
(_DWORD
*
)buff1_16
=
v20 |
1
;
/
/
buff1_16[
0
-
3
]
=
0x00000031
/
/
小端存放 buff1_16[
0
-
3
]
=
{
0x31
,
00
,
00
,
00
}
v17
=
v14
+
v15;
/
/
v17
=
32
*
((_DWORD
*
)buff1_16
+
1
)
=
v17;
/
/
buff1_16[
4
-
7
]
=
0x00000032
/
/
buff1_16[
4
-
7
]
=
{
0x20
,
0
,
0
,
0
}
buff_48__[v17]
=
0
;
/
/
字符串
"3390fd362dfdda0030d5737632d3d213"
后添加
0
确保截断
return
result;
/
/
总结一下:
/
/
buff1_16[
0
-
3
] 存放着申请堆空间大小
+
1
,也就是
49
/
/
buff1_16[
4
-
7
] 存放着字符串
"3390fd362dfdda0030d5737632d3d213"
的长度,也就是
32
/
/
buff1_16[
8
-
11
] 存放着字符串
"3390fd362dfdda0030d5737632d3d213"
的指针
}
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
unsigned
int
__cdecl set_buff2(BYTE
*
buff2)
{
v31
=
__readgsdword(
0x14u
);
*
(_DWORD
*
)buff2
=
&off_19CAAC;
/
/
buff2[
0
-
3
]
=
19aac
+
imagebase
*
((_DWORD
*
)buff2
+
2
)
=
0
;
/
/
buff2[
4
-
19
]
=
0
*
((_DWORD
*
)buff2
+
1
)
=
0
;
*
((_DWORD
*
)buff2
+
4
)
=
0
;
*
((_DWORD
*
)buff2
+
3
)
=
0
;
memcpy(dest, byte_151F3A, sizeof(dest));
/
/
把固定地址的数据拷贝至dest,这里暂时将这串数据记作const_data
memcpy(buff2
+
20
, dest,
512u
);
/
/
buff2[
20
-
531
]
=
const_data
RAND_bytes(v29,
32
);
/
/
生成
32
字节大小的随机数
v1
=
v29[
3
];
/
/
v1
=
v29[
3
]
v28[
3
]
=
v29[
3
];
v2
=
v29[
2
];
/
/
v2
=
v29[
2
]
v28[
2
]
=
v29[
2
];
v3
=
v29[
1
];
/
/
v3
=
v29[
1
]
v28[
1
]
=
v29[
1
];
v28[
0
]
=
v29[
0
];
/
/
v28[
0
-
3
]
=
v29[
0
-
3
]
*
(_QWORD
*
)(buff2
+
532
)
=
v29[
0
];
/
/
buff2[
532
-
539
]
=
v29[
0
]
*
(_QWORD
*
)(buff2
+
540
)
=
v3;
/
/
buff2[
540
-
547
]
=
v3
*
(_QWORD
*
)(buff2
+
548
)
=
v2;
/
/
buff2[
548
-
555
]
=
v2
*
(_QWORD
*
)(buff2
+
556
)
=
v1;
/
/
buff2[
556
-
563
]
=
v1
*
(_QWORD
*
)(buff2
+
588
)
=
v28[
3
];
/
/
buff2[
588
-
595
]
=
v1
v4
=
v28[
1
];
*
(_QWORD
*
)(buff2
+
564
)
=
v28[
0
];
/
/
buff2[
564
-
571
]
=
v29[
0
]
*
(_QWORD
*
)(buff2
+
580
)
=
v28[
2
];
/
/
buff2[
580
-
587
]
=
v2
*
(_QWORD
*
)(buff2
+
572
)
=
v4;
/
/
buff2[
572
-
579
]
=
v3
/
/
小结一下:
/
/
buff2[
532
-
563
]
=
buff2[
564
-
595
] 是两组相同的随机数
v5
=
-
64
;
v6
=
0
;
do
v6
=
*
(_WORD
*
)&buff2[
2
*
(buff2[v5
+
+
+
596
] ^ HIBYTE(v6))
+
20
] ^ (v6 <<
8
);
while
( v5 );
/
/
观察这个公式,buff2[v5
+
+
+
596
]指定就是最后的
64
个随机字节,v5
+
+
依次取出,这里将这个值记作rand[i]
/
/
观察buff2[
2
*
(rand[i] ^ HIBYTE(v6))
+
20
],对照当前 buff2 的内存布局,这指的就是之前的 const_data
/
/
也明白了buff2的空间为什么是
596
这个奇怪的大小,它的组成是
20
+
256
*
2
+
64
/
/
需要注意的是最后使用了
*
(WORD
*
)&,也就是 const_data 被当做WORD取出
/
/
/
/
小结一下:
/
/
随机字节和v6异或也是随机的,就记为随机
/
/
循环将 const_data 以 WORD 类型随机取出,再和上一次结果的高位八位异或
/
/
总的来讲就是得到了一个随机数 v6
v27
=
v6;
*
(_WORD
*
)((char
*
)v28
+
5
)
=
v6;
/
/
这里修改了v28的第
5
-
6
字节为v6
hmac_ctx_hash((BYTE
*
)v28, (BYTE
*
)v29, buff2
+
532
);
/
/
这是一个已经分析过的函数,
hash
和data没有关系
/
/
把随机数 v29 作为key,
hash
结果放在buff2[
532
-
595
]中
v7
=
-
32
;
v8
=
0
;
do
v8
=
*
(_WORD
*
)&buff2[
2
*
(
*
((unsigned __int8
*
)v29
+
v7
+
+
) ^ HIBYTE(v8))
+
20
] ^ (v8 <<
8
);
while
( v7 );
/
/
这个循环和上边差不多,无非是随机数在v29这个随机字节数组中取,最终得到一个随机数 v8
v9
=
lrand48()
%
12
;
/
/
v9 是个
0
-
11
范围内的随机数
*
(_WORD
*
)&buff2[v9
+
532
]
=
v8;
/
/
随机数 v8 被随机的写在 buff2[
532
-
544
]中,注意 v8 是 WODD 类型
*
(_WORD
*
)&buff2[v9
+
534
]
=
v27;
/
/
一个
0
被随机的写在 buff2[
534
-
546
]中,注意
0
是 WODD 类型
v10
=
_mm_xor_ps(
*
(__m128
*
)(buff2
+
36
), (__m128)xmmword_148490);
*
(__m128
*
)(buff2
+
20
)
=
_mm_xor_ps(
*
(__m128
*
)(buff2
+
20
), (__m128)xmmword_148490);
/
/
buff2[
20
-
35
] ^
=
0x00360036003600360036003600360036
*
(__m128
*
)(buff2
+
36
)
=
v10;
/
/
buff2[
36
-
51
] ^
=
0x00360036003600360036003600360036
v11
=
_mm_xor_ps(
*
(__m128
*
)(buff2
+
68
), (__m128)xmmword_148490);
*
(__m128
*
)(buff2
+
52
)
=
_mm_xor_ps(
*
(__m128
*
)(buff2
+
52
), (__m128)xmmword_148490);
/
/
/
/
发现了规律,其实就是,buff2[
20
-
532
]
2
字节一组与
0x0036
异或,并更新buff2中的值
/
/
在这里总结一下:
/
/
buff2[
0
-
3
]
=
19aac
+
imagebase
/
/
buff2[
4
-
19
]
=
0
/
/
buff2[
20
-
531
]
=
const_data ^
0x0036
/
/
buff2[
532
-
595
]
=
hash
......
v25
=
_mm_xor_ps(
*
(__m128
*
)(buff2
+
516
), (__m128)xmmword_148490);
*
(__m128
*
)(buff2
+
500
)
=
_mm_xor_ps(
*
(__m128
*
)(buff2
+
500
), (__m128)xmmword_148490);
*
(__m128
*
)(buff2
+
516
)
=
v25;
return
__readgsdword(
0x14u
);
}
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
unsigned
int
__cdecl set_buff3(BYTE
*
buff3)
{
v32
=
__readgsdword(
0x14u
);
*
(_DWORD
*
)buff3
=
&off_19CA8C;
/
/
buff3[
0
-
3
]
=
19ca8c
+
image_base
*
((_DWORD
*
)buff3
+
18
)
=
0
;
/
/
buff3[
68
-
83
]
=
0
*
((_DWORD
*
)buff3
+
17
)
=
0
;
*
((_DWORD
*
)buff3
+
20
)
=
0
;
*
((_DWORD
*
)buff3
+
19
)
=
0
;
memcpy(dest, &unk_1485A0, sizeof(dest));
/
/
把固定地址的数据拷贝至dest,这里暂时将这串数据记作const_data
memcpy(buff3
+
84
, dest,
512u
);
/
/
buff3[
84
-
595
]
=
const_data
RAND_bytes(key,
32
);
/
/
生成
32
字节大小的随机数
v1
=
*
(_QWORD
*
)&key[
24
];
/
/
v1
=
key[
24
-
31
]
*
(_QWORD
*
)&data[
24
]
=
*
(_QWORD
*
)&key[
24
];
v2
=
*
(_QWORD
*
)&key[
16
];
/
/
v2
=
v32[
16
-
23
]
*
(_QWORD
*
)&data[
16
]
=
*
(_QWORD
*
)&key[
16
];
v3
=
*
(_QWORD
*
)&key[
8
];
/
/
v3
=
key[
8
-
15
]
*
(_QWORD
*
)&data[
8
]
=
*
(_QWORD
*
)&key[
8
];
*
(_QWORD
*
)data
=
*
(_QWORD
*
)key;
/
/
data[
0
-
31
]
=
key[
0
-
31
]
*
(_QWORD
*
)(buff3
+
4
)
=
*
(_QWORD
*
)key;
/
/
buff3[
4
-
11
]
=
key[
0
-
7
]
*
(_QWORD
*
)(buff3
+
12
)
=
v3;
/
/
buff3[
12
-
19
]
=
v3
*
(_QWORD
*
)(buff3
+
20
)
=
v2;
/
/
buff3[
20
-
27
]
=
v2
*
(_QWORD
*
)(buff3
+
28
)
=
v1;
/
/
buff3[
28
-
35
]
=
v1
*
(_QWORD
*
)(buff3
+
60
)
=
*
(_QWORD
*
)&data[
24
];
/
/
buff3[
60
-
67
]
=
v1
v4
=
*
(_QWORD
*
)&data[
8
];
*
(_QWORD
*
)(buff3
+
36
)
=
*
(_QWORD
*
)data;
/
/
buff3[
36
-
43
]
=
key[
0
-
7
]
*
(_QWORD
*
)(buff3
+
52
)
=
*
(_QWORD
*
)&data[
16
];
/
/
buff3[
52
-
59
]
=
v2
*
(_QWORD
*
)(buff3
+
44
)
=
v4;
/
/
buff3[
44
-
51
]
=
v3
/
/
小结:
/
/
buff3[
4
-
35
]
=
buff3[
36
-
67
] 是两组相同的随机数
v5
=
-
64
;
v6
=
0
;
do
v6
=
*
(_WORD
*
)&buff3[
2
*
(buff3[v5
+
+
+
68
] ^ HIBYTE(v6))
+
84
] ^ (v6 <<
8
);
/
/
这里和 setbuff2 基本一致,总的来讲就是得到了一个随机数 v6
while
( v5 );
v28
=
v6;
*
(_WORD
*
)&data[
5
]
=
v6;
/
/
data 的第
5
-
6
字节被赋值为v6
get_hash2(v27, (
int
)data, (
int
)key, buff3
+
4
);
/
/
buff3[
4
-
67
]
=
hash
v7
=
-
32
;
v8
=
0
;
do
v8
=
*
(_WORD
*
)&buff3[
2
*
(key[v7
+
+
] ^ HIBYTE(v8))
+
84
] ^ (v8 <<
8
);
/
/
通过 key 得到一个随机的 v8
while
( v7 );
v9
=
lrand48()
%
30
;
/
/
v9 一个范围在
0
-
29
的随机数
*
(_WORD
*
)&buff3[v9
+
4
]
=
v8;
/
/
buff3[
4
-
31
] 随机位置被赋值 v8
*
(_WORD
*
)&buff3[v9
+
6
]
=
v28;
/
/
buff3[
6
-
35
]随机位置被赋值v6
v10
=
_mm_xor_ps(
*
(__m128
*
)(buff3
+
100
), (__m128)xmmword_1483D0);
/
/
buff3[
84
-
595
]
2
字节一组与
0x0042
异或,并更新buff3中的值
/
/
总结:
/
/
buff3[
0
-
3
]
=
19ca8c
+
image_base
/
/
buff3[
4
-
67
]
=
hash
/
/
buff3[
68
-
83
]
=
0
/
/
buff3[
84
-
595
]
=
const_data ^
0x0042
......
v25
=
_mm_xor_ps(
*
(__m128
*
)(buff3
+
580
), (__m128)xmmword_1483D0);
*
(__m128
*
)(buff3
+
564
)
=
_mm_xor_ps(
*
(__m128
*
)(buff3
+
564
), (__m128)xmmword_1483D0);
*
(__m128
*
)(buff3
+
580
)
=
v25;
return
__readgsdword(
0x14u
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
jbyteArray __cdecl Java_com_test_pac_demo_MainActivity_getbt(JNIEnv
*
env, jobject a2, void
*
user_name_,
int
num_3)
{
v12
=
__readgsdword(
0x14u
);
user_name_len
=
(
*
env)
-
>GetArrayLength(env, user_name_);
user_name
=
(BYTE
*
)(
*
env)
-
>GetByteArrayElements(env, user_name_,
0
);
dword_1A50BC[
0
]
=
dword_1A50BC[num_3];
fun3
=
(
int
(__cdecl
*
*
*
)(_DWORD, BYTE
*
,
int
, char
*
))dword_1A50BC[
0
];
memset(password,
0
, sizeof(password));
len
=
(
*
*
fun3)(fun3, user_name, user_name_len, password);
v6
=
(
*
env)
-
>NewByteArray(env,
len
);
(
*
env)
-
>SetByteArrayRegion(env, v6,
0
,
len
, password);
(
*
env)
-
>ReleaseByteArrayElements(env, user_name_, (jbyte
*
)user_name,
0
);
return
v6;
}
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
int
__cdecl sub_425F0(
int
a1,
int
hash
,
int
iv,
int
num_12,
int
username_reverse,
int
username_reverse_len,
int
key_hash,
int
key_hash_len,char
*
dest)
{
int
ctx;
/
/
esi
int
cipher;
/
/
eax
size_t v11;
/
/
eax
__int64 v12;
/
/
xmm1_8
int
v14;
/
/
[esp
+
0h
] [ebp
-
41Ch
] BYREF
size_t
len
;
/
/
[esp
+
4h
] [ebp
-
418h
] BYREF
BYTE outbuff[
1024
];
/
/
[esp
+
8h
] [ebp
-
414h
] BYREF
unsigned
int
v17;
/
/
[esp
+
408h
] [ebp
-
14h
]
v17
=
__readgsdword(
0x14u
);
ctx
=
EVP_CIPHER_CTX_new();
cipher
=
EVP_aes_256_gcm();
EVP_EncryptInit_ex(ctx, cipher,
0
,
0
,
0
);
/
/
初始化 ctx
EVP_CIPHER_CTX_ctrl(ctx,
9
, num_12,
0
);
/
/
/
/
设置向量IV的长度为
12
EVP_EncryptInit_ex(ctx,
0
,
0
,
hash
, iv);
/
/
使用
hash
作为 key 初始化 ctx
EVP_EncryptUpdate(ctx,
0
, (
int
)&
len
, username_reverse, username_reverse_len);
/
/
输出地址为
0
,也就是说username没有参与加密
EVP_EncryptUpdate(ctx, (
int
)outbuff, (
int
)&
len
, key_hash, key_hash_len);
memcpy(dest, outbuff,
len
);
/
/
dest[
0
-
1
]
=
encrydata[
0
-
1
]
EVP_EncryptFinal_ex(ctx, outbuff, &v14);
EVP_CIPHER_CTX_ctrl(ctx,
16
,
16
, (
int
)outbuff);
/
/
获取
16
字节长度的 tag
v11
=
len
;
/
/
v11
=
2
v12
=
*
(_QWORD
*
)&outbuff[
8
];
/
/
v12
=
tag[
8
-
15
]
*
(_QWORD
*
)&dest[
len
]
=
*
(_QWORD
*
)outbuff;
/
/
dest[
2
-
9
]
=
tag[
0
-
7
]
*
(_QWORD
*
)&dest[v11
+
8
]
=
v12;
/
/
dest[
10
-
17
]
=
tag[
8
-
15
]
EVP_CIPHER_CTX_free(ctx);
/
/
总结:
/
/
dest[
0
-
1
]
=
encrydata[
0
-
1
]
/
/
dest[
2
-
9
]
=
tag[
0
-
7
]
/
/
dest[
10
-
17
]
=
tag[
8
-
15
]
return
len
+
16
;
}
主要功能为设置 src 区域的内存数据,并返回一个影响密码长度的值,另外我记着在分析这个函数的时候遇到了阻碍 IDA 分析的一连串 nop,我的解决方法是把 nop 改为 mov eax, eax ,然后依次使用快捷键 u 、c、p、f5,重新识别。
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
size_t __cdecl sub_43070(_DWORD
*
fun3, _BYTE
*
password, size_t num_20, _BYTE
*
src)
{
v22
=
__readgsdword(
0x14u
);
do
v4
=
get_hash3_and_change_fun3(fun3);
/
/
这里把结果的低两字节放到了v4中
/
/
此处通过位移修改了 fun3 中的数据
while
( (((_BYTE)v4
-
8
) &
0xC0
)
=
=
0
);
/
/
循环判断 v4
-
8
后[
2
-
3
]位的值是否为 C
/
/
直到不为C结束
*
(_WORD
*
)src
=
v4;
src[
2
]
=
BYTE1(num_20);
/
/
src[
2
]
=
n的高位
=
0
src[
3
]
=
num_20;
/
/
src[
3
]
=
20
memcpy(src
+
4
, password, num_20);
/
/
src[
4
-
23
]
=
password
v5
=
(unsigned __int16)(num_20
+
4
);
/
/
v5
=
24
v6
=
32
-
num_20;
/
/
v6
=
12
if
(
300
-
v5 >
32
-
num_20 )
v6
=
300
-
v5;
/
/
v6
=
276
v16
=
1399
-
(v6
+
v5);
/
/
v16
=
1099
v7
=
get_hash3_and_change_fun3(fun3);
/
/
hash3
v8
=
0
;
v18
=
v6
+
v7
%
v16
-
3
*
(((v6
+
v7
%
v16)
/
3
) &
0xFFFFFFF0
);
/
/
v18
=
276
+
v7
%
16
-
3
*
((
276
+
v7
%
1099
)
/
3
) &
0xfffffff0
v9
=
v18
+
32
;
/
/
v9
=
v18
+
32
memset(s,
0
, sizeof(s));
v17
=
v18
+
32
;
do
{
v10
=
v9
-
v8;
if
( v10 <
8
)
{
v19
=
get_hash3_and_change_fun3(fun3);
/
/
这里再次计算
hash
memcpy(&s[v8], &v19, v10);
/
/
将
hash
复制到数组 s 中
}
else
{
v11
=
get_hash3_and_change_fun3(fun3);
/
/
再次计算
hash
v19
=
v11;
*
(_DWORD
*
)&s[v8
+
4
]
=
HIDWORD(v11);
/
/
设置数组 s 的内容
*
(_DWORD
*
)&s[v8]
=
v11;
}
v8
+
=
8
;
/
/
v8
+
=
8
v9
=
v18
+
32
;
}
while
( v17 > v8 );
/
/
循环直到 v17 <
=
v8 结束
v12
=
num_20
+
v18;
memcpy(&src[num_20
+
4
], s, v17);
/
/
这里将 s 拷贝到 src
v13
=
*
(_QWORD
*
)&src[num_20
+
20
+
v18];
v20
=
*
(_QWORD
*
)&src[num_20
+
28
+
v18];
v19
=
v13;
sub_43990((
int
)fun3, (
int
)(src
+
2
), num_20
+
v18
+
18
, (unsigned
int
*
)&src[num_20
+
20
+
v18]);
/
/
通过一系列的变换,修改了src中的值
v14
=
v20;
*
(_QWORD
*
)&src[v12
+
20
]
=
v19;
/
/
src[
20
+
v18
+
20
]
=
src[num_20
+
20
+
v18]
*
(_QWORD
*
)&src[v12
+
28
]
=
v14;
/
/
src[
20
+
v18
+
28
]
=
src[num_20
+
28
+
v18]
return
num_20
+
v18
+
36
;
/
/
总结:
/
/
首先修改fun3的数据并计算出一个hash1,放到src[
0
-
1
]
/
/
再次修改fun3的数据并计算出一个hash2,通过hash2和一些常数的计算
/
/
得到贯穿整个函数的v18,在
43
-
60
行的代码中,反复修改fun3的数据并计算
hash
/
/
来设置数组 s, 随后将数组 s拷贝到 src 中,此时函数先将src的[
40
+
v18]到[
40
+
v18]保存了一份
/
/
调用函数 sub_43990 中通过一系列的位移变换,再次修改src中的数据
/
/
然后将保存的数据进行恢复,返回值为 v18
+
56
}
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
__int64 __cdecl sub_43D00(_DWORD
*
fun3)
{
v1
=
fun3[
17
];
v2
=
fun3[
18
];
if
(
*
(_QWORD
*
)(fun3
+
17
) )
{
v3
=
fun3[
20
];
v4
=
fun3[
19
];
}
else
{
gettimeofday(&tv,
0
);
v5
=
tv.tv_usec ^ (
*
(unsigned __int64
*
)&tv >>
9
) ^ HIDWORD(v31) ^ (HIDWORD(v31) >>
26
) ^ ((tv.tv_usec ^ (unsigned
int
)(
*
(unsigned __int64
*
)&tv >>
9
)) >>
17
);
v6
=
tv.tv_sec ^ (tv.tv_sec <<
23
) ^ v31 ^ (v31 >>
26
) ^ ((
*
(_QWORD
*
)&tv ^ (unsigned __int64)(
*
(_QWORD
*
)&tv <<
23
)) >>
17
);
v7
=
v31 ^ ((_DWORD)v31 <<
23
);
LODWORD(v31)
=
HIDWORD(v31) ^ (v31 >>
9
);
v8
=
((unsigned
int
)v31 >>
17
) ^ (v5 >>
26
) ^ v5 ^ v31;
......
LODWORD(v10)
=
v25 ^ (v25 <<
23
);
v4
=
v1 ^ (__PAIR64__(v2, v1) >>
26
) ^ v10 ^ (v10 >>
17
);
v3
=
v2 ^ (v2 >>
26
) ^ HIDWORD(v10) ^ (HIDWORD(v10) >>
17
);
fun3[
17
]
=
v1;
fun3[
18
]
=
v2;
fun3[
19
]
=
v4;
fun3[
20
]
=
v3;
}
v26
=
v2 ^ (__PAIR64__(v2, v1) >>
9
);
HIDWORD(v27)
=
(v3 >>
26
) ^ v26 ^ v3 ^ (v26 >>
17
);
HIDWORD(v28)
=
v26;
LODWORD(v28)
=
v1 ^ (v1 <<
23
);
LODWORD(v27)
=
(__PAIR64__(v3, v4) >>
26
) ^ (v28 >>
17
) ^ v4 ^ v28;
fun3[
17
]
=
v4;
fun3[
18
]
=
v3;
*
(_QWORD
*
)(fun3
+
19
)
=
v27;
return
__PAIR64__(v3, v4)
+
v27;
/
/
/
/
其实就是拼接成
64
位
8
字节的宏
/
/
接下来这些位运算居然还有奇数是最棘手的,想不到化简的办法
/
/
但可以知道,结果是fun3[
20
]和fun[
19
]拼接的值加上 v27
/
/
v27 这些位运算得来的,而整个函数其实就是在做一件事:
/
/
/
/
通过这些位移修改了 fun3[
68
-
80
]区间内的数据
/
/
/
/
/
/
/
/
v1
=
fun3[
17
];
/
/
/
/
v2
=
fun3[
18
];
/
/
/
/
v3
=
fun3[
20
];
/
/
/
/
v4
=
fun3[
19
];
/
/
/
/
v26
=
fun3[
18
] ^ (__PAIR64__(fun3[
18
], fun3[
17
]) >>
9
);
/
/
HIDWORD(v27)
=
(fun3[
20
] >>
26
) ^ v26 ^ fun3[
20
] ^ (v26 >>
17
);
/
/
HIDWORD(v28)
=
v26;
/
/
LODWORD(v28)
=
fun3[
17
] ^ (fun3[
17
] <<
23
);
/
/
LODWORD(v27)
=
(__PAIR64__(fun3[
20
], fun3[
19
]) >>
26
) ^ (v28 >>
17
) ^ fun3[
19
] ^ v28;
/
/
fun3[
17
]
=
fun3[
19
];
/
/
fun3[
18
]
=
fun3[
20
];
/
/
*
(_QWORD
*
)(fun3
+
19
)
=
v27;
/
/
return
__PAIR64__(fun3[
20
], fun[
19
])
+
v27;
}
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
size_t __cdecl sub_42940(_DWORD
*
fun3, BYTE
*
user_name,
int
user_name_len, char
*
password)
{
v73
=
__readgsdword(
0x14u
);
v4
=
-
64
;
v5
=
0
;
do
v5
=
*
((_WORD
*
)fun3
+
(
*
((unsigned __int8
*
)fun3
+
v4
+
+
+
68
) ^ HIBYTE(v5))
+
42
) ^ (v5 <<
8
);
while
( v4 );
/
/
又见到了这个算法,这个算法已经出现了不止一次
/
/
不过我突然觉着这个算法可能没有我之前分析的那么简单
/
/
因为我发现对于整个算法来讲,最重要的是v5的高位
/
/
参与算法的并不是当前v5的低位,而是上一次 v5 的低位
/
/
这种情况可以将v5拆成两个uint8,以便更好的分析算法
/
/
这里使用 res 记录当前v5高位,old_data 记录上次 v5 低位
/
/
/
/
这里对这个算法进行重新分析:
/
/
将 v5 转为 uint8 类型后整个算法逐渐变的更加清晰
/
/
首先通过公式
id
=
(data[i] ^ v5)
*
2
+
84
得到
id
作为下一次从 data 中取数据的下标
/
/
随后通过
id
+
1
将高位取出,再和上一次的 v5 低位异或得到该轮循环的值
/
/
注意:这种情况最终的结果将不再是 uint16 类型,因为这里是为了更清晰的分析算法
/
/
如果需要,可以添加一个一直指向 res 的指针,最终取出 uint16 的数据
/
/
通过之前分析 buff3 的内存结构可以知道,data 的数据是应用启动时生成的一段随机
hash
/
/
这里返回的 v5 如果应用不重启,那么这个值是是固定的,如果应用重启这个值就会变化
/
/
/
/
int
id
=
0
;
/
/
unsigned char res
=
0
;
/
/
unsigned char old_data
=
0
;
/
/
/
/
for
(
int
i
=
4
; i <
68
; i
+
+
)
/
/
{
/
/
id
=
(data[i] ^ res)
*
2
+
84
;
/
/
res
=
data[
id
+
1
] ^ old_data;
/
/
old_data
=
data[
id
];
/
/
}
v62
=
v5;
/
/
v61
=
v5
*
((_WORD
*
)password
+
1
)
=
v5;
/
/
password[
2
-
3
]
=
v5
if
( user_name )
{
if
( user_name_len >
0
)
/
/
判断用户名长度大于
0
{
v6
=
0
;
if
( (unsigned
int
)user_name_len <
0x10
)
/
/
判断用户名长度小于
16
goto LABEL_33;
v6
=
user_name_len &
0xFFFFFFF0
;
/
/
v16
=
16
v7
=
0
;
/
/
v7
=
0
si128
=
_mm_load_si128((const __m128i
*
)(&(&off_1A25DC)[
-
11539
]
+
1
));
/
/
si128
=
0F0F
0F0F
0F0F
0F0F
0F0F
0F0F
0F0F
0F0F
v59
=
_mm_load_si128((const __m128i
*
)(&off_1A25DC
-
92307
));
/
/
v59
=
9F9F
9F9F
9F9F
9F9F
9F9F
9F9F
9F9F
9F9F
v9
=
_mm_load_si128((const __m128i
*
)(&(&off_1A25DC)[
-
11538
]
+
1
));
/
/
v9
=
3030
3030
3030
3030
3030
3030
3030
3030
v10
=
_mm_load_si128((const __m128i
*
)(&off_1A25DC
-
92299
));
/
/
v10
=
5757
5757
5757
5757
5757
5757
5757
5757
v11
=
_mm_load_si128((const __m128i
*
)(&(&off_1A25DC)[
-
11537
]
+
1
));
/
/
v11
=
0909
0909
0909
0909
0909
0909
0909
0909
do
{
v12
=
_mm_loadu_si128((const __m128i
*
)&user_name[v7]);
/
/
将 username 加载到寄存器 v12 中
/
/
v13
=
_mm_and_si128(_mm_srli_epi16(v12,
4u
), si128);
/
/
username分为
8
组
16bit
的数据,对它们分别逻辑右移
4
位,高位补零
/
/
再与上
0F0F
0F0F
0F0F
0F0F
0F0F
0F0F
0F0F
0F0F
/
/
v13
=
username>>
4
^si128
/
/
v14
=
_mm_cmpeq_epi8(_mm_min_epu8(v12, v59), v12);
/
/
username 和
9F9F
9F9F
9F9F
9F9F
9F9F
9F9F
9F9F
9F9F
的最小值
/
/
而字符串必然小于
9F
,所以v14
=
FFFFFFFFFFFFFFFF
/
/
v15
=
_mm_or_si128(_mm_andnot_si128(v14, _mm_add_epi8(v13, v10)), _mm_and_si128(_mm_or_si128(v13, v9), v14));
/
/
/
/
v15
=
(!v14 & (v13
+
v10))|((v13|v9)&v14)
/
/
注意 andnot 会将 v14 反转,后边的 v14 不再是ffff而是
0
/
/
稍微化简即可得到 v15
=
v13|v9
v16
=
_mm_and_si128(v12, si128);
/
/
v16
=
username & si128
v17
=
_mm_cmpeq_epi8(_mm_subs_epu8(v16, v11), (__m128i)
0LL
);
/
/
(v16分组减v11) 和
0
比较,v16 每个字节不会超过
0f
, v11 每个字节是
09
/
/
所以 v17 为 ff 和
00
组成
v18
=
_mm_or_si128(_mm_andnot_si128(v17, _mm_add_epi8(v16, v10)), _mm_and_si128(_mm_or_si128(v16, v9), v17));
/
/
通过动态调试得知,v18
=
username
*
(__m128i
*
)&key[
2
*
v7
+
16
]
=
_mm_unpackhi_epi8(v15, v18);
/
/
将 v15 和 v18 的低
64
位数以
8
位为单位进行交错
/
/
key[
16
-
31
]
=
username>>
4
^si128 低
64
位分组交错 username
*
(__m128i
*
)&key[
2
*
v7]
=
_mm_unpacklo_epi8(v15, v18);
/
/
key[
0
-
15
]
=
username>>
4
^si128 高
64
位分组交错 username
v7
+
=
16
;
}
while
( v6 !
=
v7 );
/
/
看似循环,实际只执行一次
/
/
小结:
/
/
key[
0
-
15
]
=
(username
8bit
分组右移四位) ^ si128 再高
64
位分组交错 username
/
/
key[
16
-
31
]
=
(username
8bit
分组右移四位) ^ si128 再低
64
位分组交错 username
/
/
if
( v6 !
=
user_name_len )
/
/
判断不成立
{
......
}
}
}
HMAC_CTX_init(ctx);
md
=
EVP_sha256();
HMAC_Init_ex(ctx, key,
32
, md,
0
);
HMAC_Update((
int
)ctx, (
int
)&v62,
2
);
/
/
这里的 v61 就是每次重启应用才会改的那个值,计算这个值的
hash
len
=
0
;
HMAC_Final((
int
)ctx, (
int
)
hash
, (
int
)&
len
);
/
/
得到
hash
v26
=
-
32
;
key_hash_
=
0
;
do
key_hash_
=
*
((_WORD
*
)fun3
+
((unsigned __int8)v71[v26
+
+
] ^ HIBYTE(key_hash_))
+
42
) ^ (key_hash_ <<
8
);
while
( v26 );
/
/
越界访问 key,通过(key[i] ^ v27)
*
2
+
84
得到
id
/
/
作为下一次从 data 中取数据的下标,最终得到 v27
key_hash
=
key_hash_;
/
/
v76
=
v27
uesrname_reverse
=
0LL
;
v28
=
user_name;
if
( user_name_len >
0
)
{
v29
=
0
;
if
( (unsigned
int
)user_name_len <
=
0x1F
)
goto LABEL_21;
/
/
跳转到 LABEL_21
......
v28
=
user_name;
if
( v29 !
=
user_name_len )
{
LABEL_21:
v35
=
user_name_len
-
v29;
/
/
v35
=
16
uesrname_reverse_
=
&ctx[v29
-
16
];
/
/
v36
=
__int128 v69
=
0
do
*
uesrname_reverse_
+
+
=
v28[
-
-
v35];
/
/
将 username 倒序存入 username_reverse 中
while
( v35 );
}
}
username_
=
(
int
)v28;
memset(dest,
0
, sizeof(dest));
sub_425F0(v57, (
int
)
hash
, username_,
12
, (
int
)&uesrname_reverse,
16
, (
int
)&key_hash,
2
, dest);
/
/
总结:
/
/
dest[
0
-
1
]
=
encrydata[
0
-
1
]
/
/
dest[
2
-
9
]
=
tag[
0
-
7
]
/
/
dest[
10
-
17
]
=
tag[
8
-
15
]
*
(_WORD
*
)password
=
*
(_WORD
*
)dest;
/
/
password[
0
-
1
]
=
dest[
0
-
1
]
v38
=
*
(_QWORD
*
)&dest[
2
];
*
(_QWORD
*
)(password
+
12
)
=
*
(_QWORD
*
)&dest[
10
];
/
/
password[
12
-
19
]
=
dest[
10
-
17
]
*
(_QWORD
*
)(password
+
4
)
=
v38;
/
/
password[
4
-
11
]
=
dest[
2
-
9
]
memset(password_,
0
,
0x400u
);
/
/
v77 被清零,v65也被越界清空
v65
=
*
((_DWORD
*
)password
+
4
);
/
/
v64
=
password[
16
-
19
]
v39
=
*
(_QWORD
*
)password;
/
/
v39
=
password[
0
-
7
]
*
(_QWORD
*
)&password_[
8
]
=
*
((_QWORD
*
)password
+
1
);
/
/
v77[
8
-
15
]
=
password[
8
-
15
]
*
(_QWORD
*
)password_
=
v39;
/
/
v77[
0
-
7
]
=
password[
0
-
7
]
v40
=
sub_43070(fun3, password_,
0x14u
, src);
/
/
/
/
首先修改fun3的数据并计算出一个hash1,放到src[
0
-
1
]
/
/
再次修改fun3的数据并计算出一个hash2,通过hash2和一些常数的计算
/
/
得到贯穿整个函数的v18(这个值最终决定了password长度),
/
/
在
43
-
60
行的代码中,反复修改fun3的数据并计算
hash
/
/
来设置数组 s, 随后将数组 s拷贝到 src 中,此时函数先将src的[
40
+
v18]到[
40
+
v18]保存了一份
/
/
调用函数 sub_43990 中通过一系列的位移变换,再次修改src中的数据
/
/
然后将保存的数据进行恢复,返回值为 v18
+
56
memset(v72,
0
,
256
);
memset(v71,
0
, sizeof(v71));
for
( i
=
0
; i !
=
256
;
+
+
i )
{
v71[i]
=
i;
/
/
初始化v71
=
0
1
2
3
4
5
6
...
v72[i]
=
*
((_BYTE
*
)fun3
+
(i &
0x3F
)
+
4
);
/
/
使用 fun3 的数据初始化 v72
}
v42
=
0
;
v43
=
-
256
;
do
{
v44
=
(unsigned __int8)v72[v43];
v45
=
v44
+
v42
+
(unsigned __int8)v72[v43
+
256
];
v45
%
=
256
;
v46
=
v71[v45];
v71[v45]
=
v44;
v72[v43
+
+
]
=
v46;
/
/
不断变换交换 v71 和 v72 的数据
v42
=
v45;
}
while
( v43 );
v58
=
v40;
if
( v40 >
0
)
/
/
v40
=
上个函数中的v18
+
56
判断成立
{
v47
=
0
;
v48
=
0
;
src_
=
src;
v50
=
v58;
do
{
v51
=
v47
+
1
-
((v47
+
((unsigned
int
)((v47
+
1
) >>
31
) >>
24
)
+
1
) &
0xFFFFFF00
);
v52
=
(unsigned __int8)v71[v51];
v48
=
(v52
+
v48)
%
256
;
v53
=
v50;
v54
=
v71[v48];
v71[v48]
=
v52;
/
/
变换 v71 中的数据
v71[v51]
=
v54;
*
src_
+
+
^
=
v71[(unsigned __int8)(v71[v48]
+
v54)];
/
/
循环将 v71 的数据异或再赋值给src
v50
=
v53
-
1
;
v55
=
v53
=
=
1
;
v47
=
v51;
}
while
( !v55 );
}
memcpy(password, src, v58);
/
/
将 src 的数据拷贝到 password
return
v58;
/
/
返回拷贝数据的长度
}
可以发现主要就是在调用函数 dword_1A50BC[0] + 4 而这个地址其实就是在 _init 初始化的 [19ca8c + image_base]+4(函数sub_43280)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
__cdecl Java_com_test_pac_demo_MainActivity_getck(JNIEnv
*
a1,
int
a2, void
*
username_, void
*
password_)
{
username_len
=
(
*
a1)
-
>GetArrayLength(a1, username_);
username
=
(
*
a1)
-
>GetByteArrayElements(a1, username_,
0
);
password_len
=
(
*
a1)
-
>GetArrayLength(a1, password_);
password
=
(
*
a1)
-
>GetByteArrayElements(a1, password_,
0
);
v8
=
(
*
(
int
(__cdecl
*
*
)(
int
, jbyte
*
, jsize, jbyte
*
, jsize))(
*
(_DWORD
*
)dword_1A50BC[
0
]
+
4
))(
dword_1A50BC[
0
],
username,
username_len,
password,
password_len);
(
*
a1)
-
>ReleaseByteArrayElements(a1, username_, username,
0
);
(
*
a1)
-
>ReleaseByteArrayElements(a1, password_, password,
0
);
return
v8;
}
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
int
__cdecl sub_43280(BYTE
*
fun3, BYTE
*
username,
int
username_len, char
*
password,
int
password_len)
{
result
=
0
;
if
( username_len
=
=
16
)
{
memset(password_encrypt,
0
,
256
);
memset(v71,
0
,
256
);
......
v15
=
v13
+
1
-
((v13
+
((unsigned
int
)((v13
+
1
) >>
31
) >>
24
)
+
1
) &
0xFFFFFF00
);
v16
=
(unsigned __int8)v71[v15];
v14
=
(v16
+
v14)
%
256
;
v17
=
v71[v14];
v71[v14]
=
v16;
v71[v15]
=
v17;
password[v12
+
+
] ^
=
v71[(unsigned __int8)(v71[v14]
+
v17)];
/
/
/
/
上面的代码在 sub_42940 是分析过的
/
/
注意和 password 进行异或的值都是通过
/
/
fun3 初始化 v71、
72
在通过相同的位运算计算出来的
/
/
也就是说这里就是在对 password 进行解密
v13
=
v15;
}
while
( password_len !
=
v12 );
if
( password_len >
=
32
)
{
v19
=
password;
sub_43990((
int
)fun3, (
int
)(password
+
2
), password_len
-
18
, (unsigned
int
*
)&password[password_len
-
16
]);
/
/
/
/
这里的第二次调用 sub_43990 也是同样的道理
/
/
也是异或相同的值对 password 进行解密
v20
=
(unsigned __int8)password[
3
];
v21
=
16
*
(unsigned __int8)password[
2
];
v22
=
v20
+
v21
=
=
0
;
v23
=
v20
+
v21;
v18
=
0
;
if
( !v22 )
{
v24
=
v20
+
16
*
(unsigned __int8)password[
2
];
v25
=
0
;
if
( v24 <
0x20
)
goto LABEL_17;
/
/
动态调试得知此处跳转至 LABEL_17
v25
=
v24 &
0xFFFFFFE0
;
......
if
( v24 !
=
v25 )
{
LABEL_17:
v29
=
v24
-
v25;
v30
=
&v19[v25
+
4
];
do
{
*
(v30
-
4
)
=
*
v30;
/
/
v30 就是 password,在函数 sub_42940 中也是
/
/
存在这段代码的,这里还是对password进行解密
+
+
v30;
-
-
v29;
}
while
( v29 );
}
v18
=
v23;
/
/
v20
=
(unsigned __int8)password[
3
];
/
/
v21
=
16
*
(unsigned __int8)password[
2
];
/
/
v22
=
v20
+
v21
=
=
0
;
/
/
v23
=
v20
+
v21;
/
/
/
/
v18
=
password[
2
]
*
16
+
password[
3
]
}
}
else
{
v18
=
0
;
}
}
v31
=
v18;
/
/
v31
=
password[
2
]
*
16
+
password[
3
]
/
/
/
/
回去翻看了一下 sub_43070 的分析过程,发现这个值是固定的
=
20
/
/
src[
2
]
=
BYTE1(num_20);
/
/
src[
2
]
=
n的高位
=
0
/
/
src[
3
]
=
num_20;
/
/
src[
3
]
=
20
memset(password_encrypt,
0
, sizeof(password_encrypt));
v32
=
sub_43860(v47, password, v31, password_encrypt, COERCE_FLOAT(
120
));
/
/
这里出现了一个在加密过程中没有出现过的函数
/
/
这个函数将password加密的数据存放到 password_encrypt 中
/
/
参数 v31 应该是欲加密的长度为
20
,返回值 v32
=
v31
=
20
result
=
0
;
if
( v32
=
=
20
)
{
if
( username )
{
v33
=
_mm_loadu_si128((const __m128i
*
)username);
si128
=
_mm_load_si128((const __m128i
*
)&xmmword_148380);
......
*
(__m128i
*
)&key[
16
]
=
_mm_unpackhi_epi8(v39, v42);
*
(__m128i
*
)key
=
_mm_unpacklo_epi8(v39, v42);
/
/
这段循环之前在 sub_42940 也是分析过的
/
/
显然这就是把用户名变换一下,然后当做下面求
hash
的key使用
/
/
key[
0
-
15
]
=
(username
8bit
分组右移四位) ^ si128 再高
64
位分组交错 username
/
/
key[
16
-
31
]
=
(username
8bit
分组右移四位) ^ si128 再低
64
位分组交错 username
}
HMAC_CTX_init(ctx);
v43
=
EVP_sha256();
HMAC_Init_ex(ctx, key,
32
, v43,
0
);
HMAC_Update((
int
)ctx, (
int
)&password_encrypt[
2
],
2
);
/
/
将 password_encrypt[
2
-
3
] 传入
v49
=
0
;
HMAC_Final((
int
)ctx, (
int
)
hash
, (
int
)&v49);
/
/
得到
hash
v44
=
-
32
;
v45
=
0
;
do
v45
=
*
(_WORD
*
)&fun3[
2
*
(key[v44
+
+
+
32
] ^ HIBYTE(v45))
+
84
] ^ (v45 <<
8
);
while
( v44 );
/
/
这个算法之前也分析过,把 key[i]
*
2
*
v45 当做
id
/
/
求得fun3数据区的
hash
v48
=
_mm_shuffle_epi8(_mm_loadu_si128((const __m128i
*
)username), (__m128i)xmmword_1483E0);
/
/
/
/
xmmword_1483E0
=
102030405060708090A0B0C0D0E0Fh
/
/
对 username 的顺序进行变换,传入下边的 sub_424e0
/
/
不过这个参数并不影响结果
memset(v71,
0
, sizeof(v71));
result
=
sub_424E0(
/
/
EVP_DecryptFinal_ex 失败,result
=
0
v46,(
int
)
hash
,(
int
)username,
12
,(
int
)&v48,
16
,(
int
)&password_encrypt[
4
],
16
,(
int
)password_encrypt,
2
,v71);
if
( !result || v45 !
=
*
(_WORD
*
)v71 )
return
0
;
/
/
返回
0
}
}
return
result;
}
EVP_DecryptFinal_ex 解密失败,这里我查阅了一下 openssl 源码,结合动态调试返回的地方,对应在 EVP_R_INVALID_OPERATION 处返回,正好源码里有注释,翻译了一下是:防止解密时意外使用加密上下文。 应该是解密的数据不对导致的失败返回 0。
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
int
__cdecl sub_424E0(
int
a1,
int
hash
,BYTE
*
username,
int
num_12,
int
a5,
int
num_16_,
BYTE
*
password_encrypt_4,
int
num_16,BYTE
*
password_encrypt,
int
num_2,BYTE
*
dest)
{
ctx
=
EVP_CIPHER_CTX_new();
cipher
=
EVP_aes_256_gcm();
EVP_DecryptInit_ex(ctx, cipher,
0
,
0
,
0
);
/
/
使用 chiper 初始化 ctx
EVP_CIPHER_CTX_ctrl(ctx,
9
, num_12,
0
);
/
/
设置向量长度
EVP_DecryptInit_ex(ctx,
0
,
0
,
hash
, username);
/
/
使用
hash
作为 key,使用username作为iv
if
( num_16_ )
EVP_DecryptUpdate(ctx,
0
, &v15, a5, num_16_);
/
/
输出地址为
0
,无意义
EVP_DecryptUpdate(ctx, src, &v15, password_encrypt, num_2);
/
/
将 password_encrpt 前两个字节输入并解密,解密数据放到src中
memcpy(dest, src, v15);
/
/
拷贝解密数据到 dest 中
EVP_CIPHER_CTX_ctrl(ctx,
0x11
, num_16, (
int
)password_encrypt_4);
/
/
/
/
设置 password_encrypt[
4
]作为 TAG
v13
=
EVP_DecryptFinal_ex(ctx, src, &v15);
/
/
此处在动态调试的时候发现解密失败,跟进看了一下
/
/
/
/
*
a3
=
0
;
/
/
v3
=
*
a1;
/
/
if
( (
*
(
*
a1
+
18
) &
0x10
) !
=
0
)
/
/
{
/
/
savedregs
=
0
;
/
/
v16
=
(
*
(v3
+
24
))();
/
/
if
( v16 <
0
)
/
/
{
/
/
return
0
;
/
/
会在此处返回
/
/
}
/
/
/
/
查阅了一下 openssl 源码,这里对应的应该是下边这一句
/
/
翻译一下是:防止解密时意外使用加密上下文,应该是解密的数据不对
/
/
失败就也挺合理,因为前面调用了一个password_encrypt 是调用了
/
/
一个和 check_bt 无法逆向对应的一个函数
/
/
/
/
/
*
Prevent accidental use of encryption context when decrypting
*
/
/
/
if
(ctx
-
>encrypt) {
/
/
ERR_raise(ERR_LIB_EVP, EVP_R_INVALID_OPERATION);
/
/
return
0
;
/
/
}
/
/
EVP_CIPHER_CTX_free(ctx);
return
v13;
/
/
返回失败
}
和 generate3 相比,check3 的代码量少了许多,并没有像check2那样完全逆向流程解密,导致验证失败,而且我思考了一下,注册机应该是写不了的,因为最开始在 _init 函数中初始化的数据是随机的。
本次样本分析共历时七天,遇到了数不清的问题,但总的来讲主要集中在 OpenSSL 函数、SSE指令集的用法的细节问题,但最终经过网络搜索、动态调试和搭建实验环境调试 demo 予以解决,这七天的时间可以说是收获满满,增加了大量的实战经验,为今后的工作与学习指明方向。
https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE2&text=_mm_cmpeq_epi32&expand=773