Analyzing the Defrost Finance Inside Job

Analyzing the Defrost Finance Inside Job

librehash

Multiple reports have come out alleging that Defrost Finance was compromised over the Christmas holiday (Dec 23-26th, 2022), with the current prevailing narrative being that this compromise was done by or at the behest of the team (AKAinside job).

We're going to take a look at what information is publicly available to support this claim and why / why you shouldn't be in agreement that the Defrost Finance hack was an inside job.

Backstory on Defrost Finance

This project is apparently one that's located on Avalanche (blockchain) exclusively.

According to their website, "Defrost Finance is the platform behind the next generation stablecoin and provides remunerative investment opportunities. A fully fair launch, decentralized project, its aim is to change the world of finance for good."

https://www.defrost.finance/home
Additional NoteSince the prevailing narrative is that the project has been compromised by the team itself, exercise caution before visiting any links leading to webpages / online resources that the team owns exclusively. If these claims are true, you never know how far these individuals may be willing to go to fester money out from the community. Consider visiting their sites to be analogous to having unprotected sex with someone that's been rumored to have a number of untreated STDs - even if you aren't totally convinced that these rumors are true...are you still willing to risk it? Didn't think so.

Documentation

We're not going to do a total project review of 'Defrost Finance' here for the sake of time, but we will take a brief look at their documentation to get a gist of what it is this project was purportedly trying to accomplish.

Documentation can be found here: https://docs.defrost.finance/

Again, please heed the warning given in the prior section and proceed at your own risk. Viruses/malware, like STDs, are sometimes removable but in certain cases, they may not be so easy to extract or perhaps not possible at all.


Above is a picture from the 'Introduction' portion of the project's docs.

In essence, this project:

  • Is a decentralized finance venture (obviously)
  • Allows users that interact with the project to engage in "margin/leverage trading" akin to what you would see in futures / derivatives markets. Typically, any protocol or project offering such a feature to its users will involve the integration of a 'flash loan' feature of some sort (important to note since this is the facet of the project that was allegedly exploited to drain several million dollars out of its lending pools).
  • Liquidity providers are necessary (of course), whom are subsequently compensated when their assets "accrue interest and mining rewards".

Defrost Finance V2

As alluded to in the 'introduction' section, there is a 'Defrost Finance V2' that is apparently different than what was presented in 'Decentralized Finance V1'.

Below is the team's description of this new protocol per their documentation:

As expected, this protocol involves the integration of 'Aave', which is a decentralized finance platform that's most notably used for implementing 'flash loans'.

What are Flash Loans?

Flash loans are effectively leveraged loans on blockchain. This practice is sustained by mandating that the loan be repaid within the same transaction in which it is given.

Gemini explains the nature of these loans eloquently below:

https://www.gemini.com/cryptopedia/aave-flashloans

Skipping to the 'Security' Section

Without getting bogged down in the unnecessary details about how the protocol works, let's take a look at the "Security" section of the documentation.

Below is a look at what the project maintainers listed under that section as assurance to users on the outside that their funds were in good hands:

https://docs.defrost.finance/defrost-v2/security

Some curious observations:

  1. It's noted that the project was audited by Certik in September 2022. However, as we'll come to find, Certik now holds the position that the recent hack of the Defrost Finance protocol was an 'inside job'.
  2. Its acknowledged in the documentation that the primary contract orchestration for the project is controlled via a "multi-signature controlling account". This is accurate and reflected in the code of the relevant smart contracts for this project as well. This is worth noting since it has been claimed by various sources (including the project owners) that a singular key was compromised, serving as one of the factors that allowed the execution of the hack itself.
  3. The claim that, "all contracts in defrost finance cannot be upgraded, meaning manipulation by the contract manager becomes impossible", is patently false. As we'll see later on, there are a number of delegatecall functions laden within the smart contract orchestration for this project. The presence of a delegatecall function almost always indicates that some facet of the deployment (either that contract or one connected to it in the sol2uml) is upgradable since this is an inherent feature and motivation for instantiating the ability to proxy calls in the first place.
  4. Its noted that Chainlink was utilized to implement the project's price oracles. However, reports concerning the hack have indicated that a malicious oracle was spun up by the team and used to substitute the original price oracle instantiation using Chainlink. So this is another observation worth noting.

The reason for verbosely identifying all contradictions and questionable details in the security section is to note that each aspect of the project that its owners identified as being a security enhancement ultimately served as one of the indicators of 'false compromise' in one way or another.

Reviewing Peckshield's Analysis

Peckshield was the security firm responsible for initially blowing the whistle on the 'Defrost Finance' situation and raising questions about whether it was a legitimate hack or simply a 'rug pull' (akainside job).

They noted many of their concerns in a Twitter thread published on December 25th, 2022, which can be found here: 

However, their first thread analyzing the attack as initially reported can be found here: https://twitter.com/PeckShieldAlert/status/1606276020276891650?s=20&t=nm3oai-oRbuCIb3w2FlZYg

image

The first tweet (above) in the thread claims that the hack was made possible "due to the lack of reentrancy lock for the flashloan()/deposit() functions, which was used by the hacker to manipulate the share price of LSWUSDC".

The photos that accompanied said tweet actually proved helpful in evaluating Peckshield's findings.

Those are re-posted below for convenience sake:

Note how the funds originated from 'TornadoCash' in the PeckShield diagram shown above; PeckShield's diagram also shows the contracts that the alleged hacker interacted with in order to exploit an identified reentrancy flaw in the Defrost Finance contracts"

Looking for the Identified Functions

As stated in the first tweet of their thread analyzing the attack itself, the would-be attacker was able to pull off the heist by exploiting the project's failure to include a reentrancy lock on two functions, which were:

  1. flashloan()
  2. deposit()

So let's see if we can't find exactly where these functions are in the smart contract orchestration to get a better idea for how this could've taken place.

First Step

Our first step was identifying if any public repository of the relevant smart contracts for the 'Defrost Finance' project existed online.

Fortunately, there is one.

image
https://github.com/DefrostFinance/defrost-finance-farm (repo was confirmed via link from CertiK's audit page for the project leading directly here)

Second Step

While there are a plethora of smart contract analysis tools that could be deployed aid us in dissecting the various flaws, issues, and potentially exploitable functions in the Defrost Finance smart orchestration, most of those tools must be used on the command line.

Since not everyone reading this breakdown may be familiar with using command line or handy with a terminal, we'll restrict this exploration to tools that can be accessed and navigated by anyone with a computer and a browser.

Thus, our next stop is at the Ethereum 'Remix' IDE, which is hosted online for all those that need it.

Upon visiting remix-project.org, you simply scroll down and click the 'Remix Online IDE' button:

https://remix.ethereum.org (this is where that link takes you)

Third Step

Truthfully, there are several sub-steps included within this one that we're going to map out here all at once for the sake of brevity.

  1. We're going to pull in the full repository via 'git import' (try your best to find this if you don't know where it is; this exercise will help you to become more familiar with the online IDE)
  2. From here, we're going to 'activate' some of the extensions that the online IDE provides as a complementary addition to their tool. In specific, we want to activate the SOLHINT LINTER and SOLIDITY STATIC ANALYSIS extensions.
  3. From here, we'll tweak the solidity compiler options so that smart contracts are auto-compiled upon selection within this repository.

Once we're finished with the sub-steps listed above, we're going to search very specifically for the deposit() function that was referenced by PeckShield.

In doing so, its important that we keep in mind that we're looking for the actual implementation of this function in the smart contract - not the interface definition or the library code defining how its to be implemented. If you're confused about the difference, take a second to consult the Solidity developer documentation online.

Closer Look at the Certik Audit

The link to the Certik audit is: https://www.certik.com/projects/defrostfinance

Upon clicking said link, we can see three separate audits were performed by CertiK. For our purposes, we're going to take a look at the most recent audit they performed (underneath a tab labeled, 'Defrost Finance III').

The page is a bit cluttered, but hopefully you find it.

On that tab, there's an option to view the pdf version of the audit report, which we'll select. For some reason, there were only 4 different .sol files audited out of the 30+ that are involved in this project.

This is worth noting since the reentrancy flaw that allowed this exploit to take place is easily identified by any basic static analysis. As we'll see in the Remix IDE later on, the 'stock' static linter extension they provide is able to identify the relevant reentrancy flaw in the code that was ultimately exploited to facilitate the theft of millions of dollars.

