Z123 Exploit Analysis
💰

Z123 Exploit Analysis

Published
May 28, 2024
Author
daleewong

Intro

time:2024-04-22
Total Lost : 135k

Detail

闪电贷借了18_000_000 ether
notion image
之后就是一直执行大概80次。然后就还钱走人了
notion image
合约.swapExactTokensForTokensSupportingFeeOnTransferTokens(7125 ether, 1, path, address(this), block.timestamp);
这个合约没有verify,先看看这个函数调用都干了啥吧。
notion image
发现一个QiaoswapV2Pair,没听说过。看下合约代码,和uniswap一样。
notion image
仔细看交易,发现了华点。

漏洞点

每次的swap之后,它都销毁流动性阿。好人阿,销毁的还不少呢。这不就越换越多吗。
notion image
function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline) public payable { require(msg.data.length - 4 >= 160); require(path <= 0x100000000); require(path.data <= 4 + (msg.data.length - 4)); require(!((path.length > 0x100000000) | (path.data + (path.length << 5) > 4 + (msg.data.length - 4)))); v0 = new uint256[](path.length); CALLDATACOPY(v0.data, path.data, path.length << 5); v0[path.length] = 0; require(stor_1_0_0, Error('ReentrancyGuard: reentrant call')); stor_1_0_0 = 0; assert(v0.length); v1 = v0.data; require(address(v0[0x0]) == _setToken, Error('not:sell')); v2 = _SafeMul(4, amountIn); v3 = _SafeDiv(10, v2); require(bool(_setToken.code.size)); v4 = _setToken.transferFrom(msg.sender, _swapExactTokensForTokensSupportingFeeOnTransferTokens, amountIn).gas(msg.gas); require(bool(v4), 0, RETURNDATASIZE()); // checks call status, propagates error data on error require(RETURNDATASIZE() >= 32); v5, v6, v7, v8, v9 = v10, v9 = v11 = 0x2153(_swapExactTokensForTokensSupportingFeeOnTransferTokens); if (_setToken == address(v6)) { v12 = v13 = v8 - v11; } else { v12 = v7 - v10; } if (_setToken != address(v6)) { } v14 = v15 = 0x942(v9, v9, v12); if (_setToken == address(v6)) { v14 = v16 = 0; } else { v14 = 0; } assert(1 < v0.length); require(bool((address(v0[0x1])).code.size)); v17, /* uint256 */ v18 = address(v0[0x1]).balanceOf(to).gas(msg.gas); require(bool(v17), 0, RETURNDATASIZE()); // checks call status, propagates error data on error require(RETURNDATASIZE() >= 32); v19 = new uint256[](0); v20 = 0; while (v20 < 0) { MEM[v20 + v19.data] = MEM[v20 + (32 + MEM[64])]; v20 += 32; } v21 = v19.data; if (0) { MEM[v19.data] = ~0x0 & MEM[v19.data]; } require(bool(_swapExactTokensForTokensSupportingFeeOnTransferTokens.code.size)); v22 = _swapExactTokensForTokensSupportingFeeOnTransferTokens.call(uint32(0xb6b4c488), v14, v14, to, v12, v19).gas(msg.gas); require(bool(v22), 0, RETURNDATASIZE()); // checks call status, propagates error data on error require(bool((address(v0[0x1])).code.size)); v23, /* uint256 */ v24 = address(v0[0x1]).balanceOf(to).gas(msg.gas); require(bool(v23), 0, RETURNDATASIZE()); // checks call status, propagates error data on error v25 = 0x536166654d6174683a207375627472616374696f6e206f766572666c6f770000fff6cae900000000000000000000000000000000000000000000000000000000; require(RETURNDATASIZE() >= 32); if (v18 <= v25.length) { if (v25.length - v18 >= amountOutMin) { if (deadline >= block.timestamp) { MEM[4 + MEM[64]] = _swapExactTokensForTokensSupportingFeeOnTransferTokens; require(bool(_setToken.code.size)); v26 = _setToken.update(_swapExactTokensForTokensSupportingFeeOnTransferTokens, v3).gas(msg.gas); require(bool(v26), 0, RETURNDATASIZE()); // checks call status, propagates error data on error require(bool(_swapExactTokensForTokensSupportingFeeOnTransferTokens.code.size)); v27 = _swapExactTokensForTokensSupportingFeeOnTransferTokens.sync().gas(msg.gas); require(bool(v27), 0, RETURNDATASIZE()); // checks call status, propagates error data on error stor_1_0_0 = 1; exit; } else { MEM[MEM[64] + 4] = 32; revert(Error('QiaoswapV2Router: EXPIRED')); } } else { MEM[4 + MEM[64]] = 32; revert(Error('QiaoswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT', v28, v28, 0)); } } else { MEM[MEM[64] + 4] = 32; v29 = new bytes[](v25.length); if (v25.length) { v30 = v25.data; v31 = v29.data; v29[0] = v25[0]; v32 = v33 = 32; while (v32 < v25.length) { v29[v32] = v25[v32]; v32 += 32; } } if (30) { MEM[v29.data] = ~0xffff & MEM[v29.data]; } revert(Error(v29, v28, v28, 0)); } }
代码出在这行上面,销毁流动性,抬高了价格
//v3等于amount in 的 4/10 v2 = _SafeMul(4, amountIn); v3 = _SafeDiv(10, v2); //调用z123的update v26 = _setToken.update(_swapExactTokensForTokensSupportingFeeOnTransferTokens, v3).gas(msg.gas); ///////////////// //z123把pair 里的token向dead地址销毁 function update(address pair,uint256 amount) public onlyMinter { super._transfer(pair, 0x000000000000000000000000000000000000dEaD, amount); }

End

burn的时候,一定注意,最近看到好多因为销毁流动性造成的exploit了