To overcome this challenge, we have to become the owner of the contract.
The contract is very simple:
// 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;
}
}
}
We see that we've a public variable of type address called owner, wich is
definided at the time that the contract is deployed (at that moment, the
constructor function is executed, defining the owner variable as the address
that deployed the contract).
After that, the contract has a public function called changeOwner, which
receives as parameter an address, and performs the following check:
if (tx.origin != msg.sender)
If this instruction returns true, the function assings the state variable
owner as the address passed as parameter.
What is the difference between tx.origin vs msg.sender?
Both are native Solidity global variables of type address, which means that
these variables can be called from anywhere in the contract without the need
to import anything.
tx.origin:
◉ The original user wallet that initiated the transaction
◉ The origin address of potentially an entire chain of transactions and
calls
◉ Only user wallet addresses can be the tx.origin
◉A contract address never can be the tx.origin
msg.sender:
◉ The immediate sender of this specific transaction or call
◉ Both user wallets and smart contracts can be the msg.sender
In a simple call chain A->B->C->D, inside D msg.sender will be C, and tx.origin will be A.
Example
msg.sender checks where the external function call directly came
from.
Overcoming the challenge
To hack the contract and claim ownership all we need to do is to create a
new malicious contract (Contract B) that we'll use it for call the
changeOwner
function of the original contract Telephone.sol (Contract A in our
example).
To create smart contracts we've so many options of frameworks like
Hardhat, Truffle, etc. but for this challenge I go to use Remix IDE. This
tools offers us a complete development environment to create and deploy
Smart Contracs directly from our browser.
1. Create a new contract (with the name you want, in my case I called it
TelephoneAttack).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract
TelephoneAttack{
}
2. We create an interface of the Telephone contract, this interface is the
one we will use to interact with it. Inside the interface we must specify
the functions as external.
interface ITelephoneContract{
function changeOwner(address _owner) external;
}
3. Inside the constructor of the Attacker contract we will create the
instance of the victim contract (using the interface) with the address
provided by Ethernaut.
ITelephoneContract public challenge;
constructor(address
_victimAddress){
challenge =
ITelephoneContract(_victimAddress);
}
4. We create a function called Attack that simply calls the changeOwner
function of the contract instance belonging to the challenge.
function attack(address _newOwner) public {
challenge.changeOwner(_newOwner);
}
Deploying the attacking contract
Before deploying the contract, we verify that the "ENVIRONMENT" parameter of Remix is set to "Injected Web3". Then, when deploying it, we pass the constructor parameter, which is the address of the challenge instance (to find out we can type "await contract.address" in the console).
We confirm the transaction in our wallet and the contract is now deployed on the Rinkeby network.
Interacting with the attacking contract
Remix creates a section with the contracts that we have deployed, in it we have in graphic form the functions or public variables of the deployed contracts with which we can interact.
In this case, since the TelephoneAttack contract has only one public function called attack, we can interact with it by sending it our address as a parameter.
And that's it! we can check if we are the owner of the contract by typing await contract.owner() and verifying that it is equal to our address.