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

2014/04/08 android 共 27230 字,约 78 分钟

描述:主要讲解如何有条例地逆向分析出软件的主要逻辑。

工具:APKIDE,JD-GUI

方法:顺藤摸瓜,smali代码主要看invoke关键函数调用,定位到相应的类中看代码。

使用APKIDE反编译xxzhushou_android.apk。

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

img

搜索“启动游戏”字符串无果,猜测是图片显示,在com.xxAssistant\res\drawable目录下找到图片:

img

在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;->(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 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分别对应“辅助开启”和没有辅助的游戏,如图:

img

继续分析几个重点的函数调用:

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出来:

img

大致看出数据库结果,这里不是重点不再继续深入分析,继续回到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,找到如下代码:

img

可以看出有许多类似函数名的字符串,以及函数类型的字符串,可以猜测这里是一个“函数表”,通常在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(这里截取一段):

img

主要调用了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():

img

如果注入成功则发送消息: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,搜索结果:img

这两处调用均在一个函数中执行,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; 搜索结果:

img

忽略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

(这一次搜索勾选上匹配大小写),搜索结果:

img

其中第一个就是构造函数中的引用代码,这里查看第二个搜索结果,是在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启动有监控,监控规则则是通过重定向的公共配置文件来读取,下面着重分析下插件的配置文件,见下篇。

文档信息

Search

    Table of Contents