Patrick Wardle Profile picture
Founder of the Objective-See Foundation πŸ› οΈπŸŽ

Mar 30, 2023, 33 tweets

RE: The 3CX VOIP supply chain attack, vendors have stated that macOS was also targeted - but I couldn't find any specific technical details (yet) πŸŽπŸ›β˜ οΈ

One vendor stated, "we cannot confirm that the Mac installer is similarly trojanized"

...let's dive in! 1/n 🧡

We'll start with 3CXDesktopApp-18.12.416.dmg
(SHA 1: 3DC840D32CE86CEBF657B17CEF62814646BA8E98)

It contains a *notarized* app ("3CX Desktop App.app") ...meaning Apple checked it for malware "and none was detected" 😜☠️ 2/n

This app is massive - 381mb 🀯
...let's focus on libffmpeg.dylib
found in the App's /Contents/Frameworks/Electron\ Framework.framework/Versions/A/Libraries directory

(SHA 1: 769383fc65d1386dd141c960c9970114547da0c2)

It was submitted to VT today:
virustotal.com/gui/file/a64fa… 3/n

It's a Mach-O universal binary w/ 2 architectures: x86_64 & arm64

At the start of (only?) the Intel version, a thread is spawned via a function called "run_avcodec"

This kicks off a (thread) function at 0x48430
...this is where things get shady πŸ‘€ 4/n

Shady how?

...a simple triage shows xor loops, timing checks, dynamically resolved APIs, and string obfuscations πŸ‘€

Shady ya?
...and means static analysis is going to be painful, and thus not recommended! 5/n

So, let's debug it! πŸ”¬

Debugging a dylib is a bit tricky - so I wrote a simply loader that will dlopen it.

% lldb dlopen libffmpeg.dylib

We can then run the loader in a debugger and set a breakpoint the dlopen function ...which breaks when the libffmpeg.dylib is loaded 6/n

We can then just instruct the debugger to jump to and begin executing the shady thread function at 0x48430 by changing the $pc register.

(lldb) reg write $pc 0x100048430

Now we can start stepping through the code to coerce it to reveal its secrets πŸ€— 7/n

First, it de-xors the following path: ~/Library/Application Support/3CX Desktop App/.session-lock

