Oyente搭建,框架结构以及helloworld案例解析(二)

博客目标

  • 完成对oyente代码的阅读和解析,主要是针对函数full_sym_exec以及内部的部分。
  • 完成对控制流图的完整概念理解。
  • 完成对z3进行约束求解的理解。

1. 控制流图的概念

1.1. 基本块(basic block)

  • 基本块是一个最大化的指令序列,程序执行只能从这个序列的第一条指令,从这个序列的最后一条指令退出。
  • 构建基本块的三个原则:
    • 遇到程序、子程序的第一条指令或语句,结束当前基本块,并将该语句作为一个新块的第一条语句。
    • 遇到跳转语句、分支语句、循环语句、将该语句作为当前块的最后一条语句,并结束当前块。
    • 遇到其他语句直接将其加入当前基本块。

1.2. 控制流图(CFG)

  • 控制流图是以基本块为节点的有向图 G = ( N , E ) G=(N,E) G=(N,E),其中N是节点集合,表示程序中的基本块; E E E是节点之间边的集合。
  • 如果从基本块 P P P的出口转向基本块块 Q Q Q,则从 P P P Q Q Q有一条有向边 P P P-> Q Q Q,表示从节点 P P P Q Q Q存在一条可执行路径, P P P Q Q Q的前驱节点, Q Q Q P P P的后继节点。
  • 也就代表在执行完节点 P P P中的代码语句后,有可能顺序执行节点 Q Q Q的代码语句。

这里我参考360核心安全技术博客的区块链安全-以太坊智能合约静态分析。

00000: PUSH1 0x80
00002: PUSH1 0x40
00004: MSTORE
00005: PUSH1 0x04
00007: CALLDATASIZE
00008: LT
00009: PUSH1 0x3e
0000b: JUMPI
0000c: PUSH4 0xffffffff
00011: PUSH29 0x0100000000000000000000000000000000000000000000000000000000
0002f: PUSH1 0x00
00031: CALLDATALOAD
00032: DIV
00033: AND
00034: PUSH4 0x1003e2d2
00039: DUP2
0003a: EQ
0003b: PUSH1 0x43
0003d: JUMPI
0003e: JUMPDEST
0003f: PUSH1 0x00
00041: DUP1
00042: REVERT
00043: JUMPDEST
00044: CALLVALUE
00045: DUP1
00046: ISZERO
00047: PUSH1 0x4e
00049: JUMPI
0004a: PUSH1 0x00
0004c: DUP1
0004d: REVERT
0004e: JUMPDEST
0004f: POP
00050: PUSH1 0x58
00052: PUSH1 0x04
00054: CALLDATALOAD
00055: PUSH1 0x73
00057: JUMP
00058: JUMPDEST
00059: PUSH1 0x40
0005b: DUP1
0005c: MLOAD
0005d: SWAP3
0005e: ISZERO
0005f: ISZERO
00060: DUP4
00061: MSTORE
00062: PUSH1 0x20
00064: DUP4
00065: ADD
00066: SWAP2
00067: SWAP1
00068: SWAP2
00069: MSTORE
0006a: DUP1
0006b: MLOAD
0006c: SWAP2
0006d: DUP3
0006e: SWAP1
0006f: SUB
00070: ADD
00071: SWAP1
00072: RETURN
00073: JUMPDEST
00074: PUSH1 0x00
00076: DUP1
00077: SLOAD
00078: DUP3
00079: ADD
0007a: DUP1
0007b: DUP3
0007c: SSTORE
0007d: DUP2
0007e: SWAP1
0007f: DUP4
00080: GT
00081: ISZERO
00082: PUSH1 0x86
00084: JUMPI
00085: Missing opcode 0xfe
00086: JUMPDEST
00087: SWAP2
00088: POP
00089: SWAP2
0008a: JUMP
0008b: STOP
.........
  • 我们从第一条指令开始分析构建基本块的过程
    • 00000地址处是程序的第一条指令,根据构建基本块的第一个原则,将其作为新的基本块的第一条指令;
    • 0000b地址处是一条跳转指令,根据构建基本块的第二个原则,将其作为新的基本块的最后一条指令。
    • 这样我们就把从地址000000000b的代码构建成一个基本块,为了之后方便描述,我们把这个基本块命名为基本块1。
  • 我们下面分析第二个基本块:
    • 0000c地址处的指令,我们作为新的基本块的第一条指令。0003d地址处是一条跳转指令,根据构建基本块的第二个原则,将其作为新的基本块的最后一条指令。于是从地址0000c0003d就构成了一个新的基本块,我们把这个基本块命名为基本块2。
  • 以此类推,构建完成后的基本块如下图所示:
    Oyente搭建,框架结构以及helloworld案例解析(二)_第1张图片

