0

I'm trying to modify the return address on the stack within a segment fault handler, so that it skips over the faulting instruction. However, I get a segment fault whenever I try to modify the return address, if I do not call the signal handler directly.

gdb is not great for debugging when the program does segfault, but when I do info frame I see that, after a segfault, there is "frame level 2" as opposed to "frame level 0"? I don't know where GDB gets that information, because when I try x/12xw $ebp or however many words, I don't see the return address to main()...

Compiled with -m32 -z execstack -fno-stack-protector on CentOS linux

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void segment_fault_handler(int signum)
{
    char* ret = (char*)(&signum)-4;
    *(ret) += 8;
}

int main()
{
    int phail = 0;

    signal(SIGSEGV, segment_fault_handler);

    segment_fault_handler(7); //Only by using this can I skip the next instruction

    phail = *( (int *) 0);

    printf("Win!\n");

    return 0;
} 

The reason that I increase by 8 is because the phail instruction in main() is 8 bytes:

0x080484e2 <+37>:   movl   $0x7,(%esp)
0x080484e9 <+44>:   call   0x8048480 <segment_fault_handler>
0x080484ee <+49>:   mov    $0x0,%eax
0x080484f3 <+54>:   mov    (%eax),%eax
0x080484f5 <+56>:   mov    %eax,0x1c(%esp)
0x080484f9 <+60>:   movl   $0x80485b4,(%esp)
0x08048500 <+67>:   call   0x8048350 <puts@plt>

Do I need to increase the offset a bit? Does my method of accessing the stack (which I think corresponds to EBP+4) need to change when dealing with a segment fault situation?

Dorj
  • 39
  • 1
  • 7
  • 1
    You need to modify the saved PC in the `ucontext_t` structure passed to the handler, *not* the immediate return address of the signal-handling function. (And *don't* call it directly.) The example program at https://stackoverflow.com/questions/5119288/getting-fault-address-that-generated-a-unix-signal/5120984 shows how to access the `ucontext_t` structure. – zwol Jan 27 '17 at 23:21

1 Answers1

0

Register the signal handler with sigaction, not signal, and use the SA_SIGINFO flag to get a siginfo_t describing the cause of the signal. This will allow you to handle SIGSEGV caused by a fault differently from one explicitly raised or sent by kill, sigqueue, etc. This also gives you the ucontext_t necessary to examine the state at the time of fault and optionally change it before returning.

BTW, in general signal is a bad idea to use anyway because it's unspecified whether the SA_RESTART flag is set - and you almost always want SA_RESTART. Get in a habit of using sigaction and treating signal as deprecated.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711