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

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
分类: app逆向

0 条评论

发表回复

Avatar placeholder

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

站点统计

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

浙公网安备33011302000604

辽ICP备20003309号