0 x0, preface

Two years ago contact Xposed, the motive is: the company annual meeting can not grab the red envelope, affect the mood to eat, want to write an automatic grab the red envelope.

With the help of the search engine, understand the Xposed, in the process of learning, by writing a tutorial to help the later-comers:

  • “Sorry, Xposed really can do whatever you want — 1. Basic knowledge reserve”
  • “Sorry, Xposed really can do anything — 2. Changed to OV machine smooth play high frame rate king of pesticide”
  • “Sorry, Xposed really can do whatever you want — 3. Wechat movement occupy cover to sell advertising space”
  • Sorry, Xposed really can do whatever you want — 4.
  • “Sorry, Xposed really can do whatever you want — 5. My own brush Xposed with what I don’t use”
  • “Sorry, Xposed really can do whatever you want — 6. Your confession withdraw not back”

The use of Xposed is not difficult, API is also those, the difficulty is: reverse figure out Hook APP method call process, how to tune, parameters are what.

After repeated practice, reverse Hook a common APP(non-enterprise class reinforcement) write available Xposed plug-in has already become proficient (mainly grinding time), but there is a concern has been lingering in my heart: Do not know the specific implementation principle of Xposed at the bottom, some interviewers (not the kind of) see the resume to write a lot of Xposed plug-in will say:

Oh, also wrote the Xposed plug-in ah, have written what plug-ins? Have understood Xposed bottom is how to achieve it?

Gnawing source code, look up information, understand the process, write clearly, is a time-consuming and laborious and challenging thing.

This year, the flag, to actively up, just in preparation for the interview, simply to understand the Xposed principle on the schedule, more content suggested collection of slow products, dry up ~

Tips: Xposed can usually Hook Java layer and application resources replacement, there are two versions: 4.4 before the Dalvik virtual machine implementation and 5.0 after the ART virtual machine implementation, this article for the latter analysis, while matching Android 5.1.1_R6 source food.

0x1, the composition of Xposed

This huge project is made up of four projects:

  • Xposed → C++ part, Xposed version of zygote, used to replace the native zygote, and provide JNI method for XposedBridge, need by XposedInstaller in root put /system/bin directory;
  • XposedBridge → Java part, after compilation will generate a JAR package, responsible for Native layer and Framework layer interaction;
  • XposedInstaller → Xposed plug-in management and function control of the APP, including enabling, downloading, disabling plug-ins and other functions;
  • XposedTools → for compiling Xposed and XposedBridge;

0x2, use of Xposed

A simple Hook example is as follows:

Walk a few steps:

  • The class implements the IXposedHookLoadPackage interface
  • Override the handleLoadPackage() method
  • (3) call XposedHelpers. FindAndHookMethod (), to complete the name of the class, the class loader, method name, parameter type, XC_MethodHook instance;
  • Override beforeHookedMethod() and afterHookedMethod() methods on demand;

By doing so, you can ravage the logic of a Java method to your own ends.

0x3 Startup process of the Android system

Before exploring the implementation principle, let’s first understand the startup process of Android system. The brief flow chart is as follows:

Focus on the Zygote process, which is started by the Init process, which creates a Davlik virtual machine instance, loads the Java runtime library into the process, and registers some JNI of the Android core classes into the Dalvik virtual machine instance created earlier.

All APP processes are incubated (fork) by the Zygote process, which not only obtains a copy of the Dalvik VIRTUAL machine instance from Zygote, but also shares the Java runtime library with Zygote.

Xposed is to use such a mechanism, only to Zygote injection xposedBridg. jar, you can achieve global injection.

Tips: Android 5.0 starts with Zygote as two processes, 32-bit (compatible with armeabi and Armeabi-V7A) and 64-bit (compatible with arm64-V8A). The init.rc file is also differentiated. Rc starts 32-bit zygote, init.zygote64 starts 64-bit Zygote.

0x4 Startup process of Zygote

With the/system/core/rootdir/init. Zygote. Rc:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
Copy the code

Section parse this code:

  • Service → ATL language syntax, start a service process;
  • Zygote → start the program name, this refers to the zygote process;
  • /system/bin/app_process → Executable file path (app_main.cpp);
  • -xzygote /system/bin → Pass the specified parameter to app_main. CPP;
  • –zygote –start-system-server → The specific parameter value passed;

Said simple point is that initiated the process of Zygote, parameters passed in/frameworks/base/CMDS/app_process/app_main CPP found in:

