安全研究|Uniswap v4 Hook攻击向量解析与检测方法

作者:Beosin 发布:2026-10-09 23:30 收录:2026-10-10 00:08 4 次阅读 约 2163 字
摘要:近期 Robinhood Chain 的热潮吸引了大量用户在其链上的Uniswap交易。当用户发现某个Uniswap v4池子显示0%的手续费,报价比市场最优价格还低几个基点时,将它选为最佳路由进行交易。但交易完成后他实际收到的代币,比报价少了接近1%。…
推荐理由:本文围绕「链上数据」、「Beosin」等主题展开。

近期Robinhood Chain的热潮吸引了大量用户在其链上的Uniswap交易。当用户发现某个Uniswap v4池子显示0%的手续费,报价比市场最优价格还低几个基点时,将它选为最佳路由进行交易。但交易完成后他实际收到的代币,比报价少了接近1%。检查链上数据后可以发现,这个损失实际是因为隐藏在Hook合约里的“暗税”,Hook通过afterSwapReturnsDelta权限在交易结算前截留了一部分代币。


9月流动性聚合基础设施0x Protocol对6条链上的84,163个Uniswap v4 Hook进行了静态分析、动态分析和实际成交观察,发现仅19.4%的Hook被判定为安全,54.2%被归类为恶意,另有26.4%存在疑似恶意行为。本文Beosin安全团队将对Uniswap v4 Hook的攻击向量进行解析,帮助用户和开发者理解其中可能存在的恶意行为。

 

一、Uniswap Hook机制


在 Uniswap v4 中,流动性池子由 PoolManager 统一管理,Hook 合约可以在池子初始化、添加流动性、移除流动性、Swap 和 Donate 等生节点插入自定义逻辑,如修改交易费、交易前后余额和结算逻辑。其安全风险主要集中在以下位置:

- Hook 返回异常的余额变化
- Hook 在回调中调用外部合约
- Hook 读取瞬时价格并据此决定费率
- Hook 保存跨池、跨用户的状态
- Hook 利用回调顺序和交易回滚机制制造拒绝服务

攻击者可能首先建立一个看起来正常的流动性池,在 Hook 中加入隐藏收费、黑名单逻辑,然后通过前端、空投、聚合器或社交媒体吸引用户。在用户进行交易或提供流动性后,即可通过自定义逻辑、外部调用恶意获取其他用户资产。据Beosin分析,目前常见的恶意Hook形式有:

- 每次交易抽取少量额外费用
- 对特定地址收取不同费率
- 在价格接近某阈值时突然改变行为
- 让流动性提供者无法移除流动性
- 通过回调消耗大量 Gas,使得兑币交易失败
- 诱导用户调用假冒的 unlock、settle 或 token 转账函数

二、恶意Hook示例解析


1. 隐性交易费

下面的Hook展示了“对指定地址收取额外费用”的逻辑:开发者通过privilegedUsers给自己、做市商或指定机器人免除费用,而其他用户需承担额外交易费用。

contract SuspiciousFeeHook {    address public immutable manager;    address public immutable feeRecipient;     mapping(address => bool) public privilegedUsers;     constructor(address _manager, address _feeRecipient) {        manager = _manager;        feeRecipient = _feeRecipient;    }     function beforeSwap(        address sender,        uint256 amountIn    ) external returns (uint256 adjustedAmountIn) {        require(msg.sender == manager, "only manager");         if (privilegedUsers[sender]) {            return amountIn;        }         uint256 hiddenFee = amountIn * 30 / 10_000;        adjustedAmountIn = amountIn - hiddenFee; // demo, in reality, Hook should not bypass the settlement rules of the PoolManager        IERC20(token()).transferFrom(            sender,            feeRecipient,            hiddenFee        );    }     function token() internal view returns (address) {        return address(0);     }}

在真实 v4 架构中,Hook 通常不能任意绕过 PoolManager 的结算规则,但它可以通过自定义 delta、外部代币调用或池配置影响最终结算。因此,审计重点是检查 Hook 的所有余额变化路径。


2. 动态价格或费率


contract AddressBasedHook {    mapping(address => bool) public blocked;    mapping(address => uint24) public customFee;     function beforeSwap(        address sender,        uint256 amountSpecified    ) external returns (uint24 fee) {        if (blocked[sender]) {            revert("blocked trader");        }         uint24 userFee = customFee[sender];         if (userFee != 0) {            return userFee;        }         return 10_000; // default 100%    }}

以上代码存在黑名单机制且根据用户地址设置不同费率。如果费率没有被前端完整展示,用户看到的池子可能与实际交易条件不一致。在审计或开发时必须确认所有费率返回值均满足协议允许范围,而不是只检查常规路径。


3. 重入和外部回调


contract ReentrantHook {    address public callbackTarget;    bool private entered;     function beforeSwap(address sender) external {        require(!entered, "reentered");        entered = true;         // 外部调用,可能回调当前 Hook 或其他协议        ICallback(callbackTarget).onSwap(sender);         entered = false;    }}

Hook 中的外部调用可能触发:

- ERC-777 或自定义 ERC-20 回调;

- ERC-721/ERC-1155 接收回调;

- 借贷协议回调;

- 聚合器回调;

- 当前 Hook 的再次调用;

- 其他池子的 Swap。


如果状态更新和外部调用顺序错误,可能发生重复计算结果、绕过单次交易限制、重复领取手续费、跨池状态污染、价格或额度检查失效等问题。需要注意,v4 的核心合约具有自身的解锁和结算约束,但这不意味着 Hook 可以安全地进行任意外部调用。安全边界必须在每个 Hook 中单独建立。


4. 利用瞬时价格进行预言机操纵


contract FragileOracleHook {    uint256 public lastObservedPrice;     function beforeSwap(        uint160 currentSqrtPriceX96    ) external {        uint256 spotPrice = priceFromSqrtPrice(currentSqrtPriceX96);         // 错误做法:直接使用当前池子的瞬时价格        if (spotPrice > 1_100e18) {            triggerLiquidation();        }         lastObservedPrice = spotPrice;    }     function triggerLiquidation() internal {        // 教学占位:根据价格执行清算或发放奖励    }}

攻击者可以在同一交易中通过闪电贷获得大量资产;然后对目标池进行大额 Swap 将瞬时价格推向极端,由此触发 Hook 的清算、奖励或抵押品估值逻辑。如果 Hook 把当前池价格当作可信预言机,攻击者可能在一个区块内完成操纵。


5. 阻止流动性退出


contract ExitBlockingHook {    mapping(address => bool) public blockedLP;     function beforeRemoveLiquidity(address provider) external {        if (blockedLP[provider]) {            revert("withdraw disabled");        }    }    function setExitLock(address user, bool locked) external onlyOwner {    blockedLP[user] = locked;    }}

这类 Hook可以让用户加入流动性,却无法移除流动性。常见触发条件包括:

- 用户地址被加入黑名单

- 池子达到某个 TVL

- 某个时间戳之后

- Hook 管理员调用隐藏开关

- 用户曾经通过聚合器交易

- 用户没有支付额外费用


6. Gas Griefing


此类攻击其实在以往的合约攻击中也非常常见,通过在交易中消耗异常 Gas导致交易失败。
contract GasGriefHook {    function beforeSwap(uint256 n) external {        for (uint256 i = 0; i < n; i++) {            keccak256(abi.encodePacked(i, msg.sender));        }    }}

更隐蔽的形式包括:

- 遍历不断增长的地址数组;

- 清理历史用户状态;

- 对大量池子逐一查询余额;

- 在失败前执行大量计算;

- 调用多个恶意外部合约。


三、检测恶意Hook方法


1. 检查 Hook 地址权限位

确认 Hook 地址声明了哪些回调权限,并与实际字节码中的函数进行比对。
如果地址声明了beforeSwap、afterSwap、beforeAddLiquidity、beforeRemoveLiquidity,则需要逐一分析对应函数。

2. 获取代码

比如可以使用区块浏览器或 RPC 调用eth_getCode(hookAddress) 检查以下关键点:

- 是否为代理合约;
- 是否存在升级入口;
- 是否存在自毁逻辑;
- 是否包含未知外部调用;
- 是否与已验证源码一致。

3. 检查流动性池初始化交易

需重点查看PoolKey、currency0/currency1、fee、tick spacing、Hook 地址、初始化地址、管理员、资金接收地址。

4. 模拟边界场景

建议至少模拟正常 Swap、极小金额的 Swap、大额 Swap、零金额Swap、价格极端变化、重复 Swap、添加后立即移除流动性、Hook 管理员变更、代币转账失败、外部调用回调等场景,比较用户实际支付和用户实际收到的代币数量、Hook 余额变化、PoolManager delta、事件记录和 Gas 消耗。

结语

Uniswap v4 Hook 旨在让 Uniswap 更具可扩展性,但它们也引入了新的攻击面和被恶意利用的可能。对于普通用户而言,Hook安全最重要的判断标准是“这个 Hook 是否经过验证、源码是否公开、权限是否受限。对于整个DeFi生态来说,DeFi协议、安全公司需采取更多措施,如检测工具和针对流动性池的审查,来提高链上交易的安全性和可靠性。

拓展阅读:
《Uniswap v4 Hook解析:架构设计、常见漏洞与防护实践》

Beosin是一家领先的区块链安全与监管合规科技公司,专注于项目上线前的智能合约安全审计、项目运行时的安全风险监控与阻断、被盗追回、虚拟资产反洗钱(AML)以及调查追踪。Beosin已为全球20多个国家和地区的监管与执法机构、200多家虚拟资产服务商以及4500多家Web3项目提供“一站式”区块链合规产品+安全服务。欢迎点击公众号留言框,与我们联系。
































转载声明:本文转载自原发布平台 (作者:Beosin), 原文标题《安全研究|Uniswap v4 Hook攻击向量解析与检测方法》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。