作者:昏鸦,Al1ex@知道创宇404区块链安全研究团队
时间:2020年9月16日
事件起因
2020年9月14日晚20:00点,未经安全审计的波场最新Defi项目Myrose.finance登陆Tokenpocket钱包,首批支持JST、USDT、SUN、DACC挖矿,并将逐步开通ZEUS、PEARL、CRT等的挖矿,整个挖矿周期将共计产出8400枚ROSE,预计将分发给至少3000名矿工,ROSE定位于波场DeFi领域的基础资产,不断为持有者创造经济价值。
分析复现
transfer
函数有显式return true
和无显式return true
pragma solidity ^0.5.0;
import "IERC20.sol";
import "SafeMath.sol";
contract USDT_Ethereum is IERC20 {
using SafeMath for uint256;
uint256 internal _totalSupply;
mapping(address => uint256) internal _balances;
mapping (address => mapping (address => uint)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint value);
constructor() public {
_totalSupply = 1 * 10 ** 18;
_balances[msg.sender] = _totalSupply;
}
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
function allowance(address owner, address spender) external view returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint amount) public returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function _approve(address owner, address spender, uint amount) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function mint(address account, uint amount) external {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
function _transfer(address _from ,address _to, uint256 _value) internal returns (bool) {
require(_to != address(0));
require(_value <= _balances[msg.sender]);
_balances[_from] = _balances[_from].sub(_value, "ERC20: transfer amount exceeds balance");
_balances[_to] = _balances[_to].add(_value);
emit Transfer(_from, _to, _value);
return true;
}
function transfer(address to, uint value) public returns (bool) {
_transfer(msg.sender, to, value);
return true;//显式return true
}
function transferFrom(address from, address to, uint value) public returns (bool) {
_transfer(from, to, value);
_approve(from, msg.sender, _allowances[from][msg.sender].sub(value, "ERC20: transfer amount exceeds allowance"));
return true;
}
}
contract USDT_Tron is IERC20 {
using SafeMath for uint256;
uint256 internal _totalSupply;
mapping(address => uint256) internal _balances;
mapping (address => mapping (address => uint)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint value);
constructor() public {
_totalSupply = 1 * 10 ** 18;
_balances[msg.sender] = _totalSupply;
}
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
function allowance(address owner, address spender) external view returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint amount) public returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function _approve(address owner, address spender, uint amount) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function mint(address account, uint amount) external {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
function _transfer(address _from ,address _to, uint256 _value) internal returns (bool) {
require(_to != address(0));
require(_value <= _balances[msg.sender]);
_balances[_from] = _balances[_from].sub(_value, "ERC20: transfer amount exceeds balance");
_balances[_to] = _balances[_to].add(_value);
emit Transfer(_from, _to, _value);
return true;
}
function transfer(address to, uint value) public returns (bool) {
_transfer(msg.sender, to, value);
//return true;//无显式return,默认返回false
}
function transferFrom(address from, address to, uint value) public returns (bool) {
_transfer(from, to, value);
_approve(from, msg.sender, _allowances[from][msg.sender].sub(value, "ERC20: transfer amount exceeds allowance"));
return true;
}
}
pragma solidity ^0.5.0;
import "IERC20.sol";
import "Address.sol";
import "SafeERC20.sol";
import "SafeMath.sol";
contract Test {
using Address for address;
using SafeERC20 for IERC20;
using SafeMath for uint256;
uint256 internal _totalSupply;
mapping(address => uint256) internal _balances;
constructor() public {
_totalSupply = 1 * 10 ** 18;
_balances[msg.sender] = _totalSupply;
}
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
function withdraw(address yAddr,uint256 amount) public {
_totalSupply = _totalSupply.sub(amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);
IERC20 y = IERC20(yAddr);
y.safeTransfer(msg.sender, amount);
}
}
USDT_Ethereum
、USDT_Tron
、Test
三个合约。mint
函数给Test合约地址增添一些代币。withdraw
函数提现测试。USDT_Ethereum
提现成功,USDT_Tron
提现失败。safeTransfer
函数中对最后返回值的校验。function safeTransfer(IERC20 token, address to, uint value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function callOptionalReturn(IERC20 token, bytes memory data) private {
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");//require校验返回的bool数值,false则回滚,提示操作失败
}
}
Missing Return Value Bug
interface ERC20Interface {
function totalSupply() external constant returns (uint);
function balanceOf(address tokenOwner) external constant returns (uint balance);
function allowance(address tokenOwner, address spender) external constant returns (uint remaining);
function transfer(address to, uint tokens) external returns (bool success);
function approve(address spender, uint tokens) external returns (bool success);
function transferFrom(address from, address to, uint tokens) external returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
interface BadERC20Basic {
function balanceOf(address who) external constant returns (uint);
function transfer(address to, uint value) external;
function allowance(address owner, address spender) external constant returns (uint);
function transferFrom(address from, address to, uint value) external;
function approve(address spender, uint value) external;
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
}
interface Token {
function transfer() returns (bool);
}
contract GoodToken is Token {
function transfer() returns (bool) { return true; }
}
contract BadToken {
function transfer() {}
}
contract Wallet {
function transfer(address token) {
require(Token(token).transfer());
}
}
selector = bytes4(sha3(“transfer()”))
returndatasize
,这个操作码存储(顾名思义)外部调用返回数据的大小,这是一个非常有用的操作码,因为它允许在函数调用中返回动态大小的数组。{'addr': '0xae616e72d3d89e847f74e8ace41ca68bbf56af79', 'name': 'GOOD', 'decimals': 6}
{'addr': '0x93e682107d1e9defb0b5ee701c71707a4b2e46bc', 'name': 'MCAP', 'decimals': 8}
{'addr': '0xb97048628db6b661d4c2aa833e95dbe1a905b280', 'name': 'PAY', 'decimals': 18}
{'addr': '0x4470bb87d77b963a013db939be332f927f2b992e', 'name': 'ADX', 'decimals': 4}
{'addr': '0xd26114cd6ee289accf82350c8d8487fedb8a0c07', 'name': 'OMG', 'decimals': 18}
{'addr': '0xb8c77482e45f1f44de1745f52c74426c631bdd52', 'name': 'BNB', 'decimals': 18}
{'addr': '0xf433089366899d83a9f26a773d59ec7ecf30355e', 'name': 'MTL', 'decimals': 8}
{'addr': '0xc63e7b1dece63a77ed7e4aeef5efb3b05c81438d', 'name': 'FUCKOLD', 'decimals': 4}
{'addr': '0xab16e0d25c06cb376259cc18c1de4aca57605589', 'name': 'FUCK', 'decimals': 4}
{'addr': '0xe3818504c1b32bf1557b16c238b2e01fd3149c17', 'name': 'PLR', 'decimals': 18}
{'addr': '0xe2e6d4be086c6938b53b22144855eef674281639', 'name': 'LNK', 'decimals': 18}
{'addr': '0x2bdc0d42996017fce214b21607a515da41a9e0c5', 'name': 'SKIN', 'decimals': 6}
{'addr': '0xea1f346faf023f974eb5adaf088bbcdf02d761f4', 'name': 'TIX', 'decimals': 18}
{'addr': '0x177d39ac676ed1c67a2b268ad7f1e58826e5b0af', 'name': 'CDT', 'decimals': 18}
library ERC20SafeTransfer {
function safeTransfer(address _tokenAddress, address _to, uint256 _value) internal returns (bool success) {
// note: both of these could be replaced with manual mstore's to reduce cost if desired
bytes memory msg = abi.encodeWithSignature("transfer(address,uint256)", _to, _value);
uint msgSize = msg.length;
assembly {
// pre-set scratch space to all bits set
mstore(0x00, 0xff)
// note: this requires tangerine whistle compatible EVM
if iszero(call(gas(), _tokenAddress, 0, add(msg, 0x20), msgSize, 0x00, 0x20)) { revert(0, 0) }
switch mload(0x00)
case 0xff {
// token is not fully ERC20 compatible, didn't return anything, assume it was successful
success := 1
}
case 0x01 {
success := 1
}
case 0x00 {
success := 0
}
default {
// unexpected value, what could this be?
revert(0, 0)
}
}
}
}
interface ERC20 {
function transfer(address _to, uint256 _value) returns (bool success);
}
contract TestERC20SafeTransfer {
using ERC20SafeTransfer for ERC20;
function ping(address _token, address _to, uint _amount) {
require(ERC20(_token).safeTransfer(_to, _amount));
}
}
pragma solidity ^0.4.24;
/*
* WARNING: Proof of concept. Do not use in production. No warranty.
*/
interface BadERC20 {
function transfer(address to, uint value) external;
}
contract BadERC20Aware {
function safeTransfer(address token, address to , uint value) public returns (bool result) {
BadERC20(token).transfer(to,value);
assembly {
switch returndatasize()
case 0 { // This is our BadToken
result := not(0) // result is true
}
case 32 { // This is our GoodToken
returndatacopy(0, 0, 32)
result := mload(0) // result == returndata of external call
}
default { // This is not an ERC20 token
revert(0, 0)
}
}
require(result); // revert() if result is false
}
}
事件总结
联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)
参考链接
往 期 热 门
(点击图片跳转)
觉得不错点个“在看”哦
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论