DeFi AMM ํ๊ณ ๋ฒ๊ทธ ๋ฐ ๊ฐ์ ์์ก ์บ์ ์ ์ฉ
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
๊ฐ์
Yearn Finance์ yETH ํ(2025๋
11์)์ ๋ณต์กํ AMMs ๋ด๋ถ์ ๊ฐ์ค ์ ์ฝ์ฉ ์บ์๊ฐ ๊ฒฝ๊ณ ์ํ ์ ํ ์ค์ ์ ์ฐ๋์ง ์์ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ์
์ฉ๋ ์ ์๋์ง๋ฅผ ๋๋ฌ๋์ต๋๋ค. ๊ฐ์ค stableswap ํ์ ์ต๋ 32๊ฐ์ liquid staking derivatives (LSDs)๋ฅผ ์ถ์ ํ๊ณ , ์ด๋ฅผ ETH ๋๋ฑ๊ฐ์ธ virtual balances (vb_i = balance_i ร rate_i / PRECISION)๋ก ๋ณํํ์ฌ ํจํน๋ ์คํ ๋ฆฌ์ง ๋ฐฐ์ด packed_vbs[]์ ์ ์ฅํฉ๋๋ค. ๋ชจ๋ LP ํ ํฐ์ด ์๊ฐ๋๋ฉด totalSupply๋ ์ฌ๋ฐ๋ฅด๊ฒ 0์ผ๋ก ๋จ์ด์ง์ง๋ง ์บ์๋ packed_vbs[i] ์ฌ๋กฏ๋ค์ ์์ฒญ๋ ๊ณผ๊ฑฐ ๊ฐ์ ์ ์งํ์ต๋๋ค. ์ดํ์ ์๊ธ์๋ ์บ์๊ฐ ์ ๋ น ์ ๋์ฑ์ ์ฌ์ ํ ๋ณด์ ํ๊ณ ์์์์๋ ๋ถ๊ตฌํ๊ณ โ์ฒซ ๋ฒ์งธโ ์ ๋์ฑ ๊ณต๊ธ์๋ก ์ทจ๊ธ๋์๊ณ , ๊ณต๊ฒฉ์๋ ์ฝ 235 septillion yETH์ ๋จ์ง 16 wei๋ก ๋ฏผํธํ ๋ค โUSD 9M ์๋น์ LSD ๋ด๋ณด๋ฅผ ํ์ทจํ ์ ์์์ต๋๋ค.
ํต์ฌ ์์:
- Derived-state caching: ๋น์ผ ์ค๋ผํด ์กฐํ๋ฅผ ํผํ๊ธฐ ์ํด ๊ฐ์ ์์ก์ ์ง์ ์ ์ฅํ๊ณ ์ ์ง์ ์ผ๋ก ์ ๋ฐ์ดํธํฉ๋๋ค.
- Missing reset when
supply == 0:remove_liquidity()์ ๋น๋ก ๊ฐ์๊ฐ ๊ฐ ์ถ๊ธ ์ฌ์ดํด ํpacked_vbs[]์ 0์ด ์๋ ์์ฌ๊ฐ์ ๋จ๊ฒผ์ต๋๋ค. - Initialization branch trusts the cache:
add_liquidity()๊ฐ_calc_vb_prod_sum()์ ํธ์ถํ๊ณprev_supply == 0์ผ ๋ ์บ์๋ 0์ผ๋ก ์ด๊ธฐํ๋์๋ค๊ณ ๊ฐ์ ํ์ฌ ๋จ์ํpacked_vbs[]๋ฅผ ์ฝ์ต๋๋ค. - Flash-loan financed state poisoning: ์์น/์ถ๊ธ ๋ฃจํ๊ฐ ์๋ณธ ์ ๊ธ ์์ด ๋ฐ์ฌ๋ฆผ ์์ฌ๋ฅผ ์ฆํญ์์ผ โ์ฒซ ์์นโ ๊ฒฝ๋ก์์ ์น๋ช ์ ์ธ ๊ณผ๋ค ๋ฏผํธ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ์ต๋๋ค.
์บ์ ์ค๊ณ ๋ฐ ๊ฒฝ๊ณ ์ฒ๋ฆฌ ๋๋ฝ
์ทจ์ฝํ ํ๋ฆ์ ์๋์ ๊ฐ์ด ๋จ์ํ๋ฉ๋๋ค:
function remove_liquidity(uint256 burnAmount) external {
uint256 supplyBefore = totalSupply();
_burn(msg.sender, burnAmount);
for (uint256 i; i < tokens.length; ++i) {
packed_vbs[i] -= packed_vbs[i] * burnAmount / supplyBefore; // truncates to floor
}
// BUG: packed_vbs not cleared when supply hits zero
}
function add_liquidity(Amounts calldata amountsIn) external {
uint256 prevSupply = totalSupply();
uint256 sumVb = prevSupply == 0 ? _calc_vb_prod_sum() : _calc_adjusted_vb(amountsIn);
uint256 lpToMint = pricingInvariant(sumVb, prevSupply, amountsIn);
_mint(msg.sender, lpToMint);
}
function _calc_vb_prod_sum() internal view returns (uint256 sum) {
for (uint256 i; i < tokens.length; ++i) {
sum += packed_vbs[i]; // assumes cache == 0 for a pristine pool
}
}
Because remove_liquidity() only applied proportional decrements, every loop left fixed-point rounding dust. After โณ10 deposit/withdraw cycles those residues accumulated into extremely large phantom virtual balances while the on-chain token balances were almost empty. Burning the final LP shares set totalSupply to zero yet caches stayed populated, priming the protocol for a malformed initialization.
Exploit playbook (yETH case study)
- Flash-loan working capital โ ํ์ ์กฐ์ํ๋ ๋์ ์๋ณธ์ ๋ฌถ์ง ์๊ธฐ ์ํด Balancer/Aave์์ wstETH, rETH, cbETH, ETHx, WETH ๋ฑ์ ์ฐจ์ฉํ๋ค.
- Poison
packed_vbs[]โ 8๊ฐ LSD ์์ฐ์ ๋์์ผ๋ก ์์น/์ธ์ถ์ ๋ฐ๋ณตํ๋ค. ๊ฐ ๋ถ๋ถ ์ธ์ถ์packed_vbs[i] โ vb_share๋ฅผ truncatesํ์ฌ ํ ํฐ๋น >0์ ์์ฌ๋ฌผ์ ๋จ๊ธด๋ค. ๋ฐ๋ณต ๋ฃจํ๋ ์ค์ ์์ก์ด ๋์ฒด๋ก ์์๋๊ธฐ ๋๋ฌธ์ ์์ฌ์ ์ ๋ฐํ์ง ์์ผ๋ฉด์ ํฌํ ETH ์๋น ์์ก์ ๋ถํ๋ฆฐ๋ค. - Force
supply == 0โ ๋จ์์๋ ๋ชจ๋ LP ํ ํฐ์ ์๊ฐํด ํ์ด ๋น์ด์๋ค๊ณ ์ธ์ํ๊ฒ ํ๋ค. ๊ตฌํ ์์ ๋๋ฝ์ผ๋ก ์ธํด ์ค์ผ๋packed_vbs[]๋ ๊ทธ๋๋ก ๋จ๋๋ค. - Dust-size โfirst depositโ โ ์ง์๋๋ LSD ์ฌ๋กฏ๋ค์ ๊ฑธ์ณ ์ด 16 wei๋ฅผ ๋ถ๋ฐฐํด์ ๋ณด๋ธ๋ค.
add_liquidity()๋prev_supply == 0์ ๊ฐ์งํ๊ณ_calc_vb_prod_sum()์ ์คํํ๋ฉฐ ์ค์ ์์ก์์ ์ฌ๊ณ์ฐํ์ง ์๊ณ ์ค๋๋ ์บ์๋ฅผ ์ฝ๋๋ค. ๋ฐ๋ผ์ ๋ฏผํธ ๊ณ์ฐ์ ์์กฐ ๋ฌ๋ฌ๊ฐ ๋ค์ด์จ ๊ฒ์ฒ๋ผ ์๋ํด ~2.35ร10^26 yETH๋ฅผ ๋ฐํํ๋ค. - Drain & repay โ ๋ถํ๋ ค์ง LP ํฌ์ง์ ์ ๋ชจ๋ ๋ณด๊ด๋ LSD๋ก ์ํํ๊ณ , Balancer์์ yETHโWETH๋ก ์ค์ํ ๋ค Uniswap v3๋ฅผ ํตํด ETH๋ก ๋ณํํ๊ณ , ํ๋์๋ก /์์๋ฃ๋ฅผ ์ํํ ๋ค ์ด์ต์ ์ธํ(์: Tornado Cash)ํ๋ค. ์์ด์ต์ ์ฝ USD 9M์ด๊ณ ๋ณธ์ธ ์๊ธ์ ๋จ์ง 16 wei๋ง ํ์ด ์ ์ดํ๋ค.
Generalized exploitation conditions
You can abuse similar AMMs when all of the following hold:
- Cached derivatives of balances (virtual balances, TWAP snapshots, invariant helpers) persist between transactions for gas savings.
- Partial updates truncate results (floor division, fixed-point rounding), letting an attacker accumulate stateful residues via symmetric deposit/withdraw cycles.
- Boundary conditions reuse caches instead of ground-truth recomputation, especially when
totalSupply == 0,totalLiquidity == 0, or pool composition resets. - Minting logic lacks ratio sanity checks (e.g., absence of
expected_value/actual_valuebounds) so a dust deposit can mint essentially the entire historic supply. - Cheap capital is available (flash loans or internal credit) to run dozens of state-adjusting operations inside one transaction or tightly choreographed bundle.
Defensive engineering checklist
- Explicit resets when supply/lpShares hit zero:
if (totalSupply == 0) {
for (uint256 i; i < tokens.length; ++i) packed_vbs[i] = 0;
}
Apply the same treatment to every cached accumulator derived from balances or oracle data.
- Recompute on initialization branches โ When
prev_supply == 0, ignore caches entirely and rebuild virtual balances from actual token balances + live oracle rates. - Minting sanity bounds โ Revert if
lpToMint > depositValue ร MAX_INIT_RATIOor if a single transaction mints >X% of historic supply while total deposits are below a minimal threshold. - Rounding-residue drains โ Aggregate per-token dust into a sink (treasury/burn) so repeated proportional adjustments do not drift caches away from real balances.
- Differential tests โ For every state transition (add/remove/swap), recompute the same invariant off-chain with high-precision math and assert equality within a tight epsilon even after full liquidity drains.
Monitoring & response
- Multi-transaction detection โ Track sequences of near-symmetric deposit/withdraw events that leave the pool with low balances but high cached state, followed by
supply == 0. Single-transaction anomaly detectors miss these poisoning campaigns. - Runtime simulations โ Before executing
add_liquidity(), recompute virtual balances from scratch and compare with cached sums; revert or pause if deltas exceed a basis-point threshold. - Flash-loan aware alerts โ Flag transactions that combine large flash loans, exhaustive pool withdrawals, and a dust-sized final deposit; block or require manual approval.
References
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


