2、叉叉助手逆向分析(上)

描述:主要讲解如何有条例地逆向分析出软件的主要逻辑。
工具:APKIDE,JD-GUI
方法:顺藤摸瓜,smali代码主要看invoke关键函数调用,定位到相应的类中看代码。

使用APKIDE反编译xxzhushou_android.apk。

各个游戏的辅助只有通过叉叉助手里“我的游戏”启动游戏才会显示,如果直接从桌面启动游戏,辅助并不会启动。

搜索“启动游戏”字符串无果,猜测是图片显示,在 com.xxAssistant\res\drawable目录下找到图片:
2、叉叉助手逆向分析(上)_第1张图片
在APKIDE里搜索图片文件名: icon_mygame_startgame_pluginon,查找结果:
<public type="drawable" name="icon_mygame_startgame_pluginon" id="0x7f0200a5" />
继续搜索: 0x7f0200a5,结果在 com\xxAssistant\a\h.smali中。那么设置“启动游戏”控件相关的代码也就在此文件中,浏览上下文找到一处设置控件onClick事件处理过程的代码:
new-instance v2, Lcom/xxAssistant/a/j; invoke-direct {v2, p0, v3}, Lcom/xxAssistant/a/j;-><init>(Lcom/xxAssistant/a/h;Lcom/xxAssistant/a/k;)V invoke-virtual {v1, v2}, Landroid/widget/LinearLayout;->setOnClickListener(Landroid/view/View$OnClickListener;)V
其中的“new-instance v2, Lcom/xxAssistant/a/j;”相当于new了一个OnClickListener,因此打开 com/xxAssistant/a/j分析:
.class Lcom/xxAssistant/a/j; .super Ljava/lang/Object; # interfaces .implements Landroid/view/View$OnClickListener; # instance fields .field final synthetica:Lcom/xxAssistant/a/h; .field private final synthetic b:Lcom/xxAssistant/a/k; # direct methods .method constructor <init>(Lcom/xxAssistant/a/h;Lcom/xxAssistant/a/k;)V.locals 0 iput-object p1, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; iput-object p2, p0, Lcom/xxAssistant/a/j;->b:Lcom/xxAssistant/a/k; invoke-direct {p0}, Ljava/lang/Object;-><init>()V return-void .end method # virtual methods .method public onClick(Landroid/view/View;)V .locals 6 const/4 v5, 0x1 iget-object v0, p0, Lcom/xxAssistant/a/j;->b:Lcom/xxAssistant/a/k; iget-object v0, v0, Lcom/xxAssistant/a/k;->d:Lcom/xxAssistant/Widget/MyTextView; invoke-virtual {v0}, Lcom/xxAssistant/Widget/MyTextView;->getPackageName()Ljava/lang/String; move-result-object v1 iget-object v0, p0, Lcom/xxAssistant/a/j;->b:Lcom/xxAssistant/a/k; iget-object v0, v0, Lcom/xxAssistant/a/k;->d:Lcom/xxAssistant/Widget/MyTextView; invoke-virtual {v0}, Lcom/xxAssistant/Widget/MyTextView;->getAppName()Ljava/lang/String; move-result-object v0 iget-object v2, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v2}, Lcom/xxAssistant/a/h;->b(Lcom/xxAssistant/a/h;)Lcom/xxAssistant/d/d; move-result-object v2 invoke-virtual {v2, v1}, Lcom/xxAssistant/d/d;->b(Ljava/lang/String;)I move-result v2 iget-object v3, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v3}, Lcom/xxAssistant/a/h;->c(Lcom/xxAssistant/a/h;)Lcom/xxAssistant/d/e; move-result-object v3 invoke-virtual {v3, v2}, Lcom/xxAssistant/d/e;->a(I)Lcom/xxAssistant/g/f; move-result-object v3 if-eqz v3, :cond_4 invoke-virtual {v3}, Lcom/xxAssistant/g/f;->a()I move-result v3 if-ne v3, v5, :cond_3 iget-object v3, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v3}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v3 const-string v4, "game_start_have_plugin" invoke-static {v3, v4, v0}, Lcom/b/a/a;->b(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V iget-object v3, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v3}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v3 const/16 v4, 0x3f4 invoke-static {v3, v4, v0}, Lcom/xxAssistant/Utils/i;->a(Landroid/content/Context;ILjava/lang/String;)V const/4 v0, 0x0 sget-object v3, Lcom/xxAssistant/View/xxApplication;->b:Landroid/content/SharedPreferences; const-stringv4, "auto_clean_memory" invoke-interface {v3, v4, v5}, Landroid/content/SharedPreferences;->getBoolean(Ljava/lang/String;Z)Z move-result v3 if-eqz v3, :cond_0 iget-objectv0, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v0}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v0 invoke-static {v0}, Lcom/xxAssistant/Utils/w;->a(Landroid/content/Context;)Landroid/widget/Toast; move-result-object v0 :cond_0 iget-object v3, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v3}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v3 invoke-static {v3, v2, v1}, Lcom/xxAssistant/Utils/ag;->a(Landroid/content/Context;ILjava/lang/String;)V if-eqz v0, :cond_1 invoke-virtual {v0}, Landroid/widget/Toast;->show()V :cond_1 sget-boolean v0, Lcom/xxAssistant/Utils/r;->a:Z if-eqz v0, :cond_2 iget-object v0, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v0}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v0 invoke-static {v1, v0}, Lcom/xxAssistant/Utils/w;->a(Ljava/lang/String;Landroid/content/Context;)Ljava/lang/Boolean; :goto_0 return-void :cond_2 iget-object v0, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; iget-object v0, v0, Lcom/xxAssistant/a/h;->a:Landroid/os/Handler; const/16 v2, 0x8 invoke-virtual {v0, v2}, Landroid/os/Handler;->sendEmptyMessage(I)Z iget-object v0, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v0}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v0 invoke-static {v1, v0}, Lcom/xxAssistant/Utils/w;->a(Ljava/lang/String;Landroid/content/Context;)Ljava/lang/Boolean; goto :goto_0 :cond_3 iget-object v2, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v2}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v2 const-string v3, "game_start_have_plugin" invoke-static {v2, v3, v0}, Lcom/b/a/a;->b(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V iget-object v2, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v2}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v2 const/16v3, 0x3f7 invoke-static {v2, v3, v0}, Lcom/xxAssistant/Utils/i;->a(Landroid/content/Context;ILjava/lang/String;)V iget-object v0, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v0, v1}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;Ljava/lang/String;)V goto :goto_0 :cond_4 iget-object v2, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v2}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v2 const-string v3, "game_start_no_plugin" invoke-static {v2, v3, v0}, Lcom/b/a/a;->b(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V iget-object v2, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v2}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;)Landroid/content/Context; move-result-object v2 const/16v3, 0x3f5 invoke-static {v2, v3, v0}, Lcom/xxAssistant/Utils/i;->a(Landroid/content/Context;ILjava/lang/String;)V iget-object v0, p0, Lcom/xxAssistant/a/j;->a:Lcom/xxAssistant/a/h; invoke-static {v0, v1}, Lcom/xxAssistant/a/h;->a(Lcom/xxAssistant/a/h;Ljava/lang/String;)V goto :goto_0 .end method
从开头可以看出这确实是一个OnClickListener,重点分析onClick处理过程。
先看这几处字符串:
auto_clean_memory
game_start_have_plugin
game_start_no_plugin
其中auto_clean_memory是在启动游戏前进行一次内存清理以为游戏腾出更多内存。
game_start_have_plugin和game_start_no_plugin分别对应“辅助开启”和没有辅助的游戏,如图:
2、叉叉助手逆向分析(上)_第2张图片
继续分析几个重点的函数调用:
getPackageName
getAppName
invoke-static {v2}, Lcom/xxAssistant/a/h;->b(Lcom/xxAssistant/a/h;)Lcom/xxAssistant/d/d;
经分析com/xxAssistant/d/d是数据库查询相关,这里把操作的数据库提取出来查看:

