In a previous article titled The Vanishing Act: How Exit Scammers Mint New Tokens Undetected, we unveiled a large-scale exit scam targeting IPO robots, orchestrated by the automation address 0xdf1a. This address was responsible for over 200 exit scams within approximately two months.
Previously, we used the $MUMI token to illustrate the exit scamming tactics of this group: implementing a backdoor in the code to directly alter the token balance of the tax collection address, without adjusting the total supply of tokens and without triggering Transfer
events. These tactics were designed to obscure any secretive token minting by the project team from users reviewing transactions on Etherscan.
In this article, we use the ZhongHua token as an example to explore another rug pull tactic employed by the same address — this one utilizing complex tax function logic to conceal transfer functions that facilitate rug pulls.
The project team managed to exchange 9.99 trillion ZhongHua tokens for approximately 5.884 WETH, effectively draining the pool's liquidity.
To fully understand this scam, let’s start with a chronological analysis of the events.
At 1:40 AM on January 18 (UTC time, used hereafter), the attacker's address (😈0x74fc) deployed the ERC20 token named ZhongHua (🪙0x71d7) and pre-mined one billion tokens, which were subsequently transferred to the attacker's same address (😈0x74fc).
The quantity of pre-mined tokens aligns with the specifications in the contract's source code.
Ten minutes after the token's creation, at 1:50 AM, the attacker's address (😈0x74fc) granted approval for the ZhongHua token to the Uniswap V2 Router, setting the stage to add liquidity.
A minute later, the attacker used the addLiquidityETH
function on the Router to establish a ZhongHua-WETH liquidity pool (🦄0x5c8b), contributing all the pre-mined tokens and 1.5 ETH, and subsequently receiving 1.225 LP tokens.
From the token transfer records mentioned, we observe a peculiar transfer where the attacker sent 0 tokens to the ZhongHua token contract itself.
This transfer, not typical for added liquidity, prompted a review of the token contract source code, revealing a function named _getAmount
. This function is responsible for deducting the transfer fee from the sender's address and sending this fee to the token's address, thereby triggering a Transfer event to record the receipt of the fee by the token address.
The _getAmount
function verifies if the sender of the transfer is the _owner
. If so, the transaction fee is set to zero. The _owner
is designated during the deployment of the Ownable contract, based on the input parameter of the constructor.
The ZhongHua token contract, being an extension of the Ownable contract, utilizes the deployer's msg.sender
as the input parameter for the Ownable constructor.
As a result, the attacker's address (😈0x74fc) is the _owner
of the token contract. The aforementioned transfer of 0 tokens during the liquidity addition process is conducted through the _getAmount
function, which is invoked within the transfer
and transferFrom
functions.
At 1:51 AM, within a minute of creating the liquidity pool, the attacker's address sent all of the 1.225 LP tokens—acquired through liquidity addition—directly to the 0xdead address, effectively locking the LP tokens permanently.
Similar to the MUMI token scenario, once the LP tokens are locked, the attacker's address (😈0x74fc) theoretically loses the ability to conduct a rug pull by withdrawing liquidity.
In the rug pull scam orchestrated by address 0xdf1a, which targets new coin listing robots, this step is designed to mislead the anti-fraud scripts of these bots.
Up to this point, from the perspective of users, it appears that all pre-mined tokens have been added to the liquidity pool without any irregularities.
At 2:10 AM, approximately 30 minutes after the creation of the ZhongHua token, the second attacker's address (👹0x5100) deployed a specific contract (🔪0xc403) specifically for the purpose of the rug pull.
Similar to the MUMI token scenario, the attackers did not use the same address that deployed the ZhongHua token contract for the rug pull, and the contract used (🔪0xc403) is not open source. These tactics are designed to complicate the tracing process for technical analysts, a common feature in most rug pull scams.
At 7:46 AM, roughly six hours after the token contract was initiated, the second attacker's address (👹0x5100) executed the rug pull. They invoked the swapExactETHForTokens
method of the attack contract (🔪0xc403), transferring out 9.99 trillion ZhongHua tokens in exchange for approximately 5.884 ETH, effectively draining most of the liquidity from the pool.
Since the attack contract (🔪0xc403) is not open source, we decompiled its bytecode, with the results viewable here.
The primary function of the swapExactETHForTokens
method in the attack contract (🔪0xc403) starts by using approve
to grant the Uniswap V2 Router the authority to transfer the maximum quantity of ZhongHua tokens, then uses the Router to exchange a specified number of "xt" ZhongHua tokens (held by the attack contract 🔪0xc403) for ETH. The ETH is subsequently transferred to the _rescue
address specified within the attack contract (🔪0xc403).
The _rescue
address matches the deployer of the attack contract (🔪0xc403), which is the second attacker's address (👹0x5100).
The input parameter "xt" for this rug pull transaction was 999,000,000,000,000,000,000, corresponding to 9.99 trillion ZhongHua tokens (ZhongHua has 9 decimals).
Ultimately, the project team used 9.99 trillion ZhongHua tokens to drain the liquidity pool of WETH, thus completing the RugPull.
Similar to the MUMI case discussed earlier, it is essential to verify the origin of the ZhongHua tokens in the attack contract (🔪0xc403). Despite the earlier finding that the total supply of ZhongHua tokens is 1 billion, post-rug pull, the block explorer still shows a total supply of 1 billion. Yet, the quantity of tokens sold by the attack contract (🔪0xc403) was 9.99 trillion, 999 times greater than the recorded total supply. The question then arises: where did these excess tokens, far surpassing the total supply, originate?
We examined the ERC20 transfer event history of the contract and observed that, similar to the rug pull case of the MUMI token, there were no incoming ERC20 token transactions for the attack contract (🔪0xc403) in the case of ZhongHua.
In the MUMI instance, tokens in the tax contract originated from a direct modification of the balance within the token contract itself, enabling the tax contract to possess an amount of tokens vastly exceeding the total supply. As the MUMI token contract did not adjust the totalSupply in correspondence with the balance changes, nor did it trigger a Transfer event, the influx of tokens into the tax contract seemed to materialize from nowhere.
Turning back to the ZhongHua case, the tokens in the attack contract (🔪0xc403) also appeared to materialize without a trace. Therefore, we inspected the ZhongHua token contract for any modifications to the "balance" keyword.
Our findings indicated only three instances where the balance variable was altered, specifically in the _getAmount
, _transferFrom
, and _transferBasic
functions.
The _getAmount
function deals with transaction fee logic, while _transferFrom
and _transferBasic
manage the transfer processes. Notably, none of these modifications involved direct alterations to the balance similar to what was seen in the MUMI token scenario.
Crucially, whereas the MUMI token contract's direct balance modifications did not trigger a Transfer event, in the ZhongHua token contract, modifications by _getAmount
, _transferFrom
, or _transferBasic
all properly triggered a Transfer event following balance changes. This discovery contradicts our initial findings—we could not identify any Transfer events linked to incoming tokens when we examined the event history for the attack contract (🔪0xc403).
This raises the question: could it be possible that, unlike in the MUMI case, the tokens in the attack contract (🔪0xc403) for the ZhongHua token genuinely materialized from nothing?
During our analysis, we noted that each modification of the balance by the ZhongHua contract correctly triggers a Transfer
event. Despite this, we found no record of token inflow or any Transfer
events linked to the attack contract (🔪0xc403), prompting us to seek a new analytical approach.
We scrutinized numerous transfer records and initially considered the performZhongSwap
function as a potential breakthrough. This function is designed for selling tokens within the token contract. In other rug pull cases we've analyzed, similar functions have been exploited as backdoors for the scam.
After inspecting various functions without success, we shifted our focus to the core transfer
function, which is central to how the attacker executed the rug pull. The most critical insights were found here.
The transfer function in the token contract invokes the _transferFrom
function directly.
At first glance, it appears to perform standard token transfer operations, triggering a Transfer
event upon completion.
However, before executing the transfer, the transfer
function uses the _isNotTax
function to check if the sender is a tax-exempt address. If the sender is not tax-exempt, the _getAmount
function is employed to collect taxes; if the sender is tax-exempt, it skips tax collection and directly sends the tokens to the recipient. The critical issue lies in this mechanism.
As previously mentioned, the _getAmount
function verifies the sender's balance, deducts the required amount, and then transfers the trading fee to the token contract.
The problem arises when the sender is a tax-exempt address. In such cases, _getAmount
is bypassed, and the transfer adds tokens directly to the recipient's balance without verifying or deducting from the sender's balance. Thus, a tax-exempt address, as defined in the token contract, can effectively send any amount of tokens to any recipient. This loophole is what allowed the attack contract (🔪0xc403) to transfer an astronomical amount of tokens—999 times the total declared supply.
Further investigation revealed that the token contract designates only the _taxReceipt
address as tax-exempt in its constructor. Crucially, the address assigned to _taxReceipt
is precisely the attack contract (🔪0xc403).
This analysis uncovers how the ZhongHua token's rug pull was pulled off. The attacker exploited a designed oversight in the token contract that exempted certain addresses from balance checks, allowing these addresses to issue vast numbers of tokens out of thin air, thus completing the rug pull. This methodical exploitation of the contract’s features highlights a critical vulnerability in its design, emphasizing the need for rigorous security checks in smart contract development.
Leveraging the vulnerability previously described, the second attacker's address (👹0x5100) directly invoked the swapExactETHForTokens
function of the privileged attack contract (🔪0xc403) to execute the rug pull. Within this function, the attack contract (🔪0xc403) authorized the Uniswap V2 Router to transfer tokens and subsequently initiated the Router's token exchange function, exchanging 999 billion ZhongHua tokens for 5.88 ETH from the liquidity pool.
Additionally, beyond the primary rug pull transaction, the project team conducted 11 separate sales of tokens via the attack contract (🔪0xc403) midway through the operation, amassing a total of 9.64 ETH. Combined with the final rug pull transaction, they accrued a total of 15.52 ETH. The associated costs were minimal, consisting only of 1.5 ETH used to add liquidity, a modest contract deployment fee, and a small amount of ETH spent on transactions designed to lure market-making bots.
Moreover, the project team utilized different Externally Owned Account (EOA) addresses to engage with this attack contract (🔪0xc403) for intermittent token sales, creating the illusion of diverse sellers. This strategy was intended to mask their ongoing cashing-out.
Reflecting on the entire ZhongHua token rug pull scenario, we understand that the underlying method was straightforward—disabling the balance checks for privileged addresses. However, analyzing this case was challenging for several reasons:
Security experts typically view balance verification as a fundamental security feature, often assuming such functions will automatically perform balance checks. This assumption leads to a decrease in vigilance against such vulnerabilities, which are deemed too basic for attackers to exploit. From the attacker's perspective, however, the simplest methods—like bypassing balance checks—tend to be the most effective and the least likely to be anticipated.
The project team deliberately obscured the code that exempted privileged addresses from balance checks. They also implemented comprehensive tax, transfer, and reinvestment logic for non-privileged addresses, making the overall transfer logic of the token appear legitimate and indistinguishable from regular transactions without a thorough inspection.
In comparison, both the MUMI and ZhongHua token cases allowed privileged addresses to control substantial volumes of tokens covertly. The MUMI case involved direct manipulation of balances without triggering a Transfer
event or altering totalSupply
, preventing users from noticing the privileged addresses' extensive token holdings.
The ZhongHua scenario was more sophisticated, completely bypassing balance checks for privileged addresses, which meant that conventional detection methods were ineffective unless the source code was inspected.
The ZhongHua token rug pull underscores the inherent security vulnerabilities within the ERC20 token standard. While this standard is designed to ensure operational uniformity and security under normal circumstances, it is ill-equipped to thwart malicious entities who embed hard-to-detect backdoors within the standard business logic. Standardizing token behavior, although restricting functional flexibility, might help eliminate such hidden backdoors and enhance overall security measures against such exploitation.