A brief analysis of Angel Drainer

Bernhard Mueller
5 min readApr 6, 2024

Angel drainer is a notorious piece of code used on web3 phishing sites. Its purpose is to extract the victim’s most valuable assets once the victim visits the phishing website. The drainer is typically found in the wild as compressed and obfuscated JavaScript. In this article, we’ll walk through the process of de-obfuscating and examining the client-side code and take a look at the drainer’s feature set.

Getting the source

To begin our analysis, we extract the drainer code from a real-world phishing website. The code is found at the end of a JavaScript file as a lengthy base64-encoded string. This string contains xz-compressed JavaScript that has been obfuscated using obfuscator.io. Angel drainer code is unpacked during runtime using a WASM xz decompressor.

Angel drainer code is unpacked during runtime using a WASM xz decompressor.

Fortunately, there’s a ready-made de-obfuscation tool by ben-sb. To get a readable version of the code, we save entire base64 string to a file named angel.b64 and run the following commands:

% base64 -d < angel.b64 | xz -d > angel-obfuscated.js
% obfuscator-io-deobfuscator angel-obfuscated.js
\
[2024-03-28T05:05:27.416Z]: Starting pass 1
[2024-03-28T05:05:27.421Z]: Executing UnusedVariableRemover
[2024-03-28T05:05:30.099Z]: Executed UnusedVariableRemover, modified true
[2024-03-28T05:05:30.099Z]: Executing ConstantPropgator
[2024-03-28T05:05:33.433Z]: Executed ConstantPropgator, modified true
[2024-03-28T05:05:33.434Z]: Executing ReassignmentRemover
(...)
Wrote deobfuscated file to deobfuscated.js

The result is a readable version of the code.

Angel’s anti-phishing extension bypass

One notable feature in Angel drainer is a hack that bypasses certain checks of a number of anti-phishing extensions such as WalletGuard and Pocket Universe. These extensions operate by proxying the request method of the Ethereum provider object to simulate and evaluate transactions.

The Ethereum provider is a component injected into web pages by Ethereum wallets like Metamask. It serves as an interface that allows websites to interact with the Ethereum blockchain and request actions from the user’s wallet, such as sending transactions or signing messages.

Angel drainer overrides the request method to redirect the victim’s RPC calls. Instead of using the Ethereum provider, it forwards the call to Metamask via window.postMessage, effectively hiding the interaction from the targeted anti-phishing tools (a different method is used for Coinbase wallet). As a result, the tools will fail to detect the drainer unless the phishing site domain is explicitly blacklisted.

Angel drainer redirects selected RPC calls to bypass anti-phishing extensions.

A possible solution here is to proxy window.postMessage as well to catch the redirected method calls.

Drainer config

Communication between the drainer and its backend API is encrypted using a couple of hardcoded AES key for incoming communications and outgoing communications. These keys appear to be the same across different releases of the drainer.

The API is located at https://api.ipjsonapi.com.

Encrypted comms between drainer client and backend

Here’s a simple decryption tool so we can peek into the configuration.

function base64ToByteString(base64) {
return Buffer.from(base64, 'base64').toString('binary');
}

const CryptoJS = require('crypto-js');

function decryptBody(base64EncryptedString) {
// Decode from Base64 to a raw byte string
const encryptedByteString = base64ToByteString(base64EncryptedString);

console.log(encryptedByteString);

// const key = "F-JaNdRgUkXp2r5u8x/A?D(G+KbPeShVmYq3t6v9y$B&E)H@McQfTjWnZr4u7x!z%C*F-JaNdRgUkXp2s5v8y/B?D(G+KbPeShVmYq3t6w9z$C&F)H@McQfTjWnZr4u7";
const key = "y$B&E)H@McQfTjWmZq4t7w!z%C*F-JaNdRgUkXp2r5u8x/A?D(G+KbPeShVmYq3t6v9y$B&E)H@McQfTjWnZr4u7x!z%C*F-JaNdRgUkXp2s5v8y/B?D(G+KbPeShVmY";

const decrypted = CryptoJS.AES.decrypt(base64EncryptedString, key);
const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);

return decryptedText;
}

if (process.argv.length !== 3) {
console.log("Usage: node decrypt.js <base64_encoded_encrypted_string>");
process.exit(1);
}

const base64EncryptedString = process.argv[2];
const decryptedString = decryptBody(base64EncryptedString);
console.log("Decrypted String:", decryptedString);%

As an example, here is part of the global drainer config:

