Xposed介绍 Xposed 框架是一个强大的 Android 逆向工程工具,它允许开发者在不修改应用程序源代码的情况下,动态地注入和修改Android 应用程序的行为。
Xposed原理 Android 系统运行的核心和起点是 Zygote 进程,所有应用都是通过它 fork 子进程产生的,当系统开始运行时由 init.rc
脚本调用/system/bin/app_process
程序启动Zygote,加载所需的类并调用初始化方法。
Xposed 使用 zygote 进程来注入代码,它会替换/system/bin/app_process
,使其加载Xposed的核心模块XposedBridge.jar
,导致所有从 Zygote fork 出来的子进程(包括应用进程和系统服务进程)都会继承 Xposed 的 Hook 环境。
图解分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [init 进程] | v [/system/bin/app_process] -- 启动 Zygote | v [ZygoteInit.main()] -- 加载核心类和资源 | v [fork 子进程] | +--> [系统服务] (如 system_server) | +--> [应用进程] (如 com.example.app) [Xposed 框架] | +--> 替换 app_process | +--> 劫持 ZygoteInit,加载 XposedBridge | +--> 影响所有子进程,进行 Hook
局限性 在 Android 9 及更高版本中,安全限制增加,Xposed 原框架不支持。
LSPosed开发环境搭建 LSPosed 是一个基于 Xposed 框架的增强工具,专注于模块化的安卓应用定制。
开发准备
面具安装LSPosed模块
Xposed API(XposedBridgeAPI-82.jar,XposedBridgeAPI-89.jar)
环境搭建 Android Studio新建空项目 在app目录下新建一个libs目录(若已有该目录则无需新建),把XposedBridgeAPI.jar放到libs目录下,右击jar包,选择add as library,这一步会在build.gradle里新增一个依赖implementation(files("libs/XposedBridgeAPI-89.jar"))
,需要把implementation
改为compileOnly
。
1 2 implementation 使用该方式依赖的库将会参与编译和打包 compileOnly 只在编译时有效,不会参与打包
修改AndroidManifest.xml文件配置,在<application>
标签内添加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 <meta-data android:name ="xposedmodule" android:value ="true" /> <meta-data android:name ="xposeddescription" android:value ="这是一个Xposed模块" /> <meta-data android:name ="xposedminversion" android:value ="82" />
在main目录下新建一个assets Folder目录,右击main,new->Folder->Assets Folder
。在assets目录下新建一个xposed_init文件(无后缀),用来说明入口类。
在项目包名目录下新建一个模块类lsptest,然后向xposed_init文件里添加一行代码,lsptest1类的全类名com.example.lsposedtest.lsptest
。
实现IXposedHookLoadPackage
接口 在lsptest类中实现IXposedHookLoadPackage
接口。
1 2 3 4 5 6 7 public class lsptest implements IXposedHookLoadPackage { @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { } }
添加Hook逻辑 1 2 3 4 5 6 public class lsptest implements IXposedHookLoadPackage { @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { XposedBridge.log("Bileton->package" +loadPackageParam.packageName); } }
运行 如果出现Default Activity not found
,找到Run/Debug Configurations
,把Launch Options
选项改为Nothing
,然后继续运行。
安装成功后,打开LSPosed,点击模块
点击我们开发的模块,然后勾选要Hook的app,勾选完成之后,不要直接退出,通过返回键,返回到桌面,然后启动勾选的app,可以在Android Studio里面的日志里看到打印出来的App的包名。
日志打印
这样我们的环境就搭建好了。
hook构造函数和普通函数 hookdemo编写 Person类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.example.lsphookdemo;import android.util.Log;public class Person { private String name; private static int age = 50 ; public Person () { this .name="Bileton" ; } public Person (String name) { this (); this .name = name; } public Person (String name, int age2) { this (name); age = age2; } public void setName (String name) { this .name = name; } public static void setAge (int age) { Person.age = age; } public String getName () { return name; } public static int getAge () { return age; } public void print () { Log.d("Bileton" , "Person的属性为:" + this .name + " " + age); } }
布局修改 在布局文件里添加一个按钮button_getname
1 2 3 4 5 6 7 8 9 10 11 <Button android:id ="@+id/button_getname" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="getname" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toTopOf ="parent" app:layout_constraintVertical_bias ="0.0" />
MainActivity类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.example.lsphookdemo;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import androidx.activity.EdgeToEdge;import androidx.appcompat.app.AppCompatActivity;import androidx.core.graphics.Insets;import androidx.core.view.ViewCompat;import androidx.core.view.WindowInsetsCompat;public class MainActivity extends AppCompatActivity implements View .OnClickListener{ @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); EdgeToEdge.enable(this ); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); findViewById(R.id.button_getname).setOnClickListener(this ); } @Override public void onClick (View v) { if (v.getId()==R.id.button_getname){ Log.d("Bileton" ,"GetName:" +new Person ("Bileton" ,21 ).getName()); } } }
LSPosed模块编写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.example.lsposedtest;import android.util.Log;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage;public class lsptest1 implements IXposedHookLoadPackage { @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (!loadPackageParam.packageName.equals("com.example.lsphookdemo" )){ return ; } XposedHelpers.findAndHookConstructor("com.example.lsphookdemo.Person" , loadPackageParam.classLoader, String.class, int .class, new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Log.d("Bileton" ,"before hook param:" +param.args[0 ]+"---" +param.args[1 ]); param.args[0 ]="blttttt" ; param.args[1 ]=0 ; } }); ClassLoader clazzLoader = loadPackageParam.classLoader; Class<?> clazz = clazzLoader.loadClass("com.example.lsphookdemo.Person" ); XposedHelpers.findAndHookMethod(clazz,"getName" ,new XC_MethodHook (){ @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); Log.d("Bileton" ,"ret:" +param.getResult()); param.setResult("bileton" ); } }); } }
输出日志
hook内部类 hookdemo扩展 添加内部类
1 2 3 4 5 6 7 8 9 10 class People { private String name = "blttttt" ; People(String name){ this .name=name; } public void print () { Log.d("Bileton" , "People的属性为:" + this .name); } }
布局修改
布局文件里添加按钮button_innerclass
1 2 3 4 5 6 7 <Button android:id ="@+id/button_innerclass" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="innerclass" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toBottomOf ="@+id/button_getname" />
调用内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class MainActivity extends AppCompatActivity implements View .OnClickListener{ @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); EdgeToEdge.enable(this ); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); findViewById(R.id.button_getname).setOnClickListener(this ); findViewById(R.id.button_innerclass).setOnClickListener(this ); } @Override public void onClick (View v) { if (v.getId()==R.id.button_getname){ Log.d("Bileton" ,"GetName:" +new Person ("Bileton" ,21 ).getName()); } if (v.getId()==R.id.button_innerclass){ Person person = new Person ("Bileton" ,21 ); Person.People people = person.new People ("Blttttt" ); people.print(); } } }
LSPosed模块编写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.example.lsposedtest;import android.util.Log;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage;public class lsptest3 implements IXposedHookLoadPackage { @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (!loadPackageParam.packageName.equals("com.example.lsphookdemo" )){ return ; } ClassLoader classLoader = loadPackageParam.classLoader; Class<?> person_clazz = classLoader.loadClass("com.example.lsphookdemo.Person" ); XposedHelpers.findAndHookConstructor("com.example.lsphookdemo.Person$People" , classLoader, person_clazz, String.class, new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Log.d("Bileton" ,"before hook param:" +param.args[0 ]+"---" +param.args[1 ]); param.args[1 ]="blttt" ; } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); } }); } }
struggle 这段内部类的hook挣扎了好久,后来弄清楚了,原因如下:
通过Jadx的反编译查看People类的构造方法,转为smali语言
空参构造
如下看到这个构造方法里面有一个参数,是Person类。
有参构造
查看有参数的构造方法,这个构造方法本来是一个参数,但是通过Smali代码看到还是多了一个参数,还是Person类
然后不断的试错,才明白了在Xposed里还要传递这个参数。
hook匿名类
hook 修改属性 hookdemo扩展 添加了几个属性
1 2 3 4 private String name;private static int age = 50 ;public static String gender = "None" ;private static String mark = "Null" ;
获取非静态属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.example.lsposedtest;import android.util.Log;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage;public class lsptest1 implements IXposedHookLoadPackage { @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (!loadPackageParam.packageName.equals("com.example.lsphookdemo" )){ return ; }else { Log.d("Bileton" ,"hooked successful!" ); } XposedHelpers.findAndHookMethod("com.example.lsphookdemo.Person" , loadPackageParam.classLoader, "print" , new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); ClassLoader classLoader = loadPackageParam.classLoader; Class<?> person_clazz = classLoader.loadClass("com.example.lsphookdemo.Person" ); Object Object_person = XposedHelpers.newInstance(person_clazz); String name = (String) XposedHelpers.getObjectField(Object_person,"name" ); Log.d("Bileton" ,"attribute: " +name); } }); } }