Part 32 Buffer Overflow
Program Slicing
Focusing only on the code relevant to the analysis.
It involves tracing a piece of data from its entry point to where it is used.
Imagine a program with thousands of instructions;
we don't need to read through every single line.
Program Slicing
helps isolate only those parts of the program that affect a specific value.
Program Slicing
Suppose we have a value:
user_input
and we want to know which parts of the program affect this value.
For example:
Input
↓
Function A
↓
Function B
↓
Function C
↓
Crash
Instead of examining the entire program,
we examine only this specific path.
Two main types:
Forward Slice
We start with a value and move forward.
The key question:
Where is this value used?
For example:
input
↓
buffer
↓
memcpy
↓
target
Backward Slice
We start from a critical point and trace back.
For example, we have a crash:
Crash
↑
memcpy
↑
buffer
↑
input
The key question:
Where did this value come from?
A simple example:
Suppose the code is:
C
int process(int input)
{
int a = input;
int b = a + 10;
int c = b * 2;
int unrelated = 500;
return c;
}
If our goal is the value of
c,we don't need to examine
unrelated,because it has no effect on
c.The slice
consists roughly of this part:
input
↓
a
↓
b
↓
c
Why is it important in Reverse Engineering? Suppose you have a crash,
and the stack trace points to a large function—
for example:
process_packet()
This function might contain hundreds of lines of code,
yet the crash depends on only one specific value—
for instance:
length
Now, instead of examining the entire function,
we trace the path of
length:length
↓
check
↓
calculation
↓
buffer operation
↓
Crash
Data dependency
For example:
C
int size = input_length;
int copy_size = size + 8;
memcpy(buffer, input, copy_size);
Here,
copy_size depends on size,and
size depends on input_length;so, the data path is:
input_length
↓
size
↓
copy_size
↓
memcpy
This is precisely what we aim to identify during slicing.
Control dependency is also important;
sometimes a value isn't passed directly but influences a program decision—
for example:
C++
if (size > 100)
{
process_large_input(); }
Here, the
size determines which execution path is taken;so, in addition to Data Dependency,
we also have a Control Dependency.
In tools like Ghidra or IDA,
once you identify a significant variable or register,
you can examine its references and
usage paths:
For example:
Variable
↓
References
↓
Functions
↓
Instructions
You then gradually construct the desired slice;
in more advanced tools, this process can be automated.
Difference between Data Flow and Slicing
Simply put:
Data Flow
describes how data moves.
Program Slicing
identifies exactly which parts of the program are relevant
to analyzing a specific value.
So:
Data Flow
↓
Data movement path
Program Slicing
↓
The portion of the code related to a specific piece of data or result
When dealing with a large binary,
there is no need to examine the entire program;
we can select a key value—such as
Input
Length
Pointer
Crash Value
and trace its dependency paths.
Consequently, large sections of code unrelated to our specific problem are excluded,
making the analysis much faster.
@reverseengine