首页
社区
课程
招聘
[原创][EVM虚拟机]区块链智能合约逆向-合约创建-调用x执行流程分析
发表于: 2023-12-14 18:48 10611

[原创][EVM虚拟机]区块链智能合约逆向-合约创建-调用x执行流程分析

2023-12-14 18:48
10611

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-JUMPI46-JUMP,此两处跳转分别对应两个处理函数

CallData为空时,就意味着本次交易连最基本的目标函数都没有被指定,那么进入后面的签名匹配流程也就毫无意义,熟悉合约开发的话就知道,在合约中存在receivefallback两个特殊的函数,我认为将其叫做回调函数并不太恰当,因为它们的每一次调用实际上如其它函数一般,都是“指名道姓”的,只是它们并不存在签名,而作为上述指令段中的存在

即便我们没有为这两个函数声明,在上述指令段中依旧存在第一处跳转,而跳转目的指令是这样的

而最后一处跳转被替换成了

有合约开发经验的话应该知道,当未指定交易函数,且特定情况下不存在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期)

最后于 2023-12-15 11:28 被LeaMov编辑 ,原因: 修改标题
收藏
免费 7
支持
分享
最新回复 (1)
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享。
2024-1-17 14:15
0
游客
登录 | 注册 方可回帖
返回
//