Table of Contents
As an introduction to IDA python, we’re going to write a script that turns immediate values function parameters into enum values. It is a very common things to do, and a simple python script can handle it in most cases. So let’s make our lives easier with some python !
Example
AS an example, we’re going to consider the very simple example below, compiled with Visual Studio :
#include <stdio.h>
#include <stdlib.h>
enum C2_COMMANDS {
LIST_FILE = 0xF,
UPLOAD_FILE = 0xA,
GET_FILE_SIZE = 0xC,
RUN_FILE = 0xE
};
void send_command(int command_type, char* parameter) {
printf("Sending 0x%x / %s\n", command_type, parameter);
}
int main (int argc, char** argv) {
send_command(UPLOAD_FILE, "testfile.txt");
send_command(LIST_FILE, "directory/test");
send_command(GET_FILE_SIZE, "testfile.txt");
send_command(RUN_FILE, "testfile.exe");
send_command(LIST_FILE, "testfile.txt");
}
(You can download the binary here : test_enum.exe)
In my experience, this kind of mechanism is often used by malware for binary protocols. A general communication function is implemented, and a parameter is taken to transmit which type of message is being send.
I like to list all those values in an enum in IDA, which let me :
- See all possible values
- Jump to any usage instantly (using the enum values cross references)
Now in our simple example there are only 5 usages, close to one another. But in a realistic scenario, we could be looking at 30 or more, all in different places around the binary code. Detecting those calls and getting the int parameter is fairly easy, so it can be scripted in python : it will be faster, and this script can be reused later.
Script conception
The assembly of the main function looks like this:
The send_command
function is obviously sub_401000
, and we want the push values 0xA
, 0xF
etc … to be placed in a enum.
We can recover the parameters easily by reading the last few instructions before the call : we’ll replace the immediate value of the operand of every last push before the call to sub_401000
.
Writing the script
We’ll use those imports from IDA:
from idc import *
from idaapi import *
from idautils import *
from ida_enum import *
First, let’s write the function prototype. To be as generic as we can, we’ll take as parameter:
- the address of the function (that works with struct members too)
- the number of push to look for up in the code, which will let us choose which function parameter to consider
- the enum name we want to create or update.
2 more parameters can be usefull :
- How much up in the code we agree to go: it is always a good idea to limit code exploration to avoid jumping to another function or even basic block (when analysis fails for example)
- a parameter indicating wether the values should be displayed in decimal or hex in the enum names (depends on the use case).
So here we are :
def replace_pushed_int(function_ea, target_push_n, target_enum_name, before_limit=0x30, int_type="hex"):
"""
Replace the <target_push_n> last immediate value push before by enum value if possible
function_ea : target function ea (will check Xref to this ea)
for structs use get_name_ea_simple
target_push_n : how many push back we want, starts at 1
target_enum_name : enum to target (created if doesn't exists)
before_limit : how much back we agree to go
str_type : "hex" or "dec", used for the enum value names (in hex or dec number)
"""
First thing to do will be to get the enum object, and create it if it doesn’t exists:
target_enum = get_enum(target_enum_name)
if target_enum == BADADDR:
if int_type == "hex":
target_enum = add_enum(0, target_enum_name, hex_flag())
else:
target_enum = add_enum(0, target_enum_name, dec_flag())
The hex_flag()
and dec_flag()
indicates to IDA how we want the values displayed :
It can be changed afterwards in the enum options.
Then we’ll proceed to analyse every Xref to the function, and check the instructions before the call :
for xref in XrefsTo(function_ea, 0):
current_ea = xref.frm
push_n = 0
while current_ea != BADADDR:
current_ea = prev_head(current_ea, xref.frm - before_limit)
Note the before_limit
paramter is used here, to limit of much back in the code we can go. We can then chec if the instruction at current_ea
is a push, and count the number of push we saw to fond the good one :
if print_insn_mnem(current_ea) == "push":
push_n += 1
if push_n == target_push_n: # that's the push we are looking for
Now we need to check the push parameter : is it a register (push eax
) or an immediate value like we are looking for ? We’ll use get_operand_type
for that.
type_n = get_operand_type(current_ea, 0)
if type_n == 5: # immediate value
value = get_operand_value(current_ea, 0)
And finaly, get the enum value, create it if it doesn’t exist, and apply it to the push operand. Note that we are checking for the int_type
parameter, to name the enum values here:
enum_value = get_enum_member(target_enum, value, 0, 0)
if enum_value == BADADDR:
# Create a new enum value
if int_type == "hex":
enum_val_name = "{:02X}".format(value)
else:
enum_val_name = str(value)
enum_value = add_enum_member(target_enum, get_enum_name(target_enum) + "_" + enum_val_name, value)
op_enum(current_ea, 0, target_enum, 0)
We could also print a warning when the type is a not an immediate value. Printing hexadecimal addresses in the console makes them clickable in IDA, which is very useful to correct errors afterwards.
else: # not an immediate value
print(f"Help needed @ {hex(current_ea)}")
break # Done here, break to the next Xref
And this is it, let’s run it!
Running the final result
The final plugin code can be found here: https://github.com/jeremybeaume/tools/blob/master/IDA_plugin/param_enum.py
Import your file in IDA with the menu File > Script File
(Or ALT + F7
). The function will be available in the python console.
We can move to sub_401000
, and run:
replace_pushed_int(here(), 1, "ENUM_COMMAND")
And here is the result:
This simple script can be very efficient, but could still be improved. For example, it doesn’t handle cases like mov [esp], 0xA
(which is the assembly produced by MingW
). I still gained a crazy amount of time last week using it (more than a 100 calls) !