0% found this document useful (0 votes)
3 views56 pages

Impl Js

The document presents 'implant.js', a modular malware platform utilizing the V8 JavaScript engine, aimed at enhancing flexibility and performance in malware operations. It outlines the architecture, features, and debugging capabilities of the platform, emphasizing cross-platform support and a user-friendly interface for operators. The presentation was delivered by Zander Work at DistrictCon 0 in February 2025.

Uploaded by

pgresql
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views56 pages

Impl Js

The document presents 'implant.js', a modular malware platform utilizing the V8 JavaScript engine, aimed at enhancing flexibility and performance in malware operations. It outlines the architecture, features, and debugging capabilities of the platform, emphasizing cross-platform support and a user-friendly interface for operators. The presentation was delivered by Zander Work at DistrictCon 0 in February 2025.

Uploaded by

pgresql
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 56

implant.

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

○ Low-level performant code for runtime

○ Easy-to-iterate plugin environment

● Improved performance and stability

● Cross-platform and cross-architecture support

● Debugging failures in “prod”

8
Intro to V8

9
Background
● Open source robust JavaScript engine
○ Powers (nearly) all browsers

● API for Embedding into 3rd Party Applications


○ NodeJS

● Incredibly Capable
○ Native debug and profiling
○ Sandboxing

10
v8 Embedding
● Platform: Top-level object defining the runtime environment

● Isolate: An instance of a JavaScript VM

● Context: Distinct execution environment where JS is executed

● 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

○ Debug modules on target

13
Operator Interface
cmd> help
implant.js commands:

lsmod - list available modules


reload - reload modules from disk
run <module> - run the specified module
debug <module> - run the specified module in interactive
debug mode
dc - disconnect from the client
exit - terminate the server

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

import { hex, ON_LINUX, ON_WINDOWS } from "lib/utils.js";

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

C++ OS-specific implementations


C++ OS-specific implementations

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);

auto ptr = STATE->mem_alloc((uint32_t)sz, (uint32_t)perms);


if (!ptr) {
THROW_ERROR("failed to alloc memory");
return;
}

v8::Local<v8::BigInt> ret(v8::BigInt::NewFromUnsigned(iso, (uint64_t)ptr));


info.GetReturnValue().Set(ret);
}
22
void* State::mem_alloc(std::size_t sz, uint8_t perms)
OS Interop Example {
...
switch (perms) {
const ptr1 = ctx.mem.alloc(500, MEM_RW);
case CONST_MEM_RW:
TEST_NE(ptr1, null);
ma->type = HEAP;
ma->ptr = Utils::Mem::alloc_heap(sz);
JS_HANDLER(JsNatives::mem::alloc) {
if (!ma->ptr) {
...
goto err;
double sz, perms; }
ARG_NUMBER(info[0], sz); break;
ARG_NUMBER(info[1], perms); case CONST_MEM_RWX:
ma->type = PAGE;
ma->ptr = Utils::Mem::alloc_pages(sz);
auto ptr = STATE->mem_alloc((uint32_t)sz, (uint32_t)perms);
if (!ptr) { if (!ma->ptr) {
THROW_ERROR("failed to alloc memory"); goto err;
return; }
} break;
...
}
v8::Local<v8::BigInt> ret(v8::BigInt::NewFromUnsigned(iso, (uint64_t)ptr));
...
info.GetReturnValue().Set(ret);
}
}
23
void* State::mem_alloc(std::size_t sz, uint8_t perms)
OS Interop Example {
...
switch (perms) {
const ptr1 = ctx.mem.alloc(500, MEM_RWX);
void* Utils::Mem::alloc_heap(std::size_t sz) { case CONST_MEM_RW:
TEST_NE(ptr1, null);
void* ptr = nullptr; ma->type = HEAP;
ma->ptr = Utils::Mem::alloc_heap(sz);
#ifdef LINUX
JS_HANDLER(JsNatives::mem::alloc) {
if (!ma->ptr) {
ptr ...
= malloc(sz);
goto err;
if (!ptr)
double {sz, perms; }
LOG_ERROR_ERRNO("failed to allocate memory via malloc");
ARG_NUMBER(info[0], sz);
return nullptr; break;
} ARG_NUMBER(info[1], perms); case CONST_MEM_RWX:
#endif ma->type = PAGE;
#ifdef auto
WINDOWS ma->ptr = Utils::Mem::alloc_pages(sz);
ptr = STATE->mem_alloc((uint32_t)sz, (uint32_t)perms);
ptr if
= HeapAlloc(GetProcessHeap(),
(!ptr) { HEAP_ZERO_MEMORY, sz);
if (!ma->ptr) {
if (!ptr) {
THROW_ERROR("failed to alloc memory"); goto err;
LOG_ERROR("failed to allocate memory via HeapAlloc");
}
return;
return nullptr;
break;
} }
...
#endif
}
v8::Local<v8::BigInt> ret(v8::BigInt::NewFromUnsigned(iso, (uint64_t)ptr));
return ptr; ...
info.GetReturnValue().Set(ret);
} }
}
24
void* State::mem_alloc(std::size_t sz, uint8_t perms)
OS Interop Example {
...
switch (perms) {
const ptr1 = ctx.mem.alloc(500, MEM_RWX);
void* Utils::Mem::alloc_heap(std::size_t sz) { case CONST_MEM_RW:
TEST_NE(ptr1, null);
void* ptr = nullptr; ma->type = HEAP;
ma->ptr = Utils::Mem::alloc_heap(sz);
#ifdef LINUX
JS_HANDLER(JsNatives::mem::alloc) {
if (!ma->ptr) {
ptr ...
= malloc(sz);
goto err;
if (!ptr)
double {sz, perms; }
LOG_ERROR_ERRNO("failed to allocate memory via malloc");
ARG_NUMBER(info[0], sz);
return nullptr; break;
} ARG_NUMBER(info[1], perms); case CONST_MEM_RWX:
#endif ma->type = PAGE;
#ifdef auto
WINDOWS ma->ptr = Utils::Mem::alloc_pages(sz);
ptr = STATE->mem_alloc((uint32_t)sz, (uint32_t)perms);
ptr if
= HeapAlloc(GetProcessHeap(),
(!ptr) { HEAP_ZERO_MEMORY, sz);
if (!ma->ptr) {
if (!ptr) {
THROW_ERROR("failed to alloc memory"); goto err;
LOG_ERROR("failed to allocate memory via HeapAlloc");
}
return;
return nullptr;
break;
} }
...
#endif
}
v8::Local<v8::BigInt> ret(v8::BigInt::NewFromUnsigned(iso, (uint64_t)ptr));
return ptr; ...
info.GetReturnValue().Set(ret);
} }
}
25
Demo 1
Demo 1
implant.js - Debugger
Debugging Embedded v8
● V8 exposes interfaces for instrumenting the runtime for debugging and
profiling purposes
● Typical use case is to proxy the traffic over a websocket to Chrome’s built-in
dev tools
● Debugger communicates with a frontend via Chrome Devtools Protocol (CDP)
○ JSON-RPC (mostly)
○ https://chromedevtools.github.io/devtools-protocol/v8/

{"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:

c, continue - continue execution


s, step - step into
n, next - step over
so, stepout - step out of (finish function)
k - show current call stack

bp, breakset - set breakpoint


bl, breaklist - list breakpoints
bc, breakclear - clear breakpoint

l, list - show source code


e, eval - show a js var/expression value

q, quit - end debugging session

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()

GetProcAddress() callback() + jshandle_t

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

● Snort/Suricata Detections published to ET OPEN

○ gr33tz to Genina & Stuart @ Proofpoint for the collab

● Sigma rules coming soon to SigmaHQ/sigma

● YARA signatures and more available in the implant.js repo

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

cobalt strike implant.js

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

You might also like