特别声明:本公众号文章只作为学术研究,不用于其它用途。
这是「进击的Coder」的第 761 篇技术分享
作者:TheWeiJun
来源:逆向与爬虫的故事
“
阅读本文大概需要 11 分钟。
”目录
一、抓包分析
二、Jadx 反编译
三、hook 调试
四、算法分析
五、思路总结
趣味模块
小东是一名后端开发工程师,自从上次使用了魔改 md4 算法,恶意请求量得到了有效控制。但是好景不长,随着 spider 技术的提升,也就过了半个月,恶意请求量就又上来了。小东心里默默地说了一句:"去 tn 的,看来这次得使用杀手锏了。"于是小东在参数中加入了一个新的参数 auth_token,小东加上这套策略后,请求量立马下降了。接下来,让我们去分析一下小东是如何设计的此方案吧!
一、抓包分析
1、抓包分析之前,给大家推荐一个神器"AirDroid", 使用此应用可在电脑端远程控制手机及镜像手机画面。截图如下:
2、和以往一样,打开指定 App,使用 charles 工具进行 App 抓包,截图如下:
经验分享:如果此处联网失败,是因为此 app 为国外 app,需要对 charles 应用进行配置,再抓包即可。
3、配置charles部分参数命令,打开本机vpn,配置截图如下:
4、再次打开 charles 应用,进行 App 抓包,应用截图如下:
说明:此刻我们可以看到 app 已经能够联网并且能够抓包了,接下来进行抓包操作。
5、使用 charles 过滤指定域名后,访问指定页面,截图如下:
说明:观察上图,我们可以确定本次分析的参数为 auth_token,初步判断该参数后,无法确定使用的什么加密手段。那么,接下来让我们进入反编译调试环节吧!
二、Jadx 反编译
1、使用 jadx 打开指定 apk,截图如下:
2、等待 jadx 反编译完毕后,使用最简单的方法(指定关键参数搜寻),截图如下:
说明:此刻我们定位到参数结果 39 个,可以通过过滤包名接口、变量参数等方式排除一些干扰结果。
3、经过分析 java 源码,确定 auth_token 参数位置如下:
说明:分析上面代码,我们确定需要 hook 的函数为 a。接下来让我们去 hook 这个内部类函数的返回值,看看是不是我们想要的结果。
三、Hook 调试
1、通过分析 java 源码,构建 frida hook 代码如下:
function java_hook() {
Java.perform(function () {
let a = Java.use("l0.b.a.a.a");
a.a.overload('java.lang.StringBuilder', 'java.lang.String', 'java.lang.String').implementation = function (sb, str, str2) {
console.log('str: ' + str);
console.log('str2: ' + str2);
let ret = this.a(sb, str, str2);
console.log('a ret value is : ' + ret);
return ret;
};
})
}
2、启动 frida 脚本,执行 hook 方法后,点击手机,分析截图如下:
说明:此处我们可以看到a方法的返回值都已经全部打印出来,明文信息如上。但是 charles 抓包的 auth_token 是经过加密处理的,显然是不对的,也就是说以往的关键字查询方式无法解决这个问题。
3、hook 指定函数、构建堆栈打印输出代码,截图如下:
4、将上图的堆栈函数内嵌在需要执行的 java_hook 函数中,再次执行 hook 代码,截图如下:
解释说明:观察上图,此刻我们可以看到当前请求的完整堆栈信息,执行顺序从上到下。接下来,我们只需要在 jadx 中分析上面的方法即可!
5、分析堆栈函数,确定真正加密的函数位置,截图如下:
6、在 IDEA 中构建此函数 hook 代码,截图如下所示:
再次运行 frida,打印截图如下:
此时 charles 中的 auth_token 的值如下所示:
{
"auth_token": "eyJhbGciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0.eyJzdWIiOiJjMmY3M2U3YTVjNDZkYzQzYjUzODk2Mzc3NjkzZTVmY2ExMDU5ZmNjOTkzNjljNzI0Y2U5MjhkMGQ1YmE4NWM5IiwiZXhwIjogIjE2NTM0NjE2ODUiLCJpYXQiOiAiMTY1MzQ2MDc4NSIsImVuZHBvaW50IjogIi9uYXRpdmVBcHBBcGkvbWVtYmVyL0xvZ2luLyJ9.YjdmZWFkYTE0MjA2NDdhNGQyNTQ0NTM5ZDQzZDhiZTM4MzFkZDU1YTJkODEyZGY2Mzc3MDk1ZjY0MjQ1MWE4YQ",
"device_specific_id": "",
"mail_address": "",
"deviceId": ""
}
结论:此时对比 frida hook 的值和 charles 抓包的值结果一致,接下来让我们分析下加密算法吧。
四、算法分析
1、结合 java 源码分析 auth_token 参数,我们发现它其实由三个字符串拼接而成,截图如下:
需要还原变量如下:
encodeToString
encodeToString2
Base64.encodeToString(bytes2, 11)
总结:通过阅读 java 源码,我们发现上面三个参数,都是 base64 加密,接下来我们一个一个直接还原即可。
encodeToString 参数还原如下:
经验分享:不出意外的话还是出了意外,base64 解码居然失败了。然后我又看了下 java 源码,原来 android.util.Base64 在 encodeToString 编码的时候,把 == 符号去掉了,导致我在 python 中无法还原并产生报错。
修复后的正确代码及打印结果如下图所示:
encodeToString2 参数还原如下:
总结:encodeToString2 还原后的明文信息如上图所示,exp、iat 都是时间戳,sub 为固定值,endpoint 参数为访问的当前 url 路径,此明文中不需要再还原任何加密参数,只需要模拟时间戳即可。
Base64.encodeToString(bytes2, 11) 参数还原如下:
总结:解密后的值如图所示,一串长度为 64 位字符的字符串信息,经过多次 charles 抓包后 base64 解密,我们发现这个字符串每次都不一样,接下来需要对此值进行还原。
2、通过阅读 java 源码,我们确定新的加密参数 sb3。但是 sb3 为局部变量,我们无法 hook,只能从他的来源去下手,截图如下:
3、因为上面的流程过于复杂,直接上关键函数 hook 代码,截图如下:
4、运行 hook 代码后,截图如下所示:
5、在 base64 在线解密工具中,解密刚刚的加密参数,截图如下:
总结:对比流程 4、5 中的值,我发现 invoke 函数的返回值 value 拼接起来即为 sb3 的值;我们只需要还原此处加密即可得到 sb3 的值。
6、sb3 参数完整代码还原如下:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class Test {
static String a3 = "token_auth的值";
static String string5 = "AAAAB3NzaC1yc2EAAAADAQABAAABgQDLzX2Mbg+Sbv1BqbK/GY/gwT2PJ/p99zndDJ9MofK9WcvYQLMLt35MT5JduD4wWyzoDJbXJNf37YnDHYl3hESAabIUrD2Lvtn15SyFOJ5QsXtoG9gSYC2C2EI5sGgGDMWQUkmaP67EvWDElCh2LCmz0zc40kTeaovBX7Zb4P4TSayYLdKo3s+BmEtiZQ0ofv1n+xgOBxeqxiaxDbul6N2nXok5ShQzjKu5zpw9YdGSW2XKvQr4h5mCj8ak/GxpcAULlkdX+A2Wfpfs7sSB9kakae7XuIop4ymwybfn5HOiEAwMXFjNrZOzNmTBWdcOFX907PP09MwGs2klQhAbKfcfa3m0/jGhNRJ/k4iOpOM5UunWMdjF9vIL4TTLGPpyS+KK1zyrRK4j9z2fCMLovDZnvq8X6ZX+q2bMEtAgCPz8crfbZdJLhG8KQKUv+hdcnh5M6Qb3XTmCHkAo6n0cHmIGv11LW4u6HdieekUUKNE1+PWe/CynTffh/i17KbOa4zc";
public static String invoke(Byte b) {
Object[] objArr = {Byte.valueOf(b.byteValue())};
String format = String.format("%02x", Arrays.copyOf(objArr, objArr.length));
return format;
}
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
byte[] bytes = a3.getBytes(StandardCharsets.UTF_8);
byte[] bArr4 = string5.getBytes("UTF-8");
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(bArr4, "HmacSHA256");
mac.init(secret_key);
byte[] doFinal = mac.doFinal(bytes);
StringBuilder sb2 = new StringBuilder();
sb2.append((CharSequence) "");
int i2 = 0;
for (byte b : doFinal) {
i2++;
if (i2 > 1) {
sb2.append((CharSequence) "");
}
if (sb2 != null) {
sb2.append((CharSequence) invoke(Byte.valueOf(b)));
} else {
sb2.append((CharSequence) String.valueOf((int) b));
}
}
sb2.append((CharSequence) "");
String sb3 = sb2.toString();
System.out.println(sb3);
}
}
五、思路总结
感谢大家阅读😊本篇分享到这里就结束了,欢迎大家关注下一期,我们不见不散!!!☀️
End
崔庆才的新书《Python3网络爬虫开发实战(第二版)》已经正式上市了!书中详细介绍了零基础用 Python 开发爬虫的各方面知识,同时相比第一版新增了 JavaScript 逆向、Android 逆向、异步爬虫、深度学习、Kubernetes 相关内容,同时本书已经获得 Python 之父 Guido 的推荐,目前本书正在七折促销中!
内容介绍:《Python3网络爬虫开发实战(第二版)》内容介绍
扫码购买
点个在看你最好看