手搓整体加密壳demo && 手撕安卓源码

为了学习一下脱壳所以学一下加壳(其实是看网上大佬的文章看晕了,我靠,怎么这么底层),其实写得我也挺晕的,正好整理一下思路。
本文可能偏向教程?大概吧。

一、环境准备

IDE: android studio
感觉gradle下载太慢可以修改镜像源,因为它默认下载谷歌的。但是不要异常退出,可能会崩掉,然后就只能重装了。
下面是我下的版本,根据自己需要下载就好了。

image-20260315182146663

模拟器:mumu12
因为我没有真机,所以用的是模拟器,其实我感觉可能夜神会更好,因为可以调android的版本,不像mumu12只能用android12。
可不可以用android studio的呢,当然可以,主要是我没试过,所以就没这么干。

python,java,frida这些默认都有,没有就搜教程。

二、创建工程

1、选择工作目录

在一个你喜欢的地方,创建一个叫ShellDemo的目录(当然叫啥也随便)。

1
2
3
4
5
6
目录结构
ShellDemo
----target_app
----shell_file
----tool
target_app和shell_file是android studio的project目录,tool则存放打包apk和脱壳脚本等杂七杂八的东西。

2、创建target_app

打开android studio -> 点击new project -> phone and tablet 选择 empty views activity -> next

1
2
3
4
5
6
Name : TargetApp
PackageName : com.demo.target
SaveLocation : 选择target_app目录
Language : Java
Min SDK : 23
Build Configuration language : Groovy DSL

点击finish即创建成功。

创建完成后,选择ShellDemo\target_app\app\src\main\java\com\demo\target\MainActivity.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
34
package com.demo.target;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.util.Log;

public class MainActivity extends Activity {

private static final String TAG = "TargetApp";
// 故意写一个明显的字符串,脱壳后反编译能看到就说明成功
private static final String SECRET = "I_AM_THE_REAL_DEX_FLAG_2024";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

TextView tv = new TextView(this);
tv.setText(String.format("目标应用正在运行\n%s", SECRET));
setContentView(tv);

Log.d(TAG, "onCreate: " + SECRET);
realBusinessLogic();
}

private void realBusinessLogic() {
// 模拟真实业务逻辑,验证脱壳完整性
int result = 0;
for (int i = 0; i < 100; i++) {
result += i;
}
Log.d(TAG, "business result: " + result);
}
}

可以明显看出来这是ai写的(bushi

ShellDemo\target_app\app\src\main\res\layout\activity_main.xml修改为

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

3、创建shell_file

创建部分同上

创建ShellDemo\shell_file\app\src\main\java\com\demo\shell_file\ShellApplication.java文件,内容如下
因为代码比较长直接跳过也行,后面会有一个大章节用来分析的^o^/。

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package com.demo.shell_file;

import android.app.Application;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.ArrayMap;
import android.util.Log;

import java.io.*;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import dalvik.system.DexClassLoader;

public class ShellApplication extends Application {

private static final String TAG = "ShellFile";
private static final byte[] AES_KEY = "ShellDemo1234567".getBytes();
private static final byte[] AES_IV = "IV_ShellDemo1234".getBytes();

@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "Application onCreate");

try {
// 用替换后的 ClassLoader 加载目标 MainActivity
Class<?> targetMain = getClassLoader()
.loadClass("com.demo.target.MainActivity");
Log.d(TAG, "找到目标 MainActivity: " + targetMain);
} catch (ClassNotFoundException e) {
Log.e(TAG, "找不到目标 MainActivity: " + e.getMessage());
}
}

@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Log.d(TAG, "attachBaseContext: 开始落地版解密加载");

try {
// 1. 读取 dex 数量
int dexCount = getDexCount(base);
Log.d(TAG, "dex 数量: " + dexCount);

// 2. 解密所有 dex 并写到私有目录
File[] decryptedFiles = decryptAndWriteToDisk(base, dexCount);

// 3. 构建 classpath(多个 dex 用 : 分隔)
StringBuilder classPath = new StringBuilder();
for (int i = 0; i < decryptedFiles.length; i++) {
if (i > 0) classPath.append(File.pathSeparator);
classPath.append(decryptedFiles[i].getAbsolutePath());
Log.d(TAG, "落地文件: " + decryptedFiles[i].getAbsolutePath());
}

// 4. 创建新的 DexClassLoader
File optimizedDir = base.getDir("odex", MODE_PRIVATE);
DexClassLoader newClassLoader = new DexClassLoader(
classPath.toString(),
optimizedDir.getAbsolutePath(),
null,
base.getClassLoader() // 父加载器是原 PathClassLoader
);

Log.d(TAG, "新 ClassLoader 创建成功: " + newClassLoader);

// 5. 替换系统的 mClassLoader
replaceClassLoader(base, newClassLoader);

Log.d(TAG, "mClassLoader 替换完成");

} catch (Exception e) {
Log.e(TAG, "壳加载失败: " + e.getMessage(), e);
}
}

