Clik here to view.

Learn How to Use Python to Assist with Network Forensics
Leverage Python ctypes for quick development of protocol decoders
During an active incident, there typically isn’t enough time to thoroughly reverse engineer a custom protocol used to encrypt, obfuscate, or compress command and control (C2) traffic. Analysis of these algorithms can be error prone and time consuming, neither of which is acceptable when trying to quickly identify actions taken and data exfiltrated by a cyber-attacker during an incident. Python® ctypes is a great solution for these situations. It allows the malware or forensic analyst to leverage the rapid prototyping features provided by Python combined with the ability to run native code. By being able to run native code, the reverse engineer can reuse portions of an implant to assist with developing a protocol decoder.
Benefits of Python ctypes
Python ctypes is intended to provide the ability to use DLLs and shared libraries within a Python application[1]. This allows programmers to use libraries not developed for Python within their applications. An additional benefit of ctypes is that it is not limited to importing shared libraries and DLLs. It is possible to take a binary string of native instructions (e.g. shellcode) and execute it directly within Python.
A simple example: C function
Take for example the following sample C function:
int sample_function(){ return 0x78563412; }
When compiled, the assembly would look something like this:
B8 12 34 56 78 mov eax 0x78563412 C3 retn
This same function can be executed within Python with the following script:
import ctypes #B8 12 34 56 78 mov eax 0x78563412 #C3 retn #Create a Python string sample_function_str = "\xB8\x12\x34\x56\x78\xC3" #Create a ctypes string buffer sample_function_cstr = ctypes. create_string_buffer(sample_function_str) #cast the string buffer to a function that returns an integer #but takes no arguments sample_function = ctypes.cast(sample_function_cstr, ctypes.WINFUNCTYPE(ctypes.c_int)) #Execute print "Return Value: %08X" % sample_function() # Return Value: 78563412
In the above example, the sample function was cast as ctype.WINFUNCTYPE. The WINFUNCTYPE uses the stdcall calling convention where arguments are pushed onto the stack in right-to-left order[2].
A detailed example: Plug-X decryption loop
For a slightly more detailed example, take the Plug-X decryption loop. The algorithm is simple enough that it can be reversed and reproduced, but these same properties make it easy to embed within a Python script. No outside libraries are called and no sub functions are used making this a very easy candidate to run natively within Python. See Figure 1 below.
Figure 1: Plug-X Decrypt Loop
Image may be NSFW.
Clik here to view.
By looking at the start of the Plug-X decryption function in Figure 2, we can see that the function is using a fastcall calling method because the first two arguments are passed using the EAX and ECX registers:
Figure 2: Original Function Entry Point
Image may be NSFW.
Clik here to view.
Through the initial reverse engineering process, it was determined that the decrypt function takes the following four arguments:
- Initial key is passed in register EAX
- Destination buffer passed in register ECX
- Encrypted buffer is the first argument on the stack
- The length of the encrypted buffer is the second argument on the stack
Step 1: Modify the arguments
Since the example Plug-x decrypt function does not use the stdcall calling convention, a few adjustments are needed for the binary to work with Python. The two arguments passed by registers need to be modified so they are passed using the stack. Typically in this situation, it is easier to move these arguments to the end of the argument list so only a minimal amount of instructions need to be modified.
This modification would change the original function definition from:
int PlugX_Decrypt(uint key, void *dst, void *encrypted, int len)
to:
int PlugX_Decrypt(void *encrypted, int len, uint key, void *dst)
If the argument list was kept in the original order, every reference to the original arg_0 would need to be modified to arg_8. By adjusting the argument order, the two arguments originally pushed onto the stack remain unchanged along with all references to them.
To make this modification, the following instructions need to be added before the instruction at address 0x0000000C in Figure 2:
8B 45 10 mov eax, [ebp+arg_8] 8B 4D 14 mov ecx, [ebp+arg_C]
The modified function start now looks like this:
Figure 3: Modified Function Start
Image may be NSFW.
Clik here to view.
Step 2: Modify the function return
Since the number of arguments pushed onto the stack was modified, the function return must also be modified to account for this. The original return looked like this:
Image may be NSFW.
Clik here to view.
The final instruction “retn 8” should be changed to “retn 10h”:
Image may be NSFW.
Clik here to view.
Step 3: Determine proper cast for the function
Now that the binary has been modified to work with the stdcall calling convention, the last step is to determine the proper cast for the function. Using the function definition:
int PlugX_Decrypt(void * encrypted, int len, uint key, void *dst,)
The proper Python ctypes cast would be:
ctypes.WINFUNCTYPE( ctypes.c_int, #return value ctypes.c_char_p, #encrypted buffer ctypes.c_int, #encrypted buffer length ctypes.c_uint, #initial key ctypes.c_char_p #destination buffer )
The pointers to character buffers are cast as c_char_p. Python strings are converted to c_char_p using the create_string_buffer function (specific details regarding the use of ctypes can be found in the Python documentation[1]).
import binascii import re import ctypes import struct plugx_decrypt_func_hex = re.sub("[ \r\n]", "", ''' 55 8B EC 83 EC 08 83 7D 0C 00 56 57 8B 45 10 8B 4D 14 8B F8 8B F1 8B CF 8B D7 89 7D FC 7E 6B 8B 7D 08 2B FE 89 7D F8 8B 7D 0C 53 89 7D 0C EB 09 8D 9B 00 00 00 00 8B 55 08 8B F8 C1 EF 03 8D 84 38 EF EE EE EE 8B F9 C1 EF 05 8D 8C 39 DE DD DD DD 8B FA C1 E7 07 BB 33 33 33 33 2B DF 8B 7D FC 03 D3 C1 E7 09 BB 44 44 44 44 2B DF 01 5D FC 8D 1C 01 02 DA 02 5D FC 89 55 08 8B 55 F8 32 1C 32 46 FF 4D 0C 88 5E FF 75 AD 5B 5F 33 C0 5E 8B E5 5D C2 10 00''') #Convert the ASCII-Hex representation to a binary format plugx_decrypt_func_hex = binascii.unhexlify(plugx_decrypt_func_hex) plugx_decrypt_func_bin = ctypes.create_string_buffer(plugx_decrypt_func_hex) #cast the string buffer to a stdcall/WINFUNCTYPE function plugx_decrypt_native = ctypes.cast( plugx_decrypt_func_bin, #function string buffer ctypes.WINFUNCTYPE( ctypes.c_int, #return value ctypes.c_char_p, #encrypted buffer ctypes.c_int, #encrypted buffer length ctypes.c_uint, #initial key ctypes.c_char_p #destination buffer )) def plugx_decrypt(encrypted, key): """Wrapper for native Plug-X decryption function""" N = struct.unpack("<I", key)[0] encrypted_ptr = ctypes.create_string_buffer(encrypted) decrypted_ptr = ctypes.create_string_buffer(len(encrypted)) decrypted = plugx_decrypt_native(encrypted_ptr, len(encrypted), N, decrypted_ptr) return decrypted_ptr.raw def plugx_parse_hdr(hdr): """Parse fields from Plug-X message header""" hdr_vars = {} key, opts, deflate_len, msg_len, pad = struct.unpack("<IIHHI", hdr) hdr_vars['options'] = opts hdr_vars['deflate_len'] = deflate_len hdr_vars['inflate_len'] = msg_len hdr_vars['pad'] = pad return hdr_vars sample_plugx_header = "77 eb b6 a5 1b cd 8e 6b 23 b2 69 94 2d 35 e8 e4" sample_plugx_header = re.sub("[ \r\n]", "", sample_plugx_header) sample_plugx_header = binascii.unhexlify(sample_plugx_header) #For plugx, the initial encryption key is the first 4 bytes of the message decrypted = plugx_decrypt(sample_plugx_header, sample_plugx_header[:4]) print plugx_parse_hdr(decrypted)
Final thoughts
In certain situations, it is faster to use native code when developing a command and control decoder. I’ve demonstrated that by adding two instructions and modifying a third, it is possible to skip the sometimes tedious process of reverse engineering a decryption function. Also, by using native code, the resulting decoder can run substantially faster than a pure Python implementation.
I would also suggest taking a look at Chopshop developed by Mitre[3]. It is a python framework that uses pynids to assist with the development of command and control decoders.
References
[1] http://docs.python.org/2/library/ctypes.html
[2] http://en.wikipedia.org/wiki/X86_calling_conventions#stdcall
[3] https://github.com/MITRECND/chopshop
Image: Fotolia.com, Andrzej Wilusz
Python is a registered trademark of Python Software Foundation
Clik here to view.