shell@android:/data/data/com.xxAssistant # ls
ls
app_plugin
cache
databases
files
lib
shared_prefs
utility.plist
xx-filter
shell@android:/data/data/com.xxAssistant # cd databases
cd databases
shell@android:/data/data/com.xxAssistant/databases # ls
ls
XXAssistantDB.db
XXAssistantDB.db-journal
webview.db
webview.db-journal
webviewCookiesChromium.db
webviewCookiesChromium.db-journal
webviewCookiesChromiumPrivate.db

adb pull /data/data/com.xxAssistant/databases/XXAssistantDB.db e:\

显示Permission denied,修改权限: chmod 755 XXAssistantDB.db
再次pull出来:
2、叉叉助手逆向分析(上)_第3张图片
大致看出数据库结果,这里不是重点不再继续深入分析,继续回到a/j,后续的几个关键调用:
com/xxAssistant/Utils/i;
Lcom/xxAssistant/Utils/w;->a(Landroid/content/Context;)Landroid/widget/Toast;
Lcom/xxAssistant/Utils/ag;->a(Landroid/content/Context;ILjava/lang/String;)V
sget-boolean v0, Lcom/xxAssistant/Utils/r;->a:Z

其中 com.xxAssistant.Utils.i代码出现字符串“ http://api.xxzhushou.cn/xxdatareport.php”,猜测是统计使用信息相关的,无重要代码没有继续分析。
com/xxAssistant/Utils/w有一个生成吐司的函数,和一个启动游戏的函数(使用jd-ui打开):
package com.xxAssistant.Utils; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo;import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; importandroid.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.Handler; import android.text.TextUtils.TruncateAt; import android.view.Display;import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.TextView; importandroid.widget.Toast; import com.d.b.bx; import com.xxAssistant.View.MainActivity; import com.xxAssistant.Widget.AlwaysMarqueeTextView; import com.xxAssistant.c.b; importcom.xxAssistant.c.f; import java.util.Iterator; import java.util.List; public class w { private static Context a; private static LinearLayout b; private static TextView c;private static TextView d; private static String e = " 叉叉助手:"; private static String f; private static Toast g; private static Handler h = new x(); public static Toast a(Context paramContext) { a = paramContext; h localh = new h(a); long l1 = localh.a(); localh.b(); long l2 = localh.a(); g = new Toast(a); g = Toast.makeText(a, null, 1); g.setGravity(48, 0, 0); LinearLayout localLinearLayout1 = (LinearLayout)g.getView(); c = new TextView(a); b.a = 1 + b.a; c.setTextSize(14.0F); c.setGravity(16); c.setTextColor(a.getResources().getColor(2131099705)); d = new AlwaysMarqueeTextView(a); int i = (int)(l2 - l1); (10 + (int)((l2 - l1) % 10L)); if (i <= 0); for (int j = 10; ; j = i) { if ((b.c == f.c) && (b.b.size() != 0) && (b.a < b.b.size())) { c.setText(" " + ((bx)b.b.get(b.a)).i() + ":"); d.setSingleLine(); d.setEllipsize(TextUtils.TruncateAt.MARQUEE); d.setText(((bx)b.b.get(b.a)).g()); d.setPadding(0, 0, 0, 0); if (b.a < 30) b.a = 1 + b.a; new z().start(); } while (true) { d.setTextSize(14.0F); d.setGravity(16); d.setTextColor(-1); LinearLayout.LayoutParams localLayoutParams1 = new LinearLayout.LayoutParams(-2, -2); LinearLayout.LayoutParams localLayoutParams2 = new LinearLayout.LayoutParams(-2, -2); LinearLayout.LayoutParams localLayoutParams3 = new LinearLayout.LayoutParams((int)(MainActivity.p.getWidth() - 80.0F * MainActivity.q), (int)(40.0F * MainActivity.q)); LinearLayout.LayoutParams localLayoutParams4 = new LinearLayout.LayoutParams((int)(35.0F * MainActivity.q), (int)(35.0F * MainActivity.q)); LinearLayout localLinearLayout2 = new LinearLayout(a); b = new LinearLayout(a); localLinearLayout2.setOrientation(0); b.setOrientation(0); b.addView(c, localLayoutParams2); b.addView(d, localLayoutParams1); localLinearLayout2.addView(b, localLayoutParams1); localLinearLayout2.setGravity(16); localLinearLayout2.setBackgroundResource(2130837522); ImageView localImageView = new ImageView(a); localImageView.setBackgroundResource(2130837650); localLinearLayout1.addView(localImageView, localLayoutParams4); localLinearLayout1.addView(localLinearLayout2, localLayoutParams3); localLinearLayout1.setOrientation(0); localLinearLayout1.setBackgroundResource(2131099661); f = "成功清理了," + j + "M内存"; return g; c.setText(e); d.setText("成功清理了," + j + "M内存"); } } } public staticBoolean a(String paramString, Context paramContext) { PackageManager localPackageManager = paramContext.getPackageManager(); try { PackageInfo localPackageInfo = localPackageManager.getPackageInfo(paramString, 0); Intent localIntent1 = new Intent("android.intent.action.MAIN", null); localIntent1.addCategory("android.intent.category.LAUNCHER"); localIntent1.setPackage(localPackageInfo.packageName); List localList = localPackageManager.queryIntentActivities(localIntent1, 0); if (localList.iterator().hasNext()) { ResolveInfo localResolveInfo = (ResolveInfo)localList.iterator().next(); String str1 = localResolveInfo.activityInfo.packageName; String str2 = localResolveInfo.activityInfo.name; Intent localIntent2 = new Intent("android.intent.action.MAIN"); localIntent2.addFlags(268435456); localIntent2.addCategory("android.intent.category.LAUNCHER"); localIntent2.setComponent(new ComponentName(str1, str2)); paramContext.startActivity(localIntent2); return Boolean.valueOf(true); } } catch (PackageManager.NameNotFoundException localNameNotFoundException) { while (true) localNameNotFoundException.printStackTrace(); } return Boolean.valueOf(false); } }
第一个返回吐司的a函数不用分析,第二个a函数是启动游戏APP的,启动方式为常规方式。

