FART:ART环境下基于主动调用的自动化脱壳方案 [android脱壳源码公开,基于android-9.0.0_r36]
2020-01-08 12:51:23 Author: bbs.pediy.com(查看原文) 阅读量:433 收藏

一、背景

hanbingle在看雪论坛上分享了[原创]FART:ART环境下基于主动调用的自动化脱壳方案,思路非常棒;可惜并没有公开修改android源码部分,这让深入理解脱壳方案或者定制化自己的脱壳方案存在困难,本文通过逆向FART所提供的system.img镜像得到思路,同时修改源码适配android-9.0.0_r36,并公开脱壳源码。

二、如何逆向system.img呢?

1、simg2img system.img system.img.ext4

2、sudo mkdir sysmain

3、sudo mount -t ext4 -o loop system.img.ext4 sysmain

进入到sysmain中,找到framework.jar、core-libart.jar、libart.so,主要涉及修改的是 framework.jar中的ActivityThread.java、core-libart.jar中的DexFile.java、libart.so中的libdexfile/dex/standard_dex_file.h、runtime/art_method-inl.h、runtime/art_method.h、runtime/native/dalvik_system_DexFile.cc。

将framework.jar改为 framework.zip解压后得到classes.dex,使用dex2jar,jd-gui转换为java代码查看,但jd-guid无法将核心的smali代码转为java代码。

所以只能通过阅读smali来了解脱壳思路,使用dex2smali将dex转换为smali,我们主要看ActivityThread.smali;

同理core-libart.jar也是同样的思路,最终我们得到DexFile.java,在这里只是加了一个函数,这个函数时个native方法,我们会在libart.so里面实现,在 ActivityThread.smali 里面调用,如何调用呢,我们看接下来的分析。

public classs DexFile {
    +private static native void dumpMethodCode(Object methodid);
}

三、分析Java层脱壳代码

我们先看 ActivityThread.smali里面核心的脱壳代码:

