Intro
It’s been a while since I last read the notorious Phrack Issue 49 Article By Aleph1: Smashing The Stack For Fun And Profit. In my daily work as a JS/C# dev I almost never think about these things. But recently, I was dabbling in some C code relating to my toy OS. I implemented the strcpy function which copies one char array to another. Simple enough, right?
Well… No.
Buffer Overruns
Example Scenario
Consider the following code using strcpy
:
#include <string.h>
void not_good(char *str)
{
char buff[8];
strcpy(buff, str);
}
⚠️ not_good
just copies whatever is in str
to buff
, without checking if buff
has enough space allocated to hold the value!
The Stack
Let’s consider what the stack looks like given different values for str
. We assume a stack that growns down btw.
Input: Hello
Input: AAAAAAAAAAAAAAAA\x08\x35\xC0\x80
Exploitation
In the second scenario, a potential attacker was able to successfully overwrite the return value to \x08\x35\xC0\x80
which is little endian for 0x80C03506
. They now have taken over the flow of control of our application. If the values at address0x80C03506
can be controlled by the attacker, e.g., if it points to the address of str
, bad things can happen: the attacker can execute any code they wish with the privileg of our application 😱.
Stack Smashing Protector
Fortunately, modern compilers have ways to try and prevent this kind of attack, like GCC’s stack smashing protector option. All you need to do is add the CFLAG -fstack-protector
. It adds a secret value to the stack and checks if it was changed at runtime. If it was, a callback function is executed. This turns our previous not_good
function into:
extern uintptr_t __stack_chk_guard;
noreturn void __stack_chk_fail(void);
void good(const char* str)
{
uintptr_t secret_value = __stack_chk_guard;
char buffer[12];
strcpy(buffer, str);
if ( (secret_value = secret_value ^ __stack_chk_guard) != 0 )
__stack_chk_fail();
}
GCC added __stack_chk_guard
, the secret value and __stack_chk_fail()
, the callback in case the secret value was changed.
Note 1: This code would most likely be “optimized away” if you added it manually.
Note 2: Note that the secret value is removed from the stack as part of the check.
In LonelyOS
GCC implements __stack_chk_fail
and __stack_chk_guard
in libssp/ssp.c. Of course the libc in LonelyOS doesn’t have this. And copy/pasting seemed kind of lame, so I went with the minimal implementation given on OSDev:
#include <stdlib.h>
#include <stdio.h>
#if UINT32_MAX == UINTPTR_MAX
#define STACK_CHK_GUARD 0x10101010
#else
#define STACK_CHK_GUARD 0x1010101010101010
#endif
unsigned long __stack_chk_guard = STACK_CHK_GUARD;
__attribute__((noreturn))
void __stack_chk_fail(void)
{
#if defined(__is_libk)
// TODOC: Add proper kernel panic.
printf("kernel: panic: __stack_chk_fail()\n");
#else
// TODOC: Abnormally terminate the process?? Hit hacker with a stick?
printf("__stack_chk_fail()\n");
#endif
}
Outlook
The “secret value” in LonelyOS is currently just a bunch of A’s. It would be great to randomize this value on each boot. Also, LonelyOS does not yet have a proper kernel panic implementation, so currently we just printf()
. Hopefully I’ll find some time to fix this in the future.
But next up will probably be interrupt handling – I really want the ability to type things 🤷♀️
so long
comments powered by Disqus