今天我们要分析的app 是6LGG55OjdjcuMTguMA==
(base64解码),小伙伴们可以去各大应用商定自行下载。赶紧记录下分析流程,是作为一个逆向分析的很好案例。
参考链接:
- https://blog.csdn.net/weixin_43582101/article/details/121670287
- https://blog.csdn.net/qq_23594799/article/details/108446352
转载文章,请注明出处,谢谢!https://blog.csdn.net/weixin_38819889/article/details/122005142
1.抓包找参数
如上图所示,通过charles抓包,找接口,然后找到我们最终要研究的参数 _sig
,
其他接口比如udid
猜测是用户uid是固定值,apikey ,device_id和apple
应该代表的就是当前设备参数信息,直接固定就能用(ps:没有大批量测试,只是猜测,不是研究的重点,请忽略。)
2.反编译apk
找到加密参数_sig,接下来就是反编译apk,看看里面的加密逻辑到底是咋写的,这时候拿出我们的大宝贝jadx
,有不会的同学可自行百度下如何使用。然后把豆瓣app拖进到jadx中,一会就会反编译好。
在这里我们直接搜索 “_sig
” 关键字,还好 还好,找到的相似代码不是很多,来我们一个一个的看 ,这里觉得 builder.add("_sig", (String) a2.first);
很可疑,点进去看看。
跳转进去以后,可以看到 "_sig
"参数 是 a2.first
的方法返回来的,具体是干嘛目前不知道,不过不要紧,继续跟进发现 a2 是Pair<String, String> a2 = ApiSignatureHelper.a(request);
这个方法声明实例化的一个对象(ps:java学的不好,表述可能不准)。然后点进去看看ApiSignatureHelper
这个类具体是咋实现的。
然后就来到这里了,可以看到ApiSignatureHelper
类的下面的a
方法,就是我们今天要搞的加密地方。
整个加密代码如下,然后我们先静态分析一下java代码,一会再用frida动态分析一波验证下我们的猜想。
3.分析加密逻辑
首先看到a方法传入三个参数str,str2 和 str3
,然后定义一个变量decode
具体干嘛的不知道, 之后定义一个str4
,这是一个AES
加密的秘钥key(很重要,后来才知道的),然后用StringBuilder
类创建一个sb对象 ,你可以理解为用python 定义一个字符串 对象,再然后 定义一个encodedPath
这个东西就是请求url的path(后来动态分析以后才知道的),同时还把url路径path 给url 编码了,然后又拼接了一个StringPool.AMPERSAND
这是啥东西 点进去看看
发现是一个&
符号,最后是拼接一个10位时间戳currentTimeMillis
,最后把拼接好的字符串sb,最后调用HMACHash1.a()
该方法,并把str4和sb作为参数传进去,最后得到加密的结果。那好,我们再点进去看看HMACHash1.a()
方法是咋写的,继续往下走。
public class ApiSignatureHelper {
static Pair<String, String> a(Request request) {
if (request == null) {
return null;
}
String header = request.header(com.douban.push.internal.api.Request.HEADER_AUTHORIZATION);
if (!TextUtils.isEmpty(header)) {
header = header.substring(7);
}
return a(request.url().toString(), request.method(), header);
}
public static Pair<String, String> a(String str, String str2, String str3) {
String decode;
if (TextUtils.isEmpty(str)) {
return null;
}
String str4 = FrodoApi.a().e.b;
if (TextUtils.isEmpty(str4)) {
return null;
}
StringBuilder sb = new StringBuilder();
sb.append(str2);
String encodedPath = HttpUrl.parse(str).encodedPath();
if (encodedPath == null || (decode = Uri.decode(encodedPath)) == null) {
return null;
}
if (decode.endsWith("/")) {
decode = decode.substring(0, decode.length() - 1);
}
sb.append(StringPool.AMPERSAND);
sb.append(Uri.encode(decode));
if (!TextUtils.isEmpty(str3)) {
sb.append(StringPool.AMPERSAND);
sb.append(str3);
}
long currentTimeMillis = System.currentTimeMillis() / 1000;
sb.append(StringPool.AMPERSAND);
sb.append(currentTimeMillis);
return new Pair<>(HMACHash1.a(str4, sb.toString()), String.valueOf(currentTimeMillis));
}
}
然后我们就来到了这里,根据关键词 HMACHash1 和Base64
,可以大胆的猜测下这是hmac和base64
加密算法,恭喜你猜对了。要是对hmac不熟悉的看看这篇文章:https://blog.csdn.net/weixin_38819889/article/details/122457122?spm=1001.2014.3001.5501
。
public class HMACHash1 {
public static final String a(String str, String str2) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(str.getBytes(), LiveHelper.HMAC_SHA1);
Mac instance = Mac.getInstance(LiveHelper.HMAC_SHA1);
instance.init(secretKeySpec);
return Base64.encodeToString(instance.doFinal(str2.getBytes()), 2);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
静态分析的差不多了,接下来用我们的frida动态调试一下,看看实际的效果是什么样子的。
4.frida调试一下:
首先启动frida服务,把js注入到进程里面 执行一下命令:
frida -U com.douban.frodo -l hook_douban.js
Java.perform(
function(){
var apisig = Java.use("com.douban.frodo.network.ApiSignatureHelper");
apisig.a.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function (a1,a2, a3){
console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓豆瓣hook--sig 开始↓↓↓↓↓↓↓↓↓↓↓↓↓");
console.log("apisig传入参数1:", a1);
console.log("apisig传入参数2:", a3);
console.log("apisig传入参数3:", a3);
var sig_res = apisig.a(a1, a2, a3)
console.log("apisig加密result:" + sig_res);
console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑豆瓣hook--sig 结束↑↑↑↑↑↑↑↑↑↑↑↑↑");
return sig_res;
}
apisig.a.overload('okhttp3.Request').implementation = function (a0){
console.log("apisig传入参数0:", a0);
var sig_res1 = apisig.a(a0)
console.log("apisig重载加密result:" + sig_res1);
return sig_res1;
}
var HMACHash1 = Java.use("com.douban.frodo.utils.crypto.HMACHash1");
HMACHash1.a.overload('java.lang.String', 'java.lang.String').implementation = function (a1,a2) {
console.log("HMACHash1传入参数1:", a1);
console.log("HMACHash1传入参数2:", a2);
var res = HMACHash1.a(a1,a2);
console.log("HMACHash加密result:" + res);
return res;
}
}
)
动态运行的效果如下:
详细分析的已经差不多了,总结下就是把get请求+请求url的path+时间戳作为sb和str4作为hmac
加密的参数,计算出来一个值,再然后base64
加密一下,就得到最终加密参数_sig
。
最后 用python还原一波,看看拿到的数据。
具体代码就不公布了,有兴趣的可以自己尝试分析一下。