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庫,並把傳入的鑰匙作爲鹽,進行了加密。
好了,分析到此結束吧!