Part 31 Buffer Overflow
Root Cause Analysis
Finding the Real Cause of a Crash
In the previous part, we learned how to classify crashes when a Fuzzer finds them. Now we want to go a step deeper. This time, we don't just want to know where the program crashed.
We want to understand.
Why it crashed.
Because saying.
Program crashed.
doesn't really help.
It's almost like a car broke down and the mechanic just said.
The car broke down.
Well, thanks for this great discovery.
Crash.
itself is not a bug.
Suppose the debugger shows this.
SIGSEGV.
This means that the program has reached an invalid memory access, but we still don't know why it happened.
It could be one of these.
Buffer Overflow.
Use After Free.
Out of Bounds Read.
Null Pointer Dereference.
So we have to go after the crash itself. Let's go
Crash
↓
What happened
↓
Why happened
↓
Root Cause
Find the problematic instruction
Suppose the Debugger shows this instruction
mov eax,[rcx]
and the program crashes right here
Here the first question is
What is the value of RCX
For example
RCX = 0x00000000000000000
Well here the program is actually reading something from address zero, so the path could be like this
mov eax,[rcx]
↓
RCX = 0
↓
Invalid Memory Access
↓
Crash
But we haven't found the root cause yet
We need to understand
Why RCX is zero
Checking the Registers
After the Crash, we check the status of the Registers
For example:
RAX = 0x000000001
RBX = 0x00000000
RCX = 0x00000000
RDX = 0x00000020
RIP = 0x401234
Here RIP tells us where the CPU was approximately at the time of the Crash
And RCX can also give us important information about the address that was supposed to be used
But the main question is still the same
RCX
↓
Where did it come from
Let's go back
Suppose we have these instructions before the Crash
mov rcx,[rbp-20h]
mov eax,[rcx]
So RCX got its value from here
mov rcx,[rbp-20h]
Now we need to go back one more step
And see
[rbp-20h]
How did it get its value
For example, we may have had something like this before
mov [rbp-20h], rdi
Now we ask
Where did RDI come from
And we go back like this
Crash
↓
RCX
↓
[rbp-20h]
↓
RDI
↓
Function Argument
↓
Input
Here we are getting closer to the source of the problem
General Analysis Path
This is the path we work with a lot during Root Cause Analysis
Crash
↓
Faulting Instruction
↓
Register / Memory
↓
Previous Instruction
↓
Data Origin
↓
Root Cause
That is, we do not just look at the last instruction
We look at how this situation arose
This is where Reverse Engineering really shows itself
A simple example:
Suppose the code is
C
void process(char *input)
{
char buffer[8];
strcpy(buffer, input);
printf("%s", buffer);
}
Now suppose the input is too large
input
↓
strcpy
↓
buffer
↓
Stack Corruption
↓
Crash
The program may crash later, for example, inside printf
If we just say
Crash in printf
Our analysis is incomplete
Because the main problem may have occurred much earlier
Where is the Root Cause
Here
buffer = 8 bytes
input = larger than 8 bytes
strcpy
↓
No bounds checking
↓
Stack Buffer Overflow
That is, strcpy wrote more data than the buffer capacity
and caused memory corruption
So
Crash
≠
Root Cause
Crash
is the final result
Root Cause
Where the problem actually occurred
Another example
Suppose the debugger shows
mov rax,[rbx]
and
RBX = 0
We may say
Null Pointer Dereference
But it is still better to stop here Let's sit
We need to check
RBX
↓
How did it get its value
↓
Why did it become NULL
↓
Should it have been checked before
↓
Root Cause
We may find that a function returned a NULL value but the program used it without checking it, so mov rax,[rbx] itself is only where the problem showed itself, not the problem itself
Why is this step important for Fuzzing
Suppose the Fuzzer found this
Crash found
input_0421.dat
So what now
We still don't know what we are dealing with
It may be:
Crash
↓
Heap Buffer Overflow
or
Crash
↓
Use After Free
or
Crash
↓
Out of Bounds Read
or
Crash
↓
Null Pointer Dereference