CertiK Audit Flagged the Reentrancy Flaw as a 'Minor Issue'

They also failed to properly label the reentrancy vulnerability - instead listing it under issue BFD-06 | Incompatibility with Deflationary Tokens.

According to the audit report, the "alleviation" was a statement provided by the Defrost Finance team that reads, "We do not use deflationary token as lp"

Interesting.

Finding the Vulnerable Code in Defrost Finance's Contracts

After git cloning the relevant repository into Remix IDE and performing a cursory search for the deposit() function in question that was reentrant-vulnerable, our search returned a positive result on the MeltFarmH2oMelt.sol file.

The relevant code can be found in lines 139-147 as well as lines 149-159.

Below is the vulnerable deposit function in lines 139-147:

    function deposit(uint256 _amount)  public payable {
        stake(_amount);

        for(uint256 i=0;i<rewardTokens.length;i++) {
            tokenFarms[rewardTokens[i]].stake(msg.sender);
        }

        emit Staked(msg.sender, _amount);
    }

The relevant code for the withdraw function is just below the deposit function in lines 149-159 (also shown below):

function withdraw(uint256 _amount) public payable{
        if(_amount==0) {
            getReward();
        }else {
            unstake(_amount);
            for(uint256 i=0;i<rewardTokens.length;i++) {
                tokenFarms[rewardTokens[i]].unstake(msg.sender);
            }
            emit Withdrawn(msg.sender, _amount);
        }
    }

If we check solhint (one of the static analysis extensions that we activated earlier), it also positively identifies these lines of code as being potentially vulnerable to reentrancy attacks.

How the Attack Took Place

As documented by PeckShield in their initial thread covering the incident, the would-be 'attacker' was able to essentially withdraw more tokens from the contract than they otherwise should have been able to.

https://twitter.com/PeckShieldAlert/status/1606276020276891650/photo/2

This is exactly what CertiK warned about in their report when they identified this as a potential attack vector within the project (re-pasted below for convenience):

Remember, Defrost Finance blew off this feedback by stating that there were no 'deflationary tokens' that would be 'used as lp'

Proving the Defrost Finance Team Was Lying

The statement, "we do not use deflationary token as lp", was not only an insufficient response to the issue raised by CertiK, it also proved incorrect.

To be clear, the specific issue that CertiK was alluding to was the demise of several different yield farming projects back in 2021, which failed to account for transaction fees.

This incident was covered by Twitter user, 'rugdoc' in the following thread:

https://twitter.com/rugdocio/status/1405192419491020800

This is effectively what happened in this case as well. If we visit the 'snowtrace' page for LSWUSDC (lending switch USDC token), and examine the contract code, we'll see that there is a fee tacked on to each specific transaction (making it deflationary by nature).

https://snowtrace.io/token/0xff152e21c5a511c478ed23d1b89bb9391be6de96#code
https://snowtrace.io/token/0xff152e21c5a511c478ed23d1b89bb9391be6de96#code

Any token that's taking fees as well as being burnt periodically (for any reason) = deflationary.

Remember this specific tweet from 'Rugdoc', which really explains the crux of the reentrancy flaw that was exploited via this token:

https://twitter.com/RugDocIO/status/1405192495349133312?s=20&t=R7EZVwJeruiiDDX6TRAmfA

Given what we read above, someone would be able to take advantage of the reentrancy vulnerability in the Defrost Finance smart contract by simply 'interrupting' the transaction flow to create a 'loop' in the logic where the contract continuously executes its initial deposit logic without accounting for the adjustment in the circulatingSupply minus the fee.

Thus, someone would be able to repeatedly deposit a certain amount and also make a call on the withdraw() function without actually having to pay the necessary fee, which would allow them to wash, rinse and repeat until they drain the pool.

