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?
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
.