android脱壳相关_dex(类)加载与执行

Posted by marginal on 2022-09-18
Estimated Reading Time 11 Minutes
Words 2.2k In Total
Viewed Times

0x00 前言

在学习android脱壳的时候在想一个点, ART是java虚拟机, 如果要执行dex文件, 需要有一个dex加载到ART虚拟机的过程, 这个加载的函数在加载的时候的dex文件必然是完整正确的dex, 除非是魔改dex的执行过程, 所以只需要找到这个加载的时机就可以把壳脱下来.

0x01 app启动过程

  1. 当点击桌面图标时, 桌面这个app会通过binder机制进行跨进程通信给AMS(ActivityManagerService)
  2. AMS通知Zygote fork需要启动的app进程
  3. app执行ActivityThread.main()方法
  4. 通过IBinder机制调用AMS的attachApplication函数
  5. app接受system_server进程发来的bindApplication消息
  6. 初始化Application并执行相关回调
  7. 初始化Activtiy并执行相关回调

这里的Application可以由自己定义, 所以直接从application的加载的地方来看dex文件的加载过程.

0x02 ActivityThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public final class ActivityThread extends ClientTransactionHandler
implements ActivityThreadInternal {
public static void main(String[] args) {
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
Looper.loop();
}
private void attach(boolean system, long startSeq) {
final IActivityManager mgr = ActivityManager.getService();
try {
//通过IBinder机制调用AMS的attachApplication函数
mgr.attachApplication(mAppThread, startSeq);
}
}
class H extends Handler {
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION: //接收到bind_application消息
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}
}
private void handleBindApplication(AppBindData data) {
try {
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
}
}
}

主线程消息循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final class Looper {
public static void loop() {
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
try {
msg.target.dispatchMessage(msg);
}
}
}

public class Handler {
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
handleMessage(msg);
}
}
}

0x03 application类加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public final class LoadedApk {
private Application makeApplicationInner(boolean forceDefaultAppClass,
Instrumentation instrumentation, boolean allowDuplicateInstances) {
Application app = null;
final java.lang.ClassLoader cl = getClassLoader();
String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
myProcessName);
if (forceDefaultAppClass || (appClass == null)) { //如果未定义用户Application, 则使用默认Application
appClass = "android.app.Application";
}
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
}
}

public class Instrumentation {
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
app.attach(context);
return app;
}
}

public class AppComponentFactory {
public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
@NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Application) cl.loadClass(className).newInstance(); //使用加载器cl加载类className
}
}

分析完上述的application加载过程, 可以清晰起来是使用类加载器来加载dex文件的. 那到这里继续往底层分析类加载器的加载过程.

这里加一点题外话(之所以说题外话是因为本文主要讲脱壳, 而这里是涉及到写壳), 就是这个类加载器的替换:

如果要实现动态dex加载, 就需要自定义类加载器. 在实现自定义类加载器之前, 需要解决一个问题, 那就是替换这里的类加载器为自定义类加载器.

1
2
3
4
5
6
7
8
9
private ClassLoader mClassLoader;
public ClassLoader getClassLoader() {
synchronized (mLock) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
}
return mClassLoader;
}
}

使用java反射机制获取mclassLoader, 然后进行替换

0x04 类加载器

ClassLoader是一个抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public final class LoadedApk {    
LoadedApk(ActivityThread activityThread) {
mDefaultClassLoader = ClassLoader.getSystemClassLoader(); //返回PathClassLoader类加载器
mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
new ApplicationInfo(mApplicationInfo)); //直接返回mDefaultClassLoader类加载器
}
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) { //构造时参数为dexpath
super(dexPath, null, null, parent);
}
}

public abstract class ClassLoader {
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;
}
}

这里的ClassLoader涉及到双亲委派机制详情请查看看雪大佬写的:动态加载和类加载机制详解

抽象类的ClassLoader为空方法, 需要具体实例加载器去重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class BaseDexClassLoader extends ClassLoader {
protected Class<?> findClass(String name) throws ClassNotFoundException {
// First, check whether the class is present in our shared libraries.
if (sharedLibraryLoaders != null) {
for (ClassLoader loader : sharedLibraryLoaders) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException ignored) {
}
}
}
// Check whether the class in question is present in the dexPath that
// this classloader operates on.
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//进入这里分析, 上下两个load都是调用的loadClass方法, 本质和上面类加载器的loadClass应该基本上一样
Class c = pathList.findClass(name, suppressedExceptions);
if (c != null) {
return c;
}
// Now, check whether the class is present in the "after" shared libraries.
if (sharedLibraryLoadersAfter != null) {
for (ClassLoader loader : sharedLibraryLoadersAfter) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException ignored) {
}
}
}
return c;
}
}

public final class DexPathList {
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
return null;
}
/*package*/ static class Element {
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
}
}

这里就涉及到dexFile类, dexFile从类名上看就知道这个是用来内存存储或者管理dex文件的类, 当ART虚拟机需要的时候直接找dexFile类.加载过程就要浮出水面了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class DexFile {
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
}
return result;
}
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
}

从这里就基本上到了native, 但是分析着分析着可以发现, 这里只是做了从DexFile加载, 并不是从dex文件映射到内存. 意思就是, 这里是ART虚拟机从内存获取类, 并不是从文件映射到内存的过程. 而指令抽取也是修改的这里来达到动态更改程序的目的.

0x05 dex文件映射到内存

回到加载Application的加载器PathClassLoader的创建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public final class LoadedApk {    
LoadedApk(ActivityThread activityThread) {
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
new ApplicationInfo(mApplicationInfo));
}

}

private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}


public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, librarySearchPath, parent, null, null, false);
}
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
ClassLoader[] sharedLibraryLoadersAfter,
boolean isTrusted) {
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
}
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);
}
private static List<File> splitDexPath(String path) {
return splitPaths(path, false);
}
private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
List<File> result = new ArrayList<>();

if (searchPath != null) {
for (String path : searchPath.split(File.pathSeparator)) {
if (directoriesOnly) {
try {
StructStat sb = Libcore.os.stat(path);
if (!S_ISDIR(sb.st_mode)) {
continue;
}
} catch (ErrnoException ignored) {
continue;
}
}
result.add(new File(path));
}
}

return result;
}
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
if (file.isDirectory()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();

DexFile dex = null; //这里对应了前面dex加载的时候的DexFile文件
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null); //以dex文件生成elements类
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
//....
}

if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
if (dex != null && isTrusted) {
dex.setTrusted();
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}

到这里文件映射到内存上的过程基本上已经清晰了, 可以继续往下深入进行hook, 也可以就在这里hook得到dex, 这里已经可以得到dex文件了, 如果有魔改, 则需要自己继续往native层分析. 具体的dex文件搜索方法有很多.

0x06 dex文件dump方法

这里从一些脱壳工具的原理讲述dump方法

frida-dexdump

这个脱壳方法是直接去搜索内存, dex文件加载到内存中了之后, 直接使用暴力搜索dex文件头的方法来获取完整dex文件. 可以用来脱360.

反射大师

这是使用的一个cookies的参数, 这个参数就代表了dex文件在内存中的位置, 直接就可以dump出来

frida-hook脱壳

直接hook OpenDexFile函数, 获取返回值就可以得到dex文件

Black*

hook脱壳


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !