APICLOUDAPK逆向

admin 2026-01-12 01:22:56 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍APICLOUD加密APK的逆向解密方法。通过分析WebView拦截机制定位解密逻辑,编写FridaHook脚本拦截关键构造函数,成功提取解密后的JS、CSS及HTML文件。该方案绕过加密限制还原源码,为后续分析与漏洞挖掘提供了有效的前置技术手段。 综合评分: 89 文章分类: 逆向分析,移动安全,安全工具,渗透测试


cover_image

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);

&nbsp; &nbsp;&nbsp;protectedfinalWebResourceResponseb(WebViewwebView,&nbsp;Stringstr,&nbsp;booleanz,&nbsp;booleanz2,&nbsp;Stringstr2,&nbsp;Map<String,&nbsp;String>map) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ala=al.a(str);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(a.o()) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnk.a(a,&nbsp;map);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;WebResourceResponseshouldInterceptRequest=WebShare.shouldInterceptRequest(webView,&nbsp;WebShare.makeRESRequest(webView,&nbsp;str,&nbsp;z,&nbsp;z2,&nbsp;str2,&nbsp;map));
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(shouldInterceptRequest!=null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnshouldInterceptRequest;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!this.b) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnnull;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Contextcontext=webView.getContext();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;StringmakeWebviewTag=WebShare.makeWebviewTag(webView);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Stringb=com.uzmap.pkg.uzcore.e.b.b(str);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return!com.uzmap.pkg.uzcore.e.b.c(b)&nbsp;?b(context,&nbsp;str,&nbsp;b,&nbsp;makeWebviewTag) :&nbsp;a(context,&nbsp;str,&nbsp;b,&nbsp;makeWebviewTag);
&nbsp; &nbsp; }

跟进到com.uzmap.pkg.uzcore.e.b.b

&nbsp; &nbsp;&nbsp;publicstaticStringb(Stringstr) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnl.a(str);
&nbsp; &nbsp; }

继续跟l.a,发现是一个对字符串做处理获取文件扩展名的操作

