Statement
The challenge statement is as follows:
Agent, nous vous avons préparé un exercice pour vérifier votre compréhension de la blockchain.En effet, l’aspect pratique est important mais la partie théorique l’est tout autant.Essayez de récupérer cette clé publique.
Contrat à l’adresse : 0x9Eb8F52b22024003C1312ea1569cf14208f3c30A
Réseau de test Ropsten
Attention, ce challenge utilise une blockchain de test (réseau Ropsten). En AUCUN CAS vous ne devez utiliser de réelles cryptomonnaies pour ce challenge. Nous ne sommes pas responsables des éventuelles pertes que vous pourriez subir si vous le faites. Si vous possédez de réelles cryptomonnaies, nous vous conseillons fortement de créer un nouveau wallet séparé et dédié à ces challenges.
The source code of the smart contract is given:
|
|
Exploitation
The source code is short and contains one variable and one function. Before explaining the source code when need to understand how the address, public key and private key work in Solidity.
There are three steps to generate a new wallet:
- First, a private key is generated, it is composed of 64 hexadecimal characters (i.e fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19).
- Then, from the private key with the algorithm ECDSA, the public key is generated, it is composed of 128 hexadecimal characters (i.e 9a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05). Sometimes the public key can be compressed to save memory space (source)
- Finally, the wallet address is generated from the public key. In solidity, this can be done as follows
address(uint160(uint256(keccak256(publickey))))
. This code takes the keccak256 hash of the public key and converts it to a 160-bit number. This number is then converted to a 20-byte address. For example, thekeccack256
of the previous public key is 793d56a696967d8f0833fd6296216849c49358b10257cb55b28ea603c874b05e (several tools online can get the keccack256 hash). The address is the last 40 characters (96216849c49358b10257cb55b28ea603c874b05e)
By knowing all this, the isPublicKey
function takes a public key as parameter, get the address from it and compare it with the secretAddress
variable.
The exploitation can be done in two steps.
- Retrieving the secretAddress variable
In solidity contract nothing is really private. Even if the visibility of the variable has been set to private, the variable is still accessible. Web3 clients has a method called
StorageAt
(or something similar depending of the language) that can be used to retrieve a hex string at a specific slot in the contract.
Beforehand, I forked the blockchain for faster performance and easier debugging.
|
|
The storage can be retrieved with cast from foundry:
|
|
or in golang
|
|
which returns the same value. We know that the secretAddress
is 0xcf9a54585b20041ac1265ad64ce21d09fd4b1324
.
- An important thing about a transaction on the blockchain, is that each transaction is signed with the private key of the sender. The sender’s public key is also included in the signature. If the node is not able to verify the signature, it will not accept the transaction. At this point, if the address
0xcf9a54585b20041ac1265ad64ce21d09fd4b1324
has sent a transaction, we will be able to retrieve its public key. Lucky we are, it has sent four transactions (ropsten.etherscan.io).
We need to recover the raw transaction that has been send by 0xcf9a54585b20041ac1265ad64ce21d09fd4b1324
. For some unknown reason etherscan can’t retrieve the raw transaction.
But we can do it with any language with a web3 module.
|
|
|
|
An online tool can do the math for us to retrieve the public key from the transaction.
In golang, no public function is available to recover the public key of a transaction. However, a private function in transaction_signing named recoverPlain
return, from the raw transaction, the address of the sender after calculating the public key (I leave as an exercice for the reader to see how the internal works. The first called function is Sender of the london signer type)
|
|
|
|
You can notice, that to convert the public key to an address, the first bytes (two characters) are ignored. This is because the 04
is a tag bytes for the ASN.1 OCTET String structure.
We can check the public key by sending it the contract with the cast
command of the foundry binaries.
|
|