1.3. 构建基本块之间的边

  • 简单来说,基本块之间的边就是基本块之间的跳转关系。以基本块1为例,其最后一条指令是条件跳转指令,如果条件成立就跳转到基本块3,否则就跳转到基本块2。
  • 所以基本块实际上就存在基本块1->基本块2基本块1->基本块3两条边。基本块6的最后一条指令是跳转指令,该指令会直接跳转到基本块8,所以基本块6就存在基本块6->基本块8这一条边。
  • 总结归纳就会如下图所示
{
     
    '基本块1': ['基本块2','基本块3'],
    '基本块2': ['基本块3','基本块4'],
    '基本块3': ['基本块11'],
    '基本块4': ['基本块5','基本块6'],
    '基本块5': ['基本块11'],
    '基本块6': ['基本块8'],
    '基本块7': ['基本块8'],
    '基本块8': ['基本块9','基本块10'],
    '基本块9': ['基本块11'],
    '基本块10': ['基本块7']
}
  • 我们把边的集合 E E E用python中的dict表示,dict中的key就是基本快,key对应的value值是一个list。还是以基本块1为例,因为基本块1存在基本块1->基本块2基本块1->基本块3两条边,所以基本块1对应的list为['基本块2', '基本块3']

1.4. 构建控制流图

  • 按照上面的思路,我们的控制流图如下所示:
    Oyente搭建,框架结构以及helloworld案例解析(二)_第2张图片

1.5. oyente的控制流图在哪里

  • 实际上verticesedge两个变量就已经获取到了所有需要的信息了。
  • 详情可以参见上一篇博客的两个截图。

2. 从控制流图开始约束求解

  • oyente框架中,他们使用了z3对控制流图进行约束求解,即求出能够满足所有约束条件的每个变量的值。
  • z3z3是由微软公司开发的一个优秀的约束求解器,用它能求解满足约束条件的变量的值,这和以前接触过的线性规划求解器如Gurobi或者CPLEX类似。
  • 从上一节的控制流图中我们能发现,菱形表示的跳转条件左右着基本块跳转的方向。如果我们用变量表示跳转条件中的输入数据,再把变量组成数学表达式,此时跳转条件就转变成了约束条件。
  • 之后我们借助z3对约束条件进行求解,根据求解的结果我们就能够判断出基本块的跳转方向,如此一来我们就能模拟整个程序的执行。

2.1. z3的使用

  • 我们以z3的python实现为例介绍z3是如何使用的。

2.1.1. 基本用法

from z3 import *
x = Int('x')
y = Int('y')
solve(x>2, y< 10, x+2*y == 7)
  • 在上面的代码中,函数Int('x')在z3中创建了一个名为x的变量,之后调用了solve函数求在三个约束条件下的解,这三个约束条件分别是x>2y<10x+2*y==7,运行上面的代码,输出结果为
[y==0, x==7]

2.1.2. 布尔运算

from z3 import *
p = Bool('p')
q = Bool('q')
r = Bool('r')
solve(Implies(p,q), r==Not(q), Or(Not(p),r))

求解结果是

