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

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

使用APKIDE反编译xxzhushou_android.apk。

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

搜索“启动游戏”字符串无果,猜测是图片显示,在 com.xxAssistant\res\drawable目录下找到图片:
在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分别对应“辅助开启”和没有辅助的游戏,如图:
继续分析几个重点的函数调用:
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出来:
大致看出数据库结果,这里不是重点不再继续深入分析,继续回到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,找到如下代码:
可以看出有许多类似函数名的字符串,以及函数类型的字符串,可以猜测这里是一个“函数表”,通常在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(这里截取一段):

主要调用了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;  搜索结果:
忽略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
(这一次搜索勾选上匹配大小写),搜索结果:
其中第一个就是构造函数中的引用代码,这里查看第二个搜索结果,是在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启动有监控,监控规则则是通过重定向的公共配置文件来读取,下面着重分析下插件的配置文件,见下篇。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/350320.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

3、叉叉助手逆向分析(下)

plugin/117/xxFknsg.apk --ui-name:com/xxAssistant/FknsgUI/xxMain --activity-name:com/babeltimes/main/MainActivity --so-path:/data/data/com.xxAssistant/app_plugin/117/libxxfknsg.so 我们将手机里的apk提取出来&#xff1a;com.babeltime.fknsango_360-1.apk&#xf…

android 我叫mt 插件,叉叉我叫MT助手

叉叉mt助手是我叫mt的安卓版辅助插件&#xff0c;提供加速、自动点箭头、自动点异常、自动售卡、买体力、一键收/送体力、副本计数等功能&#xff0c;只需要一键精英就已经OK&#xff0c;叉叉mt助手旨在为玩家节省时间&#xff0c;提高游戏效率&#xff0c;给玩家最贴心的服务&…

使用GCN根据颗粒图像预测对应性能

之前做一个小实验写的代码&#xff0c;本想创建个git repo&#xff0c;想了想好像没必要&#xff0c;直接用篇博文记录一下吧。 对应资源 &#xff1a; https://download.csdn.net/download/rayso9898/87865298 0. 大纲 0.1 代码说明 dataGeneration.py -> RSA生成n张图像&…

电表网关BL102采集DL/T645电表的操作步骤

使用钡铼BL102网关&#xff1a;西门子S7-200PLC对接ThingsBoard流程 本文主要讲述了钡铼技术BL102物联网网关如何通过RS485采集DL/T645规约电表 BL102是一款采集西门子、三菱、欧姆龙、台达、AB、施耐德等各种PLC数据转换为Modbus TCP、OPC UA、MQTT、ThingsBoard等协议的工业…

JAVA开发(神乎其神的区块链技术之数据上链)

这是我第二遍写关于区块链的博文&#xff0c;前一篇文章《神乎其神的区块链概念和技术》主要介绍区块链的由来和基本概念。因为博主最近在做一个区块链项目&#xff0c;所以有时候也遇到一些概念性的知识需要去理解&#xff0c;比如数据的上链。谈到数据上链&#xff0c;我们先…

OpenCV(图像处理)-基于Python-图像的基本变换-平移-翻转-仿射变换-透视变换

1. 概述2. 接口介绍resize()flip()rotate()仿射变换warpAffine()getRotationMatrix2D()-变换矩阵1getAffineTransform()-变换矩阵2 透视变换warpPerspective()getPerspectiveTransform() 1. 概述 为了方便开发人员的操作&#xff0c;OpenCV还提供了一些图像变换的API&#xff…

计算机改名字后找不到网络,改了wifi名字后电脑搜不到网络怎么办? | 192路由网...

问&#xff1a;为什么我改了wifi名字后&#xff0c;我的电脑就搜不到wifi信号了&#xff1f; 答&#xff1a;修改wifi名称后如果搜索不到wifi信号了&#xff0c;可以按照下面的步骤进行操作&#xff0c;以解决此问题。 1. 如果将wifi名字改成了中文&#xff0c;建议你将其修改为…

柠檬班python自动化百度云_柠字取名2019-尚名网

柠字取名2019-尚名网 名字不是一个简单、随便的称号&#xff0c;它隐含着不容忽视的信息力量。寓意好的名字有积极的暗示作用&#xff0c;使人更有信心和勇气去实现理想&#xff0c;寓意欠佳的名字反之。可见&#xff0c;名字对人们而言是非常重要的&#xff0c;为人父母者一定…

为了取一个花名,我爬下了中草药网所有的名字!

很酷哦&#xff01;不过&#xff0c;对我这个选择恐惧症来说&#xff0c;也很纠结…我们先看一下有哪些要求吧&#xff1f; 中草药名&#xff1f;人参&#xff1f;西洋参&#xff1f;还有啥&#xff1f;&#xff1f;&#xff1f; 作为一个不怎么吃药的非医学生&#xff0c;这题…