Set the zygote and startSystem flags to true, and then locate where the zygote flag is used:

With the runtime. The start () positioning to frameworks/base/core/jni/AndroidRuntime CPP, key code is as follows:

// ① Initialize the JNI interface
JniInvocation jni_invocation;
jni_invocation.Init(NULL);

// ② Create a VM
JNIEnv* env;
if (startVm(&mJavaVM, &env) ! =0) {
    return;
}
onVmCreated(env);

// (3) Register the JNI method
if (startReg(env) < 0) {
    ALOGE("Unable to register all android natives\n");
    return;
}

Static void main(String args[])
slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
// Find the main function
jmethodID startMeth = env->GetStaticMethodID(startClass, "main"."([Ljava/lang/String;)V");
if (startMeth == NULL) {
    ALOGE("JavaVM unable to find main() in '%s'\n", className);
    /* keep going */
} else {
    // call main from C++ to Java via JNI
    env->CallStaticVoidMethod(startClass, startMeth, strArray);
    if (env->ExceptionCheck())
        threadExitUncaughtException(env);
}
Copy the code

So it created a virtual machine, the registered JNI methods, then call com. Android. Internal. OS. ZygoteInit main (), With the frameworks/base/core/Java/com/android/internal/OS/ZygoteInit. Java:

public static void main(String argv[]) {
    try{...// register a socket with name zygote to communicate with other processes
        registerZygoteSocket(socketName);
        
        // ② Preload required resources to the VM, such as class, Resource, OpenGL, public Library, etc.;
        // All children of the fork share this space without reloading, reducing application startup time.
        // But it also increases the boot time of the system, one of the most time-consuming parts of Android startup.
        preload();
        
        // The initialization of gc is only to notify the VM to collect garbage. The specific collection time and how to collect garbage are determined by the VM's internal algorithm.
        // Gc () should be done before fork, so that future copied child processes have as little garbage memory unfreed as possible;
        gcAndFinalize();

        // start system_server, fork a Zygote child process
        if (startSystemServer) {
            startSystemServer(abiList, socketName);
        }

        // enter the loop mode, get the client connection and process it
        runSelectLoop(abiList);
        
        // ⑥ Close and clean the Zygote socket
        closeServerSocket();
    } catch (MethodAndArgsCaller caller) {
        caller.run();
    } catch (RuntimeException ex) {
        Log.e(TAG, "Zygote died with exception", ex);
        closeServerSocket();
        throwex; }}Copy the code

With the startSystemServer () :

private static boolean startSystemServer(String abiList, String socketName)
        throws MethodAndArgsCaller, RuntimeException {
    int pid;
    try{...// fork out the system_server process and return pid, where pid is 0
        pid = Zygote.forkSystemServer(
            parsedArgs.uid, parsedArgs.gid,
            parsedArgs.gids,
            parsedArgs.debugFlags,
            null,
            parsedArgs.permittedCapabilities,
            parsedArgs.effectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        /* Enter child process */
        if (pid == 0) {
            // Android 5.0 has two Zygote processes: Zygote and Zygote64
            // If there are two zygote processes, wait until the second zygote is created.
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }
            // Complete the rest of the system_server process
            handleSystemServerProcess(parsedArgs);
        }
        return true;
}
Copy the code

The Tips: fork() method is called once and returns two times. The difference is that the child process returns 0 and the parent process returns the child process ID, which guarantees that the child process cannot have a process ID of 0.

0x5, Xposed how to Hook Zygote

From the above trace, it is not difficult to derive a chain of calls like this:

Rc → app_process(app_main.cpp) → Start Zygote process → ZygoteInit main() → startSystemServer() → Fork the system_server child process.

Then look at how Xposed specific injection Zygote, open the Xposed warehouse, found such two files:

em… To distinguish between Android 5.0 and earlier versions, open android. mk:

You can customize the app_process file and select the corresponding file as the entry according to the SDK version. Open app_main2.app and go to the following location:

Xposed. CPP — > Initialize

bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
    // install the xposed modulexposed->zygote = zygote; xposed->startSystemServer = startSystemServer; xposed->startClassName = className; xposed->xposedVersionInt = xposedVersionInt; .// ② Load xposedBridge. jar to the system CLASSPATH path.
    return addJarToClasspath(a); }Copy the code

Initialization jump back to the front, if successful, call call XPOSED_CLASS_DOTS_ZYGOTE DE. Namely robv. Android. Xposed. XposedBridge main () method, if the initialization failed, is initialized according to the normal process. XposedBridge → main()

