Taking a Closer Look at NORMIE Exploit

6 min read

Learn how Normie was exploited, resulting in a loss of assets worth approximately $881,686.

TL;DR#

On May 26, 2024, the memecoin NORMIE was exploited on the Base network due to a smart contract vulnerability, which resulted in a loss of 224.98 ETH, worth approximately $881,686.

Introduction to NORMIE#

NORMIE is a memecoin on the Base network.

Vulnerability Assessment#

The root cause of the exploit is a vulnerability in the smart contract logic that allowed the attacker to manipulate the contract into recognizing their address as privileged. This manipulation enabled unauthorized minting of tokens, bypassing critical security checks and leading to a substantial financial loss.

Incident Analysis#

We attempt to analyze the attack transaction executed by the exploiter.

The attacker initially used 2 ETH from Sushi Router and began swapping them for 171,955 NORMIE tokens. After swapping roughly 5 million NORMIE tokens, their asset amount was equal to the balance of the token deployer address.

Consequently, as they swapped tokens equal to the balance of the deployer, the address of the attack contract was added to the `_premarket_user` list through the vulnerable `_get_premarket_user` function of the exploited contract.

function _get_premarket_user(address _address, uint256 amount) internal {
  premarket_user[_address] = !premarket_user[_address] ? (amount == balanceOf(teamWalletAddress)) : premarket_user[_address];
}

This function checks if the recipient address is not already a premarket user; if so, it will check if the amount they are buying is the same as the balance in the team wallet, and if that's the case, it will grant them permission as a premarket user.

Thus, all the exploiter had to do was somehow maintain the same balance as the team's wallet.

The exploiter took a flash loan of 11,333,141 NORMIE tokens and swapped only 9,066,513 of them for 65.97 ETH to manipulate the token supply. The remaining 2,266,628 NORMIE tokens were made to the Uniswap V2 pair, and then the skim function of the Uniswap factory was invoked to ultimately withdraw these assets.

As a result of this vulnerability, any address added to earlier list would be able to trigger the minting of the NORMIE tokens in the contract itself. The attack contract, being recognized as a premarket_user, was granted the NORMIE tokens by the underlying token contract.

function _transfer(address sender, address recipient, uint256 amount) private returns (bool) {
  emit inSwapAndLiquifyStatus(inSwapAndLiquify);
  require(sender != address(0), "ERC20: transfer from the zero address");
  require(recipient != address(0), "ERC20: transfer to the zero address");

  if (sender != owner() && recipient != owner()) require(enableTrading, "cannot trade before the market opening");

  if (inSwapAndLiquify) {
    return _basicTransfer(sender, recipient, amount);
  } else {
    if (!isTxLimitExempt[sender] && !isTxLimitExempt[recipient]) {
      require(amount <= _maxTxAmount, "Transfer amount exceeds the maxTxAmount.");
    }
    uint256 fAmount = (uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % 11) + 90;
    uint256 tokensToSwap = _minimumTokensBeforeSwap.mul(fAmount).div(100);
    uint256 contractTokenBalance = balanceOf(address(this));
    bool overMinimumTokenBalance = contractTokenBalance >= tokensToSwap;
    emit stepLiquify(overMinimumTokenBalance, !inSwapAndLiquify, !isMarketPair[sender], swapAndLiquifyEnabled);
    if (isMarketPair[sender] && !isExcludedFromFee[recipient] && premarket_user[recipient]) {
      _balances[address(this)] = _balances[address(this)].add(amount);
    }
    if (overMinimumTokenBalance && !inSwapAndLiquify && !isMarketPair[sender] && swapAndLiquifyEnabled && !isExcludedFromFee[sender]) {
      if (swapAndLiquifyByLimitOnly) contractTokenBalance = tokensToSwap;
      swapAndLiquify(contractTokenBalance);
    }
    _balances[sender] = _balances[sender].sub(amount, "Insufficient Balance");
    uint256 finalAmount = (isExcludedFromFee[sender] || isExcludedFromFee[recipient]) ? amount : takeFee(sender, recipient, amount);
    if (checkWalletLimit && !isWalletLimitExempt[recipient]) require(balanceOf(recipient).add(finalAmount) <= _walletMax);
    _balances[recipient] = _balances[recipient].add(finalAmount);
    _get_premarket_user(recipient, amount);
    emit Transfer(sender, recipient, finalAmount);
    return true;
  }
}

