零时科技|智能合约安全系列文章反汇编·下篇 – 作者:noneage

前言

上篇我们详细分析了智能合约反汇编后的代码内容,包括多个反汇编指令的含义,数据在栈中的存储方式,并通过上下文关联关系梳理代码逻辑。本篇我们将继续分析上篇遗留的反汇编代码,通过上篇学习我们已对反汇编指令在栈和内存存储的有了一定了解,该篇我们将重点来分析反汇编指令表示的代码逻辑。

反汇编内容

合约源代码

pragma solidity ^0.4.24;

contract Tee {
    
    uint256 private c;

    function a() public returns (uint256) { self(2); }
    
    function b() public { c++; }

    function self(uint n) internal returns (uint256) {
        
        if (n <= 1) { return 1; }

        return n * self(n - 1);
    }
}

上篇文章中,我们对下图中蓝色框中的内容进行了详细分析,本篇我们分析红色框中的内容以及之后的反汇编指令。

1610524510_5ffea75ea6a1885380e24.png!small?1610524510773

反汇编分析

我们从label_004E开始分析,从反汇编上篇文章中可知,执行0x4e是上一步中判断后证明该函数签名是为a()函数,接下来我们来看0x4e中指令的主要含义:

(CALLVALUE)获取交易中的转账金额,(DUP1)复制转账金额值到栈顶,(ISZERO)把栈顶转账金额值出栈,如果该值是0则把1入栈,否则把0入栈。(JUMPI)这里如果转账金额值为0,(PUSH1 0x59)该段指令就会跳转到0x59;如果转账金额不为0,则顺序执行下一行指令。由此可知该段指令主要是为了判断a()函数是否存在转账操作。

执行完后,目前栈中就只存在一条数据:转账金额值

这里我们先来看label_004E顺序执行:

1610524524_5ffea76c06f42cdd12963.png!small?1610524524044

通过上图0055部分可以看出,PUSH1 0x00和DUP1指令在这里均无实际意义,这部分最终结果为停止执行,回滚状态。

接下来,我们来看跳转后0059指令内容:(pop)把栈顶值出栈,也就是转账金额值;(PUSH1 0x60和PUSH1 0x8a)将0x60和0x8a依次压入栈中;(JUMP)跳转到栈顶0x8a位置。

1610524529_5ffea771d1b9c24e3a4ca.png!small?1610524530048

如图008a指令处,依次压入0x00,0x94,0x02,0xab,目前栈中布局如下:

4:0xab
3:0x02
2:0x94
1:0x00
0:0x60

之后JUMP指令将跳转至00AB,如上图。该段指令中:(PUSH1 0x00,PUSH1 0x01)依次将0x00和0x01压入栈;(DUP3)复制当前栈中第三个值0x02放入栈顶;(GT)把栈顶两个值出栈,先出栈的值0x02大于后出栈的值0x01,把1入栈;(ISZERO)把栈顶值1出栈,该值不是0把0入栈;继续(ISZERO)把栈顶值1出栈,该值是0把1入栈;(JUMPI)这里栈顶值为1,(PUSH1 0xbe)跳转到0xbe。

这里注意压入的0x02就是a()函数中调用self(2)函数传入的值。

接下来我们对a()函数的内部操作进行一个全面的梳理

下图为a()函数调用self()函数并赋值后的反汇编指令代码逻辑图:

1610524540_5ffea77cb3c88ff6d87ef.png!small

上图的反汇编指令操作数值在栈中的布局如下所示:

以上两张图中指令执行逻辑均已进行标注,对于每个指令的操作含义就不一一介绍,我们直接来看分析之后,重点的指令操作。通过008A段中PUSH1 0x02将2压入栈中;00AB段中DUP3将2复制到栈顶,并利用GT指令将2和1进行了对比;00BE段中DUP4将2复制到栈顶,并利用SUB指令达到(2-1),最终得到相减的数值后继续跳转到00AB中对该值和1进行对比;接下来00B7段和00CD段没有实际意义,只是对一些数值进行调整;继续看00C8段中DUP3将复制到栈顶,并利用MUL指令将2和1(也就是2-1的值)进行相乘,并将所得值压入栈中,之后的0094段和0060段含义是将栈中数值进行调整,并将计算偏移量最终输出返回值。

故此我们可以得出a()函数的输入值后的代码逻辑为以下:

function a() public returns (uint256) { self(2); }
  
  function self(uint n) internal returns (uint256) {
        
        if (n <= 1) { return 1; }

        return n * self(n - 1);
  }

由于汇编指令较多,这里就不进行全面分析,需要深入学习的同学可移步反汇编二:反汇编2

总结

本篇文章我们分享了如何通过反汇编指令分析得到智能合约源代码逻辑,看似一段简单的智能合约代码,但反汇编出来的汇编指令却非常多。我们在之前的文章中也分享了通过分析反编译代码得到智能合约源代码逻辑,所以对于智能合约编译部署后的opcode,建议大家选择适合自己的逆向方式。

solidity智能合约逆向工具推荐:

https://ethervm.io/decompile
https://contract-library.com/
https://github.com/crytic/ida-evm
https://github.com/comaeio/porosity
https://github.com/meyer9/ethdasm

来源:freebuf.com 2020-12-24 10:05:54 by: noneage

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论