目录
EOSIOAnalyzer
EOSIOAnalyzer是一个用于分析EOSIO智能合约字节码的分析框架。分析框架由三个部分组成:
- Control flow graph builder
- Static analyzer
- Vulnerability detector
以上是该框架的主要部分。除了Vulnerability detector部分是通过 Datalog编写的(执行引擎为Souffle),其他部分均用python编写。
Datalog大约900行,python大约3000行
下图是其框架图:
Control flow graph builder
输入为Wasm字节码,如果是源代码,可以用现有的编译器(llvm,eosio.cdt,emscripten,rustup等等)编译为字节码,然后基于现有开源工具Octopus将Wasm字节码构造对应的控制流图,作为下一步的输入
Static analyzer
这是EOSIOAnalyzer的核心。由于EOS VM的基于堆栈的紧凑执行模型混淆了跳转地址,所以将 Wasm字节码转换为中间表示,其中执行堆栈被寄存器变量替换,然后构建合约的过程间控制流图(ICFG)以进行漏洞分析
将得到的Wasm CFG转为IR CFG
将上一步得到的Wasm CFG进行转换,将低级的wasm指令转为更高级中间表示(IR)
对IR CFG基本块进行数据流分析
对得到的IR CFG进行数据流分析,得到完整的数据传播关系(Def_Use)
细粒度分析得到过程内CFG(intra-procedural control flow graph)以及粗粒度分析得到调用图(call graph),结合得到过程间控制流图(ICFG,inter-procedural control flow graph)
具体过程如下:
- 将 Wasm字节码转为Wasm基本块(在Control flow graph builder中已经实现)
- 将每个Wasm基本块转为对应的IR基本块
- 构造对应的IR CFG,作为程序
src/exporter.py
的输入,输出以下内容
状态转换函数:
输出所有的EDB(.facts)文件
- edge:包含块内与块间之间的边
- def:新赋值的变量,每个变量都是一个寄存器变量(唯一)
- use:每个语句需要使用之前定义的变量
- op:wasm操作码
- value:程序中的常量
- imm:操作码需要的立即数
- block_in_function:每个函数包含的块名
- func_call_edge:包含所有直接调用(call)以及间接调用(call_indirect)之间的边
- in_function:每个函数包含的语句(statement)
- is_function:程序中的所有函数(包括import、export、local)
- function_types:只包含export和local函数的签名(参数及返回值)
- static_datas:程序中的常量及字符串
- block_dom:包含块内与块间的dominator关系
- types:间接调用对应的函数类型(call_indirect的第一个立即数)
程序介绍
实现:目录src下包含大部分python程序,下面将介绍这些程序功能
src/cfg.py
:定义基类ControlFlowGraph
和BasicBlock
src/wasm_cfg.py
:专门的cfg.ControlFlowGraph
和cfg.BasicBlock
用于表示Wasm字节码的控制流图src/ir_cfg.py
:专门wasm_cfg.*
用于在我们的中间表示中表示合约的类。实现定义ir_cfg.Destackifier
了一个将单个wasm_cfg.EVMBasicBlock
实例转换为ir_cfg.IRBasicBlock
实例的类;和辅助类IROp
,IRAssignOp
,IRArg
,IRCallOp
以及IRLocRef
我们的中间表示src/exporter.py
:定义了从给定的控制流图生成 EOSIOAnalyzer输出表示的类。在其构造函数中接受一个 CFG 实例,并包含一个产生输出的export()
函数,输出所有EDB文件src/dataflow.py
:src/lattice.py
:实现lattice类,用于定点数据流分析src/memtypes.py
:用于表示和使用我们的 IR 中使用的寄存器变量的类。变量可以是常数、设定值或未知src/wasm_inst.py
:使用官方文档中的信息定义 Wasm指令集src/number.py
:包含所有与number相关的函数和类src/requirement.txt
:包含程序运行所需要的包
分析脚本
我们的分析脚本
tools/bulk_analysis.py
可以分析单个合约字节码目录,也可以分析包含多个智能合约字节码目录(可以多线程运行),只需修改路径即可。结合Souffle 引擎以及编写的漏洞规则,最终会输出一个json格式的文件,包含最终的漏洞分析结果
Datalog规范
我们编写的Datalog规则在目录
tools/spec.dl
、tools/instructions.dl
中
例如,以下规则用于检测Fake EOS Transfer漏洞
.decl DetectFakedEosTransfer(call_stmt: Statement)
.output DetectFakedEosTransfer
DetectFakedEosTransfer(call_stmt) :-
NotEosioToken(true_stmt),
Call_Transfer(call_stmt),
Dominates(call_stmt, true_stmt).
Vulnerability detector
Fake EOS Transfer
描述:由于eosio.token源代码完全公开的,所以任何人都能复制其源代码,并发布一个token(相同的名字、符号和代码),虚假的EOS和官方的唯一不同就是具有不同的发布人。或者直接调用漏洞合约的transfer函数进行转账
规则:code == N(eosio.token) --> action == N(transfer)
在apply函数中,看是否存在这样一条验证路径,如存在则不存在该漏洞,反之则存在;
Forged Transfer Notification
描述:攻击者在 EOS 网络中控制两个账户 A 和 B,通过账户 A 向账户 B 发送真正的 EOS,eosio.token 合约在转账成功后会向 B 发送 notification。当账户 B 收到 notification后随即转发到受害者智能合约 C
Transfer --> _self == to
在找到的所以可能“transfer”函数中,看是否存在*_self*与to的比较,如存在则不存在该漏洞,反之则存在;
Block Information Dependency
描述:开发者一般调用tapos_block_prefix或tapos_block_number获得区块链信息,并将其返回值作为控制流的判断条件,并最后触发调用send_inline函数进行转账行为
tapos_block_prefix/tapos_block_num --> send_inline
如果存在一条由tapos_block_prefix/tapos_block_num函数作为控制条件并最后触发send_inline函数的调用,则存在该漏洞,反之不存在