Xposed common inverse function

Keywords: Android Xposed

1. Create Xposed project

Create an app project in Android Studio, modify the AndroidManifest.xml file, and add the following code in the < Application > < / Application > tag

<meta-data
	android:name="xposedmodule"
	android:value="true" />
<meta-data
	android:name="xposeddescription"
	android:value="hello xposed" />
<meta-data
	android:name="xposedminversion"
	android:value="82" />

Add Xposed dependency libraries, api-82.jar and api-82-sources.jar, in the app/libs directory (search on the Internet)

Modify the build.gradle file in the app directory and add the above two dependency libraries to dependencies {}, as follows

dependencies {
    compileOnly 'de.robv.android.xposed:api:82'
    compileOnly 'de.robv.android.xposed:api:82:sources'
    
    // other content
    ......
}

Create an assets folder in the mian directory and an xposed folder in the assets directory_ Init file, in which the implementation is written   Class name and package name of IXposedHookLoadPackage interface; For example, I have created a new class TestReverse to implement it   IXposedHookLoadPackage interface, and the current package name is com.test.xposed, then xposed_ Content in init should be

com.test.xposed.TestReverse

2. Introduction to relevant API s

Xposed module API usage: XposedHelpers | Xposed Framework API

a.IXposedHookLoadPackage interface. The class that implements hook logic must implement this interface. When the APP is loaded, it will call the   handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) method, so we need to rewrite this function and implement our hook logic in it

b.XC_LoadPackage.LoadPackageParam, which contains information about the application being loaded. Enter, where packageName can be used to judge the APP name of the current hook, processName represents the process of the current APP, and classloader represents the classloader used by the current APP.

  c.XposedHelpers class

        1.findClass(String className, ClassLoader classLoader), use a specific ClassLoader to find the specified class, and the return value is the class of the specified class; Use the following

final Class<?> callContextClass = XposedHelpers.findClass("com.alibaba.ariver.engine.api.bridge.model.NativeCallContext",lpparam.classLoader);

        2. Xposedhelpers. Findandhookmethod (string classname, classloader, classloader, string methodname, object... Parametertypesandcallback), the method specified by hook.

         among   Object... parameterTypesAndCallback is a normal form in java, which can pass multiple parameters; When using the XposedHelpers.findAndHookMethod() method, you need to pass the bytecode of all parameters required by the hook function. These bytecodes can be obtained through. class or findClass reflection; java's own type, such as String, can be obtained with String.class.

         Callback object XC_MethodHook() needs to rewrite two methods beforeHookedMethod(MethodHookParam param): the code logic in this method will be executed before the hook method is called; The code logic in the afterHookedMethod(MethodHookParam param) method will be executed after the hook method call is completed.

         The following code logic indicates that the dispatchOnUiThread() function under the com.baidu.swan.apps.jsbridge.SwanAppGlobalJsBridge class has been hook. Before and after the function is executed, the logic in beforeHookedMethod() and afterHookedMethod() will be executed respectively.

XposedHelpers.findAndHookMethod("com.baidu.swan.apps.jsbridge.SwanAppGlobalJsBridge", lpparam.classLoader, "dispatchOnUiThread", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    XposedBridge.log("Baidu: dispatchOnUiThread Thread:" + Thread.currentThread().getName());
                    XposedBridge.log("Baidu:dispatchOnUiThread Input:" + java.net.URLDecoder.decode(param.args[0].toString()) + "Return value:" + param.getResult());
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    Field mCallbackHandler = jsbridge_a.getDeclaredField("mCallbackHandler");
                    Object mmCallbackHandler = (Object) mCallbackHandler.get(param.thisObject);
                    //com.baidu.swan.apps.core.slave.SwanAppWebViewWidget
                    XposedBridge.log("Baidu: mCallbackHandler stay webview Call in API Implementation class for:" + mmCallbackHandler.getClass().getName());
            });

        3. XposedHelpers.findAndHookConstructor(String className, ClassLoader classLoader, Object...   parameterTypesAndCallback), the meaning of the parameter is the same as findAndHookMethod().

         The following code logic shows that when the constructor of com.baidu.swan.apps.jsbridge.SwanAppNativeSwanJsBridge class is called, the logic in beforeHookedMethod() and afterHookedMethod() will be executed respectively before and after the constructor is executed.