The premarket_user map bypasses the checks within the swapAndLiquify function to mint additional tokens. The contract only checks for a wallet as a valid entity in premarket_user mapping if the token balance of the said wallet address is the same as the team wallet, which is then triggered to sell 4.65 million newly minted NORMIE tokens each time.

function swapAndLiquify(uint256 tAmount) private lockTheSwap {
  uint256 tokensForLP = tAmount.mul(_liquidityShare).div(_totalDistributionShares).div(2);
  uint256 tokensForSwap = tAmount.sub(tokensForLP);
  swapTokensForEth(tokensForSwap);
  uint256 amountReceived = address(this).balance;
  emit eventSwapAndLiquify(amountReceived);
  uint256 totalBNBFee = _totalDistributionShares.sub(_liquidityShare.div(2));
  uint256 amountBNBLiquidity = amountReceived.mul(_liquidityShare).div(totalBNBFee).div(2);
  uint256 amountBNBTeam = amountReceived.mul(_teamShare).div(totalBNBFee);
  uint256 amountBNBMarketing = amountReceived.sub(amountBNBLiquidity).sub(amountBNBTeam);

  emit teamGetBnb(amountBNBTeam);
  emit marketingGetBnb(amountBNBMarketing);
  emit liquidityGetBnb(amountBNBLiquidity);

  if (amountBNBMarketing > 0) transferToAddressETH(teamWalletAddress, amountBNBMarketing);

  if (amountBNBTeam > 0) transferToAddressETH(devWalletAddress, amountBNBTeam);

  if (amountBNBLiquidity > 0 && tokensForLP > 0) addLiquidity(tokensForLP, amountBNBLiquidity);
}

The exploiter later swapped 0.5 ETH for approximately 11,040,494 NORMIE tokens at a much lower price following its price manipulation to ultimately repay the borrowed NORMIE tokens.

The token supply of the exploited assets came to 650 billion NORMIE tokens, despite only having a total supply of 1 billion tokens. The profit from these trades for the attacker was 224.98 ETH, which was worth approximately $881,686.

In short, the exploiter bought the 5 million tokens (this is the value in the team's wallet), got the contract's permission, and then used a flash loan to drain the contract, leaving the pool at zero value. At this point in time, they bought the tokens for essentially no cost and then sold them, as people jumped onto the bandwagon to purchase the dip time and again.

Aftermath#

Following the exploit, the exploiter took to X (formerly Twitter) to acknowledge their efforts. One of their demands is to have the CEO of the Normie team step down from their position. As per this first on-chain message to the Normie deployer, they offer to return 90% of the exploited ETH by rewarding them with the remaining 10% as a bounty. Their next condition is to have the 600 ETH in the developer wallet to be used in the fair launch of a new token that will reimburse the NORMIE holders affected during the incident.

According to the second and third on-chain communication between them, they cite that the developer of this meme coin made significantly more (830 ETH) than what the exploiter was profiting from during the exploit.

Solution#

The exploit of the NORMIE token was a clear consequence of poor contract design, highlighting critical flaws in the smart contract logic. The vulnerability allowed the attacker to manipulate the contract into recognizing their address as privileged, leading to the unauthorized minting of tokens and bypassing crucial security checks. This incident underscores the importance of robust contract design and thorough security audits before deployment.

To address this exploit and prevent future occurrences, several measures must be taken. Firstly, the smart contract needs to be redesigned to eliminate the vulnerability in the vulnerable functions. These functions should incorporate stringent checks to prevent unauthorized addresses from being added to the premarket user list. Ensuring that only verified and trusted addresses can gain such privileges is essential. Additionally, implementing a multi-signature approval process for significant actions, such as token minting, can add an extra layer of security.

Secondly, it is crucial to review and audit any forked code from the exploited contract. Since forked contracts are inherently susceptible to the same vulnerabilities, a comprehensive audit should be conducted to identify and rectify any inherited flaws. This proactive approach can mitigate the risk of similar exploits in the future.

The team's involvement during the exploit raises serious ethical concerns. Instead of taking decisive action to contain the exploit, it appears that the team may have profited from the situation. This behavior undermines trust and raises questions about their integrity. If the developer was willing to exploit the situation for personal gain, it is plausible that they could have eventually rugged the users, causing even greater harm.

Rebuilding trust within the community requires transparency and accountability. The team must provide a detailed account of their actions during the exploit and take responsibility for any unethical behavior. Establishing a clear protocol for incident response, including immediate suspension of trading and public communication, can help manage such situations more effectively in the future.

Reference Source CertiK

By

Tags