IDA Pro 7.6
Static disassembly of ARM executable.
This binary has 40K functions or so, and no symbols. So all the functions are sub_49FFA etc.
However, 90% of these functions have a call to a debug logging function, which says what the function is.
e.g.:
sub_49FFA() {
....do stuff......
debug_message(0x9887, "save_config()","Error: Cannot save config: %s","No disk space!");
....do stuff......
}
So we know sub_49FFA is really save_config()
As this is ARM, this means arg2 is the r1 register pointing to a string. This is shown in the disassembly with LDR r1,=save_config ; "save_config()" immediately before BL debug_message
Is there a way for a script to find all the code refs to debug_message(), and rename the calling function (if not already named) with the string that names the function?
Spent an hour searching, but I'm not skilled in IDA scripting or python so any help would be appreciated.
Up until now, I've been exporting the binary as pseudo code, parsing it to make an .idc script to name the functions, but it's pretty error prone, takes a long time and I often have to load multiple versions of the executable for analysis. Must be a better way.
edit: Thanks to Rolf Rolles for their helpful suggestion of some script functions that might help.
I've been able to cobble something together using the below
ref_addr=0x01D7448
args = idaapi.get_arg_addrs(ref_addr)
func_name = get_func_name(ref_addr)
func_addr = get_name_ea(0, func_name);
print ("func_name for %x is %s (%x)" % (ref_addr,func_name,func_addr))
if args:
arg_offset = args[2]
print ("arg_offset: %x " % arg_offset)
debug_func_dcd = idc.get_operand_value(arg_offset, 1)
debug_func_addr = idaapi.get_dword(debug_func_dcd)
print ("debug_func_dcd: %x debug_func_addr: %x " % (debug_func_dcd,debug_func_addr))
str_type = idc.get_str_type(debug_func_addr)
debug_func = get_strlit_contents(debug_func_addr,-1,str_type).decode("utf-8")
#print ("value: %x" % debug_func)
print ("value: %s" % debug_func)
set_name(func_addr, str(debug_func), 0)
(I'm sure this isn't best practice and doesn't have error checking etc - just a proof of concept).
However, unlike my original question many of the binaries actually have the debug function string as argument 5. i.e. not held in a register at time of debug call, but rather placed on the stack.
.text:001D7414 LDR R4, =aUsbPower_0 ; "usb_power_reset"
.text:001D7434 STR R4, [SP,#0x30+function] ; function
.text:001D7448 BL debug_log
arg_offset = args[4] gives 001D7434, but idc.get_operand_value(arg_offset, 1) (i.e. [SP,#0x30+function]) gives 0
I don't think I can read register R4 even though we know what the value always is unless we're in a live debug session.
Can I evaluate this in a different way to get the address of R4 being set?
Not sure about the etiquette here - so if this has to be a separate question please let me know.
edit2:
In the end, I decided to simply use DumpPseudoCode and parse it to find the right strings I need to name the function properly.
That means most of the entire executable gets pseudocode generated for it, and probably isn't terribly efficient.
Additionally, because the debug strings contain the source file name e.g. "/src/network/upnp/upnp_server.c" I added logic to create folders for the functions and move all functions to the correct place.
That makes things much better. I just have an excuse to go and make a coffee while the script runs.