1 前言
前面章节我们学习了【Android Framework系列】第4章 PMS原理我们了解了PMS原理,【Android Framework系列】第9章 AMS之Hook实现登录页跳转我们知道AMS可以Hook拦截下来实现未注册Activity页面的跳转,本章节我们来尝试一下HookPMS实现广播的发送
。
这里我们只简单介绍一下HookPMS思路和重点代码,需要详细了解的请到文末处项目地址下载查看。
同学们是否遇到需要动态下发组件的需求?是通过什么方法实现的呢?
之前的章节里我们分析了PMS
相关的原理,简单回顾一下PMS
:
PMS
是包管理系统服务,用来管理所有的包信息,包括应用安装、卸载、更新以及解析AndroidManifest.xml
。手机开机后,它会遍历设备上/data/app/
和/system/app/
目录下的所有apk
文件,通过解析所有安装应用的AndroidManifest.xml
,将xml
中的数据(应用信息
、权限
、四大组件
等)信息都缓存到内存中,后续提供给AMS
等服务使用。
通过HookPMS
是否可以来实现动态apk
组件的下发,本章节我们通过HookPMS
拿到PMS内的receivers(BroadcastReceiver)
来实现调用动态下发apk
的BroadcastReceiver
。**
2 实现
PMS内管理了四大组件,会将所有apk都解析后保存对应四大组件的信息,分别保存到对应的集合Activity
、receivers
、probiders
、services
。
/frameworks/base/core/java/android/content/pm/PackageParser.java
java
6460 @UnsupportedAppUsage
6461 public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
6462 @UnsupportedAppUsage
6463 public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
6464 @UnsupportedAppUsage
6465 public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
6466 @UnsupportedAppUsage
6467 public final ArrayList<Service> services = new ArrayList<Service>(0);
本章节我们HookPMS的BroadcastReceiver
那就得获取到receivers
集合。
2.1 实现思路
- 先将要下发的
apk
下载下来(我们这里直接加到设备的存储里)- 将
apk
通过Hook
到的PMS
方法解压解析到PMS
内- 再通过
HookPMS
拿到装有BroadcastReceiver
信息的receivers
集合- 将动态下发
apk
里的BroadcastReceiver
名称作为参数,通过HookPMS
的方法进调用,从而实现本章节的目的。
2.2 项目结构
上图我们可以看到,有两个module,分别为app和pmsbr。
app模块:
HookPMS
的主要逻辑在PMSPackageParser
类,对apk
进行解析和PMS
的Hook
,ClientActivity
用于发送和接收广播,对HookPMS
进行操作。
pmsbr模块:
这个module
其实只是为了打包成动态apk
,内部的BroadcastReceiver
用于验证HookPMS
后是否能调用动态下发apk
内的这个BroadcastReceiver
2.3 ClientActivity
java
package com.yvan.hookpms;
import android.Manifest;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
/**
* @author yvan
* @date 2023/8/7
* @description
*/
public class ClientActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
checkPermission(this);
IntentFilter filter = new IntentFilter();
filter.addAction("com.yvan.client");
registerReceiver(new FinishBroadcastReceiver(), filter);
}
public static boolean checkPermission(
Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && activity.checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
activity.requestPermissions(new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
}
return false;
}
public void registerBroaderCast(View view) {
PMSPackageParser pmsPackageParser = new PMSPackageParser();
try {
pmsPackageParser.parserReceivers(this,
new File(getFilesDir(), "input.apk"));
} catch (Exception e) {
e.printStackTrace();
}
}
public void sendBroaderCast(View view) {
Toast.makeText(this, "1.发送消息给server", Toast.LENGTH_SHORT).show();
view.postDelayed(() -> {
Intent intent = new Intent();
intent.setAction("com.yvan.server");
sendBroadcast(intent);
}, 3000);
}
static class FinishBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "3.收到server回复消息", Toast.LENGTH_SHORT).show();
}
}
}
activity_client.xml
xml
<?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=".ClientActivity">
<Button
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:onClick="registerBroaderCast"
android:text="注册广播" />
<Button
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:onClick="sendBroaderCast"
android:text="发送广播" />
</LinearLayout>
- 页面打开后,即创建一个
BroadcastReceiver
用于接收来自server
的回复消息,两个按钮分别"注册广播"、"发送广播"。 - 点击"注册广播":将下发的apk(此处是用pmsbr模块打包的
input.pak
放于设备的data/data/com.yvan.hookpms/files这个私有目录下)通过PMS解析,然后通过HookPMS
实现下发apk中的广播注册。 - 点击"发送广播":给上面1中注册的广播发送消息
我们主要来看看第2步注册广播,这里是本章的重点HookPMS
2.4 PMSPackageParser
java
package com.yvan.hookpms;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import dalvik.system.DexClassLoader;
/**
* @author yvan
* @date 2023/8/7
* @description
*/
public class PMSPackageParser {
public void parserReceivers(Context context, File apkFile) throws Exception {
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage",
File.class, int.class);
parsePackageMethod.setAccessible(true);
Object packageParser = packageParserClass.newInstance();
Object packageObj = parsePackageMethod.invoke(packageParser, apkFile,
PackageManager.GET_RECEIVERS);
packageObj.hashCode();
Field receiversField = packageObj.getClass().getDeclaredField("receivers");
List receivers = (List) receiversField.get(packageObj);
// AndroidManifest---> Package对象 描述信息
DexClassLoader dexClassLoader = new DexClassLoader(apkFile.getAbsolutePath(),
context.getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath(),
null, context.getClassLoader());
// 动态注册
Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
Field intentsField = componentClass.getDeclaredField("intents");
for (Object receiverObject : receivers) {
String name = (String) receiverObject.getClass().getField("className")
.get(receiverObject);
// class --->对象
try {
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) dexClassLoader.loadClass(name).newInstance();
List<? extends IntentFilter> filters = (List<? extends IntentFilter>)
intentsField.get(receiverObject);
for (IntentFilter filter : filters) {
context.registerReceiver(broadcastReceiver, filter);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
我们看到HookPMS
做了以下操作:
Hook
的是PMS
解析类android.content.pm.PackageParser
的parsePackage()
方法获取到Package
对象- 然后获取到
Package
对象内存储BroadcastReceiver
的receivers
集合 - 将动态下发的apk通过类加载器加载
- 然后遍历PMS的
receivers
集合找到这个下发apk中注册的广播 - 最后注册这个广播。
这里注册的这个广播是动态下发组件input.apk
的PMSBroadcastReceiver
,我们继续往下看
2.5 PMSBroadcastReceiver
java
package com.yvan.pmsbr;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
/**
* @author yvan
* @date 2023/8/7
* @description
*/
public class PMSBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 收到你的信息了
Toast.makeText(context, "2.接收到client的消息", Toast.LENGTH_SHORT).show();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
Intent intent1 = new Intent();
intent1.setAction("com.yvan.client");
context.sendBroadcast(intent1);
}, 3000);
}
}
PMSBroadcastReceiver
中主要是接收来自client
的广播,然后给client
回复一个广播。我们在上面FinishBroadcastReceiver
中收到server
的回复后,弹出Toast展示则表示完成了,发送、接收动态下发组件input.apk
的消息。
3 总结
到这里我们就完成了整个动态下发apk的调用及被调用,这里我们再稍微总结一下:
主要通过HookPMS
实现将动态下发的apk
进行解析,将信息存储在PMS
内,然后对PMS
中装有BroadcastReceiver
信息的receivers
集合拿到,程序(Client)发送广播给动态下发apk内定义好的广播(Server),该广播(Server)对程序(Client)作出回应,然后在程序(Client)接收回应(类似TCP
的三次握手逻辑)。从而实现本章节对PMS
进行Hook
的目的。
文章只做核心HookPMS
代码思路的分析,这里是项目地址,小伙伴可以自行下载查看,别忘了点Star喔,谢谢!!