Using the concepts we looked at in A For Loop Without a For Loop and A Simple Infinite Loop, we can call a different function rather than calling the same one in a loop. In this post, we are going to call a function within main(), by overwriting main()‘s return address and replacing it with the address to the first instruction of the function we want to call:
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char **argv)
{
argv[-0x1c] = argv[-0x18] + 0x30;
}
void my_func ()
{
printf("Greetings from %s(), line %d!\n", __FUNCTION__, __LINE__);
exit(0);
}
And here’s the output in the console when I run it:

All that my_func() does is print a greeting on screen, using the pre-defined macros __LINE__ and __FUNCTION__ (see here and here for more info). Also, and this is important for cosmetic reasons, my_func() calls exit(). The reason it does that is to avoid the program crashing.
Why would it crash? Consider that every function call is expected to create and posssess its own stack frame. Some of the information in the stack frame is prepared by the caller. In particular, the call instruction stores the return address on the stack. See the section on Calling Convention at this link. Since we aren’t preparing my_func()‘s stack frame, there is also no return address for my_func(), but instead some junk at the address it expects.
What main() is doing is a bit more interesting. Essentially, we are using argv as an anchor and placing data at an array index whose location corresponds to main()‘s return address. We can verify this by firing up GDB and having a closer look at what’s going on under the hood:
(gdb) file call
Reading symbols from call...
(gdb) break call.c:1
Breakpoint 1 at 0x1150: file ./call.c, line 6.
(gdb) run
Starting program: /home/thebel/programming/chack/misc/call
Breakpoint 1, main (argc=1, argv=0x7fffffffe228) at ./call.c:6
6 argv[-0x1c] = argv[-0x18] + 0x30;
(gdb) x/8a $rsp-0x20
0x7fffffffe120: 0x5555555551a0 <__libc_csu_init> 0x555555555060 <_start>
0x7fffffffe130: 0x7fffffffe228 0x100000000
0x7fffffffe140: 0x5555555551a0 <__libc_csu_init> 0x7ffff7e20bbb <__libc_start_main+235>
0x7fffffffe150: 0x0 0x7fffffffe228
(gdb)
The highlighted line shows us the return address for main(). It is located at 0x7fffffffe148 and has the value 0x7ffff7e20bbb (which GDB resolves to <__libc_start_main+235>). We can verify that that value changes when argv is manipulated:
(gdb) run
Starting program: /home/thebel/programming/chack/misc/call
Breakpoint 1, main (argc=1, argv=0x7fffffffe228) at ./call.c:6
6 argv[-0x1c] = argv[-0x18] + 0x30;
(gdb) p/a argv[-0x1c]
$3 = 0x7ffff7e20bbb <__libc_start_main+235>
(gdb) step
7 }
(gdb) p/a argv[-0x1c]
$4 = 0x555555555175 <my_func>
(gdb)
Consequently, when we step further through call, we “return” to my_func() rather than main()‘s caller. And there you have it, calling a function without calling it!