描述:主要讲解如何有条例地逆向分析出软件的主要逻辑。
工具: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;->
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 synthetic a: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-string v4, "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-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 {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/16 v3, 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/16 v3, 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;
import android.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;
import android.widget.Toast;
import com.d.b.bx;
import com.xxAssistant.View.MainActivity;
import com.xxAssistant.Widget.AlwaysMarqueeTextView;
import com.xxAssistant.c.b;
import com.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 static Boolean 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(new File("/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 int registerNativeMethods(JNIEnv* env)
{
int nError = 0;
jclass clazz = NULL;
clazz = env->FindClass(JNIREG_CLASS);
if (clazz == NULL) {
LOGE("clazz is null");
return JNI_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;
import android.content.pm.ApplicationInfo;
import android.os.Handler;
import com.xxAssistant.Service.GhostService;
@SuppressLint({"SdCardPath"})
public class r
{
public static boolean a = false;
public static boolean b = true;
public 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 = new StringBuilder();
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 = false;
return;
}
paramHandler.sendEmptyMessage(79);
a = false;
b = false;
return;
}
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;
import android.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 = null;
static 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 = new File(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)
{
return;
if (!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;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.view.Window;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.b.a.a;
import com.xxAssistant.Utils.i;
import com.xxAssistant.h.b;
import java.util.HashMap;
class an extends Handler
{
an(MainActivity paramMainActivity)
{
}
public void handleMessage(Message paramMessage)
{
switch (paramMessage.what)
{
default:
case 10:
case 11:
case 12:
case 13:
do
{
do
{
do
{
do
return;
while (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 = new AlphaAnimation(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;
return;
if (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);
return;
case 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);
return;
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;
case 79:
AlphaAnimation localAlphaAnimation1 = new AlphaAnimation(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);
return;
case 80:
MainActivity.f.setVisibility(0);
return;
case 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;
import com.xxAssistant.DialogView.RebootWindowActivity;
import com.xxAssistant.Utils.Utility;
import com.xxAssistant.Utils.k;
import com.xxAssistant.Utils.q;
import com.xxAssistant.Utils.r;
import com.xxAssistant.Utils.v;
import com.xxAssistant.f.s;
class as extends Thread
{
as(MainActivity paramMainActivity)
{
}
public void 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启动有监控,监控规则则是通过重定向的公共配置文件来读取,下面着重分析下插件的配置文件,见下篇。
文档信息
- 本文作者:zhupite
- 本文链接:https://zhupite.com/android/xxzhushou-2.html
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)