ReverseEngineering
Program Slicing با Ghidra تا اینجا Backward Slicing و Forward Slicing رو یاد گرفتیم حالا وقتشه همین مفاهیم رو روی یه باینری واقعی اجرا کنیم هدف اینه که وقتی یه تابع شلوغ و پر از متغیر و دستور دیدیم لازم نباشه کل تابع رو زیر و رو کنیم فقط مسیر دادهای که…
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
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
[rbp-20h]
↓
+5
↓
×3
↓
[rbp-18h]
Now we need to see where [rbp-18h] is used later
If we end up with something like this
mov eax, [rbp-18h]
ret
We understand that this value is part of the data path that leads to the function output
Combine Forward and Backward
Here's where things get more interesting
Assume the function input is in EDI
We start from the input
Forward Slicing
EDI
↓
EAX
↓
ADD
↓
IMUL
↓
SUB
↓
RETURN
Now on the other hand we go back from RETURN
Backward Slicing
RETURN
↑
SUB
↑
IMUL
↑
ADD
↑
EAX
↑
EDI
If these two analyses reach a common path
We are more confident that we found the main Data Flow path correctly
EDI
↓
ADD
↓
IMUL
↓
SUB
↓
RETURN
What to look for in Ghidra
When you are doing Program Slicing on a real function, there are a few things that are very important
Decompiler
Function Graph
References
Register Usage
Memory References
Variable Definitions
Variable Uses
But the two most important questions are
Where is this value created
And
Where is this value used next
These two questions seem simple, but they are the basis of a lot of serious Reverse Engineering analysis
Every time you see a Register or Memory Location, you should follow this relationship
Definition
↓
Value
↓
Use
↓
New Value
↓
Next Use
A slightly more realistic example
Let's say we have this function
C++
int verify(int input) {
int x = input ^ 0x55;
int y = x + 0x1234;
int noise = 777;
noise *= 4;
int z = y ^ 0xAA;
return z == 0x5678;
}
Here our goal is to understand the last condition
lua
return z == 0x5678;
After return, we go back
first z
then y
then x
and finally input
the path
return
↑
comparison
↑
z
↑
y
↑
x
↑
input
If we add the calculations
input
↓
XOR 0x55
↓
+ 0x1234
↓
XOR 0xAA
↓
compare 0x5678
↓
return
But this part
noise = 777
noise *= 4
has nothing to do with z and the final condition
So for the analysis of this condition, it can be outside our Slice for now
Why is this important to us
Because in a real program, a function may have hundreds or even thousands of instructions
But what we are really looking for may be just this
Input
↓
Transformation
↓
Comparison
↓
Decision
Slicing
It helps to separate this path from a large amount of code
Especially when dealing with things like this We will
Obfuscation
Junk Code
Function Complexity
Chain of Calculations
Multiple Conditions
The function may look very busy
But when we follow the Data Flow, we will find that only a few instructions actually affect the result that matters to us
Exercise:
Examine this function
int verify(int input) {
int a = input ^ 0x31;
int junk1 = 1234;
junk1 += 55;
int b = a * 7;
int junk2 = 999;
junk2 ^= 0x44;
int c = b - 12;
return c == 0x500;
}
Extract two slices
First, the Backward Slice from return
That is, start here
return
↑
?
↑
?
↑
?
↑
input
Then find the Forward Slice from input
input
↓
?
↓
?
↓
?
↓
comparison
↓
return
In the end, you should be able to get the main path to look something like this
input
↓
XOR 0x31
↓
× 7
↓
- 12
↓
comparison 0x500
↓
return
And these two parts
junk1
junk2
should not go into the Slice for this output because they have no effect on the final result
Now there is a more important step
Do the same analysis on Assembly, there is no more clean and beautiful input and result names
You have to follow the Registers and Memory Locations yourself and reconstruct the Data Flow from the Instructions
This is where the difference between just reading the Decompiler and really analyzing the binary becomes clear
@reverseengine
↓
+5
↓
×3
↓
[rbp-18h]
Now we need to see where [rbp-18h] is used later
If we end up with something like this
mov eax, [rbp-18h]
ret
We understand that this value is part of the data path that leads to the function output
Combine Forward and Backward
Here's where things get more interesting
Assume the function input is in EDI
We start from the input
Forward Slicing
EDI
↓
EAX
↓
ADD
↓
IMUL
↓
SUB
↓
RETURN
Now on the other hand we go back from RETURN
Backward Slicing
RETURN
↑
SUB
↑
IMUL
↑
ADD
↑
EAX
↑
EDI
If these two analyses reach a common path
We are more confident that we found the main Data Flow path correctly
EDI
↓
ADD
↓
IMUL
↓
SUB
↓
RETURN
What to look for in Ghidra
When you are doing Program Slicing on a real function, there are a few things that are very important
Decompiler
Function Graph
References
Register Usage
Memory References
Variable Definitions
Variable Uses
But the two most important questions are
Where is this value created
And
Where is this value used next
These two questions seem simple, but they are the basis of a lot of serious Reverse Engineering analysis
Every time you see a Register or Memory Location, you should follow this relationship
Definition
↓
Value
↓
Use
↓
New Value
↓
Next Use
A slightly more realistic example
Let's say we have this function
C++
int verify(int input) {
int x = input ^ 0x55;
int y = x + 0x1234;
int noise = 777;
noise *= 4;
int z = y ^ 0xAA;
return z == 0x5678;
}
Here our goal is to understand the last condition
lua
return z == 0x5678;
After return, we go back
first z
then y
then x
and finally input
the path
return
↑
comparison
↑
z
↑
y
↑
x
↑
input
If we add the calculations
input
↓
XOR 0x55
↓
+ 0x1234
↓
XOR 0xAA
↓
compare 0x5678
↓
return
But this part
noise = 777
noise *= 4
has nothing to do with z and the final condition
So for the analysis of this condition, it can be outside our Slice for now
Why is this important to us
Because in a real program, a function may have hundreds or even thousands of instructions
But what we are really looking for may be just this
Input
↓
Transformation
↓
Comparison
↓
Decision
Slicing
It helps to separate this path from a large amount of code
Especially when dealing with things like this We will
Obfuscation
Junk Code
Function Complexity
Chain of Calculations
Multiple Conditions
The function may look very busy
But when we follow the Data Flow, we will find that only a few instructions actually affect the result that matters to us
Exercise:
Examine this function
int verify(int input) {
int a = input ^ 0x31;
int junk1 = 1234;
junk1 += 55;
int b = a * 7;
int junk2 = 999;
junk2 ^= 0x44;
int c = b - 12;
return c == 0x500;
}
Extract two slices
First, the Backward Slice from return
That is, start here
return
↑
?
↑
?
↑
?
↑
input
Then find the Forward Slice from input
input
↓
?
↓
?
↓
?
↓
comparison
↓
return
In the end, you should be able to get the main path to look something like this
input
↓
XOR 0x31
↓
× 7
↓
- 12
↓
comparison 0x500
↓
return
And these two parts
junk1
junk2
should not go into the Slice for this output because they have no effect on the final result
Now there is a more important step
Do the same analysis on Assembly, there is no more clean and beautiful input and result names
You have to follow the Registers and Memory Locations yourself and reconstruct the Data Flow from the Instructions
This is where the difference between just reading the Decompiler and really analyzing the binary becomes clear
@reverseengine
System Call
برنامه چجور با Kernel حرف میزنه؟
تا اینجا با چیزهایی مثل:
lua
fork()
exec()
wait()
exit()
کار کردیم
اما یک سوال مهم وجود داره
این توابع چطور میتونن از Kernel درخواست انجام یک کار بکنن؟
مثلا برنامهای که داخل User Space اجرا میشه چطور میتونه:
فایل باز کنه؟
داده بخوانه؟
Process
جدید بسازه؟
حافظه بگیره؟
Process
خودش رو تموم کنه؟
جواب اصلی:
System Call
User Mode و Kernel Mode
CPU
معمولا اجرای برنامه رو در سطح دسترسی محدودی انجام میدهد که به اون:
User Mode
میگیم
برنامه در این حالت نمیتونه مستقیما هر کاری که دلش خواست با سیستم انجام بده
مثلا نمیتونه مستقیما:
Hardware
Kernel Memory
Page Tables
Device Control
رو دستکاری کنه
چون اگر هر برنامهای چنین دسترسیای داشت یک برنامه خرابکار میتونست کل سیستم رو به فنا بده😁
و ادم ها قبلا به اندازه کافی راه های دیگه ای برای این کار پیدا کردن
Kernel Mode
Kernel
در سطح دسترسی بالاتری اجرا میشه:
Kernel Mode
در این حالت Kernel میتونه به منابع حساس سیستم دسترسی داشته باشه
پس ساختار کلی تقریبا این شکلیه:
User Space
│
│ System Call
↓
Kernel Space
│
↓
Hardware / Resources
برنامه مستقیم وارد Kernel نمیشه
بلکه از یک دروازه کنترل شده استفاده میکنه این دروازه همون System Call هست
یک مثال ساده
فرض کنیم برنامه میخاد از یک فایل بخونه
در سطح برنامه ممکنه چیزی شبیه این داشته باشیم:
read(fd, buffer, size);
برنامه درخواست میکنه:
من میخاهم از این File Descriptor
به اندازه مشخصی داده بخونم
در اخر درخواست باید به Kernel برسه
Kernel
بررسی میکنه:
این Process چه کسیه؟
اجازه دسترسی داره؟
fd
معتبره؟
آدرس buffer معتبره؟
چه مقدار داده باید خونده بشه؟
بعد عملیات رو انجام میده و نتیجه رو برمیگردونه
مسیر کلی System Call
به شکل ساده:
Program
│
↓
Library / API Wrapper
│
↓
System Call Mechanism
│
↓
CPU → Kernel Mode
│
↓
Kernel
│
↓
Operation
│
↓
Return Value
│
↓
User Mode
یعنی برنامه برای انجام عملیات حساس از یک مسیر مشخص وارد Kernel میشه
System Call با Function Call
یکی نیست
این دو تا رو قاطی نکنیم
یک Function Call معمولی مثلا:
result = add(a, b);
معمولا در همون فضای اجرای برنامه انجام میشه
اما System Call یک عبور از مرز دسترسی User و Kernel ایجاد میکنه
پس:
Function Call
↓
همون فضای اجرا
System Call
↓
User Mode
↓
Kernel Mode
↓
برگشت به User Mode
این تفاوت در Reverse Engineering خیلی مهمه
هم از همین مسیر استفاده میکنن
مثلا در برنامه C مینویسیم:
fork();
اما این به معنی یک Function ساده مثل:
C
int add(int a, int b)
نیست
در سیستمهای Unix-like این درخواست در اخر باید به Kernel برسه تا Kernel بتونه Process جدید ایجاد کنه
همین ایده برای عملیات هایی مثل:
shell
read
write
open
mmap
fork
exec
wait
هم وجود داره
البته یک نکته مهم
چیزی که در کد میبینیم لزوما خود System Call خام نیست ممکنه یک Library Wrapper باشه که در اخر System Call مناسب رو انجام میده
System Call
چطور به Kernel میرسه؟
در معماریهای مدرن CPU مکانیزم مخصوصی برای این انتقال داره
مثلا در x86-64 لینوکس دستور:
syscall
برای ورود به مسیر System Call استفاده میشه
به صورت مفهومی:
User Code
↓
Arguments آماده میشن
↓
System Call Number
↓
syscall
↓
CPU
↓
Kernel Entry
↓
Kernel Handler
Kernel
بعد از ورود درخواست رو بررسی و پردازش میکنه
برای ما چرا مهمه؟
فرض کنید داخل یک Binary این رو ببینید:
syscall
دیگه فقط با یک دستور اسمبلی معمولی طرف نیستید
این دستور میتونه نشون دهنده عبور برنامه از:
User Space
↓
Kernel Space
باشه
از طرف دیگه اگر داخل کد برنامه API هایی مثل:
CreateFile
ReadFile
VirtualAlloc
CreateProcess
رو ببینید باید بدونید این API ها در اخر برای انجام تعداد زیادی از عملیات های سیستمی به مکانیزمهای Kernel وابسته هستن پس در RE باید بتونید این زنجیره رو در ذهنتون ببینید:
Application
↓
Library / API
↓
System Call
↓
Kernel
↓
Resource / Hardware
این دیدگاه بعدا هنگام تحلیل Process ها Memory File System Thread ها و حتی رفتارهای پیچیدهتر Binary ها خیلی به کارتون میاد
برنامه چجور با Kernel حرف میزنه؟
تا اینجا با چیزهایی مثل:
lua
fork()
exec()
wait()
exit()
کار کردیم
اما یک سوال مهم وجود داره
این توابع چطور میتونن از Kernel درخواست انجام یک کار بکنن؟
مثلا برنامهای که داخل User Space اجرا میشه چطور میتونه:
فایل باز کنه؟
داده بخوانه؟
Process
جدید بسازه؟
حافظه بگیره؟
Process
خودش رو تموم کنه؟
جواب اصلی:
System Call
User Mode و Kernel Mode
CPU
معمولا اجرای برنامه رو در سطح دسترسی محدودی انجام میدهد که به اون:
User Mode
میگیم
برنامه در این حالت نمیتونه مستقیما هر کاری که دلش خواست با سیستم انجام بده
مثلا نمیتونه مستقیما:
Hardware
Kernel Memory
Page Tables
Device Control
رو دستکاری کنه
چون اگر هر برنامهای چنین دسترسیای داشت یک برنامه خرابکار میتونست کل سیستم رو به فنا بده😁
و ادم ها قبلا به اندازه کافی راه های دیگه ای برای این کار پیدا کردن
Kernel Mode
Kernel
در سطح دسترسی بالاتری اجرا میشه:
Kernel Mode
در این حالت Kernel میتونه به منابع حساس سیستم دسترسی داشته باشه
پس ساختار کلی تقریبا این شکلیه:
User Space
│
│ System Call
↓
Kernel Space
│
↓
Hardware / Resources
برنامه مستقیم وارد Kernel نمیشه
بلکه از یک دروازه کنترل شده استفاده میکنه این دروازه همون System Call هست
یک مثال ساده
فرض کنیم برنامه میخاد از یک فایل بخونه
در سطح برنامه ممکنه چیزی شبیه این داشته باشیم:
read(fd, buffer, size);
برنامه درخواست میکنه:
من میخاهم از این File Descriptor
به اندازه مشخصی داده بخونم
در اخر درخواست باید به Kernel برسه
Kernel
بررسی میکنه:
این Process چه کسیه؟
اجازه دسترسی داره؟
fd
معتبره؟
آدرس buffer معتبره؟
چه مقدار داده باید خونده بشه؟
بعد عملیات رو انجام میده و نتیجه رو برمیگردونه
مسیر کلی System Call
به شکل ساده:
Program
│
↓
Library / API Wrapper
│
↓
System Call Mechanism
│
↓
CPU → Kernel Mode
│
↓
Kernel
│
↓
Operation
│
↓
Return Value
│
↓
User Mode
یعنی برنامه برای انجام عملیات حساس از یک مسیر مشخص وارد Kernel میشه
System Call با Function Call
یکی نیست
این دو تا رو قاطی نکنیم
یک Function Call معمولی مثلا:
result = add(a, b);
معمولا در همون فضای اجرای برنامه انجام میشه
اما System Call یک عبور از مرز دسترسی User و Kernel ایجاد میکنه
پس:
Function Call
↓
همون فضای اجرا
System Call
↓
User Mode
↓
Kernel Mode
↓
برگشت به User Mode
این تفاوت در Reverse Engineering خیلی مهمه
fork() و wait() هم از همین مسیر استفاده میکنن
مثلا در برنامه C مینویسیم:
fork();
اما این به معنی یک Function ساده مثل:
C
int add(int a, int b)
نیست
در سیستمهای Unix-like این درخواست در اخر باید به Kernel برسه تا Kernel بتونه Process جدید ایجاد کنه
همین ایده برای عملیات هایی مثل:
shell
read
write
open
mmap
fork
exec
wait
هم وجود داره
البته یک نکته مهم
چیزی که در کد میبینیم لزوما خود System Call خام نیست ممکنه یک Library Wrapper باشه که در اخر System Call مناسب رو انجام میده
System Call
چطور به Kernel میرسه؟
در معماریهای مدرن CPU مکانیزم مخصوصی برای این انتقال داره
مثلا در x86-64 لینوکس دستور:
syscall
برای ورود به مسیر System Call استفاده میشه
به صورت مفهومی:
User Code
↓
Arguments آماده میشن
↓
System Call Number
↓
syscall
↓
CPU
↓
Kernel Entry
↓
Kernel Handler
Kernel
بعد از ورود درخواست رو بررسی و پردازش میکنه
برای ما چرا مهمه؟
فرض کنید داخل یک Binary این رو ببینید:
syscall
دیگه فقط با یک دستور اسمبلی معمولی طرف نیستید
این دستور میتونه نشون دهنده عبور برنامه از:
User Space
↓
Kernel Space
باشه
از طرف دیگه اگر داخل کد برنامه API هایی مثل:
CreateFile
ReadFile
VirtualAlloc
CreateProcess
`رو ببینید باید بدونید این API ها در اخر برای انجام تعداد زیادی از عملیات های سیستمی به مکانیزمهای Kernel وابسته هستن پس در RE باید بتونید این زنجیره رو در ذهنتون ببینید:
Application
↓
Library / API
↓
System Call
↓
Kernel
↓
Resource / Hardware
این دیدگاه بعدا هنگام تحلیل Process ها Memory File System Thread ها و حتی رفتارهای پیچیدهتر Binary ها خیلی به کارتون میاد
System Call
رابط کنترلشده ای که یک برنامه در User Mode از طریق اون درخواست هایی رو از Kernel میخاد
مهمترین نکته:
User Mode
│
│ System Call
↓
Kernel Mode
│
↓
Kernel performs operation
│
↓
User Mode
و این دقیقا یکی از پایه های مهم درک OS Internals و Reverse Engineering هست
@reverseengine
رابط کنترلشده ای که یک برنامه در User Mode از طریق اون درخواست هایی رو از Kernel میخاد
مهمترین نکته:
User Mode
│
│ System Call
↓
Kernel Mode
│
↓
Kernel performs operation
│
↓
User Mode
و این دقیقا یکی از پایه های مهم درک OS Internals و Reverse Engineering هست
@reverseengine
ReverseEngineering
System Call برنامه چجور با Kernel حرف میزنه؟ تا اینجا با چیزهایی مثل: lua fork() exec() wait() exit() کار کردیم اما یک سوال مهم وجود داره این توابع چطور میتونن از Kernel درخواست انجام یک کار بکنن؟ مثلا برنامهای که داخل User Space اجرا میشه چطور میتونه:…
System Call
How does a program talk to the Kernel?
So far we have worked with things like:
lua
fork()
exec()
wait()
exit()
But there is an important question
How can these functions request the Kernel to do something?
For example, how can a program running in User Space:
Open a file?
Read data?
Create a new process?
Get memory?
Process
terminate itself?
Original answer:
System Call
User Mode and Kernel Mode
The CPU
usually executes the program at a limited access level, which we call:
User Mode
The program in this mode cannot directly do anything it wants with the system
For example, it cannot directly manipulate:
Hardware
Kernel Memory
Page Tables
Device Control
Because if any program had such access, a malicious program could destroy the entire system😁
And people have already found enough other ways to do this
Kernel Mode
The Kernel is executed at a higher access level:
Kernel Mode
In this mode, the Kernel can access sensitive system resources
So the overall structure is approximately as follows:
User Space
│
│ System Call
↓
Kernel Space
│
↓
Hardware / Resources
The program does not enter the Kernel directly
But uses a controlled gateway, this gateway is the System Call
A simple example
Suppose the program wants to Read a file
At the program level we might have something like this:
read(fd, buffer, size);
The program requests:
I want to read a certain amount of data from this File Descriptor
At the end of the request, it should reach the Kernel
The Kernel checks:
Who is this Process?
Does it have access?
Is the fd
valid?
Is the buffer address valid?
How much data should be read?
Then it performs the operation and returns the result
General System Call Path
In simple terms:
Program
│
↓
Library / API Wrapper
│
↓
System Call Mechanism
│
↓
CPU → Kernel Mode
│
↓
Kernel
│
↓
Operation
│
↓
Return Value
│
↓
User Mode
That is, the program enters the Kernel from a specific path to perform sensitive operations
System Call is not the same as Function Call
Let's not confuse the two
A normal Function Call, for example:
result = add(a, b);
Usually it is done in the same execution space of the program
But System Call creates a crossing of the User and Kernel access boundaries
So:
Function Call
↓
Same execution space
System Call
↓
User Mode
↓
Kernel Mode
↓
Return to User Mode
This difference is very important in Reverse Engineering
Fork() and wait()
also use the same path
For example, in a C program we write:
fork();
But this does not mean a simple Function like:
C
int add(int a, int b)
In Unix-like systems this request must finally reach the Kernel so that the Kernel can create a new Process
The same idea exists for operations like:
shell
read
write
open
mmap
fork
exec
wait
Of course, one important point
What we see in the code is not necessarily the raw System Call itself, it may be a Library Wrapper that finally performs the appropriate System Call
How does System Call
reach the Kernel?
In modern architectures, the CPU has a special mechanism for this transfer. For example, in x86-64 Linux, the command:
syscall
is used to enter the System Call path. Conceptually:
User Code
↓
Arguments are prepared
↓
System Call Number
↓
syscall
↓
CPU
↓
Kernel Entry
↓
Kernel Handler
The Kernel
checks and processes the request after it has entered.
Why is it important to us?
Suppose you see this inside a Binary:
syscall
You are no longer dealing with just a regular assembly instruction
This instruction can indicate that the program passes through:
User Space
↓
Kernel Space
On the other hand, if you see APIs like:
CreateFile
ReadFile
VirtualAlloc
CreateProcess
in the program code, you should know that these APIs ultimately depend on Kernel mechanisms to perform a large number of system operations, so in RE you should be able to see this chain in your mind:
Application
↓
Library / API
↓
System Call
↓
Kernel
↓
Resource / Hardware
This perspective will come in handy later when analyzing Processes, Memory, File System, Threads, and even more complex behavior of Binary.
How does a program talk to the Kernel?
So far we have worked with things like:
lua
fork()
exec()
wait()
exit()
But there is an important question
How can these functions request the Kernel to do something?
For example, how can a program running in User Space:
Open a file?
Read data?
Create a new process?
Get memory?
Process
terminate itself?
Original answer:
System Call
User Mode and Kernel Mode
The CPU
usually executes the program at a limited access level, which we call:
User Mode
The program in this mode cannot directly do anything it wants with the system
For example, it cannot directly manipulate:
Hardware
Kernel Memory
Page Tables
Device Control
Because if any program had such access, a malicious program could destroy the entire system😁
And people have already found enough other ways to do this
Kernel Mode
The Kernel is executed at a higher access level:
Kernel Mode
In this mode, the Kernel can access sensitive system resources
So the overall structure is approximately as follows:
User Space
│
│ System Call
↓
Kernel Space
│
↓
Hardware / Resources
The program does not enter the Kernel directly
But uses a controlled gateway, this gateway is the System Call
A simple example
Suppose the program wants to Read a file
At the program level we might have something like this:
read(fd, buffer, size);
The program requests:
I want to read a certain amount of data from this File Descriptor
At the end of the request, it should reach the Kernel
The Kernel checks:
Who is this Process?
Does it have access?
Is the fd
valid?
Is the buffer address valid?
How much data should be read?
Then it performs the operation and returns the result
General System Call Path
In simple terms:
Program
│
↓
Library / API Wrapper
│
↓
System Call Mechanism
│
↓
CPU → Kernel Mode
│
↓
Kernel
│
↓
Operation
│
↓
Return Value
│
↓
User Mode
That is, the program enters the Kernel from a specific path to perform sensitive operations
System Call is not the same as Function Call
Let's not confuse the two
A normal Function Call, for example:
result = add(a, b);
Usually it is done in the same execution space of the program
But System Call creates a crossing of the User and Kernel access boundaries
So:
Function Call
↓
Same execution space
System Call
↓
User Mode
↓
Kernel Mode
↓
Return to User Mode
This difference is very important in Reverse Engineering
Fork() and wait()
also use the same path
For example, in a C program we write:
fork();
But this does not mean a simple Function like:
C
int add(int a, int b)
In Unix-like systems this request must finally reach the Kernel so that the Kernel can create a new Process
The same idea exists for operations like:
shell
read
write
open
mmap
fork
exec
wait
Of course, one important point
What we see in the code is not necessarily the raw System Call itself, it may be a Library Wrapper that finally performs the appropriate System Call
How does System Call
reach the Kernel?
In modern architectures, the CPU has a special mechanism for this transfer. For example, in x86-64 Linux, the command:
syscall
is used to enter the System Call path. Conceptually:
User Code
↓
Arguments are prepared
↓
System Call Number
↓
syscall
↓
CPU
↓
Kernel Entry
↓
Kernel Handler
The Kernel
checks and processes the request after it has entered.
Why is it important to us?
Suppose you see this inside a Binary:
syscall
You are no longer dealing with just a regular assembly instruction
This instruction can indicate that the program passes through:
User Space
↓
Kernel Space
On the other hand, if you see APIs like:
CreateFile
ReadFile
VirtualAlloc
CreateProcess
in the program code, you should know that these APIs ultimately depend on Kernel mechanisms to perform a large number of system operations, so in RE you should be able to see this chain in your mind:
Application
↓
Library / API
↓
System Call
↓
Kernel
↓
Resource / Hardware
This perspective will come in handy later when analyzing Processes, Memory, File System, Threads, and even more complex behavior of Binary.
👍1
System Call A controlled interface through which a program in User Mode makes requests to the Kernel
The most important point:
User Mode
│
│ System Call
↓
Kernel Mode
│
↓
Kernel performs operation
│
↓
User Mode
And this is exactly one of the important foundations of understanding OS Internals and Reverse Engineering
@reverseengine
The most important point:
User Mode
│
│ System Call
↓
Kernel Mode
│
↓
Kernel performs operation
│
↓
User Mode
And this is exactly one of the important foundations of understanding OS Internals and Reverse Engineering
@reverseengine
SELECT * FROM binary - Vibe Reverse Engineering with IDA, Ghidra and Binary Ninja
https://youtu.be/ADPoD7-_K5s?si=HCgt4gVmbkw5S05i
https://youtu.be/ADPoD7-_K5s?si=HCgt4gVmbkw5S05i
YouTube
RECON 2026 - SELECT * FROM binary — Vibe Reversing Across IDA, Ghidra, and Binary Ninja
Presented by Elias Bachaalany
"Vibe coding" lets developers build software by describing intent to an AI agent. Can the same approach work for reverse engineering - where the analyst describes what they want to understand and the agent drives the tools?…
"Vibe coding" lets developers build software by describing intent to an AI agent. Can the same approach work for reverse engineering - where the analyst describes what they want to understand and the agent drives the tools?…
Fileless ELF Execution via Kernel Keyring
Using the Linux kernel keyring to stage an ELF in slab memory and execute it via userland exec, skipping execve and the filesystem entirely.
https://matheuzsecurity.github.io/hacking/linux-kernel-keyring-fileless-exec/
Using the Linux kernel keyring to stage an ELF in slab memory and execute it via userland exec, skipping execve and the filesystem entirely.
https://matheuzsecurity.github.io/hacking/linux-kernel-keyring-fileless-exec/
matheuzsecurity.github.io
Fileless ELF Execution via Kernel Keyring · 0xMatheuZ
Using the Linux kernel keyring to stage an ELF in slab memory and execute it via userland exec, skipping execve and the filesystem entirely.
Dragon Power Against VMProtect
https://farena.in/dragon-tales-web/posts/dragon-power-against-vmprotect/
https://farena.in/dragon-tales-web/posts/dragon-power-against-vmprotect/
farena.in
Dragon Power Against VMProtect · dragon-tales
Reverse-engineering, naming and devirtualizing a VMProtect x64 virtual machine with dragon-tales: all 256 handlers in four views, the virtual CFG recovered from the file alone with no trace, the last MBA cleaned, and the function recompiled to an executable…
_writing_a_c_compiler_build_a_real_programming_language_from_scratch.pdf
7.2 MB
Writing a C compiler
Defeating Anti-Reverse Engineering: A Deep Dive into the 'Trouble' Binary
https://binary.ninja/2026/01/23/reversing-linux-anti-re.html
@reverseengine
https://binary.ninja/2026/01/23/reversing-linux-anti-re.html
@reverseengine
Binary Ninja
Binary Ninja - Defeating Anti-Reverse Engineering: A Deep Dive into the 'Trouble' Binary
Binary Ninja is a modern reverse engineering platform with a scriptable and extensible decompiler.
Bringing Metal to a crypto backdoor fight! Exploiting the GPU and the 90s crypto wars to crack the APT Down code signing keys
https://reverse.put.as/2025/08/24/rc4bruteforce
@reverseengine
https://reverse.put.as/2025/08/24/rc4bruteforce
@reverseengine
Reverse Engineering
Bringing Metal to a crypto backdoor fight! Exploiting the GPU and the 90s crypto wars to crack the APT Down code signing keys
The APT Down leak contained four code signing certificates and the passphrase only for the most recent one. Since the passphrase was found on the usual rockyou.txt wordlist, I was curious to see if the remaining three could be cracked using the same wordlist.…
Trap
و ورود از User Mode به Kernel Mode
تا اینجا فهمیدیم برنامه ها توی User Mode اجرا میشن و وقتی بخوان از سیستم عامل یه کار حساس بخوان از System Call استفاده میکنن
ولی یه سوال مهم داریم
CPU
دقیقا چطوری از اجرای عادی برنامه توی User Mode میره داخل Kernel Mode
اینجا مفهوم Trap وارد ماجرا میشه
Trap
یعنی یه انتقال کنترل شده از اجرای عادی برنامه به Kernel یعنی برنامه داره توی User Mode اجرا میشه
User Mode
│
│ درخواست سرویس
▼
Trap
│
▼
Kernel Mode
بعد از اینکه Kernel کارش رو انجام داد کنترل دوباره به برنامه برمیگرده
System Call و Trap
چه فرقی دارن
این دوتا یکی نیستن
System Call
در واقع درخواست برنامه از سیستم عامله
مثلا برنامه میگه من میخوام از فایل بخونم
یا چیزی بنویسم
یا حافظه بگیرم
مثل:
read()
write()
mmap()
اما Trap مکانیزمیه که باعث میشه CPU کنترل اجرا رو به Kernel منتقل کنه
پس خیلی ساده
Program
│
│ System Call
▼
Trap / System call entry
│
▼
Kernel
یعنی System Call میگه چه سرویسی میخوام
Trap
یا مسیر ورود باعث میشه اجرای برنامه وارد Kernel بشه
مثال x86 64:
توی Linux روی معماری x86 64 معمولا System Call با دستور
syscall
انجام میشه
قبل از اجرای این دستور برنامه اطلاعات لازم رو داخل Register های مشخص قرار میده
مفهوم:
Registers
│
├── شماره System Call
├── Argument 1
├── Argument 2
└──
│
▼
syscall
│
▼
Kernel
Kernel
وقتی وارد میشه شماره System Call رو بررسی میکنه تا بفهمه برنامه دقیقا چه درخواستی داشته
مثلا برنامه ممکنه درخواست خوندن اطلاعات از یه فایل رو داشته باشه
پس Kernel باید بفهمه این درخواست مربوط به کدوم System Call هست
Kernel
از کجا میفهمه چه System Call ای درخواست شده
اینجا یکی از چیزهای مهم System Call Number هست
هر System Call توی Linux یه شماره مشخص داره
البته این شماره ها به معماری و ABI وابسته هستن
یعنی نباید فکر کنیم شماره یه System Call روی همه سیستم ها یکیه
به صورت مفهومی:
System Call Number
+
Arguments
│
▼
syscall
│
▼
Kernel
Kernel
با استفاده از این اطلاعات مسیر مربوط به اون درخواست رو پیدا میکنه
Kernel
هنگام ورود چه کار میکنه
وقتی CPU وارد Kernel میشه Kernel باید درخواست برنامه رو بررسی کنه
به صورت ساده
User Code
│
▼
syscall
│
▼
Kernel Entry
│
▼
بررسی درخواست
│
▼
اجرای عملیات
│
▼
Return Value
│
▼
User Mode
Kernel
ممکنه چیزهایی مثل مجوز دسترسی و معتبر بودن بعضی اطلاعات رو بررسی کنه
مثلاً برنامه User Space نباید بتونه هر آدرس حافظه ای که دلش خواست رو بدون کنترل دستکاری کنه
سیستم عامل اینجا نقش یک مراقب رو بازی میکنه چون اگر هر برنامه ای میتونست هر کاری با منابع سیستم بکنه عملا سیستم عامل تبدیل میشد به چیزی که کاربردی نداره
Trap
فقط برای System Call نیست
اینجا یه نکته مهم داریم
وقتی میگیم Trap نباید فکر کنیم فقط برای System Call استفاده میشه
در سیستم عامل و معماری CPU انتقال کنترل به Kernel میتونه به دلایل مختلف اتفاق بیفته
مثلا:
System Call
Exception
بعضی رویدادهای سخت افزاری از مسیر Interrupt البته این مفاهیم دقیقا یکی نیستن و باید جدا از هم بررسی بشن
Exception چیه
Exception
وقتی اتفاق میفته که CPU موقع اجرای یه دستور با یه شرایط خاص روبرو بشه
مثلا:
برنامه
│
▼
اجرای دستور
│
▼
شرایط خاص
│
▼
Exception
│
▼
Kernel Handler
یکی از مثال های معروفش
Page Fault
فرض کنید برنامه به یه صفحه حافظه دسترسی پیدا کنه ولی اون صفحه در اون لحظه شرایط لازم برای دسترسی رو نداشته باشه
CPU
یه Page Fault ایجاد میکنه
بعد Kernel وارد ماجرا میشه و بررسی میکنه باید چه کاری انجام بشه
یه نکته مهم:
Page Fault
لزوما به معنی Crash نیست
ممکنه سیستم عامل بتونه اون رو کاملا عادی مدیریت کنه
مثلا صفحه موردنظر رو آماده کنه و برنامه دوباره ادامه بده
Interrupt
معمولا از طرف سخت افزار یا کنترل کننده سخت افزار ایجاد میشه مثلا یه دستگاه میخواد CPU رو از یه اتفاق باخبر کنه
به صورت ساده:
Hardware
│
▼
Interrupt
│
▼
CPU
│
▼
Kernel
مثلا یه عملیات I O تموم شده
سخت افزار میتونه با Interrupt به CPU خبر بده که عملیات تموم شده
بعد Kernel میتونه این اتفاق رو مدیریت کنه
تفاوت این سه مفهوم:
System Call
درخواست برنامه برای گرفتن یه سرویس از سیستم عامل
Exception
اتفاقی که در نتیجه اجرای دستور یا شرایط مربوط به CPU رخ میده
و ورود از User Mode به Kernel Mode
تا اینجا فهمیدیم برنامه ها توی User Mode اجرا میشن و وقتی بخوان از سیستم عامل یه کار حساس بخوان از System Call استفاده میکنن
ولی یه سوال مهم داریم
CPU
دقیقا چطوری از اجرای عادی برنامه توی User Mode میره داخل Kernel Mode
اینجا مفهوم Trap وارد ماجرا میشه
Trap
یعنی یه انتقال کنترل شده از اجرای عادی برنامه به Kernel یعنی برنامه داره توی User Mode اجرا میشه
User Mode
│
│ درخواست سرویس
▼
Trap
│
▼
Kernel Mode
بعد از اینکه Kernel کارش رو انجام داد کنترل دوباره به برنامه برمیگرده
System Call و Trap
چه فرقی دارن
این دوتا یکی نیستن
System Call
در واقع درخواست برنامه از سیستم عامله
مثلا برنامه میگه من میخوام از فایل بخونم
یا چیزی بنویسم
یا حافظه بگیرم
مثل:
read()
write()
mmap()
اما Trap مکانیزمیه که باعث میشه CPU کنترل اجرا رو به Kernel منتقل کنه
پس خیلی ساده
Program
│
│ System Call
▼
Trap / System call entry
│
▼
Kernel
یعنی System Call میگه چه سرویسی میخوام
Trap
یا مسیر ورود باعث میشه اجرای برنامه وارد Kernel بشه
مثال x86 64:
توی Linux روی معماری x86 64 معمولا System Call با دستور
syscall
انجام میشه
قبل از اجرای این دستور برنامه اطلاعات لازم رو داخل Register های مشخص قرار میده
مفهوم:
Registers
│
├── شماره System Call
├── Argument 1
├── Argument 2
└──
│
▼
syscall
│
▼
Kernel
Kernel
وقتی وارد میشه شماره System Call رو بررسی میکنه تا بفهمه برنامه دقیقا چه درخواستی داشته
مثلا برنامه ممکنه درخواست خوندن اطلاعات از یه فایل رو داشته باشه
پس Kernel باید بفهمه این درخواست مربوط به کدوم System Call هست
Kernel
از کجا میفهمه چه System Call ای درخواست شده
اینجا یکی از چیزهای مهم System Call Number هست
هر System Call توی Linux یه شماره مشخص داره
البته این شماره ها به معماری و ABI وابسته هستن
یعنی نباید فکر کنیم شماره یه System Call روی همه سیستم ها یکیه
به صورت مفهومی:
System Call Number
+
Arguments
│
▼
syscall
│
▼
Kernel
Kernel
با استفاده از این اطلاعات مسیر مربوط به اون درخواست رو پیدا میکنه
Kernel
هنگام ورود چه کار میکنه
وقتی CPU وارد Kernel میشه Kernel باید درخواست برنامه رو بررسی کنه
به صورت ساده
User Code
│
▼
syscall
│
▼
Kernel Entry
│
▼
بررسی درخواست
│
▼
اجرای عملیات
│
▼
Return Value
│
▼
User Mode
Kernel
ممکنه چیزهایی مثل مجوز دسترسی و معتبر بودن بعضی اطلاعات رو بررسی کنه
مثلاً برنامه User Space نباید بتونه هر آدرس حافظه ای که دلش خواست رو بدون کنترل دستکاری کنه
سیستم عامل اینجا نقش یک مراقب رو بازی میکنه چون اگر هر برنامه ای میتونست هر کاری با منابع سیستم بکنه عملا سیستم عامل تبدیل میشد به چیزی که کاربردی نداره
Trap
فقط برای System Call نیست
اینجا یه نکته مهم داریم
وقتی میگیم Trap نباید فکر کنیم فقط برای System Call استفاده میشه
در سیستم عامل و معماری CPU انتقال کنترل به Kernel میتونه به دلایل مختلف اتفاق بیفته
مثلا:
System Call
Exception
بعضی رویدادهای سخت افزاری از مسیر Interrupt البته این مفاهیم دقیقا یکی نیستن و باید جدا از هم بررسی بشن
Exception چیه
Exception
وقتی اتفاق میفته که CPU موقع اجرای یه دستور با یه شرایط خاص روبرو بشه
مثلا:
برنامه
│
▼
اجرای دستور
│
▼
شرایط خاص
│
▼
Exception
│
▼
Kernel Handler
یکی از مثال های معروفش
Page Fault
فرض کنید برنامه به یه صفحه حافظه دسترسی پیدا کنه ولی اون صفحه در اون لحظه شرایط لازم برای دسترسی رو نداشته باشه
CPU
یه Page Fault ایجاد میکنه
بعد Kernel وارد ماجرا میشه و بررسی میکنه باید چه کاری انجام بشه
یه نکته مهم:
Page Fault
لزوما به معنی Crash نیست
ممکنه سیستم عامل بتونه اون رو کاملا عادی مدیریت کنه
مثلا صفحه موردنظر رو آماده کنه و برنامه دوباره ادامه بده
Interrupt
معمولا از طرف سخت افزار یا کنترل کننده سخت افزار ایجاد میشه مثلا یه دستگاه میخواد CPU رو از یه اتفاق باخبر کنه
به صورت ساده:
Hardware
│
▼
Interrupt
│
▼
CPU
│
▼
Kernel
مثلا یه عملیات I O تموم شده
سخت افزار میتونه با Interrupt به CPU خبر بده که عملیات تموم شده
بعد Kernel میتونه این اتفاق رو مدیریت کنه
تفاوت این سه مفهوم:
System Call
درخواست برنامه برای گرفتن یه سرویس از سیستم عامل
Exception
اتفاقی که در نتیجه اجرای دستور یا شرایط مربوط به CPU رخ میده
Interrupt
اعلام یه رویداد که معمولا از طرف سخت افزار میاد
Trap
هم یه اصطلاح معماری و سیستم عاملیه که برای انتقال کنترل به یه Handler استفاده میشه بسته به منبعی که میخونید ممکنه تعریف دقیق این اصطلاح کمی فرق داشته باشه پس بهتره فقط یه تعریف حفظ نکنید مهم اینه بفهمی چه اتفاقی باعث میشه CPU مسیر اجرا رو عوض کنه و وارد Handler مربوطه بشه
چرا این موضوع برای Reverse Engineering مهمه
اینجا موضوع برای ما جالب تر میشه
وقتی دارید یه باینری رو Reverse میکنید
ممکنه به دستورهایی مثل
syscall
برسی
یا رفتارهایی مربوط به Exception و Page Fault ببینید
اگر فقط اسمبلی رو حفظ کرده باشید میبینید
syscall
ولی اگر OS رو فهمیده باشید میفهمید پشت این دستور چه اتفاقی افتاده
User Code
↓
System Call
↓
CPU / Kernel Entry
↓
Kernel Handler
↓
Operation
↓
Return
یعنی فرق زیادی هست بین اینکه فقط یه دستور رو ببینید و اینکه بفهمید پشت اون دستور چه اتفاقی توی سیستم عامل میوفته
این مفاهیم پایه خیلی از چیزهاییه که بعدا توی OS و Reverse Engineering باهاشون سروکار داریم
@reverseengine
اعلام یه رویداد که معمولا از طرف سخت افزار میاد
Trap
هم یه اصطلاح معماری و سیستم عاملیه که برای انتقال کنترل به یه Handler استفاده میشه بسته به منبعی که میخونید ممکنه تعریف دقیق این اصطلاح کمی فرق داشته باشه پس بهتره فقط یه تعریف حفظ نکنید مهم اینه بفهمی چه اتفاقی باعث میشه CPU مسیر اجرا رو عوض کنه و وارد Handler مربوطه بشه
چرا این موضوع برای Reverse Engineering مهمه
اینجا موضوع برای ما جالب تر میشه
وقتی دارید یه باینری رو Reverse میکنید
ممکنه به دستورهایی مثل
syscall
برسی
یا رفتارهایی مربوط به Exception و Page Fault ببینید
اگر فقط اسمبلی رو حفظ کرده باشید میبینید
syscall
ولی اگر OS رو فهمیده باشید میفهمید پشت این دستور چه اتفاقی افتاده
User Code
↓
System Call
↓
CPU / Kernel Entry
↓
Kernel Handler
↓
Operation
↓
Return
یعنی فرق زیادی هست بین اینکه فقط یه دستور رو ببینید و اینکه بفهمید پشت اون دستور چه اتفاقی توی سیستم عامل میوفته
این مفاهیم پایه خیلی از چیزهاییه که بعدا توی OS و Reverse Engineering باهاشون سروکار داریم
@reverseengine