Oyente 源码阅读笔记
Oyente 源码阅读笔记
大致文件结构
首先,通过 tree -L 2
查看大致 2 层文件结构:
1 | . |
Level One
几个关键些的文件(夹):
README.md
:我们在 Github 下载的时候已经看了,讲解
docker 或者依赖安装等 code.md
:中讲述了本项目的大致代码结构,可以先看一遍,然后看代码的时候对照着看。
setup.py
: setuptools 的构建脚本,用来告诉 setuptools
关于本项目(包)的描述、入口点、数据、包含的模块等,可以看一遍了解项目概况。关于
setup.py
的详细信息,见 Packaging Python Projects 1 misc_utils 文件夹
:
杂项,放了一些对本项目实用的工具脚本 oyente 文件夹
:
项目的主要源码文件夹
sol_examples 文件夹
: 是我在 Mythril
的 github 仓库 下载的,便于测试使用(如果可以的话)。
Level Two
oyente.oyente:main
入口点: oyente 文件夹中 oyente.py
的 main() 函数
声明 args 全局变量
后面在 analyze_bytecode 等函数中会用到输入参数们 args。
这里编码不规范,应该在所有函数外声明全局变量,虽然可以在函数内创建全局变量,但是不推荐,2
通过 argparse 模块设置参数
- 只需要 True 或者 False 的选项,使用了 argparse 的 action 参数3
比如:args.source、args.evm、args.standard_json 等输入输出类型、过程配置相关参数
输入
接受三种输入:solidity program、evm bytecode、remote contracts 这里,它使用
add_mutually_exclusive_group(require=True)
互斥参数组来确保将互斥组中只有一个参数在命令行中可用,且至少有一个。 具体输入类型: | 类型| 本地 | 远程 URL| |-------|----|-------| | solidity | -s filename | -ru url_address| | 字节码 | -b -s filename | -b -ru url_address|solidity 源码,还可以指定输入为标准 json 格式。
程序运行配置相关
如:指定运行 solidity 源码中的某(几)个合约 target-contract、并行 parallel、debug 等
运行过程中相关 IO 处理、操作相关
如,输出报错,创建报告等
检测运行相关
如:Gas、loop、depth 的限制、路径等
根据输入的参数和预先设置 0. 如果为本地文件(.sol 或 .evm),通过 -s 选项,获取到 args.source 的值
- 进行全局参数、日志配置
- 确保指定检测某个合约的功能,只能在源码输入情况下进行。
has_dependencies_installed
判断依赖(z3、evm、solc
)是否安装,并通过compare_versions
函数比较版本是否为测试版本(及以下),如不满足,则提示、并退出程序。- 如果是远程 URL,则创建 remote_contract.sol 或 remote_contract.evm,并使用 requests 模块写入,然后设置 args.source 为文件名
- 检测字节码
analyze_solidity
或者源码analyze_solidity
- 检测完毕,退出
exit_code
oyente.oyente:has_dependencies_installed
本函数判断依赖是否安装、且版本是否为测试版本(及以下), 版本要求:
z3 <= 4.5.1
、 evm <= 1.7.3
、
solc <= 0.4.19
,否则退出程序
几个注意的点:
Python 运行终端命令,
type cmd
判断命令是否存在- cmd_exist 开启一个子进程在 shell 中执行
type cmd
命令来判断是否存在
为什么要使用子进程测试呢? 属于 ”Python 中调用 Linux 命令并获取返回值“ 知识范畴6 结论:如果要兼容 python 3.5 以下就用 subprocess.call,反之,用 subprocess.run7
- cmd_exist 开启一个子进程在 shell 中执行
获得版本
比较版本 8
通过
compare_versions
函数,使用 pypthon2 的 cmp 比较,但是还通过 six 模块判断,在比较时,对 python3 做了兼容,python 中直接使用
<、>、==、>=
等进行比较即可为什么要比较小于等于? 版本的向后兼容性(Backward Compatibility)9
不过,有的 urllib2 等又没有对 python2 做兼容,真是奇怪。我们直接上 python3,不用兼容 python2,它都要被淘汰了。
判断 z3 依赖的方法
它采用捕获 import 异常的方法以及获取版本函数的方式
在我的 linux 上,z3 也可以通过
type z3
判断存在与否,通过z3 --version
来获取版本,即:和其他两个依赖方法一直。 但是我的 Ubuntu server 上貌似不行,后面看看你们可不可以,如果可以,那这里用函数把三者统一一下。
判断命令是否存在,一般有 type、hash、python 捕获导入异常10 11
oyente.oyente:analyze_bytecode
创建 Input_Helper 类的对象
类对象的初始化操作主要工作 确定要检测的文件类型与位置 设置默认参数,并获取一些配置参数
调用 helper 对象的 get_inputs() 获取返回列表的第一项,其内容为:反汇编结果的文件名
对于指定检测 solidity 源码文件中的某几个合约的,get_inputs 采用了默认参数
了解一下,可变参数(这里的话,就是 list)的默认参数需要设置为 None 15
对于字节码,它根据 args.source 中的文件名读取内容到 bytecode 变量
将 bytecode 传递给自身的私有方法
_prepare_disasm_file
_prepare_disasm_file 处理字节码,并生成反汇编文件
通过 _write_evm_file 进行去 SwarmHash 处理16,然后写入到
f"{args.source}.evm"
文件中去除它的原因:swarm hash in bytecode can be misinterpreted as INVALID opcode
SwarmHash/Swarm 是一个点对点文件共享系统,设计用来有效地存储和检索 ethereum 应用程序和合约中所需的数据。 最简单的类比应该是:Swarm 本质上是 ether 的 BitTorrent。 solc 编译后的字节码里面有个 CBOR 元数据编码,它里面就有 SwarmHash。SwarmHash 以固定的串开头,以固定的串结尾。
通过 _write_disasm_file 读取上一步处理好的
.evm
,然后反汇编,并写入到f"{args.source}.evm.disasm"
文件中读取上一步处理完的字节码到
evm_file
变量开启一个子进程 subprocess,通过执行终端命令:
evm disasm evm_file
,若反汇编失败则退出程序这里使用了 popen17 run 函数的底层,就是 Popen 函数。run 函数是同步的,要等待子进程实行结束,或者超时。Popen 创建子进程后,采用异步的方式,不会等待。Popen 比 run 要更加灵活,如果 run 函数还不能满足你的需求,就考虑 Popen
将反汇编结果写入
f"{args.source}.evm.disasm"
然后将反汇编结果文件名作
{'disasm_file': disasm_file}
字典,附加到 inputs 列表中并返回
将反汇编的文件名传入 symExec.run() 开始符号执行,检测漏洞,并返回结果和退出码
删除临时文件
包括之前生成的“处理过的字节码文件”,“反汇编结果文件”,还有日志等。
如果是全局参数 WEB 设置了,使用 six.print_ 输出一下 json 格式的结果。
返回退出码
目前发现的缺点
在函数内使用 global 创建全局变量 苏安然可以这样用,但是貌似不推荐?
python2 与 python3 兼容性没做好 虽然使用了 six 模块,但是只用其处理了 print,没处理好比较、urllib2 等,还是不能兼容 python2 和 python3
模块化不彻底,不同功能之间有点混乱。 感觉相互之间交叉,功能有点混乱,也可能是我看源码的能力不够?
动态为对象添加属性 不太方便看,不知道规不规范?
获取判断依赖那里没准可以统一用函数?
参考资料
[^class_attr]:Class and Instance Attributes Python 中的类属性、实例属性与类方法、静态方法