Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
AbxOverflow — CVE-2024-34740 的 writeup 与漏洞利用程序:Android 的 BinaryXmlSerializer 存在整数溢出,可从普通已安装应用向 system_server 写入文件,进而实现 system_server 代码执行 | Kitploit
工具/GitHubGitHub/michalbednarski/abxoverflow
Android安全权限提升漏洞分析代码分析漏洞利用学习与教育Payload 开发二进制利用
GitHubmichalbednarski/abxoverflow

AbxOverflow

CVE-2024-34740 的 writeup 与漏洞利用程序:Android 的 BinaryXmlSerializer 存在整数溢出,可从普通已安装应用向 system_server 写入文件,进而实现 system_server 代码执行

查看仓库
682510个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Android 应用截图,标题为 AbxDroppedApk,并包含大量文本描述其正在 system_server 中运行

此处所述问题的修复已出现在 CVE-2024-34740 / A-307288067 下:

  • 公告
  • 公告中链接的补丁
  • 另外两个补丁:1 2

Android 二进制 XML

在 Android system_server 内部,许多服务在跨重启时将其状态存储为 XML 文件``` $ adb shell su 0 find /data/system -name '*.xml' | sort /data/system/appops_accesses.xml /data/system/cachequota.xml /data/system/device_policies.xml /data/system/device_policy_state.xml /data/system/display-manager-state.xml /data/system/input-manager-state.xml /data/system/inputmethod/subtypes.xml /data/system/install_sessions.xml /data/system/job/jobs_1000.xml /data/system/job/jobs_10131.xml /data/system/log-files.xml /data/system/netpolicy.xml /data/system/notification_policy.xml /data/system/overlays.xml /data/system/packages.xml /data/system/package-watchdog.xml /data/system/sensor_privacy_impl.xml /data/system/sensor_privacy.xml /data/system/shortcut_service.xml /data/system/users/0/app_idle_stats.xml /data/system/users/0/appwidgets.xml /data/system/users/0/package-restrictions.xml /data/system/users/0/settings_global.xml /data/system/users/0/settings_secure.xml /data/system/users/0/settings_system.xml /data/system/users/0/wallpaper_info.xml /data/system/users/0.xml /data/system/users/userlist.xml /data/system/watchlist_settings.xml

root@kitploit:~
历史上,这些一直是带缩进的纯文本 XML 文件,便于开发者阅读;然而,[在 Android 12 中引入了该格式的新二进制版本,理由是 `system_server` 总耗时中有 1.5% 被花在了这些 XML 操作上](https://android.googlesource.com/platform/frameworks/base/+/4ccea8796991d678ead4399130ec31edf63ff4fa%5E%21/)

需要注意的是,这种格式仅供系统内部使用,其文件具有魔数 `"ABX\x00"`。它不同于 [APK 内部使用的格式](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/ResourceTypes.cpp;l=1770;drc=d4e49e63519397789d284a03aea5fafc119cb1b0),后者用于 `AndroidManifest.xml`、`res/xml/*.xml`、`res/layout/*.xml` 等,没有显式的“魔数”,但通常以 `0300 0800` 开头(即 [头部](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h;l=608;drc=d4e49e63519397789d284a03aea5fafc119cb1b0) 的 `type=RES_XML_TYPE` 和 `headerSize=8`)

每当系统读取这些内部状态 XML 文件之一时,它会[根据文件中的 `"ABX\0"` 魔数选择使用 Binary XML 文件解析器还是常规 XML 解析器](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;l=188-192;drc=97a370a95275e79c69e79d7ead11aa38934a5575)。这些文件是否以 Binary XML 形式保存由[系统属性](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;drc=97a370a95275e79c69e79d7ead11aa38934a5575;l=74?q=Xml.java)控制,且默认启用

当使用 Binary XML 文件时,你可以通过例如 `adb shell su 0 abx2xml /data/system/packages.xml -` 来读取其内容

这种二进制格式的功能之一是提供类型化访问器,因此序列化器提供了 `attributeInt(String namespace, String name, int value)` 方法,该方法将值以二进制整数形式写入,避免了通过 String 的往返转换——那会产生新的内存分配以及随后需要垃圾回收的对象

另一种可以直接序列化的类型是字节数组```java
@Override
public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
        throws IOException {
    if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
    mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64);
    mOut.writeInternedUTF(name);
    mOut.writeShort(value.length);
    mOut.write(value);
    return this;
}

还有一个类似的方法 attributeBytesHex,区别仅在于写入的 TYPE_* 标签。abx2xml 工具使用该标签将字节数组转换为相应的字符串表示。

mOut 是 FastDataOutput 的一个实例,它提供了 Java DataOutputStream 的函数。writeByte/writeShort/writeInt/writeUTF/write 使用与标准 DataOutputStream 相同的格式。

与 Parcel 类似,如果在写入/读取过程中出现不匹配,后续读取的数据会从错误的偏移量处获取;但不同于 Parcel,使用 BinaryXmlSerializer/BinaryXmlPullParser 时出现的错误并不会让攻击者能够任意篡改读取的数据(在这种情况下,攻击者无法引入新的标签/属性名/属性值)。

然而,BinaryXmlSerializer 类本身或 FastDataOutput 中的错误却可以做到这一点。

在上述方法中,如果我们尝试写入长度为 65536 的字节数组,我们会使用 writeShort() 写入长度,这实际上会写入 0,随后数组的实际内容会被写入。

选择 ABX 注入目标

为了利用这种不匹配,我们需要选择某个文件,使我们能够将任意字节数组注入到 attributeBytesBase64 或 attributeBytesHex,并且修改该文件对攻击者来说是有价值的。

PackageInstaller 类提供了为安装准备包的能力。任何应用无需任何权限即可将待安装的新 APK 写入临时目录。一旦写入安装所需的全部内容,安装应用就可以 commit() PackageInstaller.Session,这意味着它无法再对安装文件进行任何更改,并且 Session 已准备好等待用户批准或实际安装。

这些操作的状态存储在 /data/system/install_sessions.xml 中。例如,安装应用可以先将大型 APK 的一半下载到 Package Manager Service 为其 PackageInstaller.Session 创建的临时目录中,然后在重启后继续下载,写入剩余一半并提交安装。

其中一种可能是向 install_sessions.xml 写入数据,将会话标记为暂存,这意味着它将在下次启动后安装。

另一种(此处介绍的)是更改准备安装文件所用的临时目录路径,因为 openWrite()/openRead() 接受任何有效的文件名,只要没有路径穿越,并且将该文件放置在 stageDir 字段指向的目录中,而该字段是从 XML 中读取的。

利用 ABX 注入

现在我们需要真正将受我们控制的字节数组送入 attributeBytesBase64()。

PackageInstaller.Session 提供了 setChecksums() 方法。

在 system_server 一侧,提供的各个 Checksum 会根据调用者提供的签名进行可选验证,然后放入 mChecksums。

当写入 install_sessions.xml 时,checksum.getValue() 会被传递给 writeByteArrayAttribute,后者再将其传递给 attributeBytesBase64()。

有少数事件会触发 install_sessions.xml 的写入,其中之一是创建新的 Session。因此,此漏洞利用会在一个会话上设置 Checksum 后创建新的 Session,以确保第一个 Session 已保存到文件中。

现在,我们写入长度为 65536 的字节数组;在它被读取后,其大小会被解释为零,并且该数组的内容会成为 BinaryXmlPullParser 解析的原始数据。

这里没有指定属性数量;每个条目都有一个包含 token 的标签字节。低半字节是 XmlPullParser 中定义的事件类型之一,例如 START_TAG、END_TAG 或 END_DOCUMENT。除这些类型外,还有一种特殊的 ATTRIBUTE 类型,它不会通过 next() 报告,而是在看到 START_TAG token 后,解析器会查看后续 token,直到遇到非 ATTRIBUTE token。

由于没有指定属性数量,我们可以立即通过 END_TAG token 关闭当前元素。然后,我们也关闭 </session>;因为所有相关元素的属性都在 <session> 开始标签中,而我们已经越过了该位置。不过,我们现在可以打开新的 <session> 元素并在其中设置这些属性。

如上所述,FastDataInput 与 Java 的 DataInputStream 兼容,只是额外增加了一个 readInternedUTF() 方法,它可以引用之前的 String。由于我们不知道之前驻留了哪些 String,我们总是指定先前未出现的 String 已被写入。这也会将新读取的字符串添加到池中,可能会在读取我们注入点之后写入的数据时造成问题;不过,作为注入的一部分,我会插入所有结束标签和 END_DOCUMENT token,因此在我的注入之后,不会从该文件中读取其他任何内容。

使用 stageDir 被篡改的 PackageInstaller.Session

一旦系统读取了修改后的 install_sessions.xml,我们就会得到一个 stageDir 被设置为我们所控制值的 PackageInstallerSession 对象。

我最初的想法是将 stageDir 设置为 /proc/self,然后读取 maps 并写入 mem,但这些尝试没有成功。

当我尝试使用 openRead() 打开 /proc/self/maps 时,system_server 成功打开了该文件,但通过 Binder 将该文件传递给 untrusted_app 时被 SELinux 阻止。

然而,写入并不是通过将原始文件描述符传递给另一个进程来完成的,而是通过 system_server 代理执行的,因为 system_server 必须能够在会话提交后撤销写访问权限。这是否意味着我们可以写入 /proc/self/mem?事实证明,虽然 system_server 可以打开该文件,但在写入任何内容之前,它会对该文件调用 Os.chmod()(见此处),而它无法对 /proc/self/mem 执行此操作。因此,我们不能在这里利用它;不过除此之外,system_server 能够打开该文件,并在我们指定的偏移量处执行写入,而该文件允许覆盖代码页,这将直接让我们获得代码执行能力。

既然此路不通,我尝试了下一个想法:替换 /data/system/packages.xml 的内容。这个文件保存着 PackageManagerService 的状态,尤其是安装了哪些应用以及分配给它们的 uid。

看起来 system_server 不允许直接写入该文件;相反,每当系统写入该文件时,它会先写入临时文件,然后用该临时文件替换 packages.xml 并对其启用保护。

然而,在读取 /data/system/packages.xml 时,系统会首先检查 /data/system/packages-backup.xml 文件是否存在;如果存在,它会认为主 packages.xml 已损坏,并改为读取备份。在正常操作期间,/data/system/packages-backup.xml 文件并不存在,我们可以通过将 stageDir 设置为 /data/system 的特制 PackageInstallerSession 来创建该文件。

此外,当我使用 openRead() 时,system_server 被允许发送 /data/system/packages.xml 的只读文件描述符,因此我可以轻松构建只包含我修改内容的修补文件,而不会破坏原有内容。

授予 sharedUserId="android.uid.system" 访问权限

在 packages.xml 中,我有已安装应用的注册定义,例如:```xml <package name="com.android.settings" codePath="/system_ext/priv-app/Settings" ... sharedUserId="1000" ...>

root@kitploit:~
我们能否在 `/data/app` 中的某处写入新的 APK(使用另一个 `PackageInstallerSession`),并向 `packages.xml` 添加新的 `<package>` 元素,从而以这种方式完成安装?

可以,但是我们必须在 `<cert>` 中提供新安装 APK 的有效签名,系统会在启动期间将其与 APK 文件进行校验

我们能否将 `userId`(而不是使用 `sharedUserId`,用于标识在 `AndroidManifest.xml` 中没有 `<manifest android:sharedUserId>` 属性的 APK)属性设置为我们想要的值?

可以,但是我们不能使用已被其他包或 `sharedUserId` 占用的值

我们能否为我们的应用设置 `sharedUserId="1000"`?

如果这样做,系统将在启动期间通过 [`canJoinSharedUserId()`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java;l=658-750;drc=7ec13b04c3bbaeac99cbbc4db9f9f80492c508fe) 验证该设置

具体来说,该方法会使用 [`checkCapability()`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/SigningDetails.java;l=613-637;drc=97a370a95275e79c69e79d7ead11aa38934a5575) 检查签名是否完全匹配,或者一方的签名是否与另一方过去的某个签名相匹配

这些“过去的签名”来自 `packages.xml`,具体来说,当 `<sigs>` 元素中包含 `<cert>` 时,我们可以在 `<sigs>` 下添加 `<pastSigs>` 元素,从而向 [`SigningDetails.mPastSigningCertificates`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/SigningDetails.java;l=74-87;drc=97a370a95275e79c69e79d7ead11aa38934a5575) 添加新条目

最终,我们篡改后的 `<shared-user>` 元素如下所示:```xml
<shared-user name="android.uid.system" userId="1000">
  <sigs count="1" schemeVersion="3">
    <cert index="3" />
    <pastSigs count="2" schemeVersion="3">
      <cert index="19" flags="2" />
      <cert index="19" flags="2" />
    </pastSigs>
  </sigs>
</shared-user>

<pastSigs> 下的 <cert> 元素被插入两次,因为最后一个过去的签名被视为当前签名,因此不会被考虑

flags="2" 表示该证书被允许用于 sharedUserId

此外,<package sharedUserId="1000"> 注册必须应用于在 manifest 中声明了 android:sharedUserId="android.uid.system" 的应用,因此它必须是与执行利用的 APK 分开的单独 APK。

新安装的 system-uid 应用无法启动

虽然我能够注册受信任用于 android:sharedUserId="android.uid.system" 的新证书,但通常由该证书签名且仅在 manifest 中声明 sharedUserId 的应用无法启动。启动它时,我们会在 logcat 中看到以下消息:

logcat:``` signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr -------- Abort message: 'JNI FatalError called: (com.example.abxoverflow.droppedapk) frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:1976: selinux_android_setcontext(1000, 0, "default:privapp:targetSdkVersion=33:complete", "com.example.abxoverflow.droppedapk") failed'

root@kitploit:~
这是因为 [`seapp_contexts` 文件](https://cs.android.com/android/platform/superproject/main/+/main:system/sepolicy/private/seapp_contexts) 中的定义均未匹配。

该文件中的 `user=` 规则是[从 `uid` 映射而来](https://cs.android.com/android/platform/superproject/main/+/main:external/selinux/libselinux/src/android/android_seapp.c;l=819-833;drc=530165a996d8ca5ab5959c33bc040c78951bcb59)(即 `selinux_android_setcontext()` 的第一个参数);在我们的场景中它将是 `user=system`,而对于普通应用则是 `user=_app`。

另一个需要匹配的是 `seinfo=` 规则,它取自 `selinux_android_setcontext()` 的第三个参数,直到第一个冒号为止。该值最初来自将启动的应用签名与 `/system/etc/selinux/plat_mac_permissions.xml` 中定义的签名进行比较。

最终,我们的应用尝试匹配 `user=system seinfo=default`,但 `seapp_contexts` 中没有这样的规则。

然而,虽然无法为我们新的 `android:sharedUserId="android.uid.system"` 应用启动进程,但如果通过 [`android:process` 属性](https://developer.android.com/guide/topics/manifest/application-element#proc) 指定,应用仍可被加载到现有进程中。特别是,在 `android.uid.system` 下运行的应用可以指定 `android:process="system"`,从而被加载到 `system_server` 中。

# 使系统崩溃

通常,[应用触发 `system_server` 崩溃被视为具有可忽略安全影响的 bug](https://bughunters.google.com/learn/invalid-reports/android-platform/5148417640366080/bugs-with-negligible-security-impact#triggering-a-local-temporary-denial-of-service),而此处之所以值得提及,只是因为它是需要两次 `system_server` 重启的漏洞利用链的一部分。

不管怎样,我们得到了一条 `Parcelable` 链条:

* [`IAlarmManager.set()` AIDL 方法接受 `AlarmManager.AlarmClockInfo`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl;l=32-35;drc=ca41ed611ac9c6584c6d5c38ae8428b8e4f3b135)
* [`AlarmClockInfo` 调用已弃用的 `readParcelable()` 且未提供类型参数](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/apex/jobscheduler/framework/java/android/app/AlarmManager.java;l=1598;drc=04bf84e220ade9d7ad8ef0b2f7e6ce6ec72841c8)(因为它位于 apex 模块中,而这些模块尚未切换到新方法)
* 我将 [`android.content.pm.PackageParser$Activity`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/PackageParser.java;l=8240;drc=7d3ffbae618e9e728644a96647ed709bf39ae759) 指定为 `Parcelable` 类
* 读取该对象会导致[调用任何接受单个 `Parcel` 参数的公共构造函数](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/PackageParser.java;l=7789-7795;drc=7d3ffbae618e9e728644a96647ed709bf39ae759)
* 我指定 [`android.os.PooledStringWriter`,它会在提供的 `Parcel` 上调用 `writeInt(0)`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/PooledStringWriter.java;l=55;drc=782d49826862cbdc9d020fc9d85f8a6f64675dcb)
* 该 `writeInt()` 调用是在作为 [`onTransact()`](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)) 的 `data` 参数接收到的 `Parcel` 上进行的,该 `Parcel` 底层是由从 `/dev/binder` `mmap` 的只读内存支持的。写入该内存会导致 `SIGSEGV`

另外值得注意的是,[我曾在之前的报告中使用 `PackageParser`+`PooledStringWriter` 组合,例如 CVE-2023-21098](https://github.com/michalbednarski/TheLastBundleMismatch)

# 整体流程

当你在应用内按下“执行所有操作”按钮后,会发生以下情况:

1. `RebootBackgroundRunner` 以独立进程启动,现在它将仅使用 [`setsid()`](https://man7.org/linux/man-pages/man2/setsid.2.html) 来在用户空间重启后存活,之后会在后台等待。
2. 分配一个新的 `PackageInstaller.Session`,并向其中添加一个新的 `Checksum` 对象。该 `Checksum` 对象包含一个字节数组,其大小会在序列化期间导致整数溢出;一旦这些数据被反序列化回来,系统将看到其数据原本是 `Checksum` 载荷的各个 `PackageInstaller.Session`。具体来说,注入了两个会话:
    * 一个带有 `sessionStageDir="/data/system"` 和 `prepared="true"`(意味着暂存目录已就绪,无需创建)
    * 另一个带有 `sessionStageDir="/data/app/dropped_apk"` 和 `prepared="false"`(意味着该目录将在首次 `Session.openWrite()` 时创建)
3. 分配一个新的 `PackageInstaller.Session`,然后立即销毁它。这会触发系统将更新后的内容写入 `install_sessions.xml`。
4. 短暂延迟后,触发一次 `system_server` 崩溃。
5. 在下次 `system_server` 启动期间,会读取 `install_sessions.xml` 文件,现在我们注入的 `PackageInstaller.Session` 对象就可以使用了。
6. `RebootBackgroundRunner` 在用户空间重启期间一直在后台等待;一旦它注意到系统已重新启动并准备就绪,就会执行后续步骤。
7. 使用其中一个 `PackageInstaller.Session`,新 APK 从 assets 中提取出来,并写入 `/data/app/dropped_apk/base.apk`。
8. 另一个会话用于读取 `/data/system/packages.xml`;该文件被修补为声明新投放的 APK 已安装,并且用于它的证书此前曾被用于 `android:sharedUserId="android.uid.system"`,且仍被信任用于该目的。修改后的文件被写为 `/data/system/packages-backup.xml`。
9. 再次触发 `system_server` 崩溃。
10. 当 `system_server` 在启动期间看到 `packages-backup.xml` 时,它会认为原始 `packages.xml` 已损坏,并使用备份文件。
11. 由于系统已读取被修改的 `packages.xml`,刚刚投放的应用存在并从 [`ACTION_BOOT_COMPLETED`](https://developer.android.com/reference/android/content/Intent#ACTION_BOOT_COMPLETED) 启动自身。这个新应用运行在 `system_server` 内部,因为其 `AndroidManifest.xml` 中包含 `<manifest android:sharedUserId="android.uid.system">` 和 `<application android:process="system">`。

# `utils/` 中的脚本

除了 PoC 应用之外,还有一个包含几个脚本的 `utils` 目录。

* `moveapk.sh` 将编译后的 APK 移动并放入投放器的 `assets` 中,需在 `gradle :droppedapk:assembleRelease` 之后运行。
* `peeksessions.sh` 允许查看 `install_sessions.xml` 的当前内容(需要 Android 的 `eng`/`userdebug` 构建版本)
* `wipesessions.sh` 清除所有存在的 `PackageInstaller.Session` 并重启系统(需要 Android 的 `eng`/`userdebug` 构建版本)

# 花絮

不确定这是否相关,但在查看可能与 ABX 相关的 bug 的历史时(`cd frameworks/base ; git log -S ABX`),我发现了 [“Stop processing on IOException”提交](https://android.googlesource.com/platform/frameworks/base/+/5112cfef2a2023a2629a426154547444593e9f9b%5E!/),它**新增了一个使用截断 ABX 文件的单元测试**。该提交是对 [“Ignore malformed shortcuts”](https://android.googlesource.com/platform/frameworks/base/+/d5122bfaf18f1503e73c1a3a177a56d0f604a008%5E%21/) 的后续,后者在[公告中被描述为 DoS](https://source.android.com/docs/security/bulletin/2022-12-01#framework)。
下载工具