// ── 解密并写到磁盘 ────────────────────────────────

private File[] decryptAndWriteToDisk(Context context, int dexCount)
throws Exception {
File[] files = new File[dexCount];
AssetManager am = context.getAssets();

for (int i = 0; i < dexCount; i++) {
// asset 里的命名规则:encrypted_classes.dex, encrypted_classes2.dex...
String assetName = i == 0
? "encrypted_classes.dex"
: "encrypted_classes" + (i + 1) + ".dex";

// 读取加密数据
byte[] encrypted = readAsset(am, assetName);
Log.d(TAG, "读取加密 dex: " + assetName + " (" + encrypted.length + " bytes)");

// AES 解密
byte[] decrypted = decrypt(encrypted);
Log.d(TAG, "解密完成: " + decrypted.length + " bytes");

// 写到私有目录
String fileName = i == 0 ? "classes.dex" : "classes" + (i + 1) + ".dex";
File outFile = new File(context.getFilesDir(), fileName);
writeFile(outFile, decrypted);

files[i] = outFile;
Log.d(TAG, "写入磁盘: " + outFile.getAbsolutePath());
}

return files;
}

// ── AES 解密 ──────────────────────────────────────

private byte[] decrypt(byte[] encrypted) throws Exception {
Key keySpec = new SecretKeySpec(AES_KEY, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(AES_IV);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
return cipher.doFinal(encrypted);
}

// ── 读取 assets ───────────────────────────────────

private byte[] readAsset(AssetManager am, String name) throws Exception {
InputStream is = am.open(name);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) != -1) {
bos.write(buf, 0, len);
}
is.close();
return bos.toByteArray();
}

// ── 写文件 ────────────────────────────────────────

private void writeFile(File file, byte[] data) throws Exception {
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
fos.flush();
fos.close();
}

// ── 读取 dex 数量 ─────────────────────────────────

private int getDexCount(Context context) {
try {
byte[] data = readAsset(context.getAssets(), "dex_count");
return Integer.parseInt(new String(data).trim());
} catch (Exception e) {
return 1; // 默认 1 个
}
}

// ── 替换 mClassLoader ─────────────────────────────

private void replaceClassLoader(Context context, ClassLoader newClassLoader)
throws Exception {
// 1. 获取 ActivityThread 实例
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThread =
activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);

// 2. 获取 mPackages 字段
Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
ArrayMap mPackages = (ArrayMap) mPackagesField.get(activityThread);

// 3. 获取 LoadedApk 对象
String packageName = context.getPackageName();
WeakReference wr = (WeakReference) mPackages.get(packageName);
if (wr == null || wr.get() == null) {
Log.e(TAG, "LoadedApk 为空,跳过替换");
return;
}
Object loadedApk = wr.get();

// 4. 替换 mClassLoader
Class<?> loadedApkClass = Class.forName("android.app.LoadedApk");
Field mClassLoaderField = loadedApkClass.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);

// 打印替换前的 ClassLoader(用于验证)
Object oldClassLoader = mClassLoaderField.get(loadedApk);
Log.d(TAG, "替换前 ClassLoader: " + oldClassLoader);

mClassLoaderField.set(loadedApk, newClassLoader);

Log.d(TAG, "替换后 ClassLoader: " + newClassLoader);
}
}

修改AndroidManifest.xml为

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.demo.shell_file">
<application
android:name=".ShellApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="ShellFile"
android:theme="@style/Theme.ShellFile">

<!-- 壳的 MainActivity 保留但不设为启动项 -->
<activity
android:name=".MainActivity"
android:exported="false"/>

<!-- 声明目标 APP 的 MainActivity,设为启动项 -->
<activity
android:name="com.demo.target.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

</application>
</manifest>

4、创建tool

添加pack.py文件,用于将target.apk和shell.apk打包到一起

代码很长,别看,后面也有解析<( ̄︶ ̄)↗[GO!]

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
111
112
113
114
#!/usr/bin/env python3
"""
一代壳打包工具
用法: python pack.py <target.apk> <shell.apk> <output.apk> [--memory]
"""
import subprocess
import os
import sys
import shutil
import zipfile
import argparse
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import hashlib

# 密钥固定写死(真实壳会动态生成或从服务器拉取)
AES_KEY = b'ShellDemo1234567' # 16字节
AES_IV = b'IV_ShellDemo1234' # 16字节

def encrypt_dex(dex_data: bytes) -> bytes:
"""AES-CBC 加密 dex 数据"""
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
encrypted = cipher.encrypt(pad(dex_data, AES.block_size))
print(f" 原始大小: {len(dex_data)} bytes")
print(f" 加密后: {len(encrypted)} bytes")
print(f" MD5: {hashlib.md5(dex_data).hexdigest()}")
return encrypted

def get_dex_files(apk_path: str) -> dict:
"""从 APK 中提取所有 dex 文件"""
dex_files = {}
with zipfile.ZipFile(apk_path, 'r') as zf:
for name in zf.namelist():
if name.endswith('.dex'):
dex_files[name] = zf.read(name)
print(f" 找到 dex: {name} ({len(dex_files[name])} bytes)")
return dex_files
def sign_apk(unsigned_apk: str, signed_apk: str):
"""使用 debug.keystore 签名 APK"""

# 根据实际路径修改
apksigner = r"Z:\android_sdk\build-tools\36.0.0\apksigner.bat"

keystore = r"C:\Users\win11\.android\debug.keystore"


cmd = [
apksigner, "sign",
"--ks", keystore,
"--ks-key-alias", "androiddebugkey",
"--ks-pass", "pass:android",
"--key-pass", "pass:android",
"--out", signed_apk,
unsigned_apk
]

result = subprocess.run(cmd, capture_output=True, text=True)

if result.returncode == 0:
print(f"[+] 签名成功: {signed_apk}")
else:
print(f"[-] 签名失败: {result.stderr}")

def pack(target_apk: str, shell_apk: str, output_apk: str, is_memory: bool):
print(f"\n[*] 开始打包")
print(f" 目标APK: {target_apk}")
print(f" 壳APK: {shell_apk}")
print(f" 输出: {output_apk}")
print(f" 模式: {'不落地(内存加载)' if is_memory else '落地(文件加载)'}")

# 1. 提取目标 APK 的所有 dex
print(f"\n[*] 提取目标 dex...")
dex_files = get_dex_files(target_apk)
if not dex_files:
print("[-] 没有找到 dex 文件")
return

# 2. 加密所有 dex
print(f"\n[*] 加密 dex...")
encrypted_dexes = {}
for name, data in dex_files.items():
print(f" 加密 {name}:")
encrypted_dexes[name] = encrypt_dex(data)

# 3. 以壳 APK 为基础,替换 dex,塞入加密数据
print(f"\n[*] 重新打包...")
shutil.copy(shell_apk, output_apk)

with zipfile.ZipFile(output_apk, 'a', compression=zipfile.ZIP_STORED) as zf:
# 把加密的 dex 放进 assets
for name, encrypted_data in encrypted_dexes.items():
asset_name = f"assets/encrypted_{name}"
zf.writestr(asset_name, encrypted_data)
print(f" 写入: {asset_name} ({len(encrypted_data)} bytes)")

# 写入 dex 数量信息,方便壳读取
zf.writestr("assets/dex_count", str(len(encrypted_dexes)).encode())
zf.writestr("assets/dex_mode", b"memory" if is_memory else b"file")

print(f"\n[+] 打包完成: {output_apk}")
print(f" 文件大小: {os.path.getsize(output_apk)} bytes")
signed_apk = output_apk.replace('.apk', '_signed.apk')
sign_apk(output_apk, signed_apk)
print(f"[+] 最终产物: {signed_apk}")

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='一代壳打包工具')
parser.add_argument('target', help='目标APK路径')
parser.add_argument('shell', help='壳APK路径')
parser.add_argument('output', help='输出APK路径')
parser.add_argument('--memory', action='store_true', help='使用不落地模式')
args = parser.parse_args()

pack(args.target, args.shell, args.output, args.memory)

三、原理解析

1、pack.py代码分析

按照个人习惯,先从程序执行的入口开始

(1)main()

1
2
if __name__ == '__main__':
函数的入口,也就是main函数

前面几段跟argument相关的是命令行调用的参数,不太重要,在main函数中主要是调用了pack()。
pack()是pack.py的核心逻辑。

(2)pack()

get_dex_files()

首先调用了get_dex_files()提取apk的所有dex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
get_dex_files的作用是将apk以zip的形式打开,然后提取所有dex文件的文件名,也就是路径。

此处的入参为target_apk,也就是正常的apk,得到的文件列表存储在dex_files变量中。

def get_dex_files(apk_path: str) -> dict:
"""从 APK 中提取所有 dex 文件"""
dex_files = {}
with zipfile.ZipFile(apk_path, 'r') as zf:
for name in zf.namelist():
if name.endswith('.dex'):
dex_files[name] = zf.read(name)
print(f" 找到 dex: {name} ({len(dex_files[name])} bytes)")
return dex_files

tips:在zip中文件是线性而不是树状结构,通过namelist()获取zip的集中目录,即使不递归查看目录,get_dex_file也会搜索到所有.dex文件。
但是在 Android APK 的标准规范中真正的可执行代码 dex 必须放在 apk 的根目录中,在此处并没有作出筛选,如果有需要可以加上。

encrypt_dex()

然后使用encrypt_dex()对获取的dex文件进行加密

1
2
3
4
5
6
7
8
9
10
加密函数没啥好说的,就是用AES的CBC模式加密,密钥还是固定的,加密的作用就是让破解者在最后的output.apk中只能看到shell的dex,但是找不到target的dex。

def encrypt_dex(dex_data: bytes) -> bytes:
"""AES-CBC 加密 dex 数据"""
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
encrypted = cipher.encrypt(pad(dex_data, AES.block_size))
print(f" 原始大小: {len(dex_data)} bytes")
print(f" 加密后: {len(encrypted)} bytes")
print(f" MD5: {hashlib.md5(dex_data).hexdigest()}")
return encrypted

以壳 APK 为基础,替换 dex,塞入加密数据,这部分没有自定义的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
copy的作用是把shell_apk复制到output_apk中,在shell_apk中我们已经提前新建了一个assets目录。
shutil.copy(shell_apk, output_apk)

encrypted_dexes就是加密后的dex文件,每一个encrypted_dex塞入一个assets/encrypted_class...的文件中,
等到之后使用ShellApplication的时候,就从assets中提取加密后的dex文件再解密。
同理,dex_count和dex_mode也是为了方便读取,is_memory用于区别文件落地与文件不落地。

# 把加密的 dex 放进 assets
for name, encrypted_data in encrypted_dexes.items():
asset_name = f"assets/encrypted_{name}"
zf.writestr(asset_name, encrypted_data)
# 写入 dex 数量信息,方便壳读取
zf.writestr("assets/dex_count", str(len(encrypted_dexes)).encode())
zf.writestr("assets/dex_mode", b"memory" if is_memory else b"file")

sign_apk()

最后一步还要给apk签名,因为output_apk实际上是被修改过的shell_apk,需要签名才能安装。
不然就会报错。

1
2
3
4
报错如下
adb install .\output.apk
Performing Streamed Install
adb.exe: failed to install .\output.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Scanning Failed.: No signature found in package of version 2 or newer for package com.demo.shell_file]

实际上是调用的apksigner对apk签名,用的是android studio的,需要根据实际路径进行更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
apksigner = r"Z:\android_sdk\build-tools\36.0.0\apksigner.bat"

keystore = r"C:\Users\win11\.android\debug.keystore"
cmd = [
apksigner, "sign",
"--ks", keystore,
"--ks-key-alias", "androiddebugkey",
"--ks-pass", "pass:android",
"--key-pass", "pass:android",
"--out", signed_apk,
unsigned_apk
]
result = subprocess.run(cmd, capture_output=True, text=True)

总结一下,pack.py主要做了一下几步:

(1)把target.apk的dex文件加密

(2)塞到shell.apk的assets文件夹中

(3)给output.apk重新签名

2、ShellApplication.java代码分析

由于target_app实际上就是一个普通的示例demo,也没什么用,就不分析了。

从入口开始,ShellApplication.java最开始执行的入口点在attachBaseContext(),至于为什么,详情请见[原创]Android漏洞之战(11)——整体加壳原理和脱壳技巧详解 ,大佬写得很清楚,但是我快啃晕了。(文末也会列出参考链接的)。

搜索android源码可以使用https://cs.android.com/

(1)attachBaseContext()

super.attachBaseContext()

首先调用了super.attachBaseContext(base)。
super并不是ShellApplication的父类Application,而是Application的父类ContextWrapper。
Application并没有override这个函数。
进入ContextWrapper之后可以发现代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// frameworks/base/core/java/android/content/ContextWrapper.java

public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
...
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
...
}

那么base是从何而来呢,在ContextWrapper.java中可以发现ContextWrapper有一个有参构造函数,在Application.java的构造函数中,调用了super(null),此处的mBase为null。

1
2
3
4
5
// frameworks/base/core/java/android/app/Application.java

public Application() {
super(null);
}

想要知道base是从哪来的。还需要搞清一个问题,那就是谁调用了attachBaseContext()函数。

1
2
3
4
5
6
// frameworks/base/core/java/android/app/Application.java

final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

其实是Application.attach()调用了attachBaseContext(),但是到这里其实还没办法解决疑惑(为什么调用Application.attach()中的attachBaseContext()反而会调用ShellApplication.attachBaseContext()呢)

再向上追溯

1
2
3
4
5
6
7
8
9
10
// frameworks/base/core/java/android/app/Instrumentation.java

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;
}

可以看到这里的app的类型是Application,但是实际上是根据AndroidManifest.xml中的application的android:name生成。
因此实际上的app是ShellApplication,但是被转换为了父类Application。在调用attach()时,由于ShellApplication没有attach(),调用了Application.attach(),但是在调用attachBaseContext时,又有override的ShellApplication.attachBaseContext。

到这里解决了attachBaseContext的调用问题,但是仍然不知道base是从何而来。

1
2
3
4
5
6
7
8
9
目前已知的调用链如下:
Instrumentation.newApplication(cl,className,context) ->
Application() ->
ContextWrapper(null) ->
Application.attach(context) ->
ShellApplication.attachBaseContext(context)→
super.attachBaseContext(context) →
ContextWrapper.attachBaseContext()→
mBase = base

再继续往上追溯,来到LoadedApk.java中

1
2
3
4
5
6
7
8
9
10
11
12
13
// frameworks/base/core/java/android/app/LoadedApk.java

private Application makeApplicationInner(boolean forceDefaultAppClass,
Instrumentation instrumentation, boolean allowDuplicateInstances) {
...
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// The network security config needs to be aware of multiple
// applications in the same process to handle discrepancies
NetworkSecurityConfigProvider.handleNewApplication(appContext);
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
appContext.setOuterContext(app);
...
}

可以看到Instrumentation.newApplication(),mInstrumentation的类型就是Instrumentation,appContext也就是context,由ContextImpl.createAppContext()函数生成。至此,base的来源也清楚了。

其实这些在前面提到的大佬的文章也有写。

总结一下,调用链如下

1
2
3
4
5
6
7
8
9
10
11
# framework层
LoadedApk.makeApplicationInner(forceDefaultAppClass,instrumentation,allowDuplicateInstances) ->
Instrumentation.newApplication(cl,className,context) ->
Application() ->
ContextWrapper(null) ->
Application.attach(context) ->
# APP层
ShellApplication.attachBaseContext(context)→
super.attachBaseContext(context) →
ContextWrapper.attachBaseContext()→
mBase = base
getDexCount()

解决了前面的其实后面的基本都比较简单,可以说是先苦后甜了。

已知pack.py实际上的操作是将target.apk的加密dex文件塞到了shell.apk中,那么在ShellApplication中就需要解密。

1
int dexCount = getDexCount(base);

读取了dex_count文件中的内容,也就是dex文件的数量。

decryptAndWriteToDisk()

核心逻辑就是把解密出来的dex文件写入私有目录。私有目录由context.getFilesDir() 提供,它返回的一个 File 对象,指向App 私有数据目录下的 files文件夹。(一般只有本app可读,非root情况下)
由于代码中也有注释,这里就不多赘述了。

需要注意的是这里还对dex文件的命名也进行了复原,并且返回了解密后的文件路径,用于之后加载。

构建classpath和ClassLoader

classpath就是用分隔符分隔开的绝对路径。
分隔符由File.pathSeparator生成,为了保证多系统下的兼容性,使用File.pathSeparator自动获取。

接下来创建一个新的classLoader用于加载解密后的dex文件。

1
2
3
4
5
6
7
File optimizedDir = base.getDir("odex", MODE_PRIVATE);
DexClassLoader newClassLoader = new DexClassLoader(
classPath.toString(),
optimizedDir.getAbsolutePath(),
null,
base.getClassLoader() // 父加载器是原 PathClassLoader
);

odex其实没什么用,高版本好像把这个取消了,这里为了兼容性可能才加了这个。

为什么要创建一个新的classLoader?

系统自带的PathClassLoader没有办法读取解密后的dex文件,这是运行过程中动态加载的,因此需要用一个新的classLoader来加载这些文件,或者将dex目录塞入原classLoader的pathList中(这个叫dex插桩,在热修复或者打补丁中比较常用),这样原来的pathClassLorder才能够成功识别。
可以尝试以下不换classLoader,大概率会报错:java.lang.ClassNotFoundException。

replaceClassLoader()

之前已经创建了新的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
private void replaceClassLoader(Context context, ClassLoader newClassLoader)
throws Exception {
// 1. 获取 ActivityThread 实例
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);

// 2. 获取 mPackages 字段
Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
ArrayMap mPackages = (ArrayMap) mPackagesField.get(activityThread);

// 3. 获取 LoadedApk 对象
String packageName = context.getPackageName();
WeakReference wr = (WeakReference) mPackages.get(packageName);
if (wr == null || wr.get() == null) {
Log.e(TAG, "LoadedApk 为空,跳过替换");
return;
}
Object loadedApk = wr.get();

// 4. 替换 mClassLoader
Class<?> loadedApkClass = Class.forName("android.app.LoadedApk");
Field mClassLoaderField = loadedApkClass.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);

// 打印替换前的 ClassLoader(用于验证)
Object oldClassLoader = mClassLoaderField.get(loadedApk);
Log.d(TAG, "替换前 ClassLoader: " + oldClassLoader);
mClassLoaderField.set(loadedApk, newClassLoader);
Log.d(TAG, "替换后 ClassLoader: " + newClassLoader);
}

总结一下上述代码的作用
(1)通过反射获取了一个ActivityThread实例
ActivityThread 是 Android 应用进程的入口点和管理者。

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
//frameworks/base/core/java/android/app/ActivityThread.java

public final class ActivityThread extends ClientTransactionHandler
implements ActivityThreadInternal {
private static volatile ActivityThread sCurrentActivityThread;
public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
}
...
@UnsupportedAppUsage
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
...
}
...
@UnsupportedAppUsage
@RavenwoodKeep
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
...
}

/***
调用链:
main() -> attach() -> sCurrentActivityThread = this
通过反射调用的currentActivityThread()获得了当前的ActivityThread实例
***/

(2)获取 mPackages 字段
mPackages是ActivityThread的一个字段,类型为ArrayMap。

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//frameworks/base/core/java/android/app/ActivityThread.java

public final class ActivityThread extends ClientTransactionHandler
implements ActivityThreadInternal {
...
final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
...
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage, boolean isSdkSandbox) {
...
packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader,securityViolation, includeCode&& (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
...
if (differentUser || isSdkSandbox) {
// Caching not supported across users and for sdk sandboxes
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
} else {
...
}
...
}
}

/***
虽然在这个类中有很多个getPackageInfo和getPackageInfoNoCheck,但是实际上都是调用了这个六个入参的getPackageInfo。
可以看到mPackages由getPackageInfo中的mPackages.put提供数据,其中第一个参数为aInfo.packageName,
第二个参数为一个LoadedApk实例,再继续向上走看看aInfo是哪来的。

***/

//frameworks/base/core/java/android/app/ActivityThread.java

public final class ActivityThread extends ClientTransactionHandler
implements ActivityThreadInternal {
@UnsupportedAppUsage
private void handleBindApplication(AppBindData data) {
data.info = getPackageInfo(data.appInfo, mCompatibilityInfo, null /* baseLoader */,
false /* securityViolation */, true /* includeCode */,
false /* registerPackage */, isSdkSandbox);
try {
// 注意这里的makeAppliocationInner
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
}
}

class H extends Handler {
public static final int BIND_APPLICATION = 110;
public void handleMessage(Message msg) {
switch (msg.what) {
case BIND_APPLICATION:
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
}
}
}

final H mH = new H();
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
if (DEBUG_MESSAGES) {
Slog.v(TAG,"SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj);
}
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}

private class ApplicationThread extends IApplicationThread.Stub {
@Override
public final void bindApplication(参数太多了,省略了) {
AppBindData data = new AppBindData();
//注意这里有一段给data赋值的代码,就是用入参赋值的,但是我嫌太多了没复制
sendMessage(H.BIND_APPLICATION, data);
}


}
/***
getPackageInfo的参数data.appInfo来自handleBindApplication的入参data
handleBindApplication被handleMessage调用,H是ActivityThread的内部类,
使用handleMessage处理msg,如果是BIND_APPLICATION就调用handleBindApplication
既然有处理msg的handleMessage,当然也有发送信息的sendMessage,由私有类ApplicationThread中的bindApplication调用。
(注意ApplicationThread和ActivityThread的区别)
接下来要做的事就很清楚了,看一下到底是谁调用了bindApplication,就能知道数据的来源了
***/

//注意到调用了这个函数的是attachApplicationLocked(真是注意力惊人)
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
ProcessRecord app;
...
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
}
...
@GuardedBy("this")
private void attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
if (app.getIsolatedEntryPoint() != null) {
...
} else {
...
thread.bindApplication(processName,appInfo,
app.sdkSandboxClientAppVolumeUuid,
app.sdkSandboxClientAppPackage,......)
}
}

@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
if (thread == null) {
throw new SecurityException("Invalid application interface");
}
synchronized (this) {
int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid, callingUid, startSeq);
Binder.restoreCallingIdentity(origId);
}
}
}

/***
可以看到attachApplicationLocked的入参分别是thread,pid,callingUid等,这些是进程的内容。
也就是说,mPackage其实是AMS(ActivityManagerService)根据pid从系统进程中提取了对应进程的配置信息。

总结一下,调用链如下:
ActivityManagerService.attachApplication() ->
ActivityManagerService.attachApplicationLocked() ->
ApplicationThread.bindApplication() ->
ActivityThread.sendMessage() --> handleMessage收到信息

H.handleMessage() ->
ActivityThread.handleBindApplication() ->
ActivityThread.getPackageInfo() ->
mPackages.put()

然后再通过反射就能获得已经得到了系统进程信息的mPackages字段。

(3)获取 LoadedApk 对象
WeakReference是Java提供的一种弱引用对象,用于引用一个对象。
已知mPackages存储的是LoadedApk的弱引用对象,那么通过WeakReference就能获取LoadedApk了。

1
2
3
4
5
6
7
String packageName = context.getPackageName();
WeakReference wr = (WeakReference) mPackages.get(packageName);
if (wr == null || wr.get() == null) {
Log.e(TAG, "LoadedApk 为空,跳过替换");
return;
}
Object loadedApk = wr.get();

需要注意的是WeakReference引用的对象可能已经被gc回收了。

(4)替换mClassLoader
mClassLoader是LoadedApk的一个属性。

1
2
3
Class<?> loadedApkClass = Class.forName("android.app.LoadedApk");
Field mClassLoaderField = loadedApkClass.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);

替换后的mClassLoader实际上是能够读取target_app的dex文件的classLoader,因而能够加载出target_app的内容。

(2)OnCreate()

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "Application onCreate");

try {
// 用替换后的 ClassLoader 加载目标 MainActivity
Class<?> targetMain = getClassLoader().loadClass("com.demo.target.MainActivity");
Log.d(TAG, "找到目标 MainActivity: " + targetMain);
} catch (ClassNotFoundException e) {
Log.e(TAG, "找不到目标 MainActivity: " + e.getMessage());
}
}

ShellApplication.Create()调用链分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// frameworks/base/core/java/android/app/Application.java

public void onCreate() {
}

// frameworks/base/core/java/android/app/Instrumentation.java

public void callApplicationOnCreate(Application app) {
app.onCreate();
}

// frameworks/base/core/java/android/app/LoadedApk.java
private Application makeApplicationInner(boolean forceDefaultAppClass,
Instrumentation instrumentation, boolean allowDuplicateInstances) {
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
}
}

到这里可能就有细心的朋友发现了,实际上newApplication执行后最后调用了ShellApplication.attachBaseContext()
(闭环了)

然后在同一个函数的下一段代码,执行了callApplicationOnCreate() -> app.onCreate()。
由上文可知,newApplication得到的其实是一个ShellApplication,根据多态的原则,实际上调用的是ShellApplication.OnCreate()(Application的OnCreate()是一个空函数)

(3)ActivityThread启动流程

将之前的内容贯通起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ActivityManagerService.attachApplication() -> 
ActivityManagerService.attachApplicationLocked() ->
ApplicationThread.bindApplication() ->
ActivityThread.sendMessage() --> handleMessage收到信息

H.handleMessage() ->
ActivityThread.handleBindApplication() ->
ActivityThread.getPackageInfo();
LoadedApk.makeApplicationInner() ->
Instrumentation.newApplication();Instrumentation.callApplicationOnCreate() ->

newApplication()和callApplicationOnCreate()是先后顺序执行
分支一:
Instrumentation.newApplication() ->
Application() ->
ContextWrapper(null) ->
Application.attach() ->
ShellApplication.attachBaseContext()

分支二:
Instrumentation.callApplicationOnCreate(app) ->
app.onCreate() ->
ShellApplication.Create()

3、MainActivity的执行

解决了Application的执行,接下来是Activity的执行。
还是从程序的入口开始,MainActivity.OnCreate()

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
// frameworks/base/core/java/android/app/Activity.java

final void performCreate(Bundle icicle) {
performCreate(icicle, null);
}

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
}

// frameworks/base/core/java/android/app/Instrumentation.java

public Activity newActivity(ClassLoader cl, String className,Intent intent)
throws InstantiationException, IllegalAccessException,ClassNotFoundException {
String pkg = intent != null && intent.getComponent() != null
? intent.getComponent().getPackageName() : null;
return getFactory(pkg).instantiateActivity(cl, className, intent);
}

public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}

//frameworks/base/core/java/android/app/ContextImpl.java

@Override
public ClassLoader getClassLoader() {
return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
}

// frameworks/base/core/java/android/app/ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
if (isSandboxedSdkContextUsed) {
// In case of sandbox activity, the context refers to the an SDK with no visibility
// on the SandboxedActivity java class, the App context should be used instead.
cl = activityBaseContext.getApplicationContext().getClassLoader();
} else {
cl = activityBaseContext.getClassLoader();
}
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}

调用链如下:
ActivityThread.performLaunchActivity() -> Instrumentation.callActivityOnCreate() -> Activity.performCreate() -> MainActicity.onCreate()

有几个需要注意的点:
(1)在ActivityThread.performLaunchActivity()中调用ContextImpl.getClassLoader()获得cl,而getClassLoader()实际上取的是mClassLoader,也就是前面修改的dexClassLoader。
(2)在ActivityThread.performLaunchActivity()中,Instrumentation.newActivity()虽然声明的返回类型是Activity,但是实际上是MainActivity,因此在调用onCreate()时执行了MainActicity.onCreate()。

4、dex加载过程

1
2
3
4
5
6
DexClassLoader newClassLoader = new DexClassLoader(
classPath.toString(),
optimizedDir.getAbsolutePath(),
null,
base.getClassLoader() // 父加载器是原 PathClassLoader
);

在DexClassLoader生成的时候,大部分情况下dex就已经加载到内存中了。
由于分支较多,只写了适合本demo的调用链。

dex加载的调用链如下:
DexClassLoader() -> BaseDexClassLoader() -> new DexPathList() -> DexPathList.makeDexElements() -> DexPathList.loadDexFile() -> new DexFile() -> DexFile.openDexFile() -> DexFile.openDexFileNative() -> DexFile_openDexFileNative() -> OatFileManager::OpenDexFilesFromOat() -> DexFileLoader::Open() -> DexFileLoader::OpenCommon()

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
// libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}

// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

public class BaseDexClassLoader extends ClassLoader {

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) {
super(parent);
...
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
...
}
}

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

public final class DexPathList {
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
...
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);
...
}

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
}

@UnsupportedAppUsage
private static DexFile loadDexFile(File file, File optimizedDirectory,
ClassLoader loader, Element[] elements) throws IOException {
...
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
}
...
}
}

// libcore/dalvik/src/main/java/dalvik/system/DexFile.java
public final class DexFile {
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
mCookie = openDexFile(fileName, null, 0, loader, elements);
...
}

private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) throws IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return openDexFileNative(new File(sourceName).getAbsolutePath(),(outputName == null)
? null : new File(outputName).getAbsolutePath(),flags,loader,elements);
}

@UnsupportedAppUsage
private static native Object openDexFileNative(String sourceName, String outputName, int flags,ClassLoader loader, DexPathList.Element[] elements);
}
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
// art/runtime/native/dalvik_system_DexFile.cc
static JNINativeMethod gMethods[] = { ...
NATIVE_METHOD(DexFile,
openDexFileNative,
"(Ljava/lang/String;"
"Ljava/lang/String;"
"I"
"Ljava/lang/ClassLoader;"
"[Ldalvik/system/DexPathList$Element;"
")Ljava/lang/Object;"),
...
}

static jobject DexFile_openDexFileNative(JNIEnv* env,
jclass,
jstring javaSourceName,
[[maybe_unused]] jstring javaOutputName,
[[maybe_unused]] jint flags,
jobject class_loader,
jobjectArray dex_elements) {
...
std::vector<std::unique_ptr<const DexFile>> dex_files =
Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
class_loader,
dex_elements,
/*out*/ &oat_file,
/*out*/ &error_msgs);
...
}

// art/runtime/oat/oat_file_manager.cc

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
const char* dex_location,
jobject class_loader,
jobjectArray dex_elements,
const OatFile** out_oat_file,
std::vector<std::string>* error_msgs) {
...
if (dex_files.empty()) {
if (!dex_file_loader.Open(Runtime::Current()->IsVerificationEnabled(),kVerifyChecksum,
/*out*/ &error_msg,&dex_files)) {
}
}
return dex_files;
...
}


// art/libdexfile/dex/dex_file_loader.cc
bool DexFileLoader::Open(bool verify,
bool verify_checksum,
bool allow_no_dex_files,
DexFileLoaderErrorCode* error_code,
std::string* error_msg,
std::vector<std::unique_ptr<const DexFile>>* dex_files) {
...
std::unique_ptr<const DexFile> dex_file =
OpenCommon(root_container_,
root_container_->Begin() + header_offset,
root_container_->Size() - header_offset,
multidex_location,
/*location_checksum*/ {}, // Use default checksum from dex header.
/*oat_dex_file=*/nullptr,
verify,
verify_checksum,
error_msg,
error_code);
...
}

// 常用脱壳点
std::unique_ptr<DexFile> DexFileLoader::OpenCommon(std::shared_ptr<DexFileContainer> container,
const uint8_t* base,
size_t app_compat_size,
const std::string& location,
std::optional<uint32_t> location_checksum,
const OatDexFile* oat_dex_file,
bool verify,
bool verify_checksum,
std::string* error_msg,
DexFileLoaderErrorCode* error_code) {

std::unique_ptr<DexFile> dex_file;
auto header = reinterpret_cast<const DexFile::Header*>(base);
if (size >= sizeof(StandardDexFile::Header) && StandardDexFile::IsMagicValid(base)) {
uint32_t checksum = location_checksum.value_or(header->checksum_);

dex_file.reset(new StandardDexFile(base, location, checksum, oat_dex_file, container));
}
...
}

四、脱壳

力竭了,偷点懒,搬运一下大佬写的吧。大概写了两天X﹏X。

我要是写毕设有这么努力就好了(bushi

本来应该还有个不落地版的shell,但是感觉再多坐会要似了。

[原创]Android漏洞之战(11)——整体加壳原理和脱壳技巧详解 第五点的脱壳技巧归纳

五、参考链接

[原创]Android加壳脱壳学习(1)——动态加载和类加载机制详解

[原创]Android漏洞之战(11)——整体加壳原理和脱壳技巧详解

Android系统架构与系统源码目录

Android系统启动流程(一)解析init进程启动过程 (经典csdn自动收费这一块,路边也够)

Android系统启动流程(二)解析Zygote进程启动过程

Android系统启动流程(三)解析SyetemServer进程启动过程 (同上)

Android系统启动流程(四)Launcher启动过程与系统启动流程

拒绝“裸奔”!带你手搓一个 Android 加壳器(附源码解析)