使用MySQL查找姓名重名_查询名字有多少人重名,全国同名同姓查询全国姓名数据库...

查询名字有多少人重名&#xff0c;全国同名同姓查询全国姓名数据库 时间&#xff1a;2020-04-04 15:30:01 很多爸爸妈妈在帮孩子取姓名的时候&#xff0c;会想了解在全国范围内重名的人数&#xff0c;希望宝宝的名字不会跟太多人一样。或者有的小伙伴单纯想弄明白全中国同自己姓…

百度排名优化工具 V3.0 正式版

介绍 百度排名优化工具正式版是款可以迅速提升网址百度搜索排名的工具。软件拥有智能计算关键词点击数&#xff0c;点击规则自动添加等。软件还提供了维护模式&#xff0c;自动维护您的关键词排名&#xff0c;让您的关键词排名更加稳定可靠。百度排名优化工具可以将你的网站在…

给Android系统瘦身,安卓优化大师:给系统瘦身

安卓优化大师是一款基于Android平台的系统优化软件&#xff0c;最新版本界面设计简单&#xff0c;功能全面&#xff0c;可以帮助Android手机用户给系统瘦身&#xff0c;优化手机性能。 程序名称&#xff1a;安卓优化大师 平台&#xff1a;Android 类型&#xff1a;系统优化 软件…

Windows优化大师7.96版下载

Windows优化大师提供了全面且有效而简便安全的系统检测、系统优化、系统清理、系统维护四大功能模块以及数个附加的工具软件。它能够有效地帮助用户了解自己的计算机软硬件信息&#xff1b;简化操作系统设置步骤&#xff1b;提升计算机运行效率&#xff1b;清理系统运行时产生的…

SEO优化工具-免费SEO优化工具下载-SEO优化工具大全中心

什么是SEO优化工具&#xff1f;SEO优化工具&#xff08;Seo tools&#xff09;能在搜索引擎优化过程中起到辅助的作用&#xff0c;如数据查询工具、网站排名工具、网站流量分析功能&#xff0c;站群管理工具等&#xff0c;用来提高每个SEO人员工作中的效率。 seo优化工具&#…

Android性能优化之APK优化,完整版开放下载

前言 移动研发火热不停&#xff0c;越来越多人开始学习 android 开发。但很多人感觉入门容易成长很难&#xff0c;对未来比较迷茫&#xff0c;不知道自己技能该怎么提升&#xff0c;到达下一阶段需要补充哪些内容。市面上也多是谈论知识图谱&#xff0c;缺少体系和成长节奏感&a…

win10优化大师v1.0去插件免费版

名称&#xff1a;win10优化大师v1.0去插件免费版 版本&#xff1a;1.0 软件大小&#xff1a;5.70MB 软件语言&#xff1a;中文简体 软件授权&#xff1a;免费版 应用平台&#xff1a;Win10 win10优化大师是一款面向Win10操作系统提供的优化软件&#xff0c;提供常用系统功能的…

Android性能优化工具

一、性能优化工具基础 1.1 概述 在Android开发中&#xff0c;开发者可通过"系统跟踪"观察Android设备的运行情况并生成跟踪报告&#xff0c;在此基础上进行分析优化。Android 平台提供了多种获取跟踪信息的工具&#xff1a; Android Studio CPU 性能剖析器Systrace…

PS 的常见抠图工具

PS 的常见抠图工具 1. 套索工具2. 多边形套索工具3. 磁性套索工具4. 对象套索工具5. 快速套索工具6. 魔棒工具7. 其他 1. 套索工具 能完成快速抠图, 缺点是不好控制. 2. 多边形套索工具 绘制多边形区域抠图, 缺点是不够圆滑, 返回上步是 Backspace 键. 3. 磁性套索工具 吸附边缘…

PS抠图的6种方法

1. 魔棒工具 用于去除单色背景色图片。 选中魔棒工具后&#xff0c;可以点击选中图片中的背景色进行选取&#xff0c;选中后可以去除背景。魔棒工具一般用来去除背景色为单调色的背景&#xff0c;比如背景是白色或者其他纯色之类的。 在选择时可以选择容差\连续&#xff1a; 连…

【QQ聊天界面、创建模型、懒加载数据 Objective-C语言】

一、今天我们要做的就是这个案例 1.我们今天要做的案例,做好了之后的效果就是这样 这个案例,和昨天那个微博的案例是非常相像的, 哪些相像呢, 1)整体是不是也是能滚动啊, 2)能滚动,它不仅仅是一个UIScrollView 它里面,这个也是一行、两行、三行、四行、 所以说,…