Program Slicing
With Ghidra
So far we have learned Backward Slicing and Forward Slicing
Now it is time to implement these concepts on a real binary
The goal is that when we see a busy function full of variables and instructions, we do not need to go through the entire function
We just isolate the data path that is important to us and follow that
A simple program to test
For example, we have this code
C
#include <stdio.h>
int check(int input) {
int a = input + 5;
int b = a * 3;
int junk = 900;
junk ^= 123;
int result = b - 7;
return result;
}
int main() {
int x = 10;
printf("%d\n", check(x));
return 0;
}
`
If input is 10, the calculations go like this
input = 10
↓
a = 10 + 5 = 15
↓
b = 15 × 3 = 45
↓
result = 45 - 7 = 38
Finally, return returns the value 38
But we also have another variable in the middle
junk = 900
junk ^= 123
This value has no effect on the output
And this is exactly what we want to find with Program Slicing
We import the binary into Ghidra
First, we compile the program and open the executable file in Ghidra
Then we go to the function
check
Here, two parts are very important for us
Decompiler
Function Graph
Decompiler
Helps us to understand the general logic of the function more easily
Function Graph
also helps to see the different paths of function execution and the relationship between Basic Blocks
But be careful
Decompiler
Itself is the absolute truth No
What we see is Ghidra reconstruction from binary
For serious analysis, we need to go back to Assembly and examine the Data Flow itself
First we find the Return Value
Suppose the Decompiler shows something like this
C
int check(int input)
{
int a;
int b;
int junk;
int result;
a = input + 5;
b = a * 3;
junk = 900;
junk = junk ^ 123;
result = b - 7;
return result;
}
Now the main question is
What things affect return
Here we start with Backward Slicing
We go back from Return
The last part is
lua
return result;
So the first thing we need to check is result
result is created here
result = b - 7;
So our dependency is now
return
↑
result
↑
b
Now we ask where b came from
b = a * 3;
So
return
↑
result
↑
b
↑
a
Now a
a = input + 5;
As a result, we get
return
↑
result
↑
b
↑
a
↑
input
If we also put the calculations in the path
input
↓
+5
↓
×3
↓
-7
↓
return
So the Slice related to the output of this program is almost the same path
Now let's examine the Junk Code
We have this part
C
int junk = 900;
junk ^= 123;
But nowhere is the result of junk included in the result calculation
So its path is like this
junk
↓
junk ^ 123
↓
X
And there is no path to result
As a result, when our target
is
return
junk
it is not included in the Backward Slice related to return
This is where we see why Slicing is useful
Instead of considering all the function instructions as equal
We only keep the instructions that depend on the data we want
Let's do the same thing in Assembly
Assuming the compiler has converted the function to something like this
mov eax, edi
add eax, 5
imul eax, 3
sub eax, 7
ret
Now we look from the bottom up
The ret instruction returns the return value of the function
In this example, the output value is in EAX
So we go to the previous instruction
sub eax, 7
That is, the EAX value still affects the output
Before that
imul eax, 3
Again, the same value We have EAX which goes into the next calculation
add eax, 5
Again EAX is part of the data path
And finally
mov eax, edi
Here it is clear that the initial value of EDI has entered this path
So our Slice in Assembly becomes
EDI
↓
EAX
↓
EAX + 5
↓
EAX × 3
↓
EAX - 7
↓
RET
Here we are following the real Data Flow at the Instruction level
A very important point
In real binary, there is usually no more names like
input
result
a
b
You may only see things like
RAX
RBX
RCX
RDX
[rbp-0x20]
[rbp-0x18]
This is where our work begins
We have to reconstruct the relationship between these data ourselves
For example
mov eax, [rbp-20h]
add eax, 5
imul eax, 3
mov [rbp-18h], eax
We can mentally convert this path to
Post #1768
33
ReverseEngineering Program Slicing با Ghidra تا اینجا Backward Slicing و Forward Slicing رو یاد گرفتیم حالا وقتشه همین مفاهیم رو روی یه باینری واقعی اجرا کنیم هدف اینه که وقتی یه تابع شلوغ و پر از متغیر و دستور دیدیم لازم نباشه کل تابع رو زیر و رو کنیم فقط مسیر دادهای که…