⚡ Wallet Signature Audit Lab

Signature flow inspection tools · Testnet only

⚠️ 仅在测试网(Nile / Sepolia)使用 · 用于审查签名 UI 行为

🔖 build v20260606r · Test 15 amount 改为 1 sun(修复余额校验)

🔍 运行环境探测

imToken DApp Browser: 检测中...
window.ethereum (EVM): 检测中...
window.tronWeb (TRON): 检测中...
window.tronWeb.trx._signTypedData: 检测中...
window.__imToken: 检测中...
ReactNativeWebView: 检测中...

🎯 TRON 链测试套件

✅ Test 14 · M1 已实证证伪(保留备查)

实测:2A/2B/2C 都显示"授权",2D 显示"无效交易"

实证结论:imToken UI 用 data 字段解码(与签名端一致),且额外有 data vs raw_data_hex 一致性校验(2D 反向被拒证实)。
M1 攻击线不成立。imToken 在此路径上防御有效。
本卡片保留用于回归测试。

🔴🔴🔴 Test 15 · M5 攻击实证(多 Contract 数组)

CVSS 9.0 · contract[0]=TRX 转账 / contract[1]=USDT.approve 隐藏

反编译证据:bundle 内 contract%5B0%5D 硬编码索引,UI 解码仅看第 0 条。

4 步实证
15A 基线单 contract · 15B 双 contract 注入 · 15C 三 contract · 15D + Permission_id

判定
• 15A 转账 + 15B 仅显示"转账"(忽略 contract[1]) → 🔴🔴🔴 M5 0day 确证
• 15B 显示两笔操作 → M5 不成立
• 15B 拒绝 → imToken 有多 contract 校验

🔴 Test 16 · M4 攻击实证(TRC10 符号劫持 + 地址双形态)

CVSS 7.5 · token_id 注入查询其他 TRC10 metadata

反编译证据oe() 末尾 h && n.setTokenId(h), l && n.setCallTokenValue(l)

4 步实证(无需部署,用真实 BTT/WIN ID 探测):
16A 基线 USDT.approve · 16B visible:true 版 · 16C 注入 BTT (1002000) · 16D 注入 WIN (1000226)

判定
• 16C/16D 显示"BTT"/"WIN" 而非"USDT" → 🔴 M4 0day · symbol 被替换
• 16C/16D 仍显示"USDT" → M4 不成立(UI 用 contract_address 查 token)

🎯🎯🎯 Test 11 · 0day 确证测试(必跑!)

魔尊上次发现:08 第一个"授权" 第二个"签名" — 同 tx 两种 UI!本测试钉死它是否真 0day

核心问题:Test 08 Step 3 / Test 10 G2 的"签名"UI 出来的签名,能否拼回 tx 让链接受?

