Work in a new version of retrace
is ongoing, this new version features a new method of interposing functions and an advanced configuration mechanism that allows to intercept all calls using custom code. We still consider retrace v2 in beta, but it already works almost to the level of the first version of retrace.
retrace
v2 build is not enabled by default, to enable it you should run
sh autogen.sh ./configure --enable-v2 make
This will leave retrace build as a shared library in src/v2/.libs/libretrace_v2.so
. Please refer to the main README to see how to run your program with this shared library.
Optionally there’s a wrapper that makes running retrace easier. You can enable it using ./configure --enable-v2 --enable-v2_wrapper
. It depends in libnereon which you can find in https://github.com/riboseinc/libnereon. Please follow its build and installation procedure before enabling the retrace
wrapper. The wrapper parameters are:
Usage: retrace2 [options] -c <config file> : set configuration file -p <process command line> : set process command line to be traced -l <retrace v2 library path>: set retrace v2 library path -d <log level> : set log verbosity level(0 : SILENT, 1 : NORMAL, 2 : VERBOSE, 3 : DEBUG) -l <log file> : specify log file -v : print version -h : print help message
Example:
./retrace2 -l .libs/libretrace_v2.so -p /bin/ls
retrace
v2 is configuration file is specified using the RETRACE_JSON_CONFIG
environment variable. This is a JSON configuration file that will specify different "intercept scripts".
The default (hardcoded) configuration is the following:
{ "intercept_scripts": [ { "func_name": "*", "actions": [ { "action_name": "log_params" }, { "action_name": "call_real" } ] } ] }
This defines an intercept_scripts
json object that contains a func_name
and an array of actions
. func_name
refers to the function name this particular intercept will apply too. and actions
defines a list of actions that will be executed when a call to that function is intercepted.
Actions are programmable (via plugins, more on that later) but we ship with five basic actions:
log_params
would simply print the params of the call into the retrace log.
call_real
would call into the actual function that we intercepted. If you don’t specify this action the call would be skipped.
modify_in_param_str
This action lets you modify string parameters in functions call. Take for example the following program:
#include <stdlib.h> int main(void) { getenv("TEST"); getenv("OTHERTEST"); return 0; }
If we want to intercept the call to getenv()
and change "TEST" and "OTHERTEST" for "PATH", the config file would look something like this:
{ "intercept_scripts": [ { "func_name": "getenv", "actions": [ { "action_name": "log_params" }, { "action_name": "modify_in_param_str", "action_params": { "param_name" : "name", "new_str" : "PATH" } }, { "action_name": "call_real" } ] } ] }
This will change all calls to getenv()
to pass its name
parameter as "PATH". If we only wanted to change the first one ("TEST") we can specify a matching parameter so it will only be changed when we have a positive match:
{ "intercept_scripts": [ { "func_name": "getenv", "actions": [ { "action_name": "log_params" }, { "action_name": "modify_in_param_str", "action_params": { "param_name" : "name", "match_str" : "TEST", "new_str" : "PATH" } }, { "action_name": "call_real" } ] } ] }
We added the match_str
directive inside the action_params
of modify_in_param_str
so that the argument would be compared against the one provided ("TEST") and only perform the substitution if it matches. A link to all the functions we support and their parameter names can be found in the source code
modify_in_param_int
This action let’s you modify integer parameters in functions call. Take for example the following program:
#include <sys/types.h> #include <unistd.h> int main(void) { setuid(111); setuid(112); return 0; }
If we want to intercept the call to setuid()
and change the 111 and 112 for a 42, the config file would look something like this:
{ "intercept_scripts": [ { "func_name": "setuid", "actions": [ { "action_name": "log_params" }, { "action_name": "modify_in_param_int", "action_params": { "param_name" : "uid", "new_int" : 5 } }, { "action_name": "call_real" } ] } ] }
This will change all calls to setuid()
to pass its uid
parameter as 5. If we only wanted to change the first one (111) we can specify a matching parameter so it will only be changed when we have a positive match:
{ "intercept_scripts": [ { "func_name": "setuid", "actions": [ { "action_name": "log_params" }, { "action_name": "modify_in_param_int", "action_params": { "param_name" : "uid", "match_int" : 111, "new_int" : 5 } }, { "action_name": "call_real" } ] } ] }
We added the match_int
directive inside the action_params
of modify_int_param_int
so that the argument would be compared against the one provided (111) and only perform the substitution if it matches. A link to all the functions we support and their parameter names can be found in the source code
modify_in_param_arr
To be added.
modify_return_value_int
This action let’s you modify integer return values. Take for example the following program:
#include <unistd.h> #include <sys/types.h> #include <stdio.h> int main(void) { uid_t a = getuid(); printf("%d\n", a); }
If we want to intercept the call to getuid()
and change the return value to 42, the config file would look something like this:
{ "intercept_scripts": [ { "func_name": "getuid", "actions": [ { "action_name": "call_real" }, { "action_name": "modify_return_value_int", "action_params": { "retval_int" : 42 } } ] } ] }
This will make the program always print 42, regardless of the actual return value of guid().
memory_fuzz
This action let’s you randomly fail calls to malloc(), realloc() or even any other function that returns a pointer type. Take for example the following program:
#include <stdio.h> #include <stdlib.h> int main(void) { int fail = 0; int pass = 0; for (int i = 0; i < 10; i++) { void *p = malloc(10); if (p) pass++; else fail++; } printf("fail %d, pass %d\n", fail, pass); }
If we want to fail 10% of the calls to malloc we would use a config file like the following:
{ "intercept_scripts": [ { "func_name": "malloc", "actions": [ { "action_name": "call_real" }, { "action_name": "memory_fuzz", "action_params": { "fail_rate" : 0.1 } } ] } ] }
This will randomly fail 10% of the calls to malloc returning NULL, this is useful to test programs that don’t check the return value of malloc or similar functions.
The above setup only allows very basic substitutions of basic type parameters. The power of retrace lies on its abaility to extended it to intercept and modify any function and parameter type. For an example of this, the basic actions source code is in here. We plan to add many rich actions in the future but you can also add yours for whatever you like.
The retrace logger can be controlled using the following environment variables:
RETRACE_LOGGER_DEF_ENA
Setting this to 0 will completely disable the retrace
logging engine.
RETRACE_LOGGER_DEF_STDOUT_ENA
Setting this to 0 will prevent the retrace logging going to the standard output, this will prevent the program output and retrace output to get mixed.
RETRACE_LOGGER_DEF_FN
Set this to a file path where retrace
will write its log. Useful when you disabled RETRACE_LOGGER_DEF_STDOUT_ENA
.