继续分析 com/xxAssistant/Utils/ag:
package com.xxAssistant.Utils; import android.content.Context; import android.widget.Toast; import java.io.File; public class ag { public static void a(Context paramContext, int paramInt, String paramString) { ah localah = new ah(paramContext); try { File localFile = new File(paramContext.getCacheDir(), ""); if(!localFile.exists()) localFile.mkdir(); new File(paramContext.getCacheDir(), "/plist.xx"); if (Utility.doSymlinkAndChmode(newFile("/data/data/com.xxAssistant/app_plugin/" + paramInt + "/" + paramString + ".xxplist").getAbsolutePath(), paramContext.getCacheDir().toString() + "/plist.xx")) {localah.start(); return; } Toast.makeText(paramContext, "文件部署不成功,请重新启动!", 0).show(); return; } catch (Exception localException) { localException.printStackTrace(); } } }
new了一个ah并启动,可以猜出应该是个Thread,这个我们稍后细分析,中间调用了个doSymlinkAndChmode,用来将插件的配置文件重定向到一个公共的配置文件路径。
上面可以看出插件的配置文件是保存在"/data/data/com.xxAssistant/app_plugin/"目录下的,我们后面具体分析插件的时候便从这里入手
Utility.doSymlinkAndChmode是在Utility中的一个native函数: 
public static native boolean doSymlinkAndChmode(String paramString1, String paramString2);Utility
IDA打开libutility.so,参考字符串doSymlinkAndChmode,找到如下代码:
2、叉叉助手逆向分析(上)_第4张图片
可以看出有许多类似函数名的字符串,以及函数类型的字符串,可以猜测这里是一个“函数表”,通常在NDK开发时会在JNI_OnLoad函数中注册本地函数,注册函数如下所示:
/** * Table of methods associated with a single class. */static JNINativeMethod gMethods[] = { { "load""(Landroid/app/Application;Ljava/lang/String;)V", (void*)load}, {"run""(Landroid/app/Application;Ljava/lang/String;)V", (void*)run}, }; /* * Register native methods for all classes we know about. */static intregisterNativeMethods(JNIEnv* env) { int nError = 0; jclass clazz = NULL; clazz = env->FindClass(JNIREG_CLASS); if (clazz == NULL) { LOGE("clazz is null"); returnJNI_FALSE; } nError = env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]) ); if ( nError < 0 ) { LOGE("RegisterNatives error: %d num: %d",nError,sizeof(gMethods) / sizeof(gMethods[0]) ); return JNI_FALSE; } return JNI_TRUE; }
因此从上表可以找到doSymlinkAndChmode对应的函数主体是sub_1C7C(这里截取一段):

2、叉叉助手逆向分析(上)_第5张图片
主要调用了symlink和chmod,将插件的配置文件重定向到一个公共的配置文件路径。

继续分析ah:
package com.xxAssistant.Utils; import android.content.Context; import java.io.File; class ah extends Thread { ah(Context paramContext) { } public void run() { super.run();try { Thread.sleep(3000L); Utility.doRemoveFile(this.a.getCacheDir().toString() + "/plist.xx"); return; } catch (InterruptedException localInterruptedException) { localInterruptedException.printStackTrace(); } } }
猜测目的:点击“启动游戏”时如果此游戏有插件,则将插件配置文件重定向到一个公共的配置文件路径,游戏启动时底层监控到后读取此公共配置文件,由于做了重定向实际上就是读取当前游戏插件的配置文件,这里的等待三秒钟基本上配置文件也已经使用完毕,然后删除之。

最后是a/j代码中的“ sget-boolean v0, Lcom/xxAssistant/Utils/r;->a:Z”,这是获取一个布尔变量的值,打开Utils/r查看其代码:
package com.xxAssistant.Utils; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; importandroid.content.pm.ApplicationInfo; import android.os.Handler; import com.xxAssistant.Service.GhostService; @SuppressLint({"SdCardPath"}) public class r {public static boolean a = falsepublic static boolean b = truepublic static void a(Context paramContext, Handler paramHandler) { s locals = new s(paramHandler, paramContext, new Intent(paramContext, GhostService.class)); try { String str1 = v.c(paramContext) + "/injectso"; StringBuilder localStringBuilder1 = newStringBuilder(); localStringBuilder1.append(str1); StringBuilder localStringBuilder2 = new StringBuilder(" "); Object[] arrayOfObject = new Object[1]; arrayOfObject[0] = Integer.valueOf(Utility.getppid()); localStringBuilder1.append(String.format("%d", arrayOfObject)); localStringBuilder1.append(" " + "/data/data/com.xxAssistant/lib/libxxghost.so"); localStringBuilder1.append(" loadResAndPlugin"); localStringBuilder1.append(" " + paramContext.getApplicationInfo().sourceDir); String str2 = localStringBuilder1.toString(); locals.start()if ((Utility.a(str2)) || (Utility.isInjected())) {paramHandler.sendEmptyMessage(78); a = true; b = falsereturn; } paramHandler.sendEmptyMessage(79); a = false; b = falsereturn; } catch (Exception localException) { localException.printStackTrace(); } } }
其中a/j里面使用的a是r类中的一个静态布尔变量,是在一个静态的a函数中被赋值的,重点就是这个a函数。其中v.c返回路径: /data/data/com.xxAssistant/cache/injecttest:
package com.xxAssistant.Utils; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.AssetManager; importandroid.content.res.Resources; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; public class v { static String a = "injecttest/"; static String[] b = { "injectso" }; static String[] c = { "libxxghost.so" }; static File d = nullstatic File a(File paramFile, Context paramContext) {if (paramFile.exists()) paramFile.delete(); paramFile.mkdirs(); paramFile.setReadable(true); paramFile.setWritable(true); paramFile.setExecutable(true); return paramFile; } @SuppressLint({"NewApi"}) static Boolean a(File paramFile, String paramString, Context paramContext) { byte[] arrayOfByte = new byte[4096]; File localFile = newFile(paramFile.getAbsolutePath(), paramString); if (localFile.exists()) localFile.delete(); try { InputStream localInputStream = paramContext.getResources().getAssets().open(paramString); FileOutputStream localFileOutputStream = new FileOutputStream(localFile); while (true) { int i = localInputStream.read(arrayOfByte); if (i == -1) { localFileOutputStream.flush(); localFileOutputStream.close(); localFile.setReadable(true); localFile.setWritable(true); localFile.setExecutable(true); return Boolean.valueOf(true); } localFileOutputStream.write(arrayOfByte, 0, i); } } catch (Exception localException) { localException.printStackTrace(); } return Boolean.valueOf(false); } public static void a(Context paramContext) { Log.d("NativeFileInstaller", "### install begin!"); d = a(b(paramContext), paramContext); int i = 0; int j = b.length; int k = 0; if (i >= j); while (true) { if (k >= c.length) { returnif (!a(d, b[i], paramContext).booleanValue()) Log.e("NativeFileInstaller", "Orz exe: " + b[i]); i++; break; } if (!a(d, c[k], paramContext).booleanValue()) Log.e("NativeFileInstaller", "Orz so: " + c[k]); k++; } } public static File b(Context paramContext) { return new File(paramContext.getCacheDir(), a); } public static String c(Context paramContext) { return b(paramContext).getAbsolutePath(); } }
再连接上/injectso就是: /data/data/com.xxAssistant/cache/injecttest/injectso,后面格式化注入命令,也就是把/data/data/com.xxAssistant/lib/libxxghost.so注入进父进程并调用函数:loadResAndPlugin。由于叉叉助手里面也有Log输出,截获的log信息为:
/data/data/com.xxAssistant/cache/injecttest/injectso 165 /data/data/com.xxAssistant/lib/libxxghost.so loadResAndPlugin /data/app/com.xxAssistant-1.apk

