白乐天

道阻且长,行则将至。

LSPosed开发

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
<!-- 是否是xposed模块,xposed根据这个来判断是否是模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!-- 模块描述,显示在xposed模块列表那里第二行 -->
<meta-data
android:name="xposeddescription"
android:value="这是一个Xposed模块" />
<!-- 最低xposed版本号(lib文件名可知) -->
<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匿名类

1

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);
}
});
}
}