Impl Js
Impl Js
js
Building Modular Malware
Using The V8 JavaScript Engine
Zander Work (captainGeech)
Presented at DistrictCon 0
February 21-22, 2025, Washington DC
Agenda
1. Background/Motivation
2. Intro to v8
3. implant.js Platform
4. Release
2
$ whoami
● Senior Security Engineer @ Google Threat Intelligence Group
○ (Mandiant Intel + TAG)
○ Cybercrime → Exploit Brokers/Surveillance Vendors
● Always interested in cool offensive techniques and bugs
○ However, not a red teamer
○ If I make dumb assumptions, pls tell me :)
● I have IDA stickers
3
Disclaimer
The views, work, and ideas discussed in this presentation are only representative
of me, and do not reflect or represent my employer or their partners.
Any code presented and released through this project are not products of Google.
4
Setting the Stage
Modular Malware
● Modular implants are typically lightweight, and effectively are an evaluator of
capabilities pushed down from a server
● Some malware uses a mix of built-in functionality and modules to provide
extra functionality
○ eg, Cobalt Strike
● Increases the flexibility of the implant
(and removes some obvious detection surface)
6
Motivation
● Cobalt Strike/BOF-like: Native plugins, pre-compiled, OS+arch specific
○ Not very flexible, more challenging to build and use. Less resilient on
target
● Sandman APT’s LuaDream platform
○ More flexible scripting plugin environment, but Lua
● Havoc’s Kaine RISC-V VM
○ An interesting evolution for plugins, but more akin to BOFs
Scripting language-based implants often support eval(...), but that’s far less
interesting :)
7
Surely, there is a better way!
● Best of both worlds
8
Intro to V8
9
Background
● Open source robust JavaScript engine
○ Powers (nearly) all browsers
● Incredibly Capable
○ Native debug and profiling
○ Sandboxing
10
v8 Embedding
● Platform: Top-level object defining the runtime environment
● Handle: Smart pointer-like objects to handle resource lifetimes & garbage collection
More detailed info? ȓ̵̫͖̪̀̌͠ȅ̴̂̔ ̱͎͓̼͉̳̐ a̵̱̹̜̳͍͔͙̺̝̲͌̐̋͗͂ ͘ͅḏ̩̎͐̍̑͂͗͠ͅ ̷ ̣ ̫̦̠̮ţ̸̘̜̯̟̝͛̒̑ ̋̄͗͌̚ ̫̻̪̝̯͉͍͔̅̆̈͂̾͋ͅ ́͐ ̴̪ ḥ̴ ̃ ̪̟̯̏̋͌̒̈͂̓͝ ̢ę̶͠ ͍̠͓͈̮̱̝̪͙̿͂͗̒̓̅ ̀̊ ́̿ ̉̈ ̛ ͑ ḓ̶
̵̨̛̉ ̙͓̩̪̲̮̻̓̂̈͝ o̶
̢̡̧ ̙̰̿̑̂̓͝ ĉ̴͝ ̯̘͎̔̆̐̚ͅ ̕ş̵̫̜̞͔̠͙̦̘͎̙͕̏̋̽̚ ̀ ̢: https://v8.dev/docs/embed
11
implant.js - Overview
12
Overview
● Cross platform C++ implant, with a JavaScript runtime
● Python server
○ Operator CLI
● Features
○ Module packing
13
Operator Interface
cmd> help
implant.js commands:
14
JavaScript Runtime Features
● Global ctx object to expose OS-agnostic native functionality, including:
○ Raw memory manipulation
○ Executing shell commands
○ File manipulation
○ Foreign function interface (FFI)
● Single-definition constants used in C++ and JS
○ File modes
○ OS flags
○ FFI types
15
Simple Module Example
if (ctx.os() === OS_WINDOWS) {
ctx.output(ctx.system("whoami"));
} else {
ctx.output(ctx.system("id"));
}
16
Simple Module Example
if (ctx.os() === OS_WINDOWS) {
ctx.output(ctx.system("whoami"));
} else {
ctx.output(ctx.system("id"));
}
$ ./server.py
2025-02-19 17:06:47 [INFO] server listening on port 1337
2025-02-19 17:06:49 [INFO] new connection from 127.0.0.1:59902
2025-02-19 17:06:49 [INFO] client is running Linux
cmd> run whoami
running module whoami
2025-02-19 17:06:52 [INFO] output from the client:
uid=1001(user) gid=1001(user) groups=1001(user),959(docker)
17
Simple Module Example
if (ctx.os() === OS_WINDOWS) {
ctx.output(ctx.system("whoami"));
} else {
ctx.output(ctx.system("id"));
}
$ ./server.py
2025-02-19 17:06:47 [INFO] server listening on port 1337
2025-02-19 17:06:49 [INFO] new connection from 172.16.135.142:53210
$ ./server.py 2025-02-19 17:06:49 [INFO] client is running Windows
2025-02-19 17:06:47 [INFO]
cmd> run server listening on port 1337
whoami
2025-02-19 17:06:49
running[INFO]
modulenew connection from 127.0.0.1:59902
whoami
2025-02-19 17:06:49 [INFO]
2025-02-19 client [INFO]
17:06:52 is running Linux
output from the client:
cmd> run whoami
desktop-hl5i74p\lab
running module whoami
2025-02-19 17:06:52 [INFO] output from the client:
uid=1001(user) gid=1001(user) groups=1001(user),959(docker)
18
Multi-File Module
function root_dir() {
let dirs;
if (ON_WINDOWS) {
dirs = ctx.fs.dir_contents("C:\\");
const ON_WINDOWS = ctx.os() === OS_WINDOWS;
}
const ON_LINUX = ctx.os() === OS_LINUX;
if (ON_LINUX) {
dirs = ctx.fs.dir_contents("/");
function hex(v) {
}
return `0x${v.toString(16)}`;
}
ctx.output(dirs.join("\n"));
}
19
Implant Client Architecture
JS Runtime
Memory
File I/O FFI
Manipulation
20
OS Interop Example
const ptr1 = ctx.mem.alloc(500, MEM_RW);
TEST_NE(ptr1, null);
21
OS Interop Example
const ptr1 = ctx.mem.alloc(500, MEM_RW);
TEST_NE(ptr1, null);
JS_HANDLER(JsNatives::mem::alloc) {
...
double sz, perms;
ARG_NUMBER(info[0], sz);
ARG_NUMBER(info[1], perms);
{"method":"Runtime.executionContextDestroyed","params":{"exe
cutionContextId":1,"executionContextUniqueId":"-387635299073
6511187.6001991582570743140"}}
29
Methods
30
Important Object Types “v8 debugging, wow
so easy! only 5
functions to write!”
● v8_inspector::V8InspectorClient
○ runMessageLoopOnPause(...):
■ Called when JS stops execution for any reason
○ quitMessageLoopOnPause():
■ Called when JS resumes execution
● v8_inspector::V8Inspector::Channel
○ sendResponse(...):
■ Callback for CDP RPC call return values
○ sendNotification(...):
■ Callback for non-RPC triggered events (eg, the debugger loaded a
script)
○ flushProtocolNotifications():
■ ???
■ Nobody implements this so neither did I
31
Important Object Types “v8 debugging, wow
so easy! only 5
functions to write!”
● v8_inspector::V8InspectorClient ¯\_(ツ)_/¯
○ runMessageLoopOnPause(...):
■ Called when JS stops execution for any reason
○ quitMessageLoopOnPause():
■ Called when JS resumes execution
● v8_inspector::V8Inspector::Channel
○ sendResponse(...):
■ Callback for CDP RPC call return values
○ sendNotification(...):
■ Callback for non-RPC triggered events (eg, the debugger loaded a
script)
○ flushProtocolNotifications():
■ ???
■ Nobody implements this so neither did I
32
Debugger CLI Interface
debug(whoami)> help
implant.js debugger commands:
33
Inspector Message Passing
Implant
Inspector
Client
Implant C2
Platform Inspector
Netcode Server
Inspector
Channel
34
Inspector Message Passing
Implant
Inspector
Client
debug(my_mod)> bp 5
Implant C2
Platform Inspector
Netcode Server
Inspector
Channel
35
Inspector Message Passing
Implant
DebugCmd::
Inspector
Client BREAKSET
Implant C2
Platform Inspector
Netcode Server
Inspector
Channel
36
Inspector Message Passing
Implant
Inspector
Debugger Client
.setBreakpoint()
Implant C2
Platform Inspector
Netcode Server
Inspector
Channel
37
Inspector Message Passing
Implant
Inspector
Client
Implant C2
Platform Inspector
Netcode Server
Inspector
Channel
38
Inspector Message Passing
Implant
Inspector
Client
Implant C2
Platform Inspector
Netcode Server
Debugger.paused
Inspector
Channel
39
Inspector Message Passing
Implant
Inspector
Client
Implant C2
Platform Inspector
Netcode Server
DebugRespPacket
Inspector
Channel::CONTEXT
40
Inspector Message Passing
Implant
Inspector
Client
Implant C2
Platform Inspector
Netcode Server
debug(my_mod)>
Inspector c
L5 Channel
- <global>: ctx.system("rm -rf /")
debug(my_mod)>
41
Demo 2
Demo 2
implant.js - FFI
JS->Native Code FFI
const EnumDeviceDrivers = ctx.ffi.resolve("psapi.dll",
"EnumDeviceDrivers", TYPE_BOOL, [TYPE_POINTER, TYPE_INTEGER,
TYPE_POINTER]);
JsNatives::Ffi::Resolve()
const array = malloc(8 * num_of_mods);
EnumDeviceDrivers(array, cbNeeded, lpcbNeeded);
LoadLibraryA()
45
Demo 3
Demo 3
Release
implant.js -> yourplant.js
https://github.com/captainGeech42/implant.js
Caveats:
● C2 and server setup is very not robust
● Limited modules being released
● No binary releases
Goals:
● Enable researchers to benefit from the platform
● Keep the bar for abuse as high as possible
○ Script kiddies won’t even be able to get v8 to build ;)
49
implant.js Detections
50
Future Work
● Improved JS types for working with QWORD and unsigned values
○ Reuse some functionality from Theori’s pwn.js framework?
51
Future Work
● Improved JS types for working with QWORD and unsigned values
○ Reuse some functionality from Theori’s pwn.js framework?
● Dynamically load v8 runtime from existing Chromium installations on target
52
Future Work
● Improved JS types for working with QWORD and unsigned values
○ Reuse some functionality from Theori’s pwn.js framework?
● Dynamically load v8 runtime from existing Chromium installations on target
● Build for aarch64
○ Some little endian assumptions are definitely hidden around
53
Future Work
● Improved JS types for working with QWORD and unsigned values
○ Reuse some functionality from Theori’s pwn.js framework?
● Dynamically load v8 runtime from existing Chromium installations on target
● Build for aarch64
○ Some little endian assumptions are definitely hidden around
● Explore compiling plugins dynamically to WASM for faster execution
○ (and harder to detect over the wire ;) )
54
Future Work
● Improved JS types for working with QWORD and unsigned values
○ Reuse some functionality from Theori’s pwn.js framework?
● Dynamically load v8 runtime from existing Chromium installations on target
● Build for aarch64
○ Some little endian assumptions are definitely hidden around
● Explore compiling plugins dynamically to WASM for faster execution
○ (and harder to detect over the wire ;) )
● Combination of JS and C++ mechanics for better struct interfacing in FFI
55
Thank You