Solidity 逆向分析入门学习

本文介绍了作者学习智能合约逆向分析的过程,通过两个实例展示了如何对智能合约的字节码进行逆向,理解其功能。第一个简单合约涉及set和get函数,逆向后理解了函数签名和执行逻辑。第二个复杂例子来自2019数字经济jojo题目,逆向分析了包括payforflag、gift等多个函数,加深了对合约安全和逆向技巧的理解。作者计划进一步提升逆向分析和智能合约安全性方面的技能。
摘要由CSDN通过智能技术生成

前言

智能合约学到现在也学了差不多2周了,也接触了一些智能合约常见的攻击手段,跟着网上的文章也学习了很多。学习的文章给的直接都是Solidity的源代码,但是:

智能合约大多数代码不公开源码,公开字节码,所以需要使用逆向工具或者人工进行逆向分析。

因此以后要强迫自己去逆向分析代码,提高自己的智能合约逆向分析能力。
但是一开始看逆向得到的伪代码还是比较迷的,因此跟着网上的一些最基本的入门的文章学习了一下,大致的理解这些逆向得到的伪代码,先有1。只要有了1,接下来就是靠自己不断的学习,不断逆向来提高分析能力了。

简单的例子

一个简单的合约:


pragma solidity ^0.4.0;

contract Data {
    uint De;
    
    function set(uint x) public {
        De = x;
    }
    
    function get() public constant returns (uint) {
        return De;
    }
}

deploy后,去以太坊浏览器上看一下这个合约,发现字节码:
在这里插入图片描述
利用decomplie工具进行逆向:
给出了方法的签名和原型:
在这里插入图片描述
因为原本合约的代码就很简单,因此逆向得到的也很简单,加上了注释分析,比较容易理解:

contract Contract {
    function main() {
        //分配内存空间
        memory[0x40:0x60] = 0x80;
        //判断函数签名是否为4字节
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
        //逻辑就不管了,总之这样计算后是取前4个字节,即函数的签名。
        var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;
    
        if (var0 == 0x60fe47b1) {   //set函数的函数签名
            // Dispatch table entry for set(uint256)
            var var1 = msg.value;
            //表示不接受 `msg.value`,即这个函数不是payable修饰的。
            if (var1) { revert(memory[0x00:0x00]); }
        
            var1 = 0x62; //92
            var var2 = msg.data[0x04:0x24]; //4 ~ 36
            set(var2);  //调用set函数设置值
            stop();   //stop表示该函数无返回值
        } else if (var0 == 0x6d4ce63c) {    //get函数的函数签名
            // Dispatch table entry for get()
            var1 = msg.value;
        
            if (var1) { revert(memory[0x00:0x00]); }
        
            var1 = 0x76; //118
            var1 = get();
            var temp0 = memory[0x40:0x60];
            memory[temp0:temp0 + 0x20] = var1;
            var temp1 = memory[0x40:0x60];
            //有return表示函数有返回值
            return memory[temp1:temp1 + temp0 - temp1 + 0x20];
        } else { revert(memory[0x00:0x00]); }
    }
    
    function set(var arg0) {
        storage[0x00] = arg0;
    }
    
    function get() returns (var r0) {  return storage[0x00]; }
}

复杂的例子

这个例子我用的是前几天做的2019 数字经济 jojo这个题目的例子,源码如下:

pragma solidity ^0.4.24;

contract jojo {
    mapping(address => uint) public balanceOf;
    mapping(address => uint) public gift;
    address owner;

    constructor()public{
        owner = msg.sender;
    }

    event SendFlag(string b64email);

    function payforflag(string b64email) public {
        require(balanceOf[msg.sender] >= 100000);
        emit SendFlag(b64email);
    }

    function jojogame() payable{
        uint geteth = msg.value / 1000000000000000000;  //ether
        balanceOf[msg.sender] += geteth;
    }

    function gift() public {
        assert(gift[msg.sender] == 0);
        balanceOf[msg.sender] += 100;
        gift[msg.sender] = 1;
    }

    function transfer(address to,uint value) public{
        assert(balanceOf[msg.sender] >= value);
        balanceOf[msg.sender] -= value;
        balanceOf[to] += value;
    }

}

发布一下合约,然后逆向出来,最终是这样,加了很多注释,主要是自己也在学:

contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
        //函数签名
        var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;
    
        if (var0 == 0x06211f80) {
        	//这个函数是payable的
            // Dispatch table entry for 0x06211f80 (unknown)
            var var1 = 0x0084;
            func_0193();
            stop();
        } else if (var0 == 0x24b04905) {
            // Dispatch table entry for gift()
            var1 = msg.value;
            //非payable
            if (var1) { revert(memory[0x00:0x00]); }
        
            var1 = 0x0084;
            gift();
            //无返回值
            stop();
        } else if (var0 == 0x6bc344bc) {
            // Dispatch table entry for payforflag(string)
            var1 = msg.value;
        	//非payable
            if (var1) { revert(memory[0x00:0x00]); }
        	//temp0 = 0x80
            var temp0 = memory[0x40:0x60];
            //temp1 应该是 偏移量
            var temp1 = msg.data[0x04:0x24];
            //取这个动态类型的那个量,感觉可能是bytes,string或者动态数组,但是似乎存的是length,而不是动态数组的个数
            //因此应该是bytes 或者 string
            var temp2 = msg.data[temp1 + 0x04:temp1 + 0x04 + 0x20];
            //memory[0x40:0x60] = 0x80 + (length + 31) /32 * 32 + 32
            //0x40:0x60看的话,是这个string或者bytes
            //存的是memory存放完这个动态的东西之后的32整数个起始地址。(包括length和内容)
            memory[0x40:0x60] = temp0 + (temp2 + 0x1f) / 0x20 * 0x20 + 0x20;
            //memory[0x80:0x80+0x20] = temp2;放的length
            memory[temp0:temp0 + 0x20] = temp2;
            var1 = 0x0084;
            //msg.data[temp1 + 0x24:temp1 + 0x24 + temp2];取length长度的所有字符,即内容。
            memory[temp0 + 0x20:temp0 + 0x20 + temp2] = msg.data[temp1 + 0x24:temp1 + 0x24 + temp2];
            var var2 = temp0;
            payforflag(var2);
            stop();
        } else if (var0 == 0x70a08231) {
            // Dispatch table entry for balanceOf(address)
            var1 = msg.value;
            //非payable
            if (var1) { revert(memory[0x00:0x00]); }
        
            var1 = 0x0122;
            //& 0xffffffffffffffffffffffffffffffffffffffff说明这是一个地址值 
            var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
            var2 = balanceOf(var2);
        
        label_0122:
            var temp3 = memory[0x40:0x60];
            memory[temp3:temp3 + 0x20] = var2;
            var temp4 = memory[0x40:0x60];
            return memory[temp4:temp4 + temp3 - temp4 + 0x20];
            //return var2
        } else if (var0 == 0xa9059cbb) {
            // Dispatch table entry for transfer(address,uint256)
            var1 = msg.value;
        	//非payable
            if (var1) { revert(memory[0x00:0x00]); }
        	
            var1 = 0x0084;
            var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
            var var3 = msg.data[0x24:0x44];
            transfer(var2, var3);
            stop();
        } else if (var0 == 0xcbfc4bce) {
            // Dispatch table entry for gift(address)
            var1 = msg.value;
        
            if (var1) { revert(memory[0x00:0x00]); }
        
            var1 = 0x0122;
            var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
            var2 = gift(var2);
            goto label_0122;
        } else { revert(memory[0x00:0x00]); }
    }
    
    function func_0193() {
        memory[0x00:0x20] = msg.sender;
        memory[0x20:0x40] = 0x00;
        var temp0 = keccak256(memory[0x00:0x40]);
        storage[temp0] = msg.value / 0x0de0b6b3a7640000 + storage[temp0];  
    }
    
    function gift() {
        memory[0x00:0x20] = msg.sender;
        memory[0x20:0x40] = 0x01;

        //
        //slot 1好像是一个映射,mapping(address => uint) gift
        //require(!gift[msg.sender])
        if (storage[keccak256(memory[0x00:0x40])]) { assert(); }
    
        memory[0x00:0x20] = msg.sender;
        memory[0x20:0x40] = 0x00;
        var temp0 = keccak256(memory[0x00:0x40]);
        //slot 0好像是个映射,mapping(address => uint) balance
        //balance[msg.sender] = balance[msg.sender] + 100;
        storage[temp0] = storage[temp0] + 0x64;
        memory[0x20:0x40] = 0x01;
        //gift[msg.sender] = 1;
        storage[keccak256(memory[0x00:0x40])] = 0x01;

        /*
        require(!gift[msg.sender]);
        balance[msg.sender] = balance[msg.sender] + 100;
        gift[msg.sender] = 1;
        */
    }
    
    function payforflag(var arg0) {
        memory[0x00:0x20] = msg.sender;
        memory[0x20:0x40] = 0x00;
    	//require(balance[msg.sender] <= 100000);
        if (0x0186a0 > storage[keccak256(memory[0x00:0x40])]) { revert(memory[0x00:0x00]); }
    
        var var0 = 0x7c2413bb49085e565f72ec50a1fb0460b69cf327e0b0d882980385b356239ea5;
        var temp0 = arg0;
        var var1 = temp0;//var1 = 0x80
        var temp1 = memory[0x40:0x60];  //temp1就是memory不存传入的量之后的32整数倍的地址
        var var2 = temp1; 
        var var3 = var2; 
        var temp2 = var3 + 0x20; //temp2 = memory[0x40:0x60] + 0x20
        
        //不妨设memory不存传入的量之后的32整数倍的地址是a,那么
        //memory[a:a + 0x20] = 0x20
        memory[var3:var3 + 0x20] = temp2 - var3; 
        //memory[a+0x20:a+0x40] = 内容的长度,length
        //这里temp2 = a + 0x20
        memory[temp2:temp2 + 0x20] = memory[var1:var1 + 0x20];
        //再往后放0x20
        var var4 = temp2 + 0x20;
        //var5 = 0x80 + 0x20
        var var5 = var1 + 0x20;
        //var6 也是内容的长度,length
        var var6 = memory[var1:var1 + 0x20];
        //var7 也是内容的长度,length
        var var7 = var6;
        //var8 = a + 0x20 + 0x20
        var var8 = var4;
        //var9 = 0x80 + 0x20
        var var9 = var5;
        var var10 = 0x00;
    	
    	//var7是内容的长度
    	//好吧下面我就看不懂了,鬼知道这到底是一团什么东西。。。给孩子看傻了,一看代码tm的
    	//原来是emit。。。。草!!!!!!!!!!!!
        if (var10 >= var7) {
        label_026C:
            var temp3 = var6;
            var4 = temp3 + var4;
            var5 = temp3 & 0x1f;
        
            if (!var5) {
                var temp4 = memory[0x40:0x60];
                log(memory[temp4:temp4 + var4 - temp4], [stack[-6]]);
                return;
            } else {
                var temp5 = var5;
                var temp6 = var4 - temp5;
                memory[temp6:temp6 + 0x20] = ~(0x0100 ** (0x20 - temp5) - 0x01) & memory[temp6:temp6 + 0x20];
                var temp7 = memory[0x40:0x60];
                log(memory[temp7:temp7 + (temp6 + 0x20) - temp7], [stack[-6]]);
                return;
            }
        } else {
        label_025D:
        	//temp8 = 0x00
            var temp8 = var10;
            //memory[a + 0x20 + 0x20:a + 0x20 + 0x20+0x20] = memory[0x80 + 0x20:0x80 + 0x20+0x20];
            memory[temp8 + var8:temp8 + var8 + 0x20] = memory[temp8 + var9:temp8 + var9 + 0x20];
            //var10 = 0x20;
            var10 = temp8 + 0x20;
        
            if (var10 >= var7) { goto label_026C; }
            else { goto label_025D; }
        }
    }
    
    function balanceOf(var arg0) returns (var arg0) {
        memory[0x20:0x40] = 0x00;
        memory[0x00:0x20] = arg0;
        return storage[keccak256(memory[0x00:0x40])];

        /*
        return balance[arg0];
        */
    }
    
    function transfer(var arg0, var arg1) {
        memory[0x00:0x20] = msg.sender;
        memory[0x20:0x40] = 0x00;
        //require(balance[msg.sender] >= arg1);
        if (arg1 > storage[keccak256(memory[0x00:0x40])]) { assert(); }
    
        memory[0x00:0x20] = msg.sender;
        memory[0x20:0x40] = 0x00;
        var temp0 = keccak256(memory[0x00:0x40]);
        var temp1 = arg1;
        //balance[msg.sender] = balance[msg.sender] - arg1;
        storage[temp0] = storage[temp0] - temp1;
        memory[0x00:0x20] = arg0 & 0xffffffffffffffffffffffffffffffffffffffff;
        var temp2 = keccak256(memory[0x00:0x40]);
        storage[temp2] = temp1 + storage[temp2];
    }
    
    function gift(var arg0) returns (var arg0) {
        memory[0x20:0x40] = 0x01;
        memory[0x00:0x20] = arg0;
        //return gift[arg0];
        return storage[keccak256(memory[0x00:0x40])];
    }
}

除了payforflag那个函数有些看不懂,后来才发现这样的其实题目本身都给了:

pragma solidity ^0.4.24;

contract jojo {
    mapping(address => uint) public balanceOf;
    mapping(address => uint) public gift;
    address owner;
        
    constructor()public{
        owner = msg.sender;
    }
    
    event SendFlag(string b64email);
    
    function payforflag(string b64email) public {
        require(balanceOf[msg.sender] >= 100000);
        emit SendFlag(b64email);
    }
............

总结

又水了一篇博客,主要还是记录下自己的关于区块链的第一个和第二个逆向分析的例子,之后自己就要开始提升逆向分析能力和学习区块链的各种安全方面的问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值