In this lesson
we're going to discuss Buffer Overflows.
A Buffer Overflow occurs when a process in a program
stores data outside the memory range
that's allocated by the developer.
This memory range is known as a Buffer,
and a Buffer is simply a temporary storage area
that a program uses to store its data.
To better help understand this concept,
let's use an illustration.
Let's pretend that you have a glass of water
sitting on a table that can hold a certain amount of water.
For example, let's say we have a cup that's designed
to hold 16 ounces of liquid,
but you try to put in 20 ounces, what's going to happen?
Well, our cup is going to overflow with water,
and our table is going to get soaking wet.
Now, in this illustration the glass is our buffer
or our allocated range of memory
that we can use to hold data.
And if we overflow it with data,
in this illustration our water,
that means that extra data is going to spill out
onto the table
and make a huge mess.
Similarly, in the world of cybersecurity,
buffer overflows can also create big messes for us.
In fact, 85% of the data breaches
have been tracked back to unknown buffer overflow attack
being used as the initial attack vector.
So let's take a closer look
at how a buffer overflow attack really works.
Let's pretend you wanted to store my phone number
into your contacts list.
Here in the United States
our phone numbers consist of 10 digits.
The first three digits are area codes,
and this represents the city we live in.
The last seven digits
represents the person's unique phone number.
Now, before we had cell phones,
you'd actually just pick up the phone
and dial the seven digits of the phone number
because the telephone company assumed
that you wanted to make a local call,
and so they would add your area code by default.
Now, let's pretend the person
who designed the contact list application on your phone
decided they wanted to save some memory space,
and so they decide to use the smallest buffer possible.
So they decide to use an eight digit buffer
because we're going to assume
that they don't need to store the area code
because you're always going to be making local calls.
So you go ahead and try to store a phone number
like 555-1234 into the eight digit buffer.
Now when you do this,
you're going to see it takes up the first seven boxes
labeled zero through six
because computers always start counting with zero.
Now, because we could store eight digits here
and the number was only seven digits,
everything is going to work just fine.
But what would happen if we try
to enter a number that's too long?
Well, buffer A isn't the only memory buffer sitting there
in your contact list application.
Right after A comes buffer B,
and then buffer C and buffer D and so on.
Now, if you go off on vacation
and you meet a new friend,
you may want to save their phone number
into your contact application.
So you try to enter their phone number
like 787-555-1234.
Now this includes the area code for San Juan, Puerto Rico,
and now we have 10 digits we need to store,
but each buffer was only set to eight digits long
because our programmer didn't quite think through
all the different types of phone numbers
that a person might want to store in their contact list.
Now, our contact list application
is going to try to store this 10 digit number
into the eight digit buffer,
but those last two digits are going to overflow
from buffer A
and go into buffer B.
This is exactly what happens
when a buffer overflow condition occurs.
Now why is this a bad thing?
Well, to explain that,
we're going to have to get a little bit technical.
Each program reserves a chunk of memory
when it first starts up.
This allows it to have a place to store data
that it's going to have to use during processing.
This area of memory is known as the stack.
A stack is any reserved area of memory
where the program saves the return address
when a function call instruction is going to be received.
Now, for example, here's the stack that
we're going to look at.
This is a First-In-Last-Out or FILO type of stack.
Basically this means that the first thing
that's placed in the stack
is going to be the last thing to be removed,
and data is filled from the bottom of the memory
which is shown at the top of our screen
to the top of the memory,
which is shown at the bottom of our screen.
Now, I know this looks a little upside down,
but this is actually how we use it in a standard convention
inside the computer programming world.
So I'm going to stick with it here.
Now, if an attacker places too much information
into the stack
or they change the value of that return pointer,
they can then carry out an attack.
This is exactly what a threat actor is trying to do
When they perform a buffer overflow.
they're attempting to overwrite the return address
of that pointer,
so it'll point to a different place in the stack
where they may have already placed
some kind of malicious code.
That way when the non-malicious code is run
like our contact list application,
it may hit the return pointer by accident
and then return the attacker's malicious code
like the /bin/shell code that you see here.
This gives them a shell
or a command prompt on the victim system
where they can then perform a remote code execution
using that newly spawned shell.
If the attacker's able to do this,
it's known as smashing the stack.
Now, smashing the stack frequently occurs
when an attacker fills up the buffer
with a bunch of NOP instructions or NOPs.
Now, an NOP is a non-operating instruction,
and so that return address,
they may find any of these NOPs
and then continue on until they find the attacker's code
and then run it.
When this series of NOPs is hit by a non-malicious program
because the attackers filled it up with this buffer of NOPs
and a return address at the end of it,
this is known as a NOP Slide.
The NOP instruction essentially is an instruction
that tells the system to do nothing
and simply skip ahead to the next instruction.
So when the code finds a NOP,
it's simply going to move to the next instruction.
If that's a NOP as well,
it's going to continue to move forward
until it finds something that is not a NOP.
This way it slides down all the way
until it hits that final return pointer,
which causes the program to branch over
to that memory address which contains the attacker's code.
There are four main ways
to protect against a buffer overflow,
and you have to understand these.
First, you can patch your applications.
Buffer overflow vulnerabilities
exist because of poorly written application code.
When these vulnerabilities are discovered
and exploited by attackers,
the vendors of the application
will create a patch and release it
to be able to patch up those security vulnerabilities
and prevent buffer overflows from occurring.
This is one of the reasons that maintaining
a good patch management program in your organization
is so critical
because it can help identify applications
that need to be patched
to eliminate these buffer overflow conditions.
Second, if your organization
is the developer of the program,
you should always use secure coding practices.
By using secure coding practices,
this means you're going to use
software development practices
that include carefully checking your code
for vulnerabilities prior to releasing it.
One of the best secure coding practices
that can help prevent a buffer overflow
is to conduct boundary checking
and input validation on any data
being entered into your program by your end user.
For example, if I was making that contact list application,
I would make sure I'm conducting input validation
and rejecting any phone number
that's larger than eight digits
because this would prevent a buffer
overflow condition from occurring.
Third, we can use address space layout randomization
to prevent buffer overflow attacks.
Now, Address Space Layout Randomization or ASLR
is a programming technique that helps prevent
an attacker's ability to guess where the return pointer
for a non-malicious program has been set to call back to
by randomizing the memory addresses
being used by well-known programs
such as parts of the operating system.
Now, ASLR was first introduced
all the way back with Windows Vista
as a way to prevent buffer overflow attacks,
so it's commonly used in all modern operating systems.
Fourth, we can use data execution protection
to prevent buffer overflow attacks as well.
Now, Data Execution Protection or DEP
is a form of malware protection
that's designed to block applications
that attempt to run from protected memory locations.
DEP is used by the operating system
to identify areas of memory
that can contain executable code,
as well as other areas
that are only allowed to store user data
and non-exec executable code.
If executable code like malware
is being stored in the user data locations,
it's going to be marked as non-executable
and unable to be run or executed.
In addition to a standard buffer overflow though,
there's also other specialized types
like an Integer Overflow.
Now, an Integer Overflow is an attack
in which the computed result from a mathematical operation
is simply too large to fit
into its assigned variable type for storage.
When an integer overflow occurs,
it can lead to crashing or data corruption,
or it can be used to trigger a buffer overflow.
Integer overflows and buffer overflows
can also lead to arbitrary code execution
and in turn privilege escalations.
An integer is essentially any whole number
like one, two, three, or four.
And if I counted negatively,
I could then use negative numbers like negative one,
negative two, negative three, or negative four,
and those are all integers to.
But if I use a number like 1.5 or one and a half
or three quarters,
those are fractional and those are decimals.
So they're called floating points, not integers.
Integers are a very special data type
in computer programming,
and they only accept whole numbers,
either positive or negative
and integer are limited to a certain size.
This certain size is known as the boundary,
and we have upper and lower bounds for each variable space
that's going to contain an integer value.
Let's pretend for a moment
that I have a two digit storage space
for my integers in a program,
it's called Score.
Now, I can store numbers either positive or negative,
but I can only store up to two digits in this variable
known as score.
So how big of numbers can I use
and store inside of the score variable?
Well, the numbers can be anything from zero to 99
because I only have two digits available
in the program's bounds.
So let's say I was writing a program
and I'm going to add up two numbers,
and then I'm going to store the result
inside the variable score.
If I get two numbers, like 90 and 17
and I try to add them together,
could I do that?
Well, no, because 90 plus 17 is 107,
which is three digits,
and those three digits cannot be stored
inside my variable score
because it only holds two digits.
If my program tries to store 107 in the score variable,
it's either going to give me 10 or zero seven,
depending on if our system enters data from left to right
or right to left when it fills up the memory buffers
for a variable.
Either way though,
the system is going to drop one of those digits.
Either the front digit of one
or the back digit of seven
because of the bounding of this variable.
This is a problem for us,
and if we had another variable in a memory location
to the right of our score variable,
that extra digit could overflow into that space too.
Now, this is a really silly example
because we're never going to use a system
with only two digits,
but computers do limit the size of integers
that they can utilize,
and this can cause integer overflows to occur.
To prevent an integer overflow condition,
you need to perform boundary checks
and input validation in your programming
using your secure coding techniques.
When coding an integer,
you're going to use an integer of 8-bits,
16-bits, 32-bits, or 64-bits,
and those variables can either be signed or unsigned.
For example, if you're using an integer
that is a signed 8-bit value,
it can hold any number between negative 128
and positive 127,
This gives us a total boundary size of 256.
Now, if you used an unsigned 8-bit integer though,
you now can have a number from zero to 255 instead
because again, you still have 256 places to store.
Now, if you have a 16-bit integer,
you get a balance of 65,535.
If you use a 32-bit integer,
you can get up to 4.2 million,
and if you use a 64-bit integer,
you can have a value of 18 quadrillion.
So you may be wondering,
why don't programmers just always use the largest value
by using 64-bits for each integer that's going to be stored?
Well, this comes down to resource management.
As a programmer,
you need to ensure you have the appropriately sized
variable balance for your different variables
to ensure you're not wasting resources.
when you're operating in the cloud environment, for example,
you're paying for every megabyte of RAM
on your virtual servers.
So programming your code to be efficient
in terms of memory and storage
can save you a lot of money in the long term.
If I was using a variable to store the year
that somebody bought one of my courses, for example,
I don't need to use a 64-bit integer to do that
because a 16-bit unsigned integer is much more appropriate
to store a four digit value
representing your year of purchase.
So remember, when it comes to buffer overflow conditions
and integer overflow conditions,
the best things you can do to mitigate them
is to first ensure your systems
are fully patched and up to date.
Second, you should always use secure coding practices,
and remember, secure coding help prevent
both buffer overflows and integer overflows.
Third, you want to utilize ASLR
or address space layout randomization.
And fourth, you want to utilize DEP
or data execution protection.