|
Stack Smasher |
Edgar Reynaldo
Major Reynaldo
May 2007
|
I've been a little bored waiting for my laptop power adapter to arrive so I can use my laptop again, and so I've been doing a little experimenting. I managed to execute a function I never called. It's the little victories that make me happy. Anyway, here's some (/dangerous/) source code. Run at your own risk. It prints an error message inside the function that was never called. Obviously, I managed to overwrite the return address of the stack frame with the address of my function, so when it returns, arbitrary code is executed. This is really nothing special, seeing as how it's my own code and I can make it do anything I want. I won't go into attack vectors, as that's not the point. Initial attempts to 'fix' what I broke... 1
2
3
4
5#warning RUN @ YOUR OWN RISK NEWBIE
6
7#include <stdio.h>
8#include <stdlib.h>
9
10
11
12typedef void (*VOIDFUNC)();
13
14typedef VOIDFUNC* VOIDFUNCPTR;
15
16static VOIDFUNC old_data = (VOIDFUNC)0;
17static VOIDFUNCPTR return_address = (VOIDFUNCPTR)0;
18static VOIDFUNCPTR stack_address = (VOIDFUNCPTR)0;
19
20
21
22void put_it_back() {
23 if (return_address) {
24 /// Put back the previous value
25 fprintf(stderr , "Returning data {%p} to %p\n" , old_data , return_address);
26 *return_address = old_data;
27 }
28}
29
30
31
32void i_never_called_this_code() {
33 fprintf(stderr , "\n\nArbitrary code execution... oops!\n\n");
34 /// Attempt to repair the damage, so it goes back to the 'CORRECT' frame
35 put_it_back();
36 stack_address = return_address;
37 fprintf(stderr , "***Stack Address is %p ***\n" , stack_address);
38
39 /// We better abort before shit gets crazy
40// abort();/// haha just kidding
41 /// No really
42// abort();/// you really want to know what happens, don't you?
43 /// Ahem
44 abort();
45}
46
47
48
49void break_the_stack(char* stackptr) {
50
51 /// Store the old data (ie, return address we're SUPPOSED to be going to
52 old_data = *(VOIDFUNCPTR)(stackptr);
53 return_address = (VOIDFUNCPTR)stackptr;
54
55 fprintf(stderr , "Reading data {%p} at %p\n" , old_data , return_address);
56
57 /// Smash the stackptr,
58 /// When we find the return address stored on the stack, we replace it with our function address
59 /// that's where it will go next
60 /// Basicallly, we're calling our code without calling it
61 /// Once that's done, 411 UR B4535 B310NG 2 US
62 *(VOIDFUNCPTR)(stackptr) = i_never_called_this_code;
63}
64
65
66
67int main(int argc , char** argv) {
68
69 char* stack = (char*)&stack;
70
71 (void)argc;
72 (void)argv;
73
74 while (1) {
75 stack -= sizeof(unsigned int);
76 fprintf(stdout , "0x%lx\n" , (unsigned long int)stack);
77 break_the_stack(stack);
78 put_it_back();
79 if (stack_address) {/// == (VOIDFUNCPTR)stack) {
80 break;
81 }
82 else {
83 /// Cleanliness is next to godliness
84/// *return_address = old_data;
85 }
86 }
87
88 fprintf(stderr , "What just happened?\n");
89
90 return 0;
91}
So you can see, it gets the address of an object on the stack, and then decrements the address, overwriting memory until the return address is overwritten with the address of the function I want to call. NOTE : This will destroy your stack, and may? destroy your computer. Maybe you should run it in a VM, through something safe like GDB. I can only test on Windows XP at the moment. This is with MinGW GCC 8.1. So far I've managed to get it to run arbitrary code, but I can't seem to put humpty dumpty back together right yet. {"name":"611990","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/6\/7\/6793e6aa09dd2ff2b5e982912205ada2.png","w":1356,"h":1057,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/6\/7\/6793e6aa09dd2ff2b5e982912205ada2"} For now, it halts with an abort(); so it doesn't turn into Skynet. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Peter Hull
Member #1,136
March 2001
|
Looks cool! Does anyone remember Progranisms? Now that was dangerous!
|
Chris Katko
Member #1,881
January 2002
|
Peter Hull said: Progranisms Oh sweet youth -----sig: |
Edgar Reynaldo
Major Reynaldo
May 2007
|
The first screenshot reminds me of those old cartoons (edit - Family Circus) where they run around the yard and it leaves a trail : Those addresses are totally random. It's just playing hopscotch through memory. Can anyone give me a guide to the addresses on the stack? if you look at the second screenshot you can see some addresses near 0 x 0 0 2 3 f f 0 0 through 0 x 0 0 2 4 0 0 0 0. That's 256 Bytes of memory. That's the address where the stack variables live. The addresses near 0x0401.... are the addresses of functions like main and break the stack. Static variables are near there too. In between the stack variables are the frame pointers. I can't quite 'fix' the damage I caused though... when I put back the address of where it's supposed to return to it is actually trying to 'run' the stack memory. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
relpatseht
Member #5,034
September 2004
|
I'm posting now to help me remember tomorrow. I used to be heavily invested in this sort of thing. I think I may have posted my detouring library on here at some point (takes function address, decompiles function header, relocates/recompiles it somewhere else, inserts jump to your function where header was; so all function calls get redirected then return to the original function). Anyway, I want to look at what you did when I'm awake. Too exhausted to look now. [[EDIT]] So, what it comes down to, is the call and ret instructions in ASM. The call instruction is functionally equivalent to // RIP is the 64 bit pointer to the currently executing instruction. // Y is the size of a push instruction plus the size of the jmp function. // I say Y because in x86/64 land, these instructions come out to // different machine code width different numbers of bytes depending on // the arguments. For instance, a jmp instruction can be anywhere between 2 // and 14 bytes long push rip+Y // Push the return address (directly after the jmp) onto the stack jmp SomeFunction // Move the instruction pointer to the function we want to call Meanwhile, the ret instruction is functionally equivalent to // Pull the top value off the stack and put it in the instruction // pointer, effectively jumping there and removing it from the // stack simultaneously pop rip
So, obviously, as soon as you're in your function, the top stack entry is the return address, right? Wrong. sub RSP, sizeof(X) // Move the head of the stack to make room (stack grows down for some reason lost to time) mov [RSP], X // Store X at the current address of RSP, the stack head
And calling pop is the reverse. push RBP // Store the old base of the stack on the stack mov RBP, RSP // Set the new base as the current top of the stack sub RSP, X // Allocate X off the stack, where X is the sizeof all local variables This means (because, again, the stack grows down for some reason) inside a function, you can use [RBP - X] to access local variables and [RBP + X + 8] to access the parent scope's variables (+8 to skip the return address). Writing into [RBP + X] would be bad, since that means you're modifying outside your scope. Of course, there's no safety here, so nothing is stopping you. What it comes down to, is a stack frame for the following would be something like... 1void foo()
2{
3 int x;
4 // stack frame for right here below
5}
6
7void bar()
8{
9 int a, b;
10 // ...
11 foo(a + b)
12foo_done:
13}
14
15int main()
16{
17 bar();
18bar_done:
19}
// Note: This is assuming x64 with a __fastcall calling convention, // meaning the first two parameters are passed in registers, rather // than on the stack // Address Value 0x100 0x100 // Absolute base of the stack 0x108 bar_done // Return address after bar is called. The next instruction 0x110 0x100 // The base of the stack for main 0x118 a // bar::a 0x120 b // bar::b 0x128 foo_done // The return address after foo is called. 0x130 0x110 // The stack base pointer for bar 0x138 x // foo::x That's all generally true anyway. In reality, optimizing compilers will sometimes give you non-standard stack frames, particularly on leaf functions (functions that don't call anything). Furthermore, a nice debugging compiler will probably insert byte patterns in the callstack in and around function calls which they can check for on return to make sure you didn't smash the stack. If you want to know more, such as how to properly hot patch/detour a function, how to inject code into a foreign process, how to evade process intrusion detection facilities ... ... all for academic purposes only, let me know.
|
Neil Roy
Member #2,229
April 2002
|
If you want to see dangerous, I just posted another thread which has a video which shows that there is a RISC processor hidden on your X86 CPU that can bypass all security. The guy logged onto LINUX and gave himself root access when he was just a normal user using it. He gives out the source code and programs he wrote so you may be interested in seeing what you can do with it. Mind blowing stuff anyhow. See the thread called "GOD MODE ENABLED..." etc. --- |
|