Overview of hook-loadedapk-classloader-host
masterhook-loadedapk-classloader-host component is designed to hook the LoadedApk object, which serves as the in-memory representation of an APK file within the Android system.repository·master·Indexed 43 days ago
https://github.com/camnter/androidlifeA repository of source code notes and educational modules for advanced Android development. It includes dex-method-counts for managing the 64K method limit, the life-plugin Gradle plugin, and frameworks for implementing pluginization via BroadcastReceivers and ContentProviders using ClassLoader patching.
hook-loadedapk-classloader-host component is designed to hook the LoadedApk object, which serves as the in-memory representation of an APK file within the Android system.dex-method-counts is a tool used to output per-package method counts in an Android DEX executable. It groups counts by package to help developers manage and stay under the 65,536 referenced method limit (the 64K limit) in Android applications. It supports analyzing .apk, .zip, .dex, or directory inputs.AndroidLife serves as a repository of source code notes and implementations for various Android-related libraries and technologies. Developers can use this repository to study the internal workings of popular frameworks and tools.
Key areas of study include:
AndroidLife is a collection of Android implementation examples covering various aspects of Android development, including UI components, design patterns, utilities, and system services. Developers can use this repository as a reference for implementing specific features such as:
TextInputLayout, NavigationView, FloatingActionButton, Snackbar, CoordinatorLayout, and various TabLayout styles.DeviceUtil, DateUtil, ImageUtil (for downloading and saving to gallery), ReflectionUtil, and ResourcesUtil.AsyncTask templates, LaunchMode (specifically singleTask), Service (local and AIDL/remote), BroadcastReceiver (static, dynamic, and download), and ContentProvider.EasySlidingTabs, EasyRecyclerView, EasyFlowLayout, TagTextView (rich text with clickable labels), and Span effects.SQLite demos, RxAndroid usage, and Otto subscription mechanism.SensorManager and LocationManager.To hook the Activity Manager Service (AMS), you must intercept the communication between the local process and the remote AMS via the IActivityManager interface.
In Android, ActivityManagerNative holds a static Singleton<IActivityManager> named gDefault. The actual instance of the service is stored within the mInstance field of this Singleton object.
Hooking Strategy:
IActivityManager using java.lang.reflect.InvocationHandler.gDefault field in android.app.ActivityManagerNative.mInstance field within the android.util.Singleton class.mInstance with your dynamic proxy instance.// 1. Implement the handler
public class IActivityManagerHandler implements InvocationHandler {
Object base;
IActivityManagerHandler(Object base) { this.base = base; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Logic before method call
final Object object = method.invoke(this.base, args);
// Logic after method call
return object;
}
}
// 2. Use AMSHooker to perform the replacement
AMSHooker.hookActivityManagerNative();To hook Activity Manager Service (AMS) for an activity plugin, you must understand the transition between the system_server process and the target App process.
system_server (Entering App Process)The transition begins in ActivityStackSupervisor#realStartActivityLocked. This method calls app.thread.scheduleLaunchActivity(...).
app.thread is an instance of ApplicationThread (an inner class of ActivityThread).system_server process to the App process.Once in the App process, the execution follows this path:
ActivityThread.H#handleMessage receives the LAUNCH_ACTIVITY message.ActivityThread#handleLaunchActivity is called.ActivityThread#performLaunchActivity is called.Instrumentation#callActivityOnCreate is called.Activity#onCreate lifecycle method is executed.LAUNCH_ACTIVITY messageIn ActivityThread.H, the LAUNCH_ACTIVITY message can be intercepted by leveraging the Handler priority system. The Handler.dispatchMessage method follows this priority:
msg.callback (attached to the specific message).mCallback (the Handler's callback object).handleMessage (the default implementation).To intercept the LAUNCH_ACTIVITY message without knowing which specific message object is being sent, you should hook/inject a Handler.Callback into ActivityThread.H#mCallback.
Validation of whether an Activity is declared in the AndroidManifest.xml occurs within the system_server process, specifically during the execution of ActivityStarter methods.
If the Activity is not found in the manifest, ActivityStarter returns ActivityManager.START_CLASS_NOT_FOUND. This result is passed back to the application process, where Instrumentation.checkStartActivityResult catches it and throws an ActivityNotFoundException.
Error Scenario:
If you attempt to start an explicit component that is missing from the manifest, the following error is triggered:
Unable to find explicit activity class <component_name>; have you declared this activity in your AndroidManifest.xml?
// Inside ActivityStarter (system_server)
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
err = ActivityManager.START_CLASS_NOT_FOUND;
}
// Inside Instrumentation (application process)
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException("Unable to find explicit activity class " + ...);LoadedApk object is the primary abstraction used to represent an APK file while it is loaded in memory. Hooking this object is a key mechanism for managing class loaders and plugin lifecycles.When interacting with a plugin's ContentProvider, you cannot use the plugin's original URI directly. Instead, you must use a stub URI provided by the host.
The URI format is:
StubContentProvider.STUB_URI + / + [Plugin's Full Provider Class Name]
Example URI Construction:
If the plugin provider class is com.camnter.content.provider.plugin.plugin.PluginContentProvider, the URI used by the host to query it would be:
content://com.camnter.content.provider.plugin.host.StubContentProvider/com.camnter.content.provider.plugin.plugin.PluginContentProvider
// Constructing the URI to access the plugin provider
this.pluginUri = Uri.parse(StubContentProvider.STUB_URI.toString() + '/' +
"com.camnter.content.provider.plugin.plugin.PluginContentProvider");
// Using the resolver to interact with the plugin
this.resolver = this.getContentResolver();
// Querying
Cursor result = this.resolver.query(this.pluginUri, null, null, null, null);
// Inserting
ContentValues values = new ContentValues();
values.put("content", "some data");
this.resolver.insert(this.pluginUri, values);
// Deleting
this.resolver.delete(this.pluginUri, null, null);To hook the Package Manager Service (PMS), you need to intercept the IPackageManager interface. There are two primary locations where this interface is held and can be intercepted:
static volatile IPackageManager sPackageManager holds the service instance.private final IPackageManager mPM holds the service instance used by the PackageManager wrapper.Hooking Strategy:
IPackageManager.sPackageManager field in android.app.ActivityThread.mPM field in android.app.ApplicationPackageManager.The repository contains extensive implementations of Android pluginization techniques, ranging from simple class loading to complex system hooking. This is useful for developers building modular architectures or dynamic feature loading systems.
Pluginization topics covered:
To allow a plugin Activity to access resources (including layouts) from a plugin APK, you must hook the mResources field within ContextImpl.
Important Note: A plugin Activity cannot access cacheFile before calling super.attachBaseContext(newBase). You must use the newBase context provided in attachBaseContext to retrieve file paths.
There are two ways to handle plugin resources:
LayoutInflater.mResources: By using reflection to replace the mResources field in ContextImpl, you enable the standard Android resource system (including setContentView(R.layout...)) to resolve plugin resources.When using custom Views or Fragments, ensure you hold a reference to the inflated view:
final View selfView = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
this.setContentView(selfView);@Override
protected void attachBaseContext(Context newBase) {
try {
final Field field = newBase.getClass().getDeclaredField("mResources");
field.setAccessible(true);
if (this.resources == null) {
this.resources = this.getPluginResources(newBase);
}
field.set(newBase, this.resources);
} catch (Exception e) {
e.printStackTrace();
}
super.attachBaseContext(newBase);
}