To this Xposed Hook Zygote process is very clear:

Build custom app_process → call xposedinit.main () instead of zygoteinit.main () → Hook resources and some prep work → call the system method that started Zygote originally.

0x6, How to replace system app_process Xposed

The app_process of the system is replaced by a custom one.

That will have to look at the XposedInstaller, in addition to introducing the management of the Xposed module, it has two core functions: replace the system app_process and put the XposedBridge.jar file in a private directory.

Open the XposedInstaller project and search for app_process:

You can’t find it in Code, but you can find it in commit. Go to Assets /install.sh

The process is simple and clear, change the permission to move files, but this is the old version, but in the new version did not find this keyword.

Open the phone XposedInstaller grab the package, found this request:

http://dl-xda.xposed.info/framework.json
Copy the code

Then search for the domain name in the project:

A little different from the capture url, request under framework.json:

Can, is the splicing zip package URL template, including: system SDK version, architecture, Xposed version number, to my magic blue E2 as an example, after the SPLicing URL:

http://dl-xda.xposed.info/framework/sdk23/arm64/xposed-v89-sdk23-arm64.zip
Copy the code

After downloading and decompressing, I started looking for the transport script, but only found the binaries, so library, and xposedBridge.jar:

Install via Recovery and Install via Recovery with XposedInstaller

FrameworkZips → INSTALLER

It’s easy to see that this function is used to parse framework.json

Where parseZipSpec() is called:

Where is getOnline() called:

StatusInstallerFragment → addZipViews() → addZipView() → showActionDialog()

The flash() method is the spawn method, and you can see that the second parameter type is different: FlashDirectly and FlashRecoveryAuto, following the former’s flash() method:

Look again at the flash() method of FlashRecoveryAuto:

When the system is in recovery mode, the system automatically checks whether the command file exists. The commands in the command file are the same as those in the command file.

The update-binary will call a flash-script.sh file, which can be found in the zip package:

em… Obviously, is to replace the file script, also made a distinction between 32 and 64 bit of the case, verification up some trouble, if I compile Xposed after the verification of it, remember this conclusion:

XposedInstaller downloads the patch package → Obtain root permission → decompress the update-binary file and copy it to a specific directory → the flash-script.sh script is invoked when the file is executed. Write app_process, xposedBridge. jar, so library, etc to the system private directory.

0x7. APP starts the process of creating the process

In understanding how to inject the Xposed APP process, first to understand the APP to start the process of creating the process.

When we click on a desktop icon to start the application (note: the Launcher is also an APP), layer upon layer calls last walk to: ActivityStackSupervisor. StartSpecificActivityLocked, judging process does not exist would be to create a new process. The process goes something like this:

APP process → Binder mechanism → notify ActivityManagerService(AMS) in system_server → LocalSocket in system_server → notify Zygote process → Fork an APP child process.

Interlude: Why do SystemServer processes communicate with Zygote processes using sockets instead of binders?

A: Binder communication is multi-threaded. There may be a situation where the Binder parent thread has a lock and the fork child has a lock, but the child of the parent process is not copied. In this case, the child process will be deadlocked. To avoid this, fork does not allow multiple threads to use sockets instead.

The fork child receives the Socket notification and returns to zygoteinit.runselectLoop (), at which point it enters polling mode and waits for the client to connect and process:

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
    ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
    FileDescriptor[] fdArray = new FileDescriptor[4];
    while (true) {
        int index;
        / /...
        if (index < 0) {
            throw new RuntimeException("Error in select()");
        } else if (index == 0) {
            // Create ZygoteConnection and add it to FDS
            ZygoteConnection newPeer = acceptCommandPeer(abiList);
            peers.add(newPeer);
            fds.add(newPeer.getFileDescriptor());
        } else {
            // Receives data from the peer through the socket and performs corresponding operations
            boolean done;
            done = peers.get(index).runOnce();
            if (done) {
                peers.remove(index);
                fds.remove(index);  // Remove the file descriptor after processing}}}}Copy the code

AcceptCommandPeer () gets the connected client and executes zygoteconnection.runonce () to process the request. RunOnce () :

// fork child process
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                    parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                    parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
                    parsedArgs.appDataDir);

if (pid == 0) {
    // Child process execution
    IoUtils.closeQuietly(serverPipeFd);
    serverPipeFd = null;
    // Enter the child process flow
    handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
    return true;
} else {
    // The parent process executes
    IoUtils.closeQuietly(childPipeFd);
    childPipeFd = null;
    // Enter the parent process flow
    return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
Copy the code

HandleChildProc (); the key code is:

if(parsedArgs.invokeWith ! =null) {
    WrapperInit.execApplication(parsedArgs.invokeWith,
            parsedArgs.niceName, parsedArgs.targetSdkVersion,
            pipeFd, parsedArgs.remainingArgs);
} else {
    RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
            parsedArgs.remainingArgs, null /* classLoader */);
}
Copy the code

Runtimeinit.zygoteinit () :

public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
        throws ZygoteInit.MethodAndArgsCaller {
    if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
    redirectLogStreams();
    // General initialization
    commonInit();
    // Initialize Zygote
    nativeZygoteInit();
    // Apply initialization
    applicationInit(targetSdkVersion, argv, classLoader);
}
Copy the code

ApplicationInit (). The key code is as follows:

private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
        throws ZygoteInit.MethodAndArgsCaller {
    // True means appruntime.onexit () is not called when the APP exits, otherwise it will be called before the APP exits
    nativeSetExitWithoutCleanup(true);
    // Set the memory usage of the VM to 0.75
    VMRuntime.getRuntime().setTargetHeapUtilization(0.75 f);
    VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
    // Parameter parsing
    final Arguments args;
    try {
        args = new Arguments(argv);
    } catch (IllegalArgumentException ex) {
        Slog.e(TAG, ex.getMessage());
        return;
    }
    / / the args. StartClass for android. App. ActivityThread, calling it the main method
    invokeStaticMain(args.startClass, args.startArgs, classLoader);
}
Copy the code

Activitythread.main (). The key code is as follows:

public static void main(String[] args) {
    // Create a Looper for the main thread
    Looper.prepareMainLooper();
    / / AMS
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    // Initialize the main thread Handler
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    // Start the main thread message loop
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code

At this point, the child process and the main thread are created, and then the initialization is done. In this case, the Application is started. Follow the thread.

// obtain the AMS proxy object
final IActivityManager mgr = ActivityManager.getService();
try {
    // 2 Use the proxy object to call attachApplication() for information needed to start the application.
    mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
    throw ex.rethrowFromSystemServer();
}
Copy the code

To: ActivityManagerService. AttachApplication () :

public final void attachApplication(IApplicationThread thread, long startSeq) {
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid, callingUid, startSeq);/ / 1Binder.restoreCallingIdentity(origId); }}Copy the code

To: attachApplicationLocked ()

 private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    // Obtain the process information stored in AMS based on the PID
    ProcessRecord app;
    long startTime = SystemClock.uptimeMillis();
    if(pid ! = MY_PID && pid >=0) {
        synchronized(mPidsSelfLocked) { app = mPidsSelfLocked.get(pid); }}else {
        app = null;
    }
    // IApplicationThread is an internal class of ActivityThread that manages the communication with AMS.
    // ActivityThread is notified to start the Application
    thread.bindApplication(processName, appInfo, providers, null, profilerInfo, 
        null.null.null, testMode, mBinderTransactionTrackingEnabled, enableTrackAllocation, isRestrictedBackupMode || ! normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat,
        getCommonServicesLocked(app.isolated),
        mCoreSettingsObserver.getCoreSettingsLocked(),
        buildSerial, isAutofillCompatEnabled);
     // ...
}
Copy the code

With the bindApplication () :

public final void bindApplication(String processName, ApplicationInfo appInfo, //... AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    data.providers = providers;
    / /...
    sendMessage(H.BIND_APPLICATION, data);
}
Copy the code

Initialize the AppBindData instance to do some initialization and then send a BIND_APPLICATION message to the message queue. With the handleMessage () :

