前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >blockwell.ai 虚假转账 事件分析

blockwell.ai 虚假转账 事件分析

作者头像
LoRexxar
发布2023-02-21 19:27:53
2050
发布2023-02-21 19:27:53
举报
文章被收录于专栏:LoRexxar's BlogLoRexxar's Blog

一、背景

2018年9月7日早上1点左右,许多以太坊账户都收到了一种名为blockwell.ai KYC Casper Token转账消息,其中有的是收到了这种代币,而有的用户是支出了这种代币。

image.png-174.9kB
image.png-174.9kB
image.png-24.2kB
image.png-24.2kB

得到的用户以为受到了新币种的空投,满心欢喜的打开之后发现并没有获得任何代币。转出的用户着急打开钱包,以为是钱包被盗转走了代币,实际上却毫无损失。

在回过神来看看代币的名字,忍不打开blockwell.ai查看原因,一次成功的广告诞生了。

二、事件回顾

回到导致事件发生的blockwell.ai合约中,合约地址为

代码语言:javascript
复制
https://etherscan.io/address/0x212d95fccdf0366343350f486bda1ceafc0c2d63

实际转账到账户的交易信息

代码语言:javascript
复制
https://etherscan.io/token/0x212d95fccdf0366343350f486bda1ceafc0c2d63?a=0xa3fe2b9c37e5865371e7d64482a3e1a347d03acd

可以看到通过调用这个合约,发起了一笔代币转账,在event logs里可以看到实际的交易

image.png-17.8kB
image.png-17.8kB

然后具体的交易地址为

代码语言:javascript
复制
https://etherscan.io/tx/0x3230f7326ab739d9055e86778a2fbb9af2591ca44467e40f7cd2c7ba2d7e5d35

整笔交易花费了244w的gas,价值2.28美元,有针对的从500个用户转账给了500个用户。

image.png-144.1kB
image.png-144.1kB

跟踪到转账的from地址,可以看到一个很有趣的问题

代码语言:javascript
复制
https://etherscan.io/address/0xeb7a58d6938ed813f04f36a4ea51ebb5854fa545#tokentxns
image.png-117.7kB
image.png-117.7kB

所有的来源账户本身都是不持有这种代币的。

跟踪一下也可以发现,无论是发起交易者还是接受交易者,都没有发生实际代币的变化。

但交易却被记录了下来,这是怎么回事呢?

三、漏洞分析

智能合约是一种于1994年被提出的,在没有第三方的情况下进行的可信交易。而以太坊在区块链上实现了一种图灵完备的语言solidity,允许人们在区块链上编写代码来实现智能合约。而智能合约的成熟催生了合约代币的产生,合约代币中只有遵守以太坊ERC20标准的合约代币才会被承认为ERC20代币,ERC20代币会直接被交易所承认。

在ERC20标准中规定,transfer函数必须触发Transfer事件,事件会被记录在event log中,而各大平台与交易所也是通过解析event log来获取交易信息。

会触发事件的转账代码

代码语言:javascript
复制
function transfer(address _to, uint256 _value) public returns (bool) {
  require(_value <= balances[msg.sender]);
  require(_to != address(0));

  balances[msg.sender] = balances[msg.sender].sub(_value);
  balances[_to] = balances[_to].add(_value);
  emit Transfer(msg.sender, _to, _value);
  return true;
}

“昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台。HaoTian全新的引入了对opcode的反编译审计模块,正在逐步应用到对智能合约的审计中。

这里我们使用HaoTian最新的反编译功能针对该智能合约进行简单的恢复源码。

源码如下

代码语言:javascript
复制
contract 0x212D95FcCdF0366343350f486bda1ceAfC0C2d63 {

    mapping(address => uint256) balances;
    uint256 public totalSupply;
    mapping (address => mapping (address => uint256)) allowance;
    address public owner;
    string public name;
    string public symbol;
    uint8 public decimals;

    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event OwnershipRenounced(address indexed previousOwner);
    event TransferOwnership(address indexed old, address indexed new);

    function approve(address _spender, uint256 _value) public returns (bool success) {        
        allowance[msg.sender][_spender] = _value;        
        Approval(msg.sender, _spender, _value);        
        return true;    
    }  

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        // 0x841
        require(to != address(0));   
        require(balances[_from] >= _value);
        require(allowance[_from][msg.sender] >= _value);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowance[_from][msg.sender] =  allowance[_from][msg.sender].sub(_value); 
        Transfer(_from, _to, _value);
        return true;
    }

    function decreaseApproval(address _spender, uint256 _subtractedValue) {
        // 0xc0e
        uint oldValue = allowance[msg.sender][_spender];
        if (_subtractedValue > oldValue) {      
            allowance[msg.sender][_spender] = 0;    
        } else {
            allowance[msg.sender][_spender] = oldValue.sub(_subtractedValue);    
        }    
        Approval(msg.sender, _spender, allowance[msg.sender][_spender]);    
        return true;  
    }

    function balanceOf(address _owner) constant returns (uint256 balance) {       
        // 0xe9f 
        return balances[_owner];    
    }    

    function renounceOwnership() {
        // 0xee7
        require(owner == msg.sender);
        emit OwnershipRenounced(owner);
        owner = address(0);
    }

    function x_975ef7df(address[] arg0, address[] arg1, uint256 arg2) {
        require(owner == msg.sender);
        require(arg0.length > 0, "Address arrays must not be empty");
        require(arg0.length == arg1.length, "Address arrays must be of equal length");
        for (i=0; i < arg0.length; i++) {
            emit Transfer(arg0[i], arg1[i], arg2);
        }
    }

    function transfer(address arg0,uint256 arg1) {
        require(arg0 != address(0x0));
        require(balances[msg.sender] > arg1);
        balances[mag.sender] = balances[msg.sender].sub(arg1);
        balances[arg0] = balances[arg0].add(arg1);
        emit Transfer(msg.sender, arg0, arg1)
        return arg1
    }

    function increaseApproval(address arg0,uint256 arg1) {
        allowance[msg.sender][arg0] = allowance[msg.sender][arg0].add(arg1)
        emit Approval(msg.sender, arg0, arg1)
        return true;
    }

    function transferOwnership(address arg0) {
        require(owner == arg0);
        require(arg0 != adress(0x0));
        emit TransferOwnership(owner, arg0);
        owner = arg0;
    }
}

从代码中可以很明显的看到一个特殊的函数x_975ef7df,这是唯一一个涉及到数组操作,且会触发Tranfser事件的函数。

代码语言:javascript
复制
function x_975ef7df(address[] arg0, address[] arg1, uint256 arg2) {
    require(owner == msg.sender);
    require(arg0.length > 0, "Address arrays must not be empty");
    require(arg0.length == arg1.length, "Address arrays must be of equal length");
    for (i=0; i < arg0.length; i++) {
        emit Transfer(arg0[i], arg1[i], arg2);
    }
}

从代码中可以很清晰的看到, 在对地址列表的循环中,只触发了Transfer事件,没有任何其余的操作。

是不是说明平台和交易所在获取ERC20代币交易信息,是通过event log事件获取的呢?我们来测试一下。

事件复现

首先我们需要编写一个简单的ERC20标准的代币合约

代码语言:javascript
复制
contract MyTest {

    mapping(address => uint256) balances;
    uint256 public totalSupply;
    mapping (address => mapping (address => uint256)) allowance;
    address public owner;
    string public name;
    string public symbol;
    uint8 public decimals = 18;

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    function MyTest() {
        name = "we are ruan mei bi";
        symbol = "RMB";
        totalSupply = 100000000000000000000000000000000000;
    }

    function mylog(address arg0, address arg1, uint256 arg2) public {
        Transfer(arg0, arg1, arg2);
    }

}

合约代币需要规定好代币的名称等信息,然后我们定义一个mylog函数。

这里我们通过remix进行部署(由于需要交易所获得提示信息,所以我们需要部署在公链上)

image.png-303kB
image.png-303kB

测试合约地址

代码语言:javascript
复制
https://etherscan.io/address/0xd69381aec4efd9599cfce1dc85d1dee9a28bfda2

然后直接发起交易

image.png-81.2kB
image.png-81.2kB

然后我们的imtoken提示了消息

image.png-60.7kB
image.png-60.7kB

回看余额可以发现没有实际转账诞生。

五、总结

回顾前面的测试,可以发现这是一件有趣的利用ERC20标准本身被信任的问题,攻击者花费了约2.28美元的gas,对1000个用户有针对的发送了广告,花费小额的gas完成了广告这个过程。

事件的核心就在于,ERC20作为标准要求要遵守才可以被承认,但交易所/平台却盲目信任符合ERC20标准的合约,将平台本身原理上的bug利用到发放小广告上,是一次比较特别的体验。

可以说,这是智能合约的一次极为特殊的漏洞利用,本身不涉及盗币,但却会对现在的交易所已经平台造成严重的危害,而且其本身底层逻辑bug难以从底层修复,只能在上层做修复,但黑名单的过滤方式很难真正奏效,一个属于区块链广告的黑暗时代到来了…

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/09/14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、事件回顾
  • 三、漏洞分析
  • 事件复现
  • 五、总结
相关产品与服务
区块链
云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档