論如何操控ICO走向人生巔峰

Ethereum從2013年一直走到現(xiàn)在,已經(jīng)過去了五年。在2017年,出盡了風(fēng)頭,然而卻因?yàn)橹悄芎霞s的問題就搞出了好幾個(gè)大事情,其背后透漏出的就是開發(fā)者對(duì)Ethereum或者說EVM的不了解。普通大眾就更不明白了。2017年,尤其ICO項(xiàng)目層出不窮,然而合約代碼有多少是嚴(yán)格審計(jì)的?今天,我們來仔細(xì)審視一段代碼,看看有些項(xiàng)目方是如何操控ICO進(jìn)程的。

老規(guī)矩,先上ICO代碼

這段ICO代碼,有個(gè)提取限制,即owner在ICO完后的第一個(gè)week里可以最多取1個(gè)ETH,第二個(gè)week里可以最多取2個(gè)ETH,第三個(gè)week里可以最多取4TH,第四個(gè)week里可以最多取8TH,以此類推。

代碼來源于這里

/**
 *  Merdetoken
 *
 *  See: https://theethereum.wiki/w/index.php/ERC20_Token_Standard
 *
 *  This ICO allows participants to send ether to this contract in exchange
 *  for MDT tokens during the presale period (4 weeks).
 *
 *  Once the presale period is over, no addition tokens may be created and the owner is
 *  able to withdraw the funds in according to a schedule which increases over time:
 *    - week 1: 1 ether
 *    - week 2: 2 ether
 *    - week 3: 4 ether
 *    - week 4: 8 ether
 *  ...and so on. This way as the project grows and further funds are required, more can
 *  be withdrawn, however, by deferring the funding, the owner cannot just cash out and
 *  skip town.
 *
 *  An entry for a sinister contract...
 *
 *  Content Info:
 *    http://u.solidity.cc/?t=1&cn=ZmxleGlibGVfcmVjc18y&iid=41e0d6e3f8114ddc86b2bd023e624f01&uid=869384457991671808&nid=244+272699400
 *
 *
 *  Licensed under both:
 *    - MIT License
 *    - Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
 *
 *  Author information will be added once the contest has ended.
 */


// Not part of the exploit... Just shutting up compiler warnings...
pragma solidity ^0.4.10;


