一、背景
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里面核心的脱壳代码:
转换为java代码如下: 主要是开启了一个线程,睡眠10s后开始干活,fart方法执行流程如下: 1、通过反射获取了DexFile类的getClassNameList方法和dumpMethodCode方法,dumpMethodCode我们刚刚在DexFile里面填加上的native方法。 2、通过当前进程的classloader,一步一步根据如下类结构pathList->dexElements->dexFile->mCookie进一步获取到当前classloader所加载的dexfile的mCookie,这个
mCookie 是native层所加载dex文件结构的标识。 那么当前进程的classlodaer如何获取的呢? 通过反射调用
ActivityThread 类的静态函数
currentActivityThread
获取当前的
ActivityThread对象,然后获取
ActivityThread对象的mBoundApplication成员变量,t之后获取mBoundApplication对象的info成员变量,他是个LoadedApk类型;最终获取
info对象的mApplication成员变量,他的类型是Application,最后通过调用
Application.getClassLoader得到当前进程的classloader。理解整个流程请参考下面的类关系:.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
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();
}
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);
....
}
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();
}
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参数如下:
调用getClassNameList(mCookie)来获取当前dex中的所有类名。 4、loadClassAndInvoke,首先通过loadClass来主动加载所有类,然后调用dumpMethodCode来进行脱壳,参数为Method或者Constructor对象。 这个有个重点我们的标题是《ART环境下基于主动调用的自动化脱壳方案》,这里主动调用就体现到这里了,loadClass。加壳程序hook了加载类的方法,当真正执行时加载类的时候会进行还原,这个加载类相当于隐式加载。我们这里loadClass是显示加载所有的类,这时候类的方法已经被还原。 其他关于反射所使用的方法,请参考文末github。 那么什么时候调用fartthread呢? 原作者是在native层判断的进程,这里采用在ActivityThread的performLaunchActivity判断。public final class DexFile {
private Object mCookie;
private static native void dumpMethodCode(Object methodid);
private static native String[] getClassNameList(Object cookie);
....
}
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层脱壳代码
通过逆向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编辑 ,原因: