加固

加固

加壳app的运行流程

参考

[原创]FART:ART环境下基于主动调用的自动化脱壳方案-Android安全-看雪-安全社区|安全招聘|kanxue.com

BaseDexClassLoader.java - Android Code Search

unpacker/android-7.1.2_r33/art/runtime/unpacker/unpacker.cc at master · Youlor/unpacker

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现 | 安全后厨团队

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
package com.grand.packer;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.grand.packer.databinding.ActivityMainBinding;

public class myapplication extends Application{

@Override
public void onCreate() {
super.onCreate();
Log.e("myapplication.onCreate","now is in the onCreate");
}

@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Log.e("myapplication.attachBaseContext","now is in the attachBaseContext");
}
}

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
package com.grand.packer;

import androidx.appcompat.app.AppCompatActivity;

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

import com.grand.packer.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

// Used to load the 'packer' library on application startup.
static {
System.loadLibrary("packer");
}

private ActivityMainBinding binding;

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

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());

Log.e("MainActivity.attachBaseContext","now is in the MainActivity.attachBaseContext");
}

/**
* A native method that is implemented by the 'packer' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}

先执行myapplication的内容再进行main的内容

image

image

在activitythread中的main中 先是设置了进程名 再去创建了activitythread的实例

重新写一个myapplication的代码

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
package com.grand.packer;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.grand.packer.databinding.ActivityMainBinding;

public class myapplication extends Application{

public myapplication(){
Log.e("myapplication","now is myapplication.instence");
}


public myapplication(int a){
Log.e("myapplication","now is myapplication.instence(a)");
}

static {
Log.e("static","now is static");
}

@Override
public void onCreate() {
super.onCreate();
Log.e("myapplication.onCreate","now is in the onCreate");
}

@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Log.e("myapplication.attachBaseContext","now is in the attachBaseContext");
}
}

image

观察一下代码的执行流程

源码分析

image

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
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

// Install selective syscall interception
AndroidOs.install();

// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);

Environment.initForCurrentUser();

// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);

// Call per-process mainline module initialization.
initializeMainlineModules();

Looper.prepareMainLooper();

Process.setArgV0("<pre-initialized>");

// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

先来到main函数的地方 ActivityThread这个类的sCurrentActivityThread静态变量用于全局保存创建的ActivityThread实例

1
Process.setArgV0("<pre-initialized>");

先是setargv0起了一个进程 再是创建了一个activity的实例

1
ActivityThread thread = new ActivityThread();

在后一条命令中 调用thread.attach(false)完成一系列初始化准备工作 并完成全局静态变量sCurrentActivityThread的初始化

1
thread.attach(false, startSeq);

当收到系统发送来的bindapplication的进程间调用时,调用函数handlebindapplication来处理该请求

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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
private void handleBindApplication(AppBindData data) {
mDdmSyncStageUpdater.next(Stage.Bind);

// Register the UI Thread as a sensitive thread to the runtime.
VMRuntime.registerSensitiveThread();
// In the case the stack depth property exists, pass it down to the runtime.
String property = SystemProperties.get("debug.allocTracker.stackDepth");
if (property.length() != 0) {
VMDebug.setAllocTrackerStackDepth(Integer.parseInt(property));
}
if (data.trackAllocation) {
DdmVmInternal.setRecentAllocationsTrackingEnabled(true);
}
// Note when this process has started.
Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(),
data.startRequestedElapsedTime, data.startRequestedUptime);

AppCompatCallbacks.install(data.disabledCompatChanges, data.mLoggableCompatChanges);
// Let libcore handle any compat changes after installing the list of compat changes.
AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();

// Initialize the zip path validator callback depending on the targetSdk.
// This has to be after AppCompatCallbacks#install() so that the Compat
// checks work accordingly.
initZipPathValidatorCallback();

mBoundApplication = data;
mConfigurationController.setConfiguration(data.config);
mConfigurationController.setCompatConfiguration(data.config);
mConfiguration = mConfigurationController.getConfiguration();
mCompatibilityInfo = data.compatInfo;

mProfiler = new Profiler();
String agent = null;
if (data.initProfilerInfo != null) {
mProfiler.profileFile = data.initProfilerInfo.profileFile;
mProfiler.profileFd = data.initProfilerInfo.profileFd;
mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
mProfiler.mClockType = data.initProfilerInfo.clockType;
mProfiler.mProfilerOutputVersion = data.initProfilerInfo.profilerOutputVersion;
if (data.initProfilerInfo.attachAgentDuringBind) {
agent = data.initProfilerInfo.agent;
}
}

VMDebug.setUserId(UserHandle.myUserId());
VMDebug.addApplication(data.appInfo.packageName);
// send up app name; do this *before* waiting for debugger
Process.setArgV0(data.processName);
android.ddm.DdmHandleAppName.setAppName(data.processName,
data.appInfo.packageName,
UserHandle.myUserId());
VMRuntime.setProcessPackageName(data.appInfo.packageName);
mDdmSyncStageUpdater.next(Stage.Named);

// Pass data directory path to ART. This is used for caching information and
// should be set before any application code is loaded.
VMRuntime.setProcessDataDirectory(data.appInfo.dataDir);

if (mProfiler.profileFd != null) {
mProfiler.startProfiling();
}

// If the app is Honeycomb MR1 or earlier, switch its AsyncTask
// implementation to use the pool executor. Normally, we use the
// serialized executor as the default. This has to happen in the
// main thread so the main looper is set right.
if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

// Let the util.*Array classes maintain "undefined" for apps targeting Pie or earlier.
UtilConfig.setThrowExceptionForUpperArrayOutOfBounds(
data.appInfo.targetSdkVersion >= Build.VERSION_CODES.Q);

Message.updateCheckRecycle(data.appInfo.targetSdkVersion);

// Supply the targetSdkVersion to the UI rendering module, which may
// need it in cases where it does not have access to the appInfo.
android.graphics.Compatibility.setTargetSdkVersion(data.appInfo.targetSdkVersion);

/*
* Before spawning a new process, reset the time zone to be the system time zone.
* This needs to be done because the system time zone could have changed after the
* the spawning of this process. Without doing this this process would have the incorrect
* system time zone.
*/
TimeZone.setDefault(null);

/*
* Set the LocaleList. This may change once we create the App Context.
*/
LocaleList.setDefault(data.config.getLocales());

if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
try {
Typeface.setSystemFontMap(data.mSerializedSystemFontMap);
} catch (IOException | ErrnoException e) {
Slog.e(TAG, "Failed to parse serialized system font map");
Typeface.loadPreinstalledSystemFontMap();
}
}

synchronized (mResourcesManager) {
/*
* Update the system configuration since its preloaded and might not
* reflect configuration changes. The configuration object passed
* in AppBindData can be safely assumed to be up to date
*/
mResourcesManager.applyConfigurationToResources(data.config, data.compatInfo);
mCurDefaultDisplayDpi = data.config.densityDpi;

// This calls mResourcesManager so keep it within the synchronized block.
mConfigurationController.applyCompatConfiguration();
}

final boolean isSdkSandbox = data.sdkSandboxClientAppPackage != null;
data.info = getPackageInfo(data.appInfo, mCompatibilityInfo, null /* baseLoader */,
false /* securityViolation */, true /* includeCode */,
false /* registerPackage */, isSdkSandbox);
if (isSdkSandbox) {
data.info.setSdkSandboxStorage(data.sdkSandboxClientAppVolumeUuid,
data.sdkSandboxClientAppPackage);
}

if (agent != null) {
handleAttachAgent(agent, data.info);
}

/**
* Switch this process to density compatibility mode if needed.
*/
if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
== 0) {
mDensityCompatMode = true;
Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
}
mConfigurationController.updateDefaultDensity(data.config.densityDpi);

// mCoreSettings is only updated from the main thread, while this function is only called
// from main thread as well, so no need to lock here.
final String use24HourSetting = mCoreSettings.getString(Settings.System.TIME_12_24);
Boolean is24Hr = null;
if (use24HourSetting != null) {
is24Hr = "24".equals(use24HourSetting) ? Boolean.TRUE : Boolean.FALSE;
}
// null : use locale default for 12/24 hour formatting,
// false : use 12 hour format,
// true : use 24 hour format.
DateFormat.set24HourTimePref(is24Hr);

updateDebugViewAttributeState();

StrictMode.initThreadDefaults(data.appInfo);
StrictMode.initVmDefaults(data.appInfo);

// Allow binder tracing, and application-generated systrace messages if we're profileable.
boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
boolean isAppProfileable = isAppDebuggable || data.appInfo.isProfileable();
Trace.setAppTracingAllowed(isAppProfileable);
if ((isAppProfileable || Build.IS_DEBUGGABLE) && data.enableBinderTracking) {
Binder.enableStackTracking();
}

// Initialize heap profiling.
if (isAppProfileable || Build.IS_DEBUGGABLE) {
nInitZygoteChildHeapProfiling();
}

// Allow renderer debugging features if we're debuggable.
HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
HardwareRenderer.setPackageName(data.appInfo.packageName);

// Pass the current context to HardwareRenderer
HardwareRenderer.setContextForInit(getSystemContext());
if (data.persistent) {
HardwareRenderer.setIsSystemOrPersistent();
}

// Instrumentation info affects the class loader, so load it before
// setting up the app context.
final InstrumentationInfo ii;
if (data.instrumentationName != null) {
ii = prepareInstrumentation(data);
} else {
ii = null;
}

final IActivityManager mgr = ActivityManager.getService();
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
mConfigurationController.updateLocaleListFromAppContext(appContext);

// Initialize the default http proxy in this process.
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies");
try {
// In pre-boot mode (doing initial launch to collect password), not all system is up.
// This includes the connectivity service, so trying to obtain ConnectivityManager at
// that point would return null. Check whether the ConnectivityService is available, and
// avoid crashing with a NullPointerException if it is not.
final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
if (b != null) {
final ConnectivityManager cm =
appContext.getSystemService(ConnectivityManager.class);
Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}

if (!Process.isIsolated()) {
final int oldMask = StrictMode.allowThreadDiskWritesMask();
try {
setupGraphicsSupport(appContext);
} finally {
StrictMode.setThreadPolicyMask(oldMask);
}
} else {
HardwareRenderer.setIsolatedProcess(true);
}

// Install the Network Security Config Provider. This must happen before the application
// code is loaded to prevent issues with instances of TLS objects being created before
// the provider is installed.
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "NetworkSecurityConfigProvider.install");
NetworkSecurityConfigProvider.install(appContext);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

// For backward compatibility, TrafficStats needs static access to the application context.
// But for isolated apps which cannot access network related services, service discovery
// is restricted. Hence, calling this would result in NPE.
if (!Process.isIsolated()) {
TrafficStats.init(appContext);
}

// Continue loading instrumentation.
if (ii != null) {
initInstrumentation(ii, data, appContext);
} else {
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);
}

if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
} else {
// Small heap, clamp to the current growth limit and let the heap release
// pages after the growth limit to the non growth limit capacity. b/18387825
dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
}

// Allow disk access during application and provider setup. This could
// block processing ordered broadcasts, but later processing would
// probably end up doing the same disk access.
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();

if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
mDdmSyncStageUpdater.next(Stage.Debugger);
if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
waitForDebugger(data);
} else if (data.debugMode == ApplicationThreadConstants.DEBUG_SUSPEND) {
suspendAllAndSendVmStart(data);
}
// Nothing special to do in case of DEBUG_ON.
}
mDdmSyncStageUpdater.next(Stage.Running);

long timestampApplicationOnCreateNs = 0;
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);

// Propagate autofill compat state
app.setAutofillOptions(data.autofillOptions);

// Propagate Content Capture options
app.setContentCaptureOptions(data.contentCaptureOptions);
sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName);

mInitialApplication = app;
final boolean updateHttpProxy;
synchronized (this) {
updateHttpProxy = mUpdateHttpProxyOnBind;
// This synchronized block ensures that any subsequent call to updateHttpProxy()
// will see a non-null mInitialApplication.
}
if (updateHttpProxy) {
ActivityThread.updateHttpProxy(app);
}

// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
}
}

// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
timestampApplicationOnCreateNs = SystemClock.uptimeNanos();
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
timestampApplicationOnCreateNs = 0;
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
} finally {
// If the app targets < O-MR1, or doesn't change the thread policy
// during startup, clobber the policy to maintain behavior of b/36951662
if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
|| StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
StrictMode.setThreadPolicy(savedPolicy);
}
}

// Preload fonts resources
FontsContract.setApplicationContextForResources(appContext);
if (!Process.isIsolated()) {
try {
final ApplicationInfo info =
getPackageManager().getApplicationInfo(
data.appInfo.packageName,
PackageManager.GET_META_DATA /*flags*/,
UserHandle.myUserId());
if (info.metaData != null) {
final int preloadedFontsResource = info.metaData.getInt(
ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
if (preloadedFontsResource != 0) {
data.info.getResources().preloadFonts(preloadedFontsResource);
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

try {
mgr.finishAttachApplication(mStartSeq, timestampApplicationOnCreateNs);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}

// Set binder transaction callback after finishing bindApplication
Binder.setTransactionCallback(new IBinderCallback() {
@Override
public void onTransactionError(int pid, int code, int flags, int err) {
final long now = SystemClock.uptimeMillis();
if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE) {
Slog.d(TAG, "Too many transaction errors, throttling freezer binder callback.");
return;
}
mBinderCallbackLast = now;
try {
mgr.frozenBinderTransactionDetected(pid, code, flags, err);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
});

// Register callback to report native memory metrics post GC cleanup
// Note: we do not report memory metrics of isolated processes unless
// their native allocations become more significant
if (!Process.isIsolated() && Flags.reportPostgcMemoryMetrics() &&
com.android.libcore.readonly.Flags.postCleanupApis()) {
VMRuntime.addPostCleanupCallback(new Runnable() {
@Override public void run() {
MetricsLoggerWrapper.logPostGcMemorySnapshot();
}
});
}
}

再来查看一下参数类的信息

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
static final class AppBindData {
@UnsupportedAppUsage
AppBindData() {
}
@UnsupportedAppUsage
LoadedApk info;
@UnsupportedAppUsage
String processName;
@UnsupportedAppUsage
ApplicationInfo appInfo;
String sdkSandboxClientAppVolumeUuid;
String sdkSandboxClientAppPackage;
boolean isSdkInSandbox;
@UnsupportedAppUsage
List<ProviderInfo> providers;
ComponentName instrumentationName;
@UnsupportedAppUsage
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
IUiAutomationConnection instrumentationUiAutomationConnection;
int debugMode;
boolean enableBinderTracking;
boolean trackAllocation;
@UnsupportedAppUsage
boolean restrictedBackupMode;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
boolean persistent;
Configuration config;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
CompatibilityInfo compatInfo;
String buildSerial;

/** Initial values for {@link Profiler}. */
ProfilerInfo initProfilerInfo;

AutofillOptions autofillOptions;

/**
* Content capture options for the application - when null, it means ContentCapture is not
* enabled for the package.
*/
@Nullable
ContentCaptureOptions contentCaptureOptions;

long[] disabledCompatChanges;
long[] mLoggableCompatChanges;

SharedMemory mSerializedSystemFontMap;

long startRequestedElapsedTime;
long startRequestedUptime;

@Override
public String toString() {
return "AppBindData{appInfo=" + appInfo + "}";
}
}

可以看到有app的包名信息等 接下来寻找makeapplication 低版本交这个 高版本使用的是makeApplicationInner 也就是

1
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);

其中的info就是loaddapk类 查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
return makeApplicationInner(forceDefaultAppClass, instrumentation,
/* allowDuplicateInstances= */ true);
}

/**
* This is for all the (internal) callers, for which we do return the cached instance.
*/
public Application makeApplicationInner(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
return makeApplicationInner(forceDefaultAppClass, instrumentation,
/* allowDuplicateInstances= */ false);
}

private Application makeApplicationInner(boolean forceDefaultAppClass,
Instrumentation instrumentation, boolean allowDuplicateInstances) {
if (mApplication != null) {
return mApplication;
}

其中的mapplication就是

1
private Application mApplication;

当然由于我们mapplication肯定就是空的

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
private Application makeApplicationInner(boolean forceDefaultAppClass,
Instrumentation instrumentation, boolean allowDuplicateInstances) {
if (mApplication != null) {
return mApplication;
}


if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
}

try {
synchronized (sApplications) {
final Application cached = sApplications.get(mPackageName);
if (cached != null) {
// Looks like this is always happening for the system server, because
// the LoadedApk created in systemMain() -> attach() isn't cached properly?
if (!"android".equals(mPackageName)) {
Slog.wtfStack(TAG, "App instance already created for package="
+ mPackageName + " instance=" + cached);
}
if (!allowDuplicateInstances) {
mApplication = cached;
return cached;
}
// Some apps intentionally call makeApplication() to create a new Application
// instance... Sigh...
}
}

Application app = null;

final String myProcessName = Process.myProcessName();
String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
myProcessName);
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}

try {
final java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}

// Rewrite the R 'constants' for all library apks.
SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(
false, false);
for (int i = 0, n = packageIdentifiers.size(); i < n; i++) {
final int id = packageIdentifiers.keyAt(i);
if (id == 0x01 || id == 0x7f) {
continue;
}

rewriteRValues(cl, packageIdentifiers.valueAt(i), id);
}

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);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ " package " + mPackageName + ": " + e.toString(), e);
}
}
mActivityThread.addApplication(app);
mApplication = app;
if (!allowDuplicateInstances) {
synchronized (sApplications) {
sApplications.put(mPackageName, app);
}
}

if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!instrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}

return app;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}

所以还会走下面的流程

image

1
2
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);

在这里的newApplication中使用了appclass的内容创建了一个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
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;
}

/**
* Perform instantiation of the process's {@link Application} object. The
* default implementation provides the normal system behavior.
*
* @param clazz The class used to create an Application object from.
* @param context The context to initialize the application with
*
* @return The newly instantiated Application object.
*/
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
1
2
3
4
5
public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
@NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Application) cl.loadClass(className).newInstance();
}

正常的是走上面的 在执行到instantiateApplication也就是classloader的时候其实我们写的类还是没有被初始化 只是触发了类的加载 我们编写的

1
2
3
static {
Log.e("static","now is static");
}

实际上是没有输出的 但是执行newInstance()以后类就会初始化 并且使用构造方法进行实例化 所以静态代码块先运行在无参数的构造方法前 当然了由于newInstance()没参数所以有参数的构造方法是不会执行的

image

1
2
2025-06-24 22:35:59.127 20940-20940 static                  com.grand.packer                     E  now is static
2025-06-24 22:35:59.127 20940-20940 myapplication com.grand.packer E now is myapplication.instence

接下来查看application的attch函数

1
2
3
4
/* package */ final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

可以看到其中含有一个attachBaseContext(context)的方法 这也就是我们log中attachBaseContext的输出位置 回到handleBindApplication中

image

再次往下就是oncreate了

image

1
mInstrumentation.callApplicationOnCreate(app);

查看

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

就是使用了oncreate方法 以上即是从源码的角度上进行的app运行流程的分析 从而可以分析出来壳的app会替换掉androidmanifest的入口 去解密dex 交换控制权执行主函数

加壳app运行流程

BootClassLoader加载系统核心库

PathClassLoader加载APP自身dex

进入APP自身组件开始执行
调用声明Application的attachBaseContext

调用声明Application的onCreate

image

未加固

image

加固后 360整体加固

image

加固 爱加密 代码抽取

image

image

整体加固

落地加载

dexclassloader

image

这个需要dex文件的路径 所以dex文件需要出现在文件系统中 现在较为少见

内存加载

直接加载内存中解密完成的dex内容 不释放文件 较为常见

解决方案

fart

在dex加载、解析过程中,找一个合适的时机,得到DexFile内存地址和大小,将解密状态的dex保存下来、还可以通过artMethod来得到DexFile

blackdex

mcookie脱壳

fdex2

原是xposed模块,低版本中存在通过class获取到dexfile对象使用getbytes方法获取到dex文件

frida-dexdump

内存搜索dex文件

抽取加固

运行时回填函数,回填以后不再抽取,可以延时dump

运行时回填函数,回填以后再抽取,主动调用dump

类加载器

类加载的时机

隐式加载:访问类的静态变量、为静态变量赋值、调用类的静态方法创建类的实例、某个类的子类,父类也会被加载

显式加载:使用Class.forName加载使用loadClass加载

类加载器

BootClassLoader:单例模式,用来加载系统类
BaseDexClassLoader:是PathClassLoader、DexClassLoader、
InMemor yDexClassLoader的父类,类加载的主要逻辑都是在BaseDexClassLoader完成的
PathClassLoader:是默认使用的类加载器,用于加载app自身的dex
DexClassLoader:用于实现插件化、热修复、dex加固等
nMemoryDexClassLoader:安卓8.0以后引入,用于内存加载dex

加固对hook的影响

普通app运行流程

BootClassLoader加载统核心库

PathClassLoader加载app自身dex mClassLoader

加固app运行流程

BootClassLoader加载系统核心库
PathClassLoader加载壳的dex mClassLoader
壳的dex/so加载原先app自身dex

加固app的pathclassloader加载的是壳的dex 存放在mclassloader中 此时去hook由于是壳dex使用的是dexclassloader去获取的原先的类 所以是获取不到的 但是安卓系统也可能找不到 所以要去对classloader进行一个修正

加固对ClassLoader的常见修正方式

插入ClassLoader

BootClassLoader
DexClassLoader
PathClassLoader mClassLoader

替换ClassLoader

BootClassLoader
PathClassLoader
DexClassLoader mClassLoader

art下的常见脱壳点

安卓5以后进行加入

dex的加载流程

通过mCook ie脱壳的
通过openCommen函数脱壳的
通过DexFile构造函数脱壳的
youpk:通过ClassLinker来得到DexFile

dex2oat的编译流程

通过修改dex2oat脱壳的

类的加载和初始化流程

DexHunter在defineClass进行类解析

函数执行过程中的脱壳点

FART:Execute整体脱壳
FART:ArtMethod::invoke函数中进行dump Codeltem
youpk:直接到了解释执行的函数中进行dump Codeltem

inmemorydexclassloader

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
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dalvik.system;

import libcore.util.NonNull;
import libcore.util.Nullable;
import java.nio.ByteBuffer;

/**
* A {@link ClassLoader} implementation that loads classes from a
* buffer containing a DEX file. This can be used to execute code that
* has not been written to the local file system.
*/
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
/**
* Create an in-memory DEX class loader with the given dex buffers.
*
* @param dexBuffers array of buffers containing DEX files between
* <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.
* @param librarySearchPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be {@code null}
* @param parent the parent class loader for delegation.
*/
public InMemoryDexClassLoader(@NonNull ByteBuffer @NonNull [] dexBuffers,
@Nullable String librarySearchPath, @Nullable ClassLoader parent) {
super(dexBuffers, librarySearchPath, parent);
}

/**
* Create an in-memory DEX class loader with the given dex buffers.
*
* @param dexBuffers array of buffers containing DEX files between
* <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.
* @param parent the parent class loader for delegation.
*/
public InMemoryDexClassLoader(@NonNull ByteBuffer @NonNull [] dexBuffers,
@Nullable ClassLoader parent) {
this(dexBuffers, null, parent);
}

/**
* Creates a new in-memory DEX class loader.
*
* @param dexBuffer buffer containing DEX file contents between
* <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.
* @param parent the parent class loader for delegation.
*/
public InMemoryDexClassLoader(@NonNull ByteBuffer dexBuffer, @Nullable ClassLoader parent) {
this(new ByteBuffer[] { dexBuffer }, parent);
}
}

双亲委派的体现 使用父类尝试加载

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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dalvik.system;

import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;

import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import libcore.util.NonNull;
import libcore.util.Nullable;
import sun.misc.CompoundEnumeration;

/**
* Base class for common functionality between various dex-based
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {

/**
* Hook for customizing how dex files loads are reported.
*
* This enables the framework to monitor the use of dex files. The
* goal is to simplify the mechanism for optimizing foreign dex files and
* enable further optimizations of secondary dex files.
*
* The reporting happens only when new instances of BaseDexClassLoader
* are constructed and will be active only after this field is set with
* {@link BaseDexClassLoader#setReporter}.
*/
/* @NonNull */ private static volatile Reporter reporter = null;

@UnsupportedAppUsage
private final DexPathList pathList;

/**
* Array of ClassLoaders that can be used to load classes and resources that the code in
* {@code pathList} may depend on. This is used to implement Android's
* <a href=https://developer.android.com/guide/topics/manifest/uses-library-element>
* shared libraries</a> feature.
* <p>The shared library loaders are always checked before the {@code pathList} when looking
* up classes and resources.
*
* <p>{@code null} if the class loader has no shared library.
*
* @hide
*/
protected final ClassLoader[] sharedLibraryLoaders;

/**
* Array of ClassLoaders identical to {@code sharedLibraryLoaders} except that these library
* loaders are always checked after the {@code pathList} when looking up classes and resources.
*
* The placement of a library into this group is done by the OEM and cannot be configured by
* an App.
*
* <p>{@code null} if the class loader has no shared library.
*
* @hide
*/
protected final ClassLoader[] sharedLibraryLoadersAfter;

/**
* Constructs an instance.
* Note that all the *.jar and *.apk files from {@code dexPath} might be
* first extracted in-memory before the code is loaded. This can be avoided
* by passing raw dex files (*.dex) in the {@code dexPath}.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android.
* @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
* @param librarySearchPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, librarySearchPath, parent, null, null, false);
}

/**
* @hide
*/
@UnsupportedAppUsage
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent, boolean isTrusted) {
this(dexPath, librarySearchPath, parent, null, null, isTrusted);
}

/**
* @hide
*/
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] libraries) {
this(dexPath, librarySearchPath, parent, libraries, null, false);
}

/**
* @hide
*/
public BaseDexClassLoader(String dexPath, String librarySearchPath,
ClassLoader parent, ClassLoader[] libraries, ClassLoader[] librariesAfter) {
this(dexPath, librarySearchPath, parent, libraries, librariesAfter, false);
}


/**
* BaseDexClassLoader implements the Android
* <a href=https://developer.android.com/guide/topics/manifest/uses-library-element>
* shared libraries</a> feature by changing the typical parent delegation mechanism
* of class loaders.
* <p> Each shared library is associated with its own class loader, which is added to a list of
* class loaders this BaseDexClassLoader tries to load from in order, immediately checking
* after the parent.
* The shared library loaders are always checked before the {@code pathList} when looking
* up classes and resources.
* <p>
* The shared library loaders defined in sharedLibraryLoadersAfter are always checked
* <b>after</b> the {@code pathList}
*
* @hide
*/
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
ClassLoader[] sharedLibraryLoadersAfter,
boolean isTrusted) {
super(parent);
// Setup shared libraries before creating the path list. ART relies on the class loader
// hierarchy being finalized before loading dex files.
this.sharedLibraryLoaders = sharedLibraryLoaders == null
? null
: Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

this.sharedLibraryLoadersAfter = sharedLibraryLoadersAfter == null
? null
: Arrays.copyOf(sharedLibraryLoadersAfter, sharedLibraryLoadersAfter.length);
// Run background verification after having set 'pathList'.
this.pathList.maybeRunBackgroundVerification(this);

reportClassLoaderChain();
}

/**
* Reports the current class loader chain to the registered {@code reporter}.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public void reportClassLoaderChain() {
if (reporter == null) {
return;
}

String[] classPathAndClassLoaderContexts = computeClassLoaderContextsNative();
if (classPathAndClassLoaderContexts.length == 0) {
return;
}
Map<String, String> dexFileMapping =
new HashMap<>(classPathAndClassLoaderContexts.length / 2);
for (int i = 0; i < classPathAndClassLoaderContexts.length; i += 2) {
dexFileMapping.put(classPathAndClassLoaderContexts[i],
classPathAndClassLoaderContexts[i + 1]);
}
reporter.report(Collections.unmodifiableMap(dexFileMapping));
}

/**
* Computes the classloader contexts for each classpath entry in {@code pathList.getDexPaths()}.
*
* Note that this method is not thread safe, i.e. it is the responsibility of the caller to
* ensure that {@code pathList.getDexPaths()} is not modified concurrently with this method
* being called.
*
* @return A non-null array of non-null strings of length
* {@code 2 * pathList.getDexPaths().size()}. Every even index (0 is even here) is a dex file
* path and every odd entry is the class loader context used to load the previously listed dex
* file. E.g. a result might be {@code { "foo.dex", "PCL[]", "bar.dex", "PCL[foo.dex]" } }.
*/
private native String[] computeClassLoaderContextsNative();

/**
* Constructs an instance.
*
* dexFile must be an in-memory representation of a full dexFile.
*
* @param dexFiles the array of in-memory dex files containing classes.
* @param librarySearchPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be {@code null}
* @param parent the parent class loader
*
* @hide
*/
public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) {
super(parent);
this.sharedLibraryLoaders = null;
this.sharedLibraryLoadersAfter = null;
this.pathList = new DexPathList(this, librarySearchPath);
this.pathList.initByteBufferDexPath(dexFiles);
// Run background verification after having set 'pathList'.
this.pathList.maybeRunBackgroundVerification(this);
}

@Override
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>();
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) {
}
}
}
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;
}

/**
* Adds a new dex path to path list.
*
* @param dexPath dex path to add to path list
*
* @hide
*/
@UnsupportedAppUsage
@SystemApi(client = MODULE_LIBRARIES)
public void addDexPath(@Nullable String dexPath) {
addDexPath(dexPath, false /*isTrusted*/);
}

/**
* @hide
*/
@UnsupportedAppUsage
public void addDexPath(String dexPath, boolean isTrusted) {
pathList.addDexPath(dexPath, null /*optimizedDirectory*/, isTrusted);
}

/**
* Adds additional native paths for consideration in subsequent calls to
* {@link #findLibrary(String)}.
*
* @param libPaths collection of paths to be added to path list
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public void addNativePath(@NonNull Collection<String> libPaths) {
pathList.addNativePath(libPaths);
}

@Override
protected URL findResource(String name) {
if (sharedLibraryLoaders != null) {
for (ClassLoader loader : sharedLibraryLoaders) {
URL url = loader.getResource(name);
if (url != null) {
return url;
}
}
}
URL url = pathList.findResource(name);
if (url != null) {
return url;
}
if (sharedLibraryLoadersAfter != null) {
for (ClassLoader loader : sharedLibraryLoadersAfter) {
URL url2 = loader.getResource(name);
if (url2 != null) {
return url2;
}
}
}
return null;
}

@Override
protected Enumeration<URL> findResources(String name) {
Enumeration<URL> myResources = pathList.findResources(name);
if (sharedLibraryLoaders == null && sharedLibraryLoadersAfter == null) {
return myResources;
}

int sharedLibraryLoadersCount =
(sharedLibraryLoaders != null) ? sharedLibraryLoaders.length : 0;
int sharedLibraryLoadersAfterCount =
(sharedLibraryLoadersAfter != null) ? sharedLibraryLoadersAfter.length : 0;

Enumeration<URL>[] tmp =
(Enumeration<URL>[]) new Enumeration<?>[sharedLibraryLoadersCount +
sharedLibraryLoadersAfterCount
+ 1];
// First add sharedLibrary resources.
// This will add duplicate resources if a shared library is loaded twice, but that's ok
// as we don't guarantee uniqueness.
int i = 0;
for (; i < sharedLibraryLoadersCount; i++) {
try {
tmp[i] = sharedLibraryLoaders[i].getResources(name);
} catch (IOException e) {
// Ignore.
}
}
// Then add resource from this dex path.
tmp[i++] = myResources;

// Finally add resources from shared libraries that are to be loaded after.
for (int j = 0; j < sharedLibraryLoadersAfterCount; i++, j++) {
try {
tmp[i] = sharedLibraryLoadersAfter[j].getResources(name);
} catch (IOException e) {
// Ignore.
}
}
return new CompoundEnumeration<>(tmp);
}

@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}

/**
* Returns package information for the given package.
* Unfortunately, instances of this class don't really have this
* information, and as a non-secure {@code ClassLoader}, it isn't
* even required to, according to the spec. Yet, we want to
* provide it, in order to make all those hopeful callers of
* {@code myClass.getPackage().getName()} happy. Thus we construct
* a {@code Package} object the first time it is being requested
* and fill most of the fields with fake values. The {@code
* Package} object is then put into the {@code ClassLoader}'s
* package cache, so we see the same one next time. We don't
* create {@code Package} objects for {@code null} arguments or
* for the default package.
*
* <p>There is a limited chance that we end up with multiple
* {@code Package} objects representing the same package: It can
* happen when when a package is scattered across different JAR
* files which were loaded by different {@code ClassLoader}
* instances. This is rather unlikely, and given that this whole
* thing is more or less a workaround, probably not worth the
* effort to address.
*
* @param name the name of the class
* @return the package information for the class, or {@code null}
* if there is no package information available for it
*
* @deprecated See {@link ClassLoader#getPackage(String)}
*/
@Deprecated
@Override
protected synchronized Package getPackage(String name) {
if (name != null && !name.isEmpty()) {
Package pack = super.getPackage(name);

if (pack == null) {
pack = definePackage(name, "Unknown", "0.0", "Unknown",
"Unknown", "0.0", "Unknown", null);
}

return pack;
}

return null;
}

/**
* Returns colon-separated set of directories where libraries should be
* searched for first, before the standard set of directories.
*
* @return colon-separated set of search directories
*
* @hide
*/
@UnsupportedAppUsage
@SystemApi(client = MODULE_LIBRARIES)
public @NonNull String getLdLibraryPath() {
StringBuilder result = new StringBuilder();
for (File directory : pathList.getNativeLibraryDirectories()) {
if (result.length() > 0) {
result.append(':');
}
result.append(directory);
}

return result.toString();
}

@Override public String toString() {
return getClass().getName() + "[" + pathList + "]";
}

/**
* Sets the reporter for dex load notifications.
* Once set, all new instances of BaseDexClassLoader will report upon
* constructions the loaded dex files.
*
* @param newReporter the new Reporter. Setting {@code null} will cancel reporting.
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static void setReporter(@Nullable Reporter newReporter) {
reporter = newReporter;
}

/**
* @hide
*/
public static Reporter getReporter() {
return reporter;
}

/**
* Reports the construction of a {@link BaseDexClassLoader} and provides opaque
* information about the class loader chain.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public interface Reporter {
/**
* Reports the construction of a BaseDexClassLoader and provides opaque information about
* the class loader chain. For example, if the childmost ClassLoader in the chain:
* {@quote BaseDexClassLoader { foo.dex } -> BaseDexClassLoader { base.apk }
* -> BootClassLoader } was just initialized then the load of {@code "foo.dex"} would be
* reported with a classLoaderContext of {@code "PCL[];PCL[base.apk]"}.
*
* @param contextsMap A map from dex file paths to the class loader context used to load
* each dex file.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
void report(@NonNull Map<String, String> contextsMap);
}
}

进入到basedexclassloader中 再次使用父类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
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dalvik.system;

import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
import android.system.StructStat;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import libcore.io.ClassPathURLStreamHandler;
import libcore.io.IoUtils;
import libcore.io.Libcore;

import static android.system.OsConstants.S_ISDIR;

/**
* A pair of lists of entries, associated with a {@code ClassLoader}.
* One of the lists is a dex/resource path &mdash; typically referred
* to as a "class path" &mdash; list, and the other names directories
* containing native code libraries. Class path entries may be any of:
* a {@code .jar} or {@code .zip} file containing an optional
* top-level {@code classes.dex} file as well as arbitrary resources,
* or a plain {@code .dex} file (with no possibility of associated
* resources).
*
* <p>This class also contains methods to use these lists to look up
* classes and resources.</p>
*
* @hide
*/
public final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String zipSeparator = "!/";

/** class definition context */
@UnsupportedAppUsage
private final ClassLoader definingContext;

/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
@UnsupportedAppUsage
private Element[] dexElements;

/** List of native library path elements. */
// Some applications rely on this field being an array or we'd use a final list here
@UnsupportedAppUsage
/* package visible for testing */ NativeLibraryElement[] nativeLibraryPathElements;

/** List of application native library directories. */
@UnsupportedAppUsage
private final List<File> nativeLibraryDirectories;

/** List of system native library directories. */
@UnsupportedAppUsage
private final List<File> systemNativeLibraryDirectories;

/**
* Exceptions thrown during creation of the dexElements list.
*/
@UnsupportedAppUsage
private IOException[] dexElementsSuppressedExceptions;

private List<File> getAllNativeLibraryDirectories() {
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
return allNativeLibraryDirectories;
}

/**
* Construct an instance.
*
* @param definingContext the context in which any as-yet unresolved
* classes should be defined
*
* @param dexFiles the bytebuffers containing the dex files that we should load classes from.
*/
public DexPathList(ClassLoader definingContext, String librarySearchPath) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}

this.definingContext = definingContext;
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
}

/**
* Constructs an instance.
*
* @param definingContext the context in which any as-yet unresolved
* classes should be defined
* @param dexPath list of dex/resource path elements, separated by
* {@code File.pathSeparator}
* @param librarySearchPath list of native library directory path elements,
* separated by {@code File.pathSeparator}
* @param optimizedDirectory directory where optimized {@code .dex} files
* should be found and written to, or {@code null} to use the default
* system directory for same
*/
@UnsupportedAppUsage
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);
}

DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}

if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}

if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}

if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}

this.definingContext = definingContext;

ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);

// Native libraries may exist in both the system and
// application library paths, and we use this search order:
//
// 1. This class loader's library path for application libraries (librarySearchPath):
// 1.1. Native library directories
// 1.2. Path to libraries in apk-files
// 2. The VM's library path from the system property for system libraries
// also known as java.library.path
//
// This order was reversed prior to Gingerbread; see http://b/2933456.
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());

if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}

@Override public String toString() {
return "DexPathList[" + Arrays.toString(dexElements) +
",nativeLibraryDirectories=" +
Arrays.toString(getAllNativeLibraryDirectories().toArray()) + "]";
}

/**
* For BaseDexClassLoader.getLdLibraryPath.
*/
public List<File> getNativeLibraryDirectories() {
return nativeLibraryDirectories;
}

/**
* Adds a new path to this instance
* @param dexPath list of dex/resource path element, separated by
* {@code File.pathSeparator}
* @param optimizedDirectory directory where optimized {@code .dex} files
* should be found and written to, or {@code null} to use the default
* system directory for same
*/
@UnsupportedAppUsage
public void addDexPath(String dexPath, File optimizedDirectory) {
addDexPath(dexPath, optimizedDirectory, false);
}

public void addDexPath(String dexPath, File optimizedDirectory, boolean isTrusted) {
final List<IOException> suppressedExceptionList = new ArrayList<IOException>();
final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptionList, definingContext, isTrusted);

if (newElements != null && newElements.length > 0) {
dexElements = concat(Element.class, dexElements, newElements);
}

if (suppressedExceptionList.size() > 0) {
final IOException[] newSuppExceptions = suppressedExceptionList.toArray(
new IOException[suppressedExceptionList.size()]);
dexElementsSuppressedExceptions = dexElementsSuppressedExceptions != null
? concat(IOException.class, dexElementsSuppressedExceptions, newSuppExceptions)
: newSuppExceptions;
}
}

private static<T> T[] concat(Class<T> componentType, T[] inputA, T[] inputB) {
T[] output = (T[]) Array.newInstance(componentType, inputA.length + inputB.length);
System.arraycopy(inputA, 0, output, 0, inputA.length);
System.arraycopy(inputB, 0, output, inputA.length, inputB.length);
return output;
}

/**
* For InMemoryDexClassLoader. Initializes {@code dexElements} with dex files
* loaded from {@code dexFiles} buffers.
*
* @param dexFiles ByteBuffers containing raw dex data. Apks are not supported.
*/
/* package */ void initByteBufferDexPath(ByteBuffer[] dexFiles) {
if (dexFiles == null) {
throw new NullPointerException("dexFiles == null");
}
if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {
throw new NullPointerException("dexFiles contains a null Buffer!");
}
if (dexElements != null || dexElementsSuppressedExceptions != null) {
throw new IllegalStateException("Should only be called once");
}

final List<IOException> suppressedExceptions = new ArrayList<IOException>();

try {
Element[] null_elements = null;
DexFile dex = new DexFile(dexFiles, definingContext, null_elements);
dexElements = new Element[] { new Element(dex) };
} catch (IOException suppressed) {
System.logE("Unable to load dex files", suppressed);
suppressedExceptions.add(suppressed);
dexElements = new Element[0];
}

if (suppressedExceptions.size() > 0) {
dexElementsSuppressedExceptions = suppressedExceptions.toArray(
new IOException[suppressedExceptions.size()]);
}
}

/* package */ void maybeRunBackgroundVerification(ClassLoader loader) {
// Spawn background thread to verify all classes and cache verification results.
// Must be called *after* `this.dexElements` has been initialized and `loader.pathList`
// has been set for ART to find its classes (the fields are hardcoded in ART and dex
// files iterated over in the order of the array).
// We only spawn the background thread if the bytecode is not backed by an oat
// file, i.e. this is the first time this bytecode is being loaded and/or
// verification results have not been cached yet.
for (Element element : dexElements) {
if (element.dexFile != null && !element.dexFile.isBackedByOatFile()) {
element.dexFile.verifyInBackground(loader);
}
}
}

/**
* Splits the given dex path string into elements using the path
* separator, pruning out any elements that do not refer to existing
* and readable files.
*/
private static List<File> splitDexPath(String path) {
return splitPaths(path, false);
}

/**
* Splits the given path strings into file elements using the path
* separator, combining the results and filtering out elements
* that don't exist, aren't readable, or aren't either a regular
* file or a directory (as specified). Either string may be empty
* or {@code null}, in which case it is ignored. If both strings
* are empty or {@code null}, or all elements get pruned out, then
* this returns a zero-element list.
*/
@UnsupportedAppUsage
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;
}

// This method is not used anymore. Kept around only because there are many legacy users of it.
@SuppressWarnings("unused")
@UnsupportedAppUsage
public static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
List<IOException> suppressedExceptions) {
Element[] elements = new Element[dexFiles.length];
int elementPos = 0;
for (ByteBuffer buf : dexFiles) {
try {
DexFile dex = new DexFile(new ByteBuffer[] { buf }, /* classLoader */ null,
/* dexElements */ null);
elements[elementPos++] = new Element(dex);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + buf, suppressed);
suppressedExceptions.add(suppressed);
}
}
if (elementPos != elements.length) {
elements = Arrays.copyOf(elements, elementPos);
}
return elements;
}

/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
@UnsupportedAppUsage
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
}


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;
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);
}
} 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) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(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;
}

/**
* Constructs a {@code DexFile} instance, as appropriate depending on whether
* {@code optimizedDirectory} is {@code null}. An application image file may be associated with
* the {@code loader} if it is not null.
*/
@UnsupportedAppUsage
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}

/**
* Converts a dex/jar file path and an output directory to an
* output file path for an associated optimized dex file.
*/
private static String optimizedPathFor(File path,
File optimizedDirectory) {
/*
* Get the filename component of the path, and replace the
* suffix with ".dex" if that's not already the suffix.
*
* We don't want to use ".odex", because the build system uses
* that for files that are paired with resource-only jar
* files. If the VM can assume that there's no classes.dex in
* the matching jar, it doesn't need to open the jar to check
* for updated dependencies, providing a slight performance
* boost at startup. The use of ".dex" here matches the use on
* files in /data/dalvik-cache.
*/
String fileName = path.getName();
if (!fileName.endsWith(DEX_SUFFIX)) {
int lastDot = fileName.lastIndexOf(".");
if (lastDot < 0) {
fileName += DEX_SUFFIX;
} else {
StringBuilder sb = new StringBuilder(lastDot + 4);
sb.append(fileName, 0, lastDot);
sb.append(DEX_SUFFIX);
fileName = sb.toString();
}
}

File result = new File(optimizedDirectory, fileName);
return result.getPath();
}

/*
* TODO (dimitry): Revert after apps stops relying on the existence of this
* method (see http://b/21957414 and http://b/26317852 for details)
*/
@UnsupportedAppUsage
@SuppressWarnings("unused")
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}

/**
* Makes an array of directory/zip path elements for the native library search path, one per
* element of the given array.
*/
@UnsupportedAppUsage
private static NativeLibraryElement[] makePathElements(List<File> files) {
NativeLibraryElement[] elements = new NativeLibraryElement[files.size()];
int elementsPos = 0;
for (File file : files) {
String path = file.getPath();

if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
File zip = new File(split[0]);
String dir = split[1];
elements[elementsPos++] = new NativeLibraryElement(zip, dir);
} else if (file.isDirectory()) {
// We support directories for looking up native libraries.
elements[elementsPos++] = new NativeLibraryElement(file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}

/**
* Finds the named class in one of the dex files pointed at by
* this instance. This will find the one in the earliest listed
* path element. If the class is found but has not yet been
* defined, then this method will define it in the defining
* context that this instance was constructed with.
*
* @param name of class to find
* @param suppressed exceptions encountered whilst finding the class
* @return the named class or {@code null} if the class is not
* found in any of the dex files
*/
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}

if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}

/**
* Finds the named resource in one of the zip/jar files pointed at
* by this instance. This will find the one in the earliest listed
* path element.
*
* @return a URL to the named resource or {@code null} if the
* resource is not found in any of the zip/jar files
*/
public URL findResource(String name) {
for (Element element : dexElements) {
URL url = element.findResource(name);
if (url != null) {
return url;
}
}

return null;
}

/**
* Finds all the resources with the given name, returning an
* enumeration of them. If there are no resources with the given
* name, then this method returns an empty enumeration.
*/
public Enumeration<URL> findResources(String name) {
ArrayList<URL> result = new ArrayList<URL>();

for (Element element : dexElements) {
URL url = element.findResource(name);
if (url != null) {
result.add(url);
}
}

return Collections.enumeration(result);
}

/**
* Finds the named native code library on any of the library
* directories pointed at by this instance. This will find the
* one in the earliest listed directory, ignoring any that are not
* readable regular files.
*
* @return the complete path to the library or {@code null} if no
* library was found
*/
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);

for (NativeLibraryElement element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);

if (path != null) {
return path;
}
}

return null;
}

/**
* Returns the list of all individual dex files paths from the current list.
* The list will contain only file paths (i.e. no directories).
*/
/*package*/ List<String> getDexPaths() {
List<String> dexPaths = new ArrayList<String>();
for (Element e : dexElements) {
String dexPath = e.getDexPath();
if (dexPath != null) {
// Add the element to the list only if it is a file. A null dex path signals the
// element is a resource directory or an in-memory dex file.
dexPaths.add(dexPath);
}
}
return dexPaths;
}

/**
* Adds a collection of library paths from which to load native libraries. Paths can be absolute
* native library directories (i.e. /data/app/foo/lib/arm64) or apk references (i.e.
* /data/app/foo/base.apk!/lib/arm64).
*
* Note: This method will attempt to dedupe elements.
* Note: This method replaces the value of {@link #nativeLibraryPathElements}
*/
@UnsupportedAppUsage
public void addNativePath(Collection<String> libPaths) {
if (libPaths.isEmpty()) {
return;
}
List<File> libFiles = new ArrayList<>(libPaths.size());
for (String path : libPaths) {
libFiles.add(new File(path));
}
ArrayList<NativeLibraryElement> newPaths =
new ArrayList<>(nativeLibraryPathElements.length + libPaths.size());
newPaths.addAll(Arrays.asList(nativeLibraryPathElements));
for (NativeLibraryElement element : makePathElements(libFiles)) {
if (!newPaths.contains(element)) {
newPaths.add(element);
}
}
nativeLibraryPathElements = newPaths.toArray(new NativeLibraryElement[newPaths.size()]);
}

/**
* Element of the dex/resource path. Note: should be called DexElement, but apps reflect on
* this.
*/
/*package*/ static class Element {
/**
* A file denoting a zip file (in case of a resource jar or a dex jar), or a directory
* (only when dexFile is null).
*/
@UnsupportedAppUsage
private final File path;
/** Whether {@code path.isDirectory()}, or {@code null} if {@code path == null}. */
private final Boolean pathIsDirectory;

@UnsupportedAppUsage
private final DexFile dexFile;

private ClassPathURLStreamHandler urlHandler;
private boolean initialized;

/**
* Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath
* should be null), or a jar (in which case dexZipPath should denote the zip file).
*/
@UnsupportedAppUsage
public Element(DexFile dexFile, File dexZipPath) {
if (dexFile == null && dexZipPath == null) {
throw new NullPointerException("Either dexFile or path must be non-null");
}
this.dexFile = dexFile;
this.path = dexZipPath;
// Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString().
this.pathIsDirectory = (path == null) ? null : path.isDirectory();
}

public Element(DexFile dexFile) {
this(dexFile, null);
}

public Element(File path) {
this(null, path);
}

/**
* Constructor for a bit of backwards compatibility. Some apps use reflection into
* internal APIs. Warn, and emulate old behavior if we can. See b/33399341.
*
* @deprecated The Element class has been split. Use new Element constructors for
* classes and resources, and NativeLibraryElement for the library
* search path.
*/
@UnsupportedAppUsage
@Deprecated
public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
this(dir != null ? null : dexFile, dir != null ? dir : zip);
System.err.println("Warning: Using deprecated Element constructor. Do not use internal"
+ " APIs, this constructor will be removed in the future.");
if (dir != null && (zip != null || dexFile != null)) {
throw new IllegalArgumentException("Using dir and zip|dexFile no longer"
+ " supported.");
}
if (isDirectory && (zip != null || dexFile != null)) {
throw new IllegalArgumentException("Unsupported argument combination.");
}
}

/*
* Returns the dex path of this element or null if the element refers to a directory.
*/
private String getDexPath() {
if (path != null) {
return path.isDirectory() ? null : path.getAbsolutePath();
} else if (dexFile != null) {
// DexFile.getName() returns the path of the dex file.
return dexFile.getName();
}
return null;
}

@Override
public String toString() {
if (dexFile == null) {
return (pathIsDirectory ? "directory \"" : "zip file \"") + path + "\"";
} else if (path == null) {
return "dex file \"" + dexFile + "\"";
} else {
return "zip file \"" + path + "\"";
}
}

public synchronized void maybeInit() {
if (initialized) {
return;
}

if (path == null || pathIsDirectory) {
initialized = true;
return;
}

try {
// Disable zip path validation for loading APKs as it does not pose a risk of the
// zip path traversal vulnerability.
urlHandler = new ClassPathURLStreamHandler(path.getPath(),
/* enableZipPathValidator */ false);
} catch (IOException ioe) {
/*
* Note: ZipException (a subclass of IOException)
* might get thrown by the ZipFile constructor
* (e.g. if the file isn't actually a zip/jar
* file).
*/
System.logE("Unable to open zip file: " + path, ioe);
urlHandler = null;
}

// Mark this element as initialized only after we've successfully created
// the associated ClassPathURLStreamHandler. That way, we won't leave this
// element in an inconsistent state if an exception is thrown during initialization.
//
// See b/35633614.
initialized = true;
}

public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}

public URL findResource(String name) {
maybeInit();

if (urlHandler != null) {
return urlHandler.getEntryUrlOrNull(name);
}

// We support directories so we can run tests and/or legacy code
// that uses Class.getResource.
if (path != null && path.isDirectory()) {
File resourceFile = new File(path, name);
if (resourceFile.exists()) {
try {
return resourceFile.toURI().toURL();
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
}

return null;
}
}

/**
* Element of the native library path
*/
/*package*/ static class NativeLibraryElement {
/**
* A file denoting a directory or zip file.
*/
@UnsupportedAppUsage
private final File path;

/**
* If path denotes a zip file, this denotes a base path inside the zip.
*/
private final String zipDir;

private ClassPathURLStreamHandler urlHandler;
private boolean initialized;

@UnsupportedAppUsage
public NativeLibraryElement(File dir) {
this.path = dir;
this.zipDir = null;

// We should check whether path is a directory, but that is non-eliminatable overhead.
}

public NativeLibraryElement(File zip, String zipDir) {
this.path = zip;
this.zipDir = zipDir;

// Simple check that should be able to be eliminated by inlining. We should also
// check whether path is a file, but that is non-eliminatable overhead.
if (zipDir == null) {
throw new IllegalArgumentException();
}
}

@Override
public String toString() {
if (zipDir == null) {
return "directory \"" + path + "\"";
} else {
return "zip file \"" + path + "\"" +
(!zipDir.isEmpty() ? ", dir \"" + zipDir + "\"" : "");
}
}

public synchronized void maybeInit() {
if (initialized) {
return;
}

if (zipDir == null) {
initialized = true;
return;
}

try {
// Disable zip path validation for loading APKs as it does not pose a risk of the
// zip path traversal vulnerability.
urlHandler = new ClassPathURLStreamHandler(path.getPath(),
/* enableZipPathValidator */ false);
} catch (IOException ioe) {
/*
* Note: ZipException (a subclass of IOException)
* might get thrown by the ZipFile constructor
* (e.g. if the file isn't actually a zip/jar
* file).
*/
System.logE("Unable to open zip file: " + path, ioe);
urlHandler = null;
}

// Mark this element as initialized only after we've successfully created
// the associated ClassPathURLStreamHandler. That way, we won't leave this
// element in an inconsistent state if an exception is thrown during initialization.
//
// See b/35633614.
initialized = true;
}

public String findNativeLibrary(String name) {
maybeInit();

if (zipDir == null) {
String entryPath = new File(path, name).getPath();
if (IoUtils.canOpenReadOnly(entryPath)) {
return entryPath;
}
} else if (urlHandler != null) {
// Having a urlHandler means the element has a zip file.
// In this case Android supports loading the library iff
// it is stored in the zip uncompressed.
String entryName = zipDir + '/' + name;
if (urlHandler.isEntryStored(entryName)) {
return path.getPath() + zipSeparator + entryName;
}
}

return null;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NativeLibraryElement)) return false;
NativeLibraryElement that = (NativeLibraryElement) o;
return Objects.equals(path, that.path) &&
Objects.equals(zipDir, that.zipDir);
}

@Override
public int hashCode() {
return Objects.hash(path, zipDir);
}
}
}

这里是为了查看 在安卓7中dexfile存在getbyte方法可以直接得到对应的dex字节码 对应的也就是fdex2对应的脱壳手段

image

跟进dexfile中

1
2
3
4
5
6
7
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
mCookie = openDexFile(fileName, null, 0, loader, elements);
mInternalCookie = mCookie;
mFileName = fileName;
//System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
}

发现mCookie 也是一个脱壳点

image

继续跟进

image

这时候就是native的函数了

image

进入art进行查看 这里是进行了函数的注册

image

这里就是进行了类目和方法名的拼接 也就是我们常看见的so中的导出函数的形式 那么他正确的函数名为dexfile_openDexFileNative

image

在这里进行

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
static jobject DexFile_openInMemoryDexFilesNative(JNIEnv* env,
jclass,
jobjectArray buffers,
jobjectArray arrays,
jintArray jstarts,
jintArray jends,
jobject class_loader,
jobjectArray dex_elements) {
jsize buffers_length = env->GetArrayLength(buffers);
CHECK_EQ(buffers_length, env->GetArrayLength(arrays));
CHECK_EQ(buffers_length, env->GetArrayLength(jstarts));
CHECK_EQ(buffers_length, env->GetArrayLength(jends));

ScopedIntArrayAccessor starts(env, jstarts);
ScopedIntArrayAccessor ends(env, jends);

// Allocate memory for dex files and copy data from ByteBuffers.
std::vector<MemMap> dex_mem_maps;
dex_mem_maps.reserve(buffers_length);
for (jsize i = 0; i < buffers_length; ++i) {
jobject buffer = env->GetObjectArrayElement(buffers, i);
jbyteArray array = reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(arrays, i));
jint start = starts.Get(i);
jint end = ends.Get(i);

MemMap dex_data = AllocateDexMemoryMap(env, start, end);
if (!dex_data.IsValid()) {
DCHECK(Thread::Current()->IsExceptionPending());
return nullptr;
}

if (array == nullptr) {
// Direct ByteBuffer
uint8_t* base_address = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
if (base_address == nullptr) {
ScopedObjectAccess soa(env);
ThrowWrappedIOException("dexFileBuffer not direct");
return nullptr;
}
size_t length = static_cast<size_t>(end - start);
memcpy(dex_data.Begin(), base_address + start, length);
} else {
// ByteBuffer backed by a byte array
jbyte* destination = reinterpret_cast<jbyte*>(dex_data.Begin());
env->GetByteArrayRegion(array, start, end - start, destination);
}

dex_mem_maps.push_back(std::move(dex_data));
}

// Hand MemMaps over to OatFileManager to open the dex files and potentially
// create a backing OatFile instance from an anonymous vdex.
std::vector<std::string> error_msgs;
const OatFile* oat_file = nullptr;
std::vector<std::unique_ptr<const DexFile>> dex_files =
Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(std::move(dex_mem_maps),
class_loader,
dex_elements,
/*out*/ &oat_file,
/*out*/ &error_msgs);
return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}

返回含有dex信息的cookie CreateCookieFromOatFileManagerResult创建的

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
static jobject CreateCookieFromOatFileManagerResult(
JNIEnv* env,
std::vector<std::unique_ptr<const DexFile>>& dex_files,
const OatFile* oat_file,
const std::vector<std::string>& error_msgs) {
ClassLinker* linker = Runtime::Current()->GetClassLinker();
if (dex_files.empty()) {
ScopedObjectAccess soa(env);
CHECK(!error_msgs.empty());
// The most important message is at the end. So set up nesting by going forward, which will
// wrap the existing exception as a cause for the following one.
auto it = error_msgs.begin();
auto itEnd = error_msgs.end();
for ( ; it != itEnd; ++it) {
ThrowWrappedIOException("%s", it->c_str());
}
return nullptr;
}

jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
if (array == nullptr) {
ScopedObjectAccess soa(env);
for (auto& dex_file : dex_files) {
if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) {
dex_file.release(); // NOLINT
}
}
}
return array;
}

mcookie就含有dexfile的信息可以进行脱壳

接下来查看OpenDexFilesFromOat

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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "oat_file_manager.h"

#include <stdlib.h>
#include <sys/stat.h>

#include <memory>
#include <queue>
#include <vector>

#include "android-base/file.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "art_field-inl.h"
#include "base/bit_vector-inl.h"
#include "base/file_utils.h"
#include "base/logging.h" // For VLOG.
#include "base/mutex-inl.h"
#include "base/sdk_version.h"
#include "base/stl_util.h"
#include "base/systrace.h"
#include "class_linker.h"
#include "class_loader_context.h"
#include "dex/art_dex_file_loader.h"
#include "dex/dex_file-inl.h"
#include "dex/dex_file_loader.h"
#include "dex/dex_file_tracking_registrar.h"
#include "gc/scoped_gc_critical_section.h"
#include "gc/space/image_space.h"
#include "handle_scope-inl.h"
#include "jit/jit.h"
#include "jni/java_vm_ext.h"
#include "jni/jni_internal.h"
#include "mirror/class_loader.h"
#include "mirror/object-inl.h"
#include "oat_file.h"
#include "oat_file_assistant.h"
#include "obj_ptr-inl.h"
#include "runtime_image.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"
#include "thread_list.h"
#include "thread_pool.h"
#include "vdex_file.h"
#include "verifier/verifier_deps.h"
#include "well_known_classes.h"

namespace art HIDDEN {

using android::base::StringPrintf;

// If true, we attempt to load the application image if it exists.
static constexpr bool kEnableAppImage = true;

// If true, we attempt to load an app image generated by the runtime.
static const bool kEnableRuntimeAppImage = true;

#if defined(__ANDROID__)
static const char* kDisableAppImageKeyword = "_disable_art_image_";
#endif

const OatFile* OatFileManager::RegisterOatFile(std::unique_ptr<const OatFile> oat_file,
bool in_memory) {
// Use class_linker vlog to match the log for dex file registration.
VLOG(class_linker) << "Registered oat file " << oat_file->GetLocation();
PaletteNotifyOatFileLoaded(oat_file->GetLocation().c_str());

WriterMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
CHECK(in_memory ||
!only_use_system_oat_files_ ||
LocationIsTrusted(oat_file->GetLocation(), !Runtime::Current()->DenyArtApexDataFiles()) ||
!oat_file->IsExecutable())
<< "Registering a non /system oat file: " << oat_file->GetLocation() << " android-root="
<< GetAndroidRoot();
DCHECK(oat_file != nullptr);
if (kIsDebugBuild) {
CHECK(oat_files_.find(oat_file) == oat_files_.end());
for (const std::unique_ptr<const OatFile>& existing : oat_files_) {
CHECK_NE(oat_file.get(), existing.get()) << oat_file->GetLocation();
// Check that we don't have an oat file with the same address. Copies of the same oat file
// should be loaded at different addresses.
CHECK_NE(oat_file->Begin(), existing->Begin()) << "Oat file already mapped at that location";
}
}
const OatFile* ret = oat_file.get();
oat_files_.insert(std::move(oat_file));
return ret;
}

void OatFileManager::UnRegisterAndDeleteOatFile(const OatFile* oat_file) {
WriterMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
DCHECK(oat_file != nullptr);
std::unique_ptr<const OatFile> compare(oat_file);
auto it = oat_files_.find(compare);
CHECK(it != oat_files_.end());
oat_files_.erase(it);
compare.release(); // NOLINT b/117926937
}

const OatFile* OatFileManager::FindOpenedOatFileFromDexLocation(
const std::string& dex_base_location) const {
ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) {
const std::vector<const OatDexFile*>& oat_dex_files = oat_file->GetOatDexFiles();
for (const OatDexFile* oat_dex_file : oat_dex_files) {
if (DexFileLoader::GetBaseLocation(oat_dex_file->GetDexFileLocation()) == dex_base_location) {
return oat_file.get();
}
}
}
return nullptr;
}

const OatFile* OatFileManager::FindOpenedOatFileFromOatLocation(const std::string& oat_location)
const {
ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
return FindOpenedOatFileFromOatLocationLocked(oat_location);
}

const OatFile* OatFileManager::FindOpenedOatFileFromOatLocationLocked(
const std::string& oat_location) const {
for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) {
if (oat_file->GetLocation() == oat_location) {
return oat_file.get();
}
}
return nullptr;
}

std::vector<const OatFile*> OatFileManager::GetBootOatFiles() const {
std::vector<gc::space::ImageSpace*> image_spaces =
Runtime::Current()->GetHeap()->GetBootImageSpaces();
std::vector<const OatFile*> oat_files;
oat_files.reserve(image_spaces.size());
for (gc::space::ImageSpace* image_space : image_spaces) {
oat_files.push_back(image_space->GetOatFile());
}
return oat_files;
}

OatFileManager::OatFileManager()
: only_use_system_oat_files_(false) {}

OatFileManager::~OatFileManager() {
// Explicitly clear oat_files_ since the OatFile destructor calls back into OatFileManager for
// UnRegisterOatFileLocation.
oat_files_.clear();
}

std::vector<const OatFile*> OatFileManager::RegisterImageOatFiles(
const std::vector<gc::space::ImageSpace*>& spaces) {
std::vector<const OatFile*> oat_files;
oat_files.reserve(spaces.size());
for (gc::space::ImageSpace* space : spaces) {
// The oat file was generated in memory if the image space has a profile.
bool in_memory = !space->GetProfileFiles().empty();
oat_files.push_back(RegisterOatFile(space->ReleaseOatFile(), in_memory));
}
return oat_files;
}

bool OatFileManager::ShouldLoadAppImage() const {
Runtime* const runtime = Runtime::Current();
if (!kEnableAppImage || runtime->IsJavaDebuggableAtInit()) {
return false;
}

#if defined(__ANDROID__)
const char* process_name = getprogname();
// Some processes would rather take the runtime impact in the interest of memory (b/292210260)
if (process_name != nullptr && strstr(process_name, kDisableAppImageKeyword) != nullptr) {
LOG(INFO) << "Skipping app image load for " << process_name;
return false;
}
#endif

return true;
}

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) {
ScopedTrace trace(StringPrintf("%s(%s)", __FUNCTION__, dex_location));
CHECK(dex_location != nullptr);
CHECK(error_msgs != nullptr);

// Verify we aren't holding the mutator lock, which could starve GC when
// hitting the disk.
Thread* const self = Thread::Current();
Locks::mutator_lock_->AssertNotHeld(self);
Runtime* const runtime = Runtime::Current();

std::vector<std::unique_ptr<const DexFile>> dex_files;
std::unique_ptr<ClassLoaderContext> context(
ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements));

// If the class_loader is null there's not much we can do. This happens if a dex files is loaded
// directly with DexFile APIs instead of using class loaders.
if (class_loader == nullptr) {
LOG(WARNING) << "Opening an oat file without a class loader. "
<< "Are you using the deprecated DexFile APIs?";
} else if (context != nullptr) {
auto oat_file_assistant = std::make_unique<OatFileAssistant>(dex_location,
kRuntimeQuickCodeISA,
context.get(),
runtime->GetOatFilesExecutable(),
only_use_system_oat_files_);

// Get the current optimization status for trace debugging.
// Implementation detail note: GetOptimizationStatus will select the same
// oat file as GetBestOatFile used below, and in doing so it already pre-populates
// some OatFileAssistant internal fields.
std::string odex_location;
std::string compilation_filter;
std::string compilation_reason;
std::string odex_status;
OatFileAssistant::Location ignored_location;
oat_file_assistant->GetOptimizationStatus(
&odex_location, &compilation_filter, &compilation_reason, &odex_status, &ignored_location);

ScopedTrace odex_loading(StringPrintf(
"location=%s status=%s filter=%s reason=%s",
odex_location.c_str(),
odex_status.c_str(),
compilation_filter.c_str(),
compilation_reason.c_str()));

const bool has_registered_app_info = Runtime::Current()->GetAppInfo()->HasRegisteredAppInfo();
const AppInfo::CodeType code_type =
Runtime::Current()->GetAppInfo()->GetRegisteredCodeType(dex_location);
// We only want to madvise primary/split dex artifacts as a startup optimization. However,
// as the code_type for those artifacts may not be set until the initial app info registration,
// we conservatively madvise everything until the app info registration is complete.
const bool should_madvise = !has_registered_app_info ||
code_type == AppInfo::CodeType::kPrimaryApk ||
code_type == AppInfo::CodeType::kSplitApk;

// Proceed with oat file loading.
std::unique_ptr<const OatFile> oat_file(oat_file_assistant->GetBestOatFile().release());
VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()="
<< (oat_file != nullptr ? oat_file->GetLocation() : "")
<< " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")";

CHECK(oat_file == nullptr || odex_location == oat_file->GetLocation())
<< "OatFileAssistant non-determinism in choosing best oat files. "
<< "optimization-status-location=" << odex_location
<< " best_oat_file-location=" << oat_file->GetLocation();

if (oat_file != nullptr) {
bool compilation_enabled =
CompilerFilter::IsAotCompilationEnabled(oat_file->GetCompilerFilter());
// Load the dex files from the oat file.
bool added_image_space = false;
if (should_madvise) {
VLOG(oat) << "Madvising oat file: " << oat_file->GetLocation();
size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex();
Runtime::MadviseFileForRange(madvise_size_limit,
oat_file->Size(),
oat_file->Begin(),
oat_file->End(),
oat_file->GetLocation());
}

ScopedTrace app_image_timing("AppImage:Loading");

// We need to throw away the image space if we are debuggable but the oat-file source of the
// image is not otherwise we might get classes with inlined methods or other such things.
std::unique_ptr<gc::space::ImageSpace> image_space;
if (ShouldLoadAppImage()) {
if (oat_file->IsExecutable()) {
// App images generated by the compiler can only be used if the oat file
// is executable.
image_space = oat_file_assistant->OpenImageSpace(oat_file.get());
}
// Load the runtime image. This logic must be aligned with the one that determines when to
// keep runtime images in `ArtManagerLocal.cleanup` in
// `art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java`.
if (kEnableRuntimeAppImage && image_space == nullptr && !compilation_enabled) {
std::string art_file = RuntimeImage::GetRuntimeImagePath(dex_location);
std::string error_msg;
image_space = gc::space::ImageSpace::CreateFromAppImage(
art_file.c_str(), oat_file.get(), &error_msg);
if (image_space == nullptr) {
(OS::FileExists(art_file.c_str()) ? LOG_STREAM(INFO) : VLOG_STREAM(image))
<< "Could not load runtime generated app image: " << error_msg;
}
}
}
if (image_space != nullptr) {
ScopedObjectAccess soa(self);
StackHandleScope<1> hs(self);
Handle<mirror::ClassLoader> h_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
// Can not load app image without class loader.
if (h_loader != nullptr) {
oat_file->SetAppImageBegin(image_space->Begin());
std::string temp_error_msg;
// Add image space has a race condition since other threads could be reading from the
// spaces array.
{
ScopedThreadSuspension sts(self, ThreadState::kSuspended);
gc::ScopedGCCriticalSection gcs(self,
gc::kGcCauseAddRemoveAppImageSpace,
gc::kCollectorTypeAddRemoveAppImageSpace);
ScopedSuspendAll ssa("Add image space");
runtime->GetHeap()->AddSpace(image_space.get());
}
{
ScopedTrace image_space_timing("Adding image space");
gc::space::ImageSpace* space_ptr = image_space.get();
added_image_space = runtime->GetClassLinker()->AddImageSpaces(
ArrayRef<gc::space::ImageSpace*>(&space_ptr, /*size=*/1),
h_loader,
context.get(),
/*out*/ &dex_files,
/*out*/ &temp_error_msg);
}
if (added_image_space) {
// Successfully added image space to heap, release the map so that it does not get
// freed.
image_space.release(); // NOLINT b/117926937

// Register for tracking.
for (const auto& dex_file : dex_files) {
dex::tracking::RegisterDexFile(dex_file.get());
}
} else {
LOG(INFO) << "Failed to add image file: " << temp_error_msg;
oat_file->SetAppImageBegin(nullptr);
dex_files.clear();
{
ScopedThreadSuspension sts(self, ThreadState::kSuspended);
gc::ScopedGCCriticalSection gcs(self,
gc::kGcCauseAddRemoveAppImageSpace,
gc::kCollectorTypeAddRemoveAppImageSpace);
ScopedSuspendAll ssa("Remove image space");
runtime->GetHeap()->RemoveSpace(image_space.get());
}
// Non-fatal, don't update error_msg.
}
}
}
if (!added_image_space) {
DCHECK(dex_files.empty());

if (oat_file->RequiresImage()) {
LOG(WARNING) << "Loading "
<< oat_file->GetLocation()
<< " non-executable as it requires an image which we failed to load";
// file as non-executable.
auto nonexecutable_oat_file_assistant =
std::make_unique<OatFileAssistant>(dex_location,
kRuntimeQuickCodeISA,
context.get(),
/*load_executable=*/false,
only_use_system_oat_files_);
oat_file.reset(nonexecutable_oat_file_assistant->GetBestOatFile().release());

// The file could be deleted concurrently (for example background
// dexopt, or secondary oat file being deleted by the app).
if (oat_file == nullptr) {
LOG(WARNING) << "Failed to reload oat file non-executable " << dex_location;
}
}

if (oat_file != nullptr) {
dex_files = oat_file_assistant->LoadDexFiles(*oat_file.get(), dex_location);

// Register for tracking.
for (const auto& dex_file : dex_files) {
dex::tracking::RegisterDexFile(dex_file.get());
}
}
}
if (dex_files.empty()) {
ScopedTrace failed_to_open_dex_files("FailedToOpenDexFilesFromOat");
error_msgs->push_back("Failed to open dex files from " + odex_location);
} else if (should_madvise) {
size_t madvise_size_limit = Runtime::Current()->GetMadviseWillNeedTotalDexSize();
for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
// Prefetch the dex file based on vdex size limit (name should
// have been dex size limit).
VLOG(oat) << "Madvising dex file: " << dex_file->GetLocation();
Runtime::MadviseFileForRange(madvise_size_limit,
dex_file->Size(),
dex_file->Begin(),
dex_file->Begin() + dex_file->Size(),
dex_file->GetLocation());
if (dex_file->Size() >= madvise_size_limit) {
break;
}
madvise_size_limit -= dex_file->Size();
}
}

if (oat_file != nullptr) {
VLOG(class_linker) << "Registering " << oat_file->GetLocation();
*out_oat_file = RegisterOatFile(std::move(oat_file));
}
} else {
// oat_file == nullptr
// Verify if any of the dex files being loaded is already in the class path.
// If so, report an error with the current stack trace.
// Most likely the developer didn't intend to do this because it will waste
// performance and memory.
if (oat_file_assistant->GetBestStatus() == OatFileAssistant::kOatContextOutOfDate) {
std::set<const DexFile*> already_exists_in_classpath =
context->CheckForDuplicateDexFiles(MakeNonOwningPointerVector(dex_files));
if (!already_exists_in_classpath.empty()) {
ScopedTrace duplicate_dex_files("DuplicateDexFilesInContext");
auto duplicate_it = already_exists_in_classpath.begin();
std::string duplicates = (*duplicate_it)->GetLocation();
for (duplicate_it++ ; duplicate_it != already_exists_in_classpath.end(); duplicate_it++) {
duplicates += "," + (*duplicate_it)->GetLocation();
}

std::ostringstream out;
out << "Trying to load dex files which is already loaded in the same ClassLoader "
<< "hierarchy.\n"
<< "This is a strong indication of bad ClassLoader construct which leads to poor "
<< "performance and wastes memory.\n"
<< "The list of duplicate dex files is: " << duplicates << "\n"
<< "The current class loader context is: "
<< context->EncodeContextForOatFile("") << "\n"
<< "Java stack trace:\n";

{
ScopedObjectAccess soa(self);
self->DumpJavaStack(out);
}

// We log this as an ERROR to stress the fact that this is most likely unintended.
// Note that ART cannot do anything about it. It is up to the app to fix their logic.
// Here we are trying to give a heads up on why the app might have performance issues.
LOG(ERROR) << out.str();
}
}
}

Runtime::Current()->GetAppInfo()->RegisterOdexStatus(
dex_location,
compilation_filter,
compilation_reason,
odex_status);
}

// If we arrive here with an empty dex files list, it means we fail to load
// it/them through an .oat file.
if (dex_files.empty()) {
std::string error_msg;
static constexpr bool kVerifyChecksum = true;
ArtDexFileLoader dex_file_loader(dex_location);
if (!dex_file_loader.Open(Runtime::Current()->IsVerificationEnabled(),
kVerifyChecksum,
/*out*/ &error_msg,
&dex_files)) {
ScopedTrace fail_to_open_dex_from_apk("FailedToOpenDexFilesFromApk");
LOG(WARNING) << error_msg;
error_msgs->push_back("Failed to open dex files from " + std::string(dex_location)
+ " because: " + error_msg);
}
}

