37

I got a homework assignment asking me to invoke a function without explicitly calling it, using buffer overflow. The code is basically this:

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

void g()
{
    printf("now inside g()!\n");
}


void f()
{   
    printf("now inside f()!\n");
    // can only modify this section
    // cant call g(), maybe use g (pointer to function)
}

int main (int argc, char *argv[])
{
    f();
    return 0;
}

Though I'm not sure how to proceed. I thought about changing the return address for the program counter so that it'll proceed directly to the address of g(), but I'm not sure how to access it. Anyway, tips will be great.

8
  • 13
    4 upvotes for a homework question! The OP didn't even come up with the question... wow, some people are easily impressed. Commented Feb 25, 2010 at 12:41
  • @Lazarus, I upvoted your comment. Uh oh! :-) Commented Feb 25, 2010 at 12:42
  • 25
    @Lazarus the fact that it is a homework question has nothing to do with the fact that I find it interesting. I also upvoted it because I want to encourage interesting homework questions rather than the simple "I closed the file buffer and now when I try reading from the file it doesn't work. Why?" (In other words, I upvote the questions I don't know the answer to, but want to) Commented Feb 25, 2010 at 12:54
  • @Alok, LOL - They were all my own words... does that help salve your conscience? ;) Commented Feb 25, 2010 at 13:11
  • 4
    Whoa, that's a hw question? I'm already loving your teacher :D Commented Feb 25, 2010 at 15:10

5 Answers 5

14

The basic idea is to alter the function's return address so that when the function returns is continues to execute at a new hacked address. As done by Nils in one of the answers, you can declare a piece of memory (usually array) and overflow it in such a way that the return address is overwritten as well.

I would suggest you to not blindly take any of the programs given here without actually understanding how they work. This article is very well written and you'll find it very useful:

A step-by-step on the buffer overflow vulnerablity

Sign up to request clarification or add additional context in comments.

Comments

13

That is compiler dependent, so no single answer can be given.

The following code will do what you want for gcc 4.4.1. Compile with optimizations disabled (important!)

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

void g()
{
    printf("now inside g()!\n");
}


void f()
{   
  int i;
  void * buffer[1];
  printf("now inside f()!\n");

  // can only modify this section
  // cant call g(), maybe use g (pointer to function)

  // place the address of g all over the stack:
  for (i=0; i<10; i++)
     buffer[i] = (void*) g;

  // and goodbye..
}

int main (int argc, char *argv[])
{
    f();
    return 0;
}

Output:

nils@doofnase:~$ gcc overflow.c
nils@doofnase:~$ ./a.out
now inside f()!
now inside g()!
now inside g()!
now inside g()!
now inside g()!
now inside g()!
now inside g()!
Segmentation fault

7 Comments

I'm using gcc 4.4.1, and not sure how to turn optimization off: tried gcc -O0 -o buff buff.c (that's oh-zero) and also gcc -O1 -fno-defer-pop -fno-thread-jumps -fno-branch-probabilities -fno-cprop-registers -fno-guess-branch-probability -fno-omit-frame-pointer -o buff buff.c neither worked.
Make the application exit inside the 'g()' function to avoid Segmentation fault =)
sa125, maybe gcc tries to optimize to a different cpu architecture. As far as I know it defaults to the cpu of the system you're running. That can change how the stackframe of f() looks like and may prevent the overflow from happening.
Nils - maybe I'm a little slow this morning -- but how is it that you're getting "now inside g()" to be printed -- I see where you're storing the pointers to g(), but I don't see in your example code where you're de-referencing the pointer(s) & invoking g()
Dan, f() gets called from main, During the call the compiler will put the return address onto the stack, so f() knows where it has to jump to when it's done. However, inside f() I overwrite a large portion of the stack with the address of g(). Chances are that I override the return address as well. So when f() exits, it will not return to main but jump to g() instead. It's really dirty, but that's what the question was about.
|
8

Since this is homework, I would like to echo codeaddict's suggestion of understanding how a buffer overflow actually works.

I learned the technique by reading the excellent (if a bit dated) article/tutorial on exploiting buffer overflow vulnerabilities Smashing The Stack For Fun And Profit.

Comments

5

While this solution doesn't use an overflow technique to overwrite the function's return address on the stack, it still causes g() to get called from f() on its way back to main() by only modifying f() and not calling g() directly.

Function epilogue-like inline assembly is added to f() to modify the value of the return address on the stack so that f() will return through g().

#include <stdio.h>

void g()
{
    printf("now inside g()!\n");
}

void f()
{   
    printf("now inside f()!\n");
    // can only modify this section
    // cant call g(), maybe use g (pointer to function)

    /* x86 function epilogue-like inline assembly */
    /* Causes f() to return to g() on its way back to main() */
    asm(
        "mov %%ebp,%%esp;"
        "pop %%ebp;"
        "push %0;"
        "ret"
        : /* no output registers */
        : "r" (&g)
        : "%ebp", "%esp"
       );
}

int main (int argc, char *argv[])
{
    f();
    return 0;
}

Understanding how this code works can lead to a better understanding of how a function's stack frame is setup for a particular architecture which forms the basis of buffer overflow techniques.

3 Comments

Studying your post carefully and having made already the OP overflow in the standard manner, I thought your answer as very interesting but I couldn't make it to work. Instead of asking you questions using comments, I posted a full question about it forcing-a-c-program-to-take-a-forged-epilogue-made-with-inline-assembly-to-jump
This only works with an unoptimized build; you're assuming -fno-omit-frame-pointer so EBP is a frame pointer that points to a saved-EBP directly below the return address. This won't be the case with -O1 or higher (even in 32-bit mode on GCC from 2010, unless that change happened a bit later.) Also, jmp *%0 would be simpler and more efficient than push %0 / ret. Either way it's just a tailcall to the function pointer. Execution doesn't come out the bottom of the asm statement, so this is a hack that GCC doesn't officially support; happens-to-work at best.
And BTW, gcc.gnu.org/onlinedocs/gcc/… - Another restriction is that the clobber list should not contain the stack pointer register. This is because the compiler requires the value of the stack pointer to be the same after an asm statement as it was on entry to the statement. However, previous versions of GCC did not enforce this rule and allowed the stack pointer to appear in the list, with unclear semantics. This behavior is deprecated and listing the stack pointer may become an error in future versions of GCC.
4

Try this one:

void f()
{   
    void *x[1];
    printf("now inside f()!\n");
    // can only modify this section
    // cant call g(), maybe use g (pointer to function)
    x[-1]=&g;
}

or this one:

void f()
{   
    void *x[1];
    printf("now inside f()!\n");
    // can only modify this section
    // cant call g(), maybe use g (pointer to function)
    x[1]=&g;
}

1 Comment

x is a local variable, so it located on the stack. Since x is an array of size 1, only x[0] is valid. By writing the address of g in x[-1] or x[1], there is a chance that we will overwrite the return address. It depends of the organisation of the stack which version works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.