[q = False, p = False, r = True]

2.1.3 位向量

  • z3中我们可以创建固定长度的位向量,不如在下面的代码中BitVec('x',16)创建了一个长度为16位,名为x的变量
from z3 import *
x = BitVec('x', 16)
y = BitVec('y', 16)
solve(x + y > 5)
  • z3中畜类可以创建向量之外,也可以创建位向量常量。下面代码中的BitVecVal(-1,16)创建了一个长度为16位,值位1的位向量常量。
from z3 import *
a = BitVecVal(-1,16)
b = BitVecVal(65535, 16)
print(simplify(a==b))

2.1.4. 求解器

from z3 import *
x = Int('x')
y = Int('y')
s = Solver()
s.add(x > 10, y == x+2)
print(s)
print(s.check())
  • 上面的代码中,Solver()创建了一个通用的求解器,之后调用add()添加约束,调用check()判断是否有满足约束的解。如果有解则返回sat,如果没有则返回unsat

2.2. 使用z3进行约束求解

  • 对于智能合约而言,当执行到CALLDATASIZECALLDATALOAD等指令的时候,表示程序要获取外部的输入数据,此时我们用z3中的BitVec函数创建一个位向量来代替输入数据;当执行到LTEQ等指令时,此时我们用z3创建一个类似If(ULE(xx,xx),0,1)的表达式。

2.2.1. 生成数学表达式

  • 接下来我们以3.2节中的基本块1为例,看看如何把智能合约的指令转换成数学表达式。
  • 我们用变量stack=[]来表示以太坊虚拟机的栈,用变量memory={}来表示以太坊虚拟机的内存,用变量storage={}来表示storage。
  • 我们回到上面的基本块1,它的指令码如下
00000: PUSH1 0x80
00002: PUSH1 0x40
00004: MSTORE
00005: PUSH1 0x04
00007: CALLDATASIZE
00008: LT
00009: PUSH1 0x3e
0000b: JUMPI
  • PUSH指令是入栈指令,执行两次入栈后,stack的值为[0x80,0x40]
  • MSTORE执行之后,stack为空,memory的值为{0x40: 0x80}
  • CALLDATASIZE指令表示要获取输入数据的长度,我们使用z3中的BitVec("Id_size", 256),生成一个长度为256位,名为Id_size的变量来表示此时输入数据的长度。
  • LT指令用来比较0x04和变量Id_size的大小,如果0x04小于变量Id_size则值为0,否则值为1。使用z3转换成表达式则为:If(ULE(4, Id_size), 0, 1)
  • JUMPI是条件跳转指令,是否跳转到0x3e地址处取决于上一步中LT指令的结果,即表达式If(ULE(4, Id_size), 0, 1)的结果。如果部位0则跳转,否则不跳转,使用z3转换表达式则为:If(ULE(4,Id_size),0,1)!=0

2.2.2. 执行数学表达式

  • 执行上一节中生成的数学表达式伪代码如下:
from z3 import *
Id_size = BitVec("Id_size",256)
exp = If(ULE(4, Id_size), 0, 1) != 0
solver = Solver()
solver.add(exp)
if solver.check() == sat:
    print "jump to BasicBlock3"
else:
    print "error "
  • 在上面的代码中调用了solvercheck()方法来判断此表达式是否有解,如果返回值等于sat则表示表达式有解,也就是说LT的指令结果不为0,那么接下来可以跳转到基本块3。
  • 基本块1之后有两条分支,如果满足判断条件则跳转到基本块3,不满足则跳转到基本块2。但在上面的代码中,当check()方法的返回值不等于sat时,我们并没有跳转到基本块2,而是直接输出错误,这是因为当条件表达式无解时,继续向下执行没有任何意义。
  • 我们只需要对条件表达式取反,然后再判断取反后的表达式是否有解,有解就跳转到基本块2。
Id_size = BitVec("Id_size",256)
exp = If(ULE(4, Id_size), 0, 1) != 0
negated_exp = Not(If(ULE(4, Id_size), 0, 1) != 0)
solver = Solver()
solver.push()
solver.add(exp)
if solver.check() == sat:
    print "jump to BasicBlock3"
else:
    print "error"
solver.pop()
solver.push()
solver.add(negated_exp)
if solver.check() == sat:
    print "falls to BasicBlock2"
else:
    print "error"

3.full_sym_exec()

def full_sym_exec():
    # executing, starting from beginning
    path_conditions_and_vars = {
     "path_condition" : []}
    global_state = get_init_global_state(path_conditions_and_vars)
    analysis = init_analysis()
    params = Parameter(path_conditions_and_vars=path_conditions_and_vars, global_state=global_state, analysis=analysis)
    if g_src_map:
        start_block_to_func_sig = get_start_block_to_func_sig()
    return sym_exec_block(params, 0, 0, 0, -1, 'fallback')

3.1. get_init_global_state()

def get_init_global_state(path_conditions_and_vars):
    global_state = {
     "balance" : {
     }, "pc": 0}
    init_is = init_ia = deposited_value = sender_address = receiver_address = gas_price = origin = currentCoinbase = currentNumber = currentDifficulty = currentGasLimit = callData = None
	# INPUT_STATE 指的是假设链的状态,这里的默认值是False
    if global_params.INPUT_STATE:
        with open('state.json') as f:
            state = json.loads(f.read())
            if state["Is"]["balance"]:
                init_is = int(state["Is"]["balance"], 16)
            if state["Ia"]["balance"]:
                init_ia = int(state["Ia"]["balance"], 16)
            if state["exec"]["value"]:
                deposited_value = 0
            if state["Is"]["address"]:
                sender_address = int(state["Is"]["address"], 16)
            if state["Ia"]["address"]:
                receiver_address = int(state["Ia"]["address"], 16)
            if state["exec"]["gasPrice"]:
                gas_price = int(state["exec"]["gasPrice"], 16)
            if state["exec"]["origin"]:
                origin = int(state["exec"]["origin"], 16)
            if state["env"]["currentCoinbase"]:
                currentCoinbase = int(state["env"]["currentCoinbase"], 16)
            if state["env"]["currentNumber"]:
                currentNumber = int(state["env"]["currentNumber"], 16)
            if state["env"]["currentDifficulty"]:
                currentDifficulty = int(state["env"]["currentDifficulty"], 16)
            if state["env"]["currentGasLimit"]:
                currentGasLimit = int(state["env"]["currentGasLimit"], 16)

    # for some weird reason these 3 vars are stored in path_conditions insteaad of global_state
    else:
    	# 定义BitVec型的变量,作为CALLDATASIZE中可能传入的变量
        sender_address = BitVec("Is", 256)
        receiver_address = BitVec("Ia", 256)
        deposited_value = BitVec("Iv", 256)
        init_is = BitVec("init_Is", 256)
        init_ia = BitVec("init_Ia", 256)
	
	# 对path_conditions_and_vars进行赋值
    path_conditions_and_vars["Is"] = sender_address
    path_conditions_and_vars["Ia"] = receiver_address
    path_conditions_and_vars["Iv"] = deposited_value

	# 先设定约束,deposited_value需要大于0
    constraint = (deposited_value >= BitVecVal(0, 256))
    path_conditions_and_vars["path_condition"].append(constraint)
    # 发送者的钱要大于储蓄才能发???
    constraint = (init_is >= deposited_value)
    path_conditions_and_vars["path_condition"].append(constraint)
    # 接收地址的值需要大于0
    constraint = (init_ia >= BitVecVal(0, 256))
    path_conditions_and_vars["path_condition"].append(constraint)

    # update the balances of the "caller" and "callee"
	# 更新发送者和接受者的储值
    global_state["balance"]["Is"] = (init_is - deposited_value)
    global_state["balance"]["Ia"] = (init_ia + deposited_value)

	# 下面的值原先都为None,由gen_xxx 指定一个变量名
	# 如gas_price就被制定了变量名Ip
    if not gas_price:
        new_var_name = gen.gen_gas_price_var()
        gas_price = BitVec(new_var_name, 256)
        path_conditions_and_vars[new_var_name] = gas_price

    if not origin:
        new_var_name = gen.gen_origin_var()
        origin = BitVec(new_var_name, 256)
        path_conditions_and_vars[new_var_name] = origin

    if not currentCoinbase:
        new_var_name = "IH_c"
        currentCoinbase = BitVec(new_var_name, 256)
        path_conditions_and_vars[new_var_name] = currentCoinbase

    if not currentNumber:
        new_var_name = "IH_i"
        currentNumber = BitVec(new_var_name, 256)
        path_conditions_and_vars[new_var_name] = currentNumber

    if not currentDifficulty:
        new_var_name = "IH_d"
        currentDifficulty = BitVec(new_var_name, 256)
        path_conditions_and_vars[new_var_name] = currentDifficulty

    if not currentGasLimit:
        new_var_name = "IH_l"
        currentGasLimit = BitVec(new_var_name, 256)
        path_conditions_and_vars[new_var_name] = currentGasLimit

    new_var_name = "IH_s"
    currentTimestamp = BitVec(new_var_name, 256)
    path_conditions_and_vars[new_var_name] = currentTimestamp

    # the state of the current current contract
    if "Ia" not in global_state:
        global_state["Ia"] = {
     }
    global_state["miu_i"] = 0
    global_state["value"] = deposited_value
    global_state["sender_address"] = sender_address
    global_state["receiver_address"] = receiver_address
    global_state["gas_price"] = gas_price
    global_state["origin"] = origin
    global_state["currentCoinbase"] = currentCoinbase
    global_state["currentTimestamp"] = currentTimestamp
    global_state["currentNumber"] = currentNumber
    global_state["currentDifficulty"] = currentDifficulty
    global_state["currentGasLimit"] = currentGasLimit

    return global_state
  • path_conditions_and_vars指的是在block跳转的时候可能会调用的变量和需要处理的约束。
  • global_state是在正常block执行的时候就有可能会调用的变量。

