2

I am trying to test this example from StackOverflow (how-can-i-invoke-buffer-overflow), but I am not having success.
I also asked for clarification two weeks ago, directly on the post (through a comment) but there was still no answer (perhaps too old, 2010).
I am asking for a parsimoniously way of make this work: compiler options, operating system configuration, if necessary changing the code to make it compliant with today's process/memory layout or able to surpass, by the least, today's security OS protections.
I tried my own guesses but nothing seems to work. I would like to avoid continuing doing superstitious attempts (compiler options that have nothing to do, operating system tinkering that has nothing to do) and opted to ask here if an expert or a well informed person come up with a proposal or at least points to a promising path.

My result:

$ gcc overflow.c
$ ./a.out  
now inside f()!

Result supposed to happen:

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

Code:

#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; 
}

My machine:

x86_64 GNU/Linux 
6.10.9-amd64
6
  • 1
    A different but easier take would be to have an array of function pointers plus a function pointer to f() next to each other in memory, e.g. as local variables in main. Fill the array with pointers to g(), writing out-of-bounds by 1 (above or below, depending on the implementation), which should overwrite the pointer to f(). Calling the function that pointer points to should then call g(). Commented Oct 16, 2024 at 14:59
  • @Peter-ReinstateMonica Before dbush and DevSolar, I started with your example: in main: int i; void (*fpointers [2]) (); void (*pf) () = f; for (i=0; i<10; i++) { pf(); fpointers[i] = (void*) g;} Worked: $ ./a.out now inside f()! now inside f()! now inside f()! now inside g()!. I don't know if this was your exact idea. Although (correct me if I am wrong please), has nothing to do with the stack/overwritting-return-addreses. Is taking advantage in an elegant way of a BO but directly in .data segment? .bss segment? Your example was very simple but ultra clarifying although avoids the stack Commented Oct 23, 2024 at 10:28
  • Well, the pointers are local variables and thus on the stack, but I admit it's probably not (fully) what the problem intended, which is to take advantage of implicit information on the stack (the return address). Commented Oct 23, 2024 at 11:02
  • Ah Ok! Sorry (big mistake), I was missing the point that main() has its own stack :) Mixing with static concepts like .data or .bss that are layouts in files (declarations for the compiler) but not in memory. Then, in memory, all starts with main() stack. Local things to main()reside there and function/routine stacks (like the ones for f(), g()), derive from main() stack calls. Right? Commented Oct 23, 2024 at 11:09
  • 1
    No worries :-). In reality, there is only one stack (that's the beauty of the concept --everything literally stacks nicely!) starting in main(), yes. We sometimes talk about "a function's stack" which is all a function can access, but in the global picture it's just a linear extension of the (only) already existing stack -- which is why the function can change data on the stack by iterating a stack-pointing pointer until it is outside the function's actual scope. Commented Oct 23, 2024 at 11:31

2 Answers 2

3

Just because you wrote past the end of buffer doesn't necessarily mean the code will crash. That's part of undefined behavior.

What most likely happened here is that you overwrote the value of i on the second iteration of the loop. When I ran this code I observed the following stack layout for f:

rsp+0  | buffer[0]
rsp+8  | (padding)
rsp+12 | i
rsp+16 | main

I also noted that the local functions had addresses in the 0x400000-0x400600 range, meaning that the upper 4 bytes of these addresses is 0.

So when you write to buffer[1], the low-order 4 bytes of the address end up where the padding is, and the high-order 4 bytes with the value 0 overwrite i. So i gets reset to 0 at the end of every iteration of the loop, resulting in buffer[1] getting repeatedly written into in an infinite loop.

If you instead do this in the loop:

if (i<2) {
    buffer[i] = (void *)((uintptr_t)g | ((uintptr_t)i << 32));
} else {
    buffer[i] = g;
}

This will set the high-order 4 bytes of buffer[i] to the current value of i for values of 0 and 1. That way the value of i will be preserved at it progresses through the loop, then the return address of main on the stack can be overwritten.

And by only doing this for only the first few iterations to protect the value of i, the address of main on the stack will get overwritten with the address of g, allowing the code to return-to g and execute the function.

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

14 Comments

I take a dim view on SO answers that encourage exploiting UB.
@DevSolar The whole point of the question is a buffer overflow exploit. Simply declaring "UB" doesn't apply in this case.
@DevSolar Not when the question isn't about writing a program that should be standard compliant. OP is aware the code has UB and is asking about how to exploit it. There's a time and place to be pedantic about the C standard. This isn't it.
@DevSolar This is a question migrated from Security. It's not about relying on UB, it's about researching vulnerability.
What about moving i to global scope?
That would solve the problem immediately. And to start understanding the thing, is a good start. Actually, was a first try. But as the comment inside the source code stands: // can only modify this section , then the challenge becomes dealing with the for-loop-"bulldozer"-overwriter that is coming and how to make i "jump" as @dbush proposed using the singularity that in his test, of the 64 bits, the HO bytes (4), where empty.
(...) In my case I decided to reset the index (i value) to certain value if the index was likely to be out of range after the overwrite (e.g. g() address unlikely a number "between 1 and 10"), to keep the for loop alive and continuing its job until reached the return address.
|
2

The program invokes undefined behavior. As such, asking "how to make it behave in way X" is a fool's errand.

Your expected behavior...

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

...is merely one thing that could happen. It can only happen if 1) the compiler doesn't check for buffer overflow, 2) the OS actually lets the buffer overflow happen without terminating the program, and 3) you are on a platform where the stack is arranged just so and grows in the right direction.