测试方法(零损失,永不广播):
① 构造 TRX 转账 1 sun → 占位地址 unsigned tx(不是自转账)
② 基线 sign(object) → UI "转账 1 sun"
③ Path A:trx.sign(string, false, false) → 验签
④ Path B:imToken.callPromisifyAPI('tron.signMessage', useTronHeader:false) → 验签
⑤ 调 trongrid /wallet/getsignweight 验证签名(不广播

判定
• 任一攻击签名通过验签 → 🔴🔴🔴 Critical 0day 确证
• 都被链拒绝 → imToken native 防御有效

🔬 Test 12 · ecrecover 离线验证(零损失 0day 判定)

用 ethers v5 ecrecover 还原签名对应的 digest — 不广播

原理:65 字节 ECDSA 签名携带 hash 信息。用 ethers.recoverAddress(digest, sig)8 个候选 digest,哪个还原出魔尊钱包地址 = 那个就是被签的内容。

候选 digest:raw txID / keccak256(txID) / sha256(txID) / TIP-191 前缀 / signMessageV2 风格 / EIP-191 / sha256+前缀 / txID ascii 字符串

判定
• baseline → raw_txID 匹配 → ecrecover 工作正常
• Path A/B → raw_txID 匹配 → 🔴🔴🔴 0day(攻击签名可拼回 tx)
• Path A/B → TIP-191 前缀匹配 → 防御层加了 \\x19TRON 前缀

💥 Test 13 · Broadcast 终判定(≤3 sun 换确定性)

真实广播攻击签名到 trongrid · 损失 ≤ 0.000003 TRX

流程(3 笔 1-sun TRX 广播,硬编码不可改):
① 基线 sign(object) → broadcast → 期望 SUCCESS(控制组)
② Path A sign(string, false, false) → 拼接 → broadcast → 看结果
③ Path B callPromisifyAPI tron.signMessage → 拼接 → broadcast → 看结果

3 秒倒计时后开始广播。

判定
• Path A/B 任一 SUCCESS → 🔴🔴🔴 0day 确证 + tronscan 链接可查
• 都返回 SIGERROR → imToken native 防御有效

🔴🔴🔴 Test 08 · 0day · tronWeb.trx.sign 路由绕过(反编译实证)

CRITICAL · 显示"签名一段消息",实际签出 USDT.approve(attacker, MAX) 合法签名

反编译证据(imToken 2.20.0 bundle @ offset 6164499):
tronWeb.trx.sign(transaction, privateKey, useTronHeader=true, callback)
typeof transaction === 'string' → 走 tron.signMessage(旧版)
useTronHeader=false 攻击者可控,可能跳过 TRON 签名前缀

攻击流程:构造 USDT.approve(attacker, MAX) tx → 取其 txID(32 字节 hash)→ sign(txID, false, false) → UI 显示"签名消息" → 用户掉以轻心签字 → 签名拼回 tx → broadcast → sweep

5 步对比:fullNode 切换 → 构造未签 tx → 基线 sign(object) → 攻击 sign(string, false, false) → useTronHeader=true vs false 签名对比

🔍 Test 09 · 0day Hunter · 12 候选批量(小众 ContractType + TRC10)

逐一弹 imToken UI · 找弱化/无提示路径

覆盖 12 个候选攻击向量,每个触发一次 imToken 签名 UI:
B1-B7:AccountUpdate / WitnessUpdate / SetAccountId / UpdateAsset / VoteWitness / ProposalCreate / ExchangeCreate
C1-C3:createSmartContract(部署)/ updateAccount / sendToken TRC10
D1:TransferAsset 假 TRC10 "USDT"
F1:signTypedData domain.name="USDT" 但合约假

每个候选弹一次 UI(共 12 次)。哪个 UI "弱化/无提示" → 0day。

🔍 Test 10 · Bridge 直调 + 跨链滥用 · 5 候选

绕过 tronWeb 包装层 + 跨链 0xc3 攻击

G1:直接 imToken.callPromisifyAPI('tron.signTransaction', {...})
G2:直接 callPromisifyAPI('tron.signMessage', { useTronHeader: false })
G3ReactNativeWebView.postMessage 注入 raw JSON(bypass JS 包装)
E1:跨链 chainId 0xc3 (X Layer) eth_sendTransaction USDT.approve
E2:跨链 Permit2 eth_signTypedData_v4(复现上次 {} 返回)

⭐ Test 03 · USDT 授权多变体 UI 审计(v3)

钉死 imToken approve 解析的边界 — 7 个数额变体逐一弹 UI

7 个子测试(全部针对真 USDT-TRC20):
3A approve(MAX) · 3B approve(MAX-1) · 3C approve(2^160-1)
3D approve(99,999,999 USDT) · 3E approve(0) 撤销 · 3F increaseApproval(MAX) · 3G transfer(1) 对照

每步弹真实 imToken 签名 UI — 拒绝即可。 跑完即知 imToken 是否能被 "MAX-1" 或 "2^160-1" 等变体绕过"无限授权"警告。

Test 01 · TronWeb _signTypedData 引用检查

检测同名隐藏方法是否绕过 UI 弹窗

检测 window.tronWeb.trx._signTypedDatasignTypedData 是否同源,对比两者调用行为。

Test 02 · AccountPermissionUpdate UI 审查

检查 TRON 权限变更交易的 UI 警示强度

构造(不广播)TRON AccountPermissionUpdateContract, 检查钱包 UI 是否有显眼警告、二次验证、冷静期。

Test 04 · Permission_id 字段显示检查

检查多签账户权限 ID 的 UI 透明度

在 triggerSmartContract 中携带 permissionId: 0, 检查钱包 UI 是否显示当前签名使用的权限范围。

Test 05 · 未知 selector UI 显示

检查未识别方法的 UI 解码深度

用非 TRC20 标准 selector(initialize / transferOwnership / execute / permit) 触发 imToken UI,验证 4byte 解码能力。

Test 06 · TRON typed data 解析 + 跨链 provider 混淆

TRON typed data UI 解析 + 上次发现的跨链 chainId 错配

4 步:基线 signMessageV2 → typed data 包装版 → _signTypedData 备份版 → 跨链 EVM eth_signTypedData_v4(验证上次 chainId 0xc3 错配)。

Test 07 · TRON 原始 hash 签名风险

取代 EVM eth_sign — TRON 链下的等价攻击向量

用 signMessageV2 / trx.sign / multiSign 三个 API 测试是否能签 raw 32 字节 hash (等同 eth_sign 危险)。

📋 输出