区块链EXP(区块链又3道题目分析(主过程))

  区块链EXP(区块链又3道题目分析(主过程))


  

RCTF2020 roiscoin

题目给了源码

  

Resource

pragma solidity ^0。4。23;contract FakeOwnerGame { event SendFlag(address _addr); uint randomNumber = 0; uint time = now; mapping (address =>; uint) public BalanceOf; mapping (address =>; uint) public WinCount; mapping (address =>; uint) public FailCount; bytes32[] public codex; address private owner; uint256 settlementBlockNumber; address guesser; uint8 guess; struct FailedLog { uint failtag; uint failtime; uint success_count; address origin; uint fail_count; bytes12 hash; address msgsender; } mapping(address =>; FailedLog[]) FailedLogs; constructor() { owner = msg。sender; } modifier onlyOwner() { require(msg。sender == owner); _; } function payforflag() onlyOwner { require(BalanceOf[msg。sender] >;= 2000); emit SendFlag(msg。sender); selfdestruct(msg。sender); } function lockInGuess(uint8 n) public payable { require(guesser == 0); require(msg。value == 1 ether); guesser = msg。sender; guess = n; settlementBlockNumber = block。number + 1; } function settle() public { require(msg。sender == guesser); require(block。number >; settlementBlockNumber); uint8 answer = uint8(keccak256(block。blockhash(block。number - 1), now)) % 2; if (guess == answer) { WinCount[msg。sender] += 1; BalanceOf[msg。sender] += 1000; } else { FailCount[msg。sender] += 1; } if (WinCount[msg。sender] == 2) { if (WinCount[msg。sender] + FailCount[msg。sender] <;= 2) { guesser = 0; WinCount[msg。sender] = 0; FailCount[msg。sender] = 0; msg。sender。transfer(address(this)。balance); } else { FailedLog failedlog; failedlog。failtag = 1; failedlog。failtime = now; failedlog。success_count = WinCount[msg。sender]; failedlog。origin = tx。origin; failedlog。fail_count = FailCount[msg。sender]; failedlog。hash = bytes12(sha3(WinCount[msg。sender] + FailCount[msg。sender])); failedlog。msgsender = msg。sender; FailedLogs[msg。sender]。push(failedlog); } } } function beOwner() payable { require(address(this)。balance >; 0); if(msg。value >;= address(this)。balance){ owner = msg。sender; } } function revise(uint idx, bytes32 tmp) { codex[idx] = tmp; }}

给了源码可以说好分析的多。 查看payforflag的条件是balanceof[msg。sender]>;=2000 还有就是调用者必须为owner。
然后查看这里的balance 如何来加, 通过赌注,但是这里赌注的随机数无法预测但是只有0和1,还是可以爆破的。首先讲非预期。

  

非预期:

由于beOwner中的 address(this)。balance在计算时算了msg。value。
所以只要原合约的初始为0,那么我们转账>;0就可以拿到BeOwner 然后在暴力猜数字2次成功就可以payforflag了。

  

预期!

我们可以看到在battle里面,如果猜错这里用了一个在这里定义的结构体。而结构体的内存这里没有声明使用memory而是使用了stroage ,这里便引起了变量覆盖。
这里的failedlog未初始化造成了storage的任意写从而我们可以来覆写我们的codex的数组长度。 数组长度任意写之后,我们下一步就是想把owner写成我们自己。 数组任意写,对长度有一定要求,利用msg。owner覆盖了数组的高20字节。
那么我们就考虑这个codex[] 他的长度codex。length在storage[5] 他的计算是从

  

keccak256(5)+var0 var0可控。 如果我们在这里 x=keccak256(5) 那么传入

  

2^256+6-x 我们就可以任意写storage[6] 也就是owner 。这一段如果不太理解最好是对着反汇编看。因为这里源代码反而没有那么直观。

  

PS:这里为什么+2^256,因为不能传入负数。

  

写完storage[6]后,只需要满足猜两次就够了。

  

他用的是未来随机数,不过他就需要猜对2次,就蒙就可以了。
这里还是不放 exp,建议师傅们自己来尝试一下。并且RCTF的wp中也有完整的exp。大家都可以去学习。

  


  

华为鸿蒙场区块链

华为鸿蒙场的区块链,比赛在考试,现在来复现下,题目没有给出源码。但是已经找不到复现了。应该是pikachu师傅用他的docker出的。这里我自己部署了下原合约。然后重新逆向一次。
经过逆向以及

  

Resource

