{
	"id": "e409d561-8c36-4873-933a-94c3002db67b",
	"created_at": "2026-04-06T00:15:07.148169Z",
	"updated_at": "2026-04-10T03:30:33.13892Z",
	"deleted_at": null,
	"sha1_hash": "cdc6b36b85059d391ffa6494f9e8a3f4334b786e",
	"title": "OSX/MacRansom: analyzing the latest ransomware to target macs",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 2501836,
	"plain_text": "OSX/MacRansom: analyzing the latest ransomware to target macs\r\nArchived: 2026-04-05 15:33:11 UTC\r\nOSX/MacRansom\r\n› analyzing the latest ransomware to target macs\r\n6/12/2017\r\nlove these blog posts? support my tools \u0026 writing on patreon! Mahalo :)\r\nWant to play along? I've shared both the malware's binary executable ('macRansom'), which can be downloaded\r\nhere (password: infect3d).\r\nPlease don't infect yourself!\r\nBackground\r\nHappy Monday! Today we've got some new macOS ransomware to blog about :)\r\nDiscovered by Fortinet researchers, the malware was originally discussed a posting titled \"MacRansom: Offered\r\nas Ransomware as a Service\" (by Rommel Joven (@rommeljoven17), and Wayne Chin Yick Low (@x9090). Go\r\nread their great report first...then come back here!\r\nNow, as I'm supposed to be working on the white paper for my VirusBulletin 2017 talk let's cut to the chase and\r\njump right in, as time is of the essence.\r\nAnalysis\r\nOSX/MacRansom is rather lame piece of ransomware. It's not particularly advanced from a technical point of\r\nhttps://objective-see.com/blog/blog_0x1E.html\r\nPage 1 of 8\n\nview. However, what makes it interesting is that it targets macOS and that it's offered 'as a service.' Honestly I'm\r\nnot 100% sure what the latter means - but Fortinet mentions a TOR-based web portal and contacting the author\r\n(via email) in order to customize the malware. I guess that's the service the malware author provides?\r\nAnyways, on to the technical details! When the malware runs (as noted in the Fortinet writeup) the malware\r\nperforms various anti-debugging and anti-VM checks. All are basic and trivial to pass:\r\nThe anti-debugging check occurs at address 0000000100001075. This is a done via call to ptrace with the\r\n'PT_DENY_ATTACH' flag.\r\nThis anti-debugging logic is well-known (we discussed it our last blog), and it's even documented in\r\nApple's man page for ptrace:\r\nman ptrace\r\nPTRACE(2)\r\nNAME\r\nptrace -- process tracing and debugging\r\n...\r\nPT_DENY_ATTACH\r\n This request is the other operation used by the traced process; it allows a process that\r\n is not currently being traced to deny future traces by its parent. All other arguments\r\n are ignored. If the process is currently being traced, it will exit with the exit status\r\n of ENOTSUP; otherwise, it sets a flag that denies future traces. An attempt by the parent\r\n to trace a process which has set this flag will result in a segmentation violation in\r\n the parent.\r\nIn short, PT_DENY_ATTACH (0x1F), once executed prevents a user-mode debugger from attaching to the\r\nprocess. However, since lldb is already attached to the process (thanks to the --waitfor argument),we can\r\nneatly sidestep this. How? Set a breakpoint on pthread then simply execute a 'thread return' command.\r\nThis tells the debugger to stop executing the code within the function and execute a return command to\r\n'exit' to the caller. Neat!\r\n* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1\r\nframe #0: 0x00007fffad499d80 libsystem_kernel.dylib`__ptrace\r\n(lldb) thread return\r\nWith the anti-debugging logic out of the way, we can debug to our heart's content!\r\nThe first anti-VM check occurs at 0x00000001000010BB.\r\nhttps://objective-see.com/blog/blog_0x1E.html\r\nPage 2 of 8\n\nAfter decoding a string (string decoding routine at: 0x0000000100001F30), the code invokes system to\r\nexecute it, and exits if it returns a non-zero value. Specifically it executes 'sysctl hw.model|grep Mac \u003e\r\n/dev/null':\r\n(lldb) x/s $rdi\r\n0x100200060: \"sysctl hw.model|grep Mac \u003e /dev/null\"\r\n(lldb) n\r\nProcess 7148 stopped\r\n* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over\r\nframe #0: 0x00000001000010bb macRansom`___lldb_unnamed_symbol1$$macRansom\r\n  -\u003e 0x1000010bb \u003c+203\u003e: callq 0x1000028fe ; symbol stub for: system\r\n  0x1000010c0 \u003c+208\u003e: testl %eax, %eax\r\n  0x1000010c2 \u003c+210\u003e: jne 0x100001b05 ; \u003c+2837\u003e\r\n  0x1000010c8 \u003c+216\u003e: movaps 0x19f1(%rip), %xmm0\r\nIn a virtual machine (VM) will return a non-zero value, as the value for hw.model will be something like\r\n'VMware7,1':\r\nOn native hardware:\r\n$ sysctl hw.model\r\nhw.model: MacBookAir7,2\r\nOn virtualized hardware (i.e. in a VM):\r\n$ sysctl hw.model\r\nhw.model: VMware7,1\r\nTo bypass this (in a debugger), step over the call to system, then just set RAX to zero:\r\n(lldb) reg write $rax 0\r\n(lldb) reg read\r\nGeneral Purpose Registers:\r\n  rax = 0x0000000000000000\r\n  rbx = 0xfffffffffffffffe\r\n  rcx = 0x0000010000000100\r\nThis will trick the malware so it continues to execute, as opposed to exiting.\r\nhttps://objective-see.com/blog/blog_0x1E.html\r\nPage 3 of 8\n\nThe next anti-VM check occurs at 0x0000000100001126. Again, the malware decodes a string, executes it\nvia system and exits if the return value is no-zero. This check executes: 'echo $((`sysctl -n\nhw.logicalcpu`/`sysctl -n hw.physicalcpu`))|grep 2 \u003e /dev/null' to check the number of CPUs. On a VM, it\nappears this value is not two, so the malware will just exit to 'avoid' analysis. On native hardware:\n$ sysctl -n hw.logicalcpu\n4\n$ sysctl -n hw.physicalcpu\n2\nOn virtualized hardware (i.e. in a VM):\n$ sysctl -n hw.logicalcpu\n2\n$ sysctl -n hw.physicalcpu\n2\nAgain, if the system that the malware is executing on does not have two CPUs (which a default VM likely\nwill not) the malware will exit. To bypass (in a debugger), again step over the call to system, then just set\nRAX to zero.\nOk that takes care of the anti-analysis checks. Though we bypassed them dynamically in a debugger, they can also\ntrivially permanently patched out. For example, one can simple patch out the JNZ instruction (that checks the\nreturn value of anti-VM checks with NOPs).\nAssumining all the anti-analysis checks pass, or have been thwarted, the malware then persists itself as a launch\nagent. It does this by:\n1. Copying itself to ~/Library/.FS_Store\n2. Decoding an embedded plist and writing it out to ~/Library/LaunchAgents/com.apple.finder.plist:\ncat ~/Library/LaunchAgents/com.apple.finder.plist\nLabelcom.apple.finderStartInterval120RunAtLoadProgramArguments https://objective-see.com/blog/blog_0x1E.html\nPage 4 of 8\n\n\u003cstring\u003ebash\u003c/string\u003e\r\n    \u003cstring\u003e-c\u003c/string\u003e\r\n    \u003cstring\u003e! pgrep -x .FS_Store \u0026\u0026 ~/Library/.FS_Store\u003c/string\u003e\r\n  \u003c/array\u003e\r\n\u003c/dict\u003e\r\n\u003c/plist\u003e\r\nAs the 'RunAtLoad'' key is set to 'true' the malware will be automatically started whenever the user logs in.\r\nSpecifically the OS will execute the value of the 'ProgramArguments' key: bash -c ! pgrep -x .FS_Store \u0026\u0026\r\n~/Library/.FS_Store. This command will first check to make sure the malware isn't already running, then will start\r\nthe malware (~/Library/.FS_Store).\r\nLucky for Objective-See users, BlockBlock will alert you about this persistent attempt:\r\nAs the malware first attempts to persist before encrypting any files, clicking 'Block' on the BlockBlock alert will\r\nstop the malware before it's done any damage :)\r\nFor the sake of analysis, if we allow the malware to persist itself, it will launch the copy of itself that it has just\r\npersisted (~/Library/.FS_Store) via:\r\nlaunchctl load ~/Library/LaunchAgents/com.apple.finder.plist:\r\nProcess 7148 stopped\r\n* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over\r\nframe #0: 0x0000000100001ec6 macRansom`___lldb_unnamed_symbol1$$macRansom + 3798\r\n  -\u003e 0x100001ec6 \u003c+3798\u003e: callq 0x1000028fe ; symbol stub for: system\r\n  0x100001ecb \u003c+3803\u003e: movb $0x25, -0x4733(%rbp)\r\n  0x100001ed2 \u003c+3810\u003e: movb $0x73, -0x4732(%rbp)\r\n  0x100001ed9 \u003c+3817\u003e: movb $0x0, -0x4731(%rbp)\r\n(lldb) x/s $rdi\r\n0x7fff5fbfb960: \"launchctl load /Users/user/Library/LaunchAgents/com.apple.finder.plist\"\r\nhttps://objective-see.com/blog/blog_0x1E.html\r\nPage 5 of 8\n\nThe orignal instance of the malware then exits, as the persistent copy is now off an running.\r\nTo continue analysis by debugging the persistent copy of the malware, execute the following in a secondary\r\ndebugger window, before the original instance of the malware has launched the persistent copy\r\n(~/Library/.FS_Store):\r\n$ sudo lldb (lldb) process attach --name .FS_Store --waitfor\r\nThis will cause the debugger to automatically attach to the persistent copy of the malware once its launched:\r\n$ sudo lldb (lldb) process attach --name .FS_Store --waitfor\r\nProcess 7280 stopped\r\n* thread #1, stop reason = signal SIGSTOP\r\nframe #0: 0x000000011140e000 dyld`_dyld_start\r\n  -\u003e 0x11140e000 \u003c+0\u003e: popq %rdi\r\n  0x11140e001 \u003c+1\u003e: pushq $0x0\r\n  0x11140e003 \u003c+3\u003e: movq %rsp, %rbp\r\n  0x11140e006 \u003c+6\u003e: andq $-0x10, %rsp\r\nExecutable module set to \"/Users/user/Library/.FS_Store\".\r\nArchitecture set to: x86_64h-apple-macosx.\r\nAs the persistent copy of the malware is, well, a copy, it executes the same anti-debugging and anti-VM logic.\r\nThen since it running in persistent state, it checks to see if it's hit a 'trigger' date. That is, it checks if the current\r\ntime is past a hard-coded value. According to the Fortinet report, this is set by the malware author (part of the\r\n'ransomware as a service'). If the current time is before this date, the malware will not encrypt (ransom) any files,\r\nand instead will exit:\r\nHowever, if the trigger date has been hit, ransoming commences! Specifically at address 0x000000010b4eb5f5,\r\nthe malware executes the following, via system to begin encrypting the user's files:\r\n(lldb)\r\nProcess 7280 stopped\r\nhttps://objective-see.com/blog/blog_0x1E.html\r\nPage 6 of 8\n\n* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over\r\nframe #0: 0x000000010b4eb5f5 .FS_Store`___lldb_unnamed_symbol1$$.FS_Store + 1541\r\n  -\u003e 0x10b4eb5f5 \u003c+1541\u003e: callq 0x10b4ec8fe ; symbol stub for: system\r\n  0x10b4eb5fa \u003c+1546\u003e: movaps 0x151f(%rip), %xmm0\r\n  0x10b4eb601 \u003c+1553\u003e: movaps %xmm0, -0x850(%rbp)\r\n  0x10b4eb608 \u003c+1560\u003e: movb $0x0, -0x840(%rbp)\r\n(lldb) x/s $rdi\r\n0x7fff547123e0: \"find /Volumes ~ ! -path \"/Users/user/Library/.FS_Store\" -type f -size +8c -user `whoami` -perm\r\n-u=r -exec \"/Users/user/Library/.FS_Store\" {} +\"\r\nWhat does this command do?\r\nfind /Volumes ~ ! -path \"/Users/user/Library/.FS_Store\" -type f -size +8c -user `whoami` -perm -u=r -exec\r\n\"/Users/user/Library/.FS_Store\" {} +\r\nFirst, returns a list of user files that are readable and bigger than 8 bytes. Then these files will be passed (to a new\r\ninstance) of the malware, in order to be encrypted! We can observe this encryption via a utility such as fs_usage:\r\naccess (_W__) /Users/user/Desktop/pleaseDontEncryptMe.txt\r\nopen F=50 (RW____) /Users/user/Desktop/pleaseDontEncryptMe.txt\r\nWrData[AT1] D=0x018906a8 /Users/user/Desktop/pleaseDontEncryptMe.txt\r\nThe actual encryption routine of the malware begins at 0x0000000100002160. This function is invoked indirectly\r\nvia a call to 'pthread_create()':\r\nAs noted by Fortinet, the encryption is not some RSA-based scheme, but rather uses a symmetric cryptographic\r\nalgorithm. Unfortunately (for users) though there is a static key (0x39A622DDB50B49E9), Joven and Chin Yick\r\nLow state that for each file the key is \"permuted with a random generated number.\" Moreover, this random\r\npermutation is not saved nor conveyed to the attacker. Thus it appears that once encrypted, the files are pretty\r\nmuch gone for good (save for a perhaps a brute force decryption attack).\r\nGood news, RansomWhere? can generically detect at block this attack:\r\nhttps://objective-see.com/blog/blog_0x1E.html\r\nPage 7 of 8\n\nAstute readers might wonder why the alert displays 'find' as the process responsible for the encryption (vs. the\r\nmalware's .FS_Store). The reason is, 'find' invokes fork()' then 'execvp()' to execute the command that is specified\r\nvia '-exec' option. RansomWhere? uses the OpenBSM auditing capabilities of macOS to track process creations -\r\nand while such auditing generates process events for fork(), exec() and execve(), it does not (AFAIK) support\r\nexecvp(). As such, while the fork() process event it detected (and the path set to /usr/bin/find), the subsequent\r\nexecvp() call is not audited. Thus the path stays as /usr/bin/find :/\r\nOne work around would be for RansomWhere? to re-query the OS later, say whenever it detects the creation of an\r\nencrypted file. At this point, the process (i.e 'find') will have execvp()'d and thus the 'correct' path should be\r\nreturned. Or Apple could just fix the auditing subsystem :P ... I mean, this is kinda an auditing bypass?! I'll file a\r\nradar, ...then pray.\r\nConclusions\r\nLuckily for users macOS malware is still rather rare. However, from a technical point of view there's no reason for\r\nthis. Macs are 'easy' to infect and writing a piece of code that encrypts user files is trivial.\r\nThough unlikely, to check if you're infected, look for the following:\r\na process named '.FS_Store' (that's running out of (~/Library)\r\na plist file: '~/Library/LaunchAgents/com.apple.finder.plist\r\nIn this short blog post, we tore apart OSX/MacRansom - a basic piece of macOS new ransomware. Luckily tools\r\nsuch as BlockBlock and RansomWhere? did their job - generally detecting the ransomware's persistence and\r\nencryption. Kinda neat, huh? \u003c3\r\nlove these blog posts? support my tools \u0026 writing on patreon! Mahalo :)\r\nSource: https://objective-see.com/blog/blog_0x1E.html\r\nhttps://objective-see.com/blog/blog_0x1E.html\r\nPage 8 of 8",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://objective-see.com/blog/blog_0x1E.html"
	],
	"report_names": [
		"blog_0x1E.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": 1775434507,
	"ts_updated_at": 1775791833,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/cdc6b36b85059d391ffa6494f9e8a3f4334b786e.pdf",
		"text": "https://archive.orkl.eu/cdc6b36b85059d391ffa6494f9e8a3f4334b786e.txt",
		"img": "https://archive.orkl.eu/cdc6b36b85059d391ffa6494f9e8a3f4334b786e.jpg"
	}
}