public void handleMessage(Message msg) {
    switch (msg.what) {
        case BIND_APPLICATION:
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
            AppBindData data = (AppBindData)msg.obj;
            handleBindApplication(data);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
Copy the code

Following handleBindApplication(), the core code is as follows:

private void handleBindApplication(AppBindData data) {
    // Register the UI thread as a running virtual machine
    VMRuntime.registerSensitiveThread();
   
    // Create the context object
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    updateLocaleListFromAppContext(appContext,
          mResourcesManager.getConfiguration().getLocales());
          
    // Create Instrumentation instances to create, start, and track the lifecycle of the Application.
    try {
        final ClassLoader cl = instrContext.getClassLoader();
        mInstrumentation = (Instrumentation)
            cl.loadClass(data.instrumentationName.getClassName()).newInstance();
    } catch (Exception e) {
        throw new RuntimeException(
            "Unable to instantiate instrumentation "
            + data.instrumentationName + ":" + e.toString(), e);
    }
    
    // Create the Application object
    Application app;
    app = data.info.makeApplication(data.restrictedBackupMode, null);
    
    // Call the Instrumentation onCreate(), the internal is empty implementation
    mInstrumentation.onCreate(data.instrumentationArgs);
    
    // The application's onCreate() is actually called internally
    mInstrumentation.callApplicationOnCreate(app);
Copy the code

Summarize the following flow chart:

Figure out the APP to start the process of creating the process, and then to How Kangkang Xposed HOOK APP process.

0x8, How Xposed Hook APP process

1) XposedInit. InitForZygote ()

Go back to the above Hook Zygote xposedinit.initForZygote (), follow:

if (needsToCloseFilesForFork()) {
    XC_MethodHook callback = new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            XposedBridge.closeFilesBeforeForkNative();
        }

        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            XposedBridge.reopenFilesAfterForkNative();
        }
    };

    Class<?> zygote = findClass("com.android.internal.os.Zygote".null);
    hookAllMethods(zygote, "nativeForkAndSpecialize", callback);
    hookAllMethods(zygote, "nativeForkSystemServer", callback);
}
Copy the code

Hook com. Android. Internal. OS. The Zygote class nativeForkAndSpecialize () and nativeForkSystemServer () method, add the Hook callback, continue to go down:

// normal process initialization (for new Activity, Service, BroadcastReceiver etc.)
findAndHookMethod(ActivityThread.class, "handleBindApplication"."android.app.ActivityThread.AppBindData".new XC_MethodHook() {
	@Override
	protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
	
	    // Obtain instances of activityThread, ApplicationInfo, and ComponentName
		ActivityThread activityThread = (ActivityThread) param.thisObject;
		ApplicationInfo appInfo = (ApplicationInfo) getObjectField(param.args[0]."appInfo");
		String reportedPackageName = appInfo.packageName.equals("android")?"system" : appInfo.packageName;
		SELinuxHelper.initForProcess(reportedPackageName);
		ComponentName instrumentationName = (ComponentName) getObjectField(param.args[0]."instrumentationName");
		
		// Check whether ComponentName is null. Null indicates that hook failed
		if(instrumentationName ! =null) {
			Log.w(TAG, "Instrumentation detected, disabling framework for " + reportedPackageName);
			XposedBridge.disableHooks = true;
			return;
		}
		CompatibilityInfo compatInfo = (CompatibilityInfo) getObjectField(param.args[0]."compatInfo");
		if (appInfo.sourceDir == null)
			return;

		setObjectField(activityThread, "mBoundApplication", param.args[0]);
		loadedPackagesInProcess.add(reportedPackageName);
		
		// Get the LoadedApk instance and set the resource directory
		LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
		XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir());
		
		// Initialize LoadPackageParam and plug the packageName, processName, classLoader, and so on
		XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);
		lpparam.packageName = reportedPackageName;
		lpparam.processName = (String) getObjectField(param.args[0]."processName");
		lpparam.classLoader = loadedApk.getClassLoader();
		lpparam.appInfo = appInfo;
		lpparam.isFirstApplication = true;
		
		// Pass the lpparam parameter to all Xposed modulesXC_LoadPackage.callAll(lpparam); }});Copy the code

Here’s the simple logic:

  • (1) the hooksActivityThreadhandleBindApplication(), the parameter type is **android.app.ActivityThread.AppBindData** In this case, you can see the App to start the creation process part of the code;
  • 2.param.args[0]Is the above **AppBindData** parameter, which gets the properties in this parameter and overwrites some properties;
  • (3) byactivityThreadExample,loadedApkExamples, get the **ClassLoader**, then pass to all Xposed modules;

Still relatively simple, in the Application call applica.onCreate (), to pass the ClassLoader to all Xposed modules.

(2) findAndHookMethod ()

With the findMethodExact () :

With the: XposedBridge hookMethod () :

(3) hookMethodNative ()

CPP, libxposed_art. CPP, libxposed_art. CPP, libxposed_art. CPP

HookedMethodCallback () :

With the: methodXposedBridgeHandleHookedMethod

