Functions
One of the most common binary abstraction level is the function
. Thus,
quokka
offers nicer way to interact to them (compared to the default IDA API).
Prerequisites
For this part of the tutorial, I asssume you already have a working
installation of quokka and you already exported qb-crackme
.
Finding functions
import quokka
prog: quokka.Program
# First way: accessing a function by its address
func = prog[0x8049000]
print(func)
# This is <Function _init_proc at 0x8049000>
# Second solution: by its name
func = prog.fun_names['_init_proc']
# Third: by the get_function method
prog.get_function(name='_init_pr', # Something in the name
approximative=True, # Accept non-exact match
normal=True) # Only regular functions
Function Types ?
Binary functions have types in IDA (e.g. NORMAL, THUNK ...). The
get_function
method allows to restrict results to the NORMAL one :
functions that are defined inside the program with regular body.
The complete list of function types is:
Type | Definition |
---|---|
EXTERN | Function defined in an external library |
IMPORTED | |
NORMAL | Regular functions |
LIBRARY | |
THUNK | Thunk functions |
INVALID | Errored type, should not exist |
The type
of a function is accessible through function.type
attribute.
The Function
object
Like most of the object in quokka
, the function object is in itself a mapping.
The keys are the address and the values the corresponding chunks.
Info
A chunk is an IDA specific concept to deal with code reuse across functions. A function must have at least one chunk but a chunk may be shared by multiple functions. See Igor's explanation
Warning
The direct successors of a function are chunks. However, the interface of function and chunk is similar and most of the functions works the same on the both levels.
Example
prog: quokka.Program
func = prog.fun_names['_init_proc']
print(f"Function {func.name} calls {len(func.calls)} function(s).")
# Print: Function _init_proc calls 1 function(s).
Manipulating function
The Function
class offers fast accessors to common properties. The snippet
below list some of them :
import quokka
prog = quokka.Program('docs/samples/qb-crackme.Quokka', 'docs/samples/qb-crackme')
func = prog.fun_names['level_1']
print(f'Func {func.name} starts at 0x{func.start:x} and finished at 0x{func.end:x}')
# Print the strings in the function
print(func.strings)
# Does the function uses constants ?
if func.constants:
print(f'{func.name} use constants')
# What are the names of the functions called by this one ?
for called in func.calls:
print(called.name)
Function CFG
The CFG of the function is accessible through the func.graph
attribute.
It is a networkx.DiGraph
where the nodes are the blocks (of all the chunks).
Warning
You must use the get_block
method to retrieve a block from a function
object as the dict in itself only refers Chunk
.
Chunks & Super chunks
We already stated that Functions are composed of Chunks, themselves composed of Basic Blocks. However, the Chunk abstraction is never really used... Thus, most accessors at the function level propagate the requests at the block level.
Super Chunks
Super chunks are an abstraction used to deal with functions have multiple non-connected subcomponents.
A SuperChunk
is composed of Chunks
itself.
To iterate through all the chunks of a program a special method exists :
program.iter_chunk
.
This method deals with SuperChunks and Functions to enumerate all the chunks
defined in the program.