博客地址: http://blog.androidcrack.com/index.php/archives/37/
感谢杨如画大佬分析的文章, 这个文章是复现大佬的文章. 动手了才是自己的.
抓包
- Pixel3
- Reqable
咱们的任务需求主要是搞定这几个算法.
定位加密位置
1. 搜索法
我们将应用拖入JADX中,搜索关键词 x-sap-ri
,发现无果. 所以搜索关键词的方法在此处显得较为无力. 在此案例中, 不适合使用. 但是并不意味着,搜索法不好. 这个要看实际的使用常见
2. 开发者思维
我们反编译虾皮的APK, 可以看到有okhttp3的主包, 那么我们就要思考. 我们应该如何给一个请求设置请求头?
https://square.github.io/okhttp/
使用addHeader, addHeader里面应该是用HashMap
, 这里我没有去跟踪okhttp, 感兴趣的读者可以自己去跟踪一下.
最终可以写一个frida hook脚本.
💡对HashMap.put方法进行了拦截, 判断key是否为我们的目标, 如果为目标,则打印调用栈. 根据调用栈定位加密位置
Java.perform(function (){
// HashMap.put
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a, b) {
if(a!=null && a.equals("x-sap-ri")){
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))
console.log("hashMap.put: ", a, b);
}
return this.put(a, b);
}
})
保证应用在前台, 我们开始Hook应用
frida -UF -l .\hook_shopee.js
堆栈非常的长, 我们看到又关键的方法com.shopee.shpssdk.SHPSSDK.uvwvvwvvw
java.lang.Throwable
at java.util.HashMap.put(Native Method)
at org.json.JSONObject.put(JSONObject.java:276)
at org.json.JSONTokener.readObject(JSONTokener.java:394)
at org.json.JSONTokener.nextValue(JSONTokener.java:104)
at org.json.JSONObject.<init>(JSONObject.java:168)
at org.json.JSONObject.<init>(JSONObject.java:185)
at com.shopee.shpssdk.SHPSSDK.uvwvvwvvw(Unknown Source:58)
at com.shopee.shpssdk.SHPSSDK.requestDefense(Unknown Source:95)
at com.shopee.app.network.antifraud.b.intercept(SourceFile:47)
at okhttp3.internal.http.RealInterceptorChain.proceed(SourceFile:19)
....(省略)
hashMap.put: x-sap-ri d3db5b67e52fdbdec805c31d01850c676f03f49ab5458cc0533f
在jadx中, 我们找到对应的位置, 怪不得我们在前文中,用搜索法无法定位到函数位置. 原来是因为该应用的字符串加密导致的.
💡以后在逆向工作中, 大家可以思考下, 如果我是开发者,我会怎么做? 有时候可能会带来意想不到的效果!
那么生成我们需要的x-sap-ri
参数的函数就是vuwuuwvw
方法
public Map<String, String> requestDefense(String str, byte[] bArr) {
if (!ShPerfC.checkNotNull(perfEntry) || !ShPerfC.on(new Object[]{
str, bArr}, this, perfEntry, false, 35729, new Class[]{
String.class, byte[].class}, Map.class)) {
Hashtable hashtable = new Hashtable();
if (str == null) {
return hashtable;
}
try {
try {
return uvwvvwvvw(wvvvuwwu.vuwuuwvw(str.getBytes(), bArr)); // vuwuuwvw方法生成结果
} catch (Throwable unused) {
hashtable.put(vvuwvuuuu.wwvvvwvwu("0F580414075A131E0F1A10", "wuwuwwuww"), vvuwvuuuu.wwvvvwvwu("130E3F013E1F1A123E1B261C3A34341C2F243F413C3232063C363D023E1F19103C1C3F4E", "vwuvwuuuw"));
return hashtable;
}
} catch (Throwable unused2) {
}
} else {
return (Map) ShPerfC.perf(new Object[]{
str, bArr}, this, perfEntry, false, 35729, new Class[]{
String.class, byte[].class}, Map.class);
}
}
wwvvvwvwu
方法是一个native方法. 需要去so中分析了.
public static native String vuwuuwvw(byte[] bArr, byte[] bArr2);
但是目前, 我们已经成功的通过开发者思维定位到函数加密的位置.
frida主动调用与unidbg模拟
1. 确定目标SO
在同个文件下搜索loadLibrary发现, 不是太容易, 因为导入的SO字符串也被处理了. 所以,我们可以hook libart
public static /* synthetic */ void access$000(String str) {
IAFz3z iAFz3z = perfEntry;
if (iAFz3z == null || !((Boolean) ShPerfB.perf(new Object[]{
str}, null, iAFz3z, true, 39835, new Class[]{
String.class}, Void.TYPE)[0]).booleanValue()) {
System.loadLibrary(str);
}
}
hook registernative脚本
function hook_hashmap() {
Java.perform(function () {
// HashMap.put
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a, b) {
if (a != null && a.equals("x-sap-ri")) {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))
console.log("hashMap.put: ", a, b);
}
return this.put(a, b);
}
})
}
function find_RegisterNatives(params) {
let symbols = Module.enumerateSymbolsSync("libart.so");
let addrRegister