前言

最近无聊,想找点事情做,刚好又在学习安卓逆向,又偶尔在玩这个游戏,拿来练练手也挺好。写的不是很好,请各位看官看个热闹就好。

使用工具

  • Fiddler 任意版本 、jadx、IDA
  • 雷电模拟器
  • 任意一款编程工具

教程内容

打开游戏,随便登录个账号密码抓个登录包。

可以发现返回内容是没经过加密,但是请求数据是经过加密了的

寻找加密过程

先看看APK是否加壳了

发现游戏是“易盾”的壳,这里我就直接脱壳,把拖出来的DEX直接丢进jadx里,全局搜索提交地址的部分信息 sdk/login.do 试着找出关键登录函数

继续跟进搜索 USERNAME_PWD_LOGIN_URL 被谁调用了

可以看到只有一个地方有使用

可以看到 他在这里面找就大概率能找到登录封装过程了。hashmap就是他的请求内容了,我们继续跟进看 loginRequestTask

到了这里,我们看到了,后面的 else 段,有个解密,可是我们并没有发现返回值是加密的,所以我们先不管,我们继续跟进到 asyncPostNew

继续跟进,这里面还是没有发现我们要的加密函数

到了这里,我们发现了一个处理params的函数,看到这里,我们就知道,加密函数一定到这里面,赶紧继续

发现了,原来是把map转成json 最后 进行一次Aes加密,既然是AES加密,那就会有个Key,我们现在就是要找到Key,我们继续跟进。

到了这里,发现,他的加密都在JNI层,这时候,我们就得用到IDA了,那我们在lib/armeabi-v7a里找到libSocketHelper.so 拖入 IDA里

还是很好看的,这里我们就得到了Key还有个IV值,我们用易语言解密看看参数,到底有啥数据

把参数带进入,运行,成功解密出来了数据,但是从参数里,我们知道了password是加密的,那我们还得继续,我们继续回到 userNamePwdLogin 我们看看是哪里调用了这个函数

最后,我们来到了这里,也发现了,password 参数 是使用DES加密,我们还是和上面一样,继续根据,寻找Key

DES加密又是走的JNI层,我们继续看看

