Skip to content

paco0x/kyber-exploit-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Here's the reproduction of the Kyber elastic exploit. For more details on the underlying issue, please see this link.

This might be one of the most intricate DeFi protocol exploits to date, requiring highly accurate and specific parameters for successful attack transactions. A discrepancy as small as 1 wei could lead to different outcomes.

I was intrigued by how the hacker managed to determine the exact parameters needed for various Kyber pools.

At first, I tried using some random numbers to reproduce this attack. But it was unsuccessful and it turned out that obtaining the the correct parameters is difficult with a very low likelihood of success.

Eventually, I found that Foundry could be used to perform brute force testing to pinpoint vulnerable parameters. (Kudos to Foundry for executing Solidity code at blazing fast speeds!)

As of now, this repository only includes methods using brute force—is there a better approach?

Reproduce the exploit in local test pool

This test setup a 1:1 pool and brute forcing to get a value to exploit the pool.

❯ forge test -vvv --mt testBruteForceExploit

It will take a while to run this test, since it's brute forcing.

A particle attack may leverage a forked network to pre-calculate necessary values, which are then utilized in the attack transaction.

Fuzz test

Run fuzz to test if it can get an exploitable value (only 1 fuzz variable).

By default, foundry fuzzer only generate 256 cases, which is not enough to get a exploitable value. I've changed the config to 65535 so that the fuzz test can take effect on this simple case.

❯ forge test -vvv --mt testFuzz
[⠊] Compiling...
No files changed, compilation skipped

Running 1 test for test/Kyber.t.sol:KyberAttack
[FAIL. Reason: Assertion failed. Counterexample: calldata=0x0x6590d6450000000000000000000000000000000030303030303030303030363636390000, args=[64053151420411946063694050374803062784 [6.405e37]]] testFuzz(uint128) (runs: 8493, μ: 9713, ~: 9800)
Logs:
  Bound Result 67288735232761868185
  liquidity 67288735232761868185
  targetSqrtP 20693058119558072255662180724088
  nextSqrtP 20693058119558072255665665854997
  Error: a == b not satisfied [bool]
    Expected: true
      Actual: false
  ...

This test is expected to fail because the invariant cannot consistently be met in Kyber. I've simplified and focused on specific mathematical operations that impact the invariant for demonstration. In real production environment, we typically run fuzz tests on a higher-level swap functions, making the failure identification more challenging.

Probability analysis

By utilizing the exceptionally fast foundry execution, we can evaluate the chances of getting a liquidity amount within a specified LP range that can be used for exploiting the protocol.

❯ forge test -vvv -mt testProbability

This runs a simple for loop to get the valid values(liquidity amount) found in a given LP range.

Some of the found numbers when setting liquidity to [80e18, 80e18+1e8]:

❯ forge test -vvv -mt testProbability
  ...
  liquidity 80000000000009325512
  liquidity 80000000000009325561
  liquidity 80000000000009325610
  liquidity 80000000000009325660
  liquidity 80000000000009325709
  liquidity 80000000000009325759
  liquidity 80000000000009325808
  liquidity 80000000000009325857
  liquidity 80000000000009325907
  liquidity 80000000000009325956
  liquidity 80000000000009326055
  liquidity 80000000000009326104
  liquidity 80000000000009326154
  liquidity 80000000000009326203
  liquidity 80000000000009326252
  liquidity 80000000000009326302
  liquidity 80000000000009326351
  liquidity 80000000000009326401
  liquidity 80000000000009326450
  liquidity 80000000000009326499
  liquidity 80000000000009326549
  liquidity 80000000000009326598
  liquidity 80000000000009326648
  liquidity 80000000000009326697
  liquidity 80000000000009326746
  liquidity 80000000000009326796
  liquidity 80000000000009326845
  liquidity 80000000000009326895
  liquidity 80000000000009326944
  liquidity 80000000000009326993
  liquidity 80000000000009327043
  liquidity 80000000000009327092
  liquidity 80000000000009327141
  liquidity 80000000000009327191
  liquidity 80000000000009327240
  liquidity 80000000000009327290
  liquidity 80000000000009327339
  found 1983 in 10000000

Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 84.69s

Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

When configuring the liquidity range to [20282409603651670423947251286016, 20693058119558072255662180724088] and initiating with a liquidity amount of 80e18, it identified 1983 valid values out of 10,000,000 attempts. Adjusting the range and initial liquidity can yield varying outcomes for further analysis.