if (Runtime::Current()->GetJit() != nullptr) {
Runtime::Current()->GetJit()->RegisterDexFiles(dex_files, class_loader);
}

// Now that we loaded the dex/odex files, notify the runtime.
// Note that we do this everytime we load dex files.
Runtime::Current()->NotifyDexFileLoaded();

return dex_files;
}

static std::vector<const DexFile::Header*> GetDexFileHeaders(const std::vector<MemMap>& maps) {
std::vector<const DexFile::Header*> headers;
headers.reserve(maps.size());
for (const MemMap& map : maps) {
DCHECK(map.IsValid());
headers.push_back(reinterpret_cast<const DexFile::Header*>(map.Begin()));
}
return headers;
}

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
std::vector<MemMap>&& dex_mem_maps,
jobject class_loader,
jobjectArray dex_elements,
const OatFile** out_oat_file,
std::vector<std::string>* error_msgs) {
std::vector<std::unique_ptr<const DexFile>> dex_files = OpenDexFilesFromOat_Impl(
std::move(dex_mem_maps),
class_loader,
dex_elements,
out_oat_file,
error_msgs);

if (error_msgs->empty()) {
// Remove write permission from DexFile pages. We do this at the end because
// OatFile assigns OatDexFile pointer in the DexFile objects.
for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
if (!dex_file->DisableWrite()) {
error_msgs->push_back("Failed to make dex file " + dex_file->GetLocation() + " read-only");
}
}
}

if (!error_msgs->empty()) {
return std::vector<std::unique_ptr<const DexFile>>();
}

return dex_files;
}

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat_Impl(
std::vector<MemMap>&& dex_mem_maps,
jobject class_loader,
jobjectArray dex_elements,
const OatFile** out_oat_file,
std::vector<std::string>* error_msgs) {
ScopedTrace trace(__FUNCTION__);
std::string error_msg;
DCHECK(error_msgs != nullptr);

// Extract dex file headers from `dex_mem_maps`.
const std::vector<const DexFile::Header*> dex_headers = GetDexFileHeaders(dex_mem_maps);

// Determine dex/vdex locations and the combined location checksum.
std::string dex_location;
std::string vdex_path;
bool has_vdex = OatFileAssistant::AnonymousDexVdexLocation(dex_headers,
kRuntimeQuickCodeISA,
&dex_location,
&vdex_path);

// Attempt to open an existing vdex and check dex file checksums match.
std::unique_ptr<VdexFile> vdex_file = nullptr;
if (has_vdex && OS::FileExists(vdex_path.c_str())) {
vdex_file = VdexFile::Open(vdex_path,
/*low_4gb=*/false,
&error_msg);
if (vdex_file == nullptr) {
LOG(WARNING) << "Failed to open vdex " << vdex_path << ": " << error_msg;
} else if (!vdex_file->MatchesDexFileChecksums(dex_headers)) {
LOG(WARNING) << "Failed to open vdex " << vdex_path << ": dex file checksum mismatch";
vdex_file.reset(nullptr);
}
}

// Load dex files. Skip structural dex file verification if vdex was found
// and dex checksums matched.
std::vector<std::unique_ptr<const DexFile>> dex_files;
for (size_t i = 0; i < dex_mem_maps.size(); ++i) {
static constexpr bool kVerifyChecksum = true;
ArtDexFileLoader dex_file_loader(std::move(dex_mem_maps[i]),
DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()));
std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(
dex_headers[i]->checksum_,
/* verify= */ (vdex_file == nullptr) && Runtime::Current()->IsVerificationEnabled(),
kVerifyChecksum,
&error_msg));
if (dex_file != nullptr) {
dex::tracking::RegisterDexFile(dex_file.get()); // Register for tracking.
dex_files.push_back(std::move(dex_file));
} else {
error_msgs->push_back("Failed to open dex files from memory: " + error_msg);
}
}

// Check if we should proceed to creating an OatFile instance backed by the vdex.
// We need: (a) an existing vdex, (b) class loader (can be null if invoked via reflection),
// and (c) no errors during dex file loading.
if (vdex_file == nullptr || class_loader == nullptr || !error_msgs->empty()) {
return dex_files;
}

// Attempt to create a class loader context, check OpenDexFiles succeeds (prerequisite
// for using the context later).
std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::CreateContextForClassLoader(
class_loader,
dex_elements);
if (context == nullptr) {
LOG(ERROR) << "Could not create class loader context for " << vdex_path;
return dex_files;
}
DCHECK(context->OpenDexFiles())
<< "Context created from already opened dex files should not attempt to open again";

// Initialize an OatFile instance backed by the loaded vdex.
std::unique_ptr<OatFile> oat_file(OatFile::OpenFromVdex(MakeNonOwningPointerVector(dex_files),
std::move(vdex_file),
dex_location,
context.get()));
if (oat_file != nullptr) {
VLOG(class_linker) << "Registering " << oat_file->GetLocation();
*out_oat_file = RegisterOatFile(std::move(oat_file));
}
return dex_files;
}

// Check how many vdex files exist in the same directory as the vdex file we are about
// to write. If more than or equal to kAnonymousVdexCacheSize, unlink the least
// recently used one(s) (according to stat-reported atime).
static bool UnlinkLeastRecentlyUsedVdexIfNeeded(const std::string& vdex_path_to_add,
std::string* error_msg) {
std::string basename = android::base::Basename(vdex_path_to_add);
if (!OatFileAssistant::IsAnonymousVdexBasename(basename)) {
// File is not for in memory dex files.
return true;
}

if (OS::FileExists(vdex_path_to_add.c_str())) {
// File already exists and will be overwritten.
// This will not change the number of entries in the cache.
return true;
}

auto last_slash = vdex_path_to_add.rfind('/');
CHECK(last_slash != std::string::npos);
std::string vdex_dir = vdex_path_to_add.substr(0, last_slash + 1);

if (!OS::DirectoryExists(vdex_dir.c_str())) {
// Folder does not exist yet. Cache has zero entries.
return true;
}

std::vector<std::pair<time_t, std::string>> cache;

DIR* c_dir = opendir(vdex_dir.c_str());
if (c_dir == nullptr) {
*error_msg = "Unable to open " + vdex_dir + " to delete unused vdex files";
return false;
}
for (struct dirent* de = readdir(c_dir); de != nullptr; de = readdir(c_dir)) {
if (de->d_type != DT_REG) {
continue;
}
basename = de->d_name;
if (!OatFileAssistant::IsAnonymousVdexBasename(basename)) {
continue;
}
std::string fullname = vdex_dir + basename;

struct stat s;
int rc = TEMP_FAILURE_RETRY(stat(fullname.c_str(), &s));
if (rc == -1) {
*error_msg = "Failed to stat() anonymous vdex file " + fullname;
return false;
}

cache.push_back(std::make_pair(s.st_atime, fullname));
}
CHECK_EQ(0, closedir(c_dir)) << "Unable to close directory.";

if (cache.size() < OatFileManager::kAnonymousVdexCacheSize) {
return true;
}

std::sort(cache.begin(),
cache.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
for (size_t i = OatFileManager::kAnonymousVdexCacheSize - 1; i < cache.size(); ++i) {
if (unlink(cache[i].second.c_str()) != 0) {
*error_msg = "Could not unlink anonymous vdex file " + cache[i].second;
return false;
}
}

return true;
}

class BackgroundVerificationTask final : public Task {
public:
BackgroundVerificationTask(const std::vector<const DexFile*>& dex_files,
jobject class_loader,
const std::string& vdex_path)
: dex_files_(dex_files),
vdex_path_(vdex_path) {
Thread* const self = Thread::Current();
ScopedObjectAccess soa(self);
// Create a global ref for `class_loader` because it will be accessed from a different thread.
class_loader_ = soa.Vm()->AddGlobalRef(self, soa.Decode<mirror::ClassLoader>(class_loader));
CHECK(class_loader_ != nullptr);
}

~BackgroundVerificationTask() {
Thread* const self = Thread::Current();
ScopedObjectAccess soa(self);
soa.Vm()->DeleteGlobalRef(self, class_loader_);
}

void Run(Thread* self) override {
std::string error_msg;
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
verifier::VerifierDeps verifier_deps(dex_files_);

// Iterate over all classes and verify them.
for (const DexFile* dex_file : dex_files_) {
for (uint32_t cdef_idx = 0; cdef_idx < dex_file->NumClassDefs(); cdef_idx++) {
const dex::ClassDef& class_def = dex_file->GetClassDef(cdef_idx);

// Take handles inside the loop. The background verification is low priority
// and we want to minimize the risk of blocking anyone else.
ScopedObjectAccess soa(self);
StackHandleScope<2> hs(self);
Handle<mirror::ClassLoader> h_loader(hs.NewHandle(
soa.Decode<mirror::ClassLoader>(class_loader_)));
Handle<mirror::Class> h_class =
hs.NewHandle(class_linker->FindClass(self, *dex_file, class_def.class_idx_, h_loader));

if (h_class == nullptr) {
DCHECK(self->IsExceptionPending());
self->ClearException();
continue;
}

if (&h_class->GetDexFile() != dex_file) {
// There is a different class in the class path or a parent class loader
// with the same descriptor. This `h_class` is not resolvable, skip it.
continue;
}

DCHECK(h_class->IsResolved()) << h_class->PrettyDescriptor();
class_linker->VerifyClass(self, &verifier_deps, h_class);
if (self->IsExceptionPending()) {
// ClassLinker::VerifyClass can throw, but the exception isn't useful here.
self->ClearException();
}

DCHECK(h_class->IsVerified() || h_class->IsErroneous())
<< h_class->PrettyDescriptor() << ": state=" << h_class->GetStatus();

if (h_class->IsVerified()) {
verifier_deps.RecordClassVerified(*dex_file, class_def);
}
}
}

// Delete old vdex files if there are too many in the folder.
if (!UnlinkLeastRecentlyUsedVdexIfNeeded(vdex_path_, &error_msg)) {
LOG(ERROR) << "Could not unlink old vdex files " << vdex_path_ << ": " << error_msg;
return;
}

// Construct a vdex file and write `verifier_deps` into it.
if (!VdexFile::WriteToDisk(vdex_path_,
dex_files_,
verifier_deps,
&error_msg)) {
LOG(ERROR) << "Could not write anonymous vdex " << vdex_path_ << ": " << error_msg;
return;
}
}

void Finalize() override {
delete this;
}

private:
const std::vector<const DexFile*> dex_files_;
jobject class_loader_;
const std::string vdex_path_;

DISALLOW_COPY_AND_ASSIGN(BackgroundVerificationTask);
};

void OatFileManager::RunBackgroundVerification(const std::vector<const DexFile*>& dex_files,
jobject class_loader) {
Runtime* const runtime = Runtime::Current();
Thread* const self = Thread::Current();

if (runtime->IsJavaDebuggable()) {
// Threads created by ThreadPool ("runtime threads") are not allowed to load
// classes when debuggable to match class-initialization semantics
// expectations. Do not verify in the background.
return;
}

{
// Temporarily create a class loader context to see if we recognize the
// chain.
std::unique_ptr<ClassLoaderContext> context(
ClassLoaderContext::CreateContextForClassLoader(class_loader, nullptr));
if (context == nullptr) {
// We only run background verification for class loaders we know the lookup
// chain. Because the background verification runs on runtime threads,
// which do not call Java, we won't be able to load classes when
// verifying, which is something the current verifier relies on.
return;
}
}

if (!IsSdkVersionSetAndAtLeast(runtime->GetTargetSdkVersion(), SdkVersion::kQ)) {
// Do not run for legacy apps as they may depend on the previous class loader behaviour.
return;
}

if (runtime->IsShuttingDown(self)) {
// Not allowed to create new threads during runtime shutdown.
return;
}

if (dex_files.size() < 1) {
// Nothing to verify.
return;
}

std::string dex_location = dex_files[0]->GetLocation();
const std::string& data_dir = Runtime::Current()->GetProcessDataDirectory();
if (!dex_location.starts_with(data_dir)) {
// For now, we only run background verification for secondary dex files.
// Running it for primary or split APKs could have some undesirable
// side-effects, like overloading the device on app startup.
return;
}

std::string error_msg;
std::string odex_filename;
if (!OatFileAssistant::DexLocationToOdexFilename(dex_location,
kRuntimeQuickCodeISA,
&odex_filename,
&error_msg)) {
LOG(WARNING) << "Could not get odex filename for " << dex_location << ": " << error_msg;
return;
}

if (LocationIsOnArtApexData(odex_filename) && Runtime::Current()->DenyArtApexDataFiles()) {
// Ignore vdex file associated with this odex file as the odex file is not trustworthy.
return;
}

{
WriterMutexLock mu(self, *Locks::oat_file_manager_lock_);
if (verification_thread_pool_ == nullptr) {
verification_thread_pool_.reset(
ThreadPool::Create("Verification thread pool", /* num_threads= */ 1));
verification_thread_pool_->StartWorkers(self);
}
}
verification_thread_pool_->AddTask(self, new BackgroundVerificationTask(
dex_files,
class_loader,
GetVdexFilename(odex_filename)));
}

void OatFileManager::WaitForWorkersToBeCreated() {
DCHECK(!Runtime::Current()->IsShuttingDown(Thread::Current()))
<< "Cannot create new threads during runtime shutdown";
if (verification_thread_pool_ != nullptr) {
verification_thread_pool_->WaitForWorkersToBeCreated();
}
}

void OatFileManager::DeleteThreadPool() {
verification_thread_pool_.reset(nullptr);
}

void OatFileManager::WaitForBackgroundVerificationTasksToFinish() {
if (verification_thread_pool_ == nullptr) {
return;
}

Thread* const self = Thread::Current();
verification_thread_pool_->Wait(self, /* do_work= */ true, /* may_hold_locks= */ false);
}

void OatFileManager::WaitForBackgroundVerificationTasks() {
if (verification_thread_pool_ != nullptr) {
Thread* const self = Thread::Current();
verification_thread_pool_->WaitForWorkersToBeCreated();
verification_thread_pool_->Wait(self, /* do_work= */ true, /* may_hold_locks= */ false);
}
}

void OatFileManager::ClearOnlyUseTrustedOatFiles() {
only_use_system_oat_files_ = false;
}

void OatFileManager::SetOnlyUseTrustedOatFiles() {
ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
if (!oat_files_.empty()) {
LOG(FATAL) << "Unexpected non-empty loaded oat files ";
}
only_use_system_oat_files_ = true;
}

void OatFileManager::DumpForSigQuit(std::ostream& os) {
ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
std::vector<const OatFile*> boot_oat_files = GetBootOatFiles();
for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) {
if (ContainsElement(boot_oat_files, oat_file.get())) {
continue;
}
os << oat_file->GetLocation() << ": " << oat_file->GetCompilerFilter() << "\n";
}
}

bool OatFileManager::ContainsPc(const void* code) {
ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
std::vector<const OatFile*> boot_oat_files = GetBootOatFiles();
for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) {
if (oat_file->Contains(code)) {
return true;
}
}
return false;
}

} // namespace art

image

image

image