这里说明了 Key 为 leiting 然后跟进去就会发现
int __fastcall DesByJ::encodeAndHexToByte(int *a1, int a2, const char *a3)
{
  const char *v6; // r5
  size_t v7; // r0
  int v8; // r8
  size_t v9; // r0
  int v10; // r11
  size_t v11; // r0
  int v12; // r5
  size_t v13; // r0
  int v14; // r0
  int v15; // r9
  int v16; // r6
  int v17; // r5
  int v18; // r0
  int v19; // r10
  int v20; // r0
  int v21; // r0
  int v22; // r1
  int v23; // r5
  int v24; // r0
  const char *v25; // r2
  int v26; // r5
  int v27; // r0
  int v28; // r5
  int v29; // r9
  int v31; // r0
  int v32; // r8
  int v33; // [sp+4h] [bp-24h]
  int v34; // [sp+8h] [bp-20h]

  if ( a2 && (*(int (__fastcall **)(int *, int))(*a1 + 656))(a1, a2) >= 1 )
  {
    v6 = (const char *)(*(int (__fastcall **)(int *, int, _DWORD))(*a1 + 676))(a1, a2, 0);
    v7 = strlen(v6);
    v8 = (*(int (__fastcall **)(int *, size_t))(*a1 + 704))(a1, v7);
    v9 = strlen(v6);
    (*(void (__fastcall **)(int *, int, _DWORD, size_t, const char *))(*a1 + 832))(a1, v8, 0, v9, v6);
    v10 = (*(int (__fastcall **)(int *, const char *))(*a1 + 24))(a1, "com/leiting/sdk/SocketHelper");
    v11 = strlen(a3);
    v12 = (*(int (__fastcall **)(int *, size_t))(*a1 + 704))(a1, v11);
    v13 = strlen(a3);
    (*(void (__fastcall **)(int *, int, _DWORD, size_t, const char *))(*a1 + 832))(a1, v12, 0, v13, a3);
    v14 = (*(int (__fastcall **)(int *, int, const char *, const char *))(*a1 + 452))(
            a1,
            v10,
            "getKey",
            "([B)Ljava/security/Key;");
    v33 = v12;
    v15 = _JNIEnv::CallStaticObjectMethod(a1, v10, v14, v12);
    v16 = (*(int (__fastcall **)(int *, const char *))(*a1 + 24))(a1, "javax/crypto/Cipher");
    v17 = (*(int (__fastcall **)(int *, int, const char *, const char *))(*a1 + 452))(
            a1,
            v16,
            "getInstance",
            "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
    v18 = (*(int (__fastcall **)(int *, const char *))(*a1 + 668))(a1, "DES/ECB/PKCS5Padding");
    v19 = _JNIEnv::CallStaticObjectMethod(a1, v16, v17, v18);
    v20 = (*(int (__fastcall **)(int *, int, const char *, const char *))(*a1 + 132))(
            a1,
            v16,
            "init",
            "(ILjava/security/Key;)V");
    v34 = v15;
    _JNIEnv::CallVoidMethod(a1, v19, v20, 1, v15);
    v21 = (*(int (__fastcall **)(int *))(*a1 + 60))(a1);
    v22 = *a1;
    if ( v21 )
    {
      (*(void (__fastcall **)(int *))(v22 + 64))(a1);
      (*(void (__fastcall **)(int *))(*a1 + 68))(a1);
      v23 = (*(int (__fastcall **)(int *, const char *))(*a1 + 24))(a1, "java/lang/Exception");
      v24 = *a1;
      v25 = "Des init fail!!";
    }
    else
    {
      v27 = (*(int (__fastcall **)(int *, int, const char *, const char *))(v22 + 132))(a1, v16, "doFinal", "([B)[B");
      v28 = _JNIEnv::CallObjectMethod(a1, v19, v27, v8);
      if ( !(*(int (__fastcall **)(int *))(*a1 + 60))(a1) )
      {
        v29 = v33;
        if ( v28 )
        {
          v31 = (*(int (__fastcall **)(int *, int, const char *, const char *))(*a1 + 452))(
                  a1,
                  v10,
                  "byteArr2HexStr",
                  "([B)Ljava/lang/String;");
          v26 = _JNIEnv::CallStaticObjectMethod(a1, v10, v31, v28);
          if ( (*(int (__fastcall **)(int *))(*a1 + 60))(a1) )
          {
            (*(void (__fastcall **)(int *))(*a1 + 64))(a1);
            (*(void (__fastcall **)(int *))(*a1 + 68))(a1);
            v32 = (*(int (__fastcall **)(int *, const char *))(*a1 + 24))(a1, "java/lang/Exception");
            (*(void (__fastcall **)(int *, int, const char *))(*a1 + 56))(a1, v32, "Encode byteArr2HexStr fail !!");
            (*(void (__fastcall **)(int *, int))(*a1 + 92))(a1, v32);
          }
        }
        else
        {
          v26 = 0;
        }
        goto LABEL_9;
      }
      (*(void (__fastcall **)(int *))(*a1 + 64))(a1);
      (*(void (__fastcall **)(int *))(*a1 + 68))(a1);
      v23 = (*(int (__fastcall **)(int *, const char *))(*a1 + 24))(a1, "java/lang/Exception");
      v24 = *a1;
      v25 = "please check input argument, last block incomplete in decryption";
    }
    (*(void (__fastcall **)(int *, int, const char *))(v24 + 56))(a1, v23, v25);
    (*(void (__fastcall **)(int *, int))(*a1 + 92))(a1, v23);
    v26 = 0;
    v29 = v33;
LABEL_9:
    (*(void (__fastcall **)(int *, int))(*a1 + 92))(a1, v16);
    (*(void (__fastcall **)(int *, int))(*a1 + 92))(a1, v10);
    (*(void (__fastcall **)(int *, int))(*a1 + 92))(a1, v29);
    (*(void (__fastcall **)(int *, int))(*a1 + 92))(a1, v34);
    (*(void (__fastcall **)(int *, int))(*a1 + 92))(a1, v19);
    return v26;
  }
  return 0;
}
以上的伪代码 还是比较好分析的,既然知道了加密方法 就可以写程序跑一下了。

我们随便输入账号密码,试试

请求成功了,返回值也是正确的,我们使用一个正确的账号密码试试看

这里,我们就奇怪了,为啥是加密的,试了几次,只要账号密码是正确的,都是加密的,别急,还记得 loginRequestTask 函数中,最后请求结果会取出 data 字段,然后使用AES解密,我们试试看。

测试结果,也是和我们预想的一样

返回关键信息
表单名称说明
sidv6dy672w游戏ID
tokenf2670b7b30eededf25af7c94ccb6a73e登录令牌
登录成功后,还会二次令牌登录游戏,这里我就不演示了,大家可以自己尝试一下

写在最后

本篇文章仅供学习交流,提供一个学习思路。致谢所有为逆向工作做出贡献的所有大佬。

附上易语言代码

最后修改:2022 年 12 月 13 日
如果觉得我的文章对你有用,请随意赞赏