Dx Protocol: Repeated Withdrawals via unlockToken() Timing Bug

BSC • Address: 0xeb3a…e449

Published on May 28, 2025

Abstract

We discovered a critical timing vulnerability in Dx Protocol's token locking mechanism through EVM decompilation. The unlockToken() function contains a race condition that allows attackers to withdraw locked tokens multiple times, potentially draining the entire contract balance. This analysis demonstrates how decompiling unverified contracts can reveal hidden security flaws that put millions of dollars at risk.

We decompiled an unverified Dx Protocol contract on BSC using EVMDecompiler and uncovered a logic flaw in unlockToken().

The issue

If unlockToken() is called before unlockTime, the function still transfers tokens to the caller but leaves isLocked set to true. This allows the caller to repeatedly invoke unlockToken() and withdraw again and again.

Decompiled fragment

function unlockToken(uint256 _tokenId) public {
    require(tokenLocks[msg.sender][_tokenId].isLocked, "Token is already unlocked");
    require(tokenLocks[msg.sender][_tokenId].unlockTime != 0, "Token is not locked");
    if (block.timestamp > tokenLocks[msg.sender][_tokenId].unlockTime) {
        tokenLocks[msg.sender][_tokenId].isLocked = false;
    }

    uint256 amount = tokenLocks[msg.sender][_tokenId].amount;
    require(IERC20(tokenLocks[msg.sender][_tokenId].tokenAddress).balanceOf(address(this)) >= amount, "Not enough balance");
    require(IERC20(tokenLocks[msg.sender][_tokenId].tokenAddress).transfer(msg.sender, amount), "Failed transfer");
    emit Unlocked(msg.sender, tokenLocks[msg.sender][_tokenId].tokenAddress, amount, block.timestamp);
}

Why it breaks

Impact

Estimated exposure was approximately $5.2M, hidden entirely in bytecode due to the contract being unverified at the time of analysis.

Fix

Reproduce

  1. Load the address in the EVMDecompiler.
  2. Locate unlockToken() in the decompiled output.
  3. Observe the conditional write to isLocked versus the unconditional transfer path.

Disclosure note: This post is for educational purposes and responsible vulnerability awareness.