解析dex 对应着dexfile构造函数脱壳 继续分析

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
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef ART_LIBDEXFILE_DEX_ART_DEX_FILE_LOADER_H_
#define ART_LIBDEXFILE_DEX_ART_DEX_FILE_LOADER_H_

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "base/macros.h"
#include "dex/dex_file_loader.h"

namespace art {

class DexFile;
class DexFileContainer;
class MemMap;
class OatDexFile;
class ZipArchive;

// Class that is used to open dex files and deal with corresponding multidex and location logic.
class ArtDexFileLoader : public DexFileLoader {
public:
using DexFileLoader::DexFileLoader; // Use constructors from base class.
using DexFileLoader::Open; // Don't shadow overloads from base class.

// Old signature preserved for app-compat.
std::unique_ptr<const DexFile> Open(const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
const OatDexFile* oat_dex_file,
bool verify,
bool verify_checksum,
std::string* error_msg,
std::unique_ptr<DexFileContainer> container) const;

// Old signature preserved for app-compat.
std::unique_ptr<const DexFile> Open(const std::string& location,
uint32_t location_checksum,
MemMap&& mem_map,
bool verify,
bool verify_checksum,
std::string* error_msg) const;

// Old signature preserved for app-compat.
bool Open(const char* filename,
const std::string& location,
bool verify,
bool verify_checksum,
std::string* error_msg,
std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
};

} // namespace art

#endif // ART_LIBDEXFILE_DEX_ART_DEX_FILE_LOADER_H_

也是一个脱壳点 含有map的内容

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
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "art_dex_file_loader.h"

#include <sys/stat.h>

#include <memory>

#include "android-base/stringprintf.h"
#include "base/file_magic.h"
#include "base/file_utils.h"
#include "base/logging.h"
#include "base/mem_map.h"
#include "base/mman.h" // For the PROT_* and MAP_* constants.
#include "base/stl_util.h"
#include "base/systrace.h"
#include "base/unix_file/fd_file.h"
#include "base/zip_archive.h"
#include "dex/compact_dex_file.h"
#include "dex/dex_file.h"
#include "dex/dex_file_verifier.h"
#include "dex/standard_dex_file.h"

namespace art {

std::unique_ptr<const DexFile> ArtDexFileLoader::Open(
const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
const OatDexFile* oat_dex_file,
bool verify,
bool verify_checksum,
std::string* error_msg,
std::unique_ptr<DexFileContainer> container) const {
return OpenCommon(base,
size,
/*data_base=*/nullptr,
/*data_size=*/0,
location,
location_checksum,
oat_dex_file,
verify,
verify_checksum,
error_msg,
std::move(container),
/*verify_result=*/nullptr);
}

std::unique_ptr<const DexFile> ArtDexFileLoader::Open(const std::string& location,
uint32_t location_checksum,
MemMap&& mem_map,
bool verify,
bool verify_checksum,
std::string* error_msg) const {
ArtDexFileLoader loader(std::move(mem_map), location);
return loader.Open(location_checksum, verify, verify_checksum, error_msg);
}

bool ArtDexFileLoader::Open(const char* filename,
const std::string& location,
bool verify,
bool verify_checksum,
std::string* error_msg,
std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
ArtDexFileLoader loader(filename, location);
return loader.Open(verify, verify_checksum, error_msg, dex_files);
}

} // namespace art

opencommon也是脱壳点 含有size和begin

dexclassloader源码

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
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dalvik.system;

/**
* A class loader that loads classes from {@code .jar} and {@code .apk} files
* containing a {@code classes.dex} entry. This can be used to execute code not
* installed as part of an application.
*
* <p>Prior to API level 26, this class loader requires an
* application-private, writable directory to cache optimized classes.
* Use {@code Context.getCodeCacheDir()} to create such a directory:
* <pre> {@code
* File dexOutputDir = context.getCodeCacheDir();
* }</pre>
*
* <p><strong>Do not cache optimized classes on external storage.</strong>
* External storage does not provide access controls necessary to protect your
* application from code injection attacks.
*/
public class DexClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code DexClassLoader} that finds interpreted and native
* code. Interpreted classes are found in a set of DEX files contained
* in Jar or APK files.
*
* <p>The path lists are separated using the character specified by the
* {@code path.separator} system property, which defaults to {@code :}.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
* @param librarySearchPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}

父类四个参数的构造方法 继承basedexclassloader

image

在这里使用pathlist构造方法

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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dalvik.system;

import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
import android.system.StructStat;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import libcore.io.ClassPathURLStreamHandler;
import libcore.io.IoUtils;
import libcore.io.Libcore;

import static android.system.OsConstants.S_ISDIR;

/**
* A pair of lists of entries, associated with a {@code ClassLoader}.
* One of the lists is a dex/resource path &mdash; typically referred
* to as a "class path" &mdash; list, and the other names directories
* containing native code libraries. Class path entries may be any of:
* a {@code .jar} or {@code .zip} file containing an optional
* top-level {@code classes.dex} file as well as arbitrary resources,
* or a plain {@code .dex} file (with no possibility of associated
* resources).
*
* <p>This class also contains methods to use these lists to look up
* classes and resources.</p>
*
* @hide
*/
public final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String zipSeparator = "!/";

/** class definition context */
@UnsupportedAppUsage
private final ClassLoader definingContext;

/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
@UnsupportedAppUsage
private Element[] dexElements;

/** List of native library path elements. */
// Some applications rely on this field being an array or we'd use a final list here
@UnsupportedAppUsage
/* package visible for testing */ NativeLibraryElement[] nativeLibraryPathElements;

/** List of application native library directories. */
@UnsupportedAppUsage
private final List<File> nativeLibraryDirectories;

/** List of system native library directories. */
@UnsupportedAppUsage
private final List<File> systemNativeLibraryDirectories;

/**
* Exceptions thrown during creation of the dexElements list.
*/
@UnsupportedAppUsage
private IOException[] dexElementsSuppressedExceptions;

private List<File> getAllNativeLibraryDirectories() {
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
return allNativeLibraryDirectories;
}

/**
* Construct an instance.
*
* @param definingContext the context in which any as-yet unresolved
* classes should be defined
*
* @param dexFiles the bytebuffers containing the dex files that we should load classes from.
*/
public DexPathList(ClassLoader definingContext, String librarySearchPath) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}

this.definingContext = definingContext;
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
}

/**
* Constructs an instance.
*
* @param definingContext the context in which any as-yet unresolved
* classes should be defined
* @param dexPath list of dex/resource path elements, separated by
* {@code File.pathSeparator}
* @param librarySearchPath list of native library directory path elements,
* separated by {@code File.pathSeparator}
* @param optimizedDirectory directory where optimized {@code .dex} files
* should be found and written to, or {@code null} to use the default
* system directory for same
*/
@UnsupportedAppUsage
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);
}

DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}

if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}

if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}

if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}

this.definingContext = definingContext;

ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);

// Native libraries may exist in both the system and
// application library paths, and we use this search order:
//
// 1. This class loader's library path for application libraries (librarySearchPath):
// 1.1. Native library directories
// 1.2. Path to libraries in apk-files
// 2. The VM's library path from the system property for system libraries
// also known as java.library.path
//
// This order was reversed prior to Gingerbread; see http://b/2933456.
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());

if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}

@Override public String toString() {
return "DexPathList[" + Arrays.toString(dexElements) +
",nativeLibraryDirectories=" +
Arrays.toString(getAllNativeLibraryDirectories().toArray()) + "]";
}

/**
* For BaseDexClassLoader.getLdLibraryPath.
*/
public List<File> getNativeLibraryDirectories() {
return nativeLibraryDirectories;
}

/**
* Adds a new path to this instance
* @param dexPath list of dex/resource path element, separated by
* {@code File.pathSeparator}
* @param optimizedDirectory directory where optimized {@code .dex} files
* should be found and written to, or {@code null} to use the default
* system directory for same
*/
@UnsupportedAppUsage
public void addDexPath(String dexPath, File optimizedDirectory) {
addDexPath(dexPath, optimizedDirectory, false);
}

public void addDexPath(String dexPath, File optimizedDirectory, boolean isTrusted) {
final List<IOException> suppressedExceptionList = new ArrayList<IOException>();
final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptionList, definingContext, isTrusted);

if (newElements != null && newElements.length > 0) {
dexElements = concat(Element.class, dexElements, newElements);
}

if (suppressedExceptionList.size() > 0) {
final IOException[] newSuppExceptions = suppressedExceptionList.toArray(
new IOException[suppressedExceptionList.size()]);
dexElementsSuppressedExceptions = dexElementsSuppressedExceptions != null
? concat(IOException.class, dexElementsSuppressedExceptions, newSuppExceptions)
: newSuppExceptions;
}
}

private static<T> T[] concat(Class<T> componentType, T[] inputA, T[] inputB) {
T[] output = (T[]) Array.newInstance(componentType, inputA.length + inputB.length);
System.arraycopy(inputA, 0, output, 0, inputA.length);
System.arraycopy(inputB, 0, output, inputA.length, inputB.length);
return output;
}

/**
* For InMemoryDexClassLoader. Initializes {@code dexElements} with dex files
* loaded from {@code dexFiles} buffers.
*
* @param dexFiles ByteBuffers containing raw dex data. Apks are not supported.
*/
/* package */ void initByteBufferDexPath(ByteBuffer[] dexFiles) {
if (dexFiles == null) {
throw new NullPointerException("dexFiles == null");
}
if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {
throw new NullPointerException("dexFiles contains a null Buffer!");
}
if (dexElements != null || dexElementsSuppressedExceptions != null) {
throw new IllegalStateException("Should only be called once");
}

final List<IOException> suppressedExceptions = new ArrayList<IOException>();

try {
Element[] null_elements = null;
DexFile dex = new DexFile(dexFiles, definingContext, null_elements);
dexElements = new Element[] { new Element(dex) };
} catch (IOException suppressed) {
System.logE("Unable to load dex files", suppressed);
suppressedExceptions.add(suppressed);
dexElements = new Element[0];
}

if (suppressedExceptions.size() > 0) {
dexElementsSuppressedExceptions = suppressedExceptions.toArray(
new IOException[suppressedExceptions.size()]);
}
}

/* package */ void maybeRunBackgroundVerification(ClassLoader loader) {
// Spawn background thread to verify all classes and cache verification results.
// Must be called *after* `this.dexElements` has been initialized and `loader.pathList`
// has been set for ART to find its classes (the fields are hardcoded in ART and dex
// files iterated over in the order of the array).
// We only spawn the background thread if the bytecode is not backed by an oat
// file, i.e. this is the first time this bytecode is being loaded and/or
// verification results have not been cached yet.
for (Element element : dexElements) {
if (element.dexFile != null && !element.dexFile.isBackedByOatFile()) {
element.dexFile.verifyInBackground(loader);
}
}
}

/**
* Splits the given dex path string into elements using the path
* separator, pruning out any elements that do not refer to existing
* and readable files.
*/
private static List<File> splitDexPath(String path) {
return splitPaths(path, false);
}

/**
* Splits the given path strings into file elements using the path
* separator, combining the results and filtering out elements
* that don't exist, aren't readable, or aren't either a regular
* file or a directory (as specified). Either string may be empty
* or {@code null}, in which case it is ignored. If both strings
* are empty or {@code null}, or all elements get pruned out, then
* this returns a zero-element list.
*/
@UnsupportedAppUsage
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;
}

// This method is not used anymore. Kept around only because there are many legacy users of it.
@SuppressWarnings("unused")
@UnsupportedAppUsage
public static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
List<IOException> suppressedExceptions) {
Element[] elements = new Element[dexFiles.length];
int elementPos = 0;
for (ByteBuffer buf : dexFiles) {
try {
DexFile dex = new DexFile(new ByteBuffer[] { buf }, /* classLoader */ null,
/* dexElements */ null);
elements[elementPos++] = new Element(dex);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + buf, suppressed);
suppressedExceptions.add(suppressed);
}
}
if (elementPos != elements.length) {
elements = Arrays.copyOf(elements, elementPos);
}
return elements;
}

/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
@UnsupportedAppUsage
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
}


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;
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);
}
} 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) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(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;
}

/**
* Constructs a {@code DexFile} instance, as appropriate depending on whether
* {@code optimizedDirectory} is {@code null}. An application image file may be associated with
* the {@code loader} if it is not null.
*/
@UnsupportedAppUsage
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}

/**
* Converts a dex/jar file path and an output directory to an
* output file path for an associated optimized dex file.
*/
private static String optimizedPathFor(File path,
File optimizedDirectory) {
/*
* Get the filename component of the path, and replace the
* suffix with ".dex" if that's not already the suffix.
*
* We don't want to use ".odex", because the build system uses
* that for files that are paired with resource-only jar
* files. If the VM can assume that there's no classes.dex in
* the matching jar, it doesn't need to open the jar to check
* for updated dependencies, providing a slight performance
* boost at startup. The use of ".dex" here matches the use on
* files in /data/dalvik-cache.
*/
String fileName = path.getName();
if (!fileName.endsWith(DEX_SUFFIX)) {
int lastDot = fileName.lastIndexOf(".");
if (lastDot < 0) {
fileName += DEX_SUFFIX;
} else {
StringBuilder sb = new StringBuilder(lastDot + 4);
sb.append(fileName, 0, lastDot);
sb.append(DEX_SUFFIX);
fileName = sb.toString();
}
}

File result = new File(optimizedDirectory, fileName);
return result.getPath();
}

/*
* TODO (dimitry): Revert after apps stops relying on the existence of this
* method (see http://b/21957414 and http://b/26317852 for details)
*/
@UnsupportedAppUsage
@SuppressWarnings("unused")
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}

/**
* Makes an array of directory/zip path elements for the native library search path, one per
* element of the given array.
*/
@UnsupportedAppUsage
private static NativeLibraryElement[] makePathElements(List<File> files) {
NativeLibraryElement[] elements = new NativeLibraryElement[files.size()];
int elementsPos = 0;
for (File file : files) {
String path = file.getPath();

if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
File zip = new File(split[0]);
String dir = split[1];
elements[elementsPos++] = new NativeLibraryElement(zip, dir);
} else if (file.isDirectory()) {
// We support directories for looking up native libraries.
elements[elementsPos++] = new NativeLibraryElement(file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}

/**
* Finds the named class in one of the dex files pointed at by
* this instance. This will find the one in the earliest listed
* path element. If the class is found but has not yet been
* defined, then this method will define it in the defining
* context that this instance was constructed with.
*
* @param name of class to find
* @param suppressed exceptions encountered whilst finding the class
* @return the named class or {@code null} if the class is not
* found in any of the dex files
*/
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}

if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}

/**
* Finds the named resource in one of the zip/jar files pointed at
* by this instance. This will find the one in the earliest listed
* path element.
*
* @return a URL to the named resource or {@code null} if the
* resource is not found in any of the zip/jar files
*/
public URL findResource(String name) {
for (Element element : dexElements) {
URL url = element.findResource(name);
if (url != null) {
return url;
}
}

return null;
}

