Ethernaut复活了,赶紧开始做题
Level 0. Hello Ethernaut
使用的是浏览器里的js对象的方式来进行的,之前还没有尝试过
Level 1. Fallback
需要知道怎么在console里调用函数的时候附加上交易的value值,以及如何调用fallback函数。不用web3py的操作一开始还真不太会。
Level 2. Fallout
一款对程序猿友好的字体是多么重要。
一开始在网页里看我是根本没看出来还有这回事,还加了个迷惑性极强的注释,导致我看了一圈发现没有改owner的操作啊。
复制到编辑器里就稍微明显一点了,不然我是真看不出区别。
Level 3. Coin Flip
1 | pragma solidity ^0.6.0; |
还需要顺便复习一下web3py的用法
最新的文档已经用get_storage_at
这个函数了
Level 4. Telephone
tx.origin是交易的发送方,不是方法调用的调用方。
1 | pragma solidity ^0.6.0; |
1 | In [8]: w3.eth.getStorageAt(w3.toChecksumAddress('0xDe3CaD19ADcbCC04674FE56B0627C230e7b81f4A'),0) |
Level 5. Token
一开始也是没有注意到有整数溢出,require(balances[msg.sender] - _value >= 0);
和require(balances[msg.sender] >= _vale);
这两个是不一样的。
第一个是先做了减法运算的,然后和0进行比较,而第二个是两个数直接进行比较。
注意转账的接收方不能是自己,不然溢出之后又溢出回来了。
Level 6. Delegation
There exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and
msg.sender
andmsg.value
do not change their values.This means that a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address.
This makes it possible to implement the “library” feature in Solidity: Reusable library code that can be applied to a contract’s storage, e.g. in order to implement a complex data structure.
<address>.delegatecall(bytes memory) returns (bool, bytes memory)
issue low-level DELEGATECALL with the given payload, returns success condition and return data, forwards all available gas, adjustable
Source
1 | pragma solidity ^0.6.0; |
The instance must be the Delegation
contract.
The fallback function of Delegation
contract trys to call the delegate address.
And the Delegate
contract has a pwn function that can change its ower to msg.sender
. Since the delegatecall will not change msg.sener
, we can change the owner to our attacker address.
Notice that delegatecall only copys code from the target address to the current contract and executes, so the effect of doing the pwn()
function is changing contract Delegation
‘s owner variable, which are all at storage[0]
.
Exploit
just call the fallback function of the Delegation
contract, and msg.data set to keccack('pwn()') = 0xdd365b8b
1 | await contract.sendTransaction({data : "0xdd365b8b"}) |
Level 7. Force
Some contracts will simply not take your money ¯_(ツ)_/¯
The goal of this level is to make the balance of the contract greater than zero.
Source
1 | pragma solidity ^0.6.0; |
Decompiled
1 | contract Contract { |
so the default fallback generated is simply revert.
Well i have no idea, so I searched for other people’s wp.
强行将以太币置入合约的相关方式:1. 通过自毁、2. 创建前预先发送Ether、3. 为其挖矿。
Exploit
I first forget to mark the contructor payable, and remix gives me kindly warnings:
1 | creation of Hacker errored: VM error: revert. revert The transaction has been reverted to the initial state. Note: The called function should be payable if you send value and the value you send should be less than your current balance. Debug the transaction to get more information. |
1 | // SPDX-License-Identifier: GPL-3.0 |
Level 8. Vault
Unlock the vault to pass the level!
Source
1 | pragma solidity ^0.6.0; |
Exploit
no secret on blockchain.
1 | In [1]: from web3.auto.infura.rinkeby import w3 |
1 |
|
1 | In [6]: w3.eth.get_storage_at(addr,0) |
Level 9. King
The contract below represents a very simple game: whoever sends it an amount of ether that is larger than the current prize becomes the new king. On such an event, the overthrown king gets paid the new prize, making a bit of ether in the process! As ponzi as it gets xD
Such a fun game. Your goal is to break it.
When you submit the instance back to the level, the level is going to reclaim kingship. You will beat the level if you can avoid such a self proclamation.
Source
1 | contract King { |
没看懂题目是啥意思,我的目标是什么,看了wp
注意到owner变量是不会改变的,所以说owner总是可以重新取回king的身份,目标是成为king并且保持king的身份。
The transfer function fails if the balance of the current contract is not large enough or if the Ether transfer is rejected by the receiving account. The transfer function reverts on failure.
transfer在失败的时候会回滚,所以只要我们不接受transfer就可以阻值代码的继续执行。
1 | // SPDX-License-Identifier: GPL-3.0 |
Level 10. Re-entrancy
The goal of this level is for you to steal all the funds from the contract.
Things that might help:
Untrusted contracts can execute code where you least expect it.
Fallback methods
Throw/revert bubbling
Sometimes the best way to attack a contract is with another contract.
See the Help page above, section “Beyond the console”
Source
1 | pragma solidity ^0.6.0; |
target: 0x22778878428C001234BC0d9A708D9664045d80BC
分析
Originally, there was 1 ether in the contract, so our goal is to transfer this 1 ether back to our account.
address.transfer
is safe as it has a gas stipend of 2300. but address.call.value().gas()()
forwards all available gas (adjustable), and is not safe against reentrancy. Here is another reading.
奥,我明白了,我总是试图在constructor函数中就完成整个exp链的构造,但是在这个时候我的各个编写的函数是还不存在的,我的fallack函数也不存在。所以这一题要利用fallback函数的话是不能够在一个constructor函数中就完成的,必须要在整个合约部署完成之后,再发起第二笔交易来进行攻击。所以说这题至少也需要两笔交易完成攻击。
关于receive关键字。
值得注意的是即便被攻击合约中写的是msg.sender.call.value(_amount)("");
,但是还是call了receive函数,也就是没有calldata。实验发现在fallback函数和receive函数同时存在的情况下会调用receive函数。
从反编译结果来看也是没有calldata。
1 | var temp0 = memory[0x40:0x60]; |
由于这里.call
函数只接受一个bytes
参数,而""
又是长度为0,所以就没有calldata了。如果把长度变成8,就是这样:
1 | (bool result, bytes memory data) = msg.sender.call.value(_amount)("deadbeef"); |
这是正常的行为,不是编译器的bug,这个bug在0.4.12就已经修复。
推荐使用call而不再推荐使用transfer和send。自己防范重入。
exp
1 | // SPDX-License-Identifier: GPL-3.0 |
Use the Checks-Effects-Interactions
Pattern
Level 11. Elevator
This elevator won’t let you reach the top of your building. Right?
Things that might help:
Sometimes solidity is not good at keeping promises.
This Elevator expects to be used from a Building.
Source
1 |
|
实现一个简单的接口
exp
1 | // SPDX-License-Identifier: GPL-3.0 |
Level 12. Privacy
The creator of this contract was careful enough to protect the sensitive areas of its storage.
Unlock this contract to beat the level.
Things that might help:
Understanding how storage works
Understanding how parameter parsing works
Understanding how casting works
Source
1 | pragma solidity ^0.6.0; |
Exp
链上没有隐私
1 | await contract.unlock('0xc9e62529e16fdfb38db596273b2db912') |
这一关的推荐阅读
Level 13. Gatekeeper One
Source
1 | pragma solidity ^0.6.0; |
require(gasleft().mod(8191) == 0);
这里我是有点懵的。如何测量走到这一步消耗了多少gas呢?
https://hitcxy.com/2019/ethernaut/
callcode操作符是在自己的上下文中执行代码,但是msg.sender会发生改变。ref
geth自定义的opcodes
Istanbul Fork新增的两个opcode
柏林升级前移除 EIP-2315
观察gateThree要求的条件,要求倒数第2 3个字节是0,要求高4个字节不为0,要求倒数1 2个字节是tx.origin
1 | // SPDX-License-Identifier: MIT |
254是用remix调试出来的。
Level 14. Gatekeeper Two
Source
1 | pragma solidity ^0.6.0; |
分析,需要在constructor中完成利用。
值得留意的事情:代码里uint64(0) - 1
在0.8.0版本的编译器里会直接revert,好高级。自动溢出保护。
1 | // SPDX-License-Identifier: MIT |
Level 15. Naught Coin
Source
1 | pragma solidity ^0.6.0; |
Analysis
1 | In [3]: target = '0x153cD5C206630Dbd726891f2aE5e6CF031460cfE' |
合约一开头是继承的父合约ERC20的结构体变量,发现lockTokens这个修饰器对时间有要求。
但是ERC20可以转账的函数显然不止transfer一个函数。可以使用approve+transferFrom
进行转账。
有地方需要注意的是,ERC20不允许向0地址转账…..
1 | function _transfer(address sender, address recipient, uint256 amount) internal virtual { |
EXP
1 | #!/usr/bin/python |
Level 16. Preservation
This contract utilizes a library to store two different times for two different timezones. The constructor creates two instances of the library for each time to be stored.
The goal of this level is for you to claim ownership of the instance you are given.
Source
1 | pragma solidity ^0.6.0; |
Analysis
delegatecall调用外部函数storage位置没有对上,造成任意指定storage篡改
target contract : 0x4464A38441e76713439883883752A60B02C24298
1 | In [31]: target = '0x4464A38441e76713439883883752A60B02C24298' |
从反编译的结果来看library code就如源码中所示的那么简单,修改stoarge 0
。
1 | contract Contract { |
思路是先调用修改timeZone1Library的地址,然后delegatecall执行任意代码。
EXP
1 | // SPDX-License-Identifier: MIT |
Insights
As the previous level, delegate mentions, the use of delegatecall to call libraries can be risky. This is particularly true for contract “libraries
“ that have their own state. This example demonstrates why the library
keyword should be used for building libraries, as it prevents the libraries from storing and accessing state variables.
library变量存取state是非常危险的,所以library关键字避免了这个行为。
Level 17. Recovery
A contract creator has built a very simple token factory contract. Anyone can create new tokens with ease. After deploying the first token contract, the creator sent 0.5 ether to obtain more tokens. They have since lost the contract address.
This level will be completed if you can recover (or remove) the 0.5 ether from the lost contract address.
Source
1 | pragma solidity ^0.6.0; |
Analysis
没看懂题目是啥意思?也不说清楚啥地址lost了。考的是暴露的selfdestruct?
EXP
1 | // SPDX-License-Identifier: MIT |
Level 18. MagicNumber
To solve this level, you only need to provide the Ethernaut with a “Solver”, a contract that responds to “whatIsTheMeaningOfLife()” with the right number.
The solver’s code needs to be really tiny. Really reaaaaaallly tiny. Like freakin’ really really itty-bitty tiny: 10 opcodes at most.
Source
1 | pragma solidity ^0.6.0; |
Analysis
意思是只要返回42就行?
bytes类型永远会在首部存储长度字段的
EXP
emmm用mstore8是过不了的,必须用mstore
1 | // SPDX-License-Identifier: MIT |
Level 19. Alien Codex
You’ve uncovered an Alien contract. Claim ownership to complete the level.
Source
1 | pragma solidity ^0.5.0; |
Analysis
看一下反编译的revise和retract发现可以直接用retract整数下溢后用revise任意storage写。
逆向的结果:在索引数组codex[i] = _content
时总会有边界检查,codex.length--
会把新旧之间的数组元素全部清零。
1 | function retract() { //检查contacted修饰符 |
EXP
1 | In [16]: w3.keccak(b'\x01'.rjust(0x20,b'\x00')) |
1 | // SPDX-License-Identifier: MIT |
Insights
This level exploits the fact that the EVM doesn’t validate an array’s ABI-encoded length vs its actual payload.
这个漏洞我好像没用到…?
Level 20. Denial
This is a simple wallet that drips funds over time. You can withdraw the funds slowly by becoming a withdrawing partner.
If you can deny the owner from withdrawing funds when they call withdraw() (whilst the contract still has funds) you will win this level.
Source
1 | pragma solidity ^0.6.0; |
Analysis
Error handling: Assert, Require, Revert and Exceptions
Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the state in the current call (and all its sub-calls) and flags an error to the caller.
When exceptions happen in a sub-call, they “bubble up” (i.e., exceptions are rethrown) automatically unless they are caught in a try/catch statement. Exceptions to this rule are send and the low-level functions
call
,delegatecall
andstaticcall
: they return false as their first return value in case of an exception instead of “bubbling up”.
确实,在合约call的时候revert不会终止调用合约的执行。所以这个是重入?
EXP
注意fallback要的是payable的,第一次没加就过不了。
1 | // SPDX-License-Identifier: MIT |
Level 21. Shop
Сan you get the item from the shop for less than the price asked?
Source
1 | pragma solidity ^0.6.0; |
Analysis
没看懂什么意思,看了皮卡丘师傅的wp,发现目标是把price改小。3000gas应该没啥影响?
好吧,是有影响的…修改一次storage(sstore)需要20000gas; sload costs 800 gas.
看了皮卡丘师傅的wp和黄皮书后发现call只需要700gas
有两种revert,我遇到的一直是Warning! Error encountered during contract execution [execution reverted]
,所以应该不是gas不够的原因。
真是非常奇怪,在私链上调试是可以的啊。。。
EXP
这一题看了一天还是不会,发现很多细节导致我一直revert。暂时认为是out-of-gas的原因,存疑。
在jsvm上调试了一下查询isSold的操作是不可控的,包含了一个sload操作,用了1069gas。所以说我自己的代码使用的gas不可以超过2000gas。手写试试。
jsvm hacker: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
jsvm shop : 0xd9145CCE52D386f254917e481eB44e9943F39138
jsvm test1
33735B38Da6a701c568545dCfcB03FcB875f56beddC41860485763a6f2ae3a600052600060006004601c600073d9145CCE52D386f254917e481eB44e9943F391385af160006000f35b63e852e741600052602060006004601c6000335af1600051606e57606460005260206000f35b606360005260206000f3
rinkeby hacker: 0x9B3754c0a0798aDe51e98c7a81aE73aAcf9C2e5F
rinkeby shop : 0xa0379c92AE6533b4C3f82606852E6ACc416DCc3A
final
1 | 33739B3754c0a0798aDe51e98c7a81aE73aAcf9C2e5F1860485763a6f2ae3a600052600060006004601c600073a0379c92AE6533b4C3f82606852E6ACc416DCc3A5af160006000f35b63e852e741600052602060006004601c6000335af1600051606e57606460005260206000f35b606360005260206000f3 |
1 | // SPDX-License-Identifier: MIT |
其实我一开始遇到的问题就是gas不足的问题,在函数调用中如果gas不足在etherscan上不会显示out-of-gas,反而是函数调用会以失败的结果返回。
Level 22. Dex
The goal of this level is for you to hack the basic DEX contract below and steal the funds by price manipulation.
You will start with 10 tokens of token1 and 10 of token2. The DEX contract starts with 100 of each token.
You will be successful in this level if you manage to drain all of at least 1 of the 2 tokens from the contract, and allow the contract to report a “bad” price of the assets.
Source
1 | pragma solidity ^0.6.0; |
Analysis
分了两种token,一开始我有各有10个token,contract各有100个token,目标是把contract的某一个token弄为0.
1 | In [24]: target |
emmmm是否可以自己写一个新的token进去玩?
要使contract的某个token为空,就是在swap函数中转出的时候把所有的都转出。
EXP
remix的gas估计,我愿称之为神。
1 | // SPDX-License-Identifier: MIT |
第一次transferFrom中忘记返回true了,remix估计会fail,就真的fail了。