Requirements: basic smart contracts knowledge.
Intro ππ
Hello fren, my name is Pacelli.
In this series I will be presenting my solutions to the Ethernaut hacking challenges created by the OpenZeppelin team.
Although there are many repos and blogs around the web with the solutions to these challenges, I still decided to make the exercise of publishing my own solutions and explations for my own benefit and in the hope that this blog will help future readers looking for up-to-date solutions.
For the majority of the challenges Remix IDE will be enough, except for some cases in which an editor like VS Code will be necessary.
To play the game you will need:
- Metamask. If you don't have it installed in your browser learn how to set up a profile for free.
- Test ether. My recommendation is to use the Sepolia tesnet because is the one with most active faucets. You can get test ether from either the Alchemy or Chainlink faucets.
Get some test ether and come back!
How does Ethernaut works? βοΈ
All smart contracts source code are compiled into two formats, by the Ethereum Virtual Machine (EVM):
- Application Binary Interface (ABI): communication layer between Solidity and Javascript, in JSON format.
- Bytecode: low-level machine language that it's interpreted and executed by the EVM.
When you request Get new instance
for each level, Ethernaut compiles and deploy the bytecode to a new address on the network you're connected.
Once a new instance of the level is created on the blockchain, an address is returned to your web client through an event emitted by the main contract game, Ethernaut.sol
:
// This is a fragment of the contract
event LevelInstanceCreatedLog(
address indexed player,
address indexed instance,
address indexed level
);
function createLevelInstance(Level _level) public payable {
// Ensure level is registered.
require(registeredLevels[address(_level)], "This level doesn't exists");
// Get level factory to create an instance.
address instance = _level.createInstance{value: msg.value}(msg.sender);
// Store emitted instance relationship with player and level.
emittedInstances[instance] = EmittedInstanceData(
msg.sender,
_level,
false
);
statistics.createNewInstance(instance, address(_level), msg.sender);
// Retrieve created instance via logs.
emit LevelInstanceCreatedLog(msg.sender, instance, address(_level));
}
Finally, with the help of web3js
an ABI is wrapped around the new contract instance that will allow you to easily interact with it using the console in your developer tools.
Also is important to mention the game provides a series of custom web3 add-ons, some of them will be useful to solve the challenges.
Challenge Walkthrough πΆπ½ββοΈπΆπ½
This a introductory challenge, its main objective is to help us get related with the UI and how to interact with a contract using web3js and the ABI.
Let's solve this challenge:
- Request a new instance of this level, send the transaction and wait for it to be mined so you to get a
Intance address
for this leven. - Open your developer tools, type
contract
and press enter. This will print the recently deployed contract to the console. This contract object contains anabi
with all the methods we can call. - Type
await contract.info()
, this will return:
'You will find what you need in info1().'
- Let's follow the new instruction by calling the
info1()
method, typeawait contract.info1()
, now you will get:
'Try info2(), but with "hello" as a parameter.'
- This time it is a little different, now we have to call the method with a parameter like this
await contract.info2("hello")
and it will return:
'The property infoNum holds the number of the next info method to call.'
- If you call the
infoNum
method it will return an object. The information we required is in the first element of thewords
property. We can access this value more easily with this syntax(await contract.infoNum()).words[0]
. - The previous contract call returned
"42"
. This is a clue to help us determine the next method we need to call. If we inspect the ABI of the contract again, we will see ainfo42
method. This is the method we have to call next. - Type
await contract.info42()
and you will see in the console:
'theMethodName is the name of the next method.'
- Call
await contract.theMethodName()
, it will return:
'The method name is method7123949.'
- After calling
await contract.method7123949()
, the contract will return this message:
'If you know the password, submit it to authenticate().'
- We have to call the
authenticate
method with a password, but we don't know it yet. Luckily for us and not for the dev who wrote this contract, the password is stored in the contract (big mistake). To read the password typepassword = await contract.password()
:
// this is the password:
'ethernaut0'
- Finally type
await contract.authenticate(password)
and press enter. Metamask will pop-up, send the transaction and wait for it to be mined to get a transaction receipt. - Submit the instance to complete this level.
Uf! π© those were a lot of contract calls, but it wasn't so terrible right?
Conclusion βοΈ
After clearing this level you will see Instance.sol
with the code you just interacted with. The methods of this contract acted as breadcrumbs that we follow to discover the password to clear this level.
Data in the blockchain is public and everybody can see it. Storing sensitive data in a blockchain is a huge mistake.
Top comments (0)