APP登陆协议之Native层分析

来源:互联网 发布:马赛克视频还原软件 编辑:程序博客网 时间:2024/05/22 16:49

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 0
原创粉丝点击