【Android Framework系列】第10章 PMS之Hook实现广播的调用

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)来实现调用动态下发apkBroadcastReceiver。**

2 实现

PMS内管理了四大组件,会将所有apk都解析后保存对应四大组件的信息,分别保存到对应的集合Activityreceiversprobidersservices

/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 实现思路

  1. 先将要下发的apk下载下来(我们这里直接加到设备的存储里)
  2. apk通过Hook到的PMS方法解压解析到PMS
  3. 再通过HookPMS拿到装有BroadcastReceiver信息的receivers集合
  4. 将动态下发apk里的BroadcastReceiver名称作为参数,通过HookPMS的方法进调用,从而实现本章节的目的。

2.2 项目结构

上图我们可以看到,有两个module,分别为app和pmsbr。

app模块:
HookPMS的主要逻辑在PMSPackageParser类,对apk进行解析和PMSHookClientActivity用于发送和接收广播,对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>
  1. 页面打开后,即创建一个BroadcastReceiver用于接收来自server的回复消息,两个按钮分别"注册广播"、"发送广播"。
  2. 点击"注册广播":将下发的apk(此处是用pmsbr模块打包的input.pak放于设备的data/data/com.yvan.hookpms/files这个私有目录下)通过PMS解析,然后通过HookPMS实现下发apk中的广播注册。
  3. 点击"发送广播":给上面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做了以下操作:

  1. Hook的是PMS解析类android.content.pm.PackageParserparsePackage()方法获取到Package对象
  2. 然后获取到Package对象内存储BroadcastReceiverreceivers集合
  3. 将动态下发的apk通过类加载器加载
  4. 然后遍历PMS的receivers集合找到这个下发apk中注册的广播
  5. 最后注册这个广播。

这里注册的这个广播是动态下发组件input.apkPMSBroadcastReceiver,我们继续往下看

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喔,谢谢!!

相关推荐
太空漫步112 小时前
android社畜模拟器
android
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android
yujunlong391914 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup
rkmhr_sef15 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb