《你还在用叉叉助手?你可知道它背后的秘密?》我行我上,深入分析,欢迎交流
本帖最后由 df09 于 2018-3-21 18:11 编辑近日,看见有大佬在分析叉叉助手背后的秘密(https://www.52pojie.cn/thread-714265-1-1.html)。哇擦,我手机上就装了一个:eee。正当我也准备和大家一样将叉叉拖进垃圾桶的时候,我迟疑了,作为一个爱逆向爱探索的大三狗,我必须探索真理{:1_918:}。而且还有人质疑我不行,我无法分析。我表示我行。
说罢,拿出安卓的抓包神器Packet Capture,清理叉叉助手的缓存,然后开始抓包,打开叉叉助手等个十几秒,停止抓包。
emm....请求略多啊,不过其中我发现了一个6m的请求。联系之前大佬的文章,就是它了。
果断命令行curl http://downapk.guopan.cn/2017-08-21/e29bd24ed9ff4a1a1d47979ea3a32314.apk -o output.apk
然后当然是大名鼎鼎的apktool伺候拉:lol
解包后整体情况一览
assets:
lib:
classes:(用JDGUI看的)
诶{:1_925:},apktool出错了吗,为啥解出来的东西感觉也是个apktool(还有一个aapt的so)。。。我特意用jdgui看了看我自己的apktool.jar
????(黑人问号),这部分简直就是apktool嘛。。不过我每个目录翻看了下,更加像一个阉割了的apktool。叉叉助手为啥会有apktool的代码???
这样还是不好分析,还是先分析他的入口类吧。MultiApkHelper。这个类竟然没有被混淆{:1_918:},分析起来就easy了。
这个函数最长了,我就先从他入手吧
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移植到手机上了:eee。想当年我想在手机上破解apk的时候还要root还要装busybox什么的很麻烦呢。。。他才2M的安装包就搞定了。。。之前用了一个工具装手机上要50M。。。
好了,最后我们看回去第五步,看下究竟干了什么坏事,解开叉叉的背后的秘密:lol。
这么多行代码,实际上有用就就只有doDirtyHack和另外一个分支的代码了,其他可以忽略。简单先看else的分支吧。
这个大概就是在改AndroidManifest.xml的内容,那是谁的AndroidManifest.xml的呢?看回前面我们就可以知道,这个实际上就是解包后的AndroidManifest.xml。跟进去看,看到了关键字
label,package
那看来就是在通过某种方式修改应用名称和应用包名了。
好了,回到doDirtyHack。看下到底有多dirty;www。
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("/");
} 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时间的函数的:lol。晚点可以分析下,看下能不能把加速器搞过来自己用。
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了
????{:1_925:}叉叉助手这是要干嘛,为啥要将两个apk合体???额,想起之前分析到classes.dex是一个加速器相关的apk,那到这里,基本可以实锤了!!!!!!
叉叉干的坏事就是。。。。把一个加速器和一个apk合体???????(手动黑人问号???)
emm....突然,我想起叉叉有一个功能.....
我tm....分析半天,原来其实就是分析叉叉的这个功能模块。。。为啥不早点想起来啊。。。。。
结论就是:
叉叉下载了这个apk,然后用动态加载apk的方法,加载这里面的代码,然后用他来多开游戏。。。
大苏打实打实的 虽然你做的事情没问题。但你没权利说别人学艺不精就不能发帖?
而且他那帖子也没说错什么东西,也不知道你在跳脚什么。 1,偷偷后台下载东西就是流氓行为。2,你们没办法担保以后偷偷下载的东西是不是没有安全问题的。把root权限给你们,你们就应该什么都公开。 他们把我们说的偷偷后台下载这种行为称之为静默下载,还自豪的说是一种微创新呢 mumu324 发表于 2018-3-21 18:01
1,偷偷后台下载东西就是流氓行为。2,你们没办法担保以后偷偷下载的东西是不是没有安全问题的。把root权限 ...
笑不动 自己去看看腾讯支付宝不都是后台下载下载完成再告诉你不懂技术的不要乱说 本帖最后由 mumu324 于 2018-3-21 18:02 编辑
竟然果然还是来了。 其实发帖的那位楼主应该是为大家着想,毕竟叉叉的确是偷偷下载东西。不过真相大白就好了。感谢两位大神,一位大神为大家着想,另一位技术大神,感谢他分析清楚了叉叉助手。不过叉叉助手偷偷下载是否可以改善?又被腾讯杀了可能以后还会有误会。 墙都不服就服你!{:1_926:} mumu324 发表于 2018-3-21 18:01
1,偷偷后台下载东西就是流氓行为。2,你们没办法担保以后偷偷下载的东西是不是没有安全问题的。把root权限 ...
你还给叉叉root权限的呀,我都不给叉叉root权限的,电脑激活就用了,这样安全放心 插件化而已,叉叉的多开是最垃圾最原始的版本的没有研究的意义再说现在很多SDK都是动态加载APK的你分析的很对 6666666666,真相大白!!技术很牛,真正的剖析 学习了谢谢大佬的警示