In this guest blog post, the security researcher Taha Karim (@lordx64) of Confiant, details a sophisticated threat targeting web3 users.
The writeup was originally posted on Confiant’s site.
Confiant monitors 2.5+ billion ads per day via 110+ integrations in the advertising stack. This provides great visibility on malicious activity infiltrating the ad stack and the broader Internet. And that includes all the web3 malicious activity funneling thru it.
The variety and the range of our detection enable Confiant to detect unique malicious activity as soon as it surfaces.
SeaFlower is an example of this unique cluster of malicious activities targeting web3 wallet users that we will document in this blog post.
The cluster of activity named “SeaFlower” was chosen for a reason. One of the injected .dylib
files in the original Mach-O of the metamask app, contained the full path to xcode derived data, that leaked a macOS username: “Zhang Haike”:
Naturally, Googling “Zhang Haike” was the next step, which gave many Chinese-speaking references, including this one that I found amusing: it is the name of a character in a Chinese novel called “Tibetan Sea Flower”.
The Chinese-speaking references conform to the context of this large campaign, and hint to a strong relationship with a Chinese-speaking entity yet to be uncovered:
Uncovered macOS usernames are Chinese names
Source code comments in the backdoor code are written in Chinese.
Modding/hooking Frameworks used are common in the Chinese-speaking modding community, based on the fact that many tutorials and usages example of these Frameworks are in Chinese and the authors of the tools are Chinese speaking.
We uncovered Provisioning profiles, signing infrastructure, and app provisioning infrastructure hosted in the Chinese IP address space and the Hong Kong IP address space in addition to the domains registered with .cn TLD.
We uncovered multiple cloned websites (mimicking official wallet websites) initially hosted in Hong Kong IP address space
CDN abused is Alibaba
Most of the search engines targeted are Chinese search engines.
As of today, the main current objective of SeaFlower is to modify web3 wallets with backdoor code that ultimately exfiltrates the seed phrase. The targeted web3 wallets are the following:
Coinbase Wallet (iOS, Android)
MetaMask wallet (iOS, Android)
TokenPocket (iOS, Android)
imToken (iOS, Android)
Any users lured into downloading SeaFlower backdoored wallets will ultimately lose their funds. We provided SHA-256 of each analyzed backdoored wallet to help our community identify these backdoored wallets and their multiple variants.
Looking at the various attacks in this new cluster, they have something in common: SeaFlower doesn’t alter the original functionality of the wallet in any way but adds code to exfiltrate the seed phrase, and does it using different techniques increasing in complexity, hopefully, documented in this blog post.
The user experience, the UI, and all the wallet functionality are unchanged, normal/advanced users won’t notice anything while using the app on their phones: it is the legitimate app from the AppStore/Play Store with a sneaking backdoor in it.
But if one is actively monitoring network requests, one will find out that there’s a single network request that is sent to weird-looking domains, for example, we have seen backdoored wallets sending traffic to trx.lnfura[.]org (mimicking infura.io) or metanask[.]cc (mimicking metmask.io) over HTTPS.
Setting up a MITM proxy we could decrypt the HTTPS traffic and find out that the seed phrase, the wallet address, and the balance are sent out to the attacker:
But how this is possible? we will have to reverse engineer the apps to determine all the techniques SeaFlower used to make these legitimate apps behave maliciously in the background.
SeaFlower drastically differs from the other web3 intrusion sets we track, with little to no overlap from the Infrastructure in place, but also from the technical capability and coordination point of view: Reverse engineering iOS and Android apps, modding them, provisioning, and automated deployments.
SeaFlower also takes care of the app distribution phase by setting up fake cloned websites where these backdoored wallets can be downloaded. The identified websites are perfect clones of legitimate websites, offering download links:
For iOS, SeaFlower is using provisioning profiles. Once installed, the iOS apps are then sideloaded to the victim’s phone and installed. Below are some of the steps we recorded of typically what the victim will see when browsing one of the SeaFlower websites using an iPhone:
The last question to be answered is how the users are targeted and redirected to these websites offering backdoored wallets? short answer: Search Engines. Indeed, search engines are one of the clear entry points for SeaFlower that we identified to this date, redirecting mobile users to fake/cloned wallet download websites. In particular, Baidu search engine results are one of the initial vectors for these attacks.
Baidu, Inc is a Chinese multinational AI technology company with a search engine. We were interested to see if there’s any SEO or targeting to coinbase or metamask users in that search engine.
We searched for “download metamask ios” and one of the baidu links on the first results page redirected us to token18[.]app website, which was SeaFlower Drive-by download page, sweet!
While monitoring for results we started noticing that there was an intermediate website, that does a fingerprinting before redirecting to the SeaFlower drive-by download pages. We extracted the client-side fingerprinting from the HTML pages and we identified a code that checks if the referer matches different search engines, in fact, multiple Chinese search engines:
Most of the search engines mentioned are all Chinese search engines:
We created a specific detection rule to hunt for any of the above js code, and we found another piece of code that has bot/spider detections, by checking the userAgent strings, we can see again references to Chinese search engines crawlers/spiders:
function isSpider() {
var flag = false;
var spider = navigator.userAgent.toLowerCase();
var spiderSite = ['baiduspider', 'baidu.', '360Spider', 'sogou.', 'soso.', 'yisouspider', 'bingbot', 'bing.', 'google.', 'googlebot'];
for (let i = 0, len = spiderSite.length; i < len; i++) {
if (spider.indexOf(spiderSite[i]) > 0) {
flag = true;
break;
}
}
if (!flag) {
goPAGE();
}
}
This particular campaign tells us more about the initial vector and the targeting that seems to be search engine oriented, with the majority being Chinese search engines.
At this point, we defined some initial context and learned a bit more about who could be potentially targeted by SeaFlower.
Next, is the backdoored wallets technical analysis part, we will shed some light on how SeaFlower is backdooring the web3 wallets. For readability, we will document in this blogpost how iOS MetaMask wallet and Android Coinbase wallet were backdoored in great detail. The other flavors of these wallets (iOS, Android) and the other wallets (imToken, TokenPocket) are using very similar backdoor code and won’t be all covered in this blogpost but will be briefly documented especially the most relevant parts.
SHA-256 of the .IPA file: 9003d11f9ccfe17527ed6b35f5fe33d28e76d97e2906c2dbef11d368de2a75f8
MetaMask for mobile is a React native app, meaning it can run on both iOS and Android. The first signs of backdoor code can be found at the main.jsbundle
.
A conditional code block was added at the beginning of WriteFile() function. This code block is not present in the official metamask wallet:
This conditional backdoor code will execute anytime writeFile()
is called on a file whose path contains “persist-root”. If we look at where this file is located using a real iPhone, it is stored within the MetaMask app container, it is a configuration file, containing the seed phrase encrypted amongst other runtime configuration data. The file is specifically found at the following path:
/private/var/mobile/Containers/Data/Application/{CONTAINER UID}/Documents/persistStore/persist-root
This new information gives us a high-level understanding of when the backdoor code is called: right after the MetaMask seed-phrase is generated and about to be stored encrypted in the “persist-root” file. We confirmed this by installing MetaMask app on a real iOS device and indeed a network request with the seed phrase is sent right after the user confirms the seed phrase during the wallet’s first setup installation, which is pretty neat as a backdoor implementation, and completely invisible during the usage.
The only issue here is that the startupload()
function highlighted above in the backdoor code, isn’t present in the main.jsbundle()
and there are 0 references to this function in any javascript file or any linked .dylib
file exported symbols.
startupload()
This step required reverse engineering and digging into some Arm64 assembly and low-level code as we will see. I will keep it brief to not confuse the readers, hopefully, it will make sense.
So I started looking at the MetaMask compiled Mach-O file, and noticed two injected .dylibs:
libmetaDylib.dylib
and mn.dylib
seems to be good candidates as these are not supposed to be injected in the original MetaMask iOS Mach-O binary.
libmetaDylib.dylib
was signed with developer ID iPhone Distribution: pl li (259JS6979T
) and team-ID 259JS6979T
libmetaDylib.dylib
contains references to 3 known modding/hooking frameworks: Cycript, Cydia Susbtrate, and the Reveal Framework. This is already a red flag, meaning that something has been done to alter the runtime behavior of the app:
I confirmed Reveal server running in the app container by connecting to it using Reveal app (newer versions of Reveal didn’t work, but got some luck with the version 14 10107, likely the version used by SeaFlower):
Full path to Xcode Derived data was left on the compiled .dylib` leaking a macOS username “lanyu”:
I’ve found multiple references to MonkeyDev Framework which is a hooking & modding utility written by AloneMonkey. MonkeyDev has custom Xcode templates https://github.com/AloneMonkey/MonkeyDev-Xcode-Templates which make it fully integrated to Xcode during the development cycle of these backdoors:
At this point, there are multiple tools for hooking or modding but still no sign of startupload()
and its implementation.
After several checks identifying where a backdoor code could be injected I started looking at the injected libraries, and ran the usual class-dump
on the libmetaDylib.dylib
revealed a strange class name FKKKSDFDFFADS
, highlighted below:
Cross-referencing the class name FKKKSDFDFFADS
I got a solid hit on a Logos tweak installed by the backdoor author, targeting the function dataWithContentsOfFile:options:error
the tweek was installed via MSHookMessageEx()
:
At this point a malicious dataWithContentsOfFile:options:error
implemented by the author will get called right before the original one. The malicious dataWithContentsOfFile:options:error
contains the following code:
At line 39 there’s a clear call to our strang class FKKKSDFDFFADS
At line 29 there’s also a test checking a variable path against the string /meta.app/main.jsbundle.
It seems this function dataWithContentsOfFile:options:error
is expecting a ``.jsbundlefile to read from and return its content, but let’s take a step back and figure out why the author hooked the call of
dataWithContentsOfFile:options:error` it must be for a specific reason.
Going back to the initial Mach-O MetaMask there’s a reference to dataWithContentsOfFile:options:error
at the function 0x1001339cc
:
This function is called by RCTJavascriptLoader::loadBundleAtURL
:
At this point, we can conclude that the author is trying to inject a backdoor in the form of a React Native Bundle and have it loaded by RCTJavascriptLoader used by the RCTBridge
to load javascript.
Every react native app starts with the creation of an RCTBridge
instance. In this, react native loads the javascript, either from the local packager or a pre-built bundle, and executes this inside JavascriptCore
.
We are left with one last exercise to confirm all this and call it a wrap by analyzing the weird class FKKKSDFDFFADS
.
Below is the decompilation of the method FKKKSDFDFFADS::ddsdf
:
Interesting :) I can see base64 blob of typical RSA pivate key/ public keys What this function does is RSA decrypting an RSA encrypted blob encoded in b64. The author linked the library with antoher library called SCRSACryptor and found reference to it in github here: https://github.com/xialun/RSAClass
So, I just created a project in Xcode, extracted the b64 encrypted blob and the RSA keys, linked it to this library, and wrote the following code snippet to decrypt the blob:
…and created an iOS project and ran it:
We finally got the missing startupload()
function. Below is the code of this function:
Above is the source code of the startupload()
function. It simply sends a POST
request to the trx.lnfura[.]org
domain with the seed phrase information that is stored in the variable xlmnmonic
.
Starting from line 59, we can see code starting with a __BUNDLE_START_TIME__
confirming that we are dealing with typical React Native Bundle. The code is basically related to the runtime loading of this bundle and to resolving module dependencies, etc:
The xlmnmonic
variable stores the seed phrase passed to the function _initFromMnemonic
which we can find in the main.jsbundle
:
As with any backdoor code found, it is important to validate it at runtime. I installed the backdoored metamask app on a real iOS device, ran debugserver on iOS and waited with LLDB
on my laptop to break right after the app is launched. I set a conditional breakpoint to break into anything “logos” :
break set -r "logos" -s libmetaDylib.dylib
…then got a first hit at _logosLocalInit()
:
After that I stopped at the function I am interested in _logos_meta_method$_ungrouped$NSData$dataWithContentsOfFile$options$error$
(the one added by the backdoor author using MSHookMessageEx()
):
From there all I have to do is to find where the obj_msgSend()
that will call the weird class name FKKKSDFDFFADS::ddsdf
, and the backdoor code is finally about to be executed via obj_msgSend()
as we can see in the screenshot below:
…and that’s a wrap we confirmed statically, and dynamically the backdoor code and its execution!
By analyzing multiple backdoored iOS MetaMask wallets I found other variants of the backdoor code, with this one having source code comments in Chinese
SHA-256 of the .IPA: 2334e9fc13b6fe12a6dd92f8bd65467cf700f43fdb713a209a74174fdaabd2e2
A single injected dylib
libWalletDylib.dylib
was used, below output of otool -L
:
@executable_path/Frameworks/libWalletDylib.dylib (compatibility version 0.0.0, current version 0.0.0)
This .dylib is signed with Developper ID certificate: iPhone Distribution: Universitas Muhammadiyah Malang (9MJG6A8RD7
) and Team-ID 9MJG6A8RD7
Dumping the strings, we found the same author macOS username “lanyu”, as in the metamask Wallet iOS app, confirming we are dealing with the same author, and also confirmed the usage of the same Monkeydev xcode templates:
The backdoor code wasn’t really hidden like in the MetaMask wallet iOS app, as we could see more methods implemented directly in the injected .dylib
:
Logos was also used with multiple MSHookMessageEx()
hooks at multiple ViewControllers of the app, calling back specific backdoor code methods each time:
SHA-256 of the .IPA: 1e232c74082e4d72c86e44f1399643ffb6f7836805c9ba4b4235fedbeeb8bdca
Similar to the Coinbase iOS wallet, one .dylib
libimtokenhookDylib.dylib
was injected:
@executable_path/Frameworks/libimtokenhookDylib.dylib (compatibility version 0.0.0, current version 0.0.0)
This .dylib
is signed with Developper ID certificate : Sjdbfbd Jdjffb (9J3Q9W2QG2
) and Team-ID 9J3Q9W2QG2
We also found reference to the same macOS username “lanyu” and references to the MonkeyDev framework:
The backdoor was hidden and encrypted same as in the Metamask iOS wallet and I found the exact same backdoor React Native bundle that was loaded. We noticed additional hooks via MSHookMessageEx()
were added to the RCTJavaScriptLoader
, ensuring eventually the loading of the backdoor React Native bundle:
So it seems the Author didn’t do anything specific for imToken Wallet iOS app.
SHA-256 of the .IPA file: 46002ac5a0caaa2617371bddbdbc7eca74cd9cb48878da0d3218a78d5be7a53a
A single ``.dylib
libpocketDylib.dylib` was injected:
@executable_path/Frameworks/libpocketDylib.dylib (compatibility version 0.0.0, current version 0.0.0)
This .dylib is signed with Developper ID certificate: hang Bai (GNY64NUGXC
):
Authority=iPhone Distribution: hang Bai (GNY64NUGXC)
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=Mar 3, 2022 at 5:06:06 PM
Info.plist=not bound
TeamIdentifier=GNY64NUGXC
A new author macOS username leaked named “trader”, we also confirmed the usage of the MonkeyDev Framework:
Logos tweaks are used, in particular hooks to setMnemonic:
were added:
The captured seed phrase is sent to a domain controlled by the attacker:
What I liked about this cluster of activity, is the fact that it is unique, it is web3 related, and not reported before. It seems there was a lot of efforts in the iOS side of things, for example setting up provisioning profiles, automatic deployments, sophisticated backdoor code, etc. More work has been done compared to the Android side of things.
There are some notable challenges when it comes to SeaFlower attribution, for example figuring out if the provisioning servers are run by the same group, and also identifying more initial vectors of the attack beside the Chinese search engines. All these are difficult challenges due to the geographical and language barrier aspects.
We are planning to release sometime in the near future a part 2 of this blog post, where we will do a deep dive into the infrastructure used by SeaFlower and add more elements of attribution.
Definitely not an easy one when it comes to protecting crypto-related software like mobile web3 wallets used by millions of people.
What we write in this section won’t prevent a skilled or determined attacker from tampering with your apps, but there are some easy fixes that could cost money and time to your attackers.
First of all, know and understand your attack surface (hopefully this blog can help), as well as reading “iOS Tampering and Reverse Engineering” document which lists different attack surfaces crypto wallets could be exposed to.
Secondly, make your stuff harder to break :) Detecting inline hooks, injected libraries, detecting instrumentation tools, etc.. are well-known and documented topics.
Always download mobile apps from official stores: Apple AppStore & Play Store.
Never install or accept random provisioning profiles on your iPhone, as you saw in this blog post, they allow the download of unverified software that could potentially steal your crypto.