However, with this aside, it appears that the team decided to take things even further than (per PeckShield's reports).

New Allegations from PeckShield

Following the initial reports that 'Defrost Finance' had been hacked, PeckShield followed up with a tweet claiming that a "malicious price oracle" was "used to liquidate current users", resulting in losses "estimated to be >$12M".

https://twitter.com/peckshield/status/1606767457099993088?s=20&t=ZU1IeXfolv9iN-Euudc-Vw

Accompanying this tweet were two screenshots, which are reposted below for convenience:

Of particular note is the second screenshot here because it shows:

  • A function being called named, setOracleAddress, which was apparently part of the stack execution trace for the transaction being analyzed by PeckShield (they unfortunately failed to provide any TX ID for us for some reason)
  • The address of the supposed malicious oracle that was added.
  • The appearance of the multiSignature.getValidSignature function, accompanied with a msghash, which must've been valid for the transaction to execute successfully.

Based on what's shown above, its fair to suspect or outright claim that this hack / exploit was the product of an 'inside job'.

The specific function setOracleAddress can be found in the main contract orchestration which is located here: https://github.com/DefrostFinance/defrost-finance-contract

Within those contracts, this function is defined in the file named IDSOracle.sol in lines 24-26, which reads as thus:

function setOracleAddress(address oracle)public OwnerOrOrigin{
    _oracle = IDSOracle(oracle);
}

The key to understanding this function requires us to know what OwnerOrOrigin is defined as in the code.

Curiously, this can be found in the .sol file named proxyOwner.sol in lines 106-114 (reposted below for convenience):

 modifier OwnerOrOrigin(){
    if (isOwner()){
    }else if(isOrigin()){
        checkMultiSignature();
    }else{
        require(false,"proxyOwner: caller is not owner or origin");
    }
    _;
}

Quick Rundown of This Code:

  • OwnerOrOrigin is a modifier, which means that it gets appended to function calls to specify that said function is only applicable when its called in adherence to whatever the modifier (OwnerOrOrigin in this case) dictates.
  • This function states that the caller must be the owner via the isOwner declaration. This is an if statement, however, so that means that this requirement is conditional.
  • Should the caller not match whatever was assigned to the isOwner variable, then another check is performed to see if the caller isOrigin, which is defined just a few lines above this modifier in the same file (proxyOwner.sol)

Here's how the isOrigin function is defined in lines 91-94 (also including isOwner from lines 95-97, since they're listed together:

function isOrigin() public view returns (bool){
    (address _origin0,address _origin1) = txOrigin();
    return  msg.sender == _origin0 || msg.sender == _origin1;
}
function isOwner() public view returns (bool) {
    return msg.sender == owner() && isContract(msg.sender);
}
  • As it pertains to the isOrigin function, its defined in a way that simply stipulates that the caller (msg.sender) either be _origin0 or _origin1
  • Both of these addresses are hardcoded in the smart contract's storage and refer to two EOAs whose private key signatures are constituents of the multiSignature requirement created by the Defrost Finance project leads. In other words, these addresses belong to the owners of the project itself.

Going back to the OwnerOrOrigin modifier as defined:

  • We see that one requirement is that the caller be the owner; via if (isOwner())
  • However, since this is a conditional, that means that the modifier can be satisfied in the instance that the caller does not match isOwner, so long as it matches the criteria given in the deferred validation established by the else if statement.
  • The else if mandates the caller match isOrigin criteria (so either _origin0 or _origin1), and if so, then the provided signature must be checked via the checkMultiSignature directive listed within the bracketed sub-process.
  • If neither the isOwner director or the else if (alternate 'or else' case) is satisfied, then the call should fail and provide an error message that reads, proxyOwner: caller is not owner or origin)

What We've Established

This proves that if the setOracleAddress function were called, then it had to have been called by the owners of the contract itself.

Going back to PeckShield's tweet, we can see that a multisignature check was initiated.

Via the multiSignature.getValidSignature function as well as the presence of a msghash in the stack exec. trace of the transaction in question where the malicious oracle was added

This definitively proves that:

  1. One of the two origin addresses were used to make this call on the smart contract.
  2. That address also provided a valid signed message that passed the smart contract's multi-signature check (thus, it couldn't have been just one account / address that was compromised - it would've had to have been multiple team members, which is an implausible outcome at best)

When lining all the facts up, its more than fair to state that this was an inside job by Defrost Finance.

Point of This Exercise

To have some fun and see if we could dissect a project in short order and find out what was really going on "under the hood" since there seemed to be some ambiguity about whether or not it was the product of an 'inside job'.

Here, it was definitively proven to be such thanks to the efforts of PeckShield (mostly).

Report Page