In the cybersecurity domain, a buffer overflow is a mechanism where a software writes data to a buffer and subsequently overruns the buffer’s limitations, causing an overwrite to other adjacent memory slots.
Exploiting the behaviour of the buffer overflow is a common exploit used by attackers. Many of which will scan your public facing enterprise architecture for this vulnerabilities. The risk becomes greater when we witness even secure software become susceptible to this exploit, which is why it’s imperative your business looks towards regular penetration testing to help mitigate this risk .
How Program Memory Works
The RAM of a computer can be visualised as a large block. With addresses stacked on top of each other, ranging from 0x000… (bottom) to 0xFFF…. (top), how many bits the addresses take depends on the size of the memory.
Certain parts of the memory are reserved for certain things:
Kernel – command line parameters, etc…
Text – the code of the program goes to this area in memory
Data – variables are stored here
Heap – large area of memory used for large processing
Stack – holds local variables
The heap grows from smaller addresses to larger ones, the stack grows in the opposite direction.
When a Function is Called
When a program wants to perform a task:
It first adds the parameters it is passing to the function onto the stack.
The assembler code then goes to another area in memory and executes the function using the parameters.
Write a program that allocated some memory on the stack and copies a string into it from the command line.
The function will then add the return address. This is the address the main will return to once it has finished executing.
Run your program in GDB (Debugger) and run it with an argument that exceeds the buffer size that is specified in the program.
Write in an argument that not only writes over the buffer size but also writes into the return address on the function.
The return address is now the part of the input that has rewritten the return address which of course will be another memory address which your program will now jump to.
If you create a separate program that executes the attack it might get stores somewhere else in memory and you’ll have to find the address and type that in as an argument which is time consuming.
What is easier is to input the attack as an argument followed by the return address which jumps back and executes the attack in the buffer itself.
One of the problems while attempting this attack is that sometimes things move around in memory at different times.
This can be countered by altering your argument to begin with \x90 which is a no-op. This simply means move to the next one. This means even if you return to a place that is not exactly where the attack code executes from you will land in an area of no-ops. This means you will be pushed along towards the malicious code step by step.
In your input then you need to have a few no-ops, followed by your exploit, followed by the return address.
If you view the registers using GDB you can see the contents at a location relative to the stack pointer. You can pick a location that is in the middle of the no-op address locations as the return address.
This exploit can enable an attacker to gain root access to the victim’s system and view password files, delete, edit or copy files and so on.
Many applications try to address this vulnerability through hard coded mechanisms to detect and prevent buffer overflow errors. However, cyber attacks continue to become more complex which is why regular penetration testing has become a standard for any successful cyber security program.
For more information, please feel free to contact us. to speak with a consultant today.