最近在研究微爱
这款应用的请求时,发现每一条请求都携带了sig
这个参数,并且sig
随着每一次登录都会变化,不同的行为触发的HTTP请求所携带的sig也都不相同。
图片中的sig
是经过URLEecode的,我把他Decode一下得到gO5EnwNaGxqEWk/uyGWQn6+sktk=
。
这显然就是一个经过了Base64编码的字符串,但在对该字符串Base64解码的时候发现结果是乱码,这说明该串是被加密的。
这里我注意到sig
是28位的base64
,所以我猜测是不是对某些字符串做了MD5
的16位编码之后再base64
编码,测试了几个字符串结果都与sig
不同,只能作罢。
要弄明白加密算法是什么,只剩下了一条路,反编译APK并分析了。
把手机上的APK com.welove520.welove.apk
拷贝到电脑中,使用dex2jar:
d2j-dex2jar.bat com.welove520.welove.apk
得到com.welove520.welove-dex2jar.jar
,接着使用JD-GUI就可以查看jar的代码了,因为被混淆过,所以阅读非常困难。
要想从茫茫的被混淆代码中找到自己需要的,是不可能的事情,所以只能另辟蹊径。
打开Android Studio
,打开logcat
,把手机连上电脑,选择监视welove
的log
信息,随意在APP中做一些操作,观察logcat
窗口,果然出现了有效的信息。
这里包含了SigUtils
,相比这是生成sig的类吧,在代码中搜索SigUtils
和sig
,发现sig
注入的代码块和SigUtils
的逻辑:
具体算法则在com.welove520.welove.l.e.a(String, String, Map)
方法中,查看之:
public static String a(String paramString1, String paramString2, Map<String, String> paramMap)
{
return encode(paramString1, paramString2, sloveMapData(paramString1, paramString2, paramMap).getBytes());
}
public static String encode(String paramString1, String paramString2, byte[] paramArrayOfByte)
{
try
{
paramString1 = Mac.getInstance("HmacSHA1");
paramString1.init(new SecretKeySpec("8b5b6eca8a9d1d1f".getBytes(), "HmacSHA1"));
paramString1 = Base64.encodeToString(paramString1.doFinal(paramArrayOfByte), 0).replaceAll("\r", "").replaceAll("\n", "").trim();
return paramString1;
}
catch (NoSuchAlgorithmException paramString1)
{
Log.e("SigUtils:", String.valueOf(paramString1.toString()));
return "";
}
catch (InvalidKeyException paramString1)
{
for (;;)
{
Log.e("SigUtils:", String.valueOf(paramString1.toString()));
}
}
}
找到加密的算法了!
HmacSHA1
,且密钥是写死在代码中的8b5b6eca8a9d1d1f
,接下来找到被加密的字段就可以了,还是在这个被混淆的类中:
private static String sloveMapData(String paramString1, String paramString2, Map<String, String> paramMap)
{
Object localObject = new String[paramMap.size()];
paramString2 = new StringBuilder(paramString2);
StringBuilder localStringBuilder = new StringBuilder();
LinkedHashMap localLinkedHashMap = new LinkedHashMap();
Iterator localIterator = paramMap.entrySet().iterator();
int i = 0;
while (localIterator.hasNext())
{
localObject[i] = ((String)((Map.Entry)localIterator.next()).getKey());
i += 1;
}
Arrays.sort((Object[])localObject, String.CASE_INSENSITIVE_ORDER);
int j = localObject.length;
i = 0;
while (i < j)
{
localIterator = localObject[i];
localLinkedHashMap.put(localIterator, a((String)paramMap.get(localIterator)));
i += 1;
}
paramMap = localLinkedHashMap.entrySet().iterator();
while (paramMap.hasNext())
{
localObject = (Map.Entry)paramMap.next();
localStringBuilder.append((String)((Map.Entry)localObject).getKey()).append("=").append((String)((Map.Entry)localObject).getValue()).append("&");
}
paramString2.append("&").append(a(paramString1)).append("&").append(a(localStringBuilder));
Log.d("SigUtils", "SigUtils#" + paramString2.toString());
return paramString2.toString();
}
配合Logcat
信息了解到加密的字段是{GET|POST}&{url}&{content}
并且url
和content
都是经过Base64
编码的,POST
、url
都是固定的,content则是请求的信息出除去sig
字段,对这一构造得到的字符串进行HmacSHA1
加密之后再进行Base64
编码就是请求中需要的sig
了!!
这里有一个坑就是Java自带的Base64
编码的的结果是小写的,比如=
编码之后是%3d
,而微爱
请求的则是%3D
,这里需要转换大小写。
测试一下算法是否正确(我在这里使用了Golang):
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"net/url"
)
func main() {
key := []byte("8b5b6eca8a9d1d1f")
mac := hmac.New(sha1.New, key)
method := "POST"
u := "http://api.welove520.com/v1/game/house/home"
content := "access_token=562949961343086-*****9974dd0&love_space_id=8444*****15867"
mac.Write([]byte(method + "&" + url.QueryEscape(u) + "&" + url.QueryEscape(content)))
result := mac.Sum(nil)
s := base64.StdEncoding.EncodeToString(result)
fmt.Println(s)
}
执行得到的sig
为gO5EnwNaGxqEWk/uyGWQn6+sktk=
与Fiddler截包获得的sig
一致:
access_token=562949961343086-*****9974dd0&love_space_id=8444*****15867&sig=gO5EnwNaGxqEWk%2FuyGWQn6%2Bsktk%3D
只不过这里的sig
也经过了URLEncode。