It then attempts to open this file. If it does not exist it bails (so you'll have to create it to keep debugging). 8/n

It then queries the host to get the OS version, computer name, etc, etc.

It appears to encrypt then write out this info another file in the same 3CX Desktop App directory, named ".main_storage": 9/n

After various anti-debugging logic (e.g. timing checks) it builds a URL to query. We can easily dump this in the debugger: https[://]pbxsources[.]com/queue

This URL is mentioned as an IOC by @Sophos, who performed analysis on the Windows version

news.sophos.com/en-us/2023/03/… 10/n

After setting a static user-agent and adding host info, it connects out πŸ“‘

This may trigger an firewall alert (initially on the DNS resolution).

Note: alert will originate from the program hosting the malicious dylib (e.g. loader or "3CX Desktop App.app" ) 11/n

Unfortunately the URL (pbxsources[.]com) is now offline ...so the malware doesn't get the HTTP 200 OK it wants, and thus goes off to snooze πŸ₯² 12/n

Continued static analysis appears to show the malware expects to download a 2nd-stage payload.

This appears to be saved as "UpdateAgent" (in the Application Support/3CX Desktop App/ directory).

I don't have access to this binary, so what it does is a mystery!πŸ€·πŸ½β€β™‚οΈ 13/n

For IoCs, I don't know what "3CX Desktop App.app" normally does, but the malicious .dylib interacts w/ the following files (~/Library/Application Support/3CX Desktop App/):

UpdateAgent
.session-lock
.main_storage

C&C URLs appear to be same as Window variant 14/n

I've just added a sample of the malicious .dylib to @objective_see's public macOS malware repo on GitHub: github.com/objective-see/… (password: infect3d) #SharingIsCaring

...it's still notarized by @Apple and has 0 detections on VT πŸ€¦πŸ»β€β™‚οΈβ˜ οΈ 15/n

Hooray, @Apple finally appears to be atoning for their sins of originally approving (notarizing) the application πŸ™ŒπŸ½ 16/n

Now there's (a more detailed) blog post on this all!

Just posted: "Ironing out (the macOS details) of a Smooth Operator"

objective-see.org/blog/blog_0x73…

Interested in learning more about Mac malware & analysis? πŸ‘ΎπŸŽπŸ”¬

You're in luck! I've written a whole book on this topic: taomm.org

It's 100% free online, while all royalties from sales of the printed version are donated to the Objective-See Foundation πŸ˜‡

Better yet, I want to invite you to our Mac Security conference, "Objective by the Sea" (#OBTS) which will be held in Spain this October: objectivebythesea.org/v6/index.html

There, I'm also teaching a 3-day training on Mac malware detection & analysis: objectivebythesea.org/v6/taomm.html

Come join! πŸ€—

Let's keep this thread on the #3CX / #3CXpocalypse supply-chain attack going! πŸ€—

Today let's talk about the 2nd-stage (macOS) payload named "UpdateAgent"

Recall this is downloaded & executed (via popen) by the malicious libffmpeg.dylib:

Using the file command we can see the "UpdateAgent" binary is an x86_64 (Intel) Mach-O.

Via codesign we see though "signed" its signature is adhoc (and thus not notarized):

Also from UpdateAgent's code signing information, its identifier: "payload2-55554944839216049d683075bc3f5a8628778bb8"

Other Lazarus group (πŸ‡°πŸ‡΅) payloads are signed adhoc and use a similar identifier scheme 🧐

For example compare UpdateAgent & another πŸ‡°πŸ‡΅ payload, AppleJuice.C πŸ‘€

In terms of hashes, this UpdateAgent's SHA-1 is 9E9A5F8D86356796162CEE881C843CDE9EAEDFB3

Running the strings command (with the "-" option which instructs it to scan the whole file), we find strings that appear to be related to:

▫️Config files
▫️Config parameters
▫️Attacker server (sbmsa[.]wiki)
▫️Method names of networking APIs

Enough triage πŸ˜… Off to a disassembler/debugger!

First, we find some (basic) anti-analysis logic. The malware forks (making debugging a bit more difficult) and then self-deletes via unlink()

We can observe the self-deletion in a file monitor (event: ES_EVENT_TYPE_NOTIFY_UNLINK)

The rest of UpdateAgent's core logic is also found in its main()

Thanks to non-stripped symbols we see it:

▫️ Calls "parse_json_config"
▫️ Calls "read_config"
▫️ Calls "enc_text"
▫️ Builds a string ("3cx_auth_id=..." + ?)
▫️ Calls "send_post" to sbmsa[.]wiki

...and that's it!

First up, the "parse_json_config" function.

This attempts to open a file, config.json (found in ~/Library/Application Support/3CX Desktop App).

It then extracts values from the keys: "url" & "AccountName":

(I made a fake config.json file, so the malware would keep executing).

With the "url" & "AccountName" extracted from the config.json, the malware then calls a function named "read_config"

This opens/reads in the contents of the .main_storage file (recall that created by libffmpeg.dylib, and contains a UUID) and de-XORs it with the key 0x7a

Next it concats/encrypts the "url" & "AccountName" values via a function named "enc_text". Then combines this + the UUID (from .main_storage) w/ the following format:

3cx_auth_id=UUID;3cx_auth_token_content=encryted url;account name;__tutma=true"

Let's dump this in a debugger:

On to the "send_post" function. This takes as its parameters the URI "https://sbmsa[.]wiki/blog/_insert" and the "3cx_auth_id=..." string:

First, it configures an url request with a hardcoded user-agent string ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...") and stores the "3cx_auth_id=..." string in the "Cookie" HTTP header.

Then, it makes the request to https://sbmsa[.]wiki/blog/_insert

If you're running a network tool such as DNSMonitor (objective-see.org/products/utili…) you'll be able to see capture/view this request.

...and associate it with the pid/process signing id of the malware:

Once UpdateAgent has made the request ...it simply exits 🧐

Maybe this sample is incomplete or is a basic "placeholder" binary that could be updated/swapped out at any time for specific targets of interest. (Supply-chain attacks provide a lot of "non-relevant" targets). πŸ€”

Share this Scrolly Tale with your friends.

A Scrolly Tale is a new way to read Twitter threads with a more visually immersive experience.
Discover more beautiful Scrolly Tales like this.

Keep scrolling