跳转至

Phishing

Definition

链上钓鱼行为(On-chain phishing)指的是攻击者通过特定手段诱导受害者将加密代币直接或间接地转移给攻击者的一种网络安全攻击形式。具体而言,此类行为可细分为直接转移(Direct Transfer)和间接转移(Indirect Transfer)。在直接转移的攻击模式中,受害者被诱导执行 transfer 操作,将代币直接从其钱包发送到攻击者的地址。间接转移涉及更为复杂的步骤,首先包括受害者在不完全了解情况的情况下对攻击者进行授权(approve/permit),允许攻击者代表他们执行transferFrom操作。这一过程通常涵盖了对受害者的权限设置和可能的信任滥用。

Ice Phishing:Ice Phishing中,攻击者诱使用户通过授权操作,允许攻击者从用户账户中转移代币。一旦获得授权,攻击者随后执行transferFrom操作,将用户的资产转移到攻击者控制的地址。

Address Poisoning Phishing:Address Poisoning Phishing涉及向用户发送微量的代币或不具价值的代币,使得攻击者的地址出现在用户的交易历史记录中。这一策略利用了用户可能会错误地将值钱的代币转移到先前交易中出现的地址的心理和操作失误,从而误将资产转给攻击者。

Detection

Ice Phishing Detection

定义2.1.1 定义一个交易 \(tx\) 中,所有的 \(transfer\) 行为为\(\text{TransferSet}(tx)\)

定义2.1.2 定义一个交易 \(tx\) 中,一个账户 \(account\) 的余额变化为 \(\text{BalanceChange}(account)\)

定义2.1.3 定义一个调用在一笔交易中的 \(\text{Participant}(action)\) (在RugPull检测中定义了)

定义2.1.4 考虑到钓鱼交易往往比较简单的特点,我们定义一个判断\(transfer\)是否简单的函数。判断依据主要有2个

  • 钓鱼合约一般不打Log。

[!NOTE]

这个并不是很有依据。唯一发现的打Log的疑似钓鱼交易:https://app.blocksec.com/explorer/tx/eth/0x3f6d8a8384b3c8fdb676541b319ac6f7777e5ee418b3fa06b4ce60bc383c8208?line=0

  • 钓鱼交易的资金流一般比较简单,受害者的资金单纯的减少。
\[ \text{IsSimple}(transfer) \leftarrow (\forall c \in \text{BalanceChange}(transfer.from), c \le 0) \\ \land (\forall i \in \text{Participant}(transfer), |i.logs| = 0) \]

定义2.1.5 给出判断一个transfer是否是ice phishing的方法:

  1. transfer.from 不是 tx.origin 。这说明transfer 不是用户主动发起的。
  2. transfer.from 是 EOA。
  3. Transfer的金额几乎是用户的余额。攻击者没有理由不把用户的余额取光

[!NOTE]

然而现实有一些疑似钓鱼的交易,留了很多钱: https://app.blocksec.com/explorer/tx/eth/0x32f17ac8b2d471d8a1461c7f32da32a1bb73e1948ee53bcc5346e8c77dcbf61b/?line=0

  1. 钓鱼交易比较简单(定义2.1.4)
  2. transfer的Participant和recipient不在白名单中。由于一些交易实在难以区分,加入白名单机制。(~~或者结合历史交易?例如有资金往来?Approve和TransferFrom的间隔?有待考证~~)如果不加入,会有较多的FP。目前,导入了所有热钱包的地址,以及0x0和0xdead和部分合约为\(Whitelist\)。不加入白名单导致的FP如下。如果不看标签,我真会觉得可能是钓鱼。

  3. e.g. 交易所热钱包 https://app.blocksec.com/explorer/tx/eth/0x7dc52028adacda75a5a90d25f2dc48f5a8282b2788aaba5a9783fa3c46cf8b3c

  4. e.g. 跨链桥 https://app.blocksec.com/explorer/tx/eth/0xf9db918627ad9774aa06929070cfdf7e65c7b2db093da600889b903ee6f9e414?line=5
