{
	"id": "420cb95e-3b50-42cc-acca-7317bd976fba",
	"created_at": "2026-04-06T00:07:09.754291Z",
	"updated_at": "2026-04-10T03:34:43.801958Z",
	"deleted_at": null,
	"sha1_hash": "0e8726ec942e3a247a63e8df08f800db0b2167a4",
	"title": "Defeating macOS Malware Anti-Analysis Tricks with Radare2",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 8059399,
	"plain_text": "Defeating macOS Malware Anti-Analysis Tricks with Radare2\r\nBy Phil Stokes\r\nPublished: 2021-09-20 · Archived: 2026-04-05 15:03:39 UTC\r\nIn this second post in our series on intermediate to advanced macOS malware reversing, we start our journey into\r\ntackling common challenges when dealing with macOS malware samples. Last time out, we took a look at how to\r\nuse radare2 for rapid triage, and we’ll continue using r2 as we move through these various challenges. Along the\r\nway, we’ll pick up tips on both how to beat obstacles put in place by malware authors and how to use r2 more\r\nproductively.\r\nAlthough we can achieve a lot from static analysis, sometimes it can be more efficient to execute the malware in a\r\ncontrolled environment and conduct dynamic analysis. Malware authors, however, may have other ideas and can\r\nset up various roadblocks to stop us doing exactly that. Consequently, one of the first challenges we often have to\r\novercome is working around these attempts to prevent execution in our safe environment.\r\nIn this post, we’ll look at how to circumvent the malware author’s control flow to avoid executing unwanted parts\r\nof their code, learning along the way how to take advantage of some nice features of the r2 debugger! We’ll be\r\nlooking at a sample of EvilQuest (password: infect3d), so fire up your VM and download it before reading on.\r\nA note for the unwary: if you’re using Safari in your VM to download the file and you see “decompression failed”,\r\ngo to Safari Preferences and turn off the ‘Open “safe” files after downloading’ option in the General tab and try\r\nthe download again.\r\nGetting Started With the radare2 Debugger\r\nOur sample hit the headlines in July 2020, largely because at first glance it appeared to be a rare example of\r\nmacOS ransomware. SentinelLabs quickly analyzed it and produced a decryptor to help any potential victims, but\r\nit turned out the malware was not very effective in the wild.\r\nIt may well have been a PoC, or a project still in early development stages, as the code and functionality have the\r\nlook and feel of someone experimenting with how to achieve various attacker objectives. However, that’s all good\r\nnews for us, as EvilQuest implements several anti-analysis features that will serve us as good practice.\r\nThe first thing you will want to do is remove any extended attributes and codesigning if the sample has a revoked\r\nsignature. In this case, the sample isn’t signed at all, but if it were we could use:\r\n% sudo codesign --remove-signature \u003cpath to bundle or file\u003e\r\nIf we need the sample to be codesigned for execution, we can also sign it (remember your VM needs to have\r\ninstalled the Xcode command line tools via xcode-select --install ) with:\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 1 of 13\n\n% sudo codesign -fs - \u003cpath to bundle or file\u003e --deep\r\nWe’ll remove the extended attributes to bypass Gatekeeper and Notarization checks with\r\n% xattr -rc \u003cpath to bundle or file\u003e\r\nAnd we’ll attempt to attach to the radare2 debugger by adding the -d switch to our initialization command:\r\n% r2 -AA -d patch\r\nUnfortunately, our first attempt doesn’t go well. We already removed the extended attributes and codesigning isn’t\r\nthe issue here, but the radare2 debugger fails to attach.\r\nFailing to attach the debugger.\r\nThat ptrace: Cannot Attach: Invalid argument looks ominous, but actually the error message is misleading.\r\nThe problem is that we need elevated privileges to debug, so a simple sudo should get us past our current\r\nobstacle.\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 2 of 13\n\nThe debugger needs elevated privileges\r\nYay, attach success! Let’s take a look around before we start diving further into the debugger.\r\nA Faster Way of Finding XREFS and Interesting Code\r\nLet’s run afll as we did when analyzing OSX.Calisto previously, but this time we’ll output the function list to\r\nfile so that we can sort it and search it more conveniently without having to keep running the command or\r\nscrolling up in the Terminal window.\r\n\u003e afll \u003e functions.txt\r\nLooking through our text file, we can see there are a number of function names that could be related to some kind\r\nof anti-analysis.\r\nSome of EvilQuest’s suspected anti-analysis functions\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 3 of 13\n\nWe can see that some of these only have a single cross-reference, and if we dig into these using the axt\r\ncommmand, we see the cross-reference (XREF) for the is_virtual_mchn function happens to be main() , so\r\nthat looks a good place to start.\r\nGetting help on radare2’s axt command\r\n\u003e axt sym._is_debugging\r\nmain 0x10000be5f [CALL] sys._is_virtual_mchn\r\nMany commands in r2 support tab expansion\r\nHere’s a useful powertrick for those already comfortable with r2. You can run any command on a for-each loop\r\nusing @@ . For example, with\r\naxt @@f:\u003csearch term\u003e\r\nwe can get the XREFS to any function containing the search term in one go.\r\nIn this case I tell r2 to give me the XREFS for every function that contains “_is_”. Then I do the same with “get”.\r\nTry @@? to see more examples of what you can do with @@ .\r\nUsing a for-each in radare2\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 4 of 13\n\nSince we see that is_virtual_mchn is called in main , we should start by disassembling the entire main\r\nfunction to see what’s going on, but first I’m going to change the r2 color theme to something a bit more reader-friendly with the eco command (try eco and hit the tab key to see a list of available themes).\r\neco focus\r\npdf @ main\r\nVisual Graph Mode and Renaming Functions with Radare2\r\nAs we scroll back up to the beginning of the function, we can see the disassembly provides pretty interesting\r\nreading. At the beginning of main , we can see some unnamed functions are called. We’re going to jump into\r\nVisual Graph mode and start renaming code as this will give us a good idea of the malware’s execution flow and\r\nindicate what we need to do to beat the anti-analysis.\r\nHit VV to enter Visual Graph mode. I will try to walk you through the commands, but if you get lost at any point,\r\ndon’t feel bad. It happens to us all and is part of the r2 learning curve! You can just quit out and start again if\r\nneeds be (part of the beauty of r2’s speed; you can also save your project: type uppercase P? to see project\r\noptions).\r\nI prefer to view the graph as a horizontal, left-to-right flow; you can toggle between horizontal and vertical by\r\npressing the @ key.\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 5 of 13\n\nViewing the sample’s visual graph horizontally\r\nHere’s a quick summary of some useful commands (there are many more as you’ll see if you play around):\r\nhjkl(arrow keys) – move the graph around\r\n-/+0 – reduce, enlarge, return to default size\r\n‘ – toggle graph comments\r\ntab/shift-tab – move to next/previous function\r\ndr – rename function\r\nq – back to visual mode\r\nt/f – follow the true/false execution chain\r\nu – go back\r\n? – help/available options\r\nHit ‘ once or twice make sure graph comments are on.\r\nUse the tab key to move to the first function after main() (the border will be highlighted), where we can see an\r\nunnamed function and a reference in square brackets that begins with the letter ‘o’ (for example, [ob] , though it\r\nmay be different in your sample). Type the letters (without the square brackets) to go to that function. Type p to\r\nrotate between different display modes till you see something similar to the next image.\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 6 of 13\n\nAs we can see, this function call is actually a call to the standard C library function strcmp() , so let’s rename it.\r\nType dr and at the prompt type in the name you want to use and hit ‘enter’. Unsurprisingly, I’m going to call it\r\nstrcmp .\r\nTo return to the main graph, type u and you should see that all references to that previously unnamed function\r\nnow show strcmp , making things much clearer.\r\nIf you scroll through the graph (hjkl, remember) you will see many other unnamed functions that, once you\r\nexplore them in the same way, are just relocations of standard C library calls such as exit , time , sleep ,\r\nprintf , malloc , srandom and more. I suggest you repeat the above exercise and rename as many as you can.\r\nThis will both make the malware’s behaviour easier to understand and build up some valuable muscle-memory for\r\nworking in r2!\r\nBeating Anti-Analysis Without Patching\r\nThere are two approaches you can take to interrupt a program’s designed logic. One is to identify functions you\r\nwant to avoid and patch the binary statically. This is fairly easy to do in r2 and there’s quite a few tutorials on how\r\nto patch binaries already out there. We’re not going to look at patching today because our entire objective is to run\r\nthe sample dynamically, so we might as well interact with the program dynamically as well. Patching is really\r\nonly worth considering if you need to create a sample for repeated use that avoids some kind of unwanted\r\nbehaviour.\r\nWe basically have two easy options in terms of affecting control flow dynamically. We can either execute the\r\nfunction but manipulate the returned value (like put 0 in rax instead of 1) or skip execution of the function\r\naltogether.\r\nWe’ll see just how easy it is to do each of these, but we should first think about the different consequences of each\r\nchoice based on the malware we’re dealing with.\r\nIf we NOP a function or skip over it, we’re going to lose any behaviour or memory states invoked by that\r\nfunction. If the function doesn’t do anything that affects the state of our program later on, this can be a good\r\nchoice.\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 7 of 13\n\nBy the same token, if we execute the function but manipulate the value it returns, we may be allowing execution\r\nof code buried in that function that might trip us up. For example, if our function contains jumps to subroutines\r\nthat do further anti-analysis tests, then we might get blocked before the parent function even returns, so this\r\nstrategy wouldn’t help us. Clearly then, we need to take a look around the code to figure out which is the best\r\nstrategy in each particular case.\r\nLet’s take a look inside the _is_virtual_mchn function to see what it would do and work out our strategy.\r\nIf you’re still in Visual Graph mode, hit q to get back to the r2 prompt. Regardless of where you are, you can\r\ndisassemble a function with pdf and the @ symbol and provide a flag or address. Remember, you can also use\r\ntab expansion to get a list of possible symbols.\r\nIt seems this function subtracts the sleep interval from the second timestamp, then compares it against the first\r\ntimestamp. Jumping back out to how this result is consumed in main , it seems that if the result is not ‘0’, the\r\nmalware calls exit() with ‘-1’.\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 8 of 13\n\nThe is_virtual_mchn function causes the malware to exit unless it returns ‘0’\r\nThe function appears to be somewhat misnamed as we don’t see the kind of tests that we would normally expect\r\nfor VM detection. In fact, it looks like an attempt to evade automated sandboxes that patch the sleep function, and\r\nwe’re not likely to fall foul of it just by executing in our VM. However, we can also see that the next function,\r\nuser_info , also exits if it doesn’t return the expected value, so let’s practice both the techniques discussed above\r\nso that we can learn how to use the debugger whichever one we need to use.\r\nManipulating Execution with the radare2 Debugger\r\nIf you are at the command prompt, type Vp to go into radare2 visual mode (yup, this is another mode, and not\r\nthe last!).\r\nThe Visual Debugger in radare2\r\nOoh, this is nice! We get registers at the top, and source code underneath. The current line where we’re stopped in\r\nthe debugger is highlighted. If you don’t see that, hit uppercase S once (i.e., shift-s ), which steps over one\r\nsource line, and – in case you lose your way – also brings you back to the debugger view.\r\nLet’s step smartly through the source with repeated uppercase S commands (by the way, in visual mode,\r\nlowercase ‘s’ steps in, whereas uppercase ‘S’ steps over). After a dozen or so rapid step overs, you should find\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 9 of 13\n\nyourself inside this familiar code, which is main() .\r\nmain() in Visual Debugger mode\r\nNote the highlighted dword, which is holding the value of argc . It should be ‘2’, but we can see from the\r\nregister above that rdi is only 1. The code will jump over the next function call, which if you hit the ‘1’ key on\r\nthe keyboard you can inspect (hit u to come back) and see this is a string comparison. Let’s continue stepping\r\nover and let the jump happen, as it doesn’t appear to block us. We’ll stop just short of the is_virtual_mchn\r\nfunction.\r\nSeek and break locations are two different things!\r\nWe know from our earlier discussion what’s going to happen here, so let’s see how to take each of our options.\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 10 of 13\n\nThe first thing to note is that although the highlighted address is where the debugger is, that’s not where you are if\r\nyou enter an r2 command prompt, unless it’s a debugger command. To see what I mean, hit the colon key to enter\r\nthe command line.\r\nFrom there, print out one line of disassembly with this command:\r\n \u003e pd 1\r\nNote that the line printed out is r2’s current seek position, shown at the top of the visual view. This is good. It\r\nmeans you can move around the program, seek to other functions and run other r2 commands without disturbing\r\nthe debugger.\r\nOn the other hand, if you execute a debugger command on the command line it will operate on the source code\r\nwhere the debugger is currently parked, not on the current seek at the top of your view (unless they happen to be\r\nthe same).\r\nOK, let’s entirely skip execution of the _is_virtual_mchn function by entering the command line with : and\r\nthen:\r\n \u003e dss 2\r\nHit ‘return’ twice. As you can see, the dss command skips the number of source lines specified by the integer\r\nyou gave it, making it a very easy way to bypass unwanted code execution!\r\nAlternatively, if we want to execute the function then manipulate the register, stop the debugger on the line where\r\nthe register is compared, and enter the command line again. This time, we can use dr to both inspect and write\r\nvalues to our chosen register.\r\n\u003e dr eax\r\n\u003e dr eax = 0\r\n\u003e drr\r\n\u003e dro\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 11 of 13\n\nViewing and changing register values\r\nAnd that, pretty much, is all you need to defeat anti-analysis code in terms of manipulating execution. Of course,\r\nthe fun part is finding the code you need to manipulate, which is why we spent some time learning how to move\r\naround in radare2 in both visual graph mode and visual mode. Remember that in either mode you can get back to\r\nthe regular command prompt by hitting q . As a bonus, you might play around with hitting p and tab when in\r\nthe visual modes.\r\nAt this point, what I suggest you do is go back to the list of functions we identified at the beginning of the post\r\nand see what they do, and whether it’s best to skip them or modify their return values (or whether either option\r\nwill do). You might want to look up the built-in help for listing and setting breakpoints (from a command prompt,\r\ntry db? ) to move quickly through the code. By the time you’ve done this a few times, you’ll be feeling pretty\r\ncomfortable about tackling other samples in radare2’s debugger.\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 12 of 13\n\nConclusion\r\nIf you’re starting to see the potential power of r2, I strongly suggest you read the free online radare2 book, which\r\nwill be well worth investing the time in. By now you should be starting to get the feel of r2 and exploring more on\r\nyour own with the help of the ? and other resources. As we go into further challenges, we’ll be spending less\r\ntime going over the r2 basics and digging more into the actual malware code.\r\nIn the next part of our series, we’re going to start looking at one of the major challenges in reversing macOS\r\nmalware that you are bound to face on a regular basis: dealing with encrypted and obfuscated strings. I hope\r\nyou’ll join us there and practice your r2 skills in the meantime!\r\nSource: https://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nhttps://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/\r\nPage 13 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.sentinelone.com/labs/defeating-macos-malware-anti-analysis-tricks-with-radare2/"
	],
	"report_names": [
		"defeating-macos-malware-anti-analysis-tricks-with-radare2"
	],
	"threat_actors": [
		{
			"id": "aa73cd6a-868c-4ae4-a5b2-7cb2c5ad1e9d",
			"created_at": "2022-10-25T16:07:24.139848Z",
			"updated_at": "2026-04-10T02:00:04.878798Z",
			"deleted_at": null,
			"main_name": "Safe",
			"aliases": [],
			"source_name": "ETDA:Safe",
			"tools": [
				"DebugView",
				"LZ77",
				"OpenDoc",
				"SafeDisk",
				"TypeConfig",
				"UPXShell",
				"UsbDoc",
				"UsbExe"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "2d06d270-acfd-4db8-83a8-4ff68b9b1ada",
			"created_at": "2022-10-25T16:07:23.477794Z",
			"updated_at": "2026-04-10T02:00:04.625004Z",
			"deleted_at": null,
			"main_name": "Cold River",
			"aliases": [
				"Blue Callisto",
				"BlueCharlie",
				"Calisto",
				"Cobalt Edgewater",
				"Gossamer Bear",
				"Grey Pro",
				"IRON FRONTIER",
				"Mythic Ursa",
				"Nahr Elbard",
				"Nahr el bared",
				"Seaborgium",
				"Star Blizzard",
				"TA446",
				"TAG-53",
				"UNC4057"
			],
			"source_name": "ETDA:Cold River",
			"tools": [
				"Agent Drable",
				"AgentDrable",
				"DNSpionage",
				"LOSTKEYS",
				"SPICA"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "3a057a97-db21-4261-804b-4b071a03c124",
			"created_at": "2024-06-04T02:03:07.953282Z",
			"updated_at": "2026-04-10T02:00:03.813595Z",
			"deleted_at": null,
			"main_name": "IRON FRONTIER",
			"aliases": [
				"Blue Callisto ",
				"BlueCharlie ",
				"CALISTO ",
				"COLDRIVER ",
				"Callisto Group ",
				"GOSSAMER BEAR ",
				"SEABORGIUM ",
				"Star Blizzard ",
				"TA446 "
			],
			"source_name": "Secureworks:IRON FRONTIER",
			"tools": [
				"Evilginx2",
				"Galileo RCS",
				"SPICA"
			],
			"source_id": "Secureworks",
			"reports": null
		}
	],
	"ts_created_at": 1775434029,
	"ts_updated_at": 1775792083,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/0e8726ec942e3a247a63e8df08f800db0b2167a4.pdf",
		"text": "https://archive.orkl.eu/0e8726ec942e3a247a63e8df08f800db0b2167a4.txt",
		"img": "https://archive.orkl.eu/0e8726ec942e3a247a63e8df08f800db0b2167a4.jpg"
	}
}