判断是否注入成功Utility.isInjected():

如果注入成功则发送消息: paramHandler.sendEmptyMessage(78);
转换为十六进制:78 = 0x4E,在APKIDE里搜索0x4E,找到一处代码:
:sswitch_data_0 .sparse-switch 0xa -> :sswitch_0 0xb -> :sswitch_1 0xc -> :sswitch_2 0xd -> :sswitch_3 0x15 -> :sswitch_4 0x4d -> :sswitch_5 0x4e -> :sswitch_6 0x4f -> :sswitch_7 0x50 -> :sswitch_8 0x51 -> :sswitch_9 .end sparse-switch .end method
是在view/an.smali中,再在jd-gui中打开view/an的代码:
package com.xxAssistant.View; import android.app.LocalActivityManager; import android.content.Intent; import android.content.SharedPreferences; importandroid.content.SharedPreferences.Editor; import android.content.res.Resources; import android.os.Handler; import android.os.Message; import android.view.Window; importandroid.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; importandroid.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.b.a.a; importcom.xxAssistant.Utils.i; import com.xxAssistant.h.b; import java.util.HashMap; class an extends Handler { an(MainActivity paramMainActivity) { } public voidhandleMessage(Message paramMessage) { switch (paramMessage.what) { defaultcase 10: case 11: case 12: case 13: do { do { do { do returnwhile (this.a.j == 0);this.a.l.setSelected(true); MainActivity.a(this.a).setSelected(true); this.a.c.setText(this.a.getResources().getString(2131230722)); MainActivity.m.setSelected(false); MainActivity.n.setSelected(false); this.a.o.setSelected(false); MainActivity.b(this.a).setSelected(false); MainActivity.c(this.a).setSelected(false); MainActivity.d(this.a).setSelected(false); this.a.b.removeAllViews(); this.a.b.addView(MainActivity.e(this.a), MainActivity.f(this.a));this.a.e.setBackgroundResource(2130837691); this.a.j = 0; return; } while (this.a.j == 1); if ((PluginActivity.k) && (PluginActivity.m == 1)) { TranslateAnimation localTranslateAnimation = new TranslateAnimation(PluginActivity.l, PluginActivity.l + PluginActivity.n, 0.0F, 0.0F); AlphaAnimation localAlphaAnimation4 = newAlphaAnimation(0.0F, 1.0F); localAlphaAnimation4.setDuration(0L); localTranslateAnimation.setFillAfter(true); localTranslateAnimation.setDuration(0L); PluginActivity.j.startAnimation(localAlphaAnimation4); PluginActivity.j.startAnimation(localTranslateAnimation); } MainActivity.m.setSelected(true); MainActivity.b(this.a).setSelected(true); this.a.c.setText(this.a.getResources().getString(2131230723)); this.a.l.setSelected(false); MainActivity.n.setSelected(false);this.a.o.setSelected(false); MainActivity.a(this.a).setSelected(false); MainActivity.c(this.a).setSelected(false); MainActivity.d(this.a).setSelected(false);this.a.b.removeAllViews(); if (MainActivity.g(this.a) == null) MainActivity.a(this.a, this.a.getLocalActivityManager().startActivity("AllPluginActivity", MainActivity.h(this.a)).getDecorView()); this.a.b.addView(MainActivity.g(this.a), MainActivity.f(this.a)); this.a.e.setBackgroundResource(2130837676); this.a.j = 1;return; } while (this.a.j == 2); MainActivity.n.setSelected(true); this.a.c.setText(this.a.getResources().getString(2131230724)); MainActivity.m.setSelected(false); MainActivity.b(this.a).setSelected(true); this.a.l.setSelected(false); this.a.o.setSelected(false); MainActivity.a(this.a).setSelected(false); MainActivity.b(this.a).setSelected(false); MainActivity.d(this.a).setSelected(false); if (MainActivity.i(this.a) == null) MainActivity.b(this.a,this.a.getLocalActivityManager().startActivity("QuanActivity", MainActivity.j(this.a)).getDecorView()); this.a.b.removeAllViews(); this.a.b.addView(MainActivity.i(this.a), MainActivity.f(this.a)); this.a.e.setBackgroundResource(2130837676); this.a.j = 2; return; } while (this.a.j == 3); MainActivity.f.setVisibility(8); MainActivity.k(this.a).edit().putInt(MainActivity.A, MainActivity.A).commit(); this.a.o.setSelected(true); MainActivity.d(this.a).setSelected(true);this.a.c.setText(this.a.getResources().getString(2131230725)); MainActivity.m.setSelected(false); MainActivity.n.setSelected(false); this.a.l.setSelected(false); MainActivity.a(this.a).setSelected(false); MainActivity.c(this.a).setSelected(false); MainActivity.b(this.a).setSelected(false); if (MainActivity.l(this.a) == null) MainActivity.c(this.a, this.a.getLocalActivityManager().startActivity("MoreActivity", MainActivity.m(this.a)).getDecorView()); this.a.b.removeAllViews();this.a.b.addView(MainActivity.l(this.a), MainActivity.f(this.a)); this.a.e.setBackgroundResource(2130837676); if (b.m.size() != 0) if (MoreActivity.c != null) MoreActivity.c.setVisibility(0); while (true) { this.a.j = 3; returnif (MoreActivity.c != null) MoreActivity.c.setVisibility(4); } case 21: if (this.a.j != 0) { Intent localIntent1 = new Intent(); a.a(this.a, "client_share"); i.a(this.a, 1008); localIntent1.setAction("android.intent.action.SEND"); localIntent1.setType("text/plain"); localIntent1.putExtra("android.intent.extra.SUBJECT", this.a.getResources().getString(2131230800)); localIntent1.putExtra("android.intent.extra.TEXT",this.a.getResources().getString(2131230801)); Intent localIntent2 = Intent.createChooser(localIntent1, this.a.getResources().getString(2131230800));this.a.startActivity(localIntent2); return; } Intent localIntent3 = new Intent(this.a, ToolActivity.class); this.a.startActivity(localIntent3); returncase 77: AlphaAnimation localAlphaAnimation3 = new AlphaAnimation(0.0F, 1.0F); localAlphaAnimation3.setDuration(500L); this.a.g.setVisibility(0); localAlphaAnimation3.setAnimationListener(new ao(this)); this.a.g.startAnimation(localAlphaAnimation3); returncase 78: AlphaAnimation localAlphaAnimation2 = newAlphaAnimation(1.0F, 0.0F); this.a.g.setText(this.a.getResources().getString(2131230761)); localAlphaAnimation2.setDuration(2000L); localAlphaAnimation2.setAnimationListener(new ap(this)); this.a.g.startAnimation(localAlphaAnimation2); returncase 79: AlphaAnimation localAlphaAnimation1 = newAlphaAnimation(1.0F, 0.0F); this.a.g.setText(this.a.getResources().getString(2131230762)); localAlphaAnimation1.setDuration(1000L); localAlphaAnimation1.setAnimationListener(new aq(this)); this.a.g.startAnimation(localAlphaAnimation1); returncase 80: MainActivity.f.setVisibility(0); returncase 81: } Toast.makeText(this.a, MainActivity.x, 1).show(); } }
这里是所有自定义消息的处理代码,其他的先不分析,找到78消息:
case 78: AlphaAnimation localAlphaAnimation2 = new AlphaAnimation(1.0F, 0.0F); this.a.g.setText(this.a.getResources().getString(2131230761)); localAlphaAnimation2.setDuration(2000L); localAlphaAnimation2.setAnimationListener(new ap(this)); this.a.g.startAnimation(localAlphaAnimation2); return;
这里使用了一个id为 2131230761的资源,转为十六进制是0x7F080029,在APKIDE中查找 0x7F080029,搜索结果:
<public type="string" name="inject_success" id="0x7f080029" />
继续搜索inject_success,搜索结果:
<string name="inject_success">获取root成功</string>
综上,r的静态a函数其实是一段负责注入的代码,“启动游戏”只是使用了是否注入成功的布尔值,并不曾调用这个静态的a函数,下面分析a是在何时被调用的。
思路:com.xxAssistant.Utils包下的r/a调用在smali语法下应该为:com/xxAssistant/Utils/r;->a,也就是说在smali代码中应该至少有一处invoke调用,格式为:com/xxAssistant/Utils/r;->a,
因此在APKIDE中搜索com/xxAssistant/Utils/r;->a,搜索结果:
这两处调用均在一个函数中执行,jd-gui打开com.xxAssistant.View.as.class:
package com.xxAssistant.View; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; importcom.xxAssistant.DialogView.RebootWindowActivity; import com.xxAssistant.Utils.Utility; import com.xxAssistant.Utils.k; import com.xxAssistant.Utils.q; importcom.xxAssistant.Utils.r; import com.xxAssistant.Utils.v; import com.xxAssistant.f.s; class as extends Thread { as(MainActivity paramMainActivity) { } publicvoid run() { Utility.a("chmod 777 /data/data/com.xxAssistant/lib/*\n"); q.a(this.a); if (this.a.w.compareTo(this.a.v) != 0) { s.a(MainActivity.n(this.a)); k.a(); k.b(); this.a.B.edit().putString("version", this.a.v).commit(); if (Utility.isInjectFrameChanged("2.0")) { Intent localIntent = new Intent(this.a, RebootWindowActivity.class); this.a.startActivity(localIntent); if (Utility.doGetAndroidLoaderVersion().compareTo("2.0") < 0) MainActivity.o(this.a); return; } v.a(this.a); r.a(this.a, this.a.C); return; } v.a(this.a); r.a(this.a, this.a.C); } }
也就是说注入代码是在as线程中调用的,下面查找使用as的代码,同上述方法在APKIDE中搜索: com/xxAssistant/View/as;  搜索结果:
2、叉叉助手逆向分析(上)_第6张图片
忽略com/xxAssistant/View/as.smali(本身),只剩下com\xxAssistant\View\MainActivity.smali
com\xxAssistant\View\MainActivity.smali中有引用,是在构造函数中:
.method public constructor <init>()V .locals 2 …… new-instance v0, Lcom/xxAssistant/View/as; invoke-direct {v0, p0}, Lcom/xxAssistant/View/as;-><init>(Lcom/xxAssistant/View/MainActivity;)V iput-object v0, p0, Lcom/xxAssistant/View/MainActivity;->F:Ljava/lang/Thread; return-void .end method
可以看出在MainActivity构造函数中new了一个as并赋给成员变量F,后面查找F的引用,相同方法在APKIDE中搜索: com/xxAssistant/View/MainActivity;->F
(这一次搜索勾选上匹配大小写),搜索结果:
2、叉叉助手逆向分析(上)_第7张图片
其中第一个就是构造函数中的引用代码,这里查看第二个搜索结果,是在MainActivity的onCreate函数中启动此线程函数:
.method protected onCreate(Landroid/os/Bundle;)V .locals 8 …… iget-object v0, p0, Lcom/xxAssistant/View/MainActivity;->F:Ljava/lang/Thread; invoke-virtual {v0}, Ljava/lang/Thread;->start()V invoke-direct {p0}, Lcom/xxAssistant/View/MainActivity;->g()V invoke-direct {p0}, Lcom/xxAssistant/View/MainActivity;->a()V return-void ……goto :goto_0 .end method

综上:在叉叉助手的MainActivity的构造函数中声明一个as线程,并在onCreate函数中启动as线程函数,as线程调用r.a进行注入。游戏启动时只负责按照常规方式启动游戏,猜测注入的so库对于APK启动有监控,监控规则则是通过重定向的公共配置文件来读取,下面着重分析下插件的配置文件,见下篇。

你可能感兴趣的:(2、叉叉助手逆向分析(上))