TGViewer
Channel Public Channel
Subscribers
2.49K
Photos
7.3K
Videos
37
Links
6.9K

Showing posts older than #30456 · Back to latest

Older Posts 14 shown
Post #30455 444
Libre Blockchain This same vulnerability was identified by Trail of Bits in their audit of the project 'Meson Fi'. Severity = high (for obvious reasons) and this vulnerability was a lot more succinct than the one that's in the Gnosis Safe contracts. 1. They identify two…
If you are a large entity in the crypto sphere and you have a substantial amount of funds stored in a Safe multi-signature contract deployment, I would strongly recommend that you (a) just contract somebody in-house to procure you tailor-made multi-sig solution [especially if you have tens of millions of dollars to throw around...why not invest in better security for your funds?..like huh?] (b) MIGRATE from SAFE CONTRACTS UNTIL THEY FIX THIS ISSUE.

Why Large Entities Using Safe are at Risk

I'm more than certain that WazirX and Bybit were victims of this same attack vector. In both situations we have exchanges carrying hundreds of millions of dollars in Safe smart contracts (multi-sig; upgradeable proxy). Each signature provided for both hacks had a v > 30 (**what a coincidence**) and the signatures were validated against hashes that are ENTIRELY different than what we can see in the transaction log, most stack exec traces or other online visualizers.

We still don't have an official story on the WazirX situation. Remember that one required 4+ signers. Three on the exchange's end that all confirmed (via 3rd-party audit) none of their devices were compromised. Beyond that, the 4th and final signer was an entity called 'Liminal', whose sole duty was to effectively play 'last line of defense' for WazirX in the case that one or more of their signers did end up being compromised somehow. Neither party (Liminal / WazirX) willing to budge on their declaration that neither was operationally compromised in any way, shape or form. No private keys were siphoned or leaked and there is scant (or no) evidence to corroborate the idea that the exchange owners blind signed something foolishly (again, worth noting that they contracted Liminal to serve as that last line of defense in the event they did somehow get compromised or bamboozled into letting a suspicious / weird transaction get signed).

What are the chances that these two exchange hacks weren't due to their respective owners or related custodians being dipshits too lazy to double check before signing off on a transaction from a wallet holding hundreds of millions of their funds despite possessing enough wherewithal and motivation to deploy an extensively secure, clandestine and comprehensive authorization scheme for those same funds? What are the chances that this disease of apathy and carelessness didn't actually afflict every single authorized signer of these two respective exchanges, simultaneously, right when the culprit (Lazarus) had decided to strike?

This exploit/vulnerability likely won't impact smaller users (small in terms of funds), because the risk/reward ratio doesn't justify it. The effort/resource/brain power necessary to pull this off mandates a sizable payout. But when we look at WazirX losing >$250 million and Bybit losing >$1.5 billion (both in the space of less than a year) ...this has been a lucrative ass campaign for Lazarus.
  • 👍 1
Post #30451 219

Forwarded from Libre Blockchain

This same vulnerability was identified by Trail of Bits in their audit of the project 'Meson Fi'. Severity = high (for obvious reasons) and this vulnerability was a lot more succinct than the one that's in the Gnosis Safe contracts.

1. They identify two functions in the code. One verifies a signature for a "request", the other verifies a signature for a "release message".

2. The request has only the encoded swap, the release has the hash of an encoded swap + recipient's address.

3. First function looks for keccak256(abi.encodePacked(bytes28(0x1945...), encodedSwap)); and second function looks for keccak256(abi.encodePacked(bytes28(0x1945...), keccak256(abi.encodePacked(encodedSwap, recipient)). Difference is in what gets concatenated with the 0x1945 (EIP-191 signature standard) prefix.

Here, the analogy is that within Safe contracts, a product (`return` statement) of the encodeTransactionData function is analogous with encodedSwap.
Post #30449 297
Breaking Down the Vulnerability in All Safe Contracts Using Common Sense Language: (More Plain English; Part Four/Four)


This means that txHashData now refers to the return statement of encodeTransactionData with the arguments used for execTransaction inserted into that function for the following fields: (to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, nonce), which is the full byte value (in the Bybit hack): 0x1901b3ded2bdbff5db1a87f6d551fa256e9f2bd6517a3bb84f4c2ea863fb3a559622ea68fc75e93bf7286b1257680ca8e2033bfadd8e1cb8a23f3ca18f1031ecf1e0. This is the `EIP-712` pre-image data string that needs to be hashed with `keccak256` to give us the proper message hash to verify signatures against.

That step happens in the line txHash = keccak256(txHashData); [located within the execTransaction function]. The result of that (in the hack transaction) is 0xb3476d061aeb8fc1d605a873c483a2402d88a68a9cdd1a8b47655dd55ba004f8. This is the same value you see in the transaction log when you look up the transaction (ID: 0x46deef0f52e3a983b67abf4714448a41dd7ffd6d32d32da69d62081c68ad7882).

However, this is not the hash that the signatures are verified against. We just established this value is derived by correctly taking the transaction data, piping it into encodeTransactionData (via internal call), obtaining a return statement (EIP-712 pre-image), then hashing that result (keccak256(txHashData)). This is the complete, correct workflow for obtaining a valid EIP-712 hash to verify signatures against. No doubt about it.

So, this begs the question...why the heck does the Safe contract take that valid EIP-712 message hash AND PREFIX it with the EIP-191 ASCII byte string, concatenate that with the EIP-712 message hash, then keccak256 hash the result of THAT to derive the message hash the signatures should be verified against?

This COMPLETELY BREAKS the EIP-191 AND provides a viable pathway for ANYONE to break the EIP-712 STANDARD in ALL Safe Contract Versions!
Post #30448 283
Breaking Down the Vulnerability in All Safe Contracts Using Common Sense Language: (More Plain English; Part Three/Four)

The whole point of prefixing 0x19 + 0x01 + domain separator to the hash of actual transaction contents/struct (EIP-712) when creating an EIP-712 compliant hash for signature validation isn't just to promote cryptographic integrity by imbuing replay protection. This standard was also created to (a) provide determinism in EOA signature creation & validation for transactions, (b) provide structure and specificity to transaction data in a way that allows 3rd-party providers (i.e., Metamask) to programmatically decode EIP-712 compliant transaction data sent to end-users for signature request.

Finally, and perhaps most important, the EIP-712 signature standard was/is meant to be an evolution of its predecessor, the EIP-191 standard,0 by also (c) ensuring that once EIP-712 compliant data is signed that it can't / won't be re-used or even used at all in a different context (i.e., as a personal sign message versus a transaction). What that means, in effect, is that it should not be possible to repurpose data that was processed, encoded as a transaction and then keccak256 hashed (EIP-712) as a personal sign message EVER (which is exactly what every version of Safe's contracts do).


In the Bybit hack, the calldata of the hacked TX clearly shows data corresponding with a typed transaction - not a personal signed message. So why is the hash of the transaction mutated by nesting the EIP-712 signed hash within the EIP-191 standard?? Resulting in the submitted signatures being validated against a hash that reflects something vastly different than what the signer actually signed originally or what we can see at all on the blockchain?

Breaking Down How This Took Place in the Bybit Hack

1. The actual hash that we get from hashing the contents of that hacked transaction is = 0xea68fc75e93bf7286b1257680ca8e2033bfadd8e1cb8a23f3ca18f1031ecf1e0. This is given the value of safeTxHash in the contract.

2. That gets prefixed with a 0x19 byte & 0x01 byte. Then appended to that is the domain separator. This value is saved in the storage of the proxy contract (not the logic) to ensure transactions signed that are targeted for this proxy (as a recipient) can only be used on this proxy (and not the 10+ others that this same group of owners has deployed on the same blockchain; no for real). For the hack transaction, that value was 0x1901b3ded2bdbff5db1a87f6d551fa256e9f2bd6517a3bb84f4c2ea863fb3a559622ea68fc75e93bf7286b1257680ca8e2033bfadd8e1cb8a23f3ca18f1031ecf1e0 (result of the encodeTransactionData function).

3. In the execTransaction function (this is the one responsible for all of the signature validation logic), we see that this long 66 byte value (`0x1901b3ded...`), which is now the EIP-712 encoded transaction (hence the name of the function: encodeTransactiondata), then get properly hashed in the statement:


solidity
bytes memory txHashData = encodeTransactionData(
to, value, data, operation, // Transaction info
safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, // Payment info
nonce
);
Post #30447 268
Breaking Down the Vulnerability in All Safe Contracts Using Common Sense Language: (More Plain English; Part Two/Four)

Gnosis Safe's contracts take a FINAL, VALID EIP-712 signed hash that was specifically created in accordance with the EIP-712 specification and then NESTS THAT within the EIP-191 signature scheme for creating data hashes to verify against signatures for PERSONAL SIGNED MESSAGES for verifying TRANSACTIONS.


Multitude of Problems + Vulnerabilities That Spawn From Doing This

From the jump, this incorrect and flawed implementation of EIP-191: (a) destroys domain separation for all EIP-712 messages submitted to Safe (b) destroys replay protection / nonce effectiveness entirely and (c) allows data that was signed and intended to be used in one context (i.e., a signed transaction) to also be validated in another (i.e., as a personal signed message). (d) Most importantly, this implementation means we have no clue what signatures are being verified against if the v byte is > 30. This is due to the fact that this condition means the validly constructed EIP-712 hash of the encoded transaction data (which included the SAFE_TX_TYPEHASH value and domain separator directly in the hash of transaction data & encoded transaction information, respectively), has its cryptographic uniqueness properties destroyed whenever the value of v is determined to be > 30, because that results in the EVM taking that EIP-712 signed hash and prepending it with the 0x1945... EIP-191 prefix (ASCII for \x19 Ethereum Signed Message\n32`), concatenates the two values then performs a keccak256 hash on that concatenated result. (This is nowhere near or close to compliant with EIP-191)

What that literally translates to is: keccak256(EIP-191 prefix + EIP-712 signed hash of encoded TX data) = EIP-191 personal_sign hash. The EIP-712 hash is still provided in the event logs of the transaction because the checkSignatures routine (and the calculations that result in the derivation of the non-compliant, vulnerable EIP-191 signed message scheme) are all called, defined and relevant within a scoped block within the execTransaction function. A scoped block's definitions and declarations are only valid until that block of code (sort of like a sub-function within a function) have finished executing.

Why This is a Serious, Critical Issue

This is a significant flaw because EIP-191 is expecting to receive personal sign data (messages) that gets interpreted as OPAQUE BYTES whereas EIP-712 is expecting STRUCTURED TRANSACTION DATA THAT GETS INTERPRETED AND HASHED IN A CERTAIN WAY. Arbitrarily choosing to interpret data validly presented as a transaction (signed_typed_v3 / signed_typed_v4) as a personal sign message instead results in wildly unpredictable results as it pertains to signature validation.

Thus, it is very possible the owners of the Bybit proxy never signed ANY malicious (`execTransaction`) transaction explicitly (javascript injection notwithstanding) (contrary to the 'blind signing' narrative; we can state this definitively, since the signatures for that hack transaction all signed transaction data that was at least presented to them in the form of a structured transaction and processed like one in accordance with the EIP-712 standard).

The only code in the implementation/masterContract/singleton source code stipulating this alternative signature validation pathway for EIP-191 "compliant" data ('compliant' in quotes here since the Safe implementation of this signature standard isn't even in that universe) is contingent on the the v byte for each individual, provided signature value of the signatures [why let signers dictate this? this essentially allows the contract caller to choose their preferred means of validation via simple bit manipulation]).
Post #30446 290
Breaking Down the Vulnerability in All Safe Contracts Using Common Sense Language: (Part One/Four)

To be clear, these are the problematic lines of code in every version of Safe's multi-signature upgradeable proxy contracts (specifically their singleton/masterCopy/implementation deployment; the Fallback and Factory contracts are not impacted):


solidity
} 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);
} else {
// Use ecrecover with the messageHash for EOA signatures
currentOwner = ecrecover(dataHash, v, r, s);
}


These faulty lines of code are in every single deployed Safe (masterCopy/singleton/implementation) contract for each version release of Safe.

Specifically, I'm referring to (contracts for those addresses on Ethereum / main net):

A) Version 1.1.1
=
0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F

B) Version 1.2.0 = 0x6851D6fDFAfD08c0295C392436245E5bc78B0185

C) Version 1.3.0 = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552

D) Version 1.4.0 = 0x29fcB43b46531BcA003ddC8FCB67FFE91900C762

EIP-191 is a standard for verifying personal sign messages. Its outlined fairly thoroughly here: https://eips.ethereum.org/EIPS/eip-191

EIP-712 is a signature standard for verifying transaction data. That, too, is outlined very thoroughly here: https://eips.ethereum.org/EIPS/eip-712 .

The screenshot attached to this message shows how wallets that employ the EIP-712 standard are supposed to correctly dissect and display EIP-712 encoded transaction data for end users when their signature is being requested for said data (usually by a DApp or web3 app of some sort). That screenshot provides a rough reflection of what the owners of the Bybit exchange were looking at before they signed whatever transaction resulted in $1.5 billion in funds being compromised. (However this is not an indictment of those exchange owners by any means)
Post #30444 309
Libre Blockchain How Does This Get Exploited? Collision (Part One) Obviously collisions in a hash function like keccak256 are practically impossible. Would never claim otherwise. However, the issue here is most of the execTransaction calldata space is fairly fixed from one…
Exploiting (Part Two/Two)

- The Gnosis Safe implementation nests EIP-712 (typed, domain-separated) inside EIP-191 (personal message) hashing for v > 30 signatures.
- This effectively breaks the domain separation by allowing a signature to be validated against two distinct hash contexts.
- This doubles an attacker's “shot” at producing a collision: they can attempt to find a preimage that matches a hash under either scheme.
- This raises practical collision chances beyond the theoretical security bound of a single hash function collision.

- There are 100+ legitimate candidate transactions the attacker could target.
- Each provides a unique target hash `H(X)` signed by authorized owners.
- The attacker’s task reduces to finding a malicious transaction M with hash H'(M) (under buggy verification) matching any of these H(X).
- This multiplicity further increases the attacker’s probability of success, effectively multiplying the collision probability by ~100.

- Assuming effective entropy k ≈ 40 bits (a conservative estimate from variability and real-world constraints).
- Probability of random collision in one attempt: ~1 in 2^40 (~1 in 1 trillion).
- With 100 target hashes, effective collision probability per attempt: ~100/2^40 ≈ 1/2^33.
- With modern ASIC or FPGA farms capable of trillions of hashes per second, finding a collision in hours or days becomes feasible.
Post #30443 311
6. Following the safeTxGas value in the calldata, there are 317 consecutive identical null values in the calldata. This repetition is only broken by a 0x1c hexadecimal value (which is identical in both pieces of calldata.

7. Following that 0x1c value, we see 63 more identical zeroes in repetition. That is only broken by a 20-byte encoded address (which is different for both sets of calldata`). Following that 20-byte address, there are another 20 identical repetitious zeroes which are interrupted by a 10-byte value, which is `0x3691d6afc0 in the legitimate transaction calldata, but all repetitious null bytes in the hack transaction.

8. Moving past this 10-byte string, the final 182 bytes of the calldata (for both transactions) consists of repetitious identical zeroes.
  • ❤ 1
Post #30442 308
How Does This Get Exploited? Collision (Part One)

Obviously collisions in a hash function like keccak256 are practically impossible. Would never claim otherwise. However, the issue here is most of the execTransaction calldata space is fairly fixed from one transaction to the other.

Here's the first one (the hack TX `calldata`):

solidity 
0x6a76120200000000000000000000000096221423681a6d52e184d440a8efcebb105c7242000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000b2b2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000bdd077f651ebe7f7b3ce16fe5f2b025be296951600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000


Here's a legitimate transaction on the same proxy for a call (using the same function; execTransaction`) with the same nested `transferToken call embedded within it. The biggest differences here are the recipient of the execTransaction struct, the token contract being invoked and the amount to be sent as well as the nonce / _nonce.

solidity 
0x6a761202000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f84c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000ee5b5b923ffce93a870b3104b7ca09c3db80047a00000000000000000000000000000000000000000000000000003691d6afc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000


Between these two transactions, there's 44 bytes of variability. So 439 out of 489 bytes between these transactions are identical. Beyond that, the number of identical (repetitious) bytes in the two transactions.

Both calldata strings present the following repetitious, low-entry strings:

1. Both calldata have identical function signatures to begin their strings. Following this calldata are 24 repeating zeroes.

2. The 20-byte address is encoded in the calldata past that point and for both transactions that portion is different.

3. Afterward, there are 125 repeating zeroes consecutively in both sets of calldata up to the next element in the calldata.

4. There is a uint8 value in the code (represents a nibble due to typing). This one byte in the hack transaction differs from what we see in legitimate transactions.

5. Following this, there are another 60 bytes of nulls (zeroes) until the next data element is reached. This data element that gets reached is the safeTxGas. In the hack transaction, this value is 0xb2b2 and in the legitimate one its 0xf84c (however, there are plenty of transactions where that value is 0xb2b1, which would shrink the byte difference down to just a nibble or 1-byte versus 2 full bytes at this juncture).
Post #30441 284
A Quick tl;dr Can Be Found in the Discussion Channel Which Breaks the Whole Entire Zero Day Down in a Few Succinct Posts

The discussion channel is @librehashdiscussion
Post #30440 368
In What Universe Do You Encode a EIP-712 Hash Within the EIP-191 Standard?

No wallet works this way. Anywhere in the ecosystem. That's probably why users have had so many compatibility issues with their Safe multi-sig deployments and whatever other software/hardware they were using at the time (i.e., Metamask, Ledger, etc.).

The whole reason for these two EIP standards is to ensure that messages can't be repurposed across different domains. So that means if me, Bob & Joe own two multi-signature proxies, then a message that we all signed for proxy one should not be able to be validly used on proxy two. This is basic signature security.

The whole purpose of prefixing the hash of a multi-signature transaction with the 0x19 and 0x01 bytes + a domain separator is to (a) establish that the information to be hashed and verified against was meant to be a transaction (not a message; there is a difference) and (b) ensure that a message that was signed to be used on one proxy could not be replayed on another proxy that happens to have the same exact set of owners (even if that second proxy was at the same nonce at the time the first message was signed).

Where Safe Fucks Up

All one has to do to have signatures verified against another hash is change the v value of the r, s and v signature split. This value has no bearing on the signature produced, it just tells us how we need to interpret the signature. If we change that byte in the calldata & replay that very same transaction the only thing that changes is the hash that the signatures are verified against.

Signatures are not being verified against the data the owners think they are signing. In the Bybit hack, those signatures that were provided were not verified against that transaction data. So all that calldata you see in explorers that show you the full decoded struct of the hack TX with the "to" address being some 0x9622... address, followed by 0 in the value, 1 for the operation and 0xa9059cbb....bdd077f651ebe7f7b3ce16fe5f2b025be2969516... (68 byte) for the data etc...??

That's not what the signatures provided actually verify against. At all. Maybe something akin to it - but not that data directly.

If you're wondering how, that's what's broken down and explained in the pdf that I attached (getting ready to publish a quick GH issue in a few places as well).
Post #30436 698
Libre Blockchain critical_zeroday_report.pdf
This same vulnerability was identified by Trail of Bits in their audit of the project 'Meson Fi'. Severity = high (for obvious reasons) and this vulnerability was a lot more succinct than the one that's in the Gnosis Safe contracts.

1. They identify two functions in the code. One verifies a signature for a "request", the other verifies a signature for a "release message".

2. The request has only the encoded swap, the release has the hash of an encoded swap + recipient's address.

3. First function looks for keccak256(abi.encodePacked(bytes28(0x1945...), encodedSwap)); and second function looks for keccak256(abi.encodePacked(bytes28(0x1945...), keccak256(abi.encodePacked(encodedSwap, recipient)). Difference is in what gets concatenated with the 0x1945 (EIP-191 signature standard) prefix.

Here, the analogy is that within Safe contracts, a product (`return` statement) of the encodeTransactionData function is analogous with encodedSwap.
  • ❤ 1
Post #30434 603
Libre Blockchain critical_zeroday_report.pdf
This is the zero day in all versions of the Gnosis Safe contracts
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →