首页
社区
课程
招聘
fart的理解和分析过程
发表于: 2020-11-12 22:33 22142

fart的理解和分析过程

2020-11-12 22:33
22142

文章内的fart的源码素材均来自看雪搞研网课。安卓源码为8.1

学习了一段时间fart然后整理一下以便哪天忘记了翻翻

fart是一个art环境下基于主动调用的自动化脱壳方案。在定制的fart环境中,只需要运行需要脱壳的apk即可自动脱壳,将脱壳后的dex文件dump在/sdcard/fart/app包名的目录下,那么这个流程是如何实现的呢。

内部的一些原理作者有发过帖子详细进行过讲解,所以这里先是跟着作者的思想进行一步步的深入。先贴上地址

1、[FART:ART环境下基于主动调用的自动化脱壳方案][https://bbs.pediy.com/thread-252630.htm]

2、[FART正餐前甜点:ART下几个通用简单高效的dump内存中dex方法][https://bbs.pediy.com/thread-254028.htm]

3、[拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点][https://bbs.pediy.com/thread-254555.htm]

阅读几篇文章后,我简单整理一下收获

ActivityThread.main()是一个app开始的入口,下面先贴下8.1系统的main的代码

这里看到主要是创建了一个ActivityThread然后调用了attach,然后就是进入了主线程的事件循环,在attach的里面将这个ActivityThread保存在了一个全局的静态变量中。attach代码比较多,我就只截一点点了。

然后再看看主线程的事件循环做了什么

然后看到消息交给dispatchMessage这个函数进行分发处理继续看看这个函数

到这里。就进入了handleMessage处理消息分发,这里注意。这个地方的handle使用的是ActivityThread内部的H类,看ActivityThread的以下代码

所以这里看H类的handleMessage函数,代码太多,这里只贴文章里面所说的bindapplication的处理

然后继续看handleBindApplication,我们就可以看到大神所说的重要的点了

这里的callApplicationOnCreate就是app的onCreate调用了。再看看前面的makeApplication的代码

这里又调用了newApplication,继续看里面

继续再看app的attach

到这里就知道了大家都喜欢的attachBaseContext和onCreate分别是在什么时机调用的了

然后就看下面的图

这里的意思就是加壳的app会在attachBaseContext函数和onCreate函数里面对dex进行解密,解密后再用反射修复里面的变量。最后修复成正常的dex。其中最重要的就是Classloader。所有的应用中加载的dex文件最终都在应用的Classloader中。

所以我们只要取到加壳的应用最后修复完,正常加载时的Classloader就脱壳成功了。这种脱壳方式就是所谓的整体dump。

后来的第二代壳所谓的函数抽取,就是为了防止修复后的dex被直接整体dump给脱下来,然后将类和函数的关键流程在调用时,才进行解密修复。而fart的主动调用所有函数,就会触发解密修复,然后再进行dump,从而实现函数抽取壳的脱壳。

fart脱壳的原理下面贴上作者大佬的原话。

一代壳的脱法,就是选择一个已经完成解密加载的时机,然后把完整的dex给dump下来。由于加壳是通过替换attachBaseContext和onCreate函数,在这两个函数里面解密dex。所以时机选在onCreate后的任意函数都可以。作者大佬选择的是performLaunchActivity作为脱壳的时机。下面贴上关键部分代码

这里开了一个线程去处理,接着继续看fart做了些啥

这里相当于是从当前classloader一直向上层的遍历,把所有classloader挨个脱一遍。先看看他是怎么获取classloader的

由于我不太了解为什么是获取android.app.LoadedApk里面的mApplication。所以特地的去翻了一下。这个值的来源。

这样子看,在这个时机的脱壳必然是能取到那个mApplication的。大概就看出这么点原因。

==========================================================================================

里面还有很多地方不太理解。所以这里先从fart中跳出来,先看看android源码中对于获取一个ClassLoader的追溯,看看里面是怎么实现获取一个ClassLoader的。先看看java代码我们要如何获取一个ClassLoader

这里可以看到直接通过loadClass取到的classLoader,那么这个函数是怎么做到的呢,下面贴下代码追踪ClassLoader.java中的loadClass

调用了另外一个重载,继续贴第二个重载的代码

这里的意思大概就是,如果曾经获取过,findLoadedClass就直接可以获取到并返回。否则就继续通过findClass查找。

这个是个默认,好像这个是提供给子类重写的。然后我们再看看父类BaseDexClassLoader也有一个findClass

这里可以看到最后返回的Class c又是由pathList.findClass获取的。继续看DexPathList类的findClass,后面fart里面有参考这里的代码。在fart中同样是获取dexElements,然后遍历,然后取出dexFile。然后再进行各种操作。

然后继续看返回值的来源是通过loadClassBinaryName来获取的。继续看看实现的代码

接着继续看defineClass

结果又是从defineClassNative来的。而这个函数就是一个native的函数。再翻就得去c++里面找代码了。下面再回到fart的脱壳函数fartwithClassloader,就会发现不少眼熟的地方了

重新回到fart继续看看fartwithClassloader这个函数如何实现脱classloader的

继续看看fart怎么做到的调用每个类中的所有函数

接下来就看看我们提前准备的dumpMethod是如何dump函数的了。找到DexFile里面的native实现的代码

这里先是把java传递过来的函数指针转换成了ArtMethod了。然后myfartInvoke来处理每个函数。继续看看里面怎么做的

看到是调用了artmethod的Invoke。然后最后一个参数来告知里面,这是一个fart主动调用的函数。继续看看里面处理。代码有点多,下,面代码只放上和fart相关的处理

这里看到。其实他虽然传了个fart字符串进来想作为判断的。但是实际上没有使用。最后还是根据第一个参数self是否为nullptr来判断用不用dump这个函数的。继续看dumpArtMethod的处理

再贴一个codeitem_end是怎么计算包含try的函数大小

到这里fart的主动调用脱壳流程基本完成。当然还有一些辅助frida的处理函数,下面贴一些大神为了方便我们使用frida来处理而包装的函数

最后贴一下简单的流程图。

如果写的有啥问题。希望大佬们能指正。

 
 
 
 
 
 
 
 
public static void main(String[] args) {
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(new ActivityThread.EventLoggingReporter());
        Security.addProvider(new AndroidKeyStoreProvider());
        File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        Process.setArgV0("<pre-initialized>");
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
public static void main(String[] args) {
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(new ActivityThread.EventLoggingReporter());
        Security.addProvider(new AndroidKeyStoreProvider());
        File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        Process.setArgV0("<pre-initialized>");
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
private static ActivityThread sCurrentActivityThread;
public static ActivityThread currentActivityThread() {
        return sCurrentActivityThread;
}
private void attach(boolean system) {
    sCurrentActivityThread = this;
}
private static ActivityThread sCurrentActivityThread;
public static ActivityThread currentActivityThread() {
        return sCurrentActivityThread;
}
private void attach(boolean system) {
    sCurrentActivityThread = this;
}
public static void loop() {
        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        } else {
            MessageQueue queue = me.mQueue;
            Binder.clearCallingIdentity();
            long ident = Binder.clearCallingIdentity();
 
            while(true) {
                Message msg = queue.next();
                if (msg == null) {
                    return;
                }
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
                }
                msg.target.dispatchMessage(msg);
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
 
                long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf("Looper", "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what);
                }
 
                msg.recycleUnchecked();
            }
        }
    }
public static void loop() {
        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        } else {
            MessageQueue queue = me.mQueue;
            Binder.clearCallingIdentity();
            long ident = Binder.clearCallingIdentity();
 
            while(true) {
                Message msg = queue.next();
                if (msg == null) {
                    return;
                }
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
                }
                msg.target.dispatchMessage(msg);
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
 
                long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf("Looper", "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what);
                }
 
                msg.recycleUnchecked();
            }
        }
    }
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (this.mCallback != null && this.mCallback.handleMessage(msg)) {
                return;
            }
            this.handleMessage(msg);
        }
 
    }
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (this.mCallback != null && this.mCallback.handleMessage(msg)) {
                return;
            }
            this.handleMessage(msg);
        }
 
    }
