1 of 35

Masquerading code in Etherscan

Yoav Weiss - Ethereum Foundation - @yoavw

Oren Fine - SphereX - @fineoren

2 of 35

Luke: How do I know if I can trust this contract, Master Yoda?

Yoda: Trust, you seek, young Skywalker. The contract, a web of code it is. The source, use you must.

Luke: But how do I discern which source to rely upon, Master Yoda?

Yoda: Judge not by appearances, but by the Force's guidance. Feel the currents, sense the truth. Within you, the answer lies.

3 of 35

Bait & Switch techniques

Show legitimate code, execute bad code.

Examples

4 of 35

(I) Hiding in plain sight

  • It’s possible to verify additional contracts that aren’t used by the bytecode.
  • Import and verify many unrelated files, overwhelming the auditor.
  • Hide a vulnerability in plain sight in some function in the middle.
  • Put another (unused) implementation of that function and make it stand out.
  • Example: enabling ownership hijacking

5 of 35

7576 lines of code, 99.99% legit (literally!)

6 of 35

Three transferOwnership funcs - guess which one is used

onlyOwner maybe?

7 of 35

(II) Fake proxy

  • Etherscan recognizes standard proxies, e.g. EIP-1967 Transparent Proxy
    • …but doesn’t detect fake ones.
  • Fake a standard proxy, pointing to a legitimate implementation.
    • …but actually point to a malicious implementation.

8 of 35

9 of 35

(III) The non-proxy proxy

  • Legit-looking immutable token contract inheriting multiple contracts
  • One inherited contract has a fallback function delegating elsewhere
    • …to an undeployed counterfactual contract - nothing to analyze
  • After token becomes valuable, deploy the counterfactual contract.
    • Adding a new mint() function and using it to rugpull the token’s DEX pools.

10 of 35

11 of 35

12 of 35

13 of 35

(IV) Selector collisions

  • Solidity method signatures are just 4 bytes - keccak(sig)[0:4]
  • Finding a collision is easy
  • In a collision the proxy takes precedence over the implementation
  • Even a benign-looking function could compromise the contract
  • Example: compromised vaultManagers mapping

14 of 35

Vault implementation

keccak(“vaultManagers(address)”)[0:4] == 0xbab82c22

15 of 35

Vault proxy

keccak("IMGURL()")[0:4] == keccak(“vaultManagers(address)”)[0:4] == 0xbab82c22

16 of 35

Number in a Vault - only managers can set the number

17 of 35

IMGURL shadows vaultManagers

Anyone is a manager!

18 of 35

Non-manager address set the number!

19 of 35

(V) Legit code - malicious initialization

  • Legit code + compromised storage = compromised contract
  • During initialization it’s possible to backdoor the storage
  • Initialization may not be visible in the verified code
  • Example: shadow signers in a legit Safe

20 of 35

Unmodified Safe

  • Deployed by the official GnosisSafeProxyFactory
  • Points to the official GnosisSafe singleton

21 of 35

Only one visible signer

22 of 35

Etherscan confirms: One legit signer

…but also invisible ones if you know where to look

23 of 35

…and they can transact!

24 of 35

25 of 35

authQuotaOf

Caption

Let’s make it right this time

26 of 35

Obfuscating Delegate Calls

The Untold Story of the 2nd Chainswap Hack

  • We delve into the 2nd hack, July 11th, 2021
  • The bridge was exploited, resulting in $4.4M stolen. Chainswap initiates a compensation plan
  • A few explainers were published shortly after the attack, they all got it wrong.
  • Let’s find out why

27 of 35

Let’s starts with Etherscan

Implementation contract’s code is verified

Caption

Caption

Caption

Caption

Caption

28 of 35

And now dig into attacker’s transaction

Attacker calls receive() function, ends up in unverified contract

Caption

Caption

Caption

29 of 35

What just happened?

We’ll make a long story - short

Caption

30 of 35

Digging into the “delegate” hole

What used to be a mapping became a function

  • receive() => _decreaseAuthQuota() => check authQuotaOf[]

  • But the tx behaves differently… we also spotted the usage of two constant strings which do not appear in the code - “autoQuotaRatio” and “autoQuotaPeriod”
  • So we looked for them, and found…

Caption

31 of 35

authQuotaOf

  • authQuotaOf is now a function rather than a mapping

Caption

32 of 35

At last, the root cause:

33 of 35

34 of 35

The difference between theory and practice

Caption

35 of 35

Conclusion

  • We presented a few techniques to confuse auditors about code in etherscan.
  • We saw a real life vulnerability missed by auditors due to code confusion.
    • …and a transaction that exploited it on mainnet!
  • It’s a cat and mouse game. Auditors must keep up with new techniques.