对Chrome issue 1755的一点分析
环境搭建
下载一个70.0.3525.0 ~ 73.0.3676.0之间的应该都没啥问题,本人使用的是和p0文章里差不多版本的71.0.3578.0
前言
对p0给的exp做了一些分析
漏洞成因
该漏洞发生在Chrome的FileWriterImpl接口实现里
file_writer.mojom
首先是FileWriter的IDL接口描述:
1 | // src/third_party/blink/public/mojom/filesystem/file_writer.mojom |
file_system.mojom
漏洞存在于Write的是实现里,而FileWriter是被FileSystemManager管理的,其有一个CreateWriter方法,可以创建出FileWriter:
1 | // src/third_party/blink/public/mojom/filesystem/file_system.mojom |
其接口具体的实现:
1 | // src/content/browser/fileapi/file_system_manager_impl.cc |
从[1]处可以看到这里通过MakeStrongBinding来将FileWriterImpl实例和刚刚创建出来的receiver绑定到一起,StroingBinding意味着FileWriterImpl对象的生命周期和Mojo interface pointer绑定,这也代表着我们可以在连接的另一端控制该对象的生命周期。
FileWriterImpl::Write
以下是漏洞函数FileWriterImpl::Write的具体实现:
1 | void FileWriterImpl::Write(uint64_t position, |
在[2]处指出了这里使用了base::Unretained(this),base::Unretained这里表示被调者不保证对象的存在,由调用方来保证回调执行时,this指针仍然可用,此时还无法看出漏洞所在,继续跟进blob_context_->GetBlobDataFromBlobPtr:
1 | // src/storage/browser/blob/blob_storage_context.cc |
FileWriterImpl::Write向其传入了一个用户可控的Blob(我们可以在js层构造一个BlobPtr传入)和FileWriterImpl::DoWrite的callback,BlobStorageContext::GetBlobDataFromBlobPtr调用了raw_blob->GetInternalUUID,由于BlobPtr可以由我们传入,所以GetInternalUUID也是对应我们自己定义好的js函数(这里没搞懂为什么有些对象的js函数是我们可以重写的),它只需要满足其mojo idl接口即可,将一个string uuid
作为response返回,blob的mojom文件:
1 | // src/third_party/blink/public/mojom/blob/blob.mojom |
在调用完raw_blob->GetInternalUUID后,取得uuid,并通过该uuid作为context->GetBlobDataFromUUID(uuid)
的参数拿到Blob对象并传给回调base::BindOnce(&FileWriterImpl::DoWrite, base::Unretained(this), std::move(callback), position));
作为其最后一个参数调用FileWriterImpl::DoWrite:
1 | void FileWriterImpl::DoWrite(WriteCallback callback, |
总结一下FileWriterImpl::Write的执行流程为:
FileWriterImpl::Write
—> blob_context_->GetBlobDataFromBlobPtr
—> raw_blob->GetInternalUUID
—> context->GetBlobDataFromUUID(uuid)
—> FileWriterImpl::DoWrite
问题就出在raw_blob->GetInternalUUID这一步,由于GetInternalUUID是由我们自定义的,所以我们可以在GetInternalUUID的js实现中把FileWriterImpl释放掉,后续的FileWriterImpl::DoWrite就会重用我们释放掉的FileWriterImpl对象,导致UAF
触发UAF的poc代码:
1 | // 创建blob_registry_ptr和file_system_manager_ptr供后面使用 |
漏洞利用
由于是UAF的漏洞,我们需要保证占位到被释放的FileWriterImpl后程序不会崩溃,但是跟入FileWriterImpl::DoWrite后会发现,里面会有一堆的对FileWriterImpl对象的成员的引用,在不知道任何堆地址的情况下几乎就是必挂。而p0的文章中使用到了一种喷射内存的手法,可以喷4Tb以上的内存,在喷了这么大的内存之后,就可以拿到一个稳定的地址,在该地址上布置好内存布局,我们就可以避免crash了!以下是如何实现该内存喷射的分析
DataPipe和SharedBuffer
Mojo不仅有Message Pipe,其还提供了DataPipe和SharedBuffer,查看其接口文件:
1 | // src/third_party/blink/renderer/core/mojo/mojo.idl |
DataPipe分为consumer端和producer端,SharedBuffer是一块共享的内存;在windows平台的实现中,DataPipe和SharedBuffer的底层都是依赖windows的SharedMemory机制。
在DataPipeDispatcher的Deserialize方法中:
1 | // src/mojo/core/data_pipe_consumer_dispatcher.cc |
在[3]处,当Browser端接收到DataPipe consumer端的消息时,会在该函数中映射一段和render端一模一样,即大小一样内容一样的memory,如果我们能一直发送DataPipe的consumer端给Browser端,就可以依靠dispatcher->InitializeNoLock
函数来喷射Browser端的内存;但是render进程是有内存限制的,要达到4Tb是不可能的;
在前面提到过SharedBuffer和DataPipe底层是使用同一个windows机制的,在翻看mojo_handle的接口定义:
1 | // src/third_party/blink/renderer/core/mojo/mojo_handle.idl |
可以发现我们是可以dup SharedBuffer的,所以我们可以利用这个dup的操作,在配合前面DataPipeDispatcher里映射内存的机制,得到内存喷射的步骤:
- 首先创建一个1G大小的SharedBuffer
- 接着创建大量的1kb的DataPipe和SharedBuffer的dup handle
- 找到全局变量mojo::core::Core* g_core
- 根据g_core的地址找到前面DataPipe和SharedBuffer的dispatcher对象
- 交换他们的region_成员并修改MojoCreateDataPipeOptions的element_num_bytes 和capacity_num_bytes
- 使用blob_registry_ptr.registerFromStream(“”, “”, 0x1, pipes[i].consumer, null);来发送datapipe的consumer端
- browser端的mojo::core::DataPipeConsumerDispatcher::Deserialize函数会接收到render进程发过来的consumer,并进行大量1G的内存映射
与上面步骤中相关的结构体如下:
1 | 0:020> dt chrome!mojo::core::Core |
其中最重要的就是他们的region_成员,里面包含了handle和guid,这是Mojo用来识别DataPipe和SharedBuffer的,在payload代码中,我们就是交换了DataPipe和SharedBuffer的region_以达到让Browser一次性map 1G内存的操作
以下是payload中相关的代码:
1 | function spray(oob, page_data) { |
该内存喷射手法在83.0.4086.0中被修复,对应commit:https://github.com/chromium/chromium/commit/9e64c392b9d6f6e9215cf060ecb5690d3f5cc2eb#diff-0ade0ea248ab4ac9455e8fb7651882bb79e933711d6de7fcdefbee00a44b999e
参考链接
https://www.anquanke.com/post/id/231412
https://bugs.chromium.org/p/project-zero/issues/detail?id=1755
https://googleprojectzero.blogspot.com/2019/04/virtually-unlimited-memory-escaping.html