zoukankan      html  css  js  c++  java
  • 智能合约安全前传-基础知识入门

    本文首发自https://www.secpulse.com/archives/73557.html,转载请注明出处。

    本文综述

    本文将简述以太坊、智能合约的基础知识,以及如何搭建私链和编写、部署一个有漏洞的智能合约。

    以太坊简介

    以太坊是一个开源的区块链平台,以太坊的模块结构和比特币类似,和比特币最大区别就在于,用户可以在以太坊平台上随意的开发属于自己的去中心化智能合约。 
    以太坊常用的几款工具: 

    1. go-ethereum:官方的Go语言客户端,客户端文件是geth。这是使用最广泛的客户端,类似于比特币的中本聪核心客户端,可用于挖矿,组建私有链、管理账号、部署合约等 

    2. cpp-ethereum:与上面实现功能一样,不过是c++实现 

    3. Mist 客户端 :mist目前是钱包客户端,未来定义为一个DAPP市场交易客户端,类似于苹果市场

    4. Solidity:智能合约编程语言,下一章节会详细介绍 

    5. Remix:智能合约的开发IDE,在线地址 https://remix.ethereum.org

    以太坊里还有个不同于比特币的概念Gas。EVM (Ethereum Virtual Machine, 以太坊虚拟机)在执行合约代码时,每一步执行都会消耗一定 Gas,Gas 可以被看作是能量,一段代码逻辑可以假设为一套 “组合技”,而外部调用者在调用该合约的某一函数时会提供数量一定的 Gas,如果这些 Gas 大于这一套 “组合技” 所需的能量,则会成功执行,否则会由于 Gas 不足而发生 out of gas 的异常,合约状态回滚。那为什么需要加入gas这个概念呢?因为智能合约是一个图灵完备的语言,加入Gas,可以避免无限循环和拒绝服务攻击 。

    智能合约简介

    智能合约是一种以信息化方式传播、验证或者执行合约的计算机协议,能够允许在没有第三方的情况下进行可信的交易,并且这些交易是无法被追踪、同时也是不可逆的。 
    Solidity 是一门面向合约并且图灵完备的编程语言(还有其他几种语言可以编写智能合约,但是目前使用最广泛的就是 Solidity 语言)。Solidity是个编译型语言,需要编译后运行在EVM(Ethereum Virtual Machine, 以太坊虚拟机)上。 
    每一个合约账户中的代码都是一个 Contract,它与面向对象编程中类的概念非常类似,无论是合约还是类都可以有变量和函数,但是类是可以实例化的,而合约并没有实例化这一功能,它的变量和函数可以直接在合约本身上访问或者调用。

    以太坊中有两类账户,它们共用同一个地址空间。 

    1. 外部账户,该类账户被公钥-私钥对控制(人类);外部账户的地址是由公钥决定的。 

    2. 合约账户,该类账户被存储在账户中的代码控制;合约账户的地址是在创建改合约时确定的(这个地址由合约创建者的地址和该地址发出过的交易数量计算得到,地址发出过的交易数量也被称作"nonce") 

    Solidity语法总结

    基本结构

    • 合同(contract)声明:合同类似于面向对象语言中的类(Class)

      contract HelloWord { 
      }
    • 状态变量(State variable)声明:状态变量是永久存储在合同存储中的值。

      contract HelloWord { 
      uint storedData; // State variable
      }
    • 函数(function)声明:函数是合约内代码的可执行单元。

      contract HelloWord { 
      function get () {
      // todo something
      }
      }

    fallback函数

    单独拎出来讲,是因为这个函数很重要,智能合约的安全漏洞,有很大一部分都与合约实例的回退函数有关。下面就是定义的一个fallback函数,可以看到这个函数没有名字,也没有返回值。每一个合约有且仅有一个没有名字的函数。一个没有定义回退函数的合约,如果接收ether,会触发异常,并返还ether(solidity v0.4.0开始)。所以合约要接收ether,必须实现回退函数。那什么时候执行 fallback 函数呢?

    1. 当外部账户或其他合约向该合约地址发送 ether 时;

    2. 当外部账户或其他合约调用了该合约一个不存在的函数时;

    3. 当外部账户或其他合约调用该合约确没有传入任何数据时;

    类型

    • bool:false / true

    • 操作符:! , && , || , == , !=

    • uinit/int:无符整型、有符整型 (默认是256bit)

    • 操作符:

      • 比较:<= , < , == , >= , >

      • 位计算:& , | , ^ , ~

      • 计算:+ , - , * , / , % , **

    • address:用于表示以太坊地址,20字节,160bit。

      成员:

      • address.banlance (uint256):地址余额,单位 Wei

      • address.transfer(uint256 value) :向地址类型发送数量为 amount 的 Wei,失败时抛出异常,并回滚转态;当地址类型是合约地址时,调用合约地址的fallback函数,并且最多发送 2300 gas 的矿工费,不可调节。

      • address.send(value) returns (bool):向地址类型发送数量为 amount 的 Wei,失败时返回false;当地址类型是合约地址时,调用合约地址的fallback函数,并且最多发送 2300 gas 的矿工费,不可调节。

      • address.call.value()() 当发送失败时会返回 false 布尔值;传递所有可用 Gas 进行调用;

      • address.call(...) returns (bool):call 的外部调用上下文是外部合约

      • address.delegatecall(...) returns (bool) 的外部调用上下是调用合约上下文

      • address.callcode(...) returns (bool) delegatecall之前的一个版本,不鼓励使用,未来会废除

    全局变量

    • block:块

      • block.blockhash(uint blockNumber) returns (bytes32): 传入 blockNumber,返回块的哈希值

      • block.coinbase (address): 挖到当前块矿工的地址

      • block.difficulty (uint): 当前块的难度

      • block.gaslimit (uint): 当前块最多的 gas

      • block.number (uint): 当前块是第几个

      • block.timestamp (uint): 当前块创建的时间戳

      • now (uint): block.timestamp 的别名

    • msg: 当执行某一个函数的时候,函数想要知道调用函数的数据信息

      • msg.data (bytes): 包括函数名字等等,一些没有经过加工的信息。

      • msg.gas (uint): 函数调用方携带的 gas

      • msg.sender (address): 函数调用方的地址

      • msg.sig (bytes4): 整个 msg.data 的前 4 个 byte

      • msg.value (uint): 函数调用方携带的 gas,以 wei 为单位计价。

    • 关键词:

      • constant 用于变量: 表明当前变量不可修改。如果修改,编辑器会报错。

      • constant 用于函数: 表明当前函数中,不应该修改状态。但要十分小心,因为即便修改了,编译器也不会报错。

      • view : 和 constant 用于函数时功能一样。

      • payable: 表明调用函数可以接受以太币。

      • this: 指向的是当前合同的 address。

      • revert: 函数执行失败,需要通过调用 revert() 抛异常告诉函数调用方。调用后恢复合同状态,并将剩余 gas 返还。throw 已被废弃。

      • require: 用于检查条件,并在不满足条件的时候抛出异常,更偏向代码逻辑健壮性检查

      • assert:用于检查条件,并在不满足条件的时候抛出异常,更偏向用于确认一些本不该出现的情况异常发生的时候

    搭建私链

    以太坊属于公有链,官方不但提供了主链,也提供了测试链。以太坊公链的运行节点遍布全球,即使是使用测试链,运行速度也是无法达到实验级的要求的,而且不方便去控制网络中的每一个节点。所有我们有必要搭建一个测试链,由于这个测试链运行在用户自己的局域网中,一般情况下并不会开放到公网中,因此这个测试链也称私有链。 那如何快速搭建测试环境呢?TestRPC和Truffle这两款工具能帮助我们快速部署环境。TestRPC是在本地使用内存模拟的一个以太坊环境,可以用于搭建测试环境,基于Nodejs开发。Truffle是针对以太坊智能合约应用的一套开发框架。

    安装工具的命令: 

    1. 安装 TestRPC 

    sudo npm install -g ethereumjs-testrpc 

    2. 安装 Truffle 

    sudo npm install -g truffle 

    3.安装 solc 

    sudo npm install -g solc

    一般学某种语言,教程都是从 hello world 说起。那我们也以 hello world为例,来部署一个合约。

    测试环境

    Truffle v4.1.13 EthereumJS TestRPC v6.0.3 (ganache-core: 2.0.2) 
    0.4.24+commit.e67f0147.Emscripten.clang

    项目创建

    terminal终端,创建一个新目录,并truffle项目初始化。 

    > mkdir HelloWorld 
    > cd HelloWorld
    > truffle init

    初始化好之后的目录结构如下: 

    HelloWorld/├── build
    │   └── contracts
    │   └── Migrations.json
    ├── contracts
    │   └── Migrations.sol
    ├── migrations
    │   └── 1_initial_migration.js
    ├── test
    ├── truffle-config.js
    └── truffle.js

    撰写HelloWorld合约

    pragma solidity ^0.4.24;

    contract HelloWorld{   address creator;   string message;   constructor() {     creator = msg.sender;   }   function say() constant returns (string) {     return message;   }   function setMessage(string _newMsg) {   message = _newMsg;   }   /**********   Standard kill() function to recover funds   **********/   function kill() { if (msg.sender == creator) selfdestruct(creator); // kills this contract and sends remaining funds back to creator   } }

    把代码保存到contracts目录下的HelloWorld.sol

    编译

    在HelloWorld目录下: 

    > truffle compile 

    compile只会编译更新过的合约文件,如果有多个文件,且想全部编译,可以使用 truffle compile-all

    运行测试

    1. 启动testrpc

    通过testrpc可以很方便的进行测试,打开一个新的terminal终端执行命令: 

    > testrpc 

    默认会在localhost:8545进行合约部署的监听。 

    2. 修改合约配置 

    因为合约是要发给testrpc做运行,需要再HelloWorld/truffle.js中配置testrpc的地址信息,如下:

    module.exports = { 
    networks: {
    development: {
    host: "localhost",
    port: 8545,
    network_id: "*" // Match any network id
        }
    }
    };

    3. 添加迁移信息(migrate) 

    需要配置告诉truffle迁移哪些合约到testrpc,添加一个文件HelloWorld/migrations/2_deploy_contracts.js

    var HelloWorld = artifacts.require("./HelloWorld.sol");

    module.exports = function(deployer) {
    deployer.deploy(HelloWorld);
    };

    4. 运行迁移命令,部署合约到testrpc

    truffle migrate 

    同样的,这个命令只会迁移修改过的合约,如果有异常错误或者需要手动全部重新迁移,可以运行 truffle migrate --reset 。迁移成功后在testrpc窗口也会有响应的提示信息,包括函数调用和事务执行信息等。

    5. 命令行测试合约 

    通过console可以方便的测试合约的开发接口是否访问正常,运行命令:

    truffle console 

    运行成功后进入到truffle的命令行程序中,可以通过以下命令来测试合约接口,设置信息:

    HelloWorld.deployed().then(i=>i.setMessage("Hello world!"));

     

    remix基本用法

    使用在线的remix,如果要调试本地搭建的私有网络,Environment选择Web3 Provider

    这边注意的是,编译要对相应的版本号,版本号在Settings中设置

    写个有溢出漏洞的代码

    pragma solidity ^0.4.24;


    contract MyToken {
    mapping (address => uint) balances;

    // 查看余额
    function balanceOf(address _user) returns (uint256) {
    return balances[_user];
    }

    // 添加余额
    function deposit() payable {
    balances[msg.sender] += msg.value;
    }

    // 转账操作
    function transfer(address _to, uint256 _value) payable {
    require(balances[msg.sender] - _value> 0); //存在溢出
    msg.sender.transfer(_value);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    }
    }

    使用remix 直接运行代码,过程:(仔细想了下,感觉这个例子不是太好,不过可以先不要管,因为主要目是为了让大家学会remix的基本用法)

    1. 账户 a 点击 deposit 转入 50 ether

    2. 账户 a 转出  115792089237316195423570985008687907853269984665640564039457584007913129639984 到账户b

     

    3. 查看账户 b 的余额

    使用 truffle console 调用合约

    1. 关于私有链的智能合约部署上文已经说到,根据上面教程搭建MyToken智能合约

    2. truffle console 进入交互,命令可以参考这里 

    几点说明:

    1. web3.eth.accounts 是testrpc测试账户的数组

    2. deployed() 函数是获取合约实例

    3. call 它完全是一个本地调用,不会向区块链网络广播任何东西,它的返回值完全取决于函数方法的代码,不会消耗Gas

    总结

    智能合约漏洞的原理和平常的安全漏洞原理并没有什么不同,但是很多人因为不懂编写和调试从而望而却步。本文就是基础的入门文章,并没有讲到漏洞原理,而是帮助大家了解一些基本的概念和从搭建环境开始,后续有漏洞可以懂得自己调试,这样才能取得事半功倍的效果。

    参考: 
    https://www.cnblogs.com/bugmaking/p/9211225.html

    https://www.anquanke.com/post/id/146322

    https://my.oschina.net/u/2349981/blog/863731

    https://truffleframework.com/docs/getting_started/contracts

    https://bitshuo.com/topic/587e03c44dea36e72c1b381b

  • 相关阅读:
    JavaSE教程-01初识Java-思维导图
    theano 模块 MLP示例
    EM理解(转)
    交叉验证(Cross Validation)方法思想简介
    imfilter()用法
    PSNR
    conv2、filter2、imfilter的区别
    图像上采样(图像插值)增取样(Upsampling)或内插(Interpolating)下采样(降采样),
    CSS清除浮动大全共8种方法
    支付宝轮播
  • 原文地址:https://www.cnblogs.com/he1m4n6a/p/9309848.html
Copyright © 2011-2022 走看看