/**
* Finds all the resources with the given name, returning an
* enumeration of them. If there are no resources with the given
* name, then this method returns an empty enumeration.
*/
public Enumeration<URL> findResources(String name) {
ArrayList<URL> result = new ArrayList<URL>();

for (Element element : dexElements) {
URL url = element.findResource(name);
if (url != null) {
result.add(url);
}
}

return Collections.enumeration(result);
}

/**
* Finds the named native code library on any of the library
* directories pointed at by this instance. This will find the
* one in the earliest listed directory, ignoring any that are not
* readable regular files.
*
* @return the complete path to the library or {@code null} if no
* library was found
*/
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);

for (NativeLibraryElement element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);

if (path != null) {
return path;
}
}

return null;
}

/**
* Returns the list of all individual dex files paths from the current list.
* The list will contain only file paths (i.e. no directories).
*/
/*package*/ List<String> getDexPaths() {
List<String> dexPaths = new ArrayList<String>();
for (Element e : dexElements) {
String dexPath = e.getDexPath();
if (dexPath != null) {
// Add the element to the list only if it is a file. A null dex path signals the
// element is a resource directory or an in-memory dex file.
dexPaths.add(dexPath);
}
}
return dexPaths;
}

/**
* Adds a collection of library paths from which to load native libraries. Paths can be absolute
* native library directories (i.e. /data/app/foo/lib/arm64) or apk references (i.e.
* /data/app/foo/base.apk!/lib/arm64).
*
* Note: This method will attempt to dedupe elements.
* Note: This method replaces the value of {@link #nativeLibraryPathElements}
*/
@UnsupportedAppUsage
public void addNativePath(Collection<String> libPaths) {
if (libPaths.isEmpty()) {
return;
}
List<File> libFiles = new ArrayList<>(libPaths.size());
for (String path : libPaths) {
libFiles.add(new File(path));
}
ArrayList<NativeLibraryElement> newPaths =
new ArrayList<>(nativeLibraryPathElements.length + libPaths.size());
newPaths.addAll(Arrays.asList(nativeLibraryPathElements));
for (NativeLibraryElement element : makePathElements(libFiles)) {
if (!newPaths.contains(element)) {
newPaths.add(element);
}
}
nativeLibraryPathElements = newPaths.toArray(new NativeLibraryElement[newPaths.size()]);
}

/**
* Element of the dex/resource path. Note: should be called DexElement, but apps reflect on
* this.
*/
/*package*/ static class Element {
/**
* A file denoting a zip file (in case of a resource jar or a dex jar), or a directory
* (only when dexFile is null).
*/
@UnsupportedAppUsage
private final File path;
/** Whether {@code path.isDirectory()}, or {@code null} if {@code path == null}. */
private final Boolean pathIsDirectory;

@UnsupportedAppUsage
private final DexFile dexFile;

private ClassPathURLStreamHandler urlHandler;
private boolean initialized;

/**
* Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath
* should be null), or a jar (in which case dexZipPath should denote the zip file).
*/
@UnsupportedAppUsage
public Element(DexFile dexFile, File dexZipPath) {
if (dexFile == null && dexZipPath == null) {
throw new NullPointerException("Either dexFile or path must be non-null");
}
this.dexFile = dexFile;
this.path = dexZipPath;
// Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString().
this.pathIsDirectory = (path == null) ? null : path.isDirectory();
}

public Element(DexFile dexFile) {
this(dexFile, null);
}

public Element(File path) {
this(null, path);
}

/**
* Constructor for a bit of backwards compatibility. Some apps use reflection into
* internal APIs. Warn, and emulate old behavior if we can. See b/33399341.
*
* @deprecated The Element class has been split. Use new Element constructors for
* classes and resources, and NativeLibraryElement for the library
* search path.
*/
@UnsupportedAppUsage
@Deprecated
public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
this(dir != null ? null : dexFile, dir != null ? dir : zip);
System.err.println("Warning: Using deprecated Element constructor. Do not use internal"
+ " APIs, this constructor will be removed in the future.");
if (dir != null && (zip != null || dexFile != null)) {
throw new IllegalArgumentException("Using dir and zip|dexFile no longer"
+ " supported.");
}
if (isDirectory && (zip != null || dexFile != null)) {
throw new IllegalArgumentException("Unsupported argument combination.");
}
}

/*
* Returns the dex path of this element or null if the element refers to a directory.
*/
private String getDexPath() {
if (path != null) {
return path.isDirectory() ? null : path.getAbsolutePath();
} else if (dexFile != null) {
// DexFile.getName() returns the path of the dex file.
return dexFile.getName();
}
return null;
}

@Override
public String toString() {
if (dexFile == null) {
return (pathIsDirectory ? "directory \"" : "zip file \"") + path + "\"";
} else if (path == null) {
return "dex file \"" + dexFile + "\"";
} else {
return "zip file \"" + path + "\"";
}
}

public synchronized void maybeInit() {
if (initialized) {
return;
}

if (path == null || pathIsDirectory) {
initialized = true;
return;
}

try {
// Disable zip path validation for loading APKs as it does not pose a risk of the
// zip path traversal vulnerability.
urlHandler = new ClassPathURLStreamHandler(path.getPath(),
/* enableZipPathValidator */ false);
} catch (IOException ioe) {
/*
* Note: ZipException (a subclass of IOException)
* might get thrown by the ZipFile constructor
* (e.g. if the file isn't actually a zip/jar
* file).
*/
System.logE("Unable to open zip file: " + path, ioe);
urlHandler = null;
}

// Mark this element as initialized only after we've successfully created
// the associated ClassPathURLStreamHandler. That way, we won't leave this
// element in an inconsistent state if an exception is thrown during initialization.
//
// See b/35633614.
initialized = true;
}

public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}

public URL findResource(String name) {
maybeInit();

if (urlHandler != null) {
return urlHandler.getEntryUrlOrNull(name);
}

// We support directories so we can run tests and/or legacy code
// that uses Class.getResource.
if (path != null && path.isDirectory()) {
File resourceFile = new File(path, name);
if (resourceFile.exists()) {
try {
return resourceFile.toURI().toURL();
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
}

return null;
}
}

/**
* Element of the native library path
*/
/*package*/ static class NativeLibraryElement {
/**
* A file denoting a directory or zip file.
*/
@UnsupportedAppUsage
private final File path;

/**
* If path denotes a zip file, this denotes a base path inside the zip.
*/
private final String zipDir;

private ClassPathURLStreamHandler urlHandler;
private boolean initialized;

@UnsupportedAppUsage
public NativeLibraryElement(File dir) {
this.path = dir;
this.zipDir = null;

// We should check whether path is a directory, but that is non-eliminatable overhead.
}

public NativeLibraryElement(File zip, String zipDir) {
this.path = zip;
this.zipDir = zipDir;

// Simple check that should be able to be eliminated by inlining. We should also
// check whether path is a file, but that is non-eliminatable overhead.
if (zipDir == null) {
throw new IllegalArgumentException();
}
}

@Override
public String toString() {
if (zipDir == null) {
return "directory \"" + path + "\"";
} else {
return "zip file \"" + path + "\"" +
(!zipDir.isEmpty() ? ", dir \"" + zipDir + "\"" : "");
}
}

public synchronized void maybeInit() {
if (initialized) {
return;
}

if (zipDir == null) {
initialized = true;
return;
}

try {
// Disable zip path validation for loading APKs as it does not pose a risk of the
// zip path traversal vulnerability.
urlHandler = new ClassPathURLStreamHandler(path.getPath(),
/* enableZipPathValidator */ false);
} catch (IOException ioe) {
/*
* Note: ZipException (a subclass of IOException)
* might get thrown by the ZipFile constructor
* (e.g. if the file isn't actually a zip/jar
* file).
*/
System.logE("Unable to open zip file: " + path, ioe);
urlHandler = null;
}

// Mark this element as initialized only after we've successfully created
// the associated ClassPathURLStreamHandler. That way, we won't leave this
// element in an inconsistent state if an exception is thrown during initialization.
//
// See b/35633614.
initialized = true;
}

public String findNativeLibrary(String name) {
maybeInit();

if (zipDir == null) {
String entryPath = new File(path, name).getPath();
if (IoUtils.canOpenReadOnly(entryPath)) {
return entryPath;
}
} else if (urlHandler != null) {
// Having a urlHandler means the element has a zip file.
// In this case Android supports loading the library iff
// it is stored in the zip uncompressed.
String entryName = zipDir + '/' + name;
if (urlHandler.isEntryStored(entryName)) {
return path.getPath() + zipSeparator + entryName;
}
}

return null;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NativeLibraryElement)) return false;
NativeLibraryElement that = (NativeLibraryElement) o;
return Objects.equals(path, that.path) &&
Objects.equals(zipDir, that.zipDir);
}

@Override
public int hashCode() {
return Objects.hash(path, zipDir);
}
}
}

image

判断dex的格式加载

image

跟进

image

image

inmemorydexclassloader走的上面的构造函数 dexclassloader走的下面的 之后的流程就和inmemorydexclassloader差不多了

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
static jobject DexFile_openDexFileNative(JNIEnv* env,
jclass,
jstring javaSourceName,
[[maybe_unused]] jstring javaOutputName,
[[maybe_unused]] jint flags,
jobject class_loader,
jobjectArray dex_elements) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == nullptr) {
return nullptr;
}

if (isReadOnlyJavaDclChecked() && access(sourceName.c_str(), W_OK) == 0) {
LOG(ERROR) << "Attempt to load writable dex file: " << sourceName.c_str();
if (isReadOnlyJavaDclEnforced(env)) {
ScopedLocalRef<jclass> se(env, env->FindClass("java/lang/SecurityException"));
std::string message(
StringPrintf("Writable dex file '%s' is not allowed.", sourceName.c_str()));
env->ThrowNew(se.get(), message.c_str());
return nullptr;
}
}

std::vector<std::string> error_msgs;
const OatFile* oat_file = nullptr;
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);
return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}

跟进OpenDexFilesFromOat

image

参数和inmemorydexclassloader有点区别 inmemorydexclassloader第一个参数是map 在其中的oat在10以前是会进行使用也可以选择性禁用 但是在现在的安卓系统中的系统app还有oat 所以并没有进行删除

image

接下来的内容和inmemorydexclassloader差不多

image

image

openwithmagic也是早期脱壳点

youpk整体脱壳原理

unpacker/android-7.1.2_r33/art/runtime/unpacker/unpacker.cc at master · Youlor/unpacker

image

核心代码 获取到当前的现成 获取到classlinker 搞到dex对象内容 进行整体脱壳

fdex2脱壳原理

早期针对一代壳的脱壳方案 也就是整体加固 但现在随着安卓的版本提高fdex2也就淡出视野

一步一步分析一下fdex2的代码 对后面学习安卓很有帮助

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
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
initRefect();
XposedBridge.log("目标包名:"+ lpparam.packageName);
String str = "java.lang.ClassLoader";
String str2 = "loadClass";
final String packagename = "com.jiongji.andriod.card";

// protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException
if(lpparam.packageName.equals(packagename)){
// public static Unhook findAndHookMethod(String var0, ClassLoader var1, String var2, Object... var3)
XposedHelpers.findAndHookMethod(str, lpparam.classLoader, str2, String.class, Boolean.TYPE, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//获取hook函数返回类
Class cls = (Class) param.getResult();
if(cls == null){
return;
}
String name = cls.getName();
XposedBridge.log("当前类名:"+name);
byte[] bArr = (byte[]) Dex_getBytes.invoke(getDex.invoke(cls,null)); //Class.getDex().getBytes()
if(bArr == null) {
XposedBridge.log("数据为空,返回");
return;

}
XposedBridge.log("开始写数据");
String dex_path = "/data/data/"+packagename+"/"+packagename+"_"+bArr.length+".dex";
XposedBridge.log(dex_path);
File file = new File(dex_path);
if(file.exists()){
return;
}
writeByte(bArr,file.getAbsolutePath());
}

@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
}
});
}

}

public Dex getDex() {
if (dexCache == null) {
return null;
}
return dexCache.getDex();
}

public byte[] getBytes() {
ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
byte[] result = new byte[data.capacity()];
data.position(0);
data.get(result);
return result;
}

public void initRefect(){
try {
// public byte[] getBytes()
Dex = Class.forName("com.android.dex.Dex");
Dex_getBytes = Dex.getDeclaredMethod("getBytes",null);
// public Dex getDex()
getDex = Class.forName("java.lang.Class").getDeclaredMethod("getDex",null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}




1
2
3
String str = "java.lang.ClassLoader";
String str2 = "loadClass";
XposedHelpers.findAndHookMethod(str, lpparam.classLoader, str2, String.class, Boolean.TYPE, new XC_MethodHook()

明确了要hook的类和对应的方法

1
byte[] bArr = (byte[]) Dex_getBytes.invoke(getDex.invoke(cls,null));

使用了两次反射 第一次返回的是一个dex对象 再去使用Dex_getBytes获取到在内存中的信息

1
Dex_getBytes = Dex.getDeclaredMethod("getBytes",null);

image-20250429173618980

这里就可以直接进行dex的内容写入了

整理一下流程 先去hook了java.lang.ClassLoader.loadclass 再去使用反射获取到对应的dex对象 再次反射搞到dex在内存中的信息 很直白的方法

思考

这里之所以可以快速简洁的搞到内存中的内容 主要还是getBytes方法的好处 正常的脱壳往往是要查看内容的长度大小的

从源码来看可以避免fdex2的方式很多 比如显式加载我们使用forname的方法就可以直接防掉这个 当然 目前只考虑一代壳 也就是整体加固 其他的壳应该很轻松就可以薄纱fdex2

当然 从fdex2的源码来看 核心并不是hook了什么 而是getdex和getbyte这两个api 通过这两个快速的搞到内容 坏消息 7.1以上api没了

写一个简单的frida代码进行查看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function check() {
//com.android.dex.Dex
//java.lang.Class
console.log("yes1")
var dex=Java.use("java.lang.Class")
console.log("yes")
var getdex = dex.getDex
if (getdex == null){
console.log("fail")
}else{
console.log(getdex)
}
}
Java.perform(function(){
check()
})

image-20250429183012679

GG

frida脚本实现

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
function check() {
//com.android.dex.Dex
//java.lang.Class
console.log("yes1")
var dex=Java.use("java.lang.Class")
console.log("yes")
var getdex = dex.getDex
if (getdex == null){
console.log("fail")
}else{
console.log(getdex)
}
}

function fdex(classname){
Java.perform(function (){
Java.enumerateClassLoaderSync().forEach(function (loader){
try{
var thisclass = loader.loadClass()

var dexobj = thisclass.getDex()
var dexbytearry=dexobj.getbyte()

var filepath="/sdcard/"+classname+".dex"
writefile(dexbytearry,filepath)




}catch(e){

}

})
})
}


function writefile(bytearry,filepath){
Java.perform(function(){
var File=Java.use("java.io.File")
var fileoutstream =Java.use("java.io.FileOutputStream")
var fileobj = File.$new(filepath)
var filestream =fileoutstream.$new(fileobj)
filestream.write(bytearry)
filestream.close()
console.log("write over")

})
}


这个代码实现了主动的调用需要class所在的dex文件

1
firda -FU -l hook.js --no-pause
1
fdex("classname")