0

I tried to smash the stack:

int main (void) {

    int ar[5] = {1,2,3,4,5};
    for(int i =0; i<255 ; i++)
        ar[i] = 10;

    return 0;
}

with gcc -fno-stack-protector somefile.c. First question: why is there difference with fault with (SIGABRT) and without (SIGSEGV) protector, when both are accessing ilegal memory (so should have the same fault, i think). Second when objdump:

0000000000001125 <main>:
    1125:   55                      push   %rbp
    1126:   48 89 e5                mov    %rsp,%rbp
    1129:   c7 45 e0 01 00 00 00    movl   $0x1,-0x20(%rbp)
...

The start of main address is virtual 0000000000001125

BUT when compiled without protector:

Program received signal SIGSEGV, Segmentation fault.
0x0000000af7be5b6b in ?? ()

The address (0x0000000af7be5b6b) is what? virtual, physical? I can see it nowhere in disassambled (as shown above) file, so where does the address come from?

EDIT: with protector (and fault is therefor SIGABRT), the output I do not understand as well:

Program received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50

What is __GI_raise a macro? and what is in the parenthesis sig=sig@entry=6, does gcc add a table for faults, or what are these marks from linker?

Marco Bonelli
  • 48,251
  • 16
  • 95
  • 101
Herdsman
  • 609
  • 4
  • 17
  • `SIGABRT` is sent by some library function detecting violation (and this detection is enabled by using stack-protector). `SIGSEGV` is sent by OS, when *it* detects the process is touching something it is not supposed to. – Eugene Sh. Jan 29 '20 at 20:31
  • And `SIGSEGV` is not handled by any library? Or is it kernel interupter? How do the both differ in implementation? – Herdsman Jan 29 '20 at 20:32
  • have you tried a full backtrace? because you're not in main you're in "??" – Jean-François Fabre Jan 29 '20 at 20:35
  • 1
    For normal processes and general-purpose multi-user operating systems, all addresses you see in a process and the debugger are virtual addresses. You will not see physical addresses unless you are working with kernel code, including some device drivers and similarly privileged code, or special arrangements have been made. There would not be any point in the debugger reporting a physical address since the same virtual address in your program might be mapped to a different physical address after various system activity invisible to you, so the physical address is generally unusable. – Eric Postpischil Jan 29 '20 at 20:56
  • Why does the kernel and device drivers (or modules) needs to access physical address then? Give some particular (concrete) examples, in which would kernel either in IO operations or in boot mode use physical address ports? – Herdsman Jan 29 '20 at 21:22
  • The kernel needs to use physical addresses to manage use of physical memory and to establish memory maps for processes. Device drivers need to access the buffers in memory of user processes even though they do not share the virtual memory maps of those processes. (Such buffer access is generally aided by operating system routines rather than by drivers directly, but it depends on the system.) – Eric Postpischil Jan 29 '20 at 23:38

1 Answers1

2

why is there difference with fault with (SIGABRT) and without (SIGSEGV) protector, when both are accessing ilegal memory (so should have the same fault, i think).

When you compile without stack protector, you simply end up overwriting some part of memory that you are not allowed to, and the program gets killed by the operating system through a SIGSEGV signal.

However, when you compile with a stack protector, the error can be detected by the libc and in that case the __stack_chk_fail() function is called. You can see this through objdump:

 72d:   b8 00 00 00 00          mov    eax,0x0
 732:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
 736:   64 48 33 14 25 28 00    xor    rdx,QWORD PTR fs:0x28
 73d:   00 00
 73f:   74 05                   je     746 <main+0x76>
 741:   e8 3a fe ff ff          call   580 <__stack_chk_fail@plt>
 746:   c9                      leave
 747:   c3                      ret

The __stack_chk_fail() function then calls __fortify_fail_abort(), which calls __libc_message() printing the error message and finally executing abort(), which sends a SIGABRT signal to the process through raise(), killing it.

The start of main address is virtual 0000000000001125

Wrong. That is not a virtual address, that is merely an offset inside the binary, meaning that the code you see starts at byte 0x1125 in the binary itself. When the binary is then executed, a virtual memory region is created for the program starting at some (usually randomized) base virtual address. The base virtual address will then determine the position of main() and everything else. For example main() will be at base_virtual_addr + 0x1125. You have no way of determining the real, physical address where your program is loaded in RAM, only the operating system (i.e. the kernel) knows this, and an user space program does not need to.

You can take a look at the virtual memory map of your program while it is running under GDB with the command info proc mappings, the result is something like the following:

(gdb) info proc mappings
process 11084
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x555555554000     0x555555555000     0x1000        0x0 /home/marco/Desktop/a.out
      0x555555754000     0x555555755000     0x1000        0x0 /home/marco/Desktop/a.out
      0x555555755000     0x555555756000     0x1000     0x1000 /home/marco/Desktop/a.out
      0x7ffff7a3a000     0x7ffff7bcf000   0x195000        0x0 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7bcf000     0x7ffff7dcf000   0x200000   0x195000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dcf000     0x7ffff7dd3000     0x4000   0x195000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dd3000     0x7ffff7dd5000     0x2000   0x199000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dd5000     0x7ffff7dd9000     0x4000        0x0
      0x7ffff7dd9000     0x7ffff7dfc000    0x23000        0x0 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7fcf000     0x7ffff7fd1000     0x2000        0x0
      0x7ffff7ff8000     0x7ffff7ffa000     0x2000        0x0 [vvar]
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x23000 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x24000 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

In this case, you can see that my program (/home/marco/Desktop/a.out) starts at the virtual base address 0x555555554000.

What is __GI_raise a macro? and what is in the parenthesis sig=sig@entry=6, does gcc add a table for faults, or what are these marks from linker?

__GI_raise() is an implementation of the raise() function. Remember? I just mentioned it earlier when talking about the stack protector. In your case, gdb stops after the program is killed by raise(SIGABRT), and shows the exact point at which the program died, which is inside the __GI_raise() internal function used by libc to deliver the signal.

The string sig=sig@entry=6 is just a nice way for GDB to tell you that the function was called with its only argument set to 6, which is the signal number for SIGABRT.

Marco Bonelli
  • 48,251
  • 16
  • 95
  • 101
  • How to make full backtrace in gdb as suggested in comments (in order to see all addresses, that are smacked)? – Herdsman Jan 29 '20 at 21:15
  • @Herdsman you can compile with debug symbols (`-g` flag) and then use the command `backtrace` in GDB. – Marco Bonelli Jan 29 '20 at 21:16
  • I just wanted to see, that the smashing will arise at "base + 10 (0xa)" after the buffer (that is, base of buffer + 10) - that should be the moment, when would error arise (after those ten bytes), but how can I see it in gdb and virtual memory address? I do not see the ten difference in address with `info proc mappings`, so How can i see the fault after 10 offset from base? – Herdsman Jan 29 '20 at 21:34
  • @Herdsman you'll have to step through the assembly instructions by hand one at a time to see that. See [here](https://stackoverflow.com/questions/2420813/using-gdb-to-single-step-assembly-code-outside-specified-executable-causes-error). It's not that simple, the program runs without a problem and then when it checks the stack canary it realizes that it was modified by your buffer overflow and calls `__stack_chk_fail()`. You can't get back that easily with a backtrace. – Marco Bonelli Jan 29 '20 at 21:52