final ActivityThread.H mH = new ActivityThread.H();
final Handler getHandler() {
        return this.mH;
    }
final ActivityThread.H mH = new ActivityThread.H();
final Handler getHandler() {
        return this.mH;
    }
case 110:
    Trace.traceBegin(64L, "bindApplication");
    ActivityThread.AppBindData data = (ActivityThread.AppBindData)msg.obj;
    ActivityThread.this.handleBindApplication(data);
    Trace.traceEnd(64L);
    break;
case 110:
    Trace.traceBegin(64L, "bindApplication");
    ActivityThread.AppBindData data = (ActivityThread.AppBindData)msg.obj;
    ActivityThread.this.handleBindApplication(data);
    Trace.traceEnd(64L);
    break;
Application app = data.info.makeApplication(data.restrictedBackupMode, (Instrumentation)null);
this.mInitialApplication = app;
if (!data.restrictedBackupMode) {
  List<ProviderInfo> providers = data.providers;
  if (providers != null) {
    this.installContentProviders(app, providers);
    this.mH.sendEmptyMessageDelayed(132, 10000L);
  }
}
 
try {
  this.mInstrumentation.onCreate(data.instrumentationArgs);
} catch (Exception var20) {
  throw new RuntimeException("Exception thrown in onCreate() of " + data.instrumentationName + ": " + var20.toString(), var20);
}
 