Well, the final method called is the handleHookedMethod() in XposedBridge.

(4) handleHookedMethod ()

Here are better understood, is the cycle in order to: beforeHookedMethod, the original method, afterHookMethod calls again, and invoke the original method call: XposedBridge_invokeOriginalMethodNative is as follows:


0 x9, summary

Above is this section from the source code level to explore the basic realization principle of Xposed all content, with my current level, it is too difficult to dig down… Sincerely admire the author Rovo89 big guy Linux, Android system foundation, and heard that is a spare time to write, TQL! But in the process of exploration in addition to the realization of Xposed have some understanding, along with some basic posture of the Android system (system, Zygote and App process start process), benefit a lot! Here is an interview essay summarizing this question:

  • Xposed requires root permission, install XposedInstaller get root permission, execute update-binary app_process, xposedBridge-jar, so library and so on to the system private directory;
  • Rc, start Zygote process through app_process, Zygote is the parent process of all APP processes;
  • Xposed app_process to the original zygoteinit. main() to call xposedinit. main(), perform some Hook work to call the original start Zygote method;
  • When the child process forks, it not only gets a copy of the virtual machine instance, but also shares the Java runtime library with Zygote, so it only needs to inject XposedBridge.jar into Zygote once to achieve global injection.

0x? Meal: how to detect Xposed

Content excerpt from: Android Hook Technology prevention Talk

Java layer detection

① View the installation list through PackageManager

PackageManager packageManager = context.getPackageManager();
List applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo: applicationInfoList) {
    if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {
        // is Xposed TODO... }
    }
Copy the code

No what use, Hook under PackageManager → getInstalledApplications() does not return xposed package name, some people compile the Xposed package name is not necessarily this.

(2) Create the exception read stack

Procedure method exceptions in the stack will appear Xposed related figure, self-made exception try-catch, judge the log information whether there is Xposed call method:

try {
    throw new Exception("blah");
} catch(Exception e) {
    for (StackTraceElement stackTraceElement: e.getStackTrace()) {
        / / stackTraceElement. GetClassName () of stackTraceElement. GetMethodName () whether in Xposed}}Copy the code

Check that keyword Java methods become Native JNI methods

Reflectance call Modifier. IsNative (method.getModifiers()) check whether ordinary Java methods become Native JNI methods, if it is likely to be Xposed Hook, Of course, Xposed can also Hook this method return value to avoid.

Reflection reads the XposedHelper fields

Reflection traversal of XposedHelper class fieldCache, methodCache, constructorCache variables, read HashMap cache fields, such as field items in the key contains the App only or sensitive methods, can think that there is Xposed injection.

boolean methodCache = CheckHook(clsXposedHelper, "methodCache", keyWord);

private static boolean CheckHook(Object cls, String filedName, String str) {
    boolean result = false;
    String interName;
    Set keySet;
    try {
        Field filed = cls.getClass().getDeclaredField(filedName);
        filed.setAccessible(true);
        keySet = filed.get(cls)).keySet();
        if(! keySet.isEmpty()) {for (Object aKeySet: keySet) {
                interName = aKeySet.toString().toLowerCase();
                if (interName.contains("meituan") || interName.contains("dianping") ) {
                    result = true;
                    break; }}}...return result;
}
Copy the code

Native level detection

Java layer and what kind of detection can hook corresponding API to bypass detection, Xposed general hook can not Native layer, so you can use C in the Native layer to parse /proc/self/maps file, Check whether there are xposedBridge. jar, Dex, JAR and So library files in the libraries loaded by the App itself.

bool is_xposed() { bool rel = false; FILE *fp = NULL; char* filepath = "/proc/self/maps"; . string xp_name = "XposedBridge.jar"; fp = fopen(filepath,"r")) while (! feof(fp)) { fgets(strLine,BUFFER_SIZE,fp); origin_str = strLine; str = trim(origin_str); if (contain(str,xp_name)) { rel = true; // Detected Xposed module break; }}... }Copy the code

Again, Hook the File class directly and point it to another path: XposedHider

In addition, on Github to see a XposedChecker comparison full Xposed detection scheme, JNI scheme can draw on a wave ~


References:

  • Xposed injection analysis and non – restart customization

  • How does Zygote process fork an APP process in Android

  • Blog.csdn.net/ascii2/arti…

  • Do what you want Xposed is how to achieve?

  • Application startup process (most detailed & simplest)

  • Android Hook framework Xposed principle and source code analysis