注意:文章仅用作学习使用,关键加密已被替换,不存在泄漏风险,如有侵权,联系作者删除
DLL 中 加密与二维码生成逻辑分析笔记
1. 分析目标
目标 DLL 中主要关注两个 JNI 方法:
Java_com_cqddb_jni_QR_DESEncrypt
Java_com_cqddb_jni_QR_GenQR
其中:
DESEncrypt:输入字符串,返回加密后的 Base64 字符串
GenQR:输入字符串,返回二维码 PNG 图片的 Base64 字符串
样例测试
输入:
testadmin
DESEncrypt 输出:
XrMXrp1+UdQY2Er3FZHi8Q==
GenQR 输出:
iVBORw0KGgo...
2. JNI 加密入口分析

dll拖入ida反汇编静态分析一些看看,发现主加密流程很简单,不存在vmp, ollvm, 分析会简单很多。
反编译代码
int __stdcall Java_com_cqddb_jni_QR_DESEncrypt(size_t Size, int a2, void *a3)
{
size_t v3; // esi
void *v4; // edi
void *v5; // eax
int v6; // edx
size_t v7; // ebx
void *v8; // edi
v3 = Size;
v4 = a3;
v5 = (void *)(*(int (__stdcall **)(size_t, void *, _DWORD))(*(_DWORD *)Size + 676))(Size, a3, 0);
v6 = *(_DWORD *)v3;
a3 = v5;
v7 = (*(int (__stdcall **)(size_t, void *))(v6 + 656))(v3, v4);
Size = v7 + 2048;
v8 = operator new[](v7 + 2048);
memset(v8, 0, Size);
if ( DesEncrypt(a3, v7, v8, &Size) )
return 0;
else
return (*(int (__stdcall **)(size_t, void *))(*(_DWORD *)v3 + 668))(v3, v8);
}
导入一下jni 结构体,然后把一些类型推断一下,重命名得出
jstring __stdcall Java_com_cqddb_jni_QR_DESEncrypt(
_JNIEnv *out_put_len,
int a2,
jstring *_input_str
)
{
_JNIEnv *v3;
jstring *v4;
jstring *input_str;
const struct JNINativeInterface *functions;
_JNIEnv *input_len;
void *out_put_buffer;
v3 = out_put_len;
v4 = _input_str;
input_str = (jstring *)out_put_len->functions->GetStringUTFChars(
out_put_len,
_input_str,
0
);
functions = v3->functions;
_input_str = input_str;
input_len = (_JNIEnv *)functions->GetStringLength(&v3->functions, v4);
out_put_len = input_len + 512;
out_put_buffer = operator new[]((unsigned int)&input_len[512]);
memset(out_put_buffer, 0, (size_t)out_put_len);
if ( DesEncrypt(_input_str, (size_t)input_len, out_put_buffer, (rsize_t *)&out_put_len) )
return 0;
else
return v3->functions->NewStringUTF(&v3->functions, (const char *)out_put_buffer);
}
IDA 这里对 JNI 参数类型识别不准确,可以简化理解为:
Java_com_cqddb_jni_QR_DESEncrypt(JNIEnv *env, jobject thiz, jstring input)
核心逻辑:
char *input_str = env->GetStringUTFChars(input, 0);
int input_len = env->GetStringLength(input);
char *out = new char[input_len + 512];
memset(out, 0, input_len + 512);
DesEncrypt(input_str, input_len, out, &out_len);
return env->NewStringUTF(out);
真正的算法入口是:
DesEncrypt(input_str, input_len, out_put_buffer, &out_put_len);
3. DesEncrypt 函数分析
反编译代码为
int __stdcall DesEncrypt(jstring *input_str, int input_len, void *out_put_buffer, rsize_t *a4)
{
void *v4;
rsize_t *v5;
void **v7;
int v8;
rsize_t *v9;
void *Source[4];
rsize_t SourceSize;
unsigned int v12;
v9 = a4;
v8 = input_len + 64;
v4 = operator new[](input_len + 64);
memset(v4, 0, input_len + 64);
sub_10012190(
(int)"7WER90JN6UVGT6FGK",
input_str,
input_len,
(int)v4,
&v8
);
sub_1000BCD0(Source, v4, v8);
v5 = v9;
if ( out_put_buffer )
{
if ( *v9 < SourceSize )
{
*v9 = SourceSize;
if ( v12 >= 0x10 )
operator delete(Source[0]);
return 3;
}
v7 = (void **)Source[0];
if ( v12 < 0x10 )
v7 = Source;
memcpy_s(out_put_buffer, *v9, v7, SourceSize);
}
*v5 = SourceSize;
if ( v12 >= 0x10 )
operator delete(Source[0]);
return 0;
}
整理后逻辑:
int DesEncrypt(char *input, int input_len, char *output, size_t *output_len)
{
int enc_len = input_len + 64;
char *enc_buf = new char[input_len + 64];
memset(enc_buf, 0, input_len + 64);
sub_10012190(
"7WER90JN6UVGT6FGK",
input,
input_len,
enc_buf,
&enc_len
);
std::string encoded;
sub_1000BCD0(&encoded, enc_buf, enc_len);
memcpy(output, encoded.data(), encoded.size());
*output_len = encoded.size();
return 0;
}
可以看出:
sub_10012190 = 真正加密函数
sub_1000BCD0 = 加密结果编码函数
固定字符串: 7WER90JN6UVGT6FGK 被作为 key 参数传入。
4. Base64 编码函数分析
这个函数确实不好认,正常来说base64的话 不管是标准base64算法,还是魔改base64,会有一个base64 table,但是这个没有,借助AI分析得出,这个是base64
反编译代码片段:
int *__cdecl sub_1000BCD0(int *a1, char *a2, int a3)
{
...
if ( !a3 )
return a1;
do
{
--a3;
*(&v36 + v4++) = *a2;
++a2;
if ( v4 != 3 )
continue;
v32 = v36 >> 2;
v33 = (v37 >> 4) + 16 * (v36 & 3);
v34 = (v38 >> 6) + 4 * (v37 & 0xF);
*(_DWORD *)v35 = v38 & 0x3F;
...
}
while ( a3 );
if ( v4 )
{
if ( v4 < 3 )
{
memset(&v36 + v4, 0, 3 - v4);
}
v32 = v36 >> 2;
v33 = (v37 >> 4) + 16 * (v36 & 3);
v34 = (v38 >> 6) + 4 * (v37 & 0xF);
...
if ( v4 < 3 )
{
...
*((_BYTE *)v29 + a1[4]) = 61;
...
}
}
return a1;
}
关键特征:
3 字节输入 -> 4 字节输出
v36 >> 2
(v37 >> 4) + 16 * (v36 & 3)
(v38 >> 6) + 4 * (v37 & 0xF)
v38 & 0x3F
以及末尾补:
*((_BYTE *)v29 + a1[4]) = 61;
61 是 ASCII 字符: ‘=’
因此:
sub_1000BCD0 是 Base64 编码函数
5. 核心加密函数 sub_10012190
反编译代码:
int __cdecl sub_10012190(int fix_str, jstring *input_str, int input_len, int bufffer, _DWORD *len)
{
int v5;
int v6;
int v8;
int v9[35];
int v10[2];
v8 = 0;
v10[0] = 0;
v10[1] = 0;
sub_100494E0(v9);
v5 = sub_1004A480();
sub_10049FC0(v9, v5, 0, fix_str, v10);
sub_10049500((int)v9, bufffer, (int)&v8, input_str, input_len);
v6 = v8;
sub_10049690(v9, v8 + bufffer, &v8);
*len = v6 + v8;
sub_10049A20(v9);
return 0;
}
整理后逻辑:
int Encrypt(char *key, char *input, int input_len, char *out, int *out_len)
{
EVP_CIPHER_CTX ctx;
int outl = 0;
int tmplen = 0;
unsigned char iv[8] = {0};
EVP_CIPHER_CTX_init(&ctx);
cipher = sub_1004A480();
EVP_CipherInit_ex(&ctx, cipher, NULL, key, iv, 1);
EVP_CipherUpdate(&ctx, out, &outl, input, input_len);
EVP_CipherFinal_ex(&ctx, out + outl, &tmplen);
*out_len = outl + tmplen;
EVP_CIPHER_CTX_cleanup(&ctx);
return 0;
}
其中:
v10[0] = 0;
v10[1] = 0;
表示 8 字节 IV 全 0:
00 00 00 00 00 00 00 00
0 条评论