IDC Python - Executing external programs from IDA
IDA, the Interactive Disassembler, is well known tool. It also comes in Freeware version, however, there are several limitations. For example, it is known, that IDA Freeware doesn’t support IDA Python, scripting language which brings the best from the IDA and from the Python world. For scripting, IDA Freeware supports only IDC, a “toy” [2] C-like language. In past I was wondering if it is possible to run Python even from IDA Freeware, Recently I played little bit more with IDC and I found a way how to pass data from IDA to external Python, and get back results to the IDA. All in IDC, either as a script or a scriptable plugin.
Introduction
My motivation behind executing the Python or other commands/programs from IDA comes from my desire to find way how to easily extending the scripting capabilities of IDA Freeware without building native plugins for it (IDA SDK is not freely available). Of course, I own the IDA Pro version and I can use IDA Python (and I often do that in work), but because as a reverse engineer and malware analyst I love low-level things, it is not so challenging and exciting as to do it with pure IDC. Thus, I sometimes as the exercises rewrite my IDA Python scripts to IDC and I share them with the community.
I know there are many people who use IDA Freeware just because IDA Pro is too expensive for them (students, hobbyists and enthusiasts, etc.). I know, there is IDA Home version for affordable price ($365 in the time of writing) and also free IDA Educational, which is available only to universities, but not individuals. Thus, this is second reason why I often use IDC, because of these people, who would like to use some scripts in IDA, but they are limited only to the scripts written in IDC language, which are nowadays rare in comparison with the IDA Python scripts and plugins.
Execution of external programs from IDA
Let’s deep dive into IDC and IDA API. First, we can review this Alphabetical list of IDC functions, or, the idc.idc
file from the %IDADIR%/idc
directory. There is a function call_system
, which could be used for executing the OS command or external program. It returns error code from OS instead of command output. However, we can redirect the output of the command to the file using the standard OS methods (e.g. echo hello > output.txt
).
Notice that we can provide data from IDA to the command as its arguments, because we can construct the executed “command” as a string in IDC - for example, we can access some values/addresses/data from IDA with IDC and pass them to the executed external program.
The second part is to get output from the executed program back to the IDA. We already have this output saved in the file, thus we need to read the content of this file. IDC has API functions for exactly this task: fopen
and readstr
. Thus, we can get the output from the external program back to the IDA and use it in IDC script.
External Python Base64 Decoder
As a Proof of Concept of this technique I created simple Base64 decoder, which uses external Python. Let’s assume that we have python in our OS Path. Then we can decode Base64 string (e.g. “SGVsbG8gV29ybGQ=”) with the Python using the following command:
python -c "from base64 import b64decode; print(b64decode('SGVsbG8gV29ybGQ=').decode('utf-8'))"
This command is pretty universal, it works on Linux as well as on Windows (if we swap single and double quotes, the execution on Windows from IDA will result in error). It also supports Python2 and Python3, thanks to decoding the Python3 returns string instead of bytes. The only small issue is that print
in the above command will produce the trailing newline, so we will need to remove this newline in IDC.
Getting the output of this command into IDC is described above. We redirect the command output to the file and then we read the file with fopen
and readstr
. And for removing the trailing newline we could use output[:-1]
syntax, but this will fail in case that output is empty. Thus, better option is to use substr(output, 0 ,-2)
construct.
And last thing. We need to obtain Base64-encoded string from the analyzed sample via IDC. For my example I decided to keep it simple, I just decode single Base64-string, which is under the cursor in IDA View. Cursor can be placed either on the data (string value), or on the instruction which references that data. We can recognize these two cases by checking the flags of current address (get_flags(get_screen_ea())
and is_strlit(flags)/is_code(flags)
). For completeness, in case of instruction, the referenced data could be accessed with get_first_dref_from(ea)
Finally, we make a comment with the decoded Base64 value in disassembly.
You can check the final IDC script on my GitHub
Conclusion
The introduced technique isn’t as powerful as the full IDAPython, however, it could be used for lot of things:
- decoding values (as in my PoC with Base64),
- decrypting data (we can use Python cryptographic libraries and we do not need to reimplement them in pure IDC)
- analyzing the sample with external tool and use its result for enhancing IDA analysis
- using IDA features for extract some particular address or data and provide it as an input for external tool
- etc.
Just for curiosity, if anyone is interested in Base64 decoding written in IDC, just check my Snatch String Decryptor, or GandCrab String Decryptor for RC4 implementation.