APP登陆协议之Native层分析

1、整体分析

首先登陆,抓包分析,如下:
其中密码为:123456
这里写图片描述

可以看到,对密码进行了加密。

2、加密算法java层分析

定位到java代码如下:

  protected void buildSignRequestParams(String paramString, HashMap<String, String> paramHashMap)
  {
    paramHashMap.put("username", this.a);
    try
    {
      this.b = GameCenterNative.DesCbcEncrypt(this.b);
      paramHashMap.put("password", this.b);
      paramHashMap.put("deviceIdentifier", gc.a().getUniqueID());
      paramHashMap.put("captcha", this.d);
      paramHashMap.put("captchaId", this.c);
      paramHashMap.put("info", "1");
      long l = gf.a().getDateline();
      paramHashMap.put("dateline", "" + l / 1000L);
      return;
    }
    catch (Error paramString)
    {
      for (;;)
      {
        paramString.printStackTrace();
      }
    }
  }

可以看到调用了GameCenterNative.DesCbcEncrypt对密码进行了加密,但是没有看到sign签名的计算过程。看下该类,如下:

public class jr extends SignDataProvider

继承自SignDataProvider类,来到该类,如下:

public void buildRequestParams(String paramString, RequestParams paramRequestParams)
  {
    HashMap localHashMap = new HashMap();
    buildSignRequestParams(paramString, localHashMap);
    paramString = new ArrayList(localHashMap.keySet());
    Collections.sort(paramString);
    StringBuilder localStringBuilder = new StringBuilder();
    int i = 0;
    while (i < paramString.size())
    {
      String str1 = (String)paramString.get(i);
      String str2 = (String)localHashMap.get(str1);
      if (!TextUtils.isEmpty(str2))
      {
        paramRequestParams.put(str1, str2);
        localStringBuilder.append(str2);
      }
      i += 1;
    }
    paramRequestParams.put("sign", buildSignValue(localStringBuilder.toString()));
  }

  public abstract void buildSignRequestParams(String paramString, HashMap<String, String> paramHashMap);

看到没有,里面有sign的签名算法,在计算sign之前,调用了子类的buildSignRequestParams方法。而sign签名算法为buildSignValue,如下:

protected String buildSignValue(String paramString)
  {
    try
    {
      paramString = GameCenterNative.getServerApi(paramString);
      return paramString;
    }
    catch (UnsatisfiedLinkError paramString)
    {
      paramString.printStackTrace();
    }
    return "";
  }

可以看到最终调用了GameCenterNative.getServerApi(paramString);

下面来看下上面提到的对密码加密的算法GameCenterNative.DesCbcEncrypt和对sign签名的算法GameCenterNative.getServerApi(paramString);如下:

public static final native String DesCbcEncrypt(String paramString);

public static final native String getServerApi(String paramString);

可以,看到这两个加密函数在native层进行了实现。
下面,我们跟进native层看看。

2、加密算法native层分析

用IDA打开so文件,看下导出函数,发现没有java_等函数,所有猜测,此处应该采用了动态注册。找到JJNI_OnLoad,如下
这里写图片描述

可以看到动态注册过程,看下注册数组,如下:
这里写图片描述

注册过程,就分析到此为止,下面继续分析,上面说的那两个加密函数。
1、DesCbcEncrypt()函数分析:

对应的汇编代码,如下
这里写图片描述

由于DesCbcEncrypt参数应为(JNIEnv *env, jobject jobj, jobject input),通过汇编代码可知,输入待加密的字符串放到类R1寄存器中

这里写图片描述

其中 COMMON_KEY为 “u!~#7@w0”
这里写图片描述

F5后的代码为:

int __fastcall des_encrypt(JNIEnv_ *a1, jobject obj, char *a3)
{
  JNIEnv_ *env; // r9@1
  char *commonkey; // r10@1
  int result; // r0@2
  const char *v6; // r0@3
  const char *v7; // r8@3
  signed int v8; // r0@4
  signed int v9; // r6@4
  int v10; // r5@5
  void *v11; // r0@7
  void *inputBuffer; // r7@7
  size_t v13; // r0@8
  void *v14; // r4@8
  void *v15; // r5@9
  int v16; // r4@10

  env = a1;
  commonkey = a3;
  if ( obj
    && (v6 = GetStringUnicodeChars(a1, obj, "utf-8"), (v7 = v6) != 0)
    && ((v8 = strlen(v6), v9 = v8, !(v8 << 29)) ? (v10 = v8 + 8) : (v10 = ((v8 + (v8 < 0 ? 7 : 0)) & 0xFFFFFFF8) + 8),
        (v11 = calloc(v10, 1u), (inputBuffer = v11) != 0)
     && (memset(v11, 8 - v9 % 8, v10),
         v13 = strlen(v7),
         memcpy(inputBuffer, v7, v13),
         v14 = calloc(v10 + 1, 1u),
         memset(v14, 0, v10 + 1),
         v14)
     && (crypt(inputBuffer, v14, v10, commonkey, 1),
         v15 = php_base64_encode(v14, v10, 0),
         free(v14),
         free(inputBuffer),
         free(v7),
         v15)) )
  {
    v16 = (env->functions->NewStringUTF)(env, v15);
    free(v15);
    result = v16;
  }
  else
  {
    result = 0;
  }
  return result;
}

