贝壳APP签名分析头部Authorization字段分析
0x01、 目标需求:
.a) 贝壳APP的接口参数以及相关的签名获取的方式
.b) 需要获取所有城市下的区域下的门店等数据
.c) 好久没有写博客了,不知道关注我博客的朋友们是不是都快忘了我的博客了.
0x02、分析背景:
.a) 版本2.66.0(目前最新版)
.b) 软件无壳
.c) 软件通讯过程中采取了头部签名验证的方式,
0x03、分析流程:
.a) 通过数据包抓取或敏感函数hook方式获得接口功能
.b) 通过函数内部参数的组装继续分析参数的来源以及加密的流程(Authorization)0x04、分析开始:
.a) jadx-gui 分析签名
0x001. 打开jadx-gui 直接搜索关键词_Authorization:
0x002. Frida直接验证该拦截器是否是我们要的:
0x003. POST和GET函数走的加密方法中间多了一个处理函数,来处理Body的内容(如下图):
0x004. 分析下后面的加密函数:
0x005. 获取AppSecret 和 AppId:
0x006. 接下来重要的一步就是 DeviceUtil.SHA1ToString 这个函数的参数是如何计算而来的(传入的类似一个MD5的值):
let encrypt = Java.use("com.bk.base.util.bk.DeviceUtil");
encrypt.SHA1ToString.implementation = function (v1) {
let res = this.SHA1ToString(v1)
console.log("加密参数为: " + v1 + "加密结果为: " + res)
return res;
}
//加密参数为: d5e343d453aecca8b14b2dc687c381ca 加密结果为:48b82883cf47ac02ed32aef24fea684851c7dab9
0x007. 参数1是一个url,参数2是post数据,然后做了一下key的字典排序for循环拼接参数(key=value):
public String getSignString(String str, Map<String, String> map2) {
Map<String, String> urlParams = getUrlParams(str);
HashMap hashMap = new HashMap();
if (urlParams != null) {
hashMap.putAll(urlParams);
}
if (map2 != null) {
hashMap.putAll(map2);
}
ArrayList arrayList = new ArrayList(hashMap.entrySet());
Collections.sort(arrayList, new Comparator<Map.Entry<String, String>>() {
/* class com.bk.base.netimpl.a.AnonymousClass1 */
public int compare(Map.Entry<String, String> entry, Map.Entry<String, String> entry2) {
return entry.getKey().compareTo(entry2.getKey());
}
});
String httpAppSecret = ModuleRouterApi.MainRouterApi.getHttpAppSecret();
boolean notEmpty = a.e.notEmpty(httpAppSecret);
String str2 = BuildConfig.FLAVOR;
if (!notEmpty) {
try {
httpAppSecret = JniClient.GetAppSecret(com.bk.base.config.a.getContext());
} catch (Exception e) {
e.printStackTrace();
httpAppSecret = str2;
}
}
String httpAppId = ModuleRouterApi.MainRouterApi.getHttpAppId();
if (a.e.notEmpty(httpAppId)) {
str2 = httpAppId;
} else {
try {
str2 = JniClient.GetAppId(com.bk.base.config.a.getContext());
} catch (Exception e2) {
e2.printStackTrace();
}
}
StringBuilder sb = new StringBuilder(httpAppSecret);
for (int i = 0; i < arrayList.size(); i++) {
Map.Entry entry = (Map.Entry) arrayList.get(i);
sb.append(((String) entry.getKey()) + "=" + ((String) entry.getValue()));
}
String str3 = TAG;
LjLogUtil.d(str3, "sign origin=" + ((Object) sb));
String SHA1ToString = DeviceUtil.SHA1ToString(sb.toString());
String encodeToString = Base64.encodeToString((str2 + ":" + SHA1ToString).getBytes(), 2);
String str4 = TAG;
LjLogUtil.d(str4, "sign result=" + encodeToString);
return encodeToString;
}
0x008. 看到调用加密函数之前打印了一下 原始签名,我们hook一下他log的d函数看一下是啥东西:
0x009. 由于是get请求不携带参数的话不存在for循环拼接参数,所以这里有理由怀疑的是 origin=d5e343d453aecca8b14b2dc687c381ca 就是他的httpAppSecret(重启APP 看一下这个httpAppSecret 是否会变):
0x010. 整个流程就分析完了,我们最终知道的是如果是get请求 默认的 appSecret d5e343d453aecca8b14b2dc687c381ca SHA1加密后 在前面拼接 "20180111_android:" 然后在Base64编码即可,如果get请求中携带了参数也需要参与加密
如果是post请求 用默认的appSecret + key=value(这里要字典排序key)然后SHA1 后 在参数前面拼接 "20180111_android:" 得到最终的字符串后 Base64编码即可, 最终我们验证一下结果写一个get请求和一个post请求(目前没有post请求需求不写了)
友情提示:本文只为技术分享交流,请勿非法用途.产生一切法律问题与本人无关. 本文中所有的调试代码均在gitee仓库中(包含很多app的关键点的hook代码) Git公开仓库地址 欢迎star or fork
在浏览的同时希望给予作者打赏,来支持作者的服务器维护费用.一分也是爱~
qq111111
我想进群?
用户 Windows10 1076 天前回复