合约漏洞分析一例

一年前在 BSC 链看到一个很有意思的合约漏洞,涉及资金还不少,不过现在早已没有利用条件了,可以记录分享出来 源码只截取关键部分,其他可以去 Bscscan 查看合约代码 1contract CZCrazyIdea is Context, IERC20, Ownable, ReentrancyGuard { 2 using SafeMath for uint256; 3 using Address for address; 4 5 string private _name; 6 string private _symbol; 7 uint8 private constant _decimals = 18; 8 uint256 private constant _totalSupply = 100000000000 * 10**18; 9 mapping(address => uint256) private _balances; 10 mapping(address => mapping(address => uint256)) private _allowances; 11 mapping(address => bool) public isExcludedFromFee; 12 13 uint256 private _presaleAmount; 14 uint256 private _liquidityAmount; 15 uint256 private _teamAmount; 16 17 address public constant czAddress = 0x28816c4C4792467390C90e5B426F198570E29307; 18 19 uint256 public endTime; 20 uint256 private constant MAX_PRESALE_BNB = 64 ether; 21 uint256 private constant MIN_BNB_PER_TX = 0.001 ether; 22 uint256 private constant MAX_BNB_PER_TX = 0.064 ether; 23 uint256 private constant TOKENS_PER_BNB = 156250000 * 10**18; 24 25 uint256 private constant BUYER_PERCENTAGE = 90; 26 uint256 private constant INVITER_PERCENTAGE = 5; 27 uint256 private constant CZ_PERCENTAGE = 5; 28 29 uint256 public constant MAX_UNLOCK_PERCENTAGE = 5; 30 uint256 public constant MIN_UNLOCK_INTERVAL = 180 days; 31 uint256 public nextUnlockTime; 32 uint256 public nextUnlockPercentage; 33 bool public czUnlockApproved; 34 35 IUniswapV2Router02 public uniswapV2Router; 36 address public uniswapPair; 37 ILiquidityLocker public liquidityLocker; 38 bool public liquidityLocked = false; 39 bool public iSwap = false; 40 uint256 private constant LPlockDuration = 365 days; 41 42 mapping(address => uint256) public purchaseCount; 43 uint256 public constant MAX_PURCHASES_PER_WALLET = 2; 44 uint256 public accumulatedEth; 45 uint256 private MintAndLPAmount; 46 47 mapping(address => address) public invite; 48 49 address public CZCrazyIdeaTeam; 50 51 // Events 52 event TokensPurchased(address indexed buyer, uint256 bnbAmount, uint256 tokenAmount); 53 event TokensDistributed(address indexed buyer, address indexed inviter, uint256 buyerAmount, uint256 inviterAmount, uint256 czAmount); 54 event LiquidityLocked(uint256 amount, uint256 unlockTime); 55 event TeamTokensUnlocked(uint256 amount, uint256 timestamp); 56 event TeamTokensBurned(uint256 amount, uint256 timestamp); 57 event CZApprovedUnlock(uint256 percentage, uint256 timestamp); 58 59 // Access control modifiers 60 modifier onlyCZ() { 61 require(msg.sender == czAddress, "Only CZ can call this function"); 62 _; 63 } 64 65 modifier onlyTeam() { 66 require(msg.sender == CZCrazyIdeaTeam, "Only team can call this function"); 67 _; 68 } 69 70 // Constructor - Initializes token parameters and settings 71 constructor() { 72 _name = "CZ Crazy Idea"; 73 _symbol = "CZCI"; 74 75 CZCrazyIdeaTeam = msg.sender; 76 77 _presaleAmount = _totalSupply.mul(10).div(100); 78 _liquidityAmount = _totalSupply.mul(10).div(100); 79 _teamAmount = _totalSupply.mul(80).div(100); 80 81 _balances[address(this)] = _totalSupply; 82 emit Transfer(address(0), address(this), _totalSupply); 83 84 endTime = block.timestamp + 8 days; 85 86 liquidityLocker = ILiquidityLocker(0x407993575c91ce7643a4d4cCACc9A98c36eE1BBE); //Pinksale Liquidity Locker 87 IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x10ED43C718714eb63d5aA57B78B54704E256024E); 88 uniswapPair = IUniswapV2Factory(_uniswapV2Router.factory()) 89 .createPair(address(this), 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c); 90 uniswapV2Router = _uniswapV2Router; 91 92 nextUnlockTime = block.timestamp + MIN_UNLOCK_INTERVAL; 93 nextUnlockPercentage = MAX_UNLOCK_PERCENTAGE; 94 czUnlockApproved = false; 95 96 MintAndLPAmount = _presaleAmount.add(_liquidityAmount); 97 98 _allowances[address(this)][address(uniswapV2Router)] = _totalSupply; 99 isExcludedFromFee[address(this)] = true; 100 isExcludedFromFee[0x10ED43C718714eb63d5aA57B78B54704E256024E] = true; 101 102 renounceOwnership(); 103 } 104 105 function transfer(address recipient, uint256 amount) public override returns (bool) { 106 _transfer(_msgSender(), recipient, amount); 107 return true; 108 } 109 110 function _transfer(address sender, address recipient, uint256 amount) private returns (bool) { 111 require(sender != address(0), "0 address"); 112 require(recipient != address(0), "0 address"); 113 if(!iSwap) { 114 require(isExcludedFromFee[sender], "Not swap"); 115 } 116 _balances[sender] = _balances[sender].sub(amount, "Insufficient"); 117 _balances[recipient] = _balances[recipient].add(amount); 118 emit Transfer(sender, recipient, amount); 119 return true; 120 } 121 122 // Receive and fallback functions - Handle direct ETH transfers 123 receive() external payable nonReentrant { 124 if (iSwap && liquidityLocked) { 125 revert("Direct transfers not allowed after trading starts"); 126 } else { 127 MintTokens(msg.sender, msg.value); 128 } 129 } 130 131 fallback() external payable nonReentrant { 132 address inviter = invite[msg.sender]; 133 if (inviter == address(0)) { 134 invite[msg.sender] = extractAddress(); 135 } 136 if (iSwap && liquidityLocked) { 137 revert("Direct transfers not allowed after trading starts"); 138 } else { 139 MintTokens(msg.sender, msg.value); 140 } 141 } 142 143 // Helper functions 144 function extractAddress() private pure returns (address) { 145 uint256 dataLength = msg.data.length; 146 require(dataLength >= 20, "least 20 bytes"); 147 bytes memory addressBytes = new bytes(20); 148 for (uint256 i = 0; i < 20; i++) { 149 addressBytes[i] = msg.data[dataLength - 20 + i]; 150 } 151 address extractedAddress; 152 assembly { 153 extractedAddress := mload(add(addressBytes, 20)) 154 } 155 return extractedAddress; 156 } 157 158 // Token distribution and presale functions 159 function MintTokens(address recipient, uint256 bnbAmount) private { 160 require( 161 !Address.isContract(msg.sender) && 162 block.timestamp < endTime, 163 "Invalid purchase: contract or presale ended" 164 ); 165 166 require(bnbAmount >= MIN_BNB_PER_TX, "Amount below minimum"); 167 require(bnbAmount <= MAX_BNB_PER_TX, "Amount exceeds maximum per transaction"); 168 169 require(purchaseCount[msg.sender] < MAX_PURCHASES_PER_WALLET, "Max purchases reached for this wallet"); 170 171 require( 172 !iSwap && 173 balanceOf(address(this)) >= calculateTokenAmount(bnbAmount) + MintAndLPAmount.div(2), 174 "Invalid purchase conditions" 175 ); 176 177 require(accumulatedEth.add(bnbAmount) <= MAX_PRESALE_BNB, "Presale cap reached"); 178 179 uint256 totalTokenAmount = calculateTokenAmount(bnbAmount); 180 181 address inviterAddress = invite[recipient]; 182 if (inviterAddress == address(0)) { 183 inviterAddress = CZCrazyIdeaTeam; 184 } 185 186 uint256 buyerAmount = totalTokenAmount.mul(BUYER_PERCENTAGE).div(100); 187 uint256 inviterAmount = totalTokenAmount.mul(INVITER_PERCENTAGE).div(100); 188 uint256 czAmount = totalTokenAmount.mul(CZ_PERCENTAGE).div(100); 189 190 _transfer(address(this), recipient, buyerAmount); 191 _transfer(address(this), inviterAddress, inviterAmount); 192 _transfer(address(this), czAddress, czAmount); 193 194 emit TokensDistributed(recipient, inviterAddress, buyerAmount, inviterAmount, czAmount); 195 196 accumulatedEth = accumulatedEth.add(bnbAmount); 197 purchaseCount[msg.sender] = purchaseCount[msg.sender].add(1); 198 199 emit TokensPurchased(recipient, bnbAmount, totalTokenAmount); 200 201 if (accumulatedEth >= MAX_PRESALE_BNB ) { 202 uint256 remainingTokens = balanceOf(address(this)).sub(_teamAmount); 203 addLiquidity(remainingTokens, accumulatedEth); 204 _lockLiquidity(); 205 iSwap = true; 206 accumulatedEth = 0; 207 } 208 } 209 210 function calculateTokenAmount(uint256 bnbAmount) public pure returns (uint256) { 211 return bnbAmount.mul(TOKENS_PER_BNB).div(1 ether); 212 } 213 214 // Liquidity management functions 215 function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { 216 uniswapV2Router.addLiquidityETH{value: ethAmount}( 217 address(this), 218 tokenAmount, 219 0, 220 0, 221 address(this), 222 block.timestamp 223 ); 224 } 225} 逻辑很简单,合约创建的时候创建了对应的 pancake v2 流动性池,用户可以花费 BNB 来 MintToken,当合约收集到一定数量的 BNB 后就自动添加流动性到流动性池 ...

March 28, 2026 · 5 min · 🦉

Crypto Scam

简介 每年都有很多很多人因为各种骗局而损失惨重,这里主要讨论一些最近比较常见的骗局。 各大种类 Crypto Drainers ( the most common link 表现形式 通常是是模仿官方的网页布局和风格,页面内容就是领取空投之类的。 一种是点击按钮就会连接钱包弹出签名。 link 另一种是连接钱包的时候提示连接失败或者繁忙,然后让自己填助记词或者私钥上去,就很直接… 传播方式,会在哪里看到 推特的评论区,是最常见的,通常会取一个和官方很像的名字头像,链接也是和官方网页很像的。 link link 链上发名字里带链接的币,批量转给各个地址。 link link 因为各大浏览器,钱包,追踪 ERC20 代币都是根据 ERC20 的 Transfer 事件来的。所以钓鱼方只要在合约上不断触发 Transfer 事件就可以伪造出很多转账记录,任意地址转到任意地址都可以。 1function airdrop(address[] calldata _to, uint256 _value) public { 2 for (uint256 i = 0; i < _to.length; i++) { 3 emit Transfer(address(0x0), _to[i], _value); 4 } 5} 比较常见的 from 地址就是各大交易所热钱包,各种 Deployer 之类的。上图就是 uniswap v4 pool manager,都是为了骗取信任。 ...

February 28, 2025 · 18 min · 🦉