{
"receiver": "0x000099B4a4D3cEb370d3A8A6235d24e07A8c0000",
"seaport_receiver": "0xACE00CCe12F6C5dD37897244470C6FC1E759177C",
"ethContractAddress": "0x0000d169F98E078B60bFb09A69D145e72dBE0000",
"researchers_latest": [
"0x240Cf70D8A648BE133fEB342D71E5e81C686e5f8",
"0x20cCdeDB9814c83bA2D663fC04f88c7a61aA706d",
"0x2ad6FA4db57Ac71479510863643Eb6b1991788E1",
"0x33566c9D8BE6Cf0B23795E0d380E112Be9d75836",
"0x034C446b223Bb4ffbd51d2E284Fe6b3cdd271315",
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"0xC832494dce30f7303F42d829c205D6Ea6b551afb",
"0x29B876e2dd14dd034612F052ecB372E64C96A895",
"0x886258791969e6b0fEff62c0a02be819Dfc1B167",
"0x3096d3B09e6ec2E8fF923D1657d0c691148eEeE5",
(...)
"0xBF7202d336357137b90ADD46416d63dEa3E04567",
"0x6Fa9720Ff6c36D2810edDE291882720b4c8Fa8c5",
"0x367C42a6F261EC54FFBEcf5f41C226BE12A3dCA0",
"0x87810B9824d0BdD813e77c97D2a446C2c88318c6",
"0x1A309C3b9Ca844851DB3d36b68e7194ed61Dd435",
"0x6C2dd2bF689fB90c1E145D7fC475f86036073632",
"0xbEF556697a2E27a813bF5D7DC46F8fDaD3B3367c",
"0x03499474a491eF05127d8087A227089361BA7303"
],
"multicall": "0x0000d169F98E078B60bFb09A69D145e72dBE0000",
"percentage": 85,
"reserve_contract_min": 100000000,
"blurfee": "0xf929690d746DEE0e52cD7bd99a9c2Dff5B36A9d9",
"fakeCollection": "0xF22d7c68291D09B17f6Df28F715Aa11Bde9396e4",
"reserve_contract": "",
"reserve_contract_id": 0,
"exclusive_addys": {},
"multicall_addys": {},
"native_addys": {},
"eth_signAllow": false
}

The config contains global settings as well as user-specific information, including:

  • Receiver addresses for regular and seaport transactions. Low-value assets are drained to these addresses, while high-value assets are drained to previously unused addresses.
  • A blacklist of addresses (“researchers_latest”) that are exempt from being drained. This includes well-known wallets like vitalik.eth, perhaps to avoid high-visibility incidents.
  • An exhaustive list of the user’s assets, including tokens and NFTs across various blockchains, as well as the estimated value of each asset. This helps the drainer prioritize the most valuable assets.
  • Minimum asset values for triggering BlockAid bypass.

BlockAid evasion

BlockAid works by simulating transactions and alerting users if a known malicious address is involved in the transaction. To bypass BlockAid’s detection, Angel drainer uses dynamically generated “unmarked” contract addresses if the value of the stolen assets exceeds a predefined minimum threshold.

When the draining process succeeds, the drainer client notifies the backend and a smart contract is counterfactually created at the given address.

For example, in my tests the configuration contained the unmarked address 0xcf10db5a875f7ef7252d1681f1c8297fa3142d20 along with the salt for creating the withdrawal smart contract at that address. Note that BlockAid still marked the transaction as deceptive (untrusted EOA), so this evasion method is not completely successful.

"unmarkedContracts": {
"1": {
"mc_salt": "0x14c2a5fef49fd88f1910fade3dc24af70f062fdd7a43d0a7ff49c45a1aadf880",
"nv_salt": "0x92ff75903c3f37d9007779100f63356da3dbe8eb9bcf383bb3e3c2bae118b612",
"mc": "0xc864f17f5f0415dbfb6c6834dcbdeb6c6ce9a43d",
"nv": "0xcf10db5a875f7ef7252d1681f1c8297fa3142d20"

Core drainer features

Beyond its anti-detection techniques, Angel drainer has an extensive range of features aimed at maximizing the value extracted from victims’ wallets. It is able to intelligently sort tokens and NFTs based on value, prioritizing the most lucrative assets for extraction.

In most cases, Angel drainer asks the user to send an approval transaction or sign an ERC-2612 permit. If successful, the drainer then notifies its backend to withdraw the victim’s assets. A variety of methods are supported, including:

  • Unstaking and stealing stakes from popular projects like Apes, MAYC, BAYC, Potatoz, and Creepz
  • Trading NFTs on Blur and OpenSea on behalf of the victim, allowing the attackers to liquidate valuable assets
  • Stealing LP position NFTs from decentralized exchanges like PancakeSwap, Sushiswap, TraderJoe, and QuickSwap
  • Withdrawing liquidity from Curve and Aave positions
  • Generically stealing ERC20 tokens and NFTs sorted by value (including looking up valuations on DEXes/NFT exchanges)

The deobfuscated code is available for readers interested in the drainer’s feature set.

TL;DR

The latest release of Angel drainer was designed to bypass common anti-phishing tools and extensions. It is able to intelligently target and steal the victim’s most valuable assets. Understanding the inner workings of such phishing scripts is crucial for developing more robust security measures and educating users about potential risks.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Bernhard Mueller
Bernhard Mueller

Written by Bernhard Mueller

Hackers (1995) fan • “Best Research” Pwnie Awardee • Retired degen • G≡¬Prov(num(G))