try {
  this.mInstrumentation.callApplicationOnCreate(app);
} catch (Exception var26) {
  if (!this.mInstrumentation.onException(app, var26)) {
    throw new RuntimeException("Unable to create application " + app.getClass().getName() + ": " + var26.toString(), var26);
  }
}
Application app = data.info.makeApplication(data.restrictedBackupMode, (Instrumentation)null);
this.mInitialApplication = app;
if (!data.restrictedBackupMode) {
  List<ProviderInfo> providers = data.providers;
  if (providers != null) {
    this.installContentProviders(app, providers);
    this.mH.sendEmptyMessageDelayed(132, 10000L);
  }
}
 
try {
  this.mInstrumentation.onCreate(data.instrumentationArgs);
} catch (Exception var20) {
  throw new RuntimeException("Exception thrown in onCreate() of " + data.instrumentationName + ": " + var20.toString(), var20);
}
 
try {
  this.mInstrumentation.callApplicationOnCreate(app);
} catch (Exception var26) {
  if (!this.mInstrumentation.onException(app, var26)) {
    throw new RuntimeException("Unable to create application " + app.getClass().getName() + ": " + var26.toString(), var26);
  }
}
ContextImpl appContext = ContextImpl.createAppContext(this.mActivityThread, this);
app = this.mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
appContext.setOuterContext(app);
ContextImpl appContext = ContextImpl.createAppContext(this.mActivityThread, this);
app = this.mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
appContext.setOuterContext(app);
public static Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
}
public static Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
}
final void attach(Context context) {
    this.attachBaseContext(context);
    this.mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
final void attach(Context context) {
    this.attachBaseContext(context);
    this.mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
 
 
 
 
 
FART脱壳的步骤主要分为三步:
1.内存中DexFile结构体完整dex的dump
2.主动调用类中的每一个方法,并实现对应CodeItem的dump
3.通过主动调用dump下来的方法的CodeItem进行dex中被抽取的方法的修复
FART脱壳的步骤主要分为三步:
1.内存中DexFile结构体完整dex的dump
2.主动调用类中的每一个方法,并实现对应CodeItem的dump
3.通过主动调用dump下来的方法的CodeItem进行dex中被抽取的方法的修复
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    fartthread();
}
public static void fartthread() {
  new Thread(new Runnable() {
    @Override
    public void run() {
      // TODO Auto-generated method stub
      try {
        Log.e("ActivityThread", "start sleep......");
        Thread.sleep(1 * 60 * 1000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      Log.e("ActivityThread", "sleep over and start fart");
      fart();
      Log.e("ActivityThread", "fart run over");
    }
  }).start();
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    fartthread();
}
public static void fartthread() {
  new Thread(new Runnable() {
    @Override
    public void run() {
      // TODO Auto-generated method stub
      try {
        Log.e("ActivityThread", "start sleep......");
        Thread.sleep(1 * 60 * 1000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      Log.e("ActivityThread", "sleep over and start fart");
      fart();
      Log.e("ActivityThread", "fart run over");
    }
  }).start();
}
public static void fart() {
  ClassLoader appClassloader = getClassloader();
  ClassLoader tmpClassloader=appClassloader;
  ClassLoader parentClassloader=appClassloader.getParent();
  //如果当前classloader不是根,就脱他
  if(appClassloader.toString().indexOf("java.lang.BootClassLoader")==-1)
  {
    fartwithClassloader(appClassloader);
  }
  //如果有父节点,并且父节点不是根,就脱他。一直脱到根节点就结束
  while(parentClassloader!=null){
    if(parentClassloader.toString().indexOf("java.lang.BootClassLoader")==-1)
    {
      fartwithClassloader(parentClassloader);
    }
    tmpClassloader=parentClassloader;
    parentClassloader=parentClassloader.getParent();
  }
}
public static void fart() {
  ClassLoader appClassloader = getClassloader();
  ClassLoader tmpClassloader=appClassloader;
  ClassLoader parentClassloader=appClassloader.getParent();
  //如果当前classloader不是根,就脱他
  if(appClassloader.toString().indexOf("java.lang.BootClassLoader")==-1)
  {
    fartwithClassloader(appClassloader);
  }
  //如果有父节点,并且父节点不是根,就脱他。一直脱到根节点就结束
  while(parentClassloader!=null){
    if(parentClassloader.toString().indexOf("java.lang.BootClassLoader")==-1)
    {
      fartwithClassloader(parentClassloader);
    }
    tmpClassloader=parentClassloader;
    parentClassloader=parentClassloader.getParent();
  }
}
public static ClassLoader getClassloader() {
  ClassLoader resultClassloader = null;
  //这里反射获取到的currentActivityThread
  Object currentActivityThread = invokeStaticMethod(
    "android.app.ActivityThread", "currentActivityThread",
    new Class[]{}, new Object[]{});
  //这里反射获取出mBoundApplication
  Object mBoundApplication = getFieldOjbect(
    "android.app.ActivityThread", currentActivityThread,
    "mBoundApplication");
  Application mInitialApplication = (Application) getFieldOjbect("android.app.ActivityThread",
                                                                 currentActivityThread, "mInitialApplication");
  Object loadedApkInfo = getFieldOjbect(
    "android.app.ActivityThread$AppBindData",
    mBoundApplication, "info");
  //反射获取出目标application
  Application mApplication = (Application) getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplication");
  //获取classloader
  resultClassloader = mApplication.getClassLoader();
  return resultClassloader;
}
public static ClassLoader getClassloader() {
  ClassLoader resultClassloader = null;
  //这里反射获取到的currentActivityThread
  Object currentActivityThread = invokeStaticMethod(
    "android.app.ActivityThread", "currentActivityThread",
    new Class[]{}, new Object[]{});
  //这里反射获取出mBoundApplication
  Object mBoundApplication = getFieldOjbect(
    "android.app.ActivityThread", currentActivityThread,
    "mBoundApplication");
  Application mInitialApplication = (Application) getFieldOjbect("android.app.ActivityThread",
                                                                 currentActivityThread, "mInitialApplication");
  Object loadedApkInfo = getFieldOjbect(
    "android.app.ActivityThread$AppBindData",
    mBoundApplication, "info");
  //反射获取出目标application
  Application mApplication = (Application) getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplication");
  //获取classloader
  resultClassloader = mApplication.getClassLoader();
  return resultClassloader;
}
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
  if (this.mApplication != null) {
    return this.mApplication;
  } else {
    Application app = null;
    //...省略中间app创建的若干代码
    this.mActivityThread.mAllApplications.add(app);
    this.mApplication = app;
    //...省略中间若干代码
    return app;
  }
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    //....
  //省略中间的代码
  //add
  fartthread();
  //add
  return activity;
}
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
  if (this.mApplication != null) {
    return this.mApplication;
  } else {
    Application app = null;
    //...省略中间app创建的若干代码
    this.mActivityThread.mAllApplications.add(app);
    this.mApplication = app;
    //...省略中间若干代码
    return app;
  }
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    //....
  //省略中间的代码
  //add
  fartthread();
  //add
  return activity;
}
 
 
ClassLoader cl = ClassLoader.getSystemClassLoader();
cl.loadClass("com.example.demo");
ClassLoader cl = ClassLoader.getSystemClassLoader();
cl.loadClass("com.example.demo");
public Class<?> loadClass(String name) throws ClassNotFoundException {
  return loadClass(name, false);
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
  return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  // First, check if the class has already been loaded
  Class<?> c = findLoadedClass(name);
  if (c == null) {
    try {
      if (parent != null) {
        c = parent.loadClass(name, false);
      } else {
        c = findBootstrapClassOrNull(name);
      }
    } catch (ClassNotFoundException e) {
      // ClassNotFoundException thrown if class not found
      // from the non-null parent class loader
    }
 
    if (c == null) {
      // If still not found, then invoke findClass in order
      // to find the class.
      c = findClass(name);
    }
  }
  return c;
}
protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  // First, check if the class has already been loaded
  Class<?> c = findLoadedClass(name);
  if (c == null) {
    try {
      if (parent != null) {
        c = parent.loadClass(name, false);
      } else {
        c = findBootstrapClassOrNull(name);
      }
    } catch (ClassNotFoundException e) {
      // ClassNotFoundException thrown if class not found
      // from the non-null parent class loader
    }
 
    if (c == null) {
      // If still not found, then invoke findClass in order
      // to find the class.
      c = findClass(name);
    }
  }
  return c;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
  List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
  Class c = pathList.findClass(name, suppressedExceptions);
  if (c == null) {
  ClassNotFoundException cnfe = new ClassNotFoundException(
  "Didn't find class \"" + name + "\" on path: " + pathList);
  for (Throwable t : suppressedExceptions) {
  cnfe.addSuppressed(t);
  }
  throw cnfe;
  }
  return c;
}
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
  List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
  Class c = pathList.findClass(name, suppressedExceptions);
  if (c == null) {
  ClassNotFoundException cnfe = new ClassNotFoundException(
  "Didn't find class \"" + name + "\" on path: " + pathList);
  for (Throwable t : suppressedExceptions) {
  cnfe.addSuppressed(t);
  }
  throw cnfe;
  }
  return c;
}
public Class findClass(String name, List<Throwable> suppressed) {
  DexPathList.Element[] arr$ = this.dexElements;
  int len$ = arr$.length;
 
  for(int i$ = 0; i$ < len$; ++i$) {
    DexPathList.Element element = arr$[i$];
    DexFile dex = element.dexFile;
    if (dex != null) {
      Class clazz = dex.loadClassBinaryName(name, this.definingContext, suppressed);
      if (clazz != null) {
        return clazz;
      }
    }
  }
 
  if (this.dexElementsSuppressedExceptions != null) {
    suppressed.addAll(Arrays.asList(this.dexElementsSuppressedExceptions));
  }
 
  return null;
}
public Class findClass(String name, List<Throwable> suppressed) {
  DexPathList.Element[] arr$ = this.dexElements;
  int len$ = arr$.length;
 
  for(int i$ = 0; i$ < len$; ++i$) {
    DexPathList.Element element = arr$[i$];
    DexFile dex = element.dexFile;
    if (dex != null) {
      Class clazz = dex.loadClassBinaryName(name, this.definingContext, suppressed);
      if (clazz != null) {
        return clazz;
      }
    }
  }
 
  if (this.dexElementsSuppressedExceptions != null) {
    suppressed.addAll(Arrays.asList(this.dexElementsSuppressedExceptions));
  }
 
  return null;
}
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
  return defineClass(name, loader, this.mCookie, suppressed);
}
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
  return defineClass(name, loader, this.mCookie, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, long cookie, List<Throwable> suppressed) {
  Class result = null;
 
  try {
    result = defineClassNative(name, loader, cookie);
  } catch (NoClassDefFoundError var7) {
    if (suppressed != null) {
      suppressed.add(var7);
    }
  } catch (ClassNotFoundException var8) {
    if (suppressed != null) {
      suppressed.add(var8);
    }
  }
 
  return result;
}
private static Class defineClass(String name, ClassLoader loader, long cookie, List<Throwable> suppressed) {
  Class result = null;
 
  try {
    result = defineClassNative(name, loader, cookie);
  } catch (NoClassDefFoundError var7) {
    if (suppressed != null) {
      suppressed.add(var7);
    }
  } catch (ClassNotFoundException var8) {
    if (suppressed != null) {
      suppressed.add(var8);
    }
  }
 
  return result;
}
 
public static void fartwithClassloader(ClassLoader appClassloader) {
  List<Object> dexFilesArray = new ArrayList<Object>();
  //这个是刚刚的findClass的pathList。这里通过反射获取。不过这个下面没使用。
  Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
  //和上面一样。不过这个是直接获取这个属性的结果出来,比上面多了个field.get()
  Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
  //通过反射获取dexElements,这个流程可以直接回顾上面的findClass。基本是一样的。
  Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");
  Field dexFile_fileField = null;
  try {
    dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
  } catch (Exception e) {
    e.printStackTrace();
  } catch (Error e) {
    e.printStackTrace();
  }
  Class DexFileClazz = null;
  try {
    //反射获取dexfile类型
    DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
  } catch (Exception e) {
    e.printStackTrace();
  } catch (Error e) {
    e.printStackTrace();
  }
  Method getClassNameList_method = null;
  Method defineClass_method = null;
  Method dumpDexFile_method = null;
  Method dumpMethodCode_method = null;
    //通过反射将4个函数的值填充上
  for (Method field : DexFileClazz.getDeclaredMethods()) {
    //这个应该是用来获取类名称列表的
    if (field.getName().equals("getClassNameList")) {
      getClassNameList_method = field;
      getClassNameList_method.setAccessible(true);
    }
    //这个是我们在上面loadClass流程最后看到的函数
    if (field.getName().equals("defineClassNative")) {
      defineClass_method = field;
      defineClass_method.setAccessible(true);
    }
    //这个用来保存整个dex的,是fart里面的代码
    if (field.getName().equals("dumpDexFile")) {
      dumpDexFile_method = field;
      dumpDexFile_method.setAccessible(true);
    }
    //这个应该是用来保存函数代码的,也是fart里面的代码
    if (field.getName().equals("dumpMethodCode")) {
      dumpMethodCode_method = field;
      dumpMethodCode_method.setAccessible(true);
    }
  }
  //这个是获取mCookie字段。不过后面也是没有用到的
  Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");
  Log.v("ActivityThread->methods", "dalvik.system.DexPathList.ElementsArray.length:" + ElementsArray.length);//5
  //下面是遍历所有dexfile,然后主动调用所有函数
  for (int j = 0; j < ElementsArray.length; j++) {
    Object element = ElementsArray[j];
    Object dexfile = null;
    try {
      //先是反射取出每个dexfile
      dexfile = (Object) dexFile_fileField.get(element);
    } catch (Exception e) {
      e.printStackTrace();
    } catch (Error e) {
      e.printStackTrace();
    }
    if (dexfile == null) {
      Log.e("ActivityThread", "dexfile is null");
      continue;
    }
    if (dexfile != null) {
      //然后取出dexfile中的mcookie,这里为什么从mInternalCookie可以在android源码中看到mCookie本身就是由mInternalCookie赋值的
      dexFilesArray.add(dexfile);
      Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");
      if (mcookie == null) {
        Object mInternalCookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mInternalCookie");
        if(mInternalCookie!=null)
        {
          mcookie=mInternalCookie;
        }else{
          Log.v("ActivityThread->err", "get mInternalCookie is null");
          continue;
        }
 
      }
      //再根据mcookie获取dexfile的所有类名
      String[] classnames = null;
      try {
        classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);
      } catch (Exception e) {
        e.printStackTrace();
        continue;
      } catch (Error e) {
        e.printStackTrace();
        continue;
      }
      //最后遍历所有类,调用每个类中的所有函数
      if (classnames != null) {
        for (String eachclassname : classnames) {
          loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);
        }
      }
    }
  }
  return;
}
public static void fartwithClassloader(ClassLoader appClassloader) {
  List<Object> dexFilesArray = new ArrayList<Object>();
  //这个是刚刚的findClass的pathList。这里通过反射获取。不过这个下面没使用。
  Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
  //和上面一样。不过这个是直接获取这个属性的结果出来,比上面多了个field.get()
  Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
  //通过反射获取dexElements,这个流程可以直接回顾上面的findClass。基本是一样的。
  Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");
  Field dexFile_fileField = null;
  try {
    dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
  } catch (Exception e) {
    e.printStackTrace();
  } catch (Error e) {
    e.printStackTrace();
  }
  Class DexFileClazz = null;
  try {
    //反射获取dexfile类型
    DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
  } catch (Exception e) {
    e.printStackTrace();
  } catch (Error e) {
    e.printStackTrace();
  }
  Method getClassNameList_method = null;
  Method defineClass_method = null;
  Method dumpDexFile_method = null;
  Method dumpMethodCode_method = null;
    //通过反射将4个函数的值填充上
  for (Method field : DexFileClazz.getDeclaredMethods()) {
    //这个应该是用来获取类名称列表的
    if (field.getName().equals("getClassNameList")) {
      getClassNameList_method = field;
      getClassNameList_method.setAccessible(true);
    }
    //这个是我们在上面loadClass流程最后看到的函数
    if (field.getName().equals("defineClassNative")) {
      defineClass_method = field;
      defineClass_method.setAccessible(true);
    }
    //这个用来保存整个dex的,是fart里面的代码
    if (field.getName().equals("dumpDexFile")) {
      dumpDexFile_method = field;
      dumpDexFile_method.setAccessible(true);
    }
    //这个应该是用来保存函数代码的,也是fart里面的代码
    if (field.getName().equals("dumpMethodCode")) {
      dumpMethodCode_method = field;
      dumpMethodCode_method.setAccessible(true);
    }
  }
  //这个是获取mCookie字段。不过后面也是没有用到的
  Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");
  Log.v("ActivityThread->methods", "dalvik.system.DexPathList.ElementsArray.length:" + ElementsArray.length);//5
  //下面是遍历所有dexfile,然后主动调用所有函数
  for (int j = 0; j < ElementsArray.length; j++) {
    Object element = ElementsArray[j];
    Object dexfile = null;
    try {
      //先是反射取出每个dexfile
      dexfile = (Object) dexFile_fileField.get(element);
    } catch (Exception e) {
      e.printStackTrace();
    } catch (Error e) {
      e.printStackTrace();
    }

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-11-12 22:35 被misskings编辑 ,原因: 补上图片
收藏
免费 6
支持
分享
最新回复 (15)
雪    币: 156
活跃值: (3806)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
分析的很详细,马了
2020-11-13 17:09
0
雪    币: 19
活跃值: (272)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢楼主的分享,学习了
2020-11-14 15:46
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4

上文中写道:这里先是把java传递过来的函数指针转换成了ArtMethod了---------这个是怎么实现的?看样子是封装了,里面这么转的?有空的告知一下,谢谢

最后于 2021-1-6 17:42 被wx_白小纯编辑 ,原因:
2021-1-6 17:42
0
雪    币: 1490
活跃值: (9928)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
5
wx_白小纯 上文中写道:这里先是把java传递过来的函数指针转换成了ArtMethod了---------这个是怎么实现的?看样子是封装了,里面这么转的?有空的告知一下,谢谢

是封装的。下面是封装的代码

extern "C" ArtMethod* jobject2ArtMethod(JNIEnv* env, jobject javaMethod) {
    ScopedFastNativeObjectAccess soa(env);
    ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod);
    return method;
}


2021-1-6 20:41
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
misskings 是封装的。下面是封装的代码extern&nbsp;&quot;C&quot;&nbsp;ArtMethod*&nbsp;jobject2ArtMethod(JN ...
多谢!
2021-1-14 22:34
0
雪    币: 334
活跃值: (392)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
大牛。如何修复脱下的dex  麻烦指点下,蟹蟹
2021-4-1 15:16
0
雪    币: 70
活跃值: (2031)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
2021-7-27 17:31
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
厉害
2021-7-28 15:55
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
拜读了大佬的文章 收获颇多 但仍然存在以下3个疑点 大佬若能抽时间解答 我将不胜感激
1.关于获取ClassLoader的Application对象 一定得是LoadedApk类对象的mApplication吗?ActivityThread里的mInitialApplication不行吗?
2.Java层的Method对象转换成native层的ArtMethod对象只能通过修改系统源码的方式来实现吗?换句话说 能够在自定义的so里面实现这个过程吗?
3.装换成ArtMethod对象后 虽然调用了ArtMethod::Invoke函数 但是刚进入这个函数 如果是主动调用就立即返回 那和没调用又有什么区别呢?
2021-10-16 18:40
0
雪    币: 1490
活跃值: (9928)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
11
万里星河 拜读了大佬的文章 收获颇多 但仍然存在以下3个疑点 大佬若能抽时间解答 我将不胜感激 1.关于获取ClassLoader的Application对象 一定得是LoadedApk类对象的mApplic ...
1、这个其实我们看看这两个值分别是怎么赋值的。就知道是否能用mInitialApplication了。
app = data.info.makeApplication(data.restrictedBackupMode, null);
Propagate autofill compat state
app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
mInitialApplication = app;
上面是mInitalApplication的赋值
下面是LoadedApk中的代码
public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
        ....
}
这样看大概就知道了。这两个其实就是一个值。所以也是可以使用mInitialApplication的。
2、Java层的Method对象转换并不是只能修改源码实现。上面能看到实际调用的还是ArtMethod的函数来进行转换的。所以你自己写so也能实现转换的。
3、ArtMethod::Invoke这个地方调用了立即返回。这个地方其实就是主动调用链的一个时机而已。一些抽取壳会选择一些时机来把函数还原回去。主动调用后,在函数真实执行的地方直接返回,就能触发到一些壳的机制,让他们自行的还原函数。但是这个立即返回的时机深度并不是能应对一些特殊壳。所以可以在调用更深的地方来返回。
2021-10-18 09:11
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
misskings 1、这个其实我们看看这两个值分别是怎么赋值的。就知道是否能用mInitialApplication了。 app = data.info.makeApplication(data.restricted ...
关于第二点 大佬指的是先dlopen("libart.so") 然后dlsym来调用实现转换吗?
2021-10-20 00:25
0
雪    币: 2155
活跃值: (4532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
可以确定 你是看了课的人
2021-11-9 18:36
0
雪    币: 2155
活跃值: (4532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
画图工具推荐一下
2021-11-9 18:36
0
雪    币: 1490
活跃值: (9928)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
15
我用的visio。挺好用的。
2021-11-10 08:51
0
雪    币: 145
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
分析的真细,学习了。。我没看课,哈哈。
2021-11-10 09:42
0
游客
登录 | 注册 方可回帖
返回
//