直接通过以太坊智能合约的Bytecode获取函数名称

  • Post author:
  • Post category:小程序

在实时追踪链上以太坊的智能合约创建的过程中,需要快速判断这个智能合约是否是ERC20或者ERC721的标准合约。

这就需要通过智能合约的Bytecode抓去他的函数签名,由于标准接口的函数签名是可以提前获取的,所以可以在Bytecode中暴力找到,也不必完全暴力,因为Solidity 编译出的智能合约的Bytecode的函数调用都是有规律的,可以把智能合约当成一个函数,以太坊虚拟机在接受用户的消息时候,按照他的基本模型去执行智能合约这个函数,把参数通过CALLDATA传入智能合约。以太坊基本模型:

state_{new} = eth(state_{old}, msg) \\ msg 里面有所有调用的信息,EVM会无脑的把智能合约当成一个大函数执行。智能合约会自己通过CALLDATA参数判断该调用自己的哪个函数,这个调用过程是有规律的。

CALLDATALOAD
PUSH1 e0
SHR 
DUP1     
PUSH4 715018a6
GT 
PUSH2 00a0
JUMPI
################################################   
DUP1
PUSH4 b87f137a
EQ    
PUSH2 03ba
JUMPI 
DUP1    
PUSH4 c3c8cd80
EQ  
PUSH2 03e3
JUMPI 
DUP1 
PUSH4 c9567bf9
EQ 
PUSH2 03fa
JUMPI 
DUP1 
PUSH4 ccfee5d6
EQ 
PUSH2 0411
JUMPI 

就是DUP1然后PUSH4 函数签名到堆栈,然后EQ,接着PUSH2,JUMPI。啥意思?就是EQ 判断栈上的两个数据是否相等,如果相等,就JUMP 到PUSH2 的偏移,就是改PC指针。全是这个模式,所以函数签名很容易匹配。

但是PUSH2去的偏移是RUNTIME的偏移和现在的通过Transaction input拿到的代码位置不一样,通过PUSH2找不到函数真正的位置,可能要去掉构造函数。这个问题我也没有搞明白。所以我就直接用Qiling 模拟运行了一下,通过运行智能合约找到它的名字和Symbol。

def find_functions(code):
    print('Starting to disassembly')
    #code = list(map(ord, code))
    #print(code)
    functions = []
    i = 0
    while i < len(code):
        opcode = code[i]
        if opcode == 0x63:  # push4
            print("%x" % opcode)
            value = code[i + 1:i + 5]
            # an awful heuristic below
            if i + 10 < len(code):
                # (dup*), eq, push2, jumpi
                # if re.match('.?\x14\x61..\x57', code[i:i + 10])
                for off in range(2):
                    if code[i + off + 5] == 0x14 \
                            and code[i + off + 6] == 0x61 \
                            and code[i + off + 9] == 0x57:
                        offset = code[i + off + 7] * 256 + code[i + off + 8]
                        #name = ''.join(map(chr, value))
                        #print(name)
                        print('Found function %s at offset %s' % (binascii.hexlify(value), offset))
                        functions.append((offset, binascii.hexlify(value)))
                        break
            i += 5
        elif 0x60 <= opcode < 0x80:  # push
            i += opcode - 0x5f
        i += 1
    return functions

这是我在网上找到的一段分析函数的代码,能跑但是有迷惑的地方,为什么要range(2)?大家使用的时候要自己判断。