Pickle Incident: Root Cause Analysis

PeckShield
3 min readNov 22, 2020

--

Started at 18:37:24 PM +UTC, Nov-21–2020, Pickle Finance was attacked by exploiting two bugs in the ControllerV4 smart contract. The hack results in draining all invested 19.76M DAIs under the StrategyCmpdDaiV2 management. Here we elaborate the technical details of these two bugs in this blog post.

Summary

Pickle is a yield-generating YFI-related DeFi protocol on Ethereum that allows users to deposit assets and earn yields. However, it has two bugs in the controller logic: The first one is input validation bug that fails to validate whether a given jar is supported or not; and the second one is arbitrary code execution that allows for external (untrusted) code execution in the context of the controller. The exploitation leads to drain all invested 19.76M DAIs under the StrategyCmpdDaiV2 management.

Details

The Pickle Hack Walk-through

We started the analysis from the transaction behind the hack: 0xe72d…87b0. This hack is initialized from a malicious contract (located at 0x2b0b).

There are five steps involved:

  • Step 1: Query current asset balance: The query via StrategyCmpdDaiV2.getSuppliedUnleveraged() returns the asset balance of 19.72M DAIs
  • Step 2: Exploit input validation bug to withdraw all DAIs from StrategyCmpdDaiV2 to Pickle-Jar: This bug is present in the ControllerV4.swapExactJarForJar() with two given fake Jars. Without validating the given Jars, this step withdraws all invested DAIs back to Pickle-Jar for next-round of deployment.
  • Step 3: Call earn() to deploy withdrawn DAIs back to StrategyCmpdDaiV2. With the internal buffer mangement, the hacker calls earn() three times, resulting in total 950,818,864.82119677 cDAIs minted to StrategyCmpdDaiV2.

- 1st earn() call invests 19.76M DAI and gets 903,390,845.43581639 cDAI minted

- 2nd earn() call invests 988K DAI and gets 45,169,542.27179081 cDAI minted

- 3rd earn() call invests 49K DAI and gets 2,258,477.11358954 cDAI minted

  • Step 4: Exploit arbitrary code execution to withdraw all cDAIs from StrategyCmpdDaiV2 to Hacker: This step calls ControllerV4.swapExactJarForJar() but with a different set of crafted arguments to trigger external code execution in the context of ControllerV4. Specifically, the _execute() call at line 316 is triggered to delegatecall the code located at CurveProxyLogic.add_liquidity(), but with the following inputs: curve=StrategyCmpdDaiV2, curveFunctionSig=51cff8d9, curvePoolSize=1, curveUnderlyingIndex=0, underlying=0x8739c55df8ca529dce060ed43279ea2f2e122122. The first arguemnt to CurveProxyLogic.add_liquidity() is typically the curve address. However, it is miused to withdraw cDAI from StrategyCmpdDaiV2 back to ControllerV4 by calling StrategyCmpdDaiV2.withdraw(). Since it is a delegatecall, the StrategyCmpdDaiV2.withdraw() is executed with msg.sender == controller, which is unfortunate. In addition, StrategyCmpdDaiV2.withdraw() only checks want != _asset in line 142 (want is DAI here) which enables the bad actor to withdraw() cDAI. Now, the execution goes back to line 323 in ControllerV4.swapExactJarForJar(), the withdrawn cDAI is then deposited into the malicious _toJar. Inside _toJar.deposit(), all 950,818,864.8211968 cDAI are immediately transferred to the hacker address.
  • Step 5: Redeem cDAIs and walk away with all 19.759M DAIs

The Stolen Funds

The stolen funds from the above exploitations are currently held in this wallet: 0x2b0b. We are actively monitoring this wallet for any movement.

About Us

PeckShield Inc. is an industry leading blockchain security company with the goal of elevating the security, privacy, and usability of the current blockchain ecosystem. For any business or media inquiries (including the need for smart contract auditing), please contact us at telegram, twitter, or email.

--

--