contract Merdetoken {

    // ERC-20 constants
    string public constant name = "Merdetoken";
    string public constant symbol = "MDT";
    uint8 public constant decimals = 18;

    // The ICO owner
    address owner;

    // The token balance for each member
    mapping (address => uint256) balances;

    // Allowances for each member
    mapping(address => mapping (address => uint256)) allowed;

    // The total supply of these tokens
    uint256 _totalSupply;

    // The date that the presale ends and the token can be traded
    uint256 activationDate;

    // The last time the owner issued withdraw(uint256)
    uint256 lastWithdrawDate;

    /**
     *  constructor(uint256)
     *
     *  Create a new instance of the Merdetoken, with a given presale
     *  duration.
     */
    function Merdetoken(uint256 duration) payable {

        // Set the ICO owner
        owner = msg.sender;

        // The owner can buy their own tokens with an endowment (if desired)
        balances[msg.sender] = msg.value;
        _totalSupply = msg.value;

        // Set the ICO presale end date
        activationDate = now + duration;
    }

    /**
     *  withdraw(uint256)
     *
     *  After the presale, the ICO owner may withdraw Ether from the contract
     *  on a deferred schedule; once per week, the amount doubling each week.withdraw
     */
    function withdraw(uint256 amount) {

        // Cannot withdraw during the presale
        if (now < activationDate) { throw; }

        // Only the owner may withdraw funds
        if (msg.sender != owner) { throw; }

        // Only allow sane values to be sent
        if (amount == 0 || amount > this.balance) { throw; }

        // Can only withdraw once per week
        if (now < lastWithdrawDate + (1 weeks)) { throw; }
        lastWithdrawDate = now;

        // Deferred withdraw schedule; may only withdraw 1 more ether than has ever
        // been withdrawn. See top decription for schedule.
        uint256 maxAmount = (_totalSupply - this.balance + (1 ether));

        // Cannot withdraw more than the maximum allowed by schedule
        if (amount > maxAmount) { throw; }

        // Send the funds
        if (!owner.send(amount)) { throw; }
    }

    /**
     *  fallback function
     *
     *  Exchange Ether for MDT tokens. This may not be called once the presale has ended.
     */
    function () payable {

        // Can only buy tokens during the presale
        if (now >= activationDate) { throw; }

        // Give out the tokens
        balances[msg.sender] += msg.value;
        _totalSupply += msg.value;
    }


    /**
     *   ERC-20 token; nothing sinister below here (if there is, it wasn't intended)
     *   Mostly just copy and pasted from the de facto existing implmentation; changes marked
     */

     function totalSupply() constant returns (uint totalSupply) {
         return _totalSupply;
     }

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

    function transfer(address _to, uint256 _amount) returns (bool success) {

        // Cannot transfer tokens during the presale
        if (now < activationDate) { return false; }

        if (balances[msg.sender] < _amount || _amount == 0) { return false; }

        balances[msg.sender] -= _amount;
        balances[_to] += _amount;

        return true;
    }

    function transferFrom(address _from, address _to, uint256 _amount) returns (bool success) {

        // Cannot transfer tokens during the presale
        if (now < activationDate) { return false; }

        if (allowed[_from][msg.sender] < _amount || balances[_from] < _amount || _amount == 0) {
            return false;
        }

        balances[_from] -= _amount;
        allowed[_from][msg.sender] -= _amount;
        balances[_to] += _amount;

        return true;
    }

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

作弊方法

雖然明擺著有個(gè)提取限制,但是有沒有可能突破這個(gè)限制,項(xiàng)目方直接提款跑路呢?有!

請(qǐng)大家看withdraw函數(shù),最多能夠提取的金額maxAmount是由_totalSupplythis.balance決定的。而這兩者看似都是同時(shí)變化的(fallback函數(shù)和構(gòu)造函數(shù)),即_totalSuppy>= this.balance不變式總是成立,然而事實(shí)不總是如此,有多種方法可以打破這個(gè)不變式。

方法1

通過提前計(jì)算contract的部署地址,往這個(gè)地址打一筆錢(prefund),使得this.balance>_totalSupply成立,那么計(jì)算maxAmount就會(huì)下溢出,項(xiàng)目方可以直接跳過限制,直接全部提款跑路。

提前計(jì)算contract的部署地址

根據(jù)黃皮書的section 8: message call中所示,一個(gè)contract的地址取決于sendersender' nonce(sender發(fā)送的tx數(shù)目).

The address of the new account is dened as being the rightmost 160 bits of the Keccak hash of the RLP encoding of the structure containing only the sender and the nonce. Thus we dene the resultant address for the new account a:

contract address的計(jì)算方式

下面是python代碼:

# -*- coding: utf-8 -*-
from ethereum.utils import *
import sys

sender = sys.argv[1] #"0x003be5df5fef651ef0c59cd175c73ca1415f53ea";
nonce = encode_int(int(sys.argv[2]))
contract_address = mk_contract_address(sender, nonce)


print("sender: {}, nonce: {}, decode_addr: {}".format(sender, nonce,"0x" + decode_addr(contract_address)))

給一個(gè)不存在的ethereum地址轉(zhuǎn)賬會(huì)發(fā)什么?

還是根據(jù)黃皮書來找到答案。

Throughout the present work, it is assumed that if {$$\delta$$}1[r] was originally undefined, it will be created as an account with no code or state and zero balance and nonce.

(簡書markdown不支持?jǐn)?shù)學(xué)符號(hào),將就著看吧)簡單來說,如果接收方的recipient不存在,就創(chuàng)建一個(gè)。

測(cè)試

我先計(jì)算好要部署的contract的地址,通過MetaMask向其打一筆錢,然后在etherscan中可以發(fā)現(xiàn),出現(xiàn)一筆交易,此時(shí)沒有code選項(xiàng)卡。再通過remix部署合約,在etherscan中可以發(fā)現(xiàn)多了一個(gè)contract creation交易,此時(shí)code選項(xiàng)卡出現(xiàn)了。

0x5a4085C38dB8776Ff68F49BE8CDA143E0A0FCD13

方法2

其實(shí)上述方法還有不好的地方,就是可以讓人看到在合約創(chuàng)建之前就有一筆交易。其實(shí)還要一個(gè)更隱蔽的方法,selfdestruct(address), 這個(gè)函數(shù)可以向某個(gè)地址打一筆錢,而且沒有任何痕跡

結(jié)論

事實(shí)證明,類似上述代碼的ICO都有操控的嫌疑。比如說, 通過計(jì)算好預(yù)期的市場(chǎng)價(jià)格,通過suicide向ICO地址打一筆錢,使得totalSupply不變,而籌得金額卻增加,(提前)結(jié)束整個(gè)募集過程后,使得單個(gè)token的市場(chǎng)價(jià)格提高,達(dá)到預(yù)期的結(jié)果(瘋狂割韭菜)。

最后要打賞的客官這邊: 0x003be5df5fef651ef0c59cd175c73ca1415f53ea

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容