{"id":1085,"date":"2025-07-22T13:26:41","date_gmt":"2025-07-22T11:26:41","guid":{"rendered":"https:\/\/ackee.xyz\/blog\/?p=1085"},"modified":"2025-08-04T16:46:24","modified_gmt":"2025-08-04T14:46:24","slug":"gmx-hack-analysis-attack-scenarios-with-wake","status":"publish","type":"post","link":"https:\/\/ackee.xyz\/blog\/gmx-hack-analysis-attack-scenarios-with-wake\/","title":{"rendered":"GMX Hack Analysis &#038; Attack Scenarios with Wake"},"content":{"rendered":"<p class=\"code-line\" dir=\"auto\" data-line=\"5\">This analysis examines the\u00a0<a href=\"https:\/\/x.com\/GMX_IO\/status\/1943336664102756471\">42M attack on the GMX protocol<\/a>. We provide a detailed technical breakdown of the vulnerability and include a working reproduction of the attack scenario for educational purposes in a forked environment.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"7\">The attack exploited a\u00a0<strong>cross-contract reentrancy vulnerability<\/strong>\u00a0that bypassed access controls during position increases. This resulted in GLP token price manipulation with a higher price, allowing attackers to redeem tokens at the manipulated price and extract profits from the protocol.<\/p>\n<p dir=\"auto\" data-line=\"7\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1086\" src=\"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/07\/cmd_result.png\" sizes=\"auto, (max-width: 1434px) 100vw, 1434px\" srcset=\"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/07\/cmd_result.png 1434w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/07\/cmd_result-300x133.png 300w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/07\/cmd_result-1024x456.png 1024w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/07\/cmd_result-768x342.png 768w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/07\/cmd_result-370x165.png 370w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/07\/cmd_result-760x338.png 760w\" alt=\"wake test output\" width=\"1434\" height=\"638\" \/><\/p>\n<h2 dir=\"auto\" data-line=\"12\">Reproducing with Wake<\/h2>\n<ol>\n<li class=\"code-line\" dir=\"auto\" data-line=\"14\">Clone the\u00a0<a href=\"https:\/\/github.com\/Ackee-Blockchain\/gmx-exploit-scenario\" target=\"_blank\" rel=\"noopener\">repository<\/a><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"16\">Import GMX project dependencies:<br \/>\n<code class=\"codehl\">$ npm i<\/code><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"20\">Initialise Wake:<br \/>\n<code class=\"codehl\">$ wake up<\/code><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"24\">Get an Arbitrum fork URL from Alchemy or other providers and set it in\u00a0<code class=\"codehl\">.env<\/code>\u00a0similar to\u00a0<code class=\"codehl\">.env.example<\/code>.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"26\">Run the test:<br \/>\n<code class=\"codehl\">$ wake test tests\/test_attack_simple.py<\/code><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"30\">Uncomment\u00a0<code class=\"codehl\">print(tx.call_trace)<\/code>\u00a0to see the call trace.<\/li>\n<\/ol>\n<h2 dir=\"auto\" data-line=\"33\">Root cause<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"35\">The vulnerability stems from a reentrancy issue. While the reentrancy itself is straightforward, its impact is significant.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"37\">The core issue: GLP token price calculation relies on the\u00a0<code class=\"codehl\">globalShortAveragePrices<\/code>\u00a0variable from\u00a0<code class=\"codehl\">ShortsTracker<\/code>. This dependency creates an exploitable attack vector.<\/p>\n<p dir=\"auto\" data-line=\"37\">The vulnerability is cross-contract reentrancy. Multiple contracts were involved during the transaction. Each contract has a reentrancy guard; however, reentrancy occurred after it had already exited for one specific contract.<\/p>\n<div>\n<div>For a detailed explanation of cross-contract reentrancy, see\u00a0<a href=\"https:\/\/ackee.xyz\/blog\/cross-contract-reentrancy-attack\/\">this comprehensive guide<\/a>.<\/div>\n<\/div>\n<h2 dir=\"auto\" data-line=\"39\">Entry point<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"41\">The attack begins when a user increases their position:<\/p>\n<ol class=\"code-line\" dir=\"auto\" data-line=\"43\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"43\">User calls\u00a0<code class=\"codehl\">createIncreaseOrder<\/code>\u00a0to register the order<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"44\">The orderkeeper bot calls\u00a0<code class=\"codehl\">PositionManager.executeIncreaseOrder<\/code>\u00a0to execute it<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"45\">Within\u00a0<code class=\"codehl\">executeIncreaseOrder<\/code>,\u00a0<code class=\"codehl\">ShortsTracker.updateGlobalShortData<\/code>\u00a0is invoked<\/li>\n<\/ol>\n<p class=\"code-line\" dir=\"auto\" data-line=\"47\"><code class=\"codehl\">ShortsTracker.updateGlobalShortData<\/code>\u00a0stores the\u00a0<code class=\"codehl\">globalShortAveragePrice<\/code>\u00a0for the token \u2014 the average entry price of all short positions. This value directly influences GLP token price calculations.<\/p>\n<pre><code class=\"language-solidity\">contract PositionManager {\n    function executeIncreaseOrder(\n        address _account,\n        uint256 _orderIndex,\n        address payable _feeReceiver\n    ) external onlyOrderKeeper {\n        \/\/...\n        IShortsTracker(shortsTracker).updateGlobalShortData(_account, collateralToken, indexToken, isLong, sizeDelta, markPrice, true);\n        ITimelock(timelock).enableLeverage(_vault); \/\/ isLeverageEnabled &lt;- True\n        IOrderBook(orderBook).executeIncreaseOrder(_account, _orderIndex, _feeReceiver);\n        ITimelock(timelock).disableLeverage(_vault); \/\/ isLeverageEnabled &lt;- False\n        _emitDecreasePositionReferral(_account, sizeDelta);\n    }\n}<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"68\">External calls follow this path to reach the\u00a0<code class=\"codehl\">Vault<\/code>:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"70\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"70\"><code class=\"codehl\">OrderBook.executeIncreaseOrder<\/code>\n<ul>\n<li class=\"code-line\" dir=\"auto\" data-line=\"71\"><code class=\"codehl\">Router.pluginIncreasePosition<\/code>\n<ul>\n<li class=\"code-line\" dir=\"auto\" data-line=\"72\"><code class=\"codehl\">Vault.increasePosition<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"74\">The\u00a0<code class=\"codehl\">decreasePosition<\/code>\u00a0flow follows a similar pattern.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"76\">The\u00a0<code class=\"codehl\">Vault.increasePosition<\/code>\u00a0function checks that\u00a0<code class=\"codehl\">isLeverageEnabled<\/code>\u00a0equals\u00a0<code class=\"codehl\">True<\/code>\u00a0to verify that the call occurs between\u00a0<code class=\"codehl\">Timelock.enableLeverage<\/code>\u00a0and\u00a0<code class=\"codehl\">Timelock.disableLeverage<\/code>. This check proved insufficient.<\/p>\n<pre><code class=\"language-solidity\">contract Vault {\n    \/\/ function has no msg.sender check.\n    \/\/ Assumes caller transfers tokens or at least the caller is trusted.\n    function increasePosition(\n        address _account,\n        address _collateralToken,\n        address _indexToken,\n        uint256 _sizeDelta,\n        bool _isLong\n    ) external override nonReentrant {\n        _validate(isLeverageEnabled, 28); \/\/ this will be bypassed\n        _validateGasPrice();\n        _validateRouter(_account);\n        ...\n        ...\n    }\n}<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"97\">During\u00a0<code class=\"codehl\">Vault.decreasePosition<\/code>, the contract transfers collateral tokens for closed positions. When the collateral token is WETH, the system withdraws ETH and transfers it to the user\u2019s account. Notably, these WETH operations occur outside the\u00a0<code class=\"codehl\">Vault<\/code>\u00a0contract.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"100\">The call flow proceeds as follows:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"102\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"102\"><code class=\"codehl\">OrderBook.executeDecreaseOrder<\/code>\n<ul>\n<li class=\"code-line\" dir=\"auto\" data-line=\"103\"><code class=\"codehl\">Router.pluginDecreasePosition<\/code>\n<ul>\n<li class=\"code-line\" dir=\"auto\" data-line=\"104\"><code class=\"codehl\">Vault.decreasePosition<\/code>\n<ol>\n<li class=\"code-line\" dir=\"auto\" data-line=\"105\">ReentrancyGuard set to\u00a0<code class=\"codehl\">ENTERED<\/code><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"106\"><code class=\"codehl\">Vault<\/code>\u00a0closes position<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"107\">Sends WETH to OrderBook<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"108\">ReentrancyGuard set to\u00a0<code class=\"codehl\">NOT_ENTERED<\/code><\/li>\n<\/ol>\n<\/li>\n<\/ul>\n<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"110\"><code class=\"codehl\">OrderBook<\/code>\u00a0withdraws ETH<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"111\">Transfers ETH to user\n<ul>\n<li class=\"code-line\" dir=\"auto\" data-line=\"112\"><code class=\"codehl\">User.receive<\/code>\u00a0is triggered\n<ul>\n<li class=\"code-line\" dir=\"auto\" data-line=\"113\"><code class=\"codehl\">Vault.increasePosition<\/code>\u00a0(exploit)\n<ol>\n<li class=\"code-line\" dir=\"auto\" data-line=\"114\">ReentrancyGuard confirms\u00a0<code class=\"codehl\">NOT_ENTERED<\/code><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"115\">ReentrancyGuard is set to\u00a0<code class=\"codehl\">ENTERED<\/code><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"116\">Attack continues\u2026<\/li>\n<\/ol>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"119\">The reentrancy guard in\u00a0<code class=\"codehl\">Vault<\/code>\u00a0starts as\u00a0<code class=\"codehl\">NOT_ENTERED<\/code>, but the reentrant call occurs after this status has been reset, bypassing the protection.<\/p>\n<h2 dir=\"auto\" data-line=\"121\">Attack escalation<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"123\">The direct\u00a0<code class=\"codehl\">Vault.increasePosition<\/code>\u00a0call bypasses\u00a0<code class=\"codehl\">ShortsTracker.updateGlobalShortData<\/code>, causing\u00a0<code class=\"codehl\">GlpManager.getAum<\/code>\u00a0to return inflated values and artificially increase the GLP token price.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"125\">Attack sequence:<\/p>\n<ol class=\"code-line\" dir=\"auto\" data-line=\"126\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"126\">Re-enter through the open entry point<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"127\">Add liquidity to obtain GLP tokens<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"128\">Call\u00a0<code class=\"codehl\">increasePosition<\/code>\u00a0to manipulate the GLP token price upward<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"129\">Remove liquidity at the inflated GLP token price<\/li>\n<\/ol>\n<h2 dir=\"auto\" data-line=\"132\">Operational details<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"134\">The attacker uses\u00a0<code class=\"codehl\">RewardRouterV2.mintAndStakeGlp<\/code>\u00a0because\u00a0<code class=\"codehl\">GLPManager.inPrivateMode<\/code>\u00a0is enabled, preventing direct calls to\u00a0<code class=\"codehl\">GLPManager.addLiquidity<\/code>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"136\">The attacker used a flash loan with USDC in fallback function to create a large WBTC short position.<\/p>\n<h2 dir=\"auto\" data-line=\"138\">Summary<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"140\">The attack succeeded due to fragmented data responsibility across contracts. Critical state information was split between\u00a0<code class=\"codehl\">ShortsTracker<\/code>\u00a0and\u00a0<code class=\"codehl\">Vault<\/code>, rendering the reentrancy guard ineffective. This architectural vulnerability allowed attackers to manipulate GLP token prices through carefully orchestrated re-entrant calls, enabling the multimillion exploit.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This analysis examines the\u00a042M attack on the GMX protocol. We provide a detailed technical breakdown of the vulnerability and include a working reproduction of the attack scenario for educational purposes in a forked environment. The attack exploited a\u00a0cross-contract reentrancy vulnerability\u00a0that bypassed access controls during position increases. This resulted in GLP token price manipulation with a higher price, allowing attackers to redeem tokens&hellip;<\/p>\n","protected":false},"author":24,"featured_media":1036,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61,10,85,84,80,63,103],"tags":[96,24,33,14,64,28,104],"class_list":["post-1085","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-education","category-ethereum","category-exploits","category-hacks","category-solidity","category-tutorial","category-wake","tag-educational","tag-ethereum","tag-evm","tag-exploit","tag-security","tag-smart-contract","tag-wake"],"aioseo_notices":[],"featured_image_src":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/04\/Flash-Loan-Reentrancy-Attack-1-600x400.png","featured_image_src_square":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2025\/04\/Flash-Loan-Reentrancy-Attack-1-600x600.png","author_info":{"display_name":"Naoki Yoshida","author_link":"https:\/\/ackee.xyz\/blog\/author\/naoki-yoshida\/"},"_links":{"self":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/1085","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/users\/24"}],"replies":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/comments?post=1085"}],"version-history":[{"count":0,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/1085\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media\/1036"}],"wp:attachment":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media?parent=1085"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/categories?post=1085"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/tags?post=1085"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}