源码:
contract ZT is ERC20("ZERO TOKEN", "ZT"){
//RULE 1
bytes32 constant RULE_WITHDRAW_WANT = keccak256(abi.encodePacked("withdraw"));
//RULE 2
bytes32 constant RULE_NONE_WANT = keccak256(abi.encodePacked("depositByValue"));
constructor()public{
_mint(msg.sender,10000000*10**18);
}
function depositByWant(uint _amount)external payable{
uint amount = _amount.mul(10**18);
require(msg.value>=amount,"you want to trick me?");
MkaheChange(msg.sender,amount,RULE_NONE_WANT);
}
function withdraw(uint _amount)external payable returns(bool){
uint amount = _amount.mul(10**18);
require(balanceOf(msg.sender)>=amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);
return MkaheChange(msg.sender,amount,RULE_WITHDRAW_WANT);
}
function MkaheChange(address to,uint amount,bytes32 ID)internal returns(bool){
if(ID==RULE_NONE_WANT)
{
_balances[msg.sender]=_balances[msg.sender].add(amount);
return true;
}else if(ID==RULE_WITHDRAW_WANT){
bool a;
(a,)=payable(to).call.value(amount)("");
require(a,"withdraw fail");
return true;
}
else{
return false;
}
}
fallback()external payable{
MkaheChange(
msg.sender,
msg.value,
RULE_NONE_WANT
);
}
}
contract ZTstakepool{
ZT token;
uint totalsupply;
string symbol;
mapping(address=>uint)internal workbalance;
mapping(address=>bool)internal passed;
struct userInfo{
uint amount;
uint duration;
uint startTime;
}
mapping(address=>userInfo)internal userDetails;
constructor()public{
token =new ZT();
symbol = "stTGT";
totalsupply = token.balanceOf(address(this));
}
function getDetails(address account)public view returns(userInfo memory){
return userDetails[account];
}
function workBalanceOf(address account)public view returns(uint){
bool pass=passed[account];
if(pass){
return workbalance[account];
}else{
return 0;
}
}
function Zt()public view returns(address){
return address(token);
}
function stake(uint amount,uint blocknumber)external{
require(blocknumber>=1,"At least 1 block");
userInfo storage user = userDetails[msg.sender];
user.startTime = block.number;
user.duration = blocknumber;
user.amount += amount;
token.transferFrom(msg.sender,address(this),amount*10**18);
workbalance[msg.sender] += blocknumber;
}
function unstake()external{
userInfo storage user = userDetails[msg.sender];
require(block.number>=user.startTime+user.duration,"you are in a hurry ");
passed[msg.sender] = true;
uint amount = user.amount;
user.amount = 0;
token.transfer(msg.sender,amount*10**18);
}
function swap(address from,address to,uint amount)external{
require(from==address(this)&&to==address(token));
uint balance = workBalanceOf(msg.sender);
require(balance>=amount,"exceed");
workbalance[msg.sender] -= amount;
token.transfer(msg.sender,amount*10**18);
}
}
contract setup{
ZTstakepool public stakePool;
ZT public erc20;
bool solve;
constructor()public{
stakePool =new ZTstakepool();
erc20 = ZT(payable(stakePool.Zt()));
}
function isSolved()public view returns(bool){
return solve;
}
function complete()public{
require(erc20.balanceOf(msg.sender)>=500000*10**18);
solve = true;
}
}
分析:
ZT是ERC20标准的token,ZTstakepool就像是一个工作池。纵观代码,问题出在stake、unstake、swap三个方法上。只要 passed[msg.sender] = true;
就可以执行swap,然而我们可以利用在调用stake前user.startTime+user.duration
都为0,满足unstake的require条件,先执行unstake拿到passed,再执行stake输入想要的blocknumber,最后直接swap.
攻击过程
先unstake
再stake
查看workbalance是否成功了
swap提取成ZT
去ZT合约里查看balance,看转移成功没有
执行setup 的complete
成功了!
flag拿到!!