某小说的算法分析

分析第一步当然是抓包。

很轻松抓到连接,开始分析其数据加解密。

  • 小说内容解密:
    通过调用栈发现其调用了jni方法进行解密

com.km.encryption.api.Security.decrypt(String str, String str2);

image.png

通过frida打印传入的值,发现 第一个参数传入null,没有实质意义。第二个参数传入小说的密文。

  • 那我可就打开ida了。


    image.png

发现其连动态注册都懒得搞,真是方便分析。

  • 进入 Java_com_km_encryption_api_Security_decrypt ,静态分析一波
image.png

发现其jni完全没有防护,真是怎么简单怎么来。
最终发现在jni调用的java方法,顿时语塞

int __fastcall AKeyGenerator::decode(int a1, JNIEnv *a2, int a3, int a4)
{
  jclass v8; // r0
  void *v9; // r10
  jmethodID v10; // r0
  jmethodID v11; // r8
  const char *v12; // r9
  int v13; // r5
  jstring v14; // r0
  void *v15; // r6
  void *v16; // r8
  JNIEnv v17; // r0
  int v18; // r0
  int v19; // r9
  JNIEnv v21; // r0
  void *v22; // r1
  int v23; // [sp+8h] [bp-20h]

  v8 = (*a2)->FindClass(a2, "com/km/encryption/aes/AESManager");
  if ( v8 )
  {
    v9 = v8;
    v10 = (*a2)->GetMethodID(a2, v8, "", "(Ljava/lang/String;Ljava/lang/String;)V");
    if ( !v10 )
      goto LABEL_8;
    v11 = v10;
    if ( (*a2)->GetStringLength(a2, (jstring)a3) != 6 )
      goto LABEL_8;
    v23 = a4;
    v12 = (*a2)->GetStringUTFChars(a2, a3, 0);
    v13 = operator new[](0x11u);
    *(_DWORD *)v13 = *(_DWORD *)v12;
    *(_WORD *)(v13 + 4) = *((_WORD *)v12 + 2);
    _memcpy_chk(v13 + 6, a1, *(_DWORD *)(a1 + 164), 11);
    *(_BYTE *)(v13 + 16) = 0;
    (*a2)->ReleaseStringUTFChars(a2, (jstring)a3, v12);
    v14 = (*a2)->NewStringUTF(a2, v13);
    if ( v14 )
    {
      v15 = v14;
      (*a2)->NewStringUTF(a2, (const char *)(a1 + 128));
      v16 = (void *)_JNIEnv::NewObject(a2, v9, v11, v15);
      v17 = *a2;
      if ( v16 )
      {
        v18 = (int)v17->GetMethodID(a2, v9, "decrypt", "(Ljava/lang/String;)[B");
        if ( v18 )
        {
          v19 = _JNIEnv::CallObjectMethod(a2, v16, v18, v23);
          (*a2)->ReleaseStringUTFChars(a2, v15, (const char *)v13);
          (*a2)->DeleteLocalRef(a2, v9);
          (*a2)->DeleteLocalRef(a2, v15);
          (*a2)->DeleteLocalRef(a2, v16);
          return v19;
        }
        (*a2)->ReleaseStringUTFChars(a2, v15, (const char *)v13);
        (*a2)->DeleteLocalRef(a2, v9);
        (*a2)->DeleteLocalRef(a2, v15);
        v21 = *a2;
        v22 = v16;
      }
      else
      {
        v17->ReleaseStringUTFChars(a2, v15, (const char *)v13);
        (*a2)->DeleteLocalRef(a2, v9);
        v21 = *a2;
        v22 = v15;
      }
    }
    else
    {
LABEL_8:
      v21 = *a2;
      v22 = v9;
    }
    v21->DeleteLocalRef(a2, v22);
  }
  return 0;
}

返回java层进行查看,原来就是AES加解密。这不就简单的吗

 public static byte[] decrypt(String str, String str2) throws Exception {
        try {
             byte[] decode = Base64.decode(str.getBytes("UTF-8"), 0);
             IvParameterSpec ivParameterSpec = new IvParameterSpec(decode, 0, 16);
             byte[] copyOfRange = Arrays.copyOfRange(decode, 16, decode.length);
             Cipher instance = Cipher.getInstance("AES/CBC/NoPadding");
             instance.init(2, new SecretKeySpec(str2.getBytes(), "AES"), ivParameterSpec);
             return instance.doFinal(copyOfRange);
         } catch (Exception unused) {
            return "fail".getBytes();
         }
 }

一眼看去就明白了。
编写frida脚本对 AES的init进行hook,获取其key值。

AES解密(AES/CBC/NoPadding) key: 242ccb8230d709e1(固定值)

