Last active
April 6, 2016 08:44
-
-
Save DmitrySoshnikov/31ee8c247d8075352da2 to your computer and use it in GitHub Desktop.
Stack buffer overflow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Stack buffer overflow. | |
* | |
* TL;DR: In older OSes, a buffer overflow of the data on the stack | |
* allowed overriding a return address, and execute an exploit code, | |
* instead of returning to a caller. | |
* | |
* Docs: https://en.wikipedia.org/wiki/Stack_buffer_overflow | |
* | |
* by Dmitry Soshnikov <[email protected]> | |
*/ | |
var stack = []; | |
// Address 0: | |
// | |
// Main function from which we call our `foo` function, | |
// and tell where to return. | |
function main() { | |
// Nothing follows this call, so return | |
// directly from main, i.e. to OS. | |
call(foo, 'RETURN_TO_OS'); | |
} | |
// Calls a function, returning after it | |
// to the preserved return address. | |
function call(fn, returnAddress) { | |
// Put the return address. | |
stack.push(returnAddress); | |
// And then actually pass the control to a function. | |
fn(); | |
} | |
// Address 1: | |
// | |
// Our `foo` function, that allocates a buffer on the stack of | |
// length 3, and asks a user to fill the buffer (via `userInput`). | |
// | |
// After `userInput` work, we already have the stack frame actually | |
// corrupted, and the return address should be substituted to the | |
// `EXPLOIT_ADDRESS`. | |
// | |
function foo() { | |
// Once we entered the `foo`, confirm that the return address | |
// on the stack is set to `RETURN_TO_OS`. | |
// console.log('Stack', stack); // ['RETURN_TO_OS'] | |
// The `data` is a buffer of 3 bytes on the stack. | |
var data = allocate(3); | |
// Here where the buffer overflow happens, and | |
// overrides the return address. | |
userInput(data); | |
// Confirm that exploit address was copied to the | |
// return address slot on the stack. | |
// console.log('Stack', stack); // [3, 2, 1, EXPLOIT_ADDRESS] | |
// Pop the allocated data. | |
deallocate(3); | |
// By this time there should be only the return address. | |
// And we don't return to RETURN_TO_OS, but to the EXPLOIT_ADDRESS. | |
returnToCaller(); | |
} | |
// Address EXPLOIT_ADDRESS: | |
// This code was injected into memory "somehow". | |
// | |
// If we correctly overflowed the buffer, our code | |
// should be executed here. | |
// | |
function exploit() { | |
console.log('.PWNED'); | |
} | |
var instructionAddresses = { | |
RETURN_TO_OS: null, // nothing to execute | |
// Below are addresses of our functions. | |
0: main, | |
1: foo, | |
EXPLOIT_ADDRESS: exploit, | |
}; | |
// This guy just pops the return address which should be | |
// on the stack, and pass the control there. | |
// In our case it's an address of a function, or `null` if | |
// return to OS. | |
function returnToCaller() { | |
var returnAddress = stack.pop(); | |
// Execute the next instruction at that address. | |
if (typeof instructionAddresses[returnAddress] !== null) { | |
instructionAddresses[returnAddress](); | |
} | |
} | |
// Just allocates needed bytes on the stack as a "local variable". | |
function allocate(bytes) { | |
stack.length += bytes; | |
return Array(bytes); | |
} | |
// Deallocates the stuff. | |
function deallocate(bytes) { | |
stack.length -= bytes; | |
} | |
// The actual buggy code of a compiler: it doesn't check for | |
// boundaries of the input, and allows an exploit to override | |
// any data there, including the return address. | |
function userInput(data) { | |
// From `foo`, data is supposed to be of length 3. | |
data[data.length - 1] = 1; | |
data[data.length - 2] = 2; | |
data[data.length - 3] = 3; | |
// Since the compiler doesn't do any boundary checks, we can | |
// push *more* data there. Put the address of our exploit code, | |
// overriding *actual* return address. | |
data.push('EXPLOIT_ADDRESS'); | |
// *Actual* length, but not 3 | |
for (var i = 0; i < data.length; i++) { | |
stack[stack.length - i - 1] = data[i]; | |
} | |
} | |
// Run the program. | |
main(); // .PWNED |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment