Analysis of the Bybit Hack: Most Comprehensive (Part Two)

CryptomedicationCryptomedication
13 min read

The execution trace for the hack transaction reveals exactly how the attack was carried out:

"stateChanges": [
  {
    "address": "0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4",
    "storage": [
      {
        "slot": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "previousValue": "0x00000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f",
        "newValue": "0x000000000000000000000000bdd077f651ebe7f7b3ce16fe5f2b025be2969516"
      }
    ]
  }
]

This shows the critical state change: the implementation address in slot 0 was changed from the legitimate Gnosis Safe implementation (0x34cfac646f301356faa8b21e94227e3583fe3f5f) to an attacker-controlled contract (0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516).

The attack was executed through the following steps:

  1. The attacker created a transaction calling execTransaction on the Bybit proxy with these parameters:

    • to: 0x96221423681a6d52e184d440a8efcebb105c7242 (malicious contract)

    • value: 0

    • data: 0xa9059cbb000000000000000000000000bdd077f651ebe7f7b3ce16fe5f2b025be29695160000000000000000000000000000000000000000000000000000000000000000

    • operation: 1 (DELEGATECALL)

    • Signatures from three owners

  2. The critical part of the attack was using the operation parameter with value 1, which instructed the proxy to use DELEGATECALL instead of a regular CALL. This meant the malicious contract's code would execute in the context of the proxy, allowing it to modify the proxy's storage.

  3. The malicious contract at 0x96221423681a6d52e184d440a8efcebb105c7242 was specifically designed to write to storage slot 0, replacing the legitimate implementation with the attacker's implementation.

  4. With control of the implementation, the attacker could execute arbitrary code in the context of the proxy, allowing them to drain all funds.

Remembering Nonce Eligible Re-Use Was a Factor

One of the anomalies of the execTransaction method that's worth noticing is the fact that the _nonce value that is evaluated by the logic contract in these transactions only stores the incremented value if the transaction is successful. This is evidenced by the failed transaction that took place on Bybit proxy just a few days before the hacked transaction came into play.

To get a better understanding of what I mean, let's take a look at the hacked transaction for a second, which is located here (0xd20a076a43804ffd392587cfb3a1a8825a482b768c2184beffef71b566c29ed6).

As we can see from the transaction panel on Etherscan, the transaction itself ended up failing with an error stating, 'Fail with error 'Invalid owner provided'.

What makes this transaction curious is that it was initiated on February 3rd, 2025, a full 18 days before the main Bybit proxy would end up being compromised by attackers. If we look at the transaction details, we can see that the transaction was initiated by a legitimate sender (and one of the authorized owners of the proxy), 0x828424517F9F04015db02169f4026D57B2B07229. The recipient of the call was also made to a legitimate Bybit address (hot wallet) as well.

However, when we debug the transaction in Tenderly and take a closer look at the execution flow, we can see that it does indeed fail due to the signature being recovered to the wrong address. This is a phenomena that is yet to be explained by the Bybit exchange that demands explanation because it is curious that such an event would have happened.

