I'm learning the structure of stack frames. And trying to implement a function that can call another function without an explicit call in C by modifying the returning address (in its stack frame) of the function call.
The code is like the following:
#include <stdio.h>
#include <stdlib.h>
void malfunc() {
puts("hello world");
exit(0);
}
void set_arr() {
size_t a[2];
a[0] = 114;
a[1] = 514;
a[3] = (size_t)malfunc;
// a[3] points to the position of returning address in stack frame
}
int main() {
set_arr();
return 0;
}
In my expectation, the string hello world should be printed because the returning address of set_arr is modified to malfunc() by the assignment a[3] = (size_t)malfunc.
The stack frame for set_arr() should look like:
a[0]
------------------------------------
a[1]
------------------------------------
previous base pointer (rbp of main) <--- current rbp, a[2]
------------------------------------
original return address (main) <--- a[3], modified to malfunc
This code worked perfectly in Compiler Explorer, the link is here.
However, if I compile this code locally with the following compile options
gcc stk_ov.c -o stk_ov -fno-stack-protector -ggdb3
and run the code, a segmentation fault will be thrown.
And if I use gdb to catch the segmentation fault, I get the following output:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e2d540 in _int_malloc (av=av@entry=0x7ffff7fa2c80 <main_arena>, bytes=bytes@entry=640) at ./malloc/malloc.c:4375
4375 ./malloc/malloc.c: No such file or directory.
(gdb) bt
#0 0x00007ffff7e2d540 in _int_malloc (av=av@entry=0x7ffff7fa2c80 <main_arena>,
bytes=bytes@entry=640) at ./malloc/malloc.c:4375
#1 0x00007ffff7e2da49 in tcache_init () at ./malloc/malloc.c:3245
#2 0x00007ffff7e2e25e in tcache_init () at ./malloc/malloc.c:3241
#3 __GI___libc_malloc (bytes=bytes@entry=1024) at ./malloc/malloc.c:3306
#4 0x00007ffff7e07c24 in __GI__IO_file_doallocate (
fp=0x7ffff7fa3780 <_IO_2_1_stdout_>) at ./libio/filedoalloc.c:101
#5 0x00007ffff7e16d60 in __GI__IO_doallocbuf (
fp=fp@entry=0x7ffff7fa3780 <_IO_2_1_stdout_>) at ./libio/libioP.h:947
#6 0x00007ffff7e15fe0 in _IO_new_file_overflow (
f=0x7ffff7fa3780 <_IO_2_1_stdout_>, ch=-1) at ./libio/fileops.c:744
#7 0x00007ffff7e14755 in _IO_new_file_xsputn (n=11, data=<optimized out>,
f=<optimized out>) at ./libio/libioP.h:947
#8 _IO_new_file_xsputn (f=0x7ffff7fa3780 <_IO_2_1_stdout_>, data=<optimized out>,
n=11) at ./libio/fileops.c:1196
#9 0x00007ffff7e09f9c in __GI__IO_puts (str=0x555555556004 "hello world")
at ./libio/libioP.h:947
#10 0x0000555555555180 in malfunc () at stk_ov.c:6
#11 0x0000000000000001 in ?? ()
#12 0x00007ffff7db2d90 in __libc_start_call_main (
main=main@entry=0x5555555551b0 <main>, argc=1, argc@entry=-11536,
argv=argv@entry=0x7fffffffd408) at ../sysdeps/nptl/libc_start_call_main.h:58
#13 0x00007ffff7db2e40 in __libc_start_main_impl (main=0x5555555551b0 <main>,
argc=-11536, argv=0x7fffffffd408, init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7fffffffd3f8) at ../csu/libc-start.c:392
#14 0x00005555555550a5 in _start ()
However, if I pop the rbp register at the begging of malfunc, everything worked fine:
void malfunc() {
asm volatile("pop rbp");
puts("hello world");
exit(0);
}
malfunc:
push rbp
mov rbp, rsp
pop rbp ; newly added
mov edi, OFFSET FLAT:.LC0
call puts
mov edi, 0
call exit
So I'm confused by the difference between these two and what caused this segmentation fault.
For the original version, after entered malfunc, the rbp register will be set to the stack pointer (mov rbp, rsp). But after I poped it, it stayed the same.
My environment is listed on the following, hope they will be useful:
- gcc version:
gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0 - OS: Ubuntu-22.04 running on WSL2