美文网首页Xposed
xposed系列2、api熟悉 handleLoadPackag

xposed系列2、api熟悉 handleLoadPackag

作者: 阿敏其人 | 来源:发表于2018-12-10 10:14 被阅读262次

一、开发流程

开发xposed module,过程无非就是:

  • 1、反编译别人代码(如果混淆或加固的会相对麻烦)
  • 2、猜测别人的代码的作用
  • 3、利用xposed的api。进行开发

说api之间,我们先再来一个例子

来个例子吧

我们hook一下自己的应用。

虽然hook自己的应用没什么意思,但是,重在过程

目的:

  • 1、Hook TextView的内容
  • 2、Hook 方法

代码

MainActivity

public class MainActivity extends AppCompatActivity {

    private EditText mEtAccount;
    private  EditText mEtPwd;
    private Button mBtn;
    public static int staticInt = 12;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mEtAccount = findViewById(R.id.mEtAccount);
        mEtPwd = findViewById(R.id.mEtPwd);
        mBtn = findViewById(R.id.mBtn);
        mBtn.setText("set一个登录");
        
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String acc= mEtAccount.getText().toString();
                String pwd= mEtPwd.getText().toString();

                boolean isOk = checkOrRight(acc,pwd);

                if(isOk){
                    Toast.makeText(MainActivity.this,"登录成功",Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(MainActivity.this,"登录失败!!",Toast.LENGTH_SHORT).show();
                }
            }
        });

    }


    private boolean checkOrRight(String acc, String pwd) {
        if(acc.equals("123")&&pwd.equals("asd")){
            return true;
        }else{
            return false;
        }
    }
}

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/mEtAccount"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="请输入账户"
        />

    <EditText
        android:id="@+id/mEtPwd"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="请输入密码"
        />

    <Button
        android:id="@+id/mBtn"
        android:text="登录"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

hook代码

涉及到的一点api

  • findAndHookMethod
    • beforeHookedMethod
    • afterHookedMethod
  • XposedHelpers.findClass
  • XposedHelpers.setStaticIntField
public class XposedMainInit implements IXposedHookLoadPackage {

    private static final String HOOK_APP_NAME = "com.fine.hookfoo";


    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {


        //性能优化,避免操作无关app
        if (!lpparam.packageName.equals(HOOK_APP_NAME))
            return;

        XposedBridge.log("XposedMainInit handleLoadPackage 执行");
        XposedBridge.log("Loaded app: " + lpparam.packageName);


        if (lpparam.packageName.equals("com.fine.hookfoo")) {

            XposedHelpers.findAndHookMethod(
                    "com.fine.hookfoo.MainActivity",
                    lpparam.classLoader,
                    "checkOrRight", // 被Hook的函数
                    String.class, //被Hook函数的第一个参数String
                    String.class, //被Hook函数的第二个参数String
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            super.beforeHookedMethod(param);
                            // Hook函数之前执行的代码

                            // 参数获取

                            //参数1
                            XposedBridge.log("beforeHookedMethod userName:" + param.args[0]);
                            //参数2
                            XposedBridge.log("beforeHookedMethod pwd:" + param.args[1]);
                            //函数返回值
                            XposedBridge.log("beforeHookedMethod result:" + param.getResult());

                            // 参数修改
                            // param.args[0] = "被Hook了";
                        }

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

                            // Hook函数之后执行的代码
                            //通过对 checkOrRight 函数的分析发现,只要修改函数的返回值即可实现注册的破解
                            param.setResult(true);

                            //参数1
                            XposedBridge.log("afterHookedMethod userName:" + param.args[0]);
                            //参数2
                            XposedBridge.log("afterHookedMethod pwd:" + param.args[1]);
                            //函数返回值
                            XposedBridge.log("afterHookedMethod result:" + param.getResult());

                        }
                    }
            );


            // 修改按钮的内容 我们要操作的是MainActivity的onCreate方法,onCreate方法的参数是Bundle类
            // 也提现了 beforeHookedMethod 和 afterHookedMethod 的区别
            String className = "com.fine.hookfoo.MainActivity";
            XposedHelpers.findAndHookMethod(className, lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    // beforeHookedMethod 和 afterHookedMethod 区别的问题,在这里得到很好地提现

                    // 如果我们修改btn文本的操作放在 beforeHookedMethod,那么beforeHookedMethod执行过后
                    // onCreate执行因为也有setText的操作,所以被覆盖,操作不生效

                }

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

                    //获取到当前hook的类,这里是MainActivity
                    Class clazz = param.thisObject.getClass();
                    XposedBridge.log("class name:" + clazz.getName());

                    // 通过反射获取控件,无论parivate或者public
                    Field field = clazz.getDeclaredField("mBtn");

                    // 设置访问权限
                    field.setAccessible(true);

                    Button button = (Button) field.get(param.thisObject);
                    String string = button.getText().toString();
                    XposedBridge.log("原来的文本 " + string);

                    // 修改文案
                    button.setText("我是被劫持修改的mBtn");

                }
            });
        }

        //修改静态变量staticInt的值为99

        Class clazz = XposedHelpers.findClass("com.fine.hookfoo.MainActivity", lpparam.classLoader);
        XposedHelpers.setStaticIntField(clazz, "staticInt", 99);

        int resInt = XposedHelpers.getStaticIntField(clazz, "staticInt");
        XposedBridge.log("get static int value:"+resInt);

        // 待添加匿名内部类
        // 网络请求hook
    }
    
}