pragma solidity ^0。4。23;contract ContractGame { event SendFlag(address addr); mapping(address =>; bool) internal authPlayer; uint private blocknumber; uint private gameFunds; uint private cost; bool private gameStopped = false; address public owner; bytes4 private winningTicket; uint randomNumber = 0; mapping(address=>;bool) private potentialWinner; mapping(address=>;uint256) private rewards; mapping(address=>;bytes4) private ticketNumbers; constructor() public payable { gameFunds = add(gameFunds, msg。value); cost = div(gameFunds, 10); owner = msg。sender; rewards[address(this)] = msg。value; } modifier auth() { require(authPlayer[msg。sender], ";you are not authorized!";); _; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >;= a, ";SafeMath! addition overflow";); return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <;= a); uint256 c = a - b; return c; } function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, ";SafeMath! multiplication overflow";); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b >; 0); uint256 c = a / b; return c; } function BetGame(bool mark) external payable { require(msg。value == cost); require(gameFunds >;= div(cost, 2)); bytes32 entropy = blockhash(block。number-1); bytes1 coinFlip = entropy[10] & 1; if ((coinFlip == 1 && mark) (coinFlip == 0 && !mark)) { gameFunds = sub(gameFunds, div(msg。value, 2)); msg。sender。transfer(div(mul(msg。value, 3), 2)); } else { gameFunds = add(gameFunds, msg。value); } if (address(this)。balance==0) { winningTicket = bytes4(0); blocknumber = block。number + 1; gameStopped = false; potentialWinner[msg。sender] = true; rewards[msg。sender] += msg。value; ticketNumbers[msg。sender] = bytes4((msg。value - cost)/10**8); } } function closeGame() external auth { require(!gameStopped); require(blocknumber != 0); require(winningTicket == bytes4(0)); require(block。number >; blocknumber); require(msg。sender == owner rewards[msg。sender] >; 0); winningTicket = bytes4(blockhash(blocknumber)); potentialWinner[msg。sender] = false; gameStopped = true; } function winGame() external auth { require(gameStopped); require(potentialWinner[msg。sender]); if(winningTicket == ticketNumbers[msg。sender]){ emit SendFlag(msg。sender); } selfdestruct(msg。sender); } function AddAuth(address addr) external { authPlayer[addr] = true; } function() public payable auth{ if(msg。value == 0) { this。closeGame(); } else { this。winGame(); } }}

题目不难,但是逻辑比较多,比较符合pikachu师傅出题的规律非常有学习代表性。首先是在functon中自写了4种运算规则,类似safemath库。

  

这里剩下可调用的函数采用了external auth等函数声明方法,经过查询也是public的 是可以被外部调用的。主要是可以大量减少在外部传入大数组时的合约交互的gas。

  

function() public payable auth{ if(msg。value == 0) { this。closeGame(); } else { this。winGame(); } }

这里是一个fallback是非常有应用价值的。
后面几个函数也都来分析下。

  

function winGame() external auth { require(gameStopped); require(potentialWinner[msg。sender]); if(winningTicket == ticketNumbers[msg。sender]){ emit SendFlag(msg。sender); } selfdestruct(msg。sender); }

Wingame中,需要game已经停止, 并且需要potentialWinner[msg。sender]为1,并且如果winningticket == ticketNumbers[msg。sender]就会触发flag了。

  

function closeGame() external auth { require(!gameStopped); require(blocknumber != 0); require(winningTicket == bytes4(0)); require(block。number >; blocknumber); require(msg。sender == owner rewards[msg。sender] >; 0); winningTicket = bytes4(blockhash(blocknumber)); potentialWinner[msg。sender] = false; gameStopped = true; }

这里主要进行了closegame 也就是gamestop赋值。这里需要的是game还没stop且blocknumber!=0,并且winningticket=bytes4(0) 且block。number>;blocknumber 以及msg。sender已经变成owner,且rewards[msg。sender]

  

那么这里就会赋值potentialWinner[msg。sender]=false gamestopped=true。这里成功满足了wingame的第一个但是没有满足第二个。

  

那么现在接着看构造函数。

  

constructor() public payable { gameFunds = add(gameFunds, msg。value); cost = div(gameFunds, 10); owner = msg。sender; rewards[address(this)] = msg。value; }

创建的时候,直接会让gameFunds=gameFunds+msg。value传入值。

  

cost= gamefunds/10

  

owner就变成了msg。sender。

  

且rewards[address(this)]=msg。value

  

还有一个Bet函数

  

function BetGame(bool mark) external payable { require(msg。value == cost); require(gameFunds >;= div(cost, 2)); bytes32 entropy = blockhash(block。number-1); bytes1 coinFlip = entropy[10] & 1; if ((coinFlip == 1 && mark) (coinFlip == 0 && !mark)) { gameFunds = sub(gameFunds, div(msg。value, 2)); msg。sender。transfer(div(mul(msg。value, 3), 2)); } else { gameFunds = add(gameFunds, msg。value); } if (address(this)。balance==0) { winningTicket = bytes4(0); blocknumber = block。number + 1; gameStopped = false; potentialWinner[msg。sender] = true; rewards[msg。sender] += msg。value; ticketNumbers[msg。sender] = bytes4((msg。value - cost)/10**8); } }

这里先要求cost 也就是创建时候的msg。value/10 == 当前传入的msg。value

  

并且gamefunds >;= cost/2

  

然后是经典的随机数预测。 攻击合约一模一样 写就可以得到相同的结果。

  

然后写了个巨奇怪的if

  

其实就是coinFlip==mark。猜对了的话 GameFunds+=msg。value/2

  

msg。sender。transfer(msg。value*1。5)

  

要不然就GameFunds +=msg。value

  