.method public static fart()V
    .catch Ljava/lang/Exception; { :L0 .. :L1 } :L11
    .catch Ljava/lang/Exception; { :L3 .. :L4 } :L12
    .catch Ljava/lang/IllegalAccessException; { :L15 .. :L16 } :L19
    .catch Ljava/lang/IllegalAccessException; { :L21 .. :L22 } :L25
    .catch Ljava/lang/reflect/InvocationTargetException; { :L21 .. :L22 } :L24
    .registers 30
    .prologue
    .line 701
    invoke-static { }, Landroid/app/ActivityThread;->getClassloader()Ljava/lang/ClassLoader;
    move-result-object v5
    .line 702
    .local v5, appClassloader:Ljava/lang/ClassLoader;
    new-instance v9, Ljava/util/ArrayList;
    invoke-direct { v9 }, Ljava/util/ArrayList;-><init>()V
    .line 703
    .local v9, dexFilesArray:Ljava/util/List;, "Ljava/util/List<Ljava/lang/Object;>;"
    const-string/jumbo v25, "dalvik.system.BaseDexClassLoader"
    const-string/jumbo v26, "pathList"
    move-object/from16 v0, v25
    move-object/from16 v1, v26
    invoke-static { v5, v0, v1 }, Landroid/app/ActivityThread;->getClassField(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Field;
    move-result-object v23
    .line 705
    .local v23, pathList_Field:Ljava/lang/reflect/Field;
    const-string/jumbo v25, "dalvik.system.BaseDexClassLoader"
    const-string/jumbo v26, "pathList"
    move-object/from16 v0, v25
    move-object/from16 v1, v26
    invoke-static { v0, v5, v1 }, Landroid/app/ActivityThread;->getFieldOjbect(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
    move-result-object v24
    .line 706
    .local v24, pathList_object:Ljava/lang/Object;
    const-string/jumbo v25, "dalvik.system.DexPathList"
    const-string/jumbo v26, "dexElements"
    move-object/from16 v0, v25
    move-object/from16 v1, v24
    move-object/from16 v2, v26
    invoke-static { v0, v1, v2 }, Landroid/app/ActivityThread;->getFieldOjbect(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
    move-result-object v4
    check-cast v4, [Ljava/lang/Object;
    .line 707
    .local v4, ElementsArray:[Ljava/lang/Object;
    const/4 v8, 0
    :L0
    .line 709
    .local v8, dexFile_fileField:Ljava/lang/reflect/Field;
    const-string/jumbo v25, "dalvik.system.DexPathList$Element"
    const-string/jumbo v26, "dexFile"
    move-object/from16 v0, v25
    move-object/from16 v1, v26
    invoke-static { v5, v0, v1 }, Landroid/app/ActivityThread;->getClassField(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Field;
    :L1
    move-result-object v8
    :L2
    .line 713
    .end local v8
    const/4 v3, 0
    :L3
    .line 715
    .local v3, DexFileClazz:Ljava/lang/Class;
    const-string/jumbo v25, "dalvik.system.DexFile"
    move-object/from16 v0, v25
    invoke-virtual { v5, v0 }, Ljava/lang/ClassLoader;->loadClass(Ljava/lang/String;)Ljava/lang/Class;
    :L4
    move-result-object v3
    :L5
    .line 719
    .end local v3
    const/16 v19, 0
    .line 720
    .local v19, getClassNameList_method:Ljava/lang/reflect/Method;
    const/4 v7, 0
    .line 721
    .local v7, defineClass_method:Ljava/lang/reflect/Method;
    const/4 v11, 0
    .line 722
    .local v11, dumpDexFile_method:Ljava/lang/reflect/Method;
    const/4 v12, 0
    .line 724
    .local v12, dumpMethodCode_method:Ljava/lang/reflect/Method;
    invoke-virtual { v3 }, Ljava/lang/Class;->getDeclaredMethods()[Ljava/lang/reflect/Method;
    move-result-object v26
    const/16 v25, 0
    move-object/from16 v0, v26
    array-length v0, v0
    move/from16 v27, v0
    :L6
    .end local v7
    .end local v11
    .end local v12
    .end local v19
    move/from16 v0, v25
    move/from16 v1, v27
    if-ge v0, v1, :L13
    aget-object v18, v26, v25
    .line 725
    .local v18, field:Ljava/lang/reflect/Method;
    invoke-virtual/range { v18 .. v18 }, Ljava/lang/reflect/Method;->getName()Ljava/lang/String;
    move-result-object v28
    const-string/jumbo v29, "getClassNameList"
    invoke-virtual/range { v28 .. v29 }, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
    move-result v28
    if-eqz v28, :L7
    .line 726
    move-object/from16 v19, v18
    .line 727
    .local v19, getClassNameList_method:Ljava/lang/reflect/Method;
    const/16 v28, 1
    move-object/from16 v0, v19
    move/from16 v1, v28
    invoke-virtual { v0, v1 }, Ljava/lang/reflect/Method;->setAccessible(Z)V
    :L7
    .line 729
    .end local v19
    invoke-virtual/range { v18 .. v18 }, Ljava/lang/reflect/Method;->getName()Ljava/lang/String;
    move-result-object v28
    const-string/jumbo v29, "defineClassNative"
    invoke-virtual/range { v28 .. v29 }, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
    move-result v28
    if-eqz v28, :L8
    .line 730
    move-object/from16 v7, v18
    .line 731
    .local v7, defineClass_method:Ljava/lang/reflect/Method;
    const/16 v28, 1
    move/from16 v0, v28
    invoke-virtual { v7, v0 }, Ljava/lang/reflect/Method;->setAccessible(Z)V
    :L8
    .line 733
    .end local v7
    invoke-virtual/range { v18 .. v18 }, Ljava/lang/reflect/Method;->getName()Ljava/lang/String;
    move-result-object v28
    const-string/jumbo v29, "dumpDexFile"
    invoke-virtual/range { v28 .. v29 }, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
    move-result v28
    if-eqz v28, :L9
    .line 734
    move-object/from16 v11, v18
    .line 735
    .local v11, dumpDexFile_method:Ljava/lang/reflect/Method;
    const/16 v28, 1
    move/from16 v0, v28
    invoke-virtual { v11, v0 }, Ljava/lang/reflect/Method;->setAccessible(Z)V
    :L9
    .line 737
    .end local v11
    invoke-virtual/range { v18 .. v18 }, Ljava/lang/reflect/Method;->getName()Ljava/lang/String;
    move-result-object v28
    const-string/jumbo v29, "dumpMethodCode"
    invoke-virtual/range { v28 .. v29 }, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
    move-result v28
    if-eqz v28, :L10
    .line 738
    move-object/from16 v12, v18
    .line 739
    .local v12, dumpMethodCode_method:Ljava/lang/reflect/Method;
    const/16 v28, 1
    move/from16 v0, v28
    invoke-virtual { v12, v0 }, Ljava/lang/reflect/Method;->setAccessible(Z)V
    :L10
    .line 724
    .end local v12
    add-int/lit8 v25, v25, 1
    goto :L6
    :L11
    .line 710
    .end local v18
    .restart local v8
    move-exception v13
    .line 711
    .local v13, e:Ljava/lang/Exception;
    const-string/jumbo v25, "ActivityThread->err"
    invoke-static { v13 }, Landroid/util/Log;->getStackTraceString(Ljava/lang/Throwable;)Ljava/lang/String;
    move-result-object v26
    invoke-static/range { v25 .. v26 }, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
    goto/16 :L2
    :L12
    .line 716
    .end local v8
    .end local v13
    .restart local v3
    move-exception v13
    .line 717
    .restart local v13
    const-string/jumbo v25, "ActivityThread->err"
    invoke-static { v13 }, Landroid/util/Log;->getStackTraceString(Ljava/lang/Throwable;)Ljava/lang/String;
    move-result-object v26
    invoke-static/range { v25 .. v26 }, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
    goto/16 :L5
    :L13
    .line 742
    .end local v3
    .end local v13
    const-string/jumbo v25, "dalvik.system.DexFile"
    const-string/jumbo v26, "mCookie"
    move-object/from16 v0, v25
    move-object/from16 v1, v26
    invoke-static { v5, v0, v1 }, Landroid/app/ActivityThread;->getClassField(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Field;
    move-result-object v21
    .line 743
    .local v21, mCookiefield:Ljava/lang/reflect/Field;
    const/16 v20, 0
    :L14
    .local v20, j:I
    array-length v0, v4
    move/from16 v25, v0
    move/from16 v0, v20
    move/from16 v1, v25
    if-ge v0, v1, :L26
    .line 744
    aget-object v17, v4, v20
    .line 745
    .local v17, element:Ljava/lang/Object;
    const/4 v10, 0
    :L15
    .line 747
    .local v10, dexfile:Ljava/lang/Object;
    move-object/from16 v0, v17
    invoke-virtual { v8, v0 }, Ljava/lang/reflect/Field;->get(Ljava/lang/Object;)Ljava/lang/Object;
    :L16
    move-result-object v10
    :L17
    .line 751
    .end local v10
    if-nez v10, :L20
    .line 752
    const-string/jumbo v25, "ActivityThread"
    const-string/jumbo v26, "dexfile is null"
    invoke-static/range { v25 .. v26 }, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
    :L18
    .line 743
    add-int/lit8 v20, v20, 1
    goto :L14
    :L19
    .line 748
    .restart local v10
    move-exception v14
    .line 749
    .local v14, e:Ljava/lang/IllegalAccessException;
    invoke-virtual { v14 }, Ljava/lang/IllegalAccessException;->printStackTrace()V
    goto :L17
    :L20
    .line 755
    .end local v10
    .end local v14
    if-eqz v10, :L18
    .line 756
    invoke-interface { v9, v10 }, Ljava/util/List;->add(Ljava/lang/Object;)Z
    .line 757
    const-string/jumbo v25, "dalvik.system.DexFile"
    const-string/jumbo v26, "mCookie"
    move-object/from16 v0, v25
    move-object/from16 v1, v26
    invoke-static { v5, v0, v10, v1 }, Landroid/app/ActivityThread;->getClassFieldObject(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
    move-result-object v22
    .line 758
    .local v22, mcookie:Ljava/lang/Object;
    if-eqz v22, :L18
    .line 761
    const/4 v6, 0
    .line 763
    .local v6, classnames:[Ljava/lang/String;
    const/16 v25, 1
    :L21
    move/from16 v0, v25
    new-array v0, v0, [Ljava/lang/Object;
    move-object/from16 v25, v0
    const/16 v26, 0
    aput-object v22, v25, v26
    move-object/from16 v0, v19
    move-object/from16 v1, v25
    invoke-virtual { v0, v10, v1 }, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
    move-result-object v6
    .end local v6
    check-cast v6, [Ljava/lang/String;
    :L22
    .line 771
    .local v6, classnames:[Ljava/lang/String;
    if-eqz v6, :L18
    .line 772
    const/16 v25, 0
    array-length v0, v6
    move/from16 v26, v0
    :L23
    move/from16 v0, v25
    move/from16 v1, v26
    if-ge v0, v1, :L18
    aget-object v16, v6, v25
    .line 773
    .local v16, eachclassname:Ljava/lang/String;
    move-object/from16 v0, v16
    invoke-static { v5, v0, v12 }, Landroid/app/ActivityThread;->loadClassAndInvoke(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/reflect/Method;)V
    .line 772
    add-int/lit8 v25, v25, 1
    goto :L23
    :L24
    .line 767
    .end local v6
    .end local v16
    move-exception v15
    .line 768
    .local v15, e:Ljava/lang/reflect/InvocationTargetException;
    invoke-virtual { v15 }, Ljava/lang/reflect/InvocationTargetException;->printStackTrace()V
    goto :L18
    :L25
    .line 764
    .end local v15
    move-exception v14
    .line 765
    .restart local v14
    invoke-virtual { v14 }, Ljava/lang/IllegalAccessException;->printStackTrace()V
    goto :L18
    :L26
    .line 779
    .end local v14
    .end local v17
    .end local v22
    return-void
.end method

.method public static fartthread()V
    .registers 2
    .prologue
    .line 783
    new-instance v0, Ljava/lang/Thread;
    new-instance v1, Landroid/app/ActivityThread$1;
    invoke-direct { v1 }, Landroid/app/ActivityThread$1;-><init>()V
    invoke-direct { v0, v1 }, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
    invoke-virtual { v0 }, Ljava/lang/Thread;->start()V
    .line 782
    return-void
.end method

转换为java代码如下:

    public static void fart() {
        try {
            ClassLoader class_loader = getClassloader();
            Method[] md = class_loader.loadClass("dalvik.system.DexFile").getDeclaredMethods();
            Method getClassNameListMethod = null;
            Method dumpMethodCodeMethod = null;
            int mdCount = md.length;
            for (int i = 0; i < mdCount; i++) {
                if (md[i].getName().equals("getClassNameList")) {
                    getClassNameListMethod = md[i];//获取DexFile类的getClassNameList方法
                    md[i].setAccessible(true);
                } else if (md[i].getName().equals("dumpMethodCode")) {
                    dumpMethodCodeMethod = md[i];//获取DexFile类的dumpMethodCode方法
                    md[i].setAccessible(true);
                }
            }

            Object[] dexElementsObjs = (Object[]) getFieldOjbect("dalvik.system.DexPathList", getFieldOjbect("dalvik.system.BaseDexClassLoader", class_loader, "pathList"), "dexElements");
            Field dexFileField = getClassField(class_loader, "dalvik.system.DexPathList$Element", "dexFile");
            for (int i = 0; i < dexElementsObjs.length; i++) {
                Object dexFileObj = dexFileField.get(dexElementsObjs[i]);
                Object cookObj = getClassFieldObject(class_loader, "dalvik.system.DexFile", dexFileObj, "mCookie");//获取mCookie
                String[] classNames = (String[]) getClassNameListMethod.invoke(dexFileObj, new Object[]{cookObj});//调用DexFile类的getClassNameList获取dex中所有类名
                for (int j = 0; j < classNames.length; j++) {
                    Log.e(TAG, "fart classNames:" + classNames[j]);
                    loadClassAndInvoke(class_loader, classNames[j], dumpMethodCodeMethod);
                }
            }
        } catch (ClassNotFoundException e) {
            Log.e(TAG, "fart ClassNotFoundException" + e.getMessage());
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            Log.e(TAG, "fart IllegalAccessException" + e.getMessage());
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            Log.e(TAG, "fart InvocationTargetException" + e.getMessage());
            e.printStackTrace();
        }

    }

    public static void loadClassAndInvoke(ClassLoader class_loader, String className, Method dumpMethodCodeMethod) {
        try {
            Class class1 = class_loader.loadClass(className);//主动加载dex中的所有类,此时Method数据已解密
            Constructor[] constructors = class1.getDeclaredConstructors();
            for (int i = 0; i < constructors.length; i++) {
                dumpMethodCodeMethod.invoke(null, new Object[]{constructors[i]});//调用DexFile中dumpMethodCode方法,参数为Constructor对象
            }

            Method[] methods = class1.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                dumpMethodCodeMethod.invoke(null, new Object[]{methods[i]});//调用DexFile中dumpMethodCode方法,参数为Method对象
            }
            Log.e(TAG, "className:" + className + ",constructors length:" + constructors.length + ",method length:" + methods.length);
        } catch (ClassNotFoundException e) {
            Log.e(TAG, "fart ClassNotFoundException" + e.getMessage());
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            Log.e(TAG, "fart IllegalAccessException" + e.getMessage());
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            Log.e(TAG, "fart InvocationTargetException" + e.getMessage());
            e.printStackTrace();
        }
        return;
    }

    public static void fartthread() {
        (new Thread(new Runnable() {
            public void run() {
                try {
                    Log.e("ActivityThread", "start sleep......");
                    Thread.sleep(10000L);//睡眠10s钟
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
                Log.e("ActivityThread", "sleep over and start fart");
                ActivityThread.fart();//调用脱壳程序
                Log.e("ActivityThread", "fart run over");
            }
        })).start();
    }

主要是开启了一个线程,睡眠10s后开始干活,fart方法执行流程如下:

1、通过反射获取了DexFile类的getClassNameList方法和dumpMethodCode方法,dumpMethodCode我们刚刚在DexFile里面填加上的native方法。

2、通过当前进程的classloader,一步一步根据如下类结构pathList->dexElements->dexFile->mCookie进一步获取到当前classloader所加载的dexfile的mCookie,这个 mCookie 是native层所加载dex文件结构的标识。

public class BaseDexClassLoader extends ClassLoader {

   private final DexPathList pathList;
   ......
}

package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String zipSeparator = "!/";

    private Element[] dexElements;
    ......
}

 /*package*/ static class Element {
    private final DexFile dexFile;
    ......
}

public final class DexFile {

    private Object mCookie;

    private static native void dumpMethodCode(Object methodid);
    private static native String[] getClassNameList(Object cookie);
    ....
}

那么当前进程的classlodaer如何获取的呢?

public static ClassLoader getClassloader() {
       Object currentActivityThread = invokeStaticMethod("android.app.ActivityThread", "currentActivityThread", new Class[]{}, new Object[]{});
       Object mBoundApplication = getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mBoundApplication");
       return ((Application) getFieldOjbect("android.app.LoadedApk", getFieldOjbect("android.app.ActivityThread$AppBindData", mBoundApplication, "info"), "mApplication")).getClassLoader();
}

通过反射调用 ActivityThread 类的静态函数 currentActivityThread 获取当前的 ActivityThread对象,然后获取 ActivityThread对象的mBoundApplication成员变量,t之后获取mBoundApplication对象的info成员变量,他是个LoadedApk类型;最终获取 info对象的mApplication成员变量,他的类型是Application,最后通过调用 Application.getClassLoader得到当前进程的classloader。理解整个流程请参考下面的类关系:

public final class ActivityThread extends ClientTransactionHandler {

	AppBindData mBoundApplication;
	public static ActivityThread currentActivityThread() {
		return sCurrentActivityThread;
	}
	
	static final class AppBindData {
        LoadedApk info;
		......
	}
	......

}


public final class LoadedApk {
	private Application mApplication;
	......
}	

3、我们已经获取了DexFile类的 getClassNameList方法和dumpMethodCode方法,和 getClassNameList所需要的cookie参数如下:

public final class DexFile {

    private Object mCookie;

    private static native void dumpMethodCode(Object methodid);
    private static native String[] getClassNameList(Object cookie);
    ....
}

调用getClassNameList(mCookie)来获取当前dex中的所有类名。

4、loadClassAndInvoke,首先通过loadClass来主动加载所有类,然后调用dumpMethodCode来进行脱壳,参数为Method或者Constructor对象。

这个有个重点我们的标题是《ART环境下基于主动调用的自动化脱壳方案》,这里主动调用就体现到这里了,loadClass。加壳程序hook了加载类的方法,当真正执行时加载类的时候会进行还原,这个加载类相当于隐式加载。我们这里loadClass是显示加载所有的类,这时候类的方法已经被还原。

其他关于反射所使用的方法,请参考文末github。

那么什么时候调用fartthread呢?

public final class ActivityThread extends ClientTransactionHandler {
 
     /**  Core implementation of activity launch. */
     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
+        Log.e(TAG, "go into performLaunchActivity");
         ActivityInfo aInfo = r.activityInfo;
         if (r.packageInfo == null) {
             r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
@@ -2951,10 +2956,161 @@ public final class ActivityThread extends ClientTransactionHandler {
                     + ": " + e.toString(), e);
             }
         }
-
+        Log.e(TAG, "app name:" + r.packageInfo.getPackageName());
+        if (r.packageInfo.getPackageName().equals("com.example.jltxgcy.arttest")) { //在这里判断进程
+            ActivityThread.fartthread();
+        }
         return activity;
     }
}

原作者是在native层判断的进程,这里采用在ActivityThread的performLaunchActivity判断。

四、分析native层脱壳代码

通过逆向libart.so来看下关键的函数:

int __fastcall art::DexFile_dumpMethodCode(int result, int a2, int a3)
{
  __int64 v3; // kr00_8@1
  int v4; // r0@2
  int v5; // [sp+4h] [bp-14h]@1
  int v6; // [sp+8h] [bp-10h]@1
  int v7; // [sp+Ch] [bp-Ch]@1

  v3 = *(_QWORD *)(result + 4);
  v5 = *(_QWORD *)(result + 4);
  v6 = result;
  v7 = HIDWORD(v3);
  if ( a3 )
  {
    v4 = art::ArtMethod::FromReflectedMethod(&v5, a3);
    result = myfartInvoke(v4);
  }
  return result;
}

// local variable allocation has failed, the output may be wrong!
void __fastcall dumpArtMethod(int a1, const char *a2)
{
  int v2; // r4@0
  int v3; // r5@0 OVERLAPPED
  int v5; // r7@0 OVERLAPPED
  int v7; // r9@0 OVERLAPPED
  int v9; // r11@0
  int v10; // lr@0
  int v11; // r5@1
  const char *v12; // r8@1
  int v13; // r0@2
  void *v14; // r4@4
  int v15; // r0@5
  int v16; // r9@5
  int v17; // r0@10
  int v18; // r3@10
  int v19; // r10@10
  const char *v20; // r2@11
  void *v21; // ST10_4@13
  int v22; // r0@13
  int v23; // r3@13
  int v24; // r0@13
  int v25; // r3@13
  int v26; // ST00_4@13
  int v27; // ST04_4@13
  int v28; // ST08_4@13
  int v29; // ST0C_4@13
  int v30; // ST10_4@13
  int v31; // ST14_4@13
  int v32; // ST18_4@13
  int v33; // r10@13
  int v34; // r0@13
  int v35; // r3@13
  int v36; // ST00_4@13
  int v37; // ST04_4@13
  int v38; // ST08_4@13
  int v39; // ST0C_4@13
  int v40; // ST10_4@13
  int v41; // ST14_4@13
  int v42; // ST18_4@13
  int v43; // r10@14
  size_t v44; // r8@17
  int v50; // r0@17
  int v51; // r3@19
  int v52; // r8@20
  int v53; // r3@21
  int v54; // r9@22
  int v55; // r10@23
  int v56; // r5@23
  __int32 v57; // r0@23
  int v58; // r6@23
  size_t v59; // r2@24
  void *v60; // r12@25
  size_t v61; // r5@25
  int v62; // t1@26
  const void *v63; // r0@27
  int v64; // r0@28
  int v65; // r2@28
  int v66; // r3@28
  int v67; // r10@28
  int v68; // r2@28
  int v69; // r3@28
  char *v70; // r1@29
  int v71; // r0@31
  int v72; // r2@31
  int v73; // r3@31
  int v74; // ST00_4@31
  int v75; // ST04_4@31
  int v76; // ST08_4@31
  int v77; // ST0C_4@31
  int v78; // ST10_4@31
  int v79; // ST14_4@31
  int v80; // ST18_4@31
  int v81; // r0@34
  int v82; // r9@34
  const unsigned __int8 **v83; // r1@36
  int v84; // [sp+0h] [bp-198h]@10
  int v85; // [sp+0h] [bp-198h]@28
  int v86; // [sp+4h] [bp-194h]@0
  int v87; // [sp+4h] [bp-194h]@10
  int v88; // [sp+4h] [bp-194h]@28
  int v89; // [sp+8h] [bp-190h]@0
  int v90; // [sp+8h] [bp-190h]@10
  int v91; // [sp+8h] [bp-190h]@28
  int v92; // [sp+Ch] [bp-18Ch]@0
  int v93; // [sp+Ch] [bp-18Ch]@10
  int v94; // [sp+Ch] [bp-18Ch]@28
  int buf; // [sp+10h] [bp-188h]@0
  const void *bufa; // [sp+10h] [bp-188h]@17
  int bufb; // [sp+10h] [bp-188h]@28
  int v98; // [sp+14h] [bp-184h]@0
  int v99; // [sp+14h] [bp-184h]@10
  char *v100; // [sp+14h] [bp-184h]@15
  int v101; // [sp+14h] [bp-184h]@28
  int v102; // [sp+18h] [bp-180h]@0
  int v103; // [sp+18h] [bp-180h]@10
  int v104; // [sp+18h] [bp-180h]@28
  size_t v105; // [sp+1Ch] [bp-17Ch]@10
  int v106; // [sp+20h] [bp-178h]@10
  int v107; // [sp+24h] [bp-174h]@10
  int v108; // [sp+28h] [bp-170h]@10
  char s; // [sp+2Ch] [bp-16Ch]@5
  unsigned __int8 v110; // [sp+6Ch] [bp-12Ch]@5
  char v111; // [sp+6Dh] [bp-12Bh]@30
  void *v112; // [sp+74h] [bp-124h]@29
  int v113; // [sp+16Ch] [bp-2Ch]@1
  int v114; // [sp+174h] [bp-24h]@1
  __int64 v115; // [sp+178h] [bp-20h]@1
  __int64 v116; // [sp+180h] [bp-18h]@1
  __int64 v117; // [sp+188h] [bp-10h]@1
  int v118; // [sp+190h] [bp-8h]@1
  int v119; // [sp+194h] [bp-4h]@1

  v114 = v2;
  v118 = v9;
  v119 = v10;
  v115 = *(_QWORD *)&v3;
  v11 = a1;
  v116 = *(_QWORD *)&v5;
  v12 = a2;
  v117 = *(_QWORD *)&v7;
  v113 = v0;
  if ( !checkprocess() )
  {
LABEL_2:
    v13 = v0;
    if ( v113 == v0 )
      JUMPOUT(__CS__, v119);
LABEL_38:
    _stack_chk_fail(v13);
  }
  v14 = malloc(0x3E8u);
  if ( !v14 )
  {
    art::LogMessage::LogMessage(&v105, "art/runtime/art_method.cc", 595, 2);
    v64 = art::Atomic<int>::LoadJavaData(&v105);
    v67 = std::__1::operator<<<std::__1::char_traits<char>>(
            v64,
            "ArtMethod::dumpArtMethodinvoked,methodname:",
            v65,
            v66,
            -1,
            v86,
            v89,
            v92,
            buf,
            v98,
            v102,
            v105,
            v106,
            v107,
            v108,
            *(void **)&s);
    art::PrettyMethod((art *)&v110, (art::ArtMethod *)v11, 1);
    if ( v110 << 31 >= 0 )
      v70 = &v111;
    else
      v70 = (char *)v112;
    v71 = std::__1::operator<<<std::__1::char_traits<char>>(
            v67,
            v70,
            v68,
            v69,
            v85,
            v88,
            v91,
            v94,
            bufb,
            v101,
            v104,
            v105,
            v106,
            v107,
            v108,
            *(void **)&s);
    std::__1::operator<<<std::__1::char_traits<char>>(
      v71,
      "malloc 1000 byte failed",
      v72,
      v73,
      v74,
      v75,
      v76,
      v77,
      v78,
      v79,
      v80,
      v105,
      v106,
      v107,
      v108,
      *(void **)&s);
    if ( v110 & 1 )
      operator delete(v112);
    art::LogMessage::~LogMessage((art::LogMessage *)&v105);
    goto LABEL_2;
  }
  memset(&s, 0, 0x40u);
  memset(&v110, 0, 0x100u);
  getpid();
  _sprintf_chk(&s, 0, 64, "/proc/%d/cmdline");
  v15 = open(&s, 0, 420);
  v16 = v15;
  if ( v15 > 0 )
  {
    read(v15, &v110, 0x100u);
    close(v16);
  }
  v13 = v110;
  if ( v110 )
  {
    art::LogMessage::LogMessage(&v105, "art/runtime/art_method.cc", 613, 2);
    v17 = art::Atomic<int>::LoadJavaData(&v105);
    v19 = std::__1::__put_character_sequence<char,std::__1::char_traits<char>>(
            v17,
            "ArtMethod::dumpArtMethodinvoked,methodname:",
            43,
            v18,
            -1,
            v86,
            v89,
            v92,
            buf,
            v98,
            v102,
            v105,
            v106,
            v107,
            v108,
            *(void **)&s);
    art::PrettyMethod((art *)&v106, (art::ArtMethod *)v11, 1);
    if ( (unsigned __int8)v106 << 31 >= 0 )
      v20 = (char *)&v106 + 1;
    else
      v20 = (const char *)v108;
    v21 = (void *)v20;
    v22 = strlen(v20);
    v24 = std::__1::__put_character_sequence<char,std::__1::char_traits<char>>(
            v19,
            v21,
            v22,
            v23,
            v84,
            v87,
            v90,
            v93,
            (int)v21,
            v99,
            v103,
            v105,
            v106,
            v107,
            v108,
            *(void **)&s);
    v33 = std::__1::__put_character_sequence<char,std::__1::char_traits<char>>(
            v24,
            "from:",
            5,
            v25,
            v26,
            v27,
            v28,
            v29,
            v30,
            v31,
            v32,
            v105,
            v106,
            v107,
            v108,
            *(void **)&s);
    v34 = strlen(v12);
    std::__1::__put_character_sequence<char,std::__1::char_traits<char>>(
      v33,
      (void *)v12,
      v34,
      v35,
      v36,
      v37,
      v38,
      v39,
      v40,
      v41,
      v42,
      v105,
      v106,
      v107,
      v108,
      *(void **)&s);
    if ( v106 & 1 )
      operator delete((void *)v108);
    art::LogMessage::~LogMessage((art::LogMessage *)&v105);
    v43 = *(_DWORD *)(*(_DWORD *)(*(_DWORD *)v11 + 16) + 32);
    art::PrettyMethod((art *)&v106, (art::ArtMethod *)v11, 1);
    if ( v106 & 1 )
    {
      v100 = (char *)v108;
      operator delete((void *)v108);
    }
    else
    {
      v100 = (char *)&v106 + 1;
    }
    v44 = *(_DWORD *)(v43 + 8);
    bufa = *(const void **)(v43 + 4);
    memset(v14, 0, 0x3E8u);
    memset(v14, 0, 0x3E8u);
    _R12 = (int)"/sdcard/fart";
    __asm { VLDR            D16, [R12] }
    *((_DWORD *)v14 + 2) = 1953653094;
    *((_BYTE *)v14 + 12) = aSdcardFart[12];
    __asm { VSTR            D16, [R4] }
    mkdir((const char *)v14, 0x1FFu);
    memset(v14, 0, 0x3E8u);
    _sprintf_chk(v14, 0, 1000, "/sdcard/fart/%s", &v110);
    mkdir((const char *)v14, 0x1FFu);
    memset(v14, 0, 0x3E8u);
    _sprintf_chk(v14, 0, 1000, "/sdcard/fart/%s/%d_dexfile.dex", &v110, v44);
    v50 = open((const char *)v14, 0, 438);
    if ( v50 <= 0 )
    {
      v81 = open((const char *)v14, 1090, 438);
      v82 = v81;
      if ( v81 <= 0 )
      {
LABEL_19:
        v13 = *(_DWORD *)(*(_DWORD *)v11 + 16);
        v51 = *(_DWORD *)(v11 + 16);
        if ( v51 )
        {
          v52 = *(_DWORD *)(*(_DWORD *)(v13 + 32) + 4) + v51;
          if ( v52 )
          {
            v53 = *(_DWORD *)(v52 + 12);
            if ( *(_WORD *)(v52 + 6) )
            {
              v83 = (const unsigned __int8 **)(v52 + 2 * v53 + 19);
              v105 = ((unsigned int)v83 & 0xFFFFFFFC) + 8 * *(_WORD *)(v52 + 6);
              v54 = art::codeitem_end((art *)&v105, v83) - v52;
            }
            else
            {
              v54 = 2 * (v53 + 8);
            }
            memset(v14, 0, 0x3E8u);
            v55 = *(_DWORD *)(v43 + 8);
            v56 = *(_DWORD *)(v11 + 20);
            v57 = syscall(224);
            _sprintf_chk(v14, 0, 1000, "/sdcard/fart/%s/%d_ins_%d.bin", &v110, v55, v57);
            v13 = open((const char *)v14, 1090, 438);
            v58 = v13;
            if ( v13 > 0 )
            {
              lseek(v13, 0, 2);
              memset(v14, 0, 0x3E8u);
              _sprintf_chk(
                v14,
                0,
                1000,
                "{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",
                v100,
                v56,
                v52 - (_DWORD)bufa,
                v54);
              v59 = *(_BYTE *)v14;
              if ( *(_BYTE *)v14 )
              {
                v60 = v14;
                v61 = 0;
                do
                {
                  v62 = *((_BYTE *)v60 + 1);
                  v60 = (char *)v60 + 1;
                  ++v61;
                  v59 = v61;
                }
                while ( v62 );
              }
              write(v58, v14, v59);
              v105 = 0;
              v63 = (const void *)base64_encode(v52, v54, &v105);
              write(v58, v63, v105);
              write(v58, "};", 2u);
              fsync(v58);
              v13 = close(v58);
            }
          }
        }
        goto LABEL_8;
      }
      write(v81, bufa, v44);
      fsync(v82);
      v50 = v82;
    }
    close(v50);
    goto LABEL_19;
  }
LABEL_8:
  if ( v113 != v0 )
    goto LABEL_38;
  j_j_free(v14);
}

这部分代码可以简单看下,首先将Java层的Method对象通过 ArtMethod::FromReflectedMethod 转换为ArtMethod;之后检查了是否为目标进程,最后把ArtMethod里面的CodeItem.insns_ dump出来。下面的代码是自己实现的,逆向的代码已经面目全非了,并且不一定适配android9.0。

runtime/native/dalvik_system_DexFile.cc

在这里实现DexFile类的函数private static native void dumpMethodCode(Object methodid);

+static void DexFile_dumpMethodCode(JNIEnv* env, jclass, jobject j_method) {
+  ScopedFastNativeObjectAccess soa(env);
+  ArtMethod* method = ArtMethod::FromReflectedMethod(soa, j_method); //转换为ArtMethod
+  LOG(ERROR) << "fartlog, method:" << method->GetName();
+  method->DumpArtMethod();//调用ArtMethod里面的DumpArtMethod
+}
+
 static void DexFile_setTrusted(JNIEnv* env, jclass, jobject j_cookie) {
   Runtime* runtime = Runtime::Current();
   ScopedObjectAccess soa(env);
@@ -877,7 +885,8 @@ static JNINativeMethod gMethods[] = {
   NATIVE_METHOD(DexFile, getStaticSizeOfDexFile, "(Ljava/lang/Object;)J"),
   NATIVE_METHOD(DexFile, getDexFileOptimizationStatus,
                 "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
+  NATIVE_METHOD(DexFile, dumpMethodCode, "(Ljava/lang/Object;)V")
 };

runtime/art_method.h

+  void DumpArtMethod() REQUIRES_SHARED(Locks::mutator_lock_);//声明函数

runtime/art_method-inl.h

+inline void ArtMethod::DumpArtMethod() { //函数实现
+  const DexFile* dex_file = GetDexFile();
+  const DexFile::CodeItem* code_item= GetCodeItem();
+  if (dex_file != NULL && code_item != NULL && !dex_file->IsCompactDexFile())
+  {
+    const StandardDexFile::CodeItem& standardCodeItem = down_cast<const StandardDexFile::CodeItem&>(*code_item);//需要转为子类对象
+    LOG(ERROR) << "fartlog, DumpArtMethod code_item length:" << standardCodeItem.insns_size_in_code_units_;
+    for (int i = 0 ; i <  (int)standardCodeItem.insns_size_in_code_units_; i++)
+    {
+        LOG(ERROR) << "fartlog, DumpArtMethod code_item content:" << standardCodeItem.insns_[i];
+    }
+    
+  }
+}
+

libdexfile/dex/standard_dex_file.h

-   private:
+   public://CodeItem改为public,不然不能调用
     CodeItem() = default;

可以看到我们这里只是打印了些日志。

五、实验

实验使用的apk和加密后的apk以及apk的源码都已经上传到github。

看dex类中invokeStaticMethod方法的dump结果如下:

01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, method:invokeStaticMethod
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item length:35
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:8210
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:113
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:16564
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:0
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:10
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:16724
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:7693
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:546
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:2125
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:4208
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:16765
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:2
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:794
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:11456
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:8302
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:16775
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:50
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:524
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:8302
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:16771
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:2
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:524
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:4206
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:16780
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:2
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:524
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:786
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:12401
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:16529
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:801
01-05 12:25:27.037  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:268
01-05 12:25:27.038  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:4206
01-05 12:25:27.038  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:16531
01-05 12:25:27.038  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:1
01-05 12:25:27.038  2528  3589 E jltxgcy.arttes: fartlog, DumpArtMethod code_item content:14

对比原dex,使用010editor打开后:




只有个别字节码有差异,猜测是index不同导致。

六、改进点:

1、 本文采用apk为demo形态,并非复杂apk,如果脱复杂apk遇到问题,感兴趣的可以继续本文的思路继续研究。

2、本文只是通过log打印了 ArtMethod的CodeItem内容,  并没有把ArtMethod的CodeItem内容dump到文件里面,也没有dump整个dex文件,然后通过fart.py来修复。这部分工作感觉的读者可以接着实现。

七、源码地址:

八、参考

1、《[原创]FART:ART环境下基于主动调用的自动化脱壳方案》

[2020元旦礼物]《看雪论坛精华17》发布!(补齐之前所有遗漏版本)!

最后于 17小时前 被jltxgcy编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-257101.htm
如有侵权请联系:admin#unsafe.sh