-
Notifications
You must be signed in to change notification settings - Fork 2
Code Injection
ReflectTools has an abstraction layer for working with code injection using multiple mechanisms such as Xposed Framework and Cydia Substrate. The abstraction layer adds a unified way to work with both types and it also ensures that your modules quickly can add new mechanisms in the future without changing any module code.
Follow the steps below to setup your project.
Declaring a Module
The first thing to do, is to declare your module in the AndroidManifest.xml file. Sadly this is the one thing where we cannot make any abstraction. It has to be done individually for each mechanisms that you wish to enable support for.
<manifest ... >
<!--
This is needed to support Cydia Substrate -->
<uses-permission android:name="cydia.permission.SUBSTRATE"/>
<application ... >
<!--
This tells Cydia Substrate what class it should load -->
<meta-data android:name="com.saurik.substrate.main" android:value="class.path.to.MyModuleClass"/>
<!--
This is needed to support Xposed Framework -->
<meta-data android:value="true" android:name="xposedmodule" />
<meta-data android:value="30+" android:name="xposedminversion" />
<meta-data android:value="A Description" android:name="xposeddescription" />
</application>
</manifest>For Cydia Substrate the module class was defined in the above XML file. But for Xposed Framework we need to define this in a separate file assets/xposed_init.
class.path.to.MyModuleClass
The Module Initiation Class
Above we just defined the path to the class that both Xposed Framework and Cydia Substrate should initiate during boot. Now we need to create the class. Both these injection mechanisms uses their own different structure, interfaces and classes. But ReflectTools comes with abstraction classes that work with both and at the same time adds additional features.
public class MyModuleClass extends MyModuleClass {
/*
* Small hack to get around Cydia's badly statically designed structure.
*/
public final static void initialize() throws Throwable {
InitBridge.initialize( MyModuleClass.class );
}
@Override
protected void onZygoteInit() {
// Zygote has been started, Android is still not started, but we have root.
}
@Override
protected void onSystemInit(Context systemContext) {
// Main Thread and System Context is ready
}
@Override
protected void onProcessInit(Context context, String packageName, String processName) {
// Package process has been created
}
@Override
protected void onPackageInit(Context context, String packageName, String processName) {
// Package has been launched
}
}onZygoteInit is always the first to be invoked. It is invoked right after the module has been loaded from the Zygote process. This is a good place to add global hooks as it will be adapted by the whole JVM and not just one specific process/app. During this method call you will also have full root access, although with Android's newer SELinux restrictions your options is still limited outside the JVM.
onSystemInit is invoked as soon as the main thread has been started and the system Context has been created. During this time you will have full system rights and have access to all of Android's restricted internal classes. This is a good place to create hooks to system services and other core classes.
onProcessInit is invoked when a new app process has been started. Note that multiple apps can some times share a single process, which is what a lot of Google's apps does. This method is called only when the process is started, not when launching 2'nd or 3'rd apps into existing processes. This is a good place to add hooks to shared libraries between these apps to avoid multiple hooks per app launch.
onPackageInit is invoked each time a new app is launched. Unlike onProcessInit this is also called when a new app is launched into existing processes. This is a good place to add hooks that only relates to one specific app (The apps own internal classes).
Adding Hooks
Now that we have our initiator class, all we need is to add a hook to something.
public class MyModuleClass extends MyModuleClass {
...
@Override
protected void onProcessInit(Context context, String packageName, String processName) {
if ("somePackage".equals(packageName)) {
ReflectClass.fromName( packageName + ".MainActivity" ).bridge("someMethod", new MethodBridge(){
@Override
protected void bridgeBegin(BridgeParams params) {
// Executed before the original
}
@Override
protected void bridgeEnd(BridgeParams params) {
// Executed after the original
}
});
}
}
...The above code will work regardless of whether the user has Xposed Framework or Cydia Substrate installed.
The only annoyance here, is that the Cydia Substrate people decided to use a static initiator method in a language that does not play well with static classes, hence the small hack in our MyModuleClass class. There is no way to statically locate child classes, which is why we need to manually parse it. It's only 3 additional lines of code, but it could have been avoided if Cydia had used interfaces like Xposed.
Original Link
It can some times be useful to be able to call a hooked class directly, bypassing any hook. There are 3 ways to do this.
- A link to the original method is always parsed via
BridgeParamsduring a call to a hooksMethodBridge. -
ReflectMethodandReflectConstructorreturns a link during a call tobridge()which can be stored and used at a later time. -
MethodBridgehas a methodbridgeAttached()that can be overwritten. It will receive a link as argument that can be stored and used at a later time.
...
@Override
protected void bridgeBegin(BridgeParams params) {
// Call the original member
params.invokeOriginal();
}
...Checking Status
Checking the state of injection mechanisms on a device can some times also be helpful. ReflectTools has a set of data that can be used.
- Current state. Whether or not any injection mechanism was used to instantiate the abstraction layer.
- Current mechanism. Which type of injection mechanism is being used on the device.
Both of these can be used anywhere. They are not limited to be used from inside hooks.
// Checks if any injection mechanism is active
if (ReflectUtils.bridgeInitiated()) {
if (ReflectUtils.usesXposed()) {
// Xposed Framework is being used
} else if (ReflectUtils.usesCydia()) {
// Cydia Substrate is being used
}
}The last check does not have much purpose as the abstraction layer was build to avoid creating any mechanism specific code. There should be very little need for checking the specific mechanism being used.
Documentation
Checkout the Full Documentation Page for further details.