首页
论坛
专栏
课程

[原创] FART:ART环境下基于主动调用的自动化脱壳方案 [android脱壳源码公开,基于android-9.0.0_r36]

2020-1-7 18:16 3438

[原创] FART:ART环境下基于主动调用的自动化脱壳方案 [android脱壳源码公开,基于android-9.0.0_r36]

2020-1-7 18:16
3438
一、背景
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安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!

最后于 2020-1-7 18:28 被jltxgcy编辑 ,原因:
最新回复 (26)
xJJuno 2020-1-7 18:23
2
0
前排支持 地址没了?....
jltxgcy 2 2020-1-7 18:26
3
0
xJJuno 前排支持 地址没了?....
正在上传。
上海刘一刀 2 2020-1-7 18:30
4
0
前排
hanbingle 2 2020-1-7 18:45
5
0
看来你等不及了,本来打算新年大年30就会全部上传到github的,给大家作为新年礼物的。还有文中有部分讲的还不够细致,先是用loadclass加载类,虽然一部分的函数抽取的壳的实现在类加载后就已经恢复,但是事实上还有一些壳是在函数调用前甚至是在函数本身执行后才恢复,这里是主动调用的关键所在
jltxgcy 2 2020-1-7 19:08
6
0
hanbingle 看来你等不及了[em_13],本来打算新年大年30就会全部上传到github的,给大家作为新年礼物的。还有文中有部分讲的还不够细致,先是用loadclass加载类,虽然一部分的函数抽取的壳的实现在类加 ...
期待大佬的权威源码。
gtict 2020-1-7 19:40
7
0
jltxgcy 期待大佬的权威源码。[em_13]
等你的权威源码了
Fireeye 2020-1-7 21:54
8
0
谢谢大佬分享
ChenSem 1 2020-1-8 09:33
9
0
hanbingle 看来你等不及了[em_13],本来打算新年大年30就会全部上传到github的,给大家作为新年礼物的。还有文中有部分讲的还不够细致,先是用loadclass加载类,虽然一部分的函数抽取的壳的实现在类加 ...
前排观望
tDasm 2020-1-8 09:49
10
0
建议同时提供这三个文件下载:framework.jar、core-libart.jar、libart.so
免得每个人都去编译一下
最后于 2020-1-8 09:50 被tDasm编辑 ,原因:
Elice 2020-1-8 10:05
11
0
大佬牛逼
呼吸24K纯氧 2020-1-8 10:06
12
0
谢谢大佬分享
Mar.明 2020-1-8 10:31
13
0
大佬牛逼,正好最近也在看这方便的东西,学习了
灬哈密瓜 2020-1-8 10:41
14
0
大佬也是66666
caolinkai 2020-1-8 10:46
15
0
灬哈密瓜 大佬也是66666
66666666666
SANCDAYE 2020-1-8 11:14
16
0
支持分享
addhaloka 2020-1-8 14:51
17
0
感谢分享
zhenyyh 2020-1-8 21:30
18
0
日常膜拜
又见飞刀z 2020-1-9 09:53
19
0
这个CodeItem()改为public,会不会让人可以据此检测到正在运行在脱壳系统中?
又见飞刀z 2020-1-9 09:54
20
0
只能移植到art下?dvm不能实现吗?
hanbingle 2 2020-1-9 12:49
21
0
又见飞刀z 只能移植到art下?dvm不能实现吗?
FART是一个框架,虽然也支持dalvik,但是已经没有意义了
给大佬递茶 2020-1-9 14:53
22
0
hanbingle FART是一个框架,虽然也支持dalvik,但是已经没有意义了
呀呀呀,原作大佬来了
Papaya. 2020-1-9 17:19
23
0
膜拜大佬
Editor 2020-1-10 10:05
24
0
大佬!感谢分享!
Qira 2020-1-10 15:03
25
0
还是老哥的执行力高,之前我也想着这么搞,结果现在就开了个头,膜拜老哥
jltxgcy 2 2020-1-10 15:15
26
0
又见飞刀z 这个CodeItem()改为public,会不会让人可以据此检测到正在运行在脱壳系统中?
如果公开代码了,有可能被检测到。
bunnyrene 2020-1-13 13:54
27
0
有编译好的镜像没 电脑硬盘不够
游客
登录 | 注册 方可回帖
返回