{
	"id": "d807cc2f-97ca-4a12-bbe2-b3f49c62d249",
	"created_at": "2026-04-06T00:16:51.473537Z",
	"updated_at": "2026-04-10T03:19:57.14043Z",
	"deleted_at": null,
	"sha1_hash": "ec74603b20658d38d478850e3560ca3f1f810c0a",
	"title": "The Art and Science of macOS Malware Hunting with radare2 | Leveraging Xrefs, YARA and Zignatures",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 7242859,
	"plain_text": "The Art and Science of macOS Malware Hunting with radare2 |\r\nLeveraging Xrefs, YARA and Zignatures\r\nBy Phil Stokes\r\nPublished: 2022-03-21 · Archived: 2026-04-05 23:46:44 UTC\r\nWelcome back to our series on macOS reversing. Last time out, we took a look at challenges around string\r\ndecryption, following on from our earlier posts about beating malware anti-analysis techniques and rapid triage of\r\nMac malware with radare2. In this fourth post in the series, we tackle several related challenges that every\r\nmalware hunter faces: you have a sample, you know it’s malicious, but\r\nHow do you determine if it’s a variant of other known malware?\r\nIf it is unknown, how do you hunt for other samples like it?\r\nHow do you write robust detection rules that survive malware author’s refactoring and recompilation?\r\nThe answer to those challenges is part Art and part Science: a mixture of practice, intuition and occasionally\r\nluck(!) blended with a solid understanding of the tools at your disposal. In this post, we’ll get into the tools and\r\ntechniques, offer you tips to guide your practice, and encourage you to gain experience (which, in turn, will help\r\nyou make your own luck) through a series of related examples.\r\nAs always, you’re going to need a few things to follow along, with the second and third items in this list installed\r\nin the first.\r\n1. An isolated VM – see instructions here for how to get set up\r\n2. Some samples – see Samples Used below\r\n3. Latest version of r2 – see the github repo here.\r\nWhat are Zignatures and Why Are They Useful?\r\nBy now you might have wondered more than once if this post just had a really obvious typo: Zignatures, not\r\nsignatures? No, you read that right the first time! Zignatures are r2’s own format for creating and matching\r\nfunction signatures. We can use them to see if a sample contains a function or functions that are similar to other\r\nfunctions we found in other malware. Similarly, Zignatures can help analysts identify commonly re-used library\r\ncode, encryption algorithms and deobfuscation routines, saving us lots of reversing time down the road (for\r\nreaders familiar with IDA Pro or Ghidra, think F.L.I.R.T or Function ID).\r\nWhat’s particularly nice about Zignatures is that you can not only search for exact matches but also for matches\r\nwith a certain similarity score. This allows us to find functions that have been modified from one instantiation to\r\nthe other but which are otherwise the same.\r\nZignatures can help us to answer the question of whether an unknown sample is a variant of a known one. Once\r\nyou are familiar with Zignatures, they can also help you write good detection rules, since they will allow you to\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 1 of 13\n\nsee what is constant in a family of malware and what is variant. Combined with YARA rules, which we’ll take a\r\nlook at later in this post, you can create effective hunting rules for malware repositories like VirusTotal to find\r\nvariants or use them to help inform the detection logic in malware hunting software.\r\nCreate and Use A Zignature\r\nLet’s jump into some malware and create our first Zignature. Here’s a recent sample of WizardUpdate (you might\r\nremember we looked at an older sample of WizardUpdate in our post on string decryption).\r\nLoading the sample into r2, analyzing its functions, and displaying its hashes\r\nWe’ve loaded the sample into r2 and run some analysis on it. We’ve been conveniently dropped at the main()\r\nfunction, which looks like this.\r\nWizardUpdate main() function\r\nThat main function contains some malware specific strings, so should make a nice target for a Zignature. To do\r\nso, we use the zaf command, supplying the parameters of the function name and the signature name. Our\r\nsample file happened to be called “WizardUpdateB1”, so we’ll call this signature “WizardUpdateB1_main”. In r2,\r\nthe full command we need, then, is:\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 2 of 13\n\n\u003e zaf main WizardUpdate_main\r\nWe can look at the newly-created Zignature in JSON format with zj~{} (if you’re not sure why we’re using the\r\ntilde, review the earlier post on grepping in r2).\r\nAn r2 Zignature viewed in JSON format\r\nTo see that the Zignature works, try zb and note the output:\r\nzb returns how close the match was to the Zignature and the function at the current address\r\nThe first entry in the row is the most important, as that gives us the overall (i.e., average) match (between 0.00000\r\nand 1.00000). The next two show us the match for bytes and graph, respectively. In this case, it’s a perfect match\r\nto the function, which is of course what we would expect as this is the sample from which we created the rule.\r\nYou can also create Zignatures for every function in the binary in one go with zg .\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 3 of 13\n\nCreate function signatures for every function in a binary with one command\r\nBeware of using zg on large files with thousands of functions though, as you might get a lot of errors or junk\r\noutput. For small-ish binaries with up to a couple of hundred functions it’s probably fine, but for anything larger\r\nthan that I typically go for a targeted approach.\r\nSo far, we have created and tested a Zignature, but it’s real value lies in when we use the Zignature on other\r\nsamples.\r\nCreate A Reusable and Extensible Zignatures File\r\nAt the moment, your Zignatures aren’t much use because we haven’t learned yet how to save and load Zignatures\r\nbetween samples. We’ll do that now.\r\nWe can save our generated Zignatures with zos \u003cfilename\u003e . Note that if you just provide the bare filename it’ll\r\nsave in the current working directory. If you give an absolute path to an existing file, r2 will nicely merge the\r\nZignatures you’re saving with any existing ones in that file.\r\nRadare2 does have a default address from which it is supposed to autoload Zignatures if the autoload variable is\r\nset, namely ~/.local/share/radare2/zigns/ (in some documentation, it’s ~/.config/radare2/zigns/ )\r\nHowever, I’ve never quite been able to get autoload to work from either address, but if you want to try it, create\r\nthe above location and in your radare2 config file ( ~/.radare2rc ) add the following line.\r\ne zign.autoload = true\r\nIn my case, I load my zigs file manually, which is a simple command: zo \u003cfilename\u003e to load, and zb to run\r\nthe Zignatures contained in the file against the function at the current address.\r\nSample WizardUpdate_B2’s main function doesn’t match our Zignature\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 4 of 13\n\nSample WizardUpdate_B5’s main function is a perfect match for our Zignature\r\nAs you can see, the sample above B5 is a perfect match to B1, whereas B2 is way off with the match only around\r\n46.6%.\r\nWhen you’ve built up a collection of Zignatures, they can be really useful for checking a new sample against\r\nknown families. I encourage you to create Zignatures for all your samples as they will pay dividends down the\r\nline. Don’t forget to back them up too. I learned the hard way that not having a master copy of my Zigs outside of\r\nmy VMs can cause a few tears!\r\nCreating YARA Rules Within radare2\r\nZignatures will help you in your efforts to determine if some new malware belongs to a family you’ve come\r\nacross before, but that’s only half the battle when we come across a new sample. We also want to hunt – and\r\ndetect – files that are like it. For that, YARA is our friend, and r2 handily integrates the creation of YARA strings\r\nto make this easy.\r\nIn this next example, we can see that a different WizardUpdate sample doesn’t match our earlier Zignature.\r\nThe output from zb shows that the current function doesn’t match any of our previous function\r\nsignatures\r\nWhile we certainly want to add a function signature for this sample’s main() to our existing Zigs, we also want\r\nto hunt for this on external repos like VirusTotal and elsewhere where YARA can be used.\r\nOur main friend here is the pcy command. Since we’ve already been dropped at main() ’s address, we can just\r\nrun the pcy command directly to create a YARA string for the function.\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 5 of 13\n\nGenerating a YARA string for the current function\r\nHowever, this is far too specific to be useful. Fortunately, the pcy command can be tailored to give us however\r\nmany bytes we wish at whatever address.\r\nWe know that WizardUpdate makes plenty of use of ioreg , so let’s start by searching for instances of that in the\r\nbinary.\r\nSearching for the string “ ioreg ” in a WizardUpdate sample\r\nLots of hits. Let’s take a closer look at the hex of the first one.\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 6 of 13\n\nA URL embedded in the WizardUpdate sample\r\nThat URL address might be a good candidate to include in a YARA rule, let’s try it. To grab it as YARA code, we\r\njust seek to the address and state how many bytes we want.\r\nGenerating a YARA string of 48 bytes from a specific address\r\nThis works nicely and we can just copy and paste the code into VT’s search with the content modifier. Our first\r\neffort, though, only gives us 1 hit on VirusTotal, although at least it’s different from our initial sample (we’ll add\r\nthat to our collection, thanks!).\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 7 of 13\n\nOur string only found a single hit on VirusTotal\r\nBut note how we can iterate on this process, easily generating YARA strings that we can use both for inclusion and\r\nexclusion in our YARA rules.\r\nThis time we had better success with 46 hits for one string\r\nThis string gives us lots of hits, so let’s create a file and add the string.\r\npcy 32 \u003e\u003e WizardUpdate_B.yara\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 8 of 13\n\nOutputting the YARA string to a file\r\nFrom here on in, we can continue to append further strings that we might want to include or exclude in our final\r\nYARA rule. When we are finished, all we have to do is open our new .yara file and add the YARA meta data\r\nand conditional logic, or we can paste the contents of our file into VTs Livehunt template and test out our rule\r\nthere.\r\nXrefs For the Win\r\nAt the beginning of this post I said that the answer to some of the challenges we would deal with today were “part\r\nArt and part Science”. We’ve done plenty of “the Science”, so I want to round out the post by talking a little about\r\n“the Art”. Let’s return to a topic we covered briefly earlier in this series – finding cross-references in r2 – and\r\nintroduce a couple of handy tips that can make development of hunting rules a little easier.\r\nWhen developing a hunting or detection rule for a malware family, we are trying to balance two opposing\r\ndemands: we want our rule to be specific enough not to create false positives, but wide or general enough not to\r\nmiss true positives. If we had perfect knowledge of all samples that ever had been or ever would be created for the\r\nfamily under consideration, that would be no problem at all, but that’s precisely the knowledge-gap that our rule is\r\naiming to fill.\r\nA common tip for writing YARA rules is to use something like a combination of strings, method names and\r\nimports to try to achieve this balance. That’s good advice, but sometimes malware is packed to have virtually none\r\nof these, or not enough to make them easily distinguishable. On top of that, malware authors can and do easily\r\nrefactor such artifacts and that can make your rules date very quickly.\r\nA supplementary approach that I often use is to focus on code logic that is less easy for author’s to change and\r\nmore likely to be re-used.\r\nLet’s take a look at this sample of Adload written in Go. It’s a variant of a much more prolific version, also written\r\nin Google’s Golang. Both versions contain calls to a legit project found on Github, but this variant is missing one\r\nof the distinctive strings that made its more widespread cousin fairly easy to hunt.\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 9 of 13\n\nA version of Adload that calls out to a popular project on Github\r\nHowever, notice the URL at 0x7226 . That could be interesting, but if we hit on that domain name string alone in\r\nVirusTotal we only see 3 hits, so that’s way too tight for our rule.\r\nYour rules won’t catch much if your strings are too specific\r\nLet’s grab some bytes immediately after the C2 string is loaded\r\nWe might do better if we try grabbing bytes of code right after that string has been loaded, for while the API string\r\nwill certainly change, the code that consumes it perhaps might not. In this case, searching on 96 bytes from\r\n0x7255 catches a more respectable 23 hits, but that still seems too low for a malware variant that has been\r\ncirculating for many months.\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 10 of 13\n\nNotice the dates – this malware has probably far more than just 23 samples\r\nLet’s see if we can do better. One trick I find useful with r2 is to hunt down all the XREFs to a particular piece of\r\ncode and then look at the calling functions for useful sequences of byte code to hunt on.\r\nFor example, you can use sf. to seek to the beginning of a function from a given address (assuming it’s part of a\r\nfunction, of course) and then use axg to get the path of execution to that function all the way from main() . You\r\ncan use pds to give you a summary of the calls in any function along the way, which means combining axg\r\nand pds is a very good way to quickly move around a binary in r2 to find things of interest.\r\nUsing the axg command to trace execution path back to main\r\nNow that we can see the call graph to the C2 string, we can start hunting for logic that is more likely to be re-used\r\nacross samples. In this case, let’s hunt for bytes where sym.main.main calls the function that loads the C2 URL at\r\n0x01247a41 .\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 11 of 13\n\nFinding reusable logic that should be more general than individual strings\r\nGrabbing 48 bytes from that address and hunting for it on VT gives us a much more respectable 45 TP hits. We\r\ncan also see from VT that these files all have a common size, 5.33MB, which we can use as a further pivot for\r\nhunting.\r\nOur hunt is starting to give better results, but don’t stop here!\r\nWe’ve made a huge improvement on our initial hits of 3 and then 23, but we’re not really done yet. If we keep\r\niterating on this process, looking for reusable code rather than just specific strings, imports or method names,\r\nwe’re likely to do much better, and by now you should have a solid understanding of how to do that using r2 to\r\nhelp you in your quest. All you need now, just like any good piece of malware, is a bit of persistence!\r\nConclusion\r\nIn this post, we’ve taken a look at some of r2’s lesser known features that are extremely useful for hunting\r\nmalware families, both in terms of associating new samples to known families and in searching for unknown\r\nrelations to a sample or samples we already have. If you haven’t checked out the previous posts in this series, have\r\na look at Part 1, Part 2 and Part 3. The series continues with Part 5 here.\r\nSamples Used\r\nFile name SHA1\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 12 of 13\n\nWizardUpdate_B1 2f70787faafef2efb3cafca1c309c02c02a5969b\r\nWizardUpdate_B2 dfff3527b68b1c069ff956201ceb544d71c032b2\r\nWizardUpdate_B3 814b320b49c4a2386809b0bdb6ea3712673ff32b\r\nWizardUpdate_B4 6ca80bbf11ca33c55e12feb5a09f6d2417efafd5\r\nWizardUpdate_B5 92b9bba886056bc6a8c3df9c0f6c687f5a774247\r\nWizardUpdate_B6 21991b7b2d71ac731dd8a3e3f0dbd8c8b35f162c\r\nWizardUpdate_B7 6e131dca4aa33a87e9274914dd605baa4f1fc69a\r\nWizardUpdate_B8 dac9aa343a327228302be6741108b5279adcef17\r\nAdload 279d5563f278f5aea54e84aa50ca355f54aac743\r\nSource: https://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nhttps://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/\r\nPage 13 of 13\n\nmalware families, both relations to a sample in terms of associating or samples we already new samples have. If you to known families haven’t checked and in searching out the previous for unknown posts in this series, have\na look at Part 1, Part 2 and Part 3. The series continues with Part 5 here.\nSamples Used   \nFile name SHA1  \n  Page 12 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.sentinelone.com/labs/the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures/"
	],
	"report_names": [
		"the-art-and-science-of-macos-malware-hunting-with-radare2-leveraging-xrefs-yara-and-zignatures"
	],
	"threat_actors": [],
	"ts_created_at": 1775434611,
	"ts_updated_at": 1775791197,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/ec74603b20658d38d478850e3560ca3f1f810c0a.pdf",
		"text": "https://archive.orkl.eu/ec74603b20658d38d478850e3560ca3f1f810c0a.txt",
		"img": "https://archive.orkl.eu/ec74603b20658d38d478850e3560ca3f1f810c0a.jpg"
	}
}