5 min read
ยท3 years ago
๐ 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!
F.ch
ยทlast year
Good
farzad.ch
ยทlast year
And easy๐
farzad.ch
ยทlast year
Great
HopelessGramophone
ยทlast year
So Good
Soft-spokenGallop
ยทlast year
Easy