Moving forward, we can note that the _nonce value of this transaction (71) was the same as the nonce value of the exploit transaction that was made 18 days later (which falls in line with Gnosis Safe's documentation, but is still worth mentioning and enshrining in posterity moving forward).

Why Bybit’s Claims Demand Greater Scrutiny

One of the outstanding narratives in the crypto space at the time of writing is that the owners of the Bybit exchange were "passed" a malicious transaction that they blindly signed without taking a careful look at its contents. However, there are several details related to the transaction that defy this explanation.

First, we must observe the fact that the proxy that was compromised served as the cold wallet address for Bybit. The purpose of a cold wallet for exchanges is to hold assets 'offline' in custody or storage. The only time funds are to be moved out of that address is typically to top off of one of the exchanges hot wallets, not to fulfill withdrawal requests or make payments to vendors or any other outside parties. Thus, the idea that Bybit would have been prompted to make a transaction from this address to any other outside 3rd party should be considered fairly implausible.

For further evidence, if we take a look at the transaction log for the compromised Bybit cold wallet address, we'll see that all of its outgoing transactions targeted one of its hot wallet addresses (no outside third parties ever at any point in time).

And so forth it goes throughout the entire transaction log, dating back years

Another phenomenon that's worth noting are the decompiled signatures in the actual hack transaction. If we use a debugger like what Tenderly provides to us, we can see that each signature was validated via a call to Ethereum's precompiled ECRECOVER contract located at 0x0000000000000000000000000000000000000001.

Let's take a look at the first confirmed transaction (out of the three from the multi-signature) that was validated in the hack transaction:

As we can see above, the [RAW_INPUT] was: 0x28eddb7e7d1dca66673b339ca554c63603dede0512d6da0300cf782f68a8a260000000000000000000000000000000000000000000000000000000000000001bd0afef78a52fd504479dc2af3dc401334762cbd05609c7ac18db9ec5abf4a07a5cc09fc86efd3489707b89b0c729faed616459189cb50084f208d03b201b001f, which represents the signature.

Specifically:

  • 0x28eddb7e7d1dca66673b339ca554c63603dede0512d6da0300cf782f68a8a26 = the dataHash that needed to be signed

  • The hexadecimal 1f (which we can see at the end of the signature) represents the decimal number 31. Whenever this appears, that means that we're dealing with an eth_sign signature (this is important to note).

  • d0afef78a52fd504479dc2af3dc401334762cbd05609c7ac18db9ec5abf4a07a5cc09fc86efd3489707b89b0c729faed616459189cb50084f208d03b201b001f (this is the actual signature that we recover the v, r and s values from).

Looking closer at the screenshot that we produced above, we can see that the v, r and s values for the first verified signature are as follows:

  • v = 31

  • r = 0xd0afef78a52fd504479dc2af3dc401334762cbd05609c7ac18db9ec5abf4a07a

  • s = `0x5cc09fc86efd3489707b89b0c729faed616459189cb50084f208d03b201b001f

What has to also be noted is that within the scope of the Gnosis Safe contract, signatures with a v value of either 27 or 28 are accepted. This instructs us on which pubilc key to recover the signature to (because there are two potential keys that it could belong to).

"Huh? Two public keys that one signature could recover to?"

Yes, this is due to the way that the elliptic curve algorithm (secp256k1) works within the scope of signature proofs. Specifically, when creating a private and public key pair, we start off with a curve generator (G) which is multiplied by our 'private key' (itself a random integer between [0...n-1]).

Once we have our random integer, we multiply it by the curve Generator (G), which is a fixed value for secp256k1 (not going to reinput that value here due to space constraints). That gives us another value that we'll denote as the public key. From there, this allows us to create signatures via a signature proof. However, what has to be noted is that the y that gets produced can either be "odd" or "even" (in relation to what side of the 'axis' that 'y' is on). Thus, there are always two public keys that could be used whenever one is formed from a private key using the secp256k1 elliptic curve algorithm.

To account for transaction malleability, many protocols these days typically constrain the eligible public key value to one or the other. However, in Ethereum, an allowance was made with the ecrecover precompile address for backward compatibility reasons (i.e., hence the v being allowed to equal 27 or 28). That means that each signature can recover to two private keys.

No Constraints on the ‘S’ Value Either in the Signature

To get a better understanding of this potential issue, we have to go back to a GitHub issue that was opened in 2019 by an intelligent user that was looking into this issue as well: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1622

In the issue, the user notes the following: "If you allow both values 0/1 and 27/28, you allow two different signatures both resulting in a same valid recovery. (r,s,0/1) and (r,s,27/28) would both be valid, recover the same public key and sign the same data. Furthermore, given (r,s,0/1), (r,s,27/28) can be constructed by anyone. In my contracts, I removed line 37 to line 39. I further checked with geth, and in the transaction and ethapi , only 27/28 is considered. Thus, I added a function fixSignature() to convert 0/1 to 27/28 resulting from web3.eth.sign(). The current implementation opens attacks on contracts that rely where a signature is unique. This is for example ERC 865 as implemented in this pull request.

It appears that after some time, the user was able to further validate that there is no check to ensure that s remains in the lower half of the order when it comes to validating the signature (i.e., ensuring that v is 27 or 28 is just one part of the equation; however, making sure that the 's' falls within either the lower or higher part of the curve is also critical to protecting against transaction malleability).

Per EIP-2, it states: "All transaction signatures whose s-value is greater than secp256k1n/2 are now considered invalid. The ECDSA recover precompiled contract remains unchanged and will keep accepting high s-values; this is useful e.g. if a contract recovers old Bitcoin signatures"

If we debug the transactions related that are validated by the proxy orchestration that was deployed by Bybit, we can see that the signatures were all validated via the ECRECOVER recover precompiled contract, specifically. Thus, there is no reason not to believe that the signatures were not malleable. Furthermore, when considering the fact that lower and upper s values were accepted in addition to various v values being accepted (either 27 or 28), we can conclude that each signature that followed the ECRECOVER workflow for validation had four eligible signatures.

A mini-map out of those eligible signatures is provided below:

  • (v = 27, s = s)

  • (v = 28, s = s)

  • (v = 27, s = n - s)

  • (v = 28, s = n - s)

Again, as stated by the developer on GitHub, "That means ecrecover sees both signatures as valid, with lower s and upper s. So, my idea is the check for that as well and only allow lower s." This is a problem in the Gnosis Safe Proxy construction because its design mandates that all signatures provided to it be unique. If this is the case, then this signature malleability issue is something that's worth taking a closer look at. However, there's no final conclusion that can be made as of yet (although there is code that we'll provide at the end of this analysis that should validate whether the signatures were properly constructed or not).

Getting Back to the Main Hack

Each signature that was provided as part of the hack transaction contained the same v value of 31. This means that each of those signatures adhered to the eth_sign construction (per Safe's documentation).

This means that all 3 signers signed the transaction using the same method. This is worth noting, however, because this is actually an aberration and a huge departure from what can be seen with all of the other legitimate execTransaction function calls that were made prior to the hacked transaction that was executed on February 21st, 2025.

If you're looking for some examples, look no further than at the following transactions:

  • 0x5bd338eb788898addcbd079cac3e661ae139f0b3b5fb354f83012a438da88b71

  • 0x3e10310c05bb87269bfd60f67e13fd9dff4da80b4e47a541af1835f15fd96071

  • 0x631bb8e5bd386722d8e3f8dd54ca887db0eb5a4bb8b30ce1be6fb6c12714193d

  • 0xf356b8a8b3dcd1530ef7ee3d2a896e5f0035ea9833cdf6f0fbea6f7b4369c813

  • 0x197faa71ca0ed0c2aa5c35700c596efa32b4440c3c7bfc1bd23cd1dedbd5cddb

  • 0xd5d746c967b9720a2e2329ec3ccd8a24774b4a7588292d745abec325eba0b8b6

  • 0xa78efc010410fc345844796b032c28bdf44bea79bf325f00f49c83ed6f2297e0

  • 0x765f59ff894eec9c69ebc93c8c2b828d9556d287af14d444142db58b08d2a6c2

  • 0xe14b288b31fa2fe2d33220616505d20580cf2d368457d918314ca05d035694b5

In each one of those transactions (which span back to November 30th, 2024), we can see that the authorized signers of said transaction provided signatures that reflected different signing methods. In essence, what this means is that at no point in time were all 3 required signers of an execTransaction for that Bybit cold wallet ever signing an authorized transaction in the same manner (which defies the idea that they all were using the same interface/protocol to generate said signatures).

Let's take the first transaction, for instance. The first signer's v was recovered to a value of 32. Per the code in the logic contract by Gnosis:

} else if (v > 30) {

// To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover

currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v - 4, r, s);

What this code above means is that whenever the value of v exceeds 30, then the value is modulated by subtracting 4 from it. Doing so in this instance gives us the resulting value of 28.

Per the Safe documentation that we referenced earlier, we should never be getting a direct value of 28 for the signature verification (if we do, then the transaction will fail). Specifically, it states, "Regarding the EIP-191 signed message, the v value is adjusted to the ECDSA v+4. If the generated value is 28 and adjusted to 0x1f, the signature verification will fail as it should be 0x20 (28 + 4 = 32) instead. If v > 30, then the default v (27, 28) was adjusted because of the eth_sign implementation. This calculation is automatically done by the Safe{Core} SDK."

Notably, if the value is 28, then we can conclude that the signature was signed with typed data rather than just a generic eth_sign method. The difference here is that all values in the struct (particularly for execTransaction) would have been made apparent to the signer of said transaction. Beyond that, there are other transactions using the execTransaction function call where the signature validation may have involved an approved Hash or some other construction by one of the signers that deviated from the others.

Conclusion

There is a wealth of evidence to counter the ‘social engineering’ narrative that’s been put forth, particularly:

  1. Uniform 'v' Values Anomaly: We’ve confirmed that all signatures in the hack transaction had identical 'v' values (31), while legitimate transactions always showed varying 'v' values. This pattern is statistically improbable if the signatures were genuinely generated by different signers using the same interface.

  2. Historical Signature Patterns: Normal operation of the Bybit multisig showed inconsistent 'v' values, indicating signers typically used different devices or signing methods - contradicting the claim they routinely used "the same interface."

  3. Technical Attack Components: The execution trace shows a sophisticated implementation swap via DELEGATECALL - not just a simple fund transfer that might result from social engineering.

  4. Previous Targeting Pattern: Evidence shows attempts against other exchanges using the same Gnosis Safe version, suggesting a systematic technical vulnerability exploitation rather than social engineering.

  5. Signature Validation Vulnerabilities: The documented weaknesses in Gnosis Safe 1.1.1's signature validation align perfectly with the observed attack pattern.

  6. Protection Mechanisms: The fact that Zen Exchange survived a similar attempt because they implemented additional security measures indicates this was primarily a technical vulnerability.

Weighing the Evidnece

After weighing the evidence in totality, the following pieces of evidence weigh heavily against the ‘social engineering narrative’

  1. Forensic Signature Anomaly: The uniform 'v' value pattern is the most compelling piece of evidence. If signers were genuinely using the same interface as claimed, we would see this pattern in legitimate transactions - but we don't. This strongly suggests signature manipulation rather than authentic user-generated signatures.

  2. Implementation Targeting: The attack specifically targeted the implementation address in storage slot 0 - a sophisticated technical exploit rather than the simple fund transfer we'd expect from social engineering.

  3. Systematic Vulnerability Exploitation: The pattern of attempting similar attacks on other exchanges using the same Gnosis Safe version indicates a technical vulnerability was being systematically exploited.

  4. Technical Precision: The execution trace shows a precisely crafted transaction that exploited specific technical vulnerabilities in the Gnosis Safe architecture - not the hallmarks of social engineering.

Most Plausible Explanation

The most plausible explanation remains a technical exploit that leveraged:

  1. Signature validation vulnerabilities in Gnosis Safe 1.1.1

  2. The proxy pattern's vulnerability to storage slot manipulation via DELEGATECALL

  3. Programmatically generated or manipulated signatures that appeared valid to the contract

After considering potential arguments for social engineering, my conclusion remains unchanged: the Bybit hack was primarily a sophisticated technical exploit targeting specific vulnerabilities in their implementation of the Gnosis Safe multisig wallet.

The uniform 'v' values in the signatures - in stark contrast to the normal pattern of varying 'v' values - provides particularly compelling forensic evidence that contradicts the social engineering narrative and strongly supports the technical exploit hypothesis.

The most likely scenario is that the attacker exploited signature validation vulnerabilities to present manipulated but technically valid signatures to the contract, combined with the DELEGATECALL vulnerability to compromise the proxy's implementation address.

0
Subscribe to my newsletter

Read articles from Cryptomedication directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Cryptomedication
Cryptomedication

#Blockchain #BlockSec #OSINT #CyberSec #Darkweb | Isaiah 54:17 Fingerpint: 54EADD6FCBCF520E37A003E04D3ECE027AEFA381