题目地址:sepolia@0x053cd080A26CB03d5E6d2956CeBB31c56E7660CA
合约源码
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol)
pragma solidity 0.8.12;
import "./IERC20.sol";
import "./IERC20Metadata.sol";
import "./Context.sol";
//import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
//import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
//import "@openzeppelin/contracts/utils/Context.sol";
struct Coupon {
uint loankey;
uint256 amount;
address buser;
bytes reason;
}
struct Signature {
uint8 v;
bytes32[2] rs;
}
struct SignCoupon {
Coupon coupon;
Signature signature;
}
contract MyToken is Context, IERC20, IERC20Metadata {
mapping(address => uint256) public _balances;
mapping(address => uint) public _ebalances;
mapping(address => uint) public ethbalances;
mapping(address => mapping(address => uint256)) private _allowances;
mapping(address => uint) public _profited;
mapping(address => uint) public _auth_one;
mapping(address => uint) public _authd;
mapping(address => uint) public _loand;
mapping(address => uint) public _flag;
mapping(address => uint) public _depositd;
uint256 private _totalSupply;
string private _name;
string private _symbol;
address owner;
address backup;
uint secret;
uint tokenprice;
Coupon public c;
address public lala;
address public xixi;
//mid = bilibili uid
//b64email = base64(your email address)
//Don't leak your bilibili uid
//Gmail is ok. 163 and qq may have some problems.
event sendflag(string mid, string b64email);
event changeprice(uint secret_);
constructor(string memory name_, string memory symbol_, uint secret_) {
_name = name_;
_symbol = symbol_;
owner = msg.sender;
backup = msg.sender;
tokenprice = 6;
secret = secret_;
_mint(owner, 2233102400);
}
modifier onlyowner() {
require(msg.sender == owner);
_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
function deposit() public {
require(_depositd[msg.sender] == 0, "you can only deposit once");
_depositd[msg.sender] = 1;
ethbalances[msg.sender] += 1;
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
function setbackup() public onlyowner {
owner = backup;
}
function ownerbackdoor() public {
require(msg.sender == owner);
_mint(owner, 1000);
}
function auth1(uint pass_) public {
require(pass_ == secret, "auth fail");
require(_authd[msg.sender] == 0, "already authd");
_auth_one[msg.sender] += 1;
_authd[msg.sender] += 1;
}
function auth2(uint pass_) public {
uint pass = uint(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)));
require(pass == pass_, "password error, auth fail");
require(_auth_one[msg.sender] == 1, "need pre auth");
require(_authd[msg.sender] == 1, "already authd");
_authd[msg.sender] += 1;
}
function payforflag(string memory mid, string memory b64email) public {
require(_flag[msg.sender] == 2);
emit sendflag(mid, b64email);
}
function flashloan(SignCoupon calldata scoupon) public {
require(scoupon.coupon.loankey == 0, "loan key error");
require(msg.sender == address(this), "hacker get out");
Coupon memory coupon = scoupon.coupon;
Signature memory sig = scoupon.signature;
c=coupon;
require(_authd[scoupon.coupon.buser] == 2, "need pre auth");
require(_loand[scoupon.coupon.buser] == 0, "you have already loaned");
require(scoupon.coupon.amount <= 300, "loan amount error");
_loand[scoupon.coupon.buser] = 1;
_ebalances[scoupon.coupon.buser] += scoupon.coupon.amount;
}
function profit() public {
require(_profited[msg.sender] == 0);
_profited[msg.sender] += 1;
_transfer(owner, msg.sender, 1);
}
function borrow(uint amount) public {
require(amount == 1);
require(_profited[msg.sender] <= 1);
_profited[msg.sender] += 1;
_transfer(owner, msg.sender, amount);
}
function buy(uint amount) public {
require(amount <= 300, "max buy count is 300");
uint price;
uint ethmount = _ebalances[msg.sender];
if (ethmount < 10) {
price = 1000000;
} else if (ethmount >= 10 && ethmount <= 233) {
price = 10000;
} else {
price = 1;
}
uint payment = amount * price;
require(payment <= ethmount);
_ebalances[msg.sender] -= payment;
_transfer(owner, msg.sender, amount);
}
function sale(uint amount) public {
require(_balances[msg.sender] >= amount, "fail to sale");
uint earn = amount * tokenprice;
_transfer(msg.sender, owner, amount);
_ebalances[msg.sender] += earn;
}
function withdraw() public {
require(ethbalances[msg.sender] >= 1);
require(_ebalances[msg.sender] >= 1812);
payable(msg.sender).call{
value:100000000000000000 wei}("");
_ebalances[msg.sender] = 0;
_flag[msg.sender] += 1;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
require(msg.sender == owner); //不允许被owner以外调用
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
require(msg.sender == owner); //不允许被owner以外调用
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
require(msg.sender == owner); //不允许被owner以外调用
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {
}
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {
}
// debug param secret
function get_secret() public view returns (uint) {
require(msg.sender == owner);
return secret;
}
// debug param tokenprice
function get_price() public view returns (uint) {
return tokenprice;
}
// test need to be delete
function testborrowtwice(SignCoupon calldata scoupon) public {
require(scoupon.coupon.loankey == 2233);
MyToken(this).flashloan(scoupon);
}
// test need to be delete
function set_secret(uint secret_) public onlyowner {
secret = secret_;
emit changeprice(secret_);
}
}
0x01
区块链题目一般依赖于以太坊智能合约,智能合约可以理解为在以太坊上运行的程序,而编写智能合约常用语言为solidity
一般来说,我们从payforflag()函数入手,这里有个检验条件:_flag[msg.sender]==2
function payforflag(string memory mid, string memory b64email) public {
require(_flag[msg.sender] == 2);
emit sendflag(mid, b64email);
}
只有满足这个条件之后才能执行sendflag(mid,b64email);我们的最终目的是为了调用payforflag()函数,触发事件sendflag(mid,b64email); 在这里mid输入的是你的bili uid,b64email输入你邮箱的base64编码,成功触发事件后,会自动把flag发送到你的邮箱
接下来我们要找到使_flag[msg.sender]值变化的函数,_flag[msg.sender]初始值为0
function withdraw() public {
require(ethbalances[msg.sender] >= 1);
require(_ebalances[msg.sender] >= 1812);
payable(msg.sender).call{
value:100000000000000000 wei}("");
_ebalances[msg.sender] = 0;
_flag[msg.sender] += 1; //该函数执行一次_flag[msg.sender]值加1
}
而调用withdraw()函数的判断条件要满足:ethbalances[msg.sender] >= 1;_ebalances[msg.sender] >= 1812;而执行一次会使_ebalances[msg.sender] = 0;
0x02
因此我们的目标转到了满足ethbalances[msg.sender]和_ebalances[msg.sender]
第一个值ethbalances[msg.sender]只要满足等于1即可,而我们注意到deposit()函数可以使得ethbalances[msg.sender]值加1,故调用一次即可
function deposit() public {
require(_depositd[msg.sender] == 0, "you can only deposit once");
_depositd[msg.sender] = 1;
ethbalances[msg.sender] += 1;
}
第二个值_ebalances[msg.sender]>= 1812,我们发现_ebalances[msg.sender]与以下4个函数相关,其中_ebalances[msg.sender]与_balances[msg.sender]的转换关系是这道题的关键所在
function profit() public {
require(_profited[msg.sender] == 0);
_profited[msg.sender] += 1;
_transfer(owner, msg.sender, 1);
}
function borrow(uint amount) public {
require(amount == 1);
require(_profited[msg.sender] <= 1);
_profited[msg.sender] += 1;
_transfer(owner, msg.sender, amount);
}
function buy(uint amount) public {
//使用_ebalances[]换取_balances[]
require(amount <= 300, "max buy count is 300");
uint price;
uint ethmount = _ebalances[msg.sender];
if (ethmount < 10) {
price = 1000000;
} else if (ethmount >= 10 && ethmount <= 233) {
price = 10000;
} else {
price = 1;
}
uint payment = amount * price;
require(payment <= ethmount);
_ebalances[msg.sender] -= payment;
_transfer(owner, msg.sender, amount);
}
function sale(uint amount) public {
//使用_balances[]换取_ebalances[]
require(_balances[msg.sender] >= amount, "fail to sale");
uint earn = amount * tokenprice;
_transfer(msg.sender, owner, amount);
_ebalances[msg.sender] += earn;
}
这里的思路是,_ebalances[]的初始值为0,_balances[]的初始值也为0,但是_balances[]的值可以通过调用borrow()函数增加2个,borrow()函数有条件约束_profited[msg.sender] <= 1,故每个账户只能调用borrow()函数两次从而使得_balances[]的值增加为2,_transfer(owner, msg.sender, amount)会实现_balances[]值加1,因此我们可以创建多个子合约账户,每个子合约账户的2个代币转发到一个大账户里面去,这个大账户可以选取你的外部账户-就是你钱包的地址。
0x03
举个实例,当你的大账户的_balances[msg.sender]=39时,再调用sale()函数可以使得_ebalances[msg.value]=39*6,即_ebalances[msg.sender]=234,{sale函数_balances[]与_ebalances[]的转换率为1:6};然后再调用buy()函数,由于刚好不满足条件ethmount >= 10 && ethmount <= 233;因而price = 1;故_ebalances[]与_balances[]的转化率为1:1,故_balances[msg.sender]=234;再调用一次sale()函数即可使得_ebalances[] = 1404,循环多次,使得_ebalances[msg.sender]>= 1812。最后便可调用withdraw()函数
function withdraw() public {
require(ethbalances[msg.sender] >= 1);
require(_ebalances[msg.sender] >= 1812);
payable(msg.sender).call{
value:100000000000000000 wei}("");
_ebalances[msg.sender] = 0;
_flag[msg.sender] += 1; //该函数执行一次_flag[msg.sender]值加1
}
但是这个withdraw()函数调用一次使得_flag[msg.sender] = 1,而我们要满足的条件是_flag[msg.sender] = 2;因此我们需要第二次执行withdraw()函数,但是这个函数把_ebalances[msg.sender] = 0; 因此我们得重新执行0x02步骤;
0x04
function payforflag(string memory mid, string memory b64email) public {
require(_flag[msg.sender] == 2);
emit sendflag(mid, b64email);
}
最终使得_flag[msg.sender] = 2,便可调用我们的payforflag()函数了,获取flag了