-
-
[原创]沙箱逃逸之google ctf 2019 Monochromatic writeup
-
发表于: 2022-12-31 19:47 25800
-
这是入门chrome沙箱逃逸的第二篇文章,第一篇文章分析了一道题目,这里再来看19年的google ctf的题目,进一步掌握沙箱逃逸的漏洞原理。
根据官方文章Mojo的描述我们可知mojo
的架构如下图所示,主要包含的功能包括:
Bindings APIs
:用mojom Interface Definition Language (IDL) 来生成的各类接口实现,是最终用来绑定接口的api实现,是上面一层接口的进一步实现,也是大多数用户开发使用的api
,目前支持的语言包括:C++
、JS
以及Java
。
之前看的mojo
的bindings api
都是使用的c++
来实现render
与browser
之间的通信。根据上面的描述可知,事实上我们也可以使用js bindings api
来实现通信,本小节主要是简单介绍如何使用js bindings api
来实现mojo
通信,参考的主要文章是Mojo JavaScript Bindings API。
首先定义.mojom
文件:
然后在BUILD.gn
中加入编译生成binding
的目标:
上面的bindings
会被编译生成以下内容(假设interface
的名字是foo
):
编译的命令是:
会生成一些文件,其中和js bindings
相关的文件是:
到此使用echo.mojom
的前奏已经完成了,为了在代码中使用该接口,还需要在代码中include
两个文件:mojo_bindings.js
以及echo.mojom.js
,如下所示:
最后就是bindings api
的实现,和c++ bindings api
一样,整个通信的实现需要包括:
最终ehco.mojom
的实现如下,其中EchoImpl
是服务端用户定义的实现;echoServicePtr
是mojo.InterfacePtrInfo
的实例;echoServiceRequest
是mojo.InterfaceRequest
的实例,最终可以通过echoServicePtr
来发送对应的消息(ecchoInteger
),由EchoImpl
来响应消息。
题目附件下载以后,目录如下所示。
interface
是mojo js
实现所需要的依赖,chrome_diff.diff
是出题人对于chrome
的patch
,src
中包含chrome
文件以及启动服务的service.py
。
service.py
中包含启动chrome
的命令,--enable-blink-features=MojoJS
表示开启了MojoJS
接口,可以通过js
来直接使用mojo
。
重点要分析的是chrome_diff.diff
。
可以看到mojo
提供了一个BeingCreatorInterface
接口,主要功能是:
三个接口的实现基本上都是一致,以PersonInterface
为例进行说明:
每个对象中包含weight
、age
以及name
三个成员变量,分别对应至Set*
以及Get*
函数;需要关注的一点是各个成员变量的位置是不一样的,这一点在后续的利用过程中会使用的到。
再来好好看看CookAndEat
函数,可以看到它会先获取FoodInterface
,然后调用FoodInterface->GetWeight
函数,并调用base::BindOnce
函数将PersonInterfaceImpl::AddWeight
函数作为回调函数传入到FoodInterface->GetWeight
函数当中。
PersonInterfaceImpl::AddWeight
函数声明如下,可以看到调用该函数的时候传入的参数依次是base::Unretained(this)
、 std::move(callback)
以及std::move(foodPtr)
,再对照下面的参数列表,可以看到少了一个参数weight_
,按照调用约定,该参数会在FoodInterface->GetWeight
函数执行完成后,返回值作为参数weight_
,再调用AddWeight
函数。
看完了整个diff
文件,发现文件只定义了FoodInterface
的接口,并没有对应函数的实现,这是何意?
这说明出题人没有实现该接口,需要我们在render
进程实现该接口,并由browser
进程调用FoodInterface
的GetWeight
函数。相当于render
进程给browser
进程提供服务,实现两个进程之间的通信。
到这里diff
文件中所实现的mojo
的功能大致就分析清楚了,主要是实现了dog
、cat
以及person
三个类,这三个类中每个类都有成员变量weight
、name
以及age
,并有设置及获取这三个成员变量的函数,这三个成员变量声明的顺序不一样;三个类中还有一个CookAndEat
函数,会将自己的AddWeight
函数作为回调函数,然后调用FoodInterface::GetWeight
函数,FoodInterface
在diff
中没有实现。
上面的diff
文件分析了半天,漏洞究竟在哪里呢?
漏洞出现在CookAndEat
函数的实现中,还是以Person
对象为例来进行说明。
CookAndEat
函数如下所示,它会先获取FoodInterface
接口,然后调用该接口的GetWeight
函数。同时会将PersonInterfaceImpl::AddWeight
作为回调函数传入给GetWeight
函数,AddWeight
函数的参数base::Unretained(this)
、std::move(callback)
以及std::move(foodPtr)
,少了参数weight_
,该参数会在raw_food->GetWeight
函数执行完成后,返回值作为weight_
并最终调用AddWeight
函数。
关键点在于raw_food->GetWeight
函数没有实现,可以由我们来实现FoodInterface
接口中进行实现。从将base::Unretained(this)
参数作为this
指针传递给AddWeight
函数,再到AddWeight
函数运行。从传递到最终的运行,这个过程之中还包含了raw_food->GetWeight
函数的运行,而且raw_food->GetWeight
函数我们可控,如果在raw_food->GetWeight
函数中,我们将base::Unretained(this)
所对应的Person
对象给释放掉,那么最终调用AddWeight
函数进行weight += weight_
的实现时就形成了uaf
漏洞。
经过上面的分析可以知道漏洞本质是uaf
漏洞,效果是将接口对象中的weight
字段所对应的位置加上给定的任意值。如果我们在raw_food->GetWeight
函数中将原来传入的对象(如Person
)给释放掉,同时申请另外一个类型的对象(如Dog
),因为Person
字段的weight
字段是Dog
对象的name
字段,最终会将Dog
的name
字段加上weight_
。从某种意义上来说,这也算是类型混淆漏洞。
我们现在具备的能力是利用类型混淆漏洞将对象中某个对象的某个字段加上任意可控的值,如何操作才能实现利用呢?
首先要搞清楚三个类型对象的内存布局,name
是std::string
,该类的内存布局如下。
给三个类型的对象接口第一个字段再加上虚表指针,三个对象接口的内存布局如下所示:
我们可以利用Dog
和Cat
类型混淆,过程是:先申请的是Dog
,调用CookAndEat
函数,在raw_food->GetWeight
中释放掉Dog
对象,申请Cat
对象占用该内存,最终在Dog
的weight+=weight_
的时候,实际会将Cat
对象的__data__+=weight_
。如果控制得当的话,可以使得__data__
字段和另一个Cat
指向同一片内存区域,这样就构造出了overlap
内存,后续利用就很好方便了。
利用的思路是上面这个,接下来一步一步说明利用的过程。
首先是FoodInterfaceImpl
的实现,由前面的基础知识可以知道可以使用js bindings api
来实现。
还要搞清楚的是对象类型的大小,可以断点断在CreatePerson
、CreateDog
以及CreateCat
上,最终可以确定接口内存的大小为0x40
。
首先是申请8
个Dog
,对应name
申请的大小也是0x40
:
然后绑定FoodInterface
的实现,用于后续触发漏洞。
接着调用对最后一个Dog
对象调用cookAndEat
函数。
来看关键的cookAndEat
函数的实现,如下所示。在最开始释放掉最后一个Dog
对象(利用ptr.reset()
函数),然后对所有的Dog
对象的name
字段分配更大的空间,以空余出0x40
大小的hole
来布置堆风水;申请多个Cat
对象来填充这些释放的内存,最后再为这些Cat
分配与对象大小相同的name
。
最终达到的效果是某个Cat
对象占用了我们释放的Dog
内存,同时它的name
指针加上0x40
刚好是另一个Cat
对象的name
指针。
因为漏洞触发Cat
对象(被释放的Dog
对象)的name
指针(Dog
字段的weight
字段)加上FoodInterfaceImpl.prototype.getWeight
返回的0x40
,指向了下一片内存,刚好是另一个Cat
对象name
字段,形成了重叠的内存块。
接着就遍历Cat
对象,去寻找被修改了name
字段的Cat
,并找出name
字段相同的另一个Cat
,经过下面的代码后,evil
与victim
的name
字段相同。
然后我们释放掉victim
的name
字段,此时evil
的name
指针就成了悬空指针,再紧接着申请另一个对象(Person
),这样就形成了uaf
漏洞,可以通过evil
的name
指针泄露虚表指针以及堆指针,从而后续劫持控制流。
最后构造ROP
,伪造虚表指针,触发虚表函数。ROP
这里的构造可以提一句的是,可以利用execvp
函数来最终执行可执行程序,这样就不需要构造rsi
以及rdx
寄存器来,可以用命令objdump -d -j '.plt' ./src/binary/chrome | grep execvp
来查看偏移。
成功弹出计算器。
要提一点的是在泄露虚表的时候,可能是因为mojo
的编码问题,直接读出来的地址不对,需要对数据进行编码,加上下面的代码就可以了,原因我现在还搞不懂,先放着。
通过解决这题,理解了在render
端实现mojo
功能(如何使用js bindings api
),同时进一步理解了mojo
相关的uaf
漏洞的原理。
本文首发于奇安信攻防社区。
module test.echo.mojom;
interface Echo {
EchoInteger(int32 value)
=
> (int32 result);
};
module test.echo.mojom;
interface Echo {
EchoInteger(int32 value)
=
> (int32 result);
};
import
(
"//mojo/public/tools/bindings/mojom.gni"
)
mojom(
"interfaces"
) {
sources
=
[
"echo.mojom"
,
]
}
import
(
"//mojo/public/tools/bindings/mojom.gni"
)
mojom(
"interfaces"
) {
sources
=
[
"echo.mojom"
,
]
}
ninja
-
C out
/
r services
/
echo
/
public
/
interfaces:interfaces_js
ninja
-
C out
/
r services
/
echo
/
public
/
interfaces:interfaces_js
out
/
gen
/
services
/
echo
/
public
/
interfaces
/
echo.mojom.js
out
/
gen
/
services
/
echo
/
public
/
interfaces
/
echo.mojom.js
<!DOCTYPE html>
<script src
=
"URL/to/mojo_bindings.js"
><
/
script>
<script src
=
"URL/to/echo.mojom.js"
><
/
script>
<script>
var echoPtr
=
new test.echo.mojom.EchoPtr();
var echoRequest
=
mojo.makeRequest(echoPtr);
/
/
...
<
/
script>
<!DOCTYPE html>
<script src
=
"URL/to/mojo_bindings.js"
><
/
script>
<script src
=
"URL/to/echo.mojom.js"
><
/
script>
<script>
var echoPtr
=
new test.echo.mojom.EchoPtr();
var echoRequest
=
mojo.makeRequest(echoPtr);
/
/
...
<
/
script>
<!DOCTYPE html>
<script src
=
"URL/to/mojo_bindings.js"
><
/
script>
<script src
=
"URL/to/echo.mojom.js"
><
/
script>
<script>
function EchoImpl() {}
EchoImpl.prototype.echoInteger
=
function(value) {
return
Promise.resolve({result: value});
};
var echoServicePtr
=
new test.echo.mojom.EchoPtr();
var echoServiceRequest
=
mojo.makeRequest(echoServicePtr);
var echoServiceBinding
=
new mojo.Binding(test.echo.mojom.Echo,
new EchoImpl(),
echoServiceRequest);
echoServicePtr.echoInteger({value:
123
}).then(function(response) {
console.log(
'The result is '
+
response.value);
});
<
/
script>
<!DOCTYPE html>
<script src
=
"URL/to/mojo_bindings.js"
><
/
script>
<script src
=
"URL/to/echo.mojom.js"
><
/
script>
<script>
function EchoImpl() {}
EchoImpl.prototype.echoInteger
=
function(value) {
return
Promise.resolve({result: value});
};
var echoServicePtr
=
new test.echo.mojom.EchoPtr();
var echoServiceRequest
=
mojo.makeRequest(echoServicePtr);
var echoServiceBinding
=
new mojo.Binding(test.echo.mojom.Echo,
new EchoImpl(),
echoServiceRequest);
echoServicePtr.echoInteger({value:
123
}).then(function(response) {
console.log(
'The result is '
+
response.value);
});
<
/
script>
$ ls
Dockerfile build_docker.sh chrome_diff.diff flag interfaces note run_docker.sh src
$ ls
Dockerfile build_docker.sh chrome_diff.diff flag interfaces note run_docker.sh src
args
=
[
'./binary/chrome'
,
'--enable-blink-features=MojoJS'
,
'--disable-gpu'
,
'--headless'
,
'--repl'
,
#this flag makes chrome not to exit right after the webpage is loaded, this flag is not a part of the CTF challenge
server
]
args
=
[
'./binary/chrome'
,
'--enable-blink-features=MojoJS'
,
'--disable-gpu'
,
'--headless'
,
'--repl'
,
#this flag makes chrome not to exit right after the webpage is loaded, this flag is not a part of the CTF challenge
server
]
diff
-
-
git a
/
content
/
public
/
app
/
content_browser_manifest.cc b
/
content
/
public
/
app
/
content_browser_manifest.cc
index a1fa37e05edf..a1034e1b1a40
100644
-
-
-
a
/
content
/
public
/
app
/
content_browser_manifest.cc
+
+
+
b
/
content
/
public
/
app
/
content_browser_manifest.cc
@@
-
197
,
6
+
197
,
7
@@ const service_manager::Manifest& GetContentBrowserManifest() {
.ExposeInterfaceFilterCapability_Deprecated(
"navigation:frame"
,
"renderer"
,
std::
set
<const char
*
>{
+
"blink.mojom.BeingCreatorInterface"
,
"autofill.mojom.AutofillDriver"
,
...
+
import
"url/mojom/origin.mojom"
;
+
import
"third_party/blink/public/mojom/CTF/person_interface.mojom"
;
+
import
"third_party/blink/public/mojom/CTF/dog_interface.mojom"
;
+
import
"third_party/blink/public/mojom/CTF/cat_interface.mojom"
;
+
+
interface BeingCreatorInterface {
+
CreatePerson()
=
> (blink.mojom.PersonInterface? person);
+
CreateDog()
=
> (blink.mojom.DogInterface? dog);
+
CreateCat()
=
> (blink.mojom.CatInterface? cat);
+
};
diff
-
-
git a
/
content
/
public
/
app
/
content_browser_manifest.cc b
/
content
/
public
/
app
/
content_browser_manifest.cc
index a1fa37e05edf..a1034e1b1a40
100644
-
-
-
a
/
content
/
public
/
app
/
content_browser_manifest.cc
+
+
+
b
/
content
/
public
/
app
/
content_browser_manifest.cc
@@
-
197
,
6
+
197
,
7
@@ const service_manager::Manifest& GetContentBrowserManifest() {
.ExposeInterfaceFilterCapability_Deprecated(
"navigation:frame"
,
"renderer"
,
std::
set
<const char
*
>{
+
"blink.mojom.BeingCreatorInterface"
,
"autofill.mojom.AutofillDriver"
,
...
+
import
"url/mojom/origin.mojom"
;
+
import
"third_party/blink/public/mojom/CTF/person_interface.mojom"
;
+
import
"third_party/blink/public/mojom/CTF/dog_interface.mojom"
;
+
import
"third_party/blink/public/mojom/CTF/cat_interface.mojom"
;
+
+
interface BeingCreatorInterface {
+
CreatePerson()
=
> (blink.mojom.PersonInterface? person);
+
CreateDog()
=
> (blink.mojom.DogInterface? dog);
+
CreateCat()
=
> (blink.mojom.CatInterface? cat);
+
};
+
interface PersonInterface {
+
GetName()
=
> (string name);
+
SetName(string new_name)
=
> ();
+
GetAge()
=
> (uint64 age);
+
SetAge(uint64 new_age)
=
> ();
+
GetWeight()
=
> (uint64 weight);
+
SetWeight(uint64 new_weight)
=
> ();
+
CookAndEat(blink.mojom.FoodInterface food)
=
> ();
+
};
+
interface PersonInterface {
+
GetName()
=
> (string name);
+
SetName(string new_name)
=
> ();
+
GetAge()
=
> (uint64 age);
+
SetAge(uint64 new_age)
=
> ();
+
GetWeight()
=
> (uint64 weight);
+
SetWeight(uint64 new_weight)
=
> ();
+
CookAndEat(blink.mojom.FoodInterface food)
=
> ();
+
};
+
class
CONTENT_EXPORT CatInterfaceImpl
+
: public blink::mojom::CatInterface {
+
+
std::string name;
+
uint64_t age;
+
uint64_t weight;
+
class
CONTENT_EXPORT DogInterfaceImpl
+
: public blink::mojom::DogInterface {
+
+
uint64_t weight;
+
std::string name;
+
uint64_t age;
+
class
CONTENT_EXPORT PersonInterfaceImpl
+
: public blink::mojom::PersonInterface {
+
+
uint64_t age;
+
uint64_t weight;
+
std::string name;
+
class
CONTENT_EXPORT CatInterfaceImpl
+
: public blink::mojom::CatInterface {
+
+
std::string name;
+
uint64_t age;
+
uint64_t weight;
+
class
CONTENT_EXPORT DogInterfaceImpl
+
: public blink::mojom::DogInterface {
+
+
uint64_t weight;
+
std::string name;
+
uint64_t age;
+
class
CONTENT_EXPORT PersonInterfaceImpl
+
: public blink::mojom::PersonInterface {
+
+
uint64_t age;
+
uint64_t weight;
+
std::string name;
+
void PersonInterfaceImpl::CookAndEat(blink::mojom::FoodInterfacePtr foodPtr,
+
CookAndEatCallback callback) {
+
blink::mojom::FoodInterface
*
raw_food
=
foodPtr.get();
+
+
raw_food
-
>GetWeight(base::BindOnce(&PersonInterfaceImpl::AddWeight,
+
base::Unretained(this),
+
std::move(callback), std::move(foodPtr)));
+
}
+
void PersonInterfaceImpl::CookAndEat(blink::mojom::FoodInterfacePtr foodPtr,
+
CookAndEatCallback callback) {
+
blink::mojom::FoodInterface
*
raw_food
=
foodPtr.get();
+
+
raw_food
-
>GetWeight(base::BindOnce(&PersonInterfaceImpl::AddWeight,
+
base::Unretained(this),
+
std::move(callback), std::move(foodPtr)));
+
}
+
void PersonInterfaceImpl::AddWeight(
+
PersonInterfaceImpl::CookAndEatCallback callback,
+
blink::mojom::FoodInterfacePtr foodPtr, uint64_t weight_)
+
void PersonInterfaceImpl::AddWeight(
+
PersonInterfaceImpl::CookAndEatCallback callback,
+
blink::mojom::FoodInterfacePtr foodPtr, uint64_t weight_)
+
module blink.mojom;
+
+
import
"url/mojom/origin.mojom"
;
+
+
interface FoodInterface {
+
GetDescription()
=
> (string description);
+
SetDescription(string new_description)
=
> ();
+
GetWeight()
=
> (uint64 weight);
+
SetWeight(uint64 new_weight)
=
> ();
+
};
+
module blink.mojom;
+
+
import
"url/mojom/origin.mojom"
;
+
+
interface FoodInterface {
+
GetDescription()
=
> (string description);
+
SetDescription(string new_description)
=
> ();
+
GetWeight()
=
> (uint64 weight);
+
SetWeight(uint64 new_weight)
=
> ();
+
};
+
void PersonInterfaceImpl::AddWeight(
+
PersonInterfaceImpl::CookAndEatCallback callback,
+
blink::mojom::FoodInterfacePtr foodPtr, uint64_t weight_) {
+
weight
+
=
weight_;
+
std::move(callback).Run();
+
}
...
+
void PersonInterfaceImpl::CookAndEat(blink::mojom::FoodInterfacePtr foodPtr,
+
CookAndEatCallback callback) {
+
blink::mojom::FoodInterface
*
raw_food
=
foodPtr.get();
+
+
raw_food
-
>GetWeight(base::BindOnce(&PersonInterfaceImpl::AddWeight,
+
base::Unretained(this),
+
std::move(callback), std::move(foodPtr)));
+
}
+
void PersonInterfaceImpl::AddWeight(
+
PersonInterfaceImpl::CookAndEatCallback callback,
+
blink::mojom::FoodInterfacePtr foodPtr, uint64_t weight_) {
+
weight
+
=
weight_;
+
std::move(callback).Run();
+
}
...
+
void PersonInterfaceImpl::CookAndEat(blink::mojom::FoodInterfacePtr foodPtr,
+
CookAndEatCallback callback) {
+
blink::mojom::FoodInterface
*
raw_food
=
foodPtr.get();
+
+
raw_food
-
>GetWeight(base::BindOnce(&PersonInterfaceImpl::AddWeight,
+
base::Unretained(this),
+
std::move(callback), std::move(foodPtr)));
+
}
struct __long
{
pointer __data_;
size_type __size_;
size_type __cap_;
};
struct __long
{
pointer __data_;
size_type __size_;
size_type __cap_;
};
class
CONTENT_EXPORT CatInterfaceImpl:
pointer vtable;
pointer __data_;
size_type __size_;
size_type __cap_;
uint64_t age;
uint64_t weight;
class
CONTENT_EXPORT DogInterfaceImpl:
pointer vtable;
uint64_t weight;
pointer __data_;
size_type __size_;
size_type __cap_;
uint64_t age;
class
CONTENT_EXPORT PersonInterfaceImpl:
pointer vtable;
uint64_t age;
uint64_t weight;
pointer __data_;
size_type __size_;
size_type __cap_;
class
CONTENT_EXPORT CatInterfaceImpl:
pointer vtable;
pointer __data_;
size_type __size_;
size_type __cap_;
uint64_t age;
uint64_t weight;
class
CONTENT_EXPORT DogInterfaceImpl:
pointer vtable;
uint64_t weight;
pointer __data_;
size_type __size_;
size_type __cap_;
uint64_t age;
class
CONTENT_EXPORT PersonInterfaceImpl:
pointer vtable;
uint64_t age;
uint64_t weight;
pointer __data_;
size_type __size_;