How SeaFlower 藏海花 installs backdoors in iOS/Android web3 wallets to steal your seed phrase

taha aka "lordx64"
Confiant
Published in
18 min readJun 12, 2022

--

Photo by Sara Kurfeß on Unsplash

During the course of our work at Confiant, we see malicious activity on a daily basis. What matters the most for us is the ability to:

  • Protect our existing customers.
  • Share unique threat intelligence.
  • Keep finding unique vantage points for better detection.

At Confiant we monitor 2.5+ billion ads per day thanks to our 110+ integrations in the advertising stack allowing us to protect 40K premium websites from bad ads.

That itself gives us great visibility on malicious activity infiltrating the ad stack and the broader Internet, powered by our proprietary uncloaking technology. And that includes all the web3 malicious activity funneling thru it.

The variety and the range of our detection enable us 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.

What is SeaFlower?

SeaFlower is a cluster of activity that we identified earlier this year in March 2022. We believe SeaFlower is the most technically sophisticated threat targeting web3 users, right after the infamous Lazarus Group.

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”:

Author username leak

Note: this same mistake was made on other libraries that helped leaking more macOS usernames, and thus uncovering a set of personas related to SeaFlower

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. Note: signing infrastructure and provisioning infrastructure might or might not be directly related to SeaFlower as it could be abused or just used as a service.
  • 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:

Note: The wallets above are 100% safe and you can use them safely. But like any other good and very popular software, they are exposed to modding, reverse engineering, and backdoors. SeaFlower distributes a backdoored version of these wallets by modifying the original ones.

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.

SeaFlower Modus operandi

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 metamask.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:

Intercepting HTTPS traffic of SeaFlower backdoor
Intercepting HTTPS traffic of SeaFlower backdoor

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:

imToken cloned website (courtesy of DomainTools) hosted at: appim[.]xyz
cloned Metamask website, hosted at: https://74871011[.]huliqianbao[.]com/download.html
cloned Coinbase Wallet website hosted at som-coinbase[.]com
cloned token pocket website hosted at fastrpo[.]com

Note: Surprisingly we didn’t find a backdoored chrome extension delivered from these clone websites, all the links point to the real chrome extension in the Chrome Webstore, so as of now, fake chrome extension delivery isn’t part or wasn’t identified in the SeaFlower intrusion-set.

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:

iPhone with multiple installed backdoored wallets

Note: We reported at very early stage of this campaign all the Apple developer id’s linked to these provisioning profiles to Apple and they revoked them. We are planning to continue reporting this activity to Apple Threat intelligence teams on a regular basis.

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!

SeaFlower targeting via search engine results

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 :

SeaFlower intermediate Fingerprinting

Most of the search engines mentioned are all Chinese search engines:

Chinese search engines targeted by SeaFlower

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.

MetaMask wallet

MetaMask iOS app

SHA-256 .IPA file analyzed: 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:

backdoor code injected inside main.jsbundle
zoomed in backdoor code injected inside main.jsbundle

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.

hunting for 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 :

injected .dylib’s into MetaMask wallet iOS app

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.

TLDR; I am skipping the analysis of mn.dylib as this library is not relevant to the current backdoor as we will see later, so I didn’t spend time analyzing it much.

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:

Cycript, Susbtrate and Reveal linked/injected with libmetaDylib.lib

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) :

Reveal Framework installed on the backdoored metamask ios app

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!

MonkeyDev xcode template

At this point, there are multiple tools for hooking or modding but still no sign of startupload() and its implementation.

A Backdoor inside a Backdoor

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 weird class name FKKKSDFDFFADS, highlighted below:

highlighted weird looking class name

OCMethodTrace is reference to the OCMethodTrace tool written by Michael Chen aka omxcodec , enabling tracing of objective-C classes/methods. OCMethodTrace is also part of MonkeyDev xcode templates: https://github.com/AloneMonkey/MonkeyDev-Xcode-Templates/blob/master/MonkeyAppLibrary.xctemplate/Trace/OCMethodTrace.h

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()

