LearnWeb3 Logo

5 min read

ยท

3 years ago

+15,000 XP
392
29
4

๐Ÿ‘Ž Genuine looking malicious contracts through external helpers


In the crypto world, you will often hear about how contracts which looked legitimate were the reason behind a big scam. How are hackers able to execute malicious code from a legitimate looking contract?


We will learn one method today ๐Ÿ‘€


๐Ÿ‘€ What will happen?


There will be three contracts - Malicious.sol, Helper.sol and Good.sol. User will be able to enter an eligibility list using Good.sol which will further call Helper.sol to keep track of all the users which are eligible.


Malicious.sol will be designed in such a way that eligibility list can be manipulated, lets see how ๐Ÿ‘€


โš’๏ธ Build


Feel free to refer to this repo if you get stuck.

Setting up Foundry


Start by creating a new project directory.


mkdir malicious-contracts


Let's make a new Foundry project inside the malicious-contracts directory.


cd malicious-contracts forge init .


Writing the smart contracts


Start by creating a new file inside the src directory called Good.sol


// SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import "./Helper.sol"; contract Good { Helper helper; constructor(address _helper) payable { helper = Helper(_helper); } function isUserEligible() public view returns(bool) { return helper.isUserEligible(msg.sender); } function addUserToList() public { helper.setUserEligible(msg.sender); } fallback() external {} }


After creating Good.sol, create a new file inside the src directory named Helper.sol


// SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract Helper { mapping(address => bool) userEligible; function isUserEligible(address user) public view returns(bool) { return userEligible[user]; } function setUserEligible(address user) public { userEligible[user] = true; } fallback() external {} }


The last contract that we will create inside the src directory is Malicious.sol


// SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract Malicious { address owner; mapping(address => bool) userEligible; constructor() { owner = msg.sender; } function isUserEligible(address user) public view returns(bool) { if(user == owner) { return true; } return false; } function setUserEligible(address user) public { userEligible[user] = true; } fallback() external {} }


You will notice that the fact about Malicious.sol is that it will generate the same ABI as Helper.sol even though it has different code within it. This is because ABI only contains function definitions for public variables, functions and events. So Malicious.sol can be typecasted as Helper.sol.


Now because Malicious.sol can be typecasted as Helper.sol, a malicious owner can deploy Good.sol with the address of Malicious.sol instead of Helper.sol and users will believe that he is indeed using Helper.sol to create the eligibility list.


In our case, the scam will happen as follows. The scammer will first deploy Good.sol with the address of Malicious.sol in the constructor. Then when the user will enter the eligibility list using addUserToList function which will work fine because the code for this function is same within Helper.sol and Malicious.sol.


The true colours will be observed when the user will try to call isUserEligible with his address because now this function will always return false because it calls Malicious.sol's isUserEligible function which always returns false except when its the owner itself, which was not supposed to happen.


Writing the test


Let's try to write a test and see if this scam actually works, create a new file inside the test folder named Attack.t.sol


// SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import {Test} from "forge-std/Test.sol"; import {Helper} from "../src/Helper.sol"; import {Malicious} from "../src/Malicious.sol"; import {Good} from "../src/Good.sol"; import {console} from "forge-std/console.sol"; contract AttackTester is Test { //declare variables for holding instances of our contracts Good public goodContract; Malicious public maliciousContract; function setUp() public { // Deploy the malicious contract maliciousContract = new Malicious(); } function test_attack() public { //deploy the good contract with the address of the malicious contract as its constructor argument and deposit 3 ether goodContract = new Good{value: 3 ether}(address(maliciousContract)); // Get an address using its private key address address1= vm.addr(1); //impersonate address1 for sending a transaction to the good Contract vm.prank(address1); //this transaction will add adddress1 to the eligibility list goodContract.addUserToList(); //again impersonate address1, this time for checking if it eligible vm.prank(address1); bool eligible = goodContract.isUserEligible(); // the value of eligible should be false assertEq(eligible, false); } }


Testing the attack


To run this test, open up your terminal pointing to the root of the directory for this level and execute this command:


forge test


If all your tests passed, this means that the scam was successful and that the user will never be determined eligible.


๐Ÿ‘ฎ Prevention


Make the address of the external contract public and also get your external contract verified so that all users can view the code


Create a new contract, instead of typecasting an address into a contract inside the constructor. So instead of doing Helper(_helper) where you are typecasting _helper address into a contract which may or may not be the Helper contract, create an explicit new helper contract instance using new Helper().


Example


contract Good { Helper public helper; constructor() { helper = new Helper(); }


How do you make sure that your contract is trusted by your users?

  • Get your contract and external contract verified on etherscan

  • Make sure your contract's code is not verified on etherscan

  • Make sure your contract's code is verified but the external contract you are calling from your contract is not verified on etherscan


Now, take a closer look at the following code:



How can a malicious actor prevent users from joining the whitelist even though the user calls the addUserToWhitelist function in Good.sol?

  • He can't prevent it

  • He can add malicious code inside the helper contract

  • He can block the UI through which the user is calling Good.sol contract


Wow, lots of learning right? ๐Ÿคฏ


Be aware of scammers, you might need to double check the code of a new dApp you want to put money in.


๐Ÿ‘‹ Conclusion


Hope you learnt something from this level. If you have any questions or feel stuck or just want to say Hi, hit us up on our Discord. We look forward to seeing you there!

*You must be signed in to submit quiz
You must be signed in to post comments
User avatar

F.ch

ยท

last year

Good

0
User avatar

farzad.ch

ยท

last year

And easy๐Ÿ˜€

0
User avatar

farzad.ch

ยท

last year

Great

0
User avatar

HopelessGramophone

ยท

last year

So Good

0
User avatar

Soft-spokenGallop

ยท

last year

Easy

0
BUGG Logo