注意:文章仅用作学习使用,关键加密已被替换,不存在泄漏风险,如有侵权,联系作者删除

二 继续分析

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。

分类: app逆向

0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注

站点统计

  • 文章总数:332 篇
  • 分类总数:20 个
  • 标签总数:193 个
  • 运行天数:1719 天
  • 访问总数:1262749 人次

浙公网安备33011302000604

辽ICP备20003309号