本帖最后由 df09 于 2018-3-21 18:11 编辑
近日,看见有大佬在分析叉叉助手背后的秘密(https://www.52pojie.cn/thread-714265-1-1.html)。哇擦,我手机上就装了一个。正当我也准备和大家一样将叉叉拖进垃圾桶的时候,我迟疑了,作为一个爱逆向爱探索的大三狗,我必须探索真理。而且还有人质疑我不行,我无法分析。我表示我行。
说罢,拿出安卓的抓包神器Packet Capture,清理叉叉助手的缓存,然后开始抓包,打开叉叉助手等个十几秒,停止抓包。
emm....请求略多啊,不过其中我发现了一个6m的请求。联系之前大佬的文章,就是它了。
果断命令行curl http://downapk.guopan.cn/2017-08-21/e29bd24ed9ff4a1a1d47979ea3a32314.apk -o output.apk
然后当然是大名鼎鼎的apktool伺候拉
解包后整体情况一览
assets:
lib:
classes:(用JDGUI看的)
诶,apktool出错了吗,为啥解出来的东西感觉也是个apktool(还有一个aapt的so)。。。我特意用jdgui看了看我自己的apktool.jar
????(黑人问号),这部分简直就是apktool嘛。。不过我每个目录翻看了下,更加像一个阉割了的apktool。叉叉助手为啥会有apktool的代码???
这样还是不好分析,还是先分析他的入口类吧。MultiApkHelper。这个类竟然没有被混淆,分析起来就easy了。
这个函数最长了,我就先从他入手吧
[Java] 纯文本查看 复制代码 public String generateNewApk(JSONObject jSONObject, ProgressListener progressListener) {
Object obj = null;
getVersion();
if (progressListener == null) {
AnonymousClass1 anonymousClass1 = new ProgressListener(this) {
final /* synthetic */ MultiApkHelper a;
{
this.a = r1;
}
};
}
JSONObject jSONObject2;
try {
String string = jSONObject.getString("package_name");
String string2 = jSONObject.getString("target_name");
this.mIsInjectSpeed = jSONObject.getBoolean("is_speed");
this.mIsInjectAssist = jSONObject.getBoolean("is_assist");
this.mIsInjectSplash = jSONObject.getBoolean("is_splash");
this.mAssistUid = jSONObject.getInt("assist_uid");
this.mAssistUrl = jSONObject.getString("assist_url");
this.mHasLocalAssist = jSONObject.getBoolean("has_local_assist");
if (jSONObject.has("new_package_name") || string.contains("xxAssistant") || string.contains("xxnoroot")) {
obj = 1;
this.mIsInjectSpeed = false;
this.mIsInjectAssist = false;
this.mIsInjectSplash = false;
}
try {
if (isAppInjected(string)) {
jSONObject2 = new JSONObject();
jSONObject2.put("is_multi_success", false);
jSONObject2.put("inject_speed_success", false);
jSONObject2.put("inject_assist_success", false);
jSONObject2.put("msg", "应用已经被多开过了");
jSONObject2.put("apk_path", "");
progressListener.onFinish(jSONObject2);
return null;
}
String string3;
if (obj != null) {
string3 = jSONObject.has("new_package_name") ? jSONObject.getString("new_package_name") : generateRandomPackageName();
} else {
string3 = string + FEATURE_STRING + System.currentTimeMillis();
}
String str = getTempWorkingPath() + string3 + File.separator + "package";
File file = new File(str + File.separator + "dist");
if (file.mkdirs() || file.isDirectory()) {
long currentTimeMillis = System.currentTimeMillis();
coma2.apk.q.a aVar = new coma2.apk.q.a();
progressListener.onProgressUpdate(0);
deleteGeneratedApk(new File(getTempWorkingPath()));
progressListener.onProgressUpdate(3);
d.a(TAG, "拷贝apk...");
String sourceApkPath = getSourceApkPath(string);
if (new File(sourceApkPath).exists()) {
String substring = sourceApkPath.substring(sourceApkPath.lastIndexOf(File.separator) + 1, sourceApkPath.length());
d.a(TAG, "fileName = " + substring);
d.a(TAG, "拷贝apk成功 cost " + (System.currentTimeMillis() - currentTimeMillis));
currentTimeMillis = System.currentTimeMillis();
progressListener.onProgressUpdate(15);
d.a(TAG, "开始解apk包...");
if (obj != null) {
f.a(sourceApkPath, str);
} else {
coma2.apk.d.a.a(new String[]{"d", sourceApkPath, "-o", str, "-s", "-f"});
}
d.a(TAG, "解apk包成功 cost " + (System.currentTimeMillis() - currentTimeMillis));
currentTimeMillis = System.currentTimeMillis();
progressListener.onProgressUpdate(32);
System.gc();
File file2 = new File(getTempWorkingPath() + "output_dir");
if (!file2.exists()) {
file2.mkdirs();
}
progressListener.onProgressUpdate(48);
d.a(TAG, "开始做坏事...");
aVar.a = sourceApkPath;
aVar.b = str + File.separator + "dist" + File.separator + substring;
aVar.c = getTempWorkingPath() + "output_dir" + File.separator + string3 + "_signed_unaligned.apk";
aVar.d = str;
aVar.e = getTempWorkingPath() + string3 + File.separator + "temp";
aVar.f = string;
aVar.g = string3;
aVar.h = string2;
new File(aVar.e).mkdirs();
if (obj == null) {
doDirtyHack(aVar);
} else {
coma2.apk.n.a aVar2 = new coma2.apk.n.a();
d.a(TAG, "parse axml : " + aVar2.a(aVar.d + File.separator + "AndroidManifest.xml"));
aVar2.d(aVar.g);
aVar2.c(aVar.h);
d.a(TAG, "after modify package name : " + aVar2.a());
d.a(TAG, "save axml : " + aVar2.b(aVar.d + File.separator + "AndroidManifest.xml"));
}
d.a(TAG, "坏事做完了 cost " + (System.currentTimeMillis() - currentTimeMillis));
progressListener.onProgressUpdate(56);
File file3 = new File(getTempWorkingPath() + string3 + File.separator + "temp.apk");
if (!file3.exists()) {
file3.createNewFile();
}
c.a(sourceApkPath, file3.getAbsolutePath());
progressListener.onProgressUpdate(76);
currentTimeMillis = System.currentTimeMillis();
d.a(TAG, "重新打包...");
if (obj != null) {
f.b(str, file3.getAbsolutePath());
} else {
coma2.apk.d.a.a(new String[]{"b", str, "-o", file3.getAbsolutePath()});
}
d.a(TAG, "打包完毕 cost " + (System.currentTimeMillis() - currentTimeMillis));
progressListener.onProgressUpdate(93);
long currentTimeMillis2 = System.currentTimeMillis();
d.a(TAG, "开始签名...");
k kVar = new k();
kVar.a("testkey");
kVar.a(file3.getAbsolutePath(), aVar.c);
d.a(TAG, "签名成功 cost " + (System.currentTimeMillis() - currentTimeMillis2));
progressListener.onProgressUpdate(99);
System.gc();
progressListener.onProgressUpdate(100);
jSONObject2 = new JSONObject();
jSONObject2.put("is_multi_success", true);
jSONObject2.put("inject_speed_success", this.mInjectSpeedSuccess);
jSONObject2.put("inject_assist_success", this.mInjectAssistSuccess);
jSONObject2.put("msg", "成功");
jSONObject2.put("apk_path", aVar.c);
progressListener.onFinish(jSONObject2);
return aVar.c;
}
throw new Exception("包名" + string + "对应的apk路径未能找到");
}
throw new Exception("create dist dir failed");
} catch (Throwable th) {
th.printStackTrace();
}
return null;
} catch (Exception e) {
e.printStackTrace();
jSONObject2 = new JSONObject();
try {
jSONObject2.put("is_multi_success", false);
jSONObject2.put("inject_speed_success", false);
jSONObject2.put("inject_assist_success", false);
jSONObject2.put("msg", "应用已经被多开过了");
jSONObject2.put("apk_path", "");
progressListener.onFinish(jSONObject2);
return null;
} catch (JSONException e2) {
e2.printStackTrace();
return null;
}
}
}
大家喜欢看可以自己看,没混淆简单的一比。我就说下重点吧。
1、jSONObject.getString("package_name");获取了一个包名
2、会检测包名是否符合某种正则表达式,符合就停止,否则继续
3、根据包名通过PackageManager获取apk的存放路径
4、然后通过coma2.apk.d.a.a(new String[]{"d", sourceApkPath, "-o", str, "-s", "-f"});解apk包,这里迟点再看看coma2.apk.d.a.a干了啥
5、然后干坏事(这里迟点再分析)
6、然后coma2.apk.d.a.a(new String[]{"b", str, "-o", file3.getAbsolutePath()});打包apk
7、对apk包签名,实际上assets里面的keys目录下的东西就是签名的keystore文件
我发现他日志提到的解包和打包都用到了coma2.apk.d.a.a这个类,我们接下来分析下这个类
em....好吧,连人家的日志都没去掉,看了实锤了,这明明就是大名鼎鼎的apktool嘛。。。那之前的解包和打包也就明了了,其实就是利用apktool来对apk包进行反编译和回编。
不过在此也有点佩服叉叉的开发人员,竟然把apktool移植到手机上了。想当年我想在手机上破解apk的时候还要root还要装busybox什么的很麻烦呢。。。他才2M的安装包就搞定了。。。之前用了一个工具装手机上要50M。。。
好了,最后我们看回去第五步,看下究竟干了什么坏事,解开叉叉的背后的秘密。
这么多行代码,实际上有用就就只有doDirtyHack和另外一个分支的代码了,其他可以忽略。简单先看else的分支吧。
这个大概就是在改AndroidManifest.xml的内容,那是谁的AndroidManifest.xml的呢?看回前面我们就可以知道,这个实际上就是解包后的AndroidManifest.xml。跟进去看,看到了关键字
label,package
那看来就是在通过某种方式修改应用名称和应用包名了。
好了,回到doDirtyHack。看下到底有多dirty。
[Java] 纯文本查看 复制代码 private void doDirtyHack(coma2.apk.q.a aVar) {
String str;
d.a(TAG, "开始修改包名...");
String a = com.apktool.util.f.a(aVar.d + File.separator + "AndroidManifest.xml", aVar.g, aVar.h);
int i = 0;
if (a.contains("@string/")) {
str = a.split("/")[1];
} else {
i = 1;
str = a;
}
d.a(TAG, "修改包名完毕");
if (aVar.h != null && aVar.h.length() > 0) {
d.a(TAG, "开始修改游戏名为" + aVar.h + "...");
if (i == 0) {
com.apktool.util.f.b(aVar.d + File.separator + "res", "name", aVar.h, str);
}
d.a(TAG, "修改游戏名成功");
}
if (this.mIsInjectSpeed || this.mIsInjectAssist || this.mIsInjectScript) {
d.a(TAG, "开始准备注入的资源");
com.apktool.util.b.d(aVar);
d.a(TAG, "准备完毕,开始注入manifest");
com.apktool.util.b.a(aVar);
d.a(TAG, "注入manifest完毕,开始注入资源");
com.apktool.util.b.c(aVar);
d.a(TAG, "注入资源完毕,开始注入dex");
com.apktool.util.b.b(aVar);
d.a(TAG, "注入dex完毕");
}
JSONObject jSONObject = new JSONObject();
JSONObject jSONObject2 = new JSONObject();
jSONObject.put("version", getVersion());
jSONObject.put("xxPkgName", this.mContext.getPackageName());
if (this.mIsInjectSpeed) {
jSONObject2.put("so_name", "libxxspeedmanager-multi.so");
jSONObject2.put("so_path", "app_speed/libxxspeedmanager-multi.so");
jSONObject2.put("so_construct", "xx_speed_manager_init");
jSONObject2.put("apk_name", "xxp_plugin_speed.apk");
jSONObject2.put("apk_path", "app_speed/xxp_plugin_speed.apk");
jSONObject2.put("apk_entry", "com.xx.speed.XXPlugin");
jSONObject2.put("url", "");
jSONObject.put("speed", jSONObject2);
}
jSONObject2 = getAssistJsonObj(aVar);
if (this.mIsInjectAssist && jSONObject2 != null) {
jSONObject.put("assist", jSONObject2);
}
File file = new File(aVar.d + File.separator + "assets");
File file2 = new File(file, "multi_config.json");
if (!file.exists()) {
file.mkdirs();
}
if (!file2.exists()) {
file2.createNewFile();
}
c.a(jSONObject.toString().getBytes(), file2.getAbsolutePath());
d.a(TAG, "multi_config.json:\n" + jSONObject.toString());
}
emmm....我感觉看log就知道什么回事了..不过脚踏实地的我,不能被他欺骗,我们慢慢分析。
修改包名大致看了下,好像一直在操作xml文件。emmm。。。看来没骗我,果然是改了AndroidManifest.xml的package字段,其实就是改包名拉,学过安卓的都知道。
d.a(TAG, "开始准备注入的资源");
com.apktool.util.b.d(aVar);
看下都准备了啥?
emmm....看来之前assets里面的classes.dex用上了,解压出来了。InjectActivity.dex也一并拷过去。
我们手动解压看下里面是什么玩意
额。这图标好熟悉啊,不就是我用过的叉叉的加速器吗。原来这个classes.dex就是一个加速器插件啊,还用到了substrate,估计就是用来hook时间的函数的。晚点可以分析下,看下能不能把加速器搞过来自己用。
d.a(TAG, "准备完毕,开始注入manifest");
com.apktool.util.b.a(aVar);
这个注入manifest内容比较多,大致就是把刚刚解压的classes.dex里面的AndroidManifest.xml和前面用apktool解压出来的AndroidManifest.xml进行融合,融合成新的一个???
d.a(TAG, "注入manifest完毕,开始注入资源");
com.apktool.util.b.c(aVar);
这个也是老长老长的,大致就是把刚刚解压的classes.dex里面的res文件和前面用apktool解压出来的res合并起来。
d.a(TAG, "注入资源完毕,开始注入dex");
com.apktool.util.b.b(aVar);
这个就是直接把几个dex也合体成一个dex了
????叉叉助手这是要干嘛,为啥要将两个apk合体???额,想起之前分析到classes.dex是一个加速器相关的apk,那到这里,基本可以实锤了!!!!!!
叉叉干的坏事就是。。。。把一个加速器和一个apk合体???????(手动黑人问号???)
emm....突然,我想起叉叉有一个功能.....
我tm....分析半天,原来其实就是分析叉叉的这个功能模块。。。为啥不早点想起来啊。。。。。
结论就是:
叉叉下载了这个apk,然后用动态加载apk的方法,加载这里面的代码,然后用他来多开游戏。。。
|