XposedHelpers.findAndHookConstructor("com.baidu.swan.apps.jsbridge.SwanAppNativeSwanJsBridge", lpparam.classLoader, container_a, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Log.d("Baidu:", "SwanAppNativeSwanJsBridge()constructors parameters : " + param.args[0].getClass().getName());
                }

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

d.   XposedBridge class

  1. XposedBridge.log(String text) to write messages to the Xposed error log. Used to print the information you want to know

  2. XposedBridge.hookMethod(Member hookMethod, XC_MethodHook callback), hook any method or constructor with a specific callback method; This method does not require as many parameters as XposedHelpers.findAndHookMethod(). The following code logic is used to get the corresponding method instance from a class, and then pass the method object to XposedBridge.hookMethod() method to hook the method directly

            for(final Method method: TestClass.getDeclaredMethods()){
                XposedBridge.hookMethod(method, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        XposedBridge.log("Baidu:share Medium" + method.getName() + "Called");
                        printStack();
                    }
                });
            }

3. Print function call stack

In the framework logic of reverse APP, when a hook lives in a function that is successfully triggered, you often need to check its call stack, that is, the upper calling function of the function. This information will be of great help to our reverse APP framework.

private void printStack() {
        // Get StackTraceElement [] of thread
        Throwable ex = new Throwable();
        StackTraceElement[] stackElements = ex.getStackTrace();
        if (stackElements != null) {
            for (int i = 0; i < stackElements.length; i++) {
                XposedBridge.log("Baidu: Dump Stack" + i + ": " + stackElements[i].getClassName()
                        + "----" + stackElements[i].getFileName()
                        + "----" + stackElements[i].getLineNumber()
                        + "----" + stackElements[i].getMethodName());
            }
        }
        XposedBridge.log("Baidu: Dump Stack: ---------------over----------------");
    }

4. Modify the parameters of hook function

        1. Modify parameters of basic data type and String type

         Direct assignment is sufficient, as follows:

param.args[0] = 5; // int type
param.args[1] = "hello"; //String type

         two   Modify parameters of basic data type and String type

         Get the reference first and then modify it; for example

Map<String, String> target = (Map<String, String>) param.args[0]; // Map
target.put("name", "tnoy"); //Add data to Map

        3. Modify the member variable in the instance of the class

         First obtain the corresponding member variable with Field, and then modify it with Field.set (class instance, modified value); for example

final Class<?> model_d = XposedHelpers.findClass("com.alipay.android.phone.globalsearch.model.d", lpparam.classLoader);
Object dVar = param.args[0];
Field b = model_d.getDeclaredField("b"); // b is a public String variable in the model class
b.set(dVar, "search_auto");

        4. Modify the array of Object [] type

         For an element in the Object array, first use. getClass().getName() to get the type of the element, then go to the class to see the corresponding Field to be modified, then carry out the operation of the third point above, finally package it into a new Object [], and then assign the value of this Object [] to param.args[i]. for example

Object[] obj = (Object[]) param.args[2];
for(Object o: obj){
if(o != null){
 // Judge whether the class of the current Object is the one we need
        if(o.getClass().getName().equals("com.alipay.mobile.aompfavorite.base.rpc.request.MiniAppHistoryRequestPB")){
        // In jadx, the source code is: List < miniapphistoryreqitempb > miniappitems;
        Field miniAppItems = MiniAppHistoryRequestPB.getDeclaredField("miniAppItems");
        // First use the list < Object > reflection to get the list < miniapphistoryreqitempb >
        List<Object> item = (List<Object>) miniAppItems.get(o);
        // Gets the first value of the List
        Object item_1 =  item.get(0);
        Log.d("Mini Before modification", o.toString());
        Log.d("Mini List[0]", item_1.toString());
        // Member variable appId in MiniAppHistoryReqItemPB class
        Field appId = MiniAppHistoryReqItemPB.getDeclaredField("appId");
        String AppId = (String) appId.get(item_1);
        Log.d("Mini appid", AppId);
        appId.set(item_1, "2018112262208014");
        Log.d("Mini After modification", o.toString());
        }
        Log.d("Mini handler_1.1", o.getClass().getName() + " " + o.toString());
    }
}

5. hook dynamically loaded classes

        In the APP framework, some code is dynamically loaded, such as plug-in development. Using the following code, you can hook the dynamically loaded functions

XposedBridge.hookAllMethods(ClassLoader.class, "loadClass", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {

                    if (param.hasThrowable()) return;
                    if (param.args.length != 1) return;

                    Class<?> cls = (Class<?>) param.getResult();
                    String name = cls.getName();

                    // Determine whether it is the dynamically loaded class of hook
                    if ("com.bytedance.webview.chromium.ContentSettingsAdapter".equals(name)) {
                        // Specify the method to hook
                        XposedBridge.hookAllMethods(cls,
                                "setJavaScriptEnabled",
                                new XC_MethodHook() {
                                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                                        try {
                                            XposedBridge.log("commonRe: setJavaScriptEnabled Called: " + param.args[0]);
                                            printStack();
                                        } catch (Exception e) {
                                            XposedBridge.log("commonRe: hook error!");
                                        }
                                    }
                                });
                    }
                }
            });

  six   Print the storage location of dynamically loaded classes (get plug-in apk)

final Class<?> DexPathList = XposedHelpers.findClass("dalvik.system.DexPathList", lpparam.classLoader);

           for (final Method method: DexPathList.getDeclaredMethods()){
                XposedBridge.hookMethod(method, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        // The splitPaths() method passes in the path of the dynamically loaded class in the mobile phone
                        if(method.getName().equals("splitPaths")){
                            XposedBridge.log("Baidu: DexPathList Medium" + method.getName() + "Called with the parameter" + param.args[0]+" " + param.args[1]);
                        }
                }
                }) ;
            }

  7. Automated hook method script

In some cases, we have the signature of a series of methods that need to hook, and when we need to hook these methods to obtain some information, we can hook these methods one by one through automation, so we don't need to write the footbook of each method of hook one by one manually.

The code example is as follows

StaticInfo.java: used to read the signature of the hook method from the json file and obtain the corresponding method object through reflection
package com.example.ttest;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class StaticInfo {
    public static HashMap<PoolHook.normalHandlerHook, Method> HookMethods = new HashMap<>();
    public XC_LoadPackage.LoadPackageParam lpparam;

    public StaticInfo(XC_LoadPackage.LoadPackageParam lpparam) throws NoSuchFieldException {
        this.lpparam = lpparam;
        // read from file
        try{
            InputStream is = new FileInputStream(new File("/sdcard/tmp/static_info.json"));
            init(is);
            is.close();
        }catch (Exception e){
            XposedBridge.log("AMZ: read json error! " + e.toString());
        }
    }

    public void init(InputStream in) throws IOException, NoSuchFieldException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
        StringBuilder sb = new StringBuilder();
        String line;
        String ls = System.getProperty("line.separator");
        while ((line = bufferedReader.readLine()) != null){
            sb.append(line);
            sb.append(ls);
        }
        String text = sb.toString();
        XposedBridge.log("AMZ: read json File success!");
        JSONArray jsonArray = JSON.parseArray(text);
        for (int i=0; i<jsonArray.size(); i++){
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String signature = jsonObject.getString("signature");
            String filedName = jsonObject.getString("field");
            String type = jsonObject.getString("type");
            String declaringClass = jsonObject.getString("declaringClass");
            reflectMethod(signature, filedName, type, declaringClass);
        }
    }

    public void init(String text) throws NoSuchFieldException {
        JSONArray jsonArray = JSON.parseArray(text);
        for (int i=0; i<jsonArray.size(); i++){
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String signature = jsonObject.getString("signature");
            String filedName = jsonObject.getString("field");
            String type = jsonObject.getString("type");
            String declaringClass = jsonObject.getString("declaringClass");
            reflectMethod(signature, filedName, type, declaringClass);
        }
    }

    // init HashMap for Method and Field due to json file
    public void reflectMethod(String signature, String fieldName, String type, String declaringClass) throws NoSuchFieldException {
        XposedBridge.log("AMZ: Reflection acquisition in progress Method: "+ signature  + " " + fieldName + " " + type);
        Field field = XposedHelpers.findClass(declaringClass, lpparam.classLoader).getDeclaredField(fieldName);
        PoolHook.normalHandlerHook hook = new PoolHook.normalHandlerHook(fieldName,type, signature, field);
        String className = signature.split(" ")[0].substring(1,signature.split(" ")[0].length()-1);
        XposedBridge.log("AMZ: parse classname: " + className);
        try {
            Class<?> clazz = XposedHelpers.findClass(className, this.lpparam.classLoader);
            String methodName = signature.split(" ")[2].split("\\(")[0];
            ArrayList<String> paramTypes = new ArrayList(Arrays.asList(signature.split(" ")[2].split("\\(")[1].substring(0,signature.split(" ")[2].split("\\(")[1].length()-2).split(",")));
            XposedBridge.log("AMZ: The parameter of the method to find is " + paramTypes.toString());
            for (Method method: clazz.getDeclaredMethods()){
                XposedBridge.log("AMZ: Matching method:" + method.getName() + " ==> " + methodName);
                XposedBridge.log("AMZ: Number of current method parameters:" + method.getParameterTypes().length + " Number of parameters of target method:" + paramTypes.size());
                if (method.getName().equals(methodName)){
                    if (method.getParameterTypes().length == paramTypes.size()){
                        XposedBridge.log("AMZ: The method name matches the number of parameters");
                        ArrayList<String> paramTypesCopy = (ArrayList<String>) paramTypes.clone();
                        for (Class paramType: method.getParameterTypes()){
                            XposedBridge.log("AMZ: Parameter type of current method:" + paramType.getName());
                            if (paramTypes.contains(paramType.getName())){
                                paramTypesCopy.remove(paramType.getName());
                            }
                        }
                        if (paramTypesCopy.size() == 0 && !HookMethods.containsKey(hook)){
                            XposedBridge.log("AMZ: find target method: " + method.toString() + " target_field: " + field);
                            HookMethods.put(hook, method);
                        }
                    }
                    else if (method.getParameterTypes().length == 0 && paramTypes.size() ==1 && paramTypes.get(0).length() == 0){
                        XposedBridge.log("AMZ: The target method has no parameters, matching succeeded");
                        if (!HookMethods.containsKey(hook)){
                            XposedBridge.log("AMZ: find target method: " + method.toString() + " target_field: " + field);
                            HookMethods.put(hook, method);
                        }
                    }
                }
            }
        }catch (Exception e){
            XposedBridge.log("AMZ: " + e.toString());
        }
    }
}