tweek defined in _logosLocalInit() function

Logos is a Perl regex-based preprocessor that simplifies the boilerplate code needed to create hooks for Objective-C methods and C functions with an elegant Objective-C-like syntax. It’s most commonly used along with the Theos build system, which was originally developed to create jailbreak tweaks

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:

backdoor code called

at line 39 there’s a clear call to our weird 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 .jsbundle file 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:

there’s a function at 0x1001339cc calling dataWithContentsOfFile:options:error

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:

decompilation of 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:

created an iOS project and ran it :

decrypted SeaFlower backdoor

we finally got the missing startupload() function :) below is the code of this function:

Above is the source code startupload() function, all what it does is sending 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:

taken from: Rafael de Oleza — Building JavaScript bundles for React Native

xlmnmonic stores the seed phrase passed to the function _initFromMnemonic we can find it in the main.jsbundle:

xlmnmonic storing the seed phrase

Validating the backdoor code execution at runtime:

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():

debuging the backdoor code

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.

Other variants of the MetaMask iOS app backdoor:

by analyzing multiple backdoored iOS MetaMask wallets I found other variants of the backdoor code, with this one having source code comments in Chinese:

Note: this same backdoor React Native Bundle variant was re-used on the imToken Wallet iOS app as well.

Coinbase Wallet iOS app

SHA-256 of the .IPA analyzed: 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:

/Users/lanyu

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:

backdoor code can be seen via class-dump of

Logos was also used with multiple MSHookMessageEx() hooks at multiple ViewControllers of the app, calling back specific backdoor code methods each time:

coinbase Wallet iOS app with logos tweaks

imToken Wallet iOS app:

SHA-256 of the .IPA analyzed: 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.

TokenPocket iOS Wallet:

SHA-256 of the .IPA file analyzed : 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 :

Coinbase Wallet android app

It is important to note that with every iOS app, delivered via provisioning profiles, there was an android app available to download on the same page setup by SeaFlower:

We will do a quick analysis for the Coinbase Wallet APK file to showcase how the backdoor code has been added.

SHA-256 of the APK: 83dec763560049965b524932dabc6bd6252c7ca2ce9016f47c397293c6cd17a5

I installed the APK and run it on an Android emulator, with the SSL interception in place. Not suprisingly, and similar to the iOS flavour of this app, a network request is sent to an attacker controlled domain containing the seed phrase of the victim:

exfiltrated seed phrase from backdoored Coinbase wallet

Android APKs are extremely easy to backdoor, therefore we won’t be spending too much time in this section and we will limit the analysis to the Coinbase Wallet APK, only.

One very known technique is injecting smali code. Looking at the HTTP request sent, and the parameters names, it didn’t take me too much time to find out the backdoor code, that is implemented in a class called XMPMetadata:

The author didn’t add anything fancy to their backdoor, and called this class when the seed phrase is about to be saved to the Storage with the seed phrase in parameters :)

backdoor code inside saveMnemonicToStorage()

and they encoded the C2 domain/url in base64, how fancy :

echo -n "aHR0cHM6Ly9jb2xuYmFzZS5ob21lcy91L3Ntcy8=" | base64 -dhttps://colnbase.homes/u/sms/

Some Final thoughts about SeaFlower:

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.

General security recommendations:

For Web3 Wallet developers

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 this document listing different attack surfaces crypto wallets could be exposed to : https://github.com/OWASP/owasp-mstg/blob/master/Document/0x06c-Reverse-Engineering-and-Tampering.md

Secondly, make your stuff harder to break :) detecting inline hooks, injected libraries, detecting instrumentation tools, etc.. are well-known and documented topics.

For Web3 users

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.

Final words — part 2 coming soon

if you like this content, please ensure to follow me on twitter @lordx64 and please stay tuned for the part 2.

If you are working at a security team please get in touch with us at security@confiant.com to get access to our web3 threat intelligence feeds, including access to more SeaFlower intrusion-set updates.

--

--