3.2. init_analysis

def init_analysis():
    analysis = {
     
        "gas": 0,
        "gas_mem": 0,
        "money_flow": [("Is", "Ia", "Iv")],  # (source, destination, amount)
        "reentrancy_bug":[],
        "money_concurrency_bug": [],
        "time_dependency_bug": {
     }
    }
    return analysis
  • 这里我们能看到,Is对应的是sourceIa对应的是destinationIv对应的是amount

3.3. Parameter

    def __init__(self, **kwargs):
        attr_defaults = {
     
            "stack": [],
            "calls": [],
            "memory": [],
            "visited": [],
            "overflow_pcs": [],
            "mem": {
     },
            "analysis": {
     },
            "sha3_list": {
     },
            "global_state": {
     },
            "path_conditions_and_vars": {
     }
        }
        for (attr, default) in six.iteritems(attr_defaults):
            setattr(self, attr, kwargs.get(attr, default))
  • 这里的six是一个兼容性的库,你可以直接认为是iteritems
  • setattr可以参考下面的小案例
#为指定属性设置属性值
setattr(c, 'detail', 'python接口自动化')
setattr(c, 'view_times', 32)
#输出重新设置后的属性值
print(c.detail)    #python接口自动化
print(c.view_times)  #32
  • kwargs.get(attr, default)遍历的结果是:
[]
[]
[]
[]
[]
{
     }
{
     'gas': 0, 'gas_mem': 0, 'money_flow': [('Is', 'Ia', 'Iv')], 'reentrancy_bug': [], 'money_concurrency_bug': [], 'time_dependency_bug': {
     }}
{
     }
{
     'balance': {
     'Is': init_Is - Iv, 'Ia': init_Ia + Iv}, 'pc': 0, 'Ia': {
     }, 'miu_i': 0, 'value': Iv, 'sender_address': Is, 'receiver_address': Ia, 'gas_price': Ip, 'origin': Io, 'currentCoinbase': IH_c, 'currentTimestamp': IH_s, 'currentNumber': IH_i, 'currentDifficulty': IH_d, 'currentGasLimit': IH_l}
{
     'path_condition': [0 <= Iv, init_Is >= Iv, 0 <= init_Ia], 'Is': Is, 'Ia': Ia, 'Iv': Iv, 'Ip': Ip, 'Io': Io, 'IH_c': IH_c, 'IH_i': IH_i, 'IH_d': IH_d, 'IH_l': IH_l, 'IH_s': IH_s}
  • 这其中的每一个都对应着上面的stackcallsmemory等…

3.4. get_start_block_to_func_sig

def get_start_block_to_func_sig():
    state = 0
    func_sig = None
    for pc, instr in six.iteritems(instructions):
        if state == 0 and instr.startswith('PUSH4'):
            state += 1
            func_sig = instr.split(' ')[1][2:]
        elif state == 1 and instr.startswith('EQ'):
            state += 1
        elif state == 2 and instr.startswith('PUSH'):
            state = 0
            pc = instr.split(' ')[1]
            pc = int(pc, 16)
            start_block_to_func_sig[pc] = func_sig
        else:
            state = 0
    return start_block_to_func_sig
  • 这一个函数有点看不懂是什么意思,大概的感觉是遍历整个指令集,找到起始为PUSH4,且后一位是EQ且再后一位是PUSH的,然后start_block_to_func_sig就记录下func_sig
  • 例如下:
    • 54 PUSH4 => 0x75278362
    • 59 EQ
    • 60 PUSH2 => 0x4e
  • 则就会出现start_block_to_func_sig[60]=78(0x4e)
  • SODA: A Generic Online Detection Framework for
    Smart Contracts在Listing 2有提到
PUSH4 0x06fdde03
EQ
PUSH2 0x00e2
JUMPI

P2 searches the contract bytecode for the instruction sequence: PUSH4 x; EQ; PUSH2 y; JUMPI. If an instruction sequence is found, we obtain an encoded function id, x.

  • 这个是在收集函数的id,因为PUSH4后面的那个x其实就是函数的id,收集函数其实就可以认为在收集block的逻辑。

3.5. sym_exec_block

  • 这边传入的初始参数是(params, 0, 0, 0, -1, 'fallback')
# Symbolically executing a block from the start address
# 我们现在实际上已经获得了block和边了,但是对于block之间连续的逻辑,我们需要做一个深度优先遍历
# 所以你看的 sys_exec_block 会是一个递归函数
def sym_exec_block(params, block, pre_block, depth, func_call, current_func_name):
    global solver
    global visited_edges
    global money_flow_all_paths
    global path_conditions
    global global_problematic_pcs
    global all_gs
    global results
    global g_src_map

	# 对已经访问过的进行标记
    visited = params.visited
    # 作为符号化执行的虚拟出来的栈
    stack = params.stack
    # ???
    mem = params.mem
    # 符号化执行虚拟出来的内存
    memory = params.memory
    # 这是在上面定义的一些链的常量(主要是z3)
    global_state = params.global_state
    # ???
    sha3_list = params.sha3_list
    # 用于填充block与block之间的中间条件以及变量
    path_conditions_and_vars = params.path_conditions_and_vars
    # 代表着分析的j结果
    analysis = params.analysis
    # ???
    calls = params.calls
    # ???
    overflow_pcs = params.overflow_pcs

	
    Edge = namedtuple("Edge", ["v1", "v2"]) # Factory Function for tuples is used as dictionary key
    if block < 0:
        log.debug("UNKNOWN JUMP ADDRESS. TERMINATING THIS PATH")
        return ["ERROR"]

    log.debug("Reach block address %d \n", block)

	# 如果存在source_map
    if g_src_map:
    	# 如果block在起始block,或者在函数清单内
        if block in start_block_to_func_sig:
        	
            func_sig = start_block_to_func_sig[block]
            current_func_name = g_src_map.sig_to_func[func_sig]
            pattern = r'(\w[\w\d_]*)\((.*)\)$'
            match = re.match(pattern, current_func_name)
            if match:
                current_func_name =  list(match.groups())[0]

	
    current_edge = Edge(pre_block, block)
    if current_edge in visited_edges:
        updated_count_number = visited_edges[current_edge] + 1
        visited_edges.update({
     current_edge: updated_count_number})
     # 如果当前的edges没有被visited过,则更新
    else:
        visited_edges.update({
     current_edge: 1})
	# 如果这一个edges大于了循环的最高限制
    if visited_edges[current_edge] > global_params.LOOP_LIMIT:
        log.debug("Overcome a number of loop limit. Terminating this path ...")
        return stack
	# 计算当前的gas,如果大于了限制,则返回stack
    current_gas_used = analysis["gas"]
    if current_gas_used > global_params.GAS_LIMIT:
        log.debug("Run out of gas. Terminating this path ... ")
        return stack

    # Execute every instruction, one at a time
    try:
    	# 获取当前block所有的指令
        block_ins = vertices[block].get_instructions()
    except KeyError:
        log.debug("This path results in an exception, possibly an invalid jump address")
        return ["ERROR"]

	# 循环执行当前block的指令,所有的符号化执行的内容全部都在sym_exec_ins函数中
    for instr in block_ins:
        sym_exec_ins(params, block, instr, func_call, current_func_name)

    # Mark that this basic block in the visited blocks
    # visited中加入此block
    visited.append(block)
    depth += 1

	# 把之前添加的一些bug结果进行汇总
    reentrancy_all_paths.append(analysis["reentrancy_bug"])
    if analysis["money_flow"] not in money_flow_all_paths:
        global_problematic_pcs["money_concurrency_bug"].append(analysis["money_concurrency_bug"])
        money_flow_all_paths.append(analysis["money_flow"])
        path_conditions.append(path_conditions_and_vars["path_condition"])
        global_problematic_pcs["time_dependency_bug"].append(analysis["time_dependency_bug"])
        all_gs.append(copy_global_values(global_state))

    # Go to next Basic Block(s)
    # 然后前往下一个block
    # 如果这个block的类型是terminal 或者 递归的深度大于最大深度限制了
    if jump_type[block] == "terminal" or depth > global_params.DEPTH_LIMIT:
        global total_no_of_paths
        global no_of_test_cases
		
        total_no_of_paths += 1
		# 如果要求生成测试用例,则...
        if global_params.GENERATE_TEST_CASES:
            try:
                model = solver.model()
                no_of_test_cases += 1
                filename = "test%s.otest" % no_of_test_cases
                with open(filename, 'w') as f:
                    for variable in model.decls():
                        f.write(str(variable) + " = " + str(model[variable]) + "\n")
                if os.stat(filename).st_size == 0:
                    os.remove(filename)
                    no_of_test_cases -= 1
            except Exception as e:
                pass

        log.debug("TERMINATING A PATH ...")
        # 显示结果
        display_analysis(analysis)
        if is_testing_evm():
            compare_storage_and_gas_unit_test(global_state, analysis)
	# 如果是没有条件语句的跳转
    elif jump_type[block] == "unconditional":  # executing "JUMP"
    	# 继任者 = 当前block跳转的目标
        successor = vertices[block].get_jump_target()
        # 新的参数
        new_params = params.copy()
        # 获取新的program counter
        new_params.global_state["pc"] = successor
        if g_src_map:
        	# 通过program counter和之前的source map获取源码
            source_code = g_src_map.get_source_code(global_state['pc'])
            # 不太懂
            if source_code in g_src_map.func_call_names:
                func_call = global_state['pc']
        sym_exec_block(new_params, successor, block, depth, func_call, current_func_name)
     # 如果跳转类型是fall to,即什么都不做
    elif jump_type[block] == "falls_to":  # just follow to the next basic block
        successor = vertices[block].get_falls_to()
        new_params = params.copy()
        new_params.global_state["pc"] = successor
        sym_exec_block(new_params, successor, block, depth, func_call, current_func_name)
    # 如果跳转类型是条件跳转
    elif jump_type[block] == "conditional":  # executing "JUMPI"

        # A choice point, we proceed with depth first search
		# 则先获取分支的表达式
        branch_expression = vertices[block].get_branch_expression()

        log.debug("Branch expression: " + str(branch_expression))
		# 设置solver的一个边界
        solver.push()  # SET A BOUNDARY FOR SOLVER
        # 给solver增加一个边界表达式
        solver.add(branch_expression)
		#  下面的这一部分是对JUMPI的条件为true检查
        try:
        	# 如果solver检测处有不满足的地方
            if solver.check() == unsat:
            	# 则返回有不可解的路径
                log.debug("INFEASIBLE PATH DETECTED")
            else:
            	# 则跳转到下一个目标
                left_branch = vertices[block].get_jump_target()
                new_params = params.copy()
                new_params.global_state["pc"] = left_branch
                # 在path_...的变量中加入这一个分支的expression
                new_params.path_conditions_and_vars["path_condition"].append(branch_expression)
                
                last_idx = len(new_params.path_conditions_and_vars["path_condition"]) - 1
                # 定位上一个inx发生的bug并保存
                new_params.analysis["time_dependency_bug"][last_idx] = global_state["pc"]
                # 继续进入下一个block
                sym_exec_block(new_params, left_branch, block, depth, func_call, current_func_name)
        except TimeoutError:
            raise
        except Exception as e:
            if global_params.DEBUG_MODE:
                traceback.print_exc()
		
		# 下面的条件是对JUMPI为false条件的检查
        solver.pop()  # POP SOLVER CONTEXT

        solver.push()  # SET A BOUNDARY FOR SOLVER
        negated_branch_expression = Not(branch_expression)
        solver.add(negated_branch_expression)

        log.debug("Negated branch expression: " + str(negated_branch_expression))

        try:
            if solver.check() == unsat:
                # Note that this check can be optimized. I.e. if the previous check succeeds,
                # no need to check for the negated condition, but we can immediately go into
                # the else branch
                log.debug("INFEASIBLE PATH DETECTED")
            else:
                right_branch = vertices[block].get_falls_to()
                new_params = params.copy()
                new_params.global_state["pc"] = right_branch
                new_params.path_conditions_and_vars["path_condition"].append(negated_branch_expression)
                last_idx = len(new_params.path_conditions_and_vars["path_condition"]) - 1
                new_params.analysis["time_dependency_bug"][last_idx] = global_state["pc"]
                sym_exec_block(new_params, right_branch, block, depth, func_call, current_func_name)
        except TimeoutError:
            raise
        except Exception as e:
            if global_params.DEBUG_MODE:
                traceback.print_exc()
        solver.pop()  # POP SOLVER CONTEXT
        updated_count_number = visited_edges[current_edge] - 1
        visited_edges.update({
     current_edge: updated_count_number})
    else:
        updated_count_number = visited_edges[current_edge] - 1
        visited_edges.update({
     current_edge: updated_count_number})
        raise Exception('Unknown Jump-Type')
  • 介绍EVM中跳转的博客在这里:https://blog.csdn.net/Programmer_CJC/article/details/80217672
  • 对代码的解释基本都在注释中。

参考文档

  • 区块链安全-以太坊智能合约静态分析: https://blogs.360.cn/post/staticAnalysis_of_smartContract.html

你可能感兴趣的:(区块链,联邦学习,区块链)