下面来分析des_encrypt函数,流程为:
A、调用GetStringUnicodeChars以UTF-8得到输入的字符串,其中参数分别为R0 : JNIEnv_ * env , R1 : jobject input, char* utf-8
整理后,代码如下:

void *__fastcall GetStringUnicodeChars(JNIEnv_ *a1, jobject a2, char *a3)
{
  jobject obj; // r6@1
  JNIEnv_ *v4; // r4@1
  char *str; // r5@1
  jclass clazz; // r0@1
  jmethodID getBytes; // r7@1
  jstring inputStr; // r0@1
  int v9; // r6@1
  signed int v10; // r5@1
  const void *v11; // r8@1
  void *v12; // r7@2

  obj = a2;
  v4 = a1;
  str = a3;
  clazz = (a1->functions->FindClass)();
  getBytes = (v4->functions->GetMethodID)(v4, clazz, "getBytes", "(Ljava/lang/String;)[B");
  inputStr = (v4->functions->NewStringUTF)(v4, str);
  v9 = (v4->functions->CallObjectMethod)(v4, obj, getBytes, inputStr);
  v10 = (v4->functions->GetArrayLength)(v4, v9);
  v11 = (v4->functions->GetByteArrayElements)(v4, v9, 0);
  if ( v10 <= 0 )
  {
    v12 = 0;
  }
  else
  {
    v12 = malloc(v10 + 1);
    memcpy(v12, v11, v10);
    *(v12 + v10) = 0;
  }
  (v4->functions->ReleaseByteArrayElements)(v4, v9, v11, 0);
  return v12;
}

B、调用crypt进行加密,整理后的代码如下:

int __fastcall crypt(char *input, int a2, int a3, char *commonkey, int a5)
{
  char *key; // r6@1
  char *inputStr; // r9@1
  int encodeBuffer; // r8@1
  int inputStrLen; // r7@1
  size_t keyLength; // r0@1
  int result; // r0@1
  char keySchedule; // [sp+Ch] [bp-B4h]@1
  char keyEncrypt; // [sp+8Ch] [bp-34h]@1
  int ivec; // [sp+94h] [bp-2Ch]@1
  signed int v14; // [sp+98h] [bp-28h]@1
  int v15; // [sp+9Ch] [bp-24h]@1

  key = commonkey;
  inputStr = input;
  encodeBuffer = a2;
  inputStrLen = a3;
  v15 = _stack_chk_guard;
  keyLength = strlen(commonkey);
  memcpy(&keyEncrypt, key, keyLength);
  DES_set_key_unchecked(&keyEncrypt, &keySchedule);
  ivec = 2018915346;
  v14 = -271733872;
  result = DES_ncbc_encrypt(inputStr, encodeBuffer, inputStrLen, &keySchedule, &ivec, a5);
  if ( v15 != _stack_chk_guard )
    _stack_chk_fail(result);
  return result;
}

可以看到采用类openssl库进行的加密。
C、用php_base64_encode对加密后字符串进行base64加密,参数为R0 : DES加密后的字符串,R1 : 字符串长度 R3: 0

_BYTE *__fastcall php_base64_encode(char *DESencodeStr, signed int strLength, _DWORD *a3)
{
  char *v3; // r5@1
  signed int v4; // r6@1
  _DWORD *v5; // r7@1
  _BYTE *result; // r0@2
  _BYTE *i; // r4@2
  _BYTE *v8; // r3@5
  unsigned int v9; // r3@6
  char *v10; // r12@6
  unsigned int v11; // r3@6
  char v12; // r1@6
  char *v13; // r12@6
  unsigned int v14; // r3@6
  char v15; // r1@6
  unsigned int v16; // r12@8
  unsigned int v17; // r2@8
  int v18; // r12@8
  unsigned int v19; // r5@9
  char v20; // r1@9

  v3 = DESencodeStr;
  v4 = strLength;
  v5 = a3;
  if ( (strLength + 2 < 0) ^ __OFADD__(strLength, 2) )
  {
    result = 0;
    if ( a3 )
      *a3 = 0;
    else
      result = 0;
  }
  else
  {
    result = malloc(4 * ((strLength + 2) / 3) + 1);
    for ( i = result; ; *(i - 1) = v14 )
    {
      v8 = i;
      if ( v4 <= 2 )
        break;
      v9 = *v3;
      v4 -= 3;
      v3 += 3;
      i += 4;
      v10 = &aAbcdefghijklmn[16 * (v9 & 3)];
      *(i - 4) = aAbcdefghijklmn[v9 >> 2];
      v11 = *(v3 - 2);
      v12 = v10[v11 >> 4];
      v13 = &aAbcdefghijklmn[4 * (v11 & 0xF)];
      *(i - 3) = v12;
      v14 = *(v3 - 1);
      v15 = v13[v14 >> 6];
      LOBYTE(v14) = aAbcdefghijklmn[v14 & 0x3F];
      *(i - 2) = v15;
    }
    if ( v4 )
    {
      v16 = *v3;
      v17 = v16 >> 2;
      v18 = v16 & 3;
      *i = aAbcdefghijklmn[v17];
      if ( v4 == 2 )
      {
        v19 = v3[1];
        v8 = i + 4;
        i[3] = 61;
        v20 = aAbcdefghijklmn[4 * (v19 & 0xF)];
        i[1] = *(&aAbcdefghijklmn[16 * v18] + (v19 >> 4));
        i[2] = v20;
      }
      else
      {
        v8 = i + 4;
        i[1] = aAbcdefghijklmn[16 * v18];
        i[2] = 61;
        i[3] = 61;
      }
    }
    if ( v5 )
      *v5 = v8 - result;
    *v8 = 0;
  }
  return result;
}

2、getServerApi()函数分析:
汇编代码如下:
这里写图片描述

待加密的参数传递到了R1寄存器
这里写图片描述

可以看到采用了MD5加密,MD5_SERVER_KEY为”ef2vx#sf*^FlklSD*9sdf(m$&qw%d7po”
进行分析:
分析之前,需要说明下,IDA有时识别不正确,如下:
这里写图片描述

才是,可以右键 强制转换
这里写图片描述
可以得到
这里写图片描述

在介绍完技巧后,下面开始分析 md5_string函数,整理后代码如下:

char *__fastcall md5_string(JNIEnv_ *a1, char *input, const char *key)
{
  const char *commonkey; // r7@1
  JNIEnv_ *env; // r5@1
  char *inputStr; // r9@1
  const char *v6; // r6@2
  size_t v7; // r4@4
  size_t v8; // r0@4
  char *v9; // r4@4
  size_t v10; // r0@5
  size_t v11; // r0@7
  int v12; // r8@7
  int v13; // r3@8
  char *result; // r0@11
  char v15[16]; // [sp+8h] [bp-B8h]@7
  char s; // [sp+18h] [bp-A8h]@7
  char v17; // [sp+3Ch] [bp-84h]@7
  int v18; // [sp+94h] [bp-2Ch]@1

  commonkey = key;
  env = a1;
  inputStr = input;
  v18 = _stack_chk_guard;
  if ( input )
    v6 = a1->functions->GetStringUTFChars(&a1->functions, input, 0);
  else
    v6 = &unk_DA24;
  v7 = strlen(v6);
  v8 = strlen(commonkey);
  v9 = calloc(v8 + v7 + 2, 1u);
  if ( v9 )
  {
    v10 = strlen(v6);
    strncpy(v9, v6, v10 + 1);
    if ( commonkey )
      strcat(v9, commonkey);
    memset(&s, 0, 33u);
    memset(&v17, 0, 0x58u);
    MD5Init(&v17);
    v11 = strlen(v9);
    MD5Update(&v17, v9, v11);
    memset(v15, 0, 0x10u);
    v12 = 0;
    MD5Final(v15, &v17);
    do
    {
      v13 = v15[v12++];
      sprintf(&s, "%s%02x", &s, v13);
    }
    while ( v12 != 16 );
    free(v9);
    if ( inputStr )
      (env->functions->ReleaseStringUTFChars)(env, inputStr, v6);
    result = (env->functions->NewStringUTF)(env, &s);
  }
  else
  {
    result = inputStr;
  }
  if ( v18 != _stack_chk_guard )
    _stack_chk_fail(result);
  return result;
}

代码比较好理解,主要采用类openssl库,并把传入的钥匙作为盐,进行了加密。

好了,分析到此结束吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值