Radare2 Power Ups | Delivering Faster macOS Malware Analysis With r2 Customization

In previous posts, we’ve explored how analysts can use radare2 (aka r2) for macOS malware triage, work around anti-analysis tricks, decrypt encrypted strings, and generate function signatures and YARA rules. Like most reversing tools, radare2 can be customized and extended to increase the analyst’s productivity and make analysis and triage much faster.

In this fifth post in the series, we look at some effective ways to power up r2, providing practical examples to get you started on the path to making radare2 even more productive for macOS malware analysis. We’ll cover automation and customization via alias, macros and functions.

Power Up Your .radare2rc Config File With Aliases & Macros

Just as most shells have a “read command” config file (e.g., .bashrc, .zshrc), so r2 has a ~/.radare2rc file in which you can define environment variables, aliases and macros. This file doesn’t exist by default so you need to create it.

It’s often said that one of the obstacles to adopting r2 is the steep learning curve, a large part of which is getting muscle-memory familiar with r2s cryptic commands. One very fast way to flatten that curve is to define macros and aliases for new commands as you learn them – naming any hard-to-remember native commands with your own labels.

Aliases and macros are also useful for chaining oft-used commands together. If you find yourself always running the same commands as your work through your initial triage of a sample, you can save yourself some time and typing by combining those commands into one or more aliases or macros.

An r2 customization to find the entrypoint of x86 dylibsAn r2 customization to find the entrypoint of x86 dylibs
We will look at some useful examples below, but first let’s understand the syntax for aliases and macros.

An alias is defined with a name prefixed by a $ sign, an = operator and a value in single quotes. Values can be one or more commands, separated by a semi-colon. For example, if you struggle to remember r2s rather cryptic command names, you could replace them with more memorable command name of your own:

$libs=’il’

Typing $libs in an r2 session will now run the il command.

The $libs macro prints out the linked dynamic libraries in an executable fileThe $libs macro prints out the linked dynamic libraries in an executable file

From the Official Radare2 book, we learn that macros are written inside parentheses with each command separated by a semi-colon. The first item in the list is the macro name. By way of example, rather than having a $libs alias, why not print out sections and linked libraries at the same time? This example would do just that:

(secs; iS; il)

Macros are called with the syntax .(macro) like so:

Calling a macro in r2 to print out a binary’s sections and linked librariesCalling a macro in r2 to print out a binary’s sections and linked libraries

It’s easy to see how you can build on this idea. I use a macro called .(meta) to give me all the basic info about a file’s structure as soon as I’ve loaded it into radare2.

Get all the info you need about a file with the meta macroGet all the info you need about a file with the meta macro

This macro provides the file hashes in various algos, the compiled language, file size, sections, section entropy and the load commands. If the file under analysis is UPX packed, it will also indicate that, and if the source code is Go it displays the Go Build ID string. The macro is defined as follows, feel free to adopt or adapt it for your needs:

(meta; it; i~file; i~class; i~arch; i~lang; rh; iS md5,entropy; ih~cmd~!cmdsize; il; izz | grep -e Go\ build\ ID -we upx;)

Within the .(meta) macro, notice the command sequence ih~cmd~!cmdsize. This warrants a little explanation. Readers of our previous posts on r2 and macOS malware may recall that the tilde is r2’s internal grep function. The tilde followed by an exclamation mark ~!<expression> filters out the given expression, equivalent to grep -v. You can see the difference in the following image.

Filtering wanted and unwanted information with r2’s ~ commandFiltering wanted and unwanted information with r2’s ~ command

Moreover, note that the macro calls out to the system grep utility as well. The ability to utilize any command line utility on the system from within r2 is one of its major advantages over other reversing platforms.

Passing Arguments to radare2 Macros

Many of the things you can do with macros you could also do with Aliases, and vice versa; it’s largely a matter of personal preference. However, note that macros have one neat superpower – you can pass arguments to them.

Here’s a good example: r2 has a command for diffing or comparing code within a sample, either as hex or disassembly (cc and ccd). For some reason (I’m sure there’s a perfectly good one), this function counterintuitively displays the output from the first address given to the right of the output from the second address given. We can ‘correct’ this with a Macro that takes the addresses as arguments but swaps their order when it passes them to cc.

(diffs x y; cc $1 @ $0)
The cc command places the output of the first address to the right of the second address. The .(diffs) macro fixes thisThe cc command places the output of the first address to the right of the second address. The .(diffs) macro fixes this

Incidentally, the cc command (or our reimplementation of it in a macro) can be very useful for finding common code within samples when writing YARA or other hunting rules, a topic we’ll discuss a bit further below.

Finding IP Address Patterns and Other Useful Artifacts

To find IP address patterns and other useful artifacts in a binary, you can create macros with search regexes.

Here’s a few examples to get your started.

Find IP Address Patterns:

(ip; /e /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
A sample of Atomic Stealer quickly gives up its C2 with the help of the .(ip) macroA sample of Atomic Stealer quickly gives up its C2 with the help of the .(ip) macro

Find Interesting Strings

Search for places where an executable gathers user and local environment information.

(reg; /e /home/i; /e /getenv/i; /e /Users/)

You can automate different searches for XOR instructions with the following r2 macro:

(xor ;  f~xor | sort -k 2 -n; /e /xor byte/i; izz~+xor)
The LockBit for Mac ransomware uses an XOR key of 0x39The LockBit for Mac ransomware uses an XOR key of 0x39

Testing a File Against Local YARA Rules

For these macros, you will need YARA installed locally on the host. This can be done with MacPorts, Homebrew or by installing from Github and following the instructions here.

With YARA installed, it is easy to call it from within r2 to see if a rule you’ve created for a sample will fire. This is a great way to develop and test rules on the fly as you triage new samples.

On my analysis machines, I have my rules stored in a subdirectory of /usr/local/bin, so my macro looks like this:

(yara; !yara -s /usr/local/bin/scan_machos/myyara.yara `o.`)

As yara is an external command, it is prefixed by an exclamation point !. This is how to tell the r2 shell that we want to call an external command line utility, a very useful feature that allows you to bring in all the power of the command line utilities at your disposal directly into r2. The -s option allows us to see which strings hit (and how many times). See man yara for more options. The `o.` command at the end of the macro is an r2 command that returns the file name of the currently loaded binary.

A simple YARA rule to detect Geacon samples called from the r2 command lineA simple YARA rule to detect Geacon samples called from the r2 command line
Since Apple’s own built-in malware blocking tool XProtect also uses YARA rules, you can create a macro to see whether Apple has a rule for your sample. To create an .(xp) macro to check files against Apple’s XProtect database signatures file (remember: YARA must be installed first), use the following macro:

(xp; !yara -w /Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Resources/XProtect.yara `o.`)

Don’t be surprised, however, if you don’t get many matches: XProtect’s YARA signature database is thin at best.

Print Your Customizations when radare2 Starts Up

By now, you might be starting to collect quite a list of macros and aliases. How to remember them all? There’s a couple of built-in ways, and we’ll also look at one last .radare2rc customization to help us out with this, too.

From within, r2 you can see all defined aliases and macros by typing $* and (*, respectively.

Printing out aliases and macros with their valuesPrinting out aliases and macros with their values

We can also have r2 print our entire config file when it starts up by adding a further customization. At the end of the .radare2rc file, try something like this:

echo ENV: ; !cat -v /Users//.radare2rc | sed -e '$ d'; echo;

The sed command after the pipe prevents the last line of the file from being printed – an optional customization you can ignore if you wish. You could also just add the $* and (* commands above to the config file instead, but I like to see the whole file as a reminder of the entire environment.

It can be helpful to automatically print the entire config file out as r2 starts upIt can be helpful to automatically print the entire config file out as r2 starts up

These examples should be enough to get you started creating useful aliases and macros to help speed along your own analysis.

How to Diff Binaries and Binary Functions with radare2

Aliases and macros are useful shortcuts – the command line equivalent to GUI apps’ hotkeys and key chords – but there are other, more powerful ways we can customize radare2 and drive it with custom functions and scripts.

As an example, let’s add the following function to our shell config file (e.g., ~/.zshrc or ~/.bashrc):

rfunc() {
  radiff2 -AC -t 100 $1 $2 2> /dev/null | egrep --color "\bUNMATCH\b|$"
}

This leverages a radare2 tool called radiff2. This tool (among a bunch of others) is installed as part of the radare2 suite. With the function added to our shell config, we’ll start a new Terminal session and call the function directly from the command line rather than from within r2.

$ rfunc file1 file2

The rfunc() function tells us which functions match, which do not, and which are new between any two given binaries. Here’s part of the output from two very different variants of Atomic Stealer:

Two variants of Atomic Stealer. The sendlog function exfiltrates user dataTwo variants of Atomic Stealer. The sendlog function exfiltrates user data

To get a graphical output of how two functions differ, let’s begin by using radiff2 directly. This utility has many options and we’ll only explore a few here, but it is well worth digging into deeper.

You can compare two functions or offset addresses in two binaries with the following syntax:

$ radiff2 -g offset1,offset2 file1 file2

Or, in case both binaries use the same function name, e.g., sym._main.sendlog in our example above, you can simply provide the function name instead of the addresses:

$ radiff2 -g function_name file1, file2

In this example, I’ll compare the main function of two samples of Genieo adware.

Genieo samples of varying sizesGenieo samples of varying sizes

As shown in the image above, the files are quite different sizes.

$ radiff2 -g main a1219451eacd57f5ca0165681262478d4b4f829a7f7732f75884d06c2287ef6a 80573de5d79f580c32b43c82b59fbf445b91d6e106b3a4f2f67f2a84f4944433
Partial output of radiff2’s graphical diff enginePartial output of radiff2’s graphical diff engine

However, the output shows us that the main functions are structured identically and differ only in terms of offset addresses and certain hard coded values. This kind of information is extremely helpful for creating effective signatures for a malware family.

As radiff2 outputs to the Terminal, display can sometimes be tricky. It’s possible to leverage Graphviz and the dot and xdot utilities to produce more readable graphs. Though a deep dive into Graphviz takes us beyond the scope of this post, try installing xdot from brew install xdot and playing around with options such as these:

$ radiff2 -md -g  -md -g file1 file2 | odt -

Alternatively,

$ radiff2 -md -g  -md -g file1 file2 > main.dot
$ dot -Tpng main.dot -o main.png
$ open main.png

These can produce graphical diffs such as the following:


Of course, once you hit on one or more graph workflows that work for you, you can then add them as functions to your shell config file for maximum convenience.

Conclusion

In this post we’ve seen how we can power up radare2 by means of aliases, macros and functions. We’ve learned how these shortcuts and automations can allow us to make r2 easier and more productive to use.

That’s not all there is to powering up radare2, however, as we have yet to explore driving radare2 with scripts via r2pipe to do deeper analysis, decrypt strings and other advanced functions. We’ll cover that in a future post on radare2 and macOS malware, so be sure to follow us on social media to stay notified when that goes live. In the meantime, don’t forget to check out our earlier posts on radare2 if you didn’t already!

Article Link: https://www.sentinelone.com/labs/radare2-power-ups-delivering-faster-macos-malware-analysis-with-r2-customization/