新建java对其加解密。

        String data = "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";
        String result;
        try {
            result = new String(decrypt(data,"242ccb8230d709e1"));
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

成功输出内容,耐思。

image.png

再看看其sign

  • 通过其输出 为固定32位,怀疑是否为MD5。
  • 可惜测试下来,比对结果总是不对。

通过代码搜索最终定位到其同样定位了

com.km.encryption.api.Security.sign(byte[] bArr);

非常诧异,在ida里并未发现其静态注册的jni函数。刚开始还以为用了动态注册,用了yang大神的[frida_hook_libart],一番操作下来,也没有发现其动态注册,十分恼火。

  • 使用objection,对libcommon-encryption.so 的导出函数进行分析,发现其注册在 Java_com_km_encryption_api_Security_token
int __fastcall Java_com_km_encryption_api_Security_token(_JNIEnv *a1, int a2, void *a3)
{
  AndroidUtils *v5; // r5
  MD5KeyGenerator *v6; // r6
  size_t v7; // r5
  void *v8; // r9
  size_t v9; // r0
  size_t v10; // r8
  size_t v11; // r6
  char *v12; // r10
  char *v13; // r1
  int v14; // r4
  const void *v16; // [sp+8h] [bp-A8h]
  unsigned __int8 v17; // [sp+Ch] [bp-A4h] BYREF
  _BYTE v18[11]; // [sp+Dh] [bp-A3h] BYREF
  char v19[12]; // [sp+18h] [bp-98h] BYREF
  char v20[108]; // [sp+24h] [bp-8Ch] BYREF

  v5 = (AndroidUtils *)AndroidUtils::Instance(_stack_chk_guard);
  if ( !AndroidUtils::isInitialized(v5) )
    AndroidUtils::init(v5, a1);
  if ( AndroidUtils::isCheckFailed(v5) )
    return (int)a1->functions->NewStringUTF(&a1->functions, "FAIL");
  v6 = (MD5KeyGenerator *)MD5KeyGenerator::Instance(0);
  if ( !MD5KeyGenerator::isInitialized(v6) )
    MD5KeyGenerator::init(v6, a1);
  if ( !MD5KeyGenerator::isInitialized(v6) )
    return (int)a1->functions->NewStringUTF(&a1->functions, "FAIL");
  v7 = a1->functions->GetArrayLength(&a1->functions, a3);
  v8 = (void *)as_unsigned_char_array(a1, a3);
  v16 = (const void *)MD5KeyGenerator::getKeyData(v6);
  v9 = MD5KeyGenerator::getKeyDataSize(v6);
  v10 = v9 + v7;
  v11 = v9;
  v12 = (char *)operator new[](v9 + v7 + 1);
  qmemcpy(v12, v8, v7);
  qmemcpy(&v12[v7], v16, v11);
  v12[v10] = 0;
  std::string::basic_string((int)v19, v12);
  MessageDigestAlgorithm::MessageDigestAlgorithm((MessageDigestAlgorithm *)v20);
  MessageDigestAlgorithm::toStr((MessageDigestAlgorithm *)&v17);
  v13 = *(char **)&v18[7];
  if ( !(v17 << 31) )
    v13 = v18;
  v14 = (int)a1->functions->NewStringUTF(&a1->functions, v13);
  operator delete[](v8);
  operator delete[](v12);
  std::string::~string(&v17);
  std::string::~string(v19);
  return v14;
}
  • 发现其在里面做了字符串拼接,难道是加了salt

qmemcpy(v12, v8, v7);
qmemcpy(&v12[v7], v16, v11);

  • 直接编写frida脚本对其必经的函数
int __fastcall std::string::basic_string(int a1, char *s)
{
  size_t v4; // r0

  *(_DWORD *)a1 = 0;
  *(_DWORD *)(a1 + 4) = 0;
  *(_DWORD *)(a1 + 8) = 0;
  v4 = strlen(s);
  std::string::__init(a1, s, v4);
  return a1;
}

frida 脚本如下:

function hooknative(){
    var libencrypt_base = Process.findModuleByName("libcommon-encryption.so");
    if(libencrypt_base){
        console.log("libencrypt_base",JSON.stringify(libencrypt_base));
        var sub_sign = libencrypt_base.base.add(0xD329);
        Interceptor.attach(sub_sign,{
            onEnter:function(args){
                console.log("sub_sign -->",ptr(args[1]).readCString())
            },onLeave:function(){

            }
        })
    }
}

输入如下:

sub_sign --> AUTHORIZATION=app-version=60000application-id=com.kmxs.readerchannel=qm-tengxun_lfis-white=1net-env=1platform=androidqm-params=cLGUuq2-HTZ5gI9wgI9wgI9QgekzN3Me4To-th9wgI9QgI9wgI9wgI9wgI9wH5w5pyRlmqN2tq2-HTZ5gT9Lgh9EgT4nghfeghFnpho5pI4e4hHLpqG-gTu24TGzAIfLAIfLAIG-AqHlAI9n4eOLpyNzNhs2gefl4h05taGQ4qg5A5GsFeZeNeZMgeZUgeZEAI1aN5HjHSNYOLUlpCH5A5HrtT97gaHjHSkLuCNMpqFQmqF5A5G0ufZMu_kAulu7feNqByGjiMo5OMOEcSKUcIuCkexlFfpxpSGLkooVBz1hpeRwuCpy3EGnBIu0c3QHBzoshMdlBfo3fI4U3UgUfCgDgCsDBqNY4eRYBUGSH5w5mqU2m3HWH5HjHzUDpyRjHTZ5fy2rpqw5taGEByHQmqU2m3HWH5HjHSuj45UUmqF5A5GsFegENIgUgeOrFT45taGTBy22BSFQmqF5A5HwghHegeNz4hgLgqFEges5H5w54SGxBzF5A5GSBlJSByf5taGD4q2-HTZ5HSM=reg=d3dGiJc651gSQ8w1

  • 果不其然,发现其在需加密的字符串后面拼接了16个字节(d3dGiJc651gSQ8w1)
    使用在线MD5加密,测试了一下,结果完全对上。

奈斯,真是愉快的分析。

你可能感兴趣的:(某小说的算法分析)