While reading stackoverflow I recalled a little snippet I wrote 4 years ago.
This snippet allows the programmer to get a stack trace in C environment on Linux/x86 whenever a memory access problem happens. x86_64 is not supported yet.
/* */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <execinfo.h>
#include <unistd.h>
#define __USE_GNU
#include <ucontext.h>
typedef void signal_specific_handler(int signum, siginfo_t *pinfo, void *arg) ;
struct sigaction_pack
{
struct sigaction current ;
struct sigaction original ;
signal_specific_handler *pspecific ;
} ;
/* we are instrumenting the first 32 signals. We are NOT instrumenting the real-time signals */
static struct sigaction_pack g_pbs_signal_actions[32] ;
static size_t max_pbs_size = 32 ;
static void* g_a_activation_record_list[64] ;
static size_t max_activation_record_count = 64 ;
static size_t activation_record_count ;
/* This is a global variable set at program start time. It marks the highest used stack address. */
extern void *__libc_stack_end;
/*------------------------------------------------------------------------------
*
* A short refresh on the x86 assembly
* -----------------------------------
*
* %eip -> current instruction
*
* %esp -> top of the stack (the used element)
*
* This is the stack layout we see with every stack frame.
*
* +-----------------+
* %esp -> | local variable |
* | local variable |
* +-----------------+
* %ebp -> | %ebp last frame |-+
* | | |
* | return address | |
* |(old instruction | |
* | pointer ) | |
* +-----------------+ |
* | parameters 1.. | |
* +----| parameters 2... |--+
* | | parameters--- |
* | +-----------------+
* | | local variable |
* | | local variable |
* | +-----------------+
* + -> | %ebp last frame----....
* | |
* | return address |
* +-----------------+
*
*
*/
/**
* Activation record layout
* Used to parse stack contents while unwinding the stack.
*/
struct activation_record_layout
{
struct activation_record_layout * next;
void *return_address;
};
/**
* Unwind the stack at any location.
* This function works like the backtrace function provided by the GNU libc,
* with the only difference that this function doesn't unwind _current_ stack.
* Instead, it takes values of the relevant registers (EIP, EBP and ESP) as
* parameters.
* Thus, this function is suited for being called from a signal handler.
*/
int
rhyno_backtrace (
void **activation_frame_array,
int max_size,
void *eip,
void *ebp,
void *esp)
{
/* used to parse the stack */
struct activation_record_layout *current;
/* keeps track on visited stack trace */
int count = 0;
/* save the EIP into the frames list */
activation_frame_array[count++] = eip ;
/* Initialize the frame list with EBP */
current = (struct activation_record_layout *) ebp;
while (count < max_size)
{
/* If we reach past stack pointer, or end of stack segment,
* we have problem (possible memory overrun.)
*/
if ((void *) current < esp || (void *) current > __libc_stack_end)
break;
/* Save the return address of frame i into the frame list */
activation_frame_array[count++] = current->return_address;
/* Advance the frame list pointer */
current = current->next;
}
/* How many frames we had collected */
return count;
}
void sa_segv_specific_hanlder(int signum, siginfo_t *pinfo, void *arg)
{
/*
Example for SEGV...
*/
printf(" Segmentation violation: Reason: %d: %s\n",
pinfo->si_code,
pinfo->si_code == SEGV_MAPERR ?
"Address not mapped" : pinfo->si_code == SEGV_ACCERR ?
"Invalid permissions" : "?????") ;
printf(" Offending address: %p\n", pinfo->si_addr ) ;
}
void sa_bluescreen_handler(int signum, siginfo_t *pinfo, void *arg)
{
/*
siginfo_t {
int si_signo; / * Signal number * /
int si_errno; / * An errno value * /
int si_code; / * Signal code * /
pid_t si_pid; / * Sending process ID * /
uid_t si_uid; / * Real user ID of sending process * /
int si_status; / * Exit value or signal * /
clock_t si_utime; / * User time consumed * /
clock_t si_stime; / * System time consumed * /
sigval_t si_value; / * Signal value * /
int si_int; / * POSIX.1b signal * /
void * si_ptr; / * POSIX.1b signal * /
void * si_addr; / * Memory location which caused fault * /
int si_band; / * Band event * /
int si_fd; / * File descriptor * /
}
*/
printf("Got deadly signal %d, following information (%p) is available:\n",
signum,pinfo ) ;
printf(" Signal %d (code %d, errno %d) send by process %d (uid %d)\n",
pinfo->si_signo,
pinfo->si_code,
pinfo->si_errno,
pinfo->si_pid,
pinfo->si_uid ) ;
/* Call message specific handler.
For example, the segmentation fault handler will
print the offending address
*/
if (g_pbs_signal_actions[signum].pspecific != NULL)
{
(*g_pbs_signal_actions[signum].pspecific)(signum,pinfo,arg) ;
}
if ( arg == NULL )
{
printf("No register/stack information is available\n") ;
}
else
{
printf("Important CPU Registers:\n" ) ;
printf("CS: %012p DS: %012p SS: %012p \n",
((ucontext_t*)(arg))->uc_mcontext.gregs[REG_CS],
((ucontext_t*)(arg))->uc_mcontext.gregs[REG_DS],
((ucontext_t*)(arg))->uc_mcontext.gregs[REG_SS]
) ;
printf("EIP: %012p EBP: %012p ESP: %012p\n",
((ucontext_t*)(arg))->uc_mcontext.gregs[REG_EIP],
((ucontext_t*)(arg))->uc_mcontext.gregs[REG_EBP],
((ucontext_t*)(arg))->uc_mcontext.gregs[REG_ESP]
) ;
printf("\n\nStack Trace:\n") ;
/*------------------------------------------------------------------------------
* Collect activation records in format similar to that of
* glibc's `backtrace', but use the foreign stack.
*/
activation_record_count = rhyno_backtrace(
g_a_activation_record_list,
max_activation_record_count,
(void*)(((ucontext_t*)(arg))->uc_mcontext.gregs[REG_EIP]),
(void*)(((ucontext_t*)(arg))->uc_mcontext.gregs[REG_EBP]),
(void*)(((ucontext_t*)(arg))->uc_mcontext.gregs[REG_ESP])
) ;
/*------------------------------------------------------------------------------
* Use internal glibc reporting mechanism to convert
* activation records to symbolic names (requires -rdynamic linker flag)
*/
backtrace_symbols_fd(g_a_activation_record_list,activation_record_count,1) ;
} /* register information */
/* propagate the signal to the original handler */
if ( g_pbs_signal_actions[signum].original &&
(g_pbs_signal_actions[signum].original.sa_sigaction != NULL ))
{
printf("\n\nPropagating the signal %d to the original handler\n",
signum) ;
g_pbs_signal_actions[signum].original.sa_sigaction(signum,pinfo,arg) ;
}
else
{
printf("No original handler is registered for singal %d, continuing\n",
signum ) ;
}
}
int register_signal_handlers()
{
int signum ;
int result = -1 ;
for ( signum = 0 ; signum < max_pbs_size ; ++signum)
{
/* zero the signal action */
memset(&g_pbs_signal_actions[signum].current,0,sizeof (struct sigaction)) ;
/* register the generic handler */
g_pbs_signal_actions[signum].current.sa_sigaction = sa_bluescreen_handler ;
/* turn on the flag that tells the runtime to invoke the extended signal handler,
see man sigaction for details */
g_pbs_signal_actions[signum].current.sa_flags |= SA_SIGINFO ;
/* Register the signal handler, store the original signal handler for propagation */
result = sigaction(signum,&g_pbs_signal_actions[signum].current,
&g_pbs_signal_actions[signum].original) ;
/* Report result */
printf ("-- Registered signal handler for signal %d, result %d, handler %p original handler %p \n", signum,result,
g_pbs_signal_actions[signum].current.sa_sigaction,
g_pbs_signal_actions[signum].original.sa_sigaction) ;
}
g_pbs_signal_actions[SIGSEGV].pspecific = sa_segv_specific_hanlder ;
return result ;
}
/* Function prototype. Here we will do THINGS */
int malicious_code(int) ;
int
main (int argc, char* argv[], char* envp[])
{
int sleep_interval = 300 ;
/* Very important - register the handlers */
if ( argc > 1 )
register_signal_handlers() ;
sleep(sleep_interval) ;
malicious_code(10) ;
return 0 ;
}
int malicious_code(int arg)
{
if ( ! arg )
{
int * garbage_pointer = (int*)441231324;
return *((int*)(garbage_pointer)) ;
}
else
{
return malicious_code(--arg) ;
}
}