Either of those prerequisites can change, with the next compiler update, the next OS update, or by switching to a new platform.

Your observed behavior...

$ gcc overflow.c
$ ./a.out  
now inside f()!

...is in no way guaranteed to happen, and cannot be relied upon, either. Perhaps you've been lucky. Perhaps the behavior will change if you add another function. Who knows. It's UB. The soul has left your program.

As for "successfully testing", the thing you should be testing is that UB does not happen in your program. Static code analysis (compiler warnings), or dynamic code analysis (e.g. using valgrind in your build / test chain) could help with that.

As for the "homework" question you linked to, "can't call g(), maybe use g (pointer to function)", I'd go for the obvious way...

void (*x)() = g;
x();

...and tell anyone showing a solution actually using buffer overflow in no uncertain terms what I think of such coding "practices" before walking out of a course who sets something like this as "assignment".

6 Comments

All you say is so true it is actually a truism. It is clear to all but the greenest neophytes and is actually the basis for this assignment. Additionally, while the standard which describes all implementations cannot make predictions about UB, in most given real systems (architecture, compiler, options) you do get reproducible results, and people do use that, usually for illicit purposes. This was a homework specifically asking to exploit the specific behavior of a specific system. Saying that the standard does not define the behavior utterly misses the point.
@Peter-ReinstateMonica Funny. I'm not talking about "the standard" anywhere in my answer. The counter-question should be, "assignment" by whom? The OP upvoted my answer, and actually called it "clarifying in many aspects", so you can stop revenge-downvoting this as "not useful" when it obviously was.
I'm not sure why you talk about revenge. I have indeed downvoted, and left a comment why. I truly do not consider the answer helpful. That's not the end of the world, and no bad intentions from me; this is how the site works. Some of my answers have been downvoted. As to the OP: Maybe the OP is less experienced or feels it is adequate to upvote well-written answers which are not wrong (like yours).
@Peter-ReinstateMonica Because it should be somewhat obvious that it provides the counterbalance for the "greenest neophytes" who might see dbush's answer, which comes with NO warning signs whatsoever, and might think that's a nice trick to use in their programs to call functions.
That would make a good comment on dbush's answer. That's exactly what comments are for. Also, it would be more useful there: People may never read past the accepted, highest-voted answer.
|

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.