小示例代码就到这里了。

其实呢,就是本来比如账号为“123”密码为“asd”才才允许通过,但是经过hook之后,不管输入什么都可以通过了。

核心代码param.setResult(true);

其实就是让该方法永远返回true。


二、api认识

二.1、一切的开始

IXposedHookLoadPackageIXposedHookInitPackageResources

xposed,一切的开始,都是从实现IXposedHookLoadPackage或者IXposedHookInitPackageResources开始的。

IXposedHookLoadPackage

IXposedHookLoadPackage是我们最经常实现的接口。

image.png

其中,XC_LoadPackage.LoadPackageParam参数给我带来几个非常好用的东西

fields type description
packageName String 应用包名
processName String 应用加载后的进程名
classLoader ClassLoader 应用的classloader
appInfo ApplicationInfo 应用的信息,包括verisonCode,uid等

使用示例

以loadClass为例子

//不能通过Class.forName()来获取Class ,在跨应用时会失效
Class c=lpparam.classLoader.loadClass("com.wrbug.xposeddemo.MainActivity");
Field field=c.getDeclaredField("textView");
field.setAccessible(true);
//param.thisObject 为执行该方法的对象,在这里指MainActivity
TextView textView= (TextView) field.get(param.thisObject);
textView.setText("Hello Xposed");

IXposedHookInitPackageResources

这个接口,一看就是和资源相关的。

image.png

IXposedHookInitPackageResources的resparam参数有以下两个字段

field type description
packageName String 应用包名
res XResources 资源相关

resparam.res是一个非常重要的字段。

handleInitPackageResources会在
setContentView(R.layout.activity_main);
和getLayoutInflater().inflate(R.layout.view_demo, null);
时调用。

对setContentView有了解的都明白setContentView也会调用inflate方法。所以,也可以看成是hook了inflate方法。

IXposedHookInitPackageResources执行流程参考文章:https://my.oschina.net/WrBug/blog/888248

二.2、伟大的XposedHelpers

有了一切的开始之后,我们就需要用的XposedHelpers。

它,上天入地,无所不能。

hook所有网络请求

Hook org.apache.http 包中的网络请求,忽略参数然后使用hookAllMethods就可以同时拦截HttpPost、HttpGet、HttpUriRequest类型的网络请求参数。

/*
 * Hook net access
 * abstract class: org.apache.http.impl.client.AbstractHttpClient
 *  function      : execute(HttpHost target, HttpRequest request,HttpContext context)
 *                :execute(HttpUriRequest request, HttpContext context)
 */

hookAllMethods("org.apache.http.impl.client.AbstractHttpClient", lpparam.classLoader,
  "execute", new XC_MethodHook() {

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

这里参数特殊,如果直接强转成String类型然后输出,将会得到无意义的输出,形如:org.apache.http.client.methods.HttpPost@41d45200。所以输出之前可以判断下,具体操作可以类比以下示例代码片段:

    private void print_args(XC_MethodHook.MethodHookParam param) {
        Object arg = param.args[0];
        String argValue = "null";

        if(arg instanceof HttpPost){
            URI uri = ((HttpPost)arg).getURI();
            argValue = String.format("uri=%s ", uri.toString());
        }else if(arg instanceof HttpGet){
            URI uri = ((HttpGet)arg).getURI();
            argValue = uri.toString();
        }else if(arg instanceof HttpUriRequest){
            URI uri = ((HttpUriRequest)arg).getURI();
            argValue = uri.toString();
        } else {
            argValue = arg.toString();
        }
        Log.d("TAG", "args-->>" + argValue);
    }

这里需要注意的是,HttpPost 之类的包在高版本的sdk中已经不存在了,顺利通过编译需要进行以下操作:1. Android studio中修改编译文件,添加

android {
    useLibrary 'org.apache.http.legacy'
}

参考:https://www.freebuf.com/articles/terminal/114910.html

查找类

//className  完整类名,classLoader 类加载器(app应用的类加载器)
public static Class<?> findClass(String className, ClassLoader classLoader)

public static Class<?> findClassIfExists(String className, ClassLoader classLoader)

查找和设置字段

// clazz 通过findClass获取,调用findFieldRecursiveImpl获取
public static Field findField(Class<?> clazz, String fieldName)

public static Field findFieldIfExists(Class<?> clazz, String fieldName)

private static Field findFieldRecursiveImpl(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        try {
            return clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            while (true) {
                clazz = clazz.getSuperclass();
                if (clazz == null || clazz.equals(Object.class))
                    break;

                try {
                    return clazz.getDeclaredField(fieldName);
                } catch (NoSuchFieldException ignored) {}
            }
            throw e;
        }
    }

public static Field findFirstFieldByExactType(Class<?> clazz, Class<?> type)

//获取实例字段的引用
public static Object getObjectField(Object obj, String fieldName)

获取Field的方法,具体实现是在findFieldRecursiveImpl方法里面获取,外部不能访问,Field是通过getDeclaredField获取,所以只能获取static类型的字段。indFirstFieldByExactType()方法是匹配Field的classType,如果类型一样,则返回该字段,该方法的局限性是只能获取到第一个匹配到的字段,后面相同类型的无法获取

设置字段
public static void setXXXField(Object obj, String fieldName, XXX value)
public static void setStaticXXXField(Class<?> clazz, String fieldName, XXX value)

public static Xxx getXxxField(Object obj, String fieldName)
public static Xxx getStaticXxxField(Class<?> clazzj, String fieldName)


查找方法

获取Method方法,还有些其他的方法这里省略,也只能获取静态方法

public static Method findMethodExact(Class<?> clazz, String methodName, Object... parameterTypes) 

public static Method findMethodExactIfExists(Class<?> clazz, String methodName, Object... parameterTypes)
调用方法
public static Object callMethod(Object obj, String methodName, Object... args)

public static Object callMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args)

public static Object callStaticMethod(Class<?> clazz, String methodName, Object... args)

public static Object callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args)

调用实例/静态Method,返回值为方法返回值

public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback)

//通过className和classLoader获取Class<?> ,再调用上面的方法
public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback)

Hook方法的一个方法,其中parameterTypesAndCallback和findConstructorExact方法的parameterTypes类似,不过这里可变数组最后一个对象必须为XC_MethodHook对象或者其子类,前面的对象为参数的ClassType或者类字符串,在hook成功后,当调用hook的方法时,会在XC_MethodHook回调

public abstract class XC_MethodHook extends XCallback {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                //方法调用前的回调
                super.beforeHookedMethod(param);
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                //方法调用后的回调
                super.afterHookedMethod(param);
            }
        }

public abstract class XC_MethodReplacement extends XC_MethodHook{
            @Override
            protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
                //带返回值的方法执行时调用
                return null;
            }
        }
    

可以通过这两个class进行hook监听。

hook 构造器

public static Constructor<?> findConstructorExact(Class<?> clazz, Object... parameterTypes)

public static Constructor<?> findConstructorExactIfExists(Class<?> clazz, Object... parameterTypes) 

public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>... parameterTypes)

获取Constructor方法,其中Object... parameterTypes 是一个Object的可变数组,parameterTypes由Class<?>的可变数组 ,完整类名字符串和XC_MethodHook抽象类 组成。XC_MethodHook为可选参数,并且总在最后一个。XC_MethodHook在这里并无实际意义,Class<?>[] 为相应的构造函数的类型,通过一个例子简单说明,有一个T类,构造函数有三个参数,可以用以下几种方式获取:

public class T {
    String str;
    Context mContext;
    View mView;

    public T(String str, Context context, View view) {
        this.str = str;
        mContext = context;
        mView = view;

    }

}

//方式1:
Constructor constructor = XposedHelpers.findConstructorExact(clazz, String.class, Context.class, View.class);

//方式2:
Constructor constructor = XposedHelpers.findConstructorExact(T.class, String.class, "android.content.Context", View.class);

//方式3:(XC_MethodHook无实际意义)
Constructor constructor = XposedHelpers.findConstructorExact(T.class, String.class, "android.content.Context", View.class, new XC_MethodHook() {});

参考:https://juejin.im/post/5954e5976fb9a06bbe7dab40

本文完。

相关文章

网友评论

    本文标题:xposed系列2、api熟悉 handleLoadPackag

    本文链接:https://www.haomeiwen.com/subject/icfrhqtx.html