\[ \text{IsIcePhishing}(transfer) \leftarrow transfer.from \neq transfer.tx.from \land \text{isEOA}(transfer.from) \land \\ \frac{transfer.value}{transfer.from.value} > limit \land \text{IsSimple}(transfer) \land \\ \text{Participants}(transfer) \cap \text{Whitelist} = \empty \land transfer.to \notin \text{Whitelist} \]

[!NOTE]

事实上,我们无法判断用户的Approve行为,是否是受到诱骗后的行为。所以这里应该是 Potential Ice Phishing。

TransferFrom行为也分两种,一种是特权的TransferFrom,例如直接从EOA发起,或者调用有身份验证的特权合约。另一种是任何人都可以调用的TransferFrom,如Arbitrary Call,或者有问题的TransferFrom。现在也没有做任何判断。

Address Poisoning Phishing Detection

定义2.2.1 可以通过交易的存储变化来检测假代币。假Token为了欺骗用户,必然要加Log,大概是出于节省Gas的目的,观察到的假Token不会修改Storage。

\[\text{IsFakeTransfer}(transfer) \leftarrow |\text{TransferSet}(transfer.tx)| > limit \land \text{StorageWrite}(transfer.tx) = \emptyset\]

定义2.2.2 将向用户account发送假Token,或者很少量真Token的所有地址记为

\[\text{PoisonAddressSet}(account) = \bigcup_{transfer} \{ transfer.from \mid transfer.to=account \land \\ (\text{IsFakeTransfer}(transfer) \lor (\neg \text{IsFakeTransfer}(transfer) \land transfer.amount < \text{limit})) \}\]

[!NOTE]

假Token Transfer实在是太多了,改成记录Tx试试。实际上假Token Transfer没有完成钓鱼,但是攻击检测爆出的是假Token Transfer的交易,所以入另一个库。

定义2.2.3 那么我们检测Address Poisoning Phishing就可以通过如下方式

\[ IsAddressPoisonPhishing(transfer) \leftarrow transfer.from = transfer.tx.from \land \\ transfer.to \in PoisonAddressSet(transfer.from) \]

Implementation

部署在k8s上运行了,运行在 Ethereum, Bsc, Arbitrum, Optimism上。结果写入Postgresql的 archive_phishingarchive_fake_token 表。

由于和Rugpull检测共享一些底层代码,目前组合成一个服务。RugPull写在archive_rugpull_info表中。简单的过滤可以查表。

目前RugPull检测只能发现服务开始运行之后部署的Token的RugPull行为。因为对Token疑似特权账户的监控必须从Token部署开始。频繁的重启(现在基本没有因为Bug重启,主要是更新)会导致很多的遗漏。要不要单独将Token疑似特权账户的监控作为一个服务

如果发现误报,可以将白名单地址加入Postgresql的 whitelist_phishing_participants,然后POST http://host:7001/update_whitelist

Evaluation

对于降低攻击检测的FP的实验:

攻击事件数据集,158个攻击,

陈卓学长的数据集:TODO

链上历史:TODO

Something Interesting

还是想简单了,用uwin的工具调了几个case,发现两类反例:

  • 特定参数:对于permit类型的钓鱼,发起transferFrom的钓鱼合约很可能是谁都可以调用的,关键是要传正确的permit参数。只用这个维度作为区分钓鱼和攻击的话, 会把这类钓鱼全都当成arbitrary call。
  • 特定获利者:又或者是钓鱼合约谁都可以调用,但是会把获利发送到固定的地址。

在我只看了几个例子的情况下,就已经有这两类了。 这个维度肯定是有用的,但是作为单一维度还是不够, 可能得实现出来扫一遍才知道会怎么样,然后加更细粒度的规则。好麻烦。

区分Arbitrary Call, TODO

对TransferFrom的Participants依次进行替换

  • TransferFrom的msg.sender替换后,执行:
  • 成功:transferFrom可能没有校验Allowance
  • 失败:
  • Participants中的某msg.sender替换后,执行:
  • 成功:这个invocation没有对msg.sender进行校验。
  • 失败:这个invocation对msg.sender可能进行校验。

但是没有这么简单,校验可能发生在参数上。没有校验还可能是因为获利者写死了。

  • 替换参数中的获利者。
  • Permit的参数得识别出来。