Functions

Args and Return Types

In some cases, revenge will be able to identify (or guess) correctly the arugment types and return types for the function. However, in some cases you may need to tell it what to expect.

Examples

atof = process.memory[':atof']

# Tell revenge what the return type should be
atof.return_type = revenge.types.Double

# Not needed in this case, but you can tell revenge explicitly the
# parameter type
atof.argument_types = revenge.types.StringUTF8

Calling Functions

You can generically call native functions by first creating a memory object for them. Once you have that object, you usually can call it directly. This is due to some backend magic that attempts to identify argument and return types, and if it fails it falls back to integers.

However, sometimes that’s not enough, and you need to tell revenge what types to send and/or expect back. Luckily, that’s fairly strait forward.

Examples

import revenge
process = revenge.Process("/bin/ls", resume=False, verbose=False)

# Grab memory object for strlen
strlen = process.memory[':strlen']

# Call it directly on a string
strlen("test")
4

# You can specify the arg types if you need to
abs = process.memory[':abs']
abs(types.Int(-12))
12

# Sometimes you need to define what you're expecting to get in return
atof = process.memory[':atof']
atof.return_type = revenge.types.Double
atof('12.123')
12.123

Function Hooking

You can hook and replace functions. For native functions, you can either use an integer (which simply replaces the entire function and returns that integer instead), or a string that contains javascript that will be executed.

For the javascript replace, a special variable is created for you called original. You can assume this variable will always be there and will always be the original function you are replacing. This allows you to call down to the original function if needed, either replacing arguments, return types, or simply proxying the call.

To get data back from inside your replacement function, you need to define replace_on_message. That variable needs to be a callable that takes in at least one argument (the return from the script). Otherwise, all return sends will simply be ignored.

Sometimes it’s easier to just attach to the entry or exit of the function rather than replacing it. You can do this via the on_enter method, in the same way as you would for replace. The only difference is that you do not have to worry about calling the function, as that will be done automatically after your code completes.

Examples

import revenge
process = revenge.Process("/bin/ls", resume=False, verbose=False)

# Replace function 'alarm' to do nothing and simply return 1
alarm = process.memory[':alarm']
alarm.replace = 1

# Un-replace alarm, reverting it to normal functionality
alarm.replace = None

More examples in the code.

revenge.memory.MemoryBytes.replace()

revenge.memory.MemoryBytes.on_enter()

Disassembly

You can disassemble in memory using revenge via the memory object.

Examples

import revenge
process = revenge.Process("a.out", resume=False, verbose=False)

print(process.memory['a.out:main'].instruction_block)
"""
0x804843a: lea        ecx, [esp + 4]
0x804843e: and        esp, 0xfffffff0
0x8048441: push       dword ptr [ecx - 4]
0x8048444: push       ebp
0x8048445: mov        ebp, esp
0x8048447: push       ebx
0x8048448: push       ecx
0x8048449: sub        esp, 0x10
0x804844c: call       0x8048360
"""

# Or just analyze one instruction at a time
process.memory['a.out:main'].instruction
"""<AssemblyInstruction 0x804843a lea ecx, [esp + 4]>"""

Building Functions With C

As of frida version 12.7, there is now support for injecting code simply as C. The backend of frida takes care of compiling it and injecting. revenge now exports this in a super easy to use way through the create_c_function() method.

revenge extends this also by making it easier to perform function calls anywhere in process space. It does this by creating a run-time function defition based on the current known address of the function. See example.

Examples

add = process.memory.create_c_function(r"""
    int eq(int x, int y) {
        return x==y;
    }""")

assert add(4,1) == 5

#
# Runtime function calling
#

# Suppose we want to call strlen, we need to export it as a callable
# function. Since we're compiling C code, the compiler has no idea
# where this function really is, and will throw an exception. However,
# revenge allows you to easily tell the compiler where it is and run as
# if you compiled with the application itself.

# Grab the strlen address
strlen = process.memory[':strlen']

# Setup strlen's argument and return types
strlen.argument_types = types.StringUTF8
strlen.return_type = types.Int

# Main difference is that we're adding a keyword arg to say
# "export/link in strlen here". So long as you've defined the
# MemoryBytes object, this can be anywhere, not just exported symbols.

my_strlen = process.memory.create_c_function(r"""
    int my_strlen(char *s) { return strlen(s); }
    """, strlen=strlen)

assert my_strlen("blerg") == 5