一、开发流程
开发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、一切的开始
IXposedHookLoadPackage
和IXposedHookInitPackageResources
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.pngIXposedHookInitPackageResources的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'
}
查找类
//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() {});
本文完。
网友评论