Welcome to the next pikoTutorial!
Changing function behavior by modifying its implementation manually is obvious, but can we somehow mess around with the implementations of the functions at runtime of the application? Let’s organize this process in 3 steps:
- obtaining function’s source code at runtime
- converting string with source code to a callable object
- modify function’s source code before calling it
Obtaining function’s source code at runtime
Let’s first learn how to obtain a source code of the function:
# Import inspect module
import inspect
# Define some callback function
def function():
print('Do something')
source_code = inspect.getsource(function)
print(source_code)
Output:
def callback():
print('Do something')
Converting string with source code to a callable object
Now let’s see how to convert some arbitrary Python code provided in a string to a callable Python object:
# Source code that we want to execute
source_code = 'print("Hello from the inside of the string!")'
# Wrap the source code into a function definition, so that it can be accessed by name
function_name = 'print_hello'
function_definition = f'def {function_name}():\n {source_code}'
namespace = {}
# Execute code with a function definition within the given namespace, so that the function definition is created
exec(function_definition, namespace)
# Retrieve function from the namespace and save to a callable variable
print_hello = namespace[function_name]
# Call the function
print_hello()
Output:
Hello from the inside of the string!
Modifying function’s source code before calling it
Now let’s implement a function which takes as an input a function pointer and returns a callable object with the modified source code:
import inspect
def get_hacked_function(function):
# Get the source code of the given function
original_function_source_code = inspect.getsource(function)
# Append a new line to the function source code
modified_function_source_code = f'{original_function_source_code} print("You didn\'t expect me here!")'
# Call the function within the namespace
namespace = {}
exec(modified_function_source_code, namespace)
# Parse function name by taking everything what's between "def " and "(" at the first line
function_name = original_function_source_code.split('(')[0].split()[1]
# Retrieve modified function
modified_function = namespace[function_name]
# Return modified function
return modified_function
It’s time to test it!
# This is the function passed as an input
def original_function():
print("Hello")
# Call our hacking function
hacked_function = get_hacked_function(original_function)
# Call the modified function
hacked_function()
Output:
Hello
You didn't expect me here!
Note for beginners: please keep in mind that such experiments are done mainly for educational purposes. Using the
exec()
function can introduce serious security issues, so it is not recommended to use it in a production environment. If you need to modify the behavior of a function whose source code you don’t have access to, consider using function decorators instead. Always be cautious and ensure you fully understand the security implications before usingexec()
.