EVM Simulator

CTF 的时候遇到一个以太坊相关的题目,只有合约的 opcode,最后在纸上用手演算做出来的。当时就感觉如果有一个能直接调试 opcode 的东西就好了。在 Salt 大佬 的建议下,花了一天的时间改了改 octopus,攒了一个简易的调试器。

https://github.com/Hearmen/evm_emulator

EVM emulator

这个调试器是基于 octopus 开发的。 octopus 是研究者 pventuzelo 开发的一款以太坊逆向工具,使用这个工具可以很方便的将大部分 ETH 上的 opcode 编译成 CFG

调试器为了方便,直接在 octopus 的源码上进行了修改,以后可能会把这一部分抠出来。

有了 octopus 的基础,调试器实现起来就非常方便。octopus已经将将 opcode 的执行流程进行了分析,基于这个分析我们只需要实现一个 stack,一个 memory 和一个 storage 即可。

我们主要进行了四项修改

  • ssa_stack_memory_storage_flow_instruction 函数中添加了对 storage 和 memory 的处理,模拟 instruction 的真实作用
  • emulate 函数添加参数 callinfo,通过这个参数我们就可以传递 calldata 和 value 信息以及其他 transaction 信息给合约
  • 修改处理 JUMP 的函数,依据 condition 选择下一条执行的 instruction,而不是遍历所有的路径
  • 修改 state 的处理部分,不再使用 deepcopy 拷贝一个全新的 state,而是直接在原有的 state 上进行修改,从而可以直接将 instruction 的影响应用到 state 中

修改之后就可以按照下面的方法应用之,上周做的 CTF 题目可以这样来解

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 octopus.arch.evm.disassembler import EvmDisassembler
from octopus.platforms.ETH.emulator import EthereumSSAEngine
from octopus.platforms.ETH.vmstate import EthereumVMstate
from octopus.core.utils import bytecode_to_bytes
file_name = 'ctf.bytecode'
# read file
with open(file_name) as f:
bytecode_hex = f.read()
# init code
initdata = "0x608060405234801561001057600080fd5b5060008054600160a060020a0319163317815560036002557feb3effabe9960401da2b4dbf9e92b0b40569c5f005f81491c9d92f574adb5b0b907f7e782580d29c5c8c2fc261c858906ff320bd5d2e005b5669cc140d42f15d9b08905b60108110156100845791811881019160010161006d565b505060015561023e806100986000396000f300"
callinfo = {'calldata':null,'callvalue':0}
state=EthereumVMstate()
emul = EthereumSSAEngine(initdata)
emul.emulate(callinfo, state)
emul = EthereumSSAEngine(bytecode_hex)
print("******************************************************************")
print("******************************************************************")
print("******************************************************************")
print("******************************************************************")
print("******************************************************************")
print("******************************************************************")
print("******************************************************************")
# Start Call
# calldata
calldata = bytecode_to_bytes("0xc6c58bcd95529edd28cb526ab5071fd2fdebd5fc4e08b2af6876dd33a57764a970157576")
callinfo = {'calldata':calldata,'callvalue':0}
emul.emulate(callinfo, state)

目前调试器还没有加入用户交互等功能,在以后需要的时候可能会加入。代码本身由于直接在源代码上进行修改的缘故因此比较杂乱。Salt 大佬实现了一个独立的版本,可以参考使用

https://github.com/5alt/ethemu

python 记录

python 开发的时候遇到了一些小问题,这里记录一下,以备以后查阅

首先,print 函数输出的问题,有时候我们想要使输出更容易分辨,这就需要输出带有颜色, python 中可以使用 color 进行处理。而终端也自带了这一功能

1
2
3
print('This is a \033[1;35m test \033[0m!')
print('This is a \033[1;32;43m test \033[0m!')
print('\033[1;33;44mThis is a test !\033[0m')

终端的字符颜色是用转义序列控制的,是文本模式下的系统显示功能,和具体的语言无关。转义序列是以ESC开头,即用\033来完成(ESC的ASCII码用十进制表示是27,用八进制表示就是033)。

书写格式:

开头部分:\033[显示方式;前景色;背景色m +
结尾部分:\033[0m

注意:开头部分的三个参数:显示方式,前景色,背景色是可选参数,可以只写其中的某一个;另外由于表示三个参数不同含义的数值都是唯一的没有重复的,所以三个参数的书写先后顺序没有固定要求,系统都能识别;但是,建议按照默认的格式规范书写。

对于结尾部分,其实也可以省略,但是为了书写规范,建议\033[***开头,\033[0m结尾。

数值表示的参数含义:

显示方式: 0(默认\)、1(高亮)、22(非粗体)、4(下划线)、24(非下划线)、 5(闪烁)、25(非闪烁)、7(反显)、27(非反显)
前景色:   30(黑色)、31(红色)、32(绿色)、 33(黄色)、34(蓝色)、35(洋 红)、36(青色)、37(白色)
背景色:   40(黑色)、41(红色)、42(绿色)、 43(黄色)、44(蓝色)、45(洋 红)、46(青色)、47(白色)

bytearry

在调试器的设计上我使用了 bytearray 作为 EVM 的 memory。这个类型之前使用不多,但是是一个很有意思的类型。

bytearray 的使用上集成了 byte 和 list 两个类的功能.

将 int 转化为 bytearray 可以使用方法

1
(val).to_bytes(32, byteorder="big")

将字符串转化为 bytearray 可以使用方法

1
bytes.fromhex('7e782580d29c5c8c2fc261c858906ff320bd5d2e005b5669cc140d42f15d9b08')

将 bytearray 转化为 int 可以使用方法

1
int(self[p:p+0x20].hex(),16)

Reference

https://www.cnblogs.com/fangbei/p/python-print-color.html