Overview
Recently, ScamSniffer and SlowMist teams discovered that some Wallet Drainers have started exploiting the normalization process for EIP-712 to bypass security alerts in certain wallets.
By passing numerical addresses, the wallet drainer successfully bypassed the existing wallet security alerts and made the verifyingContract
in the EIP-721 signature request is unreadable on the UI.
The Wallet drainer used with this has stolen tens of millions in the past year.
Suspicious Case
About two days ago, we received a phishing case from the Cos@SlowMist. The phishing site requested a suspicious ERC20 Permit signature request, the verifyingContract
starts with 996101
, not the 0x
.
Numerical Address
After more investigation and testing with this, we found the reason. The 996101235222674412020337938588541139382869425796
is actually converted from the hexadecimal address.
As you can see, the signature result it’s the same whether the verifyingContract
is 996101235222674412020337938588541139382869425796
or 0xae7ab96520de3a18e5e111b5eaab095312d7fe84
Normalization Process
Clearly, there is a normalization process during the encoding process. If the field type is an address, it will try to convert it into hexadecimal, regardless of whether you pass a hexadecimal string or a number.
And we can get the answer from the @metamask/eth-sig-util
src/sign-typed-data.ts
/**
* Normalize a value, so that `@metamask/abi-utils` can handle it. This
* matches the behaviour of the `ethereumjs-abi` library.
*
* @param type - The type of the value to normalize.
* @param value - The value to normalize.
* @returns The normalized value.
*/
function normalizeValue(type: string, value: unknown): any {
if (isArrayType(type) && Array.isArray(value)) {
const [innerType] = getArrayType(type);
return value.map((item) => normalizeValue(innerType, item));
}
if (type === 'address') {
if (typeof value === 'number') {
return padStart(numberToBytes(value), 20);
}
if (isStrictHexString(value)) {
return padStart(hexToBytes(value).subarray(0, 20), 20);
}
if (value instanceof Uint8Array) {
return padStart(value.subarray(0, 20), 20);
}
}
}
Motivation
The obvious motivation behind this is to bypass security alerts and create unreadable messages.
The UI didn’t show it correctly as the hexadecimal address. you can’t identify it as stETH’s smart contract address.
Bypassing Security Alerts
In tests, if the verifyingContract
is a number, we can see that the security alert is bypassed. However, if you sign it, your assets will be stolen.
And most security extensions also didn’t handle it properly.
How’s the Impact?
Wallet | Normalization | Display verifyingContract | Simulation Failed |
---|---|---|---|
MetaMask | 1 | 1 | 1 |
Rabby | 1 | 1 | 0 |
Coinbase | 1 | 0 | 0 |
Rainbow | 1 | 0 | 1 |
OKX Web3 | 1 | 0 | N/A |
Token Pocket | 1 | 0 | N/A |
Trust Wallet | 0 | 1 | N/A |
Phantom | 0 | N/A | N/A |
Zerion | 0 | 0 | 0 |
Magic Eden | 0 | N/A | N/A |
We have conducted some tests on common extension wallets. The tests found that most wallets support value normalization. Some display the associated token for the ERC20 Permit signature through verifyingContract
Given that using the ERC20 Permit is a major phishing method, the consistency of handling this situation with the actual signature is still very important.
We have attempted to report this situation to these wallets.
Conclusion
By exploiting the normalization process in the wallet’s EIP712 encoding procedure, the Wallet drainer successfully bypassed existing wallet security alerts by using numerical addresses, resulting in unreadability on the UI.
This also reveals the Wallet drainer’s ongoing research into these subtle details and the escalation of attack and defense strategies.
The wallet drainer associated with this called Pink Drainer has stolen tens of millions in the past year. Staying vigilant and skeptical may be the only way for you to stay safe.