安卓逆向-豆瓣app签名算法分析与解密(下)
文章目录
- 1、反汇编豆瓣APP
- 2、定位签名计算位置
- 3、获取豆瓣APP的签名
- 4、HMAC Hash加密逻辑分析
- 5、代码实现
- 6、注意事项
完整工程上传到了GitHub上,仅限于研究使用,欢迎star,如不能运行请看注意事项
项目地址:https://github.com/bestyize/DoubanAPI
1、反汇编豆瓣APP
我们用强大的jadx来反汇编豆瓣app
选择文件-打开。然后找到豆瓣app的安装包后打开。
2、定位签名计算位置
点击搜索图标,我们搜索一下在上一节找的_sig是在哪里组装的
双击进去,可以看到一个叫做ApiSignatureHelper.a的方法获得了_sig的值
Pair<String, String> a2 = ApiSignatureHelper.a(request);
再点进去看看,可以看到这个类的实现非常简单,Pair是安卓里面的一个只有两个值的数据结构,ApiSignatureHelper.a的作用就是计算_sig的值。
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));}
}
看完代码后我们可以知道,最后是使用了HMAC Hash算法,把str4作为key,把sb.toString()作为加密内容进行的加密。
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;}}
}
但是由于HMAC Hash是一个不可逆的加密算法,我们是不能根据_sig来反推加密密钥的。
所以我们能做的就是直接获取这个加密密钥。
我们追一下str4的来源:
String str4 = FrodoApi.a().e.b;
可以清晰地看到,这个值是ZenoConfig在构造函数初始化时候传入的,是第三个参数。
我们再追究下哪里调用了这个构造函数。
可以看到,这个值是从这里来的
String d2 = FrodoUtils.d();
我们再追踪一下
在这里,我们终于到了计算值密钥的位置。
@SuppressLint({"PackageManagerGetSignatures"})public static void a(boolean z) {if (TextUtils.isEmpty(b)) {b = "74CwfJd4+7LYgFhXi1cx0IQC35UQqYVFycCE+EVyw1E=";}if (TextUtils.isEmpty(c)) {c = "bHUvfbiVZUmm2sQRKwiAcw==";}if (z) {try {String encodeToString = Base64.encodeToString(AppContext.a().getPackageManager().getPackageInfo(AppContext.a().getPackageName(), 64).signatures[0].toByteArray(), 0);b = AES.a(b, encodeToString);c = AES.a(c, encodeToString);} catch (PackageManager.NameNotFoundException e2) {e2.printStackTrace();}}}
在这段代码中,将c作为密文
c = "bHUvfbiVZUmm2sQRKwiAcw=="
将apk签名作为密钥,经过AES加密得到最终的加密密钥,作为前面提到的HMAC Hash算法中的加密密钥。
String encodeToString = Base64.encodeToString(AppContext.a().getPackageManager().getPackageInfo(AppContext.a().getPackageName(), 64).signatures[0].toByteArray(), 0);
3、获取豆瓣APP的签名
在上一节中,我们定位到了计算HMAC Hash算法密钥的位置,这个位置是由AES加密获取到一个结果,作为HMAC Hash算法密钥的,但是AES加密的文本我们可以直接找到,就是
bHUvfbiVZUmm2sQRKwiAcw==
但我们还不知道AES加密密钥是什么。熟悉安卓开发的人应该知道,这句话是用来获取当前应用的签名的,这是安卓的一种防篡改的安全机制。只要我们修改了包,签名就会变化,所以,我们不能直接修改豆瓣APP的安装包。
AppContext.a().getPackageManager().getPackageInfo(AppContext.a().getPackageName(), 64).signatures[0].toByteArray()
不过,其他应用也可以获取已安装应用的签名信息,只需要把对应app的包名填入即可。
Application application=(Application)getApplicationContext();
PackageInfo packageInfo=application.getPackageManager().getPackageInfo("com.douban.frodo",PackageManager.GET_SIGNATURES);String sign=Base64.encodeToString(packageInfo.signatures[0].toByteArray(),0);
这样我们就获取到了我们需要的字串
public final static String SIGN="MIICUjCCAbsCBEty1MMwDQYJKoZIhvcNAQEEBQAwcDELMAkGA1UEBhMCemgxEDAOBgNVBAgTB0Jl\n" +"aWppbmcxEDAOBgNVBAcTB0JlaWppbmcxEzARBgNVBAoTCkRvdWJhbiBJbmMxFDASBgNVBAsTC0Rv\n" +"dWJhbiBJbmMuMRIwEAYDVQQDEwlCZWFyIFR1bmcwHhcNMTAwMjEwMTU0NjExWhcNMzcwNjI3MTU0\n" +"NjExWjBwMQswCQYDVQQGEwJ6aDEQMA4GA1UECBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzET\n" +"MBEGA1UEChMKRG91YmFuIEluYzEUMBIGA1UECxMLRG91YmFuIEluYy4xEjAQBgNVBAMTCUJlYXIg\n" +"VHVuZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAg622fxLuwQtC8KLYp5gHk0OmfrFiIisz\n" +"kzPLBhKPZDHjYS1URhQpzf00T8qg2oEwJPPELjN2Q7YOoax8UINXLhMgFQkyAvMfjdEOSfoKH93p\n" +"v2d4n/IjQc/TaDKu6yb53DOq76HTUYLcfLKOXaGwGjAp3QqTqP9LnjJjGZCdSvMCAwEAATANBgkq\n" +"hkiG9w0BAQQFAAOBgQA3MovcB3Hv4bai7OYHU+gZcGQ/8sOLAXGD/roWPX3gm9tyERpGztveH35p\n" +"aI3BrUWg2Vir0DRjbR48b2HxQidQTVIH/HOJHV0jgYNDviD18/cBwKuLiBvdzc2Fte+zT0nnHXMy\n" +"E6tVeW3UdHC1UvzyB7Qcxiu4sBiEO1koToQTWw==\n";
不过,这个加密密钥其实是固定的,我们直接把jadx反编译后的代码,移植到这里,计算出这个加密密钥,以后就不需要再重复计算了,最后,我们得到的结果是
bf7dddc7c9cfe6f7
这就是HMAC Hash算法需要的加密密钥
4、HMAC Hash加密逻辑分析
在得到HAMC Hash的加密密钥之后,我们再看一下,被HMAC Hash算法加密的字符串是怎么得到的。
//str:API的地址,不包括后面参数,举例:str="https://frodo.douban.com/api/v2/elessar/subject/27260217/photos"//str2:请求方法,这里是GET,举例:str2="GET"//str3: str3=null;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;//HMAC Hash密钥,在前面我们得到的结果是:bf7dddc7c9cfe6f7if (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));}
至此,豆瓣的加密算法分析完成,接下来就是实现它
5、代码实现
在上面分析的代码中,有一些是安卓特有的API,但是为了让程序能run everywhere,我对其中的一些数据结构做了替换,对一些API进行了移植(感谢安卓是开源的)
由于数据结构Pair仅仅是Android中的一个类,所以,为了在别的地方用Java的地方也能用,我们可以移植,也可以用hashMap代替
public class SignatureHelper {private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();private static final String DEFAULT_ENCODING = "UTF-8";public static final String AMPERSAND = "&";private final static int NOT_FOUND = -1;
// public final static String SIGN="MIICUjCCAbsCBEty1MMwDQYJKoZIhvcNAQEEBQAwcDELMAkGA1UEBhMCemgxEDAOBgNVBAgTB0Jl\n" +
// "aWppbmcxEDAOBgNVBAcTB0JlaWppbmcxEzARBgNVBAoTCkRvdWJhbiBJbmMxFDASBgNVBAsTC0Rv\n" +
// "dWJhbiBJbmMuMRIwEAYDVQQDEwlCZWFyIFR1bmcwHhcNMTAwMjEwMTU0NjExWhcNMzcwNjI3MTU0\n" +
// "NjExWjBwMQswCQYDVQQGEwJ6aDEQMA4GA1UECBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzET\n" +
// "MBEGA1UEChMKRG91YmFuIEluYzEUMBIGA1UECxMLRG91YmFuIEluYy4xEjAQBgNVBAMTCUJlYXIg\n" +
// "VHVuZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAg622fxLuwQtC8KLYp5gHk0OmfrFiIisz\n" +
// "kzPLBhKPZDHjYS1URhQpzf00T8qg2oEwJPPELjN2Q7YOoax8UINXLhMgFQkyAvMfjdEOSfoKH93p\n" +
// "v2d4n/IjQc/TaDKu6yb53DOq76HTUYLcfLKOXaGwGjAp3QqTqP9LnjJjGZCdSvMCAwEAATANBgkq\n" +
// "hkiG9w0BAQQFAAOBgQA3MovcB3Hv4bai7OYHU+gZcGQ/8sOLAXGD/roWPX3gm9tyERpGztveH35p\n" +
// "aI3BrUWg2Vir0DRjbR48b2HxQidQTVIH/HOJHV0jgYNDviD18/cBwKuLiBvdzc2Fte+zT0nnHXMy\n" +
// "E6tVeW3UdHC1UvzyB7Qcxiu4sBiEO1koToQTWw==\n";public static Map<String, String> getVerifyMap(String str, String str2, String str3) {Map<String, String> map=new HashMap<>();String decode;if (TextUtils.isEmpty(str)) {return null;}String str4 = "bf7dddc7c9cfe6f7";if (TextUtils.isEmpty(str4)) {return null;}StringBuilder sb = new StringBuilder();sb.append(str2);String encodedPath = encodedPath(str);System.out.println(encodedPath);if (encodedPath == null || (decode = encodedPath) == null) {return null;}if (decode.endsWith("/")) {decode = decode.substring(0, decode.length() - 1);}sb.append(AMPERSAND);sb.append(uriEncode(decode,null));if (!TextUtils.isEmpty(str3)) {sb.append(AMPERSAND);sb.append(str3);}long currentTimeMillis = System.currentTimeMillis() / 1000;sb.append(AMPERSAND);sb.append(currentTimeMillis);try {map.put("_sig", URLEncoder.encode(HMACHash1.a(str4, sb.toString()),"utf-8"));} catch (Exception e) {e.printStackTrace();}map.put("_ts",String.valueOf(currentTimeMillis));return map;}public static String uriEncode(String s, String allow) {if (s == null) {return null;}StringBuilder encoded = null;int oldLength = s.length();int current = 0;while (current < oldLength) {int nextToEncode = current;while (nextToEncode < oldLength&& isAllowed(s.charAt(nextToEncode), allow)) {nextToEncode++;}if (nextToEncode == oldLength) {if (current == 0) {// We didn't need to encode anything!return s;} else {// Presumably, we've already done some encoding.encoded.append(s, current, oldLength);return encoded.toString();}}if (encoded == null) {encoded = new StringBuilder();}if (nextToEncode > current) {// Append allowed characters leading up to this point.encoded.append(s, current, nextToEncode);} else {// assert nextToEncode == current}current = nextToEncode;int nextAllowed = current + 1;while (nextAllowed < oldLength&& !isAllowed(s.charAt(nextAllowed), allow)) {nextAllowed++;}String toEncode = s.substring(current, nextAllowed);try {byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);int bytesLength = bytes.length;for (int i = 0; i < bytesLength; i++) {encoded.append('%');encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);encoded.append(HEX_DIGITS[bytes[i] & 0xf]);}} catch (UnsupportedEncodingException e) {throw new AssertionError(e);}current = nextAllowed;}return encoded == null ? s : encoded.toString();}private static boolean isAllowed(char c, String allow) {return (c >= 'A' && c <= 'Z')|| (c >= 'a' && c <= 'z')|| (c >= '0' && c <= '9')|| "_-!.~'()*".indexOf(c) != NOT_FOUND|| (allow != null && allow.indexOf(c) != NOT_FOUND);}public static String encodedPath(String url) {String scheme="https";int pathStart = url.indexOf('/', scheme.length() + 3); // "://".length() == 3.int pathEnd = delimiterOffset(url, pathStart, url.length(), "?#");return url.substring(pathStart, pathEnd);}public static int delimiterOffset(String input, int pos, int limit, String delimiters) {for(int i = pos; i < limit; ++i) {if (delimiters.indexOf(input.charAt(i)) != -1) {return i;}}return limit;}
}
6、注意事项
豆瓣为了防止抓包,还对UA进行了校验,在计算出正确地址后,如果想要请求API,需要把UA设置成(这里的UA也可以在fildder里面看到)
api-client/1 com.douban.frodo/6.42.2(194) Android/22 product/shamu vendor/OPPO model/OPPO R11 Plus rom/android network/wifi platform/mobile nd/1
关于完整工程:
完整工程是一个servlet程序,用idea导入即可,测试部分在图示位置
安卓逆向-豆瓣app签名算法分析与解密(下)相关推荐
- 安卓逆向-豆瓣app签名算法分析与解密(上)
文章目录 1.背景介绍 2.工具准备 3.Fildder抓包 3.1 配置fiddler代理 3.2 配置安卓模拟器的代理 3.3 为安卓模拟器安装证书 4.抓取豆瓣APP的网络请求 1.背景介绍 豆 ...
- 安卓逆向--豆瓣app签名sign
声明 本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!由于本人水平有限,如有理解或者描述不准确的地方,还望各位大佬指教!! 前言 今天我们要分 ...
- Web逆向、软件逆向、安卓逆向、APP逆向,关于网络安全这些你必须懂
逆向工程是网络安全行业里面一项很重要的技术. 先解释下逆向工程是什么. 逆向是一个相对正向而言的解释,相对正向来说,对一个程序来讲,正向就是开发的过程,从0到1. 就是在一个软件诞生的整个生命周期中的 ...
- 安卓逆向——修改APP的名称,图标和包名多开分身
修改APP的名称,图标和包名多开分身 1. 把apk拖入到 Android killer 2. 修改apk的名字 搜到结果,把得到的文件,把"土豆视频" 改成 修改成的名字 回编译 ...
- 安卓逆向——修改APP去广告案例
修改APP去广告案例 首先 没有去 广告打开的样子 ,显示的广告 这里使用 Android killer 工具 反编译 apk ,查看源码,打开 AndroidManifest.xml 文件查看 配置 ...
- 安卓逆向之APP抓包
市面上的抓包工具很多,其中最主要的两款就是Charles以及Fiddler,下面按照步骤讲一下Fiddler抓包工具的配置以及使用过程! 1.Fiddler软件的下载 到如下网址下载Fiddler:h ...
- 3、xx配音狂app登陆算法分析【Android逆向分析学习】
学习安卓逆向分析的一个小菜鸟,记录分析的"快乐时光",小白图个乐,大佬乎喷 分析的APP 英语配音狂包名:com.zhuoyue.peiyinkuang版本号:5.2.5模拟器安装 ...
- 安卓逆向Xposed HOOK TB直播APP的x-sign参数
最近学习安卓逆向,接触一下TB系的APP,了解大厂APP是做数据安全的,这篇文章主要介绍某宝直播APP的签名参数x-sign的HOOK过程,当然,其他的参数也是可以HOOK的.本文只用于学习交流,请勿 ...
- 安卓逆向入门练习之电影天堂APP逆向分析
准备 抓包环境及工具准备,参考:使用Fiddler对安卓App抓包 APP:电影天堂APP,版本:3.5.0 抓包 使用fiddler在模拟器里对App进行抓包,拦截到四种类型的数据: http:// ...
最新文章
- Linux 命令 top 学习总结
- iOS超全开源框架、项目和学习资料汇总:UI篇
- 《MongoDB权威指南》迷你书连载一-入门篇
- python读取指定行的txt_【Python】读取txt文件,获取指定行中指定位置数据
- 台湾国立大学郭彦甫Matlab教程笔记(14)polynomial differentiation多项式微分
- 解决Windows下Arm下Linux下Qt4程序的中文乱码问题
- verilog7人表决电路设计
- 你真的了解Python吗?这篇文章可以让你了解90%,赶紧收藏!
- html div转行,转行web前端开发的人有没有未来
- mysql事务、视图
- MiluGPS(迷路者GPS导航软件)
- html模板替换值,Go语言多值替换的HTML模板实例分析
- SogouLabDic搜狗词库
- 【秋色动人xp情侣主题】
- Recovery文件路径
- git reset --hard HEAD~X误删恢复操作
- 用maven的 Mybatis代码生成器
- 分布式记账的几种方式
- 横向扩展 纵向扩展 数据库_理解数据库扩展模式的指南
- Field brandService in cn.est.service.impl.HomeServiceImpl required a bean of type ‘cn.est.service.Br