&nbsp; &nbsp;&nbsp;publicstaticStringa(Stringstr) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intlastIndexOf;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(d.a((CharSequence)&nbsp;str)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return"";
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intlastIndexOf2=str.lastIndexOf(35);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 35 = '#'
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(lastIndexOf2>0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;str=str.substring(0,&nbsp;lastIndexOf2);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intlastIndexOf3=str.lastIndexOf(63);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 63 = '?'
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(lastIndexOf3>0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;str=str.substring(0,&nbsp;lastIndexOf3);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intlastIndexOf4=str.lastIndexOf(47);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 47 = '/'
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(lastIndexOf4>=0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;str=str.substring(lastIndexOf4+1);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(str.isEmpty()&nbsp;||&nbsp;(lastIndexOf=str.lastIndexOf(46))&nbsp;<0)&nbsp;?""&nbsp;:&nbsp;str.substring(lastIndexOf+1);
&nbsp; &nbsp; }

我们接着看com.uzmap.pkg.uzcore.e.b.c这个判断函数做了什么

&nbsp; &nbsp;&nbsp;publicstaticbooleanc(Stringstr) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnl.g(str);
&nbsp; &nbsp; }

l.g

&nbsp; &nbsp; public static boolean g(String str) {
&nbsp; &nbsp; &nbsp; &nbsp; return a.contains(str);
&nbsp; &nbsp; }

a,发现是判断是不是这几种文件后缀

&nbsp; &nbsp;&nbsp;static&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ArrayListarrayList=newArrayList();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;a=arrayList;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;arrayList.add("js");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;a.add("css");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;a.add("html");
&nbsp; &nbsp; }

判断前面有个!要注意

我们跟过来

&nbsp; &nbsp; private WebResourceResponse a(Context context, String str, String str2, String str3) {
&nbsp; &nbsp; &nbsp; &nbsp; if (!str.startsWith("contents:")) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; String e = com.deepe.c.i.l.e(str2);
&nbsp; &nbsp; &nbsp; &nbsp; String b = com.uzmap.pkg.uzcore.e.d.b(str);
&nbsp; &nbsp; &nbsp; &nbsp; byte[] b2 = k.b(b, true, true);
&nbsp; &nbsp; &nbsp; &nbsp; if (b2 != null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new com.uzmap.pkg.uzcore.i.a.c(e, new com.uzmap.pkg.uzcore.i.a.b(b2, b));
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; }

&nbsp; &nbsp; private WebResourceResponse b(Context context, String str, String str2, String str3) {
&nbsp; &nbsp; &nbsp; &nbsp; if (!str.startsWith("contents:")) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; String a = w.a(str, (UZWidgetInfo) null);
&nbsp; &nbsp; &nbsp; &nbsp; String e = com.deepe.c.i.l.e(str2);
&nbsp; &nbsp; &nbsp; &nbsp; byte[] b = k.b(a, false, true);
&nbsp; &nbsp; &nbsp; &nbsp; if ((b != null ? b.length : 0) <= 0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; return new com.uzmap.pkg.uzcore.i.a.c(e, new com.uzmap.pkg.uzcore.i.a.b(b, a));
&nbsp; &nbsp; }

发现无论是a方法还是b方法返回都是com.uzmap.pkg.uzcore.i.a.c函数

&nbsp; &nbsp; public c(String str, InputStream inputStream) {
&nbsp; &nbsp; &nbsp; &nbsp; super(str, "UTF-8", inputStream);
&nbsp; &nbsp; }

发现是最终返回的了,得看他里面的内容

继续看com.uzmap.pkg.uzcore.i.a.b,b是他的构造函数,目测要么是bArr[] 要么是str的解密的内容了

public class b extends ByteArrayInputStream {
&nbsp; &nbsp; private String a;

&nbsp; &nbsp; public b(byte[] bArr, String str) {
&nbsp; &nbsp; &nbsp; &nbsp; super(bArr);
&nbsp; &nbsp; &nbsp; &nbsp; this.a = str;
&nbsp; &nbsp; }

&nbsp; &nbsp; public String toString() {
&nbsp; &nbsp; &nbsp; &nbsp; return "forbiden";
&nbsp; &nbsp; }
}

再回头看下a函数中的b,明显像是文件路径,那么bArr就是解密后的内容了


&nbsp; &nbsp; public static final String b(String str) {
&nbsp; &nbsp; &nbsp; &nbsp; int f;
&nbsp; &nbsp; &nbsp; &nbsp; if (!com.uzmap.pkg.uzapp.a.g() || !str.startsWith("contents:")) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return str;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; if (Build.VERSION.SDK_INT > 10 && (f = f.a().f(str)) > 0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; str = "contents://" + str.substring(f);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; return str.replaceFirst("contents:", "file:");
&nbsp; &nbsp; }

hook脚本如下

Java.perform(function () {
&nbsp; &nbsp; var bClass = Java.use("com.uzmap.pkg.uzcore.i.a.b");

&nbsp; &nbsp; bClass.$init.implementation = function (bytes, name) {
&nbsp; &nbsp; &nbsp; &nbsp; console.log("=== Hook Triggered ===");

&nbsp; &nbsp; &nbsp; &nbsp; try {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 获取应用上下文
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var ActivityThread = Java.use("android.app.ActivityThread");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var context = ActivityThread.currentApplication().getApplicationContext();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 使用外部文件目录(不需要特殊权限)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var externalDir = context.getExternalFilesDir(null);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var privateDir = externalDir.getAbsolutePath() + "/hook_captures";

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var File = Java.use("java.io.File");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var dirFile = File.$new(privateDir);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!dirFile.exists()) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var created = dirFile.mkdirs();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("Created external dir: " + created);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 处理文件名
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var simpleName = name.split('/').pop();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var filePath = privateDir + "/" + simpleName;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("Saving to: " + filePath);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 写入文件
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var String = Java.use("java.lang.String");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var FileOutputStream = Java.use("java.io.FileOutputStream");

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var content = String.$new(bytes, "UTF-8");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var fos = FileOutputStream.$new(filePath);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fos.write(content.getBytes("UTF-8"));
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fos.close();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("✓ Successfully saved to external files dir");

&nbsp; &nbsp; &nbsp; &nbsp; } catch (error) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("✗ Save failed: " + error.toString());
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; return this.$init(bytes, name);
&nbsp; &nbsp; };
});

最终效果

解密后


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:夺旗赛小萌新 argyros《APICLOUD APK 逆向》

APICLOUDAPK逆向 网络安全文章

APICLOUDAPK逆向

文章总结: 本文介绍APICLOUD加密APK的逆向解密方法。通过分析WebView拦截机制定位解密逻辑,编写FridaHook脚本拦截关键构造函数,成功提取解
解散 网络安全文章

解散

文章总结: 作者朽木宣布解散公众号朽木的安全杂谈,因精力不足及重心转向事业发展,且非盈利导向导致维持必要性降低。作者决定专注事业拼搏,未来计划享受生活,并向关注
评论:0   参与:  0