Debugging and Reverse
6
Engineering
Debuggers are the main tools used for reverse engineering. With debuggers, we can
perform analysis at runtime to understand the program. We can identify the call chains and
track indirect calls. With debuggers, we can analyze and watch program runtime to guide
our reverse engineering. In this chapter, we'll learn how to use debuggers in our scripts.
Topics covered in this chapter are as follows:
Portable executable analysis
Disassembling with Capstone
PEfile with Capstone
Debugging using PyDBG
Reverse engineering
There are three main kinds of reverse engineering analysis:
Static analysis: Analysis of the contents of a binary file. This helps to determine
the structure of the executable portions and print out readable portions to get
more details about the purpose of the executable.
Dynamic analysis: This type will execute the binary with or without attaching a
debugger to discover what the purpose is and how the executable works.
Hybrid analysis: This is a mixture of static and dynamic analysis. Repeating
between static analyses, followed by a dynamic debugging, will give better
intuition about the program.
Debugging and Reverse Engineering
Portable executable analysis
Any UNIX or Windows binary executable file will have a header to describe its structure.
This includes the base address of its code, data sections, and a list of functions that can be
exported from the executable. When an executable file is executed by the operating system,
first of all the operating system reads its header information and then loads the binary data
from the binary file to populate the contents of the code and data sections of the address for
the corresponding process.
A Portable Executable (PE) file is the file type that a Windows operating system can
execute or run. The files that we run on Windows systems are Windows PE files; these can
have EXE, DLL (Dynamic Link Library), and SYS (Device Driver) extensions. Also, they
contain the PE file format.
Binary executable files on Windows have the following structure:
DOS Header (64 bytes)
PE Header
Sections (code and data)
We will now examine each of them in detail.
DOS header
The DOS Header starts with the magic numbers 4D 5A 50 00 (the first two bytes are the
letters MZ), and the last four bytes (e_lfanew) indicates the location of the PE header in the
binary executable file. All other fields are not relevant.
PE header
The PE header contains more interesting information. The following is the structure of the
PE header:
[ 88 ]
Debugging and Reverse Engineering
The PE header consists of three parts:
4-byte magic code
20-byte file header, whose data type is IMAGE_FILE_HEADER
224-byte optional header, whose data type is IMAGE_OPTIONAL_HEADER32
Also, the optional header has two parts. The first 96 bytes contain information such as major
operating systems and entry point. The second part consists of 16 entries with 8 bytes in
each entry, to form a data directory of 128 bytes.
You can read more about PE files at: [Link]
/system/platform/firmware/[Link] and structures used
within the file headers at: [Link]
ary/[Link].
We can use the pefile module (a multi-platform full Python module intended for
handling PE files) to get all the details of these file headers in Python.
Loading PE file
Loading a file is as simple as creating an instance of the PE class in the module with the
path to the executable as the argument.
[ 89 ]
Debugging and Reverse Engineering
First, import the module pefile:
Import pefile
Initiate the instance with the executable:
pe = [Link]('path/to/file')
Inspecting headers
In an interactive terminal, we can do a basic inspection of PE file headers.
As usual, import the pefile and load the executable:
>>>import pefile
>>>pe = [Link]('[Link]')
>>> dir(pe)
This will print the object. To better understand, we can use the pprint module to print this
object in a readable format:
>>> [Link](dir(pe))
This will list all in a readable format, as follows:
[ 90 ]
Debugging and Reverse Engineering
We can also print the contents of a specific header as follows:
>>> [Link](dir(pe.OPTIONAL_HEADER))
You can get the hex value of each header with hex():
>>>hex( pe.OPTIONAL_HEADER.ImageBase)
Inspecting sections
To inspect sections in the executable, we have to iterate [Link]:
>>>for section in [Link]:
print ([Link],
hex([Link]),
hex(section.Misc_VirtualSize),
[Link])
PE packers
Packers are the tools used to compress PE files. This will reduce the size of the file as well as
adding another layer of obfuscation to the file being reverse engineered statically. Even
though packers were created to decrease the overall file size of executables, later, the
benefits of obfuscation were used by many malware authors. Packers wrap the compressed
data inside a working PE file structure and decompress the PE file data into memory, and
run it while executing.
We can use signature databases to detect the packer used if the executable is packed.
Signature database files can be found by searching the Internet.
For this we require another module, peutils, which comes with the pefile module.
You can load the signature database from a local file or from a URL:
Import peutils
signatures = [Link]('/path/to/[Link]')
You can also use the following:
signatures =
[Link]('[Link]/jclausing/[Link]')
[ 91 ]
Debugging and Reverse Engineering
After loading the signature database, we can run the PE instance with this database to
identify the signature for the packer used:
matches = [Link](pe, ep_only = True)
print matches
This will output the possible packer used.
Also, if we check the section names in the packed executable, they will have a slight
difference. For example, an executable which is packed with UPX, its section names will be
UPX0, UPX1, and so on.
Listing all imported and exported symbols
The imports can be listed as follows:
for entry in pe.DIRECTORY_ENTRY_IMPORT:
print [Link]
for imp in [Link]:
print '\t', hex([Link]), [Link]
Likewise, we can't list the exports:
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
print hex(pe.OPTIONAL_HEADER.ImageBase + [Link]), [Link],
[Link]
Disassembling with Capstone
Disassembling is the opposite process of assembling. Disassemblers try to create the
assembly code from the binary machine code. For this, we are using a Python module
named Capstone. Capstone is a free, multiplatform and multi-architecture disassembler
engine.
After installation, we can use this module in our Python scripts.
First, we need to run a simple test script:
from capstone import *
cs = Cs(CS_ARCH_X86, CS_MODE_64)
for i in [Link]('\x85\xC0', 0x1000)
print("0x%x:\t%s\t%s" %([Link], [Link], i.op_str))
[ 92 ]
Debugging and Reverse Engineering
The output of the script will be as follows:
0x1000: test eax, eax
The first line imports the module, then initiates the capstone Python class with Cs, which
takes two arguments: hardware architecture and hardware mode. Here we instruct to
disassemble 64 bit code for x86 architecture.
The next line iterates the code list and passes the code to the disasm() in the capstone
instance cs. The second parameter for disasm() is the address of the first installation. The
output of disasm() is a list of installations of type Cslnsn.
Finally, we print out some of this output. Cslnsn exposes all internal information about the
disassembled installations.
Some of these are as follows:
Id: Instruction ID of the instruction
Address: Address of the instruction
Mnemonic: Mnemonic of the instruction
op_str: Operand of the instruction
size: Size of the instruction
byte: The byte sequence of the instruction
Like this, we can disassemble binary files with Capstone.
PEfile with Capstone
Next, we use the capstone disassembler to disassemble the code we extracted with pefile
to get the assemble code.
As usual, we start by importing the required modules. Here, these are capstone and
pefile:
from capstone import *
import pefile
pe = [Link]('[Link]')
entryPoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint
data = pe.get_memory_mapped_image()[entryPoint:]
cs = Cs(CS_ARCH_X86, CS_MODE_32)
for i in [Link](data, 0x1000):
print("0x%x:\t%s\t%s" %([Link], [Link], i.op_str))
[ 93 ]
Debugging and Reverse Engineering
The AddressofEntryPoint value within the IMAGE_OPTIONAL_HEADER is the pointer to
the entry point function relative to the image base address. In the case of executable files,
this is the exact point where the code of the application begins. So, we get the starting of the
code with the help of pefile as pe.OPTIONAL_HEADER.AddressOfEntryPoint and
pass this to the disassembler.
Debugging
Debugging is the process of fixing bugs in a program. Debuggers are those programs that
can run and watchdog the execution of another program. So, the debugger can have control
over the execution of the target program and can monitor or alter the memory and variables
of the targeted program.
Breakpoints
Breakpoints help to stop the execution of the target program within the debugger at a
location where we choose. At that time, execution stops and control is passed to the
debugger.
Breakpoints come in two different forms:
Hardware Breakpoints: Hardware breakpoints require hardware support from
the CPU. They use special debug registers. These registers contain the breakpoint
addresses, control information, and breakpoint type.
Software Breakpoints: A software breakpoint replaces the original instruction
with an instruction that traps the debugger. This can only break on execution.
The main difference between them is that hardware breakpoints can be set on
memory. But, software breakpoints cannot be set on memory.
Using PyDBG
We can use the PyDBG module to debug executables in run time. We can go through a basic
script with PyDBG to understand how it works.
First, we import the modules:
from pydbg import *
import sys
[ 94 ]
Debugging and Reverse Engineering
Then we define a function to handle the breakpoint. Also, it takes the pydbg instance as the
argument. Inside this function, it prints out the execution context of the process and
instructs pydbg to continue:
define breakpoint_handler(dbg):
print dbg.dump_context()
return DBG_CONTINUE
Then we initialize the pydbg instance and set the handler_breakpoint function to handle
the breakpoint exception:
dbg = pydbg()
dbg.set_callback(EXEPTION_BREAKPOINT, breakpoint_handler)
Then attach the process ID of the process which we need to debug using pydbg:
[Link](int([Link][1]))
Next we will set the address at which to trigger the breakpoint. Here, we use bp_set()
function, which accepts three arguments. The first is the address at which to set the
breakpoint, the second is an optional description, and the third argument indicates whether
pydbg restores this breakpoint:
dbg.bp_set(int([Link][1], 16), "", 1)
Finally, start pydbg in the event loop:
dbg.debug_event_loop()
In this example, we pass the breakpoint as an argument to this script. So, we can run this
script as follows:
$ python [Link] 1234 0x00001fa6
pydbg contains many other useful functionalities that can be found in the
documentation at: [Link]
blic/[Link].
[ 95 ]
Debugging and Reverse Engineering
Summary
We have discussed the basic tools that can be used to programmatically reverse engineer
and debug binary files with Python. Now you will be able to write custom scripts to debug
and reverse engineer the executables, which will help in malware analysis. We will discuss
some crypto, hash, and conversion functions with Python in the next chapter.
[ 96 ]