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庫,並把傳入的鑰匙作爲鹽,進行了加密。

好了,分析到此結束吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章