This repository contains a suite of scripts useful for analyzing iOS armv7 binaries in Radare2. It is titles SPAnal because the original target binary for these scripts was Spotify.
My usual disassembler of choice is IDA but the free version does not support armv7. Ghidra's decompiler takes until the heat death of the universe to decompile a binary on the order of 10's of MB. Therefore, I use Radare2. However, Radare2's auto-analysis also takes ages and gigabytes of memory on such a large binary. The goal of these scripts is to basically perform the analysis Radare2 would normally do on selected classes or methods so that it doesn't take so long.
This script walks the __objc_classlist
section of the Mach-O file and builds a JSON object of the classes defined in the binary. The JSON object is formatted as such:
{
<class name>: {
"methods": {
<selector name>: <implementation pointer>,
...
},
"ivars": {
<instance variable name>: <instance variable type>,
...
}
},
...
}
This script performs basically the same function as the classdump
tool but formats the output as a JSON file. The name of the output file is specified as the first commandline argument.
This script uses the JSON classes object generated by get_classlist.py
to process every method of a certain class using the process_method.py
script. The class name is specified as the first commandline argument.
Tells Radare2 to analyze the given method and then resolves the selectors used and receivers of objc_msgSend calls from within the function. The method name is specified as the first commandline argument and is formatted as ..
Tells Radare2 to analyze the function at the given address and then resolves the selectors used from within the function. This is done by iterating through each disassembled instruction and building up a table mapping registers to selectors. This can be performed because on armv7 (at least on all of the binaries I have analyzed), selector addresses are loaded into registers like so:
movw <reg a>, low 2 bytes of address
movt <reg a>, high 2 bytes of address
add <reg a>, pc
ldr <reg b>, [<reg a>]
Then the table can be updated when is moved into r1
, the sel
param for objc_msgSend
.
Using the class information from get_classlist.py
and the selector information from resolve_selectors.py
, this script tries to infer the type of receiver in each call to objc_msgSend
. This is performed by building up a table mapping the instantiated types in scope to the selector that created them. Since instance variables are actually accessed through selectors (such as <ivar>
and set<ivar>:
), when we see a selector that is also the name of an instance variable for the current class, we know that the type of the ivar is in scope.
We can then look to see if subsequent selectors are in fact selectors of an instantiated ivar type and update our table if it is.