-
-
[原创][EVM虚拟机]区块链智能合约逆向-合约创建-调用x执行流程分析
-
发表于: 2023-12-14 18:48 10609
-
http://remix.zhiguxingtu.com/
https://ethereum.org/zh/developers/docs/evm/opcodes/
https://ethervm.io/decompile#google_vignette
先把合约编译部署了一下,然后看了一下remix上的调试器,和我想的一样,所谓合约调试并不是真正的断点调试,而是一次模拟,EVM并没有提供类似系统异常中断的功能,本身并不支持调试,所有的交易都在一瞬间完成
而调试器的原理(猜测)是根据交易Hash所查询到的数据,例如发起者,调用参数,调用函数等信息,再找到对应的合约bin,归结所有程序流程,然后从opcode开始模拟一次执行流程
编译部署完,然后拿着Deletegation
合约部署的交易Hash丢到调试器里,先从合约创建开始分析
可以看到调试器直接跳过了我第一次做智能合约EVM流程实现分析的那部分,[[1.合约安全&漏洞审计#3.2 Delegation静态分析]],而是直接来到了用户代码处
在 编译器->编译详情 中可以看到 函数签名、汇编 等详细信息,功能还是很全面的
补充:关于上面直接跳到用户代码处,可以往回拖动,直接回到0开始,那就从0到构造函数结束进行一次分析吧
在04
执行前,调试器中显示的Memory为 No data available ,但EVM实际执行过程中在此处是否真为空暂时无从得知
关于MSTORE
在以太坊官网的虚拟机操作码说明书中有如下定义
根据指令判断所传参数应为 (ost=40h,val=80h)
,而80h
最终被写到了50h
的位置,这与说明书中的操作不符,我暂时不知道是调试器的错误还是我的理解错误。使用官方的Remix-ide
进行同样的指令调试,最终看到的Memory
和推测的一样,80h
应该存储在了0x40
处才正确
下面看下一段指令
此处正在进行合约创建的第二步骤,判断msg.value
是否为0,若为0则正常跳转,反之撤销本次交易,以下是构造函数定义
,此时我给构造函数添加payable
关键字修饰,再进行编译,后查看此处汇编指令,此时发现,与预想中的不同,并非将ISZERO
进行NOT
取反,而是直接去除了该判断跳转,于是我进行了一次不转账的合约部署,发现成功了,我之前并不知道合约的构造函数加了payable
修饰是不强制转账的,现在知道了
现在看下一指令段
到此,对上面这段指令作一下分析,主要做了一个操作,那就是CODECOPY(80h,33eh,20h)
,操作完成后,最终栈剩余数据 80h、80h+20h(拷贝数据长度)、32h(push)
,随后便进入了下一个函数中
截止到221-JUMP
指令前,我们先看一下此时的栈情况
此时我们还不知道这些数据有何作用,继续往后跟,并贴上此类每段的栈
至此,此段就到此结束,截止到328-JUMP
的栈状态如下
而后便正式进入了构造函数中,而上述一大串指令中,仅做了一件事,就是对Delegation
合约的构造函数参数进行了检查,最终stack
中仅剩下参数值
构造函数的内容并没有什么值得过多提起的,但有一点,在所有针对地址的操作中,例如Delegation
合约的构造函数仅将一个地址参数赋值给成员,但依旧对其作了&0xFF(x20)
保留20bytes
的操作,注意,是所有针对地址的操作
与上述 参数检查 中的不同是,并不会对 &
前后结果作对比,也就是不论结果如何,并不会因此而撤销交易
个人感官而言上述如 参数检查 的部分操作是相当繁琐的,或许设计中还有其它功能,但我只看出来了参数检查一个,且仅有一个地址参数的情况下,但可以看到作了非常多次跳转,对参数值也进行了多次拷贝,可是实际参与计算貌似就两次?AND -> EQ
,随后就进行多次 栈交换,清理栈空间最终进入构造函数
首先依旧是Non Payable Check
,因为没有参数,所以没有Arguments Copy/Check
, 注意,Arguments Copy/Check
是合约创建时的操作,发起交易时的参数不会通过CODECOPY
来加载,而是通过CallData
进行传递和加载
在EVM中,函数并不像其它语言中存在一个既定的内存地址,用户发起交易并不是靠一个随机的地址来跳转,而是靠一个唯一的签名来判断,该签名为 函数声明 的哈希值
由于当前合约除fallback
之外,对外public
的成员仅有一个owner
,所以上述指令段较短,但当合约中存在多个public
成员时,该指令段便会一一对应存在多个签名匹配
形如SwitchCase
,但却没有SwitchCase
的效率,是纯if-elseif-else
请注意25-JUMPI
和46-JUMP
,此两处跳转分别对应两个处理函数
当CallData
为空时,就意味着本次交易连最基本的目标函数都没有被指定,那么进入后面的签名匹配流程也就毫无意义,熟悉合约开发的话就知道,在合约中存在receive
和fallback
两个特殊的函数,我认为将其叫做回调函数并不太恰当,因为它们的每一次调用实际上如其它函数一般,都是“指名道姓”的,只是它们并不存在签名,而作为上述指令段中的头
和尾
存在
即便我们没有为这两个函数声明,在上述指令段中依旧存在第一处跳转,而跳转目的指令是这样的
而最后一处跳转被替换成了
有合约开发经验的话应该知道,当未指定交易函数,且特定情况下不存在fallback
或者receive
的话,交易是会被撤销的,原因在此
根据官方文档可以知道这两个函数被调用的条件,receive
是当calldata
不存在的时候被调用的,fallback
则是殿后的那位,所以当receive
不存在时,匹配签名前的第一个跳转将直接跳转到fallback
的入口点前,从而进入fallback
让我们回到程序,当calldata
存在且无法匹配到函数签名,最终进入fallback
,下面瞅指令
至此,看一下栈状态
我们已知的数据分别有 预留指针、calldataSize、函数签名
接着又是一样的,进行了一手操作,大概是在准备参数,贴一下栈
接下一段
上述指令段中将calldata
的数据根据长度拷贝至了Memory
中,随后对栈进行了清理,至此,栈终于干净了
至此就正式进入了委托调用流程,参数分别如下
随后我们继续步入,首先进入到了Non Payable Check
,接着直接来到了Calldata Prepared & Compared
,最后根据我们传入的Calldata
找到pwn()
函数
在委托函数STOP
后便回到了DelegateCall
下一条指令处
可以看到在DelegateCall
之后跟合约源代码一样,就没有其它的操作,直接进入结束流程了,但是结果是,Storage[0]
的位置,被写入了此刻的msg.sender
由此可见,DelegateCall
,虽然叫做委托调用,但实际上只是引用了外部的指令,并不开辟或引用新内存,在此情况下,若被调用函数的签名可被随意操控,也就意味着攻击者可以编写任意的shellcode在你的合约中执行,篡改你的内存数据
最后把合约代码贴一下子,差点忘了
合约来源于:https://ethernaut.openzeppelin.com/
区块链萌新,关于本文中所列指令内容,我也依然有很多不理解的地方,如有错误,欢迎指出,感谢
000
PUSH1
80
002
PUSH1
40
004
MSTORE ;MSTORE(
40h
,
80h
)
000
PUSH1
80
002
PUSH1
40
004
MSTORE ;MSTORE(
40h
,
80h
)
操作码 | 指令 | Gas | 起始堆栈 | 最终堆栈 | 内存&存储 | 描述 |
---|---|---|---|---|---|---|
52 | MSTORE | 3 | ost, val |
. |
mem[ost:ost+32] := val | write a word to memory |
在执行完该指令后调试器所展示的memory和stack如下所示 |
Stack:
No data available
Memory:
0x0
:
00000000000000000000000000000000
0x10
:
00000000000000000000000000000000
0x20
:
00000000000000000000000000000000
0x30
:
00000000000000000000000000000000
0x40
:
00000000000000000000000000000000
0x50
:
00000000000000000000000000000080
Stack:
No data available
Memory:
0x0
:
00000000000000000000000000000000
0x10
:
00000000000000000000000000000000
0x20
:
00000000000000000000000000000000
0x30
:
00000000000000000000000000000000
0x40
:
00000000000000000000000000000000
0x50
:
00000000000000000000000000000080
005
CALLVALUE ;将msg.value压栈
006
DUP1 ;拷贝一份栈顶
007
ISZERO ;弹出当前栈顶,判断是否为
0
,结果再次压栈
008
PUSH2
0010
;
10h
压栈
011
JUMPI ;
if
(栈顶
=
=
1
){jmp
10h
}
012
PUSH1
00
014
DUP1
015
REVERT
005
CALLVALUE ;将msg.value压栈
006
DUP1 ;拷贝一份栈顶
007
ISZERO ;弹出当前栈顶,判断是否为
0
,结果再次压栈
008
PUSH2
0010
;
10h
压栈
011
JUMPI ;
if
(栈顶
=
=
1
){jmp
10h
}
012
PUSH1
00
014
DUP1
015
REVERT
016
JUMPDEST ;跳转点
017
POP ;弹出上一操作中的遗留数据(不理解为何上一步中专门DUP拷贝一份,但实际上只用一次,此处还需要弹出)
018
PUSH1
40
020
MLOAD ;加载Memory
+
0x40
所存数据并压栈,既上一步中写入的
80h
021
PUSH2
033e
024
CODESIZE ;获得执行合约代码的长度,并压栈
025
SUB ;弹值后计算得到 codesize
-
33eh
026
DUP1 ;计算结果拷贝
027
PUSH2
033e
030
DUP4 ;Memory
+
0x40
值拷贝
031
CODECOPY ;指令拷贝到Memory
-
>CODECOPY(
0x80
,
33eh
,subResult)
032
DUP2 ;拷贝Memory
+
0x40
值
033
DUP2 ;拷贝SUB长度计算结果
034
ADD ;两值相加
035
PUSH1
40
037
MSTORE ;结果存回Memory
+
0x40
038
DUP2 ;原Memory
+
0x40
值拷贝【
80h
】
039
ADD ;再与Sub长度计算结果相加
040
SWAP1
041
PUSH2
0032
044
SWAP2
045
SWAP1
046
PUSH2
00ce
049
JUMP ;调用了某个函数
016
JUMPDEST ;跳转点
017
POP ;弹出上一操作中的遗留数据(不理解为何上一步中专门DUP拷贝一份,但实际上只用一次,此处还需要弹出)
018
PUSH1
40
020
MLOAD ;加载Memory
+
0x40
所存数据并压栈,既上一步中写入的
80h
021
PUSH2
033e
024
CODESIZE ;获得执行合约代码的长度,并压栈
025
SUB ;弹值后计算得到 codesize
-
33eh
026
DUP1 ;计算结果拷贝
027
PUSH2
033e
030
DUP4 ;Memory
+
0x40
值拷贝
031
CODECOPY ;指令拷贝到Memory
-
>CODECOPY(
0x80
,
33eh
,subResult)
032
DUP2 ;拷贝Memory
+
0x40
值
033
DUP2 ;拷贝SUB长度计算结果
034
ADD ;两值相加
035
PUSH1
40
037
MSTORE ;结果存回Memory
+
0x40
038
DUP2 ;原Memory
+
0x40
值拷贝【
80h
】
039
ADD ;再与Sub长度计算结果相加
040
SWAP1
041
PUSH2
0032
044
SWAP2
045
SWAP1
046
PUSH2
00ce
049
JUMP ;调用了某个函数
284
JUMPDEST
285
PUSH1
00
287
PUSH1
20
289
DUP3 ;push
80h
290
DUP5 ;push a0h
291
SUB ;pop
-
>pop
-
>push a0h
-
80h
292
SLT ;pop
-
>pop
-
>push stack[
0
] < stack[
1
](有符号)
293
ISZERO ;TRUE
294
PUSH2
0132
297
JUMPI
306
JUMPDEST
307
PUSH1
00
309
PUSH2
0140
312
DUP5 ;push a0h
313
DUP3 ;push
0
314
DUP6 ;push
80h
315
ADD ;pop
-
>pop
-
>push
80h
+
0
316
PUSH2
0107
319
JUMP
263
JUMPDEST
264
PUSH1
00
266
DUP2 ;push
80h
267
MLOAD ;push memory[
80h
]
268
SWAP1
269
POP
270
PUSH2
0116
273
DUP2 ;push memory[
80h
]
274
PUSH2
00f0
277
JUMP
240
JUMPDEST
241
PUSH2
00f9
244
DUP2 ;push memory[
80h
]
245
PUSH2
00de
248
JUMP
222
JUMPDEST
223
PUSH1
00
225
PUSH2
00e9
228
DUP3 ;push memory[
80h
]
229
PUSH2
00be
232
JUMP
190
JUMPDEST
191
PUSH1
00
193
PUSH20 ffffffffffffffffffffffffffffffffffffffff
214
DUP3 ;push memory[
80h
]
215
AND ;将memory[
80h
]的值保留
20
字节
216
SWAP1
217
POP
218
SWAP2
219
SWAP1
220
POP
221
JUMP
284
JUMPDEST
285
PUSH1
00
287
PUSH1
20
289
DUP3 ;push
80h
290
DUP5 ;push a0h
291
SUB ;pop
-
>pop
-
>push a0h
-
80h
292
SLT ;pop
-
>pop
-
>push stack[
0
] < stack[
1
](有符号)
293
ISZERO ;TRUE
294
PUSH2
0132
297
JUMPI
306
JUMPDEST
307
PUSH1
00
309
PUSH2
0140
312
DUP5 ;push a0h
313
DUP3 ;push
0
314
DUP6 ;push
80h
315
ADD ;pop
-
>pop
-
>push
80h
+
0
316
PUSH2
0107
319
JUMP
263
JUMPDEST
264
PUSH1
00
266
DUP2 ;push
80h
267
MLOAD ;push memory[
80h
]
268
SWAP1
269
POP
270
PUSH2
0116
273
DUP2 ;push memory[
80h
]
274
PUSH2
00f0
277
JUMP
240
JUMPDEST
241
PUSH2
00f9
244
DUP2 ;push memory[
80h
]
245
PUSH2
00de
248
JUMP
222
JUMPDEST
223
PUSH1
00
225
PUSH2
00e9
228
DUP3 ;push memory[
80h
]
229
PUSH2
00be
232
JUMP
190
JUMPDEST
191
PUSH1
00
193
PUSH20 ffffffffffffffffffffffffffffffffffffffff
214
DUP3 ;push memory[
80h
]
215
AND ;将memory[
80h
]的值保留
20
字节
216
SWAP1
217
POP
218
SWAP2
219
SWAP1
220
POP
221
JUMP
0
:
0x00000000000000000000000000000000000000000000000000000000000000e9
1
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
2
:
0x0000000000000000000000000000000000000000000000000000000000000000
3
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
4
:
0x00000000000000000000000000000000000000000000000000000000000000f9
5
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
6
:
0x0000000000000000000000000000000000000000000000000000000000000116
7
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
8
:
0x0000000000000000000000000000000000000000000000000000000000000080
9
:
0x00000000000000000000000000000000000000000000000000000000000000a0
10
:
0x0000000000000000000000000000000000000000000000000000000000000140
11
:
0x0000000000000000000000000000000000000000000000000000000000000000
12
:
0x0000000000000000000000000000000000000000000000000000000000000000
13
:
0x0000000000000000000000000000000000000000000000000000000000000080
14
:
0x00000000000000000000000000000000000000000000000000000000000000a0
15
:
0x0000000000000000000000000000000000000000000000000000000000000032
0
:
0x00000000000000000000000000000000000000000000000000000000000000e9
1
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
2
:
0x0000000000000000000000000000000000000000000000000000000000000000
3
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
4
:
0x00000000000000000000000000000000000000000000000000000000000000f9
5
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
6
:
0x0000000000000000000000000000000000000000000000000000000000000116
7
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
8
:
0x0000000000000000000000000000000000000000000000000000000000000080
9
:
0x00000000000000000000000000000000000000000000000000000000000000a0
10
:
0x0000000000000000000000000000000000000000000000000000000000000140
11
:
0x0000000000000000000000000000000000000000000000000000000000000000
12
:
0x0000000000000000000000000000000000000000000000000000000000000000
13
:
0x0000000000000000000000000000000000000000000000000000000000000080
14
:
0x00000000000000000000000000000000000000000000000000000000000000a0
15
:
0x0000000000000000000000000000000000000000000000000000000000000032
233
JUMPDEST
234
SWAP1
235
POP
236
SWAP2
237
SWAP1
238
POP
239
JUMP
233
JUMPDEST
234
SWAP1
235
POP
236
SWAP2
237
SWAP1
238
POP
239
JUMP
0
:
0x00000000000000000000000000000000000000000000000000000000000000f9
1
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
2
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
3
:
0x0000000000000000000000000000000000000000000000000000000000000116
4
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
5
:
0x0000000000000000000000000000000000000000000000000000000000000080
6
:
0x00000000000000000000000000000000000000000000000000000000000000a0
7
:
0x0000000000000000000000000000000000000000000000000000000000000140
8
:
0x0000000000000000000000000000000000000000000000000000000000000000
9
:
0x0000000000000000000000000000000000000000000000000000000000000000
10
:
0x0000000000000000000000000000000000000000000000000000000000000080
11
:
0x00000000000000000000000000000000000000000000000000000000000000a0
12
:
0x0000000000000000000000000000000000000000000000000000000000000032
0
:
0x00000000000000000000000000000000000000000000000000000000000000f9
1
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
2
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
3
:
0x0000000000000000000000000000000000000000000000000000000000000116
4
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
5
:
0x0000000000000000000000000000000000000000000000000000000000000080
6
:
0x00000000000000000000000000000000000000000000000000000000000000a0
7
:
0x0000000000000000000000000000000000000000000000000000000000000140
8
:
0x0000000000000000000000000000000000000000000000000000000000000000
9
:
0x0000000000000000000000000000000000000000000000000000000000000000
10
:
0x0000000000000000000000000000000000000000000000000000000000000080
11
:
0x00000000000000000000000000000000000000000000000000000000000000a0
12
:
0x0000000000000000000000000000000000000000000000000000000000000032
249
JUMPDEST
250
DUP2 ;push Memory[
80h
]
251
EQ
252
PUSH2
0104
255
JUMPI
256
PUSH1
00
258
DUP1
259
REVERT
260
JUMPDEST
261
POP
262
JUMP
278
JUMPDEST
279
SWAP3
280
SWAP2
281
POP
282
POP
283
JUMP
320
JUMPDEST
321
SWAP2
322
POP
323
POP
324
SWAP3
325
SWAP2
326
POP
327
POP
328
JUMP
249
JUMPDEST
250
DUP2 ;push Memory[
80h
]
251
EQ
252
PUSH2
0104
255
JUMPI
256
PUSH1
00
258
DUP1
259
REVERT
260
JUMPDEST
261
POP
262
JUMP
278
JUMPDEST
279
SWAP3
280
SWAP2
281
POP
282
POP
283
JUMP
320
JUMPDEST
321
SWAP2
322
POP
323
POP
324
SWAP3
325
SWAP2
326
POP
327
POP
328
JUMP
0
:
0x0000000000000000000000000000000000000000000000000000000000000032
1
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
0
:
0x0000000000000000000000000000000000000000000000000000000000000032
1
:
0x000000000000000000000000d9145cce52d386f254917e481eb44e9943f39138
016
JUMPDEST
017
POP
018
PUSH1
04
020
CALLDATASIZE ;push
len
(msg.data)
021
LT ;push
len
(msg.data) <
4
022
PUSH2
002f
025
JUMPI ;
if
()jmp
2fh
026
PUSH1
00
028
CALLDATALOAD ;push calldata[
0
]
029
PUSH1 e0
031
SHR ;stack[
0
]
=
stack[
0
] >> (
256
-
32
),位移后获函数签名
032
DUP1 ;push stack[
0
]
033
PUSH4
8da5cb5b
038
EQ
039
PUSH2
00c3
042
JUMPI
043
PUSH2
0030
046
JUMP
016
JUMPDEST
017
POP
018
PUSH1
04
020
CALLDATASIZE ;push
len
(msg.data)
021
LT ;push
len
(msg.data) <
4
022
PUSH2
002f
025
JUMPI ;
if
()jmp
2fh
026
PUSH1
00
028
CALLDATALOAD ;push calldata[
0
]
029
PUSH1 e0
031
SHR ;stack[
0
]
=
stack[
0
] >> (
256
-
32
),位移后获函数签名
032
DUP1 ;push stack[
0
]
033
PUSH4
8da5cb5b
038
EQ
039
PUSH2
00c3
042
JUMPI
043
PUSH2
0030
046
JUMP
041
PUSH1
043
DUP1
044
REVERT
041
PUSH1
043
DUP1
044
REVERT
041
PUSH1
043
DUP1
044
REVERT
041
PUSH1
043
DUP1
044
REVERT
049
PUSH1
00
051
PUSH1
01
053
PUSH1
00
055
SWAP1
056
SLOAD ;push storage[
0
]
057
SWAP1
058
PUSH2
0100
057
SWAP1
058
PUSH2
0100
061
EXP
062
SWAP1
063
DIV
064
PUSH20 ffffffffffffffffffffffffffffffffffffffff
085
AND
086
PUSH20 ffffffffffffffffffffffffffffffffffffffff
107
AND ;对Delegate合约地址进行了两次保留
20
字节的与操作,意义不明
108
PUSH1
00
110
CALLDATASIZE
111
PUSH1
40
113
MLOAD ;加载预留指针
80h
114
PUSH2
007c
117
SWAP3
118
SWAP2
119
SWAP1
120
PUSH2
0139
123
JUMP
049
PUSH1
00
051
PUSH1
01
053
PUSH1
00
055
SWAP1
056
SLOAD ;push storage[
0
]
057
SWAP1
058
PUSH2
0100
057
SWAP1
058
PUSH2
0100
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)