注意:文章仅用作学习使用,关键加密已被替换,不存在泄漏风险,如有侵权,联系作者删除
二 继续分析
6. 确认使用 OpenSSL EVP
在 sub_10049CA0 中出现明显 OpenSSL EVP 字符串:
".\\crypto\\evp\\evp_enc.c"
以及:
"EVP_CIPHER_CTX_iv_length(ctx) <= (int)sizeof(ctx->iv)"
"ctx->cipher->block_size == 1 || ctx->cipher->block_size == 8 || ctx->cipher->block_size == 16"
这说明 DLL 中静态编译了 OpenSSL 相关代码。
sub_10049FC0:
int __cdecl sub_10049FC0(void *a1, int a2, int a3, int a4, void *Src)
{
return sub_10049CA0(a1, a2, a3, a4, Src, 1);
}
a6 = 1 表示加密方向。
sub_10049CA0 本质上对应:
EVP_CipherInit_ex(ctx, cipher, engine, key, iv, enc);
7. 确认 EVP_CIPHER 算法对象
sub_1004A480:
void *sub_1004A480()
{
return &unk_100C7E48;
}
算法对象位于:
.rdata:100C7E48
对应数据:
.rdata:100C7E48 word_100C7E48 dw 2Ch
.rdata:100C7E4A db 0
.rdata:100C7E4B db 0
.rdata:100C7E4C db 8
.rdata:100C7E4D db 0
.rdata:100C7E4E db 0
.rdata:100C7E4F db 0
.rdata:100C7E50 db 18h
.rdata:100C7E51 db 0
.rdata:100C7E52 db 0
.rdata:100C7E53 db 0
.rdata:100C7E54 db 8
.rdata:100C7E55 db 0
.rdata:100C7E56 db 0
.rdata:100C7E57 db 0
.rdata:100C7E58 db 2
.rdata:100C7E59 db 12h
.rdata:100C7E5A db 0
.rdata:100C7E5B db 0
.rdata:100C7E5C dd offset sub_1004A4E0
.rdata:100C7E60 dd offset sub_1004A1A0
默认是按byte展示的,按d转成 DWORD 理解:
100C7E48 dd 0000002Ch ; nid
100C7E4C dd 00000008h ; block_size
100C7E50 dd 00000018h ; key_len
100C7E54 dd 00000008h ; iv_len
100C7E58 dd 00001202h ; flags
100C7E5C dd offset sub_1004A4E0 ; init
100C7E60 dd offset sub_1004A1A0 ; do_cipher
重点字段:
nid = 0x2C
block_size = 8
key_len = 0x18 = 24
iv_len = 8
flags = 0x1202
根据 OpenSSL 源码 EVP_CIPHER 结构:
typedef struct evp_cipher_st {
int nid;
int block_size;
int key_len;
int iv_len;
unsigned long flags;
int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char *iv, int enc);
int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out,
const unsigned char *in, size_t inl);
int (*cleanup)(EVP_CIPHER_CTX *);
int ctx_size;
...
} EVP_CIPHER;
可以判断:
block_size = 8
key_len = 24
iv_len = 8
因此算法不是单 DES,而是 3DES。
8. 确认是 3DES 而不是 DES
核心证据:
8.1 key_len 为 24
key_len = 0x18 = 24
常见 DES/3DES key 长度:
DES = 8 字节
2-key 3DES = 16 字节
3-key 3DES = 24 字节
因此这里是 3-key 3DES。
8.2 初始化函数分三次设置 DES key schedule
反编译代码:
int __cdecl sub_1004A4E0(int a1, int a2)
{
int v2;
v2 = *(_DWORD *)(a1 + 96);
*(_DWORD *)(v2 + 384) = 0;
sub_1004DC30(a2, v2);
sub_1004DC30(a2 + 8, v2 + 128);
sub_1004DC30(a2 + 16, v2 + 256);
return 1;
}
这里连续调用三次:
sub_1004DC30(a2, v2);
sub_1004DC30(a2 + 8, v2 + 128);
sub_1004DC30(a2 + 16, v2 + 256);
含义:
K1 = key[0:8]
K2 = key[8:16]
K3 = key[16:24]
每 8 字节生成一套 DES key schedule。
这正是 3DES 的典型特征。
如果是单 DES,只会有一套 key schedule。
9. 确认 key 实际内容
固定字符串位置:
.rdata:100B1B6C b2qaz0olm8uhbf7 db '7WER90JN6UVGT6FGK',0
.rdata:100B1B6C ; DATA XREF: DesEncrypt+49↑o
.rdata:100B1B6C ; DesDecrypt+84↑o
.rdata:100B1B7E align 10h
.rdata:100B1B80 flt_100B1B80 dd 4.0
字符串: 7WER90JN6UVGT6FGK长度为 17 字节。
但是 EVP_CIPHER 的 key_len 是 24 字节,并且初始化函数没有调用 strlen,而是直接读取:
a2
a2 + 8
a2 + 16
所以实际读取的是从字符串地址开始的连续 24 字节。
由于字符串后面是:
db '7WER90JN6UVGT6FGK',0
align 10h
因此后面补齐为 0。
实际 24 字节 key:
31 51 41 5A 30 4F 4B 4D
37 59 47 42 46 37 53 44
46 00 00 00 00 00 00 00
ASCII 表示:
7WER90JN6UVGT6FGK\x00\x00\x00\x00\x00\x00\x00
也就是: 7WER90JN6UVGT6FGK" + 7 个 0x00
10. 加密算法最终结论
最终还原参数:
算法:3DES
OpenSSL 名称:DES-EDE3-CBC
模式:CBC
Key 长度:24 字节
Key:
31 51 41 5A 30 4F 4B 4D
37 59 47 42 46 37 53 44
46 00 00 00 00 00 00 00
IV:
00 00 00 00 00 00 00 00
Padding: OpenSSL EVP 默认 PKCS#5/PKCS#7 Padding
输出编码: Base64
整体流程:
明文字符串
↓ UTF-8
3DES-CBC 加密
↓
PKCS#5/PKCS#7 Padding
↓
密文二进制
↓
Base64 编码
↓
返回 Java String
测试样例:
输入:
testadmin
输出:
XrMXrp1+UdQY2Er3FZHi8Q==
11. GenQR 方法分析
GenQR 的输出样例开头:
iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJ...
Base64 解码后 PNG 头:
89 50 4E 47 0D 0A 1A 0A
说明 GenQR 返回的是 PNG 图片的 Base64。
PNG 头中可以看到:
宽:0xC8 = 200
高:0xC8 = 200
因此生成的是:
200 x 200 PNG
结合测试:
输入:
testadmin
DESEncrypt 输出:
XrMXrp1+UdQY2Er3FZHi8Q==
GenQR 输出:
iVBORw0KGgo…
将 GenQR 返回的 Base64 解码为 PNG 后,扫描二维码,得到的内容正是:
XrMXrp1+UdQY2Er3FZHi8Q==
因此可以确认 GenQR 流程:
输入字符串
↓
调用 DES/3DES 加密流程
↓
得到加密 Base64 字符串
↓
将该字符串生成二维码
↓
二维码保存为 PNG 二进制
↓
PNG 二进制再做 Base64
↓
返回 Java String
等价表达:
GenQR(input) = Base64( PNG( QRCode( DesEncrypt(input) ) ) )
12. 关于二维码白边问题
DLL 生成的二维码看起来没有白边,而常见二维码库生成时可能存在白边。
原因通常是:
二维码库会保留 quiet zone / margin
而 DLL 的实现可能是:
直接把二维码有效矩阵区域铺满 200 x 200 图片
或者:生成后裁剪掉外部白边
因此用其他库复现时,如果只是设置:MARGIN = 0
仍然可能出现白边,因为二维码模块数量与目标图片尺寸不一定整除,库会居中缩放,边缘留下空白。
DLL 更接近:
取二维码有效区域
裁剪白边
缩放到 200 x 200
输出 PNG
不过这只影响 PNG 外观,不影响二维码扫描内容。
13. 整体调用链总结
加密接口调用链:
Java_com_cqddb_jni_QR_DESEncrypt
↓
GetStringUTFChars
↓
DesEncrypt
↓
sub_10012190
↓
OpenSSL EVP_CipherInit_ex
↓
OpenSSL EVP_CipherUpdate
↓
OpenSSL EVP_CipherFinal_ex
↓
sub_1000BCD0 Base64
↓
NewStringUTF
二维码接口调用链:
Java_com_cqddb_jni_QR_GenQR
↓
DesEncrypt
↓
得到加密 Base64 字符串
↓
生成二维码 PNG
↓
PNG 二进制 Base64
↓
NewStringUTF
14. 关键识别点总结
14.1 判断 Base64
特征:
3 字节输入变 4 字节输出
& 0x3F
>> 2
>> 4
>> 6
末尾补 '='
代码中:
*((_BYTE *)v29 + a1[4]) = 61; 61 即 ‘=’。
14.2 判断 OpenSSL EVP
特征字符串:
.\crypto\evp\evp_enc.c
EVP_CIPHER_CTX_iv_length(ctx)
ctx->cipher->block_size == 1 || 8 || 16
14.3 判断 3DES
关键字段:
block_size = 8
key_len = 24
iv_len = 8
以及三次 key schedule:
sub_1004DC30(a2, v2);
sub_1004DC30(a2 + 8, v2 + 128);
sub_1004DC30(a2 + 16, v2 + 256);
14.4 判断 CBC
EVP_CIPHER 对象的 flags:
flags = 0x1202
其中模式字段对应 CBC。
同时 IV 长度为: iv_len = 8
并且初始化时传入:
int v10[2] = {0, 0};
即 8 字节全 0 IV。
0 条评论