PoolHook.java: Inheritance   IXposedHookLoadPackage interface, write hook logic

package com.example.ttest;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class PoolHook implements IXposedHookLoadPackage{
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable{
        new StaticInfo(lpparam);
        if (lpparam.packageName.equals("com.eg.android.AlipayGphone")){
            XposedBridge.log("AMZ: hook start!!");
            hookPackage(lpparam);
        }
    }

    private void hookPackage(XC_LoadPackage.LoadPackageParam lpparam){
        if (StaticInfo.HookMethods.isEmpty()){
            XposedBridge.log("AMZ: HashMap is empty");
        }else {
            XposedBridge.log("AMZ: HashMap is not empty");
        }
        for (Map.Entry<normalHandlerHook, Method> entry: StaticInfo.HookMethods.entrySet()){
            XposedBridge.hookMethod(entry.getValue(), entry.getKey());
        }
    }

    static class normalHandlerHook extends XC_MethodHook{
        private String fieldName;
        private String type;
        private Field field;
        private String signature;

        public normalHandlerHook(String fieldName, String type, String signature, Field field){
            this.fieldName = fieldName;
            this.type = type;
            this.signature = signature;
            this.field = field;
        }

        protected void beforeHookedMethod(MethodHookParam param) throws Throwable{
            System.out.println("AMZ: current method: " + param.method);
            XposedBridge.log("AMZ: hook field right now:" + field.toString());
            XposedBridge.log("AMZ: pool type:" + this.type);
            this.field.setAccessible(true);
            if (this.type.equals("HashMap")){
                XposedBridge.log("AMZ: this is HashMap");
                HashMap<Object, Object> mmap = (HashMap<Object, Object>) this.field.get(param.thisObject);
                for (Map.Entry<Object, Object> entry: mmap.entrySet()){
                    Object key = entry.getKey();
                    Object value = entry.getValue();
                    XposedBridge.log("AMZ: signature=" + this.signature +" key=" + key + " value=" + value);
                }
            }else if(this.type.equals("Map") || this.type.equals("ConcurrentHashMap") || this.type.equals("LinkedHashMap")){
                XposedBridge.log("AMZ: this is Map or ConcurrentHashMap");
                Map<Object, Object> mmap_1 = (Map<Object, Object>) field.get(param.thisObject);
                for (Map.Entry<Object, Object> entry: mmap_1.entrySet()){
                    Object key = entry.getKey();
                    Object value = entry.getValue();
                    XposedBridge.log("AMZ: signature=" + this.signature +" key=" + key + " value=" + value);
                }
            }
        }
    }
}

Posted by cosmos33 on Thu, 14 Oct 2021 19:28:42 -0700