Bpazy / blog

我的博客,欢迎关注和讨论
https://github.com/Bpazy/blog/issues
MIT License
41 stars 2 forks source link

反编译APK分析APP的加密算法 #74

Open Bpazy opened 5 years ago

Bpazy commented 5 years ago

最近在研究微爱这款应用的请求时,发现每一条请求都携带了sig这个参数,并且sig随着每一次登录都会变化,不同的行为触发的HTTP请求所携带的sig也都不相同。

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,把手机连上电脑,选择监视welovelog信息,随意在APP中做一些操作,观察logcat窗口,果然出现了有效的信息。

logcat

这里包含了SigUtils,相比这是生成sig的类吧,在代码中搜索SigUtilssig,发现sig注入的代码块和SigUtils的逻辑: sig 具体算法则在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}并且urlcontent都是经过Base64编码的,POSTurl 都是固定的,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)
}

执行得到的siggO5EnwNaGxqEWk/uyGWQn6+sktk=

与Fiddler截包获得的sig一致:

access_token=562949961343086-*****9974dd0&love_space_id=8444*****15867&sig=gO5EnwNaGxqEWk%2FuyGWQn6%2Bsktk%3D

只不过这里的sig也经过了URLEncode。

有了sig就方便各种模拟HTTP请求了。

HikaruChang commented 3 years ago

感谢提供思路,不过新版似乎换了皮,secret也换了 http://imtt.dd.qq.com/16891/apk/6A6B1EFDB16F6ABE1756386BEF5C1448.apk?fsname=com.welove520.qqsweet_3.1.2_53.apk&hsr=4d5s