这里进行完事之后 如果合约的balance==0了

  

那么winningTicket=bytes(4) blocknumber+=1

  

gameStopped=0 potentialWinner[msg。sender]=1

  

rewards[msg。sender]+=msg。value

  

TicketNumbers[msg。sender]=bytes4((msg。value-cost)/10^8)

  

这里的条件直接基本把closegame这里的要求全满足了。

  

然后我们首先就是要开始进行题目了。 首先我们给两个ether,相当于让他创建一个有2eth 的游戏。 每次他会输出来0。1eth ,我们进行20次就够了。

  

然后先call AddAuth题目的合约地址,再call Addauth 外部账户地址,再CallAddauth 攻击合约的地址。
PS!这里ADDAUTH相当于给我们调用函数的权限

  

最后利用题目合约的fallback调用closegame防止他把我们的
potentialWinner 给改了。
那么现在就满足了所有条件
直接winGame就可以了。
贴下pikachu师傅的exp
modifier是为了允许我们的这些地址可以调用这些函数。
所以都要加到Addauth里面。
那么攻击步骤我这里重新列出

  

  

1。 首先建立攻击合约,并且打2 ether过去。2。 Addauth 使我们的题目合约,攻击合约,以及我们的外部账户都有权限调用函数。比特币价格3。 通过外部合约转账调用delegatecall触发closegame4。 call wingame()

这样就可以成功拿到flag了。

  


  

*CTF2021 Starndbox

六星战队在分站赛出的题,非常不错。
考察的点和2020qwb 的ezsandbox很像。 利用可用字节码清空合约余额即成功。
给出了以下源码

  

pragma solidity ^0。5。11;library Math { function invMod(int256 _x, int256 _pp) internal pure returns (int) { int u3 = _x; int v3 = _pp; int u1 = 1; int v1 = 0; int q = 0; while (v3 >; 0){ q = u3/v3; u1= v1; v1 = u1 - v1*q; u3 = v3; v3 = u3 - v3*q; } while (u1<;0){ u1 += _pp; } return u1; } function expMod(int base, int pow,int mod) internal pure returns (int res){ res = 1; if(mod >; 0){ base = base % mod; for (; pow != 0; pow >;>;= 1) { if (pow & 1 == 1) { res = (base * res) % mod; } base = (base * base) % mod; } } return res; } function pow_mod(int base, int pow, int mod) internal pure returns (int res) { if (pow >;= 0) { return expMod(base,pow,mod); } else { int inv = invMod(base,mod); return expMod(inv,abs(pow),mod); } } function isPrime(int n) internal pure returns (bool) { if (n == 2 n == 3 n == 5) { return true; } else if (n % 2 ==0 && n >; 1 ){ return false; } else { int d = n - 1; int s = 0; while (d & 1 != 1 && d != 0) { d >;>;= 1; ++s; } int a=2; int xPre; int j; int x = pow_mod(a, d, n); if (x == 1 x == (n - 1)) { return true; } else { for (j = 0; j <; s; ++j) { xPre = x; x = pow_mod(x, 2, n); if (x == n-1){ return true; }else if(x == 1){ return false; } } } return false; } } function gcd(int a, int b) internal pure returns (int) { int t = 0; if (a <; b) { t = a; a = b; b = t; } while (b != 0) { t = b; b = a % b; a = t; } return a; } function abs(int num) internal pure returns (int) { if (num >;= 0) { return num; } else { return (0 - num); } }}contract StArNDBOX{ using Math for int; constructor()public payable{ } modifier StAr() { require(msg。sender != tx。origin); _; } function StArNDBoX(address _addr) public payable{ uint256 size; bytes memory code; int res; assembly{ size != extcodesize(_addr) code != mload(0x40) mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) mstore(code, size) extcodecopy(_addr, add(code, 0x20), 0, size) } for(uint256 i = 0; i <; code。length; i++) { res = int(uint8(code[i])); require(res。isPrime() == true); } bool success; bytes memory _; (success, _) = _addr。delegatecall(";";); require(success); }}

上面的数学方法以2为基来算素数在0-255区间内,除了0是没有问题的,所以我们想到的就是用0来绕过它对字节码仅能为素数的限制。
给了delegatecall。
合约里面只有100wei,我们可以通过call(0xf1素数)方法来将余额清空。
比赛时候是利用强大的黑暗力量做的。因为题目部署合约100wei在Rinkedby测试链属实很少见,随便翻了翻就可以找到其中队伍做出的合约。
给出赛时exp(题目代码就不贴了)。

  

contract exp{ constructor()public{} address ss=0xb3879a53b3964494a149BcC1863dD262C35a64aE; address target=0x8748ec747eB7af0B7c4e82357AAA9de00d32264a; StArNDBOX a=StArNDBOX(target); function step()external{ a。StArNDBoX(ss); }}

call的其他是没有问题的,当call一个合约非方法的四字节地址时,那么就会直接给其转账。那么贴图看下字节码的执行。

  

如此一来就没有质数。部署一个bytecode如上的合约即可成功调用。

  
","content_hash"!"88668612

版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

评论