文章总结: 本文介绍APICLOUD加密APK的逆向解密方法。通过分析WebView拦截机制定位解密逻辑,编写FridaHook脚本拦截关键构造函数,成功提取解密后的JS、CSS及HTML文件。该方案绕过加密限制还原源码,为后续分析与漏洞挖掘提供了有效的前置技术手段。 综合评分: 89 文章分类: 逆向分析,移动安全,安全工具,渗透测试
APICLOUD APK 逆向
原创
argyros
夺旗赛小萌新
2026年1月10日 23:42 浙江
前言
apicloud 打包的apk可以勾选是否加密,加密后的内容无法阅读。解密是分析和挖洞的前置条件,必不可少。
gogogo
加密文件长下面这样
全局搜索shouldinterceptRequest
shouldinterceptRequest是一个webview中实现拦截功能的接口
找到有实现方法的地方
发现返回是个三元表达式根据一个bool值判断,猜测是解密流程,和非解密流程,不急,先看下b,b = com.uzmap.pkg.uzcore.e.b.b(str);
protectedfinalWebResourceResponseb(WebViewwebView, Stringstr, booleanz, booleanz2, Stringstr2, Map<String, String>map) {
ala=al.a(str);
if (a.o()) {
returnk.a(a, map);
}
WebResourceResponseshouldInterceptRequest=WebShare.shouldInterceptRequest(webView, WebShare.makeRESRequest(webView, str, z, z2, str2, map));
if (shouldInterceptRequest!=null) {
returnshouldInterceptRequest;
}
if (!this.b) {
returnnull;
}
Contextcontext=webView.getContext();
StringmakeWebviewTag=WebShare.makeWebviewTag(webView);
Stringb=com.uzmap.pkg.uzcore.e.b.b(str);
return!com.uzmap.pkg.uzcore.e.b.c(b) ?b(context, str, b, makeWebviewTag) : a(context, str, b, makeWebviewTag);
}
跟进到com.uzmap.pkg.uzcore.e.b.b
publicstaticStringb(Stringstr) {
returnl.a(str);
}
继续跟l.a,发现是一个对字符串做处理获取文件扩展名的操作
publicstaticStringa(Stringstr) {
intlastIndexOf;
if (d.a((CharSequence) str)) {
return"";
}
intlastIndexOf2=str.lastIndexOf(35);
// 35 = '#'
if (lastIndexOf2>0) {
str=str.substring(0, lastIndexOf2);
}
intlastIndexOf3=str.lastIndexOf(63);
// 63 = '?'
if (lastIndexOf3>0) {
str=str.substring(0, lastIndexOf3);
}
intlastIndexOf4=str.lastIndexOf(47);
// 47 = '/'
if (lastIndexOf4>=0) {
str=str.substring(lastIndexOf4+1);
}
return (str.isEmpty() || (lastIndexOf=str.lastIndexOf(46)) <0) ?"" : str.substring(lastIndexOf+1);
}
我们接着看com.uzmap.pkg.uzcore.e.b.c这个判断函数做了什么
publicstaticbooleanc(Stringstr) {
returnl.g(str);
}
跟l.g
public static boolean g(String str) {
return a.contains(str);
}
a,发现是判断是不是这几种文件后缀
static {
ArrayListarrayList=newArrayList();
a=arrayList;
arrayList.add("js");
a.add("css");
a.add("html");
}
判断前面有个!要注意
我们跟过来
private WebResourceResponse a(Context context, String str, String str2, String str3) {
if (!str.startsWith("contents:")) {
return null;
}
String e = com.deepe.c.i.l.e(str2);
String b = com.uzmap.pkg.uzcore.e.d.b(str);
byte[] b2 = k.b(b, true, true);
if (b2 != null) {
return new com.uzmap.pkg.uzcore.i.a.c(e, new com.uzmap.pkg.uzcore.i.a.b(b2, b));
}
return null;
}
private WebResourceResponse b(Context context, String str, String str2, String str3) {
if (!str.startsWith("contents:")) {
return null;
}
String a = w.a(str, (UZWidgetInfo) null);
String e = com.deepe.c.i.l.e(str2);
byte[] b = k.b(a, false, true);
if ((b != null ? b.length : 0) <= 0) {
return null;
}
return new com.uzmap.pkg.uzcore.i.a.c(e, new com.uzmap.pkg.uzcore.i.a.b(b, a));
}
发现无论是a方法还是b方法返回都是com.uzmap.pkg.uzcore.i.a.c函数
public c(String str, InputStream inputStream) {
super(str, "UTF-8", inputStream);
}
发现是最终返回的了,得看他里面的内容
继续看com.uzmap.pkg.uzcore.i.a.b,b是他的构造函数,目测要么是bArr[] 要么是str的解密的内容了
public class b extends ByteArrayInputStream {
private String a;
public b(byte[] bArr, String str) {
super(bArr);
this.a = str;
}
public String toString() {
return "forbiden";
}
}
再回头看下a函数中的b,明显像是文件路径,那么bArr就是解密后的内容了
public static final String b(String str) {
int f;
if (!com.uzmap.pkg.uzapp.a.g() || !str.startsWith("contents:")) {
return str;
}
if (Build.VERSION.SDK_INT > 10 && (f = f.a().f(str)) > 0) {
str = "contents://" + str.substring(f);
}
return str.replaceFirst("contents:", "file:");
}
hook脚本如下
Java.perform(function () {
var bClass = Java.use("com.uzmap.pkg.uzcore.i.a.b");
bClass.$init.implementation = function (bytes, name) {
console.log("=== Hook Triggered ===");
try {
// 获取应用上下文
var ActivityThread = Java.use("android.app.ActivityThread");
var context = ActivityThread.currentApplication().getApplicationContext();
// 使用外部文件目录(不需要特殊权限)
var externalDir = context.getExternalFilesDir(null);
var privateDir = externalDir.getAbsolutePath() + "/hook_captures";
var File = Java.use("java.io.File");
var dirFile = File.$new(privateDir);
if (!dirFile.exists()) {
var created = dirFile.mkdirs();
console.log("Created external dir: " + created);
}
// 处理文件名
var simpleName = name.split('/').pop();
var filePath = privateDir + "/" + simpleName;
console.log("Saving to: " + filePath);
// 写入文件
var String = Java.use("java.lang.String");
var FileOutputStream = Java.use("java.io.FileOutputStream");
var content = String.$new(bytes, "UTF-8");
var fos = FileOutputStream.$new(filePath);
fos.write(content.getBytes("UTF-8"));
fos.close();
console.log("✓ Successfully saved to external files dir");
} catch (error) {
console.log("✗ Save failed: " + error.toString());
}
return this.$init(bytes, name);
};
});
最终效果
解密后
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:夺旗赛小萌新 argyros《APICLOUD APK 逆向》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论