Authors: Jiu Jiu & Kong & Lisa

Editor: Liz

Background

On July 9, 2025, according to the SlowMist MistEye security monitoring system, the well-known decentralized trading platform GMX (@GMX_IO) was attacked, resulting in the loss of assets valued at over $42 million. The SlowMist security team immediately analyzed the incident and organized the findings as follows:

(https://x.com/SlowMist_Team/status/1942949653231841352)

Attacker address:

https://arbiscan.io/address/0xdf3340a436c27655ba62f8281565c9925c3a5221

Attack contract address:

https://arbiscan.io/address/0x7d3bd50336f64b7a473c51f54e7f0bd6771cc355

Contract address with vulnerabilities:

https://arbiscan.io/address/0x3963ffc9dff443c2a94f21b129d429891e32ec18

Attack transaction:

https://arbiscan.io/tx/0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef

Fundamental reason

The fundamental reasons for this attack mainly have two points:

  • First, GMX v1 updates the value of global short average prices (globalShortAveragePrices) when creating short positions, but does not update it when closing short positions.

  • Second, GMX v1 immediately increases the scale of global short positions (globalShortSizes) when handling short positions.

These two factors directly affect the calculation of total asset scale (AUM), leading to the manipulation of GLP token prices.

The attacker exploited this design flaw by utilizing the feature `timelock.enableLeverage` that is enabled when Keeper executes orders (a necessary condition for creating large short positions). They successfully created large short positions through reentrancy, manipulating the global average price and the scale of global short positions, artificially raising the price of GLP in a single transaction, and profiting through redemption operations.

Preparation before the attack

1. The attacker first used two front transactions to create a long position for the attack contract, and then created a reduction order that allowed Keeper to execute.

2. After Keeper receives a reduction order, it will call the executeDecreaseOrder function of the PositionManager contract to execute the reduction operation for the attack contract. This will first call the enableLeverage function of the Timelock contract to enable _isLeverageEnabled in the Vault contract to true, which is a necessary condition to create a short position directly in the subsequent attack process.

Then the executeDecreaseOrder function of the OrderBook contract will be called to execute the specific logic of the reduction. After updating the position of the attack contract, the collateral tokens obtained from the reduction will be transferred to the attack contract. Since the collateral tokens are WETH, they will be exchanged for ETH and then transferred to the attack contract, triggering the reentrancy operation constructed in the attack contract's fallback function.

3. The attack contract's fallback function will first transfer 3001 USDC to the Vault contract and call the increasePosition function of the Vault contract to open a 30x WBTC short position. Then, a corresponding closure order will be created for this position, allowing Keeper to continue calling it in the future.

In the increasePosition function, it first calls its internal validate function to check if isLeverageEnabled is true, and only when Keeper executes the order will it enable isLeverageEnabled, allowing the check to pass. Directly calling this function would not successfully open a position, which explains that the attacker's purpose in creating the reduction order is to allow Keeper to execute the reduction order while also re-entering through the fallback function to call the increasePosition function, directly creating short positions.

In addition, when opening positions through the increasePosition function, the value of globalShortAveragePrices will be updated, while the value of globalShortAveragePrices will not be updated when reducing or closing positions through the decreasePosition function. Therefore, the attacker can repeatedly create short orders with 3000 USDC and then immediately let Keeper close them, manipulating the value of globalShortAveragePrices, ultimately making it about 57 times lower than the normal price of WBTC. (It should be noted that all operations to short and close WBTC are completed by re-entering when Keeper executes the reduction order.)

The global short average price (GlobalShortAveragePrice) when shorting WBTC for the first time through reentrancy was 108757787000274036210359376021024492, and the global short average price (GlobalShortAveragePrice) just before launching the formal attack transaction was 1913705482286167437447414747675542, a difference of about 57 times.

Formal attack steps

1. After Keeper receives the last reduction order created by reentrancy, it will call the executeDecreaseOrder function of the OrderBook contract to execute the reduction, and when transferring the ETH obtained from the reduction to the attack contract, it will trigger its fallback function.

2. Among them, 7,538,000 USDC will first be borrowed from Uniswap flash loans, and then the mintAndStakeGlp function of the RewardRouterV2 contract will be called to mint 4,129,000 GLP tokens with 6 million USDC and stake them.

The AumInUsdg value obtained during minting is 46942248263037264990037614.

The global short position size (globalShortSizes) and global short average price (getGlobalShortAveragePrice) of WBTC are 15373061114092959107000000000000000 and 1913705482286167437447414747675542, respectively.

3. Immediately afterwards, the attack contract calls the increasePosition function of the Vault contract, transferring USDC into the Vault and creating a large WBTC short position worth 15.385 million.

At the end of the increasePosition function, this part of the newly opened large short position will be used to update the scale of global short positions, causing the global short position size (globalShortSizes) to be immediately increased.

4. Then the attack contract immediately called the unstakeAndRedeemGlp function to unstake and redeem GLP tokens after completing the creation of large short positions.

However, it can be seen here that only 386,000 GLP were redeemed but 9,731,000 USDG tokens were burned, and finally, 88 WBTC were transferred to the attack contract. Why is this? We continue to follow up on the _removeLiquidity function of the GlpManager contract:

When the user executes the redemption of GLP tokens, this function calculates the number of USDG tokens to be burned using the following formula: usdgAmount = _glpAmount * aumInUsdg / glpSupply. Afterwards, these USDG will be transferred to the Vault, sold to exchange back the assets the user needs to redeem (WBTC). The calculation of aum is roughly as follows:

aum = ((totalPoolAmounts - totalReservedAmounts) * price) + totalGuaranteedUsd + GlobalShortLoss - GlobalShortProfits - aumDeduction

Due to the previous creation of large short positions, the scale of global short positions was increased, and because the global average price (getGlobalShortAveragePrice) was manipulated to be far below the normal price, this portion of short positions was at a loss (i.e., hasProfit was false), causing GlobalShortLoss to increase by several hundred times, leading to amplified AUM (aum + delta). Ultimately, the attacker redeemed an amount of assets that exceeded the normal quantity using the manipulated AUM.

6. Finally, the attacker continues to utilize the manipulated AUM to repeatedly call the unstakeAndRedeemGlp function to redeem other assets in the Vault for profit.

MistTrack Analysis

According to on-chain anti-money laundering and tracking tool MistTrack analysis, the initial attacker address (0xdf3340a436c27655ba62f8281565c9925c3a5221) profited over $42 million, including:

The fund transfer situation is roughly organized as follows:

1. After the initial attacker address profited on Arbitrum, it quickly transferred assets such as WETH, WBTC, DAI to the intermediary address (0x99cdeb84064c2bc63de0cea7c6978e272d0f2dae), and used multiple DEXs and cross-chain bridges such as CoW Swap, Across Protocol, Stargate Finance, and Mayan Finance to exchange and transfer assets to Ethereum.

2. The attacker primarily exchanged USDC for DAI through CoW Swap, and then exchanged it for ETH.

3. A large amount of assets were ultimately exchanged for ETH, totaling 11,700 ETH flowing into address (0x6acc60b11217a1fd0e68b0ecaee7122d34a784c1).

It is worth noting that the attacker's initial funds came from 2 ETH transferred from Tornado Cash on July 7, which were then cross-chained to Arbitrum through Mayan Finance, providing the initial Gas for the entire attack process.

As of now, the balance situation is as follows:

  • Arbitrum address 0xdf3340a436c27655ba62f8281565c9925c3a5221, balance 10,494,796 Legacy Frax Dollar and 1.07 ETH;

  • Ethereum address 0xa33fcbe3b84fb8393690d1e994b6a6adc256d8a3, balance 3000 ETH;

  • Ethereum address 0xe9ad5a0f2697a3cf75ffa7328bda93dbaef7f7e7, balance 3000 ETH;

  • Ethereum address 0x69c965e164fa60e37a851aa5cd82b13ae39c1d95, balance 3000 ETH;

  • Ethereum address 0x639cd2fc24ec06be64aaf94eb89392bea98a6605, balance 2700 ETH.

We will continue to monitor the funds.

Summary

The core of this attack lies in the attacker exploiting the two characteristics that the Keeper system enables leverage when executing orders, and the global average price is updated when shorting but not when closing short positions. By using reentrancy to create large short positions, they manipulated the values of global short average prices and global short position sizes, thereby directly amplifying the GLP price to profit from redemptions.

The SlowMist security team recommends that project parties should add reentrancy lock protections to core functions based on their business logic and strictly limit the degree of direct influence of a single factor on prices. Furthermore, they should strengthen the auditing and security testing of the project's contract code to avoid similar situations.