ReverseEngineering
Copy on Write یا COW سیستم عامل چطور بدون کپی کردن همه چیز fork میسازه توی پست های قبلی گفتیم وقتی fork اجرا میشه یک Child Process ساخته میشه در نگاه اول شاید فکر کنیم سیستم عامل این کارو میکنه Parent Process │ ▼ کپی کامل حافظه │ ▼ Child Process یعنی کل…
As a result, less RAM and CPU are consumed
To put it very simply in a sentence
Copy on Write
means that the operating system does not copy anything until it is forced to, and this is one of the good examples that shows how interdependent Process and Memory are
@reverseengine
To put it very simply in a sentence
Copy on Write
means that the operating system does not copy anything until it is forced to, and this is one of the good examples that shows how interdependent Process and Memory are
@reverseengine
❤1
بخش بیست و هشتم بافر اورفلو
AFL++ و Instrumentation
چطور Fuzzer میفهمه داخل برنامه چه خبره
توی بخش قبل با libFuzzer و Coverage Guided Fuzzing آشنا شدیم
حالا میریم سراغ AFL++
AFL++
یکی از معروف ترین ابزارهای Fuzzing هست که مخصوصا برای تست برنامه های C و ++C و Binary ها خیلی استفاده میشه
میخوایم بفهمیم AFL++ چطور متوجه میشه یه ورودی برنامه رو وارد یه مسیر جدید کرده
AFL++
فقط ورودی تصادفی نمیفرسته
فرض کن یه برنامه داریم که مسیرهای مختلفی داره
Input
│
▼
┌─────────┐
│ Check A │
└────┬────┘
│
┌────┴────┐
▼ ▼
Path 1 Path 2
│
▼
┌─────────┐
│ Check B │
└────┬────┘
│
┌────┴────┐
▼ ▼
Path 3 Path 4
اگر AFL++ یه ورودی بفرسته و برنامه وارد Path 1 بشه
اون مسیر ثبت میشه
بعد AFL++ ورودی رو تغییر میده و دوباره امتحانش میکنه
اگر ورودی جدید باعث بشه برنامه وارد Path 2 بشه
AFL++
متوجه میشه یه مسیر جدید پیدا شده
همین ورودی جدید ارزشمند میشه و نگهش میداره
Instrumentation
یعنی چی
اینجا میرسیم به بخش مهم
Instrumentation
یعنی اضافه کردن یه سری مکانیزم به برنامه تا بتونیم بفهمیم موقع اجرا چه اتفاقی داخلش افتاده
مثلا AFL++ میتونه با Instrumentation اطلاعاتی درباره مسیر اجرای برنامه جمع کنه
به زبون ساده:
قبل از Instrumentation
Input
│
▼
Program
│
▼
Result
بعد از Instrumentation
Input
│
▼
Program
│
▼
Path Tracking
│
▼
Result
یعنی برنامه همچنان کار خودش رو انجام میده ولی حالا یه نفر هم داره بررسی میکنه برنامه از چه مسیرهایی رد شده
اسم این کار رو گذاشتن Instrumentation تا قضیه یکم علمی تر به نظر بیاد
یه مثال ساده:
فرض کنید این برنامه رو داریم
C
#include <stdio.h>
#include <string.h>
int main(void)
{
char input[32];
if (!fgets(input, sizeof(input), stdin))
return 0;
if (strncmp(input, "HELLO", 5) == 0)
{
puts("First check passed");
if (input[5] == '!')
{
puts("Second check passed");
}
}
return 0;
}
AFL++
اینجا دنبال چیه
اول ممکنه ورودی های ساده رو امتحان کنه
AAAA
این ورودی فقط یه مسیر معمولی رو اجرا میکنه
بعد AFL++ شروع میکنه ورودی رو تغییر دادن
اگر به این برسه
HELLO
یه شاخه جدید اجرا میشه
پس AFL++ متوجه میشه این ورودی جالبه
بعد همین ورودی رو بیشتر تغییر میده
مثلا:
HELLO!
حالا شرط دوم هم رد شده
پس یه مسیر جدید دیگه پیدا شده
به صورت ساده میتونیم این روند رو اینطوری ببینیم
AAAA
│
▼
مسیر معمولی
│
▼
AFL++ تغییر میده
│
▼
HELLO
│
▼
مسیر جدید
│
▼
AFL++ دوباره تغییر میده
│
▼
HELLO!
│
▼
مسیر جدیدتر
Seed یا Corpus
یعنی چی
AFL++
معمولا با یه سری ورودی اولیه شروع میکنه به این ورودی های اولیه میگیم Seed
مثلا یه فایل ساده
project
├── input
│ └── seed1
└── output
داخل seed1 میتونه فقط این باشه
AAAA
بعد AFL++ همین ورودی رو بارها تغییر میده
حذف میکنه
اضافه میکنه
بایت ها رو تغییر میده
و بررسی میکنه کدوم تغییر باعث شده یه مسیر جدید پیدا بشه
ورودی هایی که ارزش داشته باشن میتونن در Corpus قرار بگیرن
Corpus
یعنی مجموعه ای از ورودی های جالب که Fuzzer میتونه از اونها برای ادامه Fuzzing استفاده کنه
پس میتونیم این روند رو اینطوری تصور کنیم
Seed
│
▼
Mutation
│
├── Input A ──► مسیر جدید نیست
│
├── Input B ──► مسیر جدید
│ │
│ ▼
│ Corpus
│
└── Input C ──► مسیر جدیدتر
│
▼
Corpus
یک مثال از ساختار فایل ها
فرض کنید این پوشه رو داریم
project
├── input
│ └── seed1
└── output
داخل seed1 میتونه فقط این باشه
AAAA
بعد AFL++ با همین ورودی شروع میکنه و به مرور ورودی های جدید تولید میکنه
یک نکته مهم
وقتی AFL++ یه Crash پیدا میکنه
کار تموم نشده تازه قسمت جذاب ماجرا شروع میشه
باید بفهمیم
چه ورودی باعث Crash شده
Crash
دقیقا کجا اتفاق افتاده
چه تابعی درگیر بوده
آیا مشکل واقعا یه Memory Bug هست یا نه
❤1
برای این مرحله معمولا میریم سراغ ابزارهایی مثل
پس مسیر کلی کار میتونه این شکلی باشه
Seed Input
│
▼
AFL++
│
▼
New Paths
│
▼
Interesting Input
│
▼
Crash
│
▼
GDB
│
▼
Root Cause Analysis
یعنی Fuzzer یه ورودی جالب پیدا میکنه
بعد ممکنه همین ورودی باعث Crash بشه
از اینجا به بعد ما وارد ماجرا میشیم و باید بفهمیم علت واقعی مشکل چی بوده
AFL++
با فرستادن ورودی های مختلف فقط دنبال Crash نیست مسیرهای جدید برنامه رو هم دنبال میکنه
Instrumentation
کمک میکنه بفهمیم یه ورودی چه بخش هایی از برنامه رو اجرا کرده
هر مسیر جدید میتونه یه راه جدید برای رسیدن به یه باگ پنهان باشه
و وقتی Crash پیدا شد
کار Fuzzer تموم میشه
و نوبت ما میرسه که علت واقعی مشکل رو پیدا کنیم
تمرین:
همین برنامه رو با یه شرط سوم تغییر بدید
مثلا بعد از !HELLO یه شرط جدید اضافه کنید بعد روی کاغذ فکر کنید AFL++ چه ورودی هایی ممکنه به ترتیب پیدا کنه تا هر سه مسیر برنامه رو پوشش بده
@reverseengine
GDB
Ghidra
IDA
ASan
پس مسیر کلی کار میتونه این شکلی باشه
Seed Input
│
▼
AFL++
│
▼
New Paths
│
▼
Interesting Input
│
▼
Crash
│
▼
GDB
│
▼
Root Cause Analysis
یعنی Fuzzer یه ورودی جالب پیدا میکنه
بعد ممکنه همین ورودی باعث Crash بشه
از اینجا به بعد ما وارد ماجرا میشیم و باید بفهمیم علت واقعی مشکل چی بوده
AFL++
با فرستادن ورودی های مختلف فقط دنبال Crash نیست مسیرهای جدید برنامه رو هم دنبال میکنه
Instrumentation
کمک میکنه بفهمیم یه ورودی چه بخش هایی از برنامه رو اجرا کرده
هر مسیر جدید میتونه یه راه جدید برای رسیدن به یه باگ پنهان باشه
و وقتی Crash پیدا شد
کار Fuzzer تموم میشه
و نوبت ما میرسه که علت واقعی مشکل رو پیدا کنیم
تمرین:
همین برنامه رو با یه شرط سوم تغییر بدید
مثلا بعد از !HELLO یه شرط جدید اضافه کنید بعد روی کاغذ فکر کنید AFL++ چه ورودی هایی ممکنه به ترتیب پیدا کنه تا هر سه مسیر برنامه رو پوشش بده
@reverseengine
ReverseEngineering
بخش بیست و هشتم بافر اورفلو AFL++ و Instrumentation چطور Fuzzer میفهمه داخل برنامه چه خبره توی بخش قبل با libFuzzer و Coverage Guided Fuzzing آشنا شدیم حالا میریم سراغ AFL++ AFL++ یکی از معروف ترین ابزارهای Fuzzing هست که مخصوصا برای تست برنامه های C…
Part 28 Buffer Overflow
AFL++ and Instrumentation
How does a Fuzzer understand what is going on inside a program
In the previous section, we learned about libFuzzer and Coverage Guided Fuzzing
Now let's move on to AFL++
AFL++
is one of the most famous fuzzing tools, which is especially used for testing C, C++, and binary programs. We want to understand how AFL++ understands that a program input has entered a new path
AFL++
does not just send random input
Suppose we have a program that has different paths
Input
│
▼
┌────────┐
│ Check A │
└────┬────┘
│
┌─────┴───┐
▼ ▼
Path 1 Path 2
│
▼
┌───────┐
│ Check B │
└──────┬───┘
│
┌──────┐
│ ┌──────┐
▼ ▼
Path 3 Path 4
If AFL++ sends an input and the program enters Path 1
That path is recorded
Then AFL++ changes the input and tries it again
If the new input causes the program to enter Path 2
AFL++
notices that a new path has been found
This new input becomes valuable and keeps it
What does Instrumentation mean
Here we come to the important part
Instrumentation
means adding a series of mechanisms to the program so that we can understand what happened inside it during execution
For example, AFL++ can collect information about the path of the program execution with Instrumentation
In simple terms:
Before Instrumentation
Input
│
▼
Program
│
▼
Result
After Instrumentation
Input
│
▼
Program
│
▼
Path Tracking
│
▼
Result
That means the program is still doing its job, but now someone is also checking which paths the program has taken
I named this task Instrumentation to make it seem a little more scientific
An example Simple:
Suppose we have this program
C
#include <stdio.h>
#include <string.h>
int main(void)
{
char input[32];
if (!fgets(input, sizeof(input), stdin))
return 0;
if (strncmp(input, "HELLO", 5) == 0)
{
puts("First check passed");
if (input[5] == '!')
{
puts("Second check passed");
}
}
return 0;
}
What is AFL++ looking for here?
First it might try simple inputs
AAAA
This input just executes a normal path
Then AFL++ starts modifying the input
If it reaches
HELLO
a new branch is executed
So AFL++ realizes that this input is interesting
Then it modifies this input further
For example:
HELLO!
Now the second condition is also met
So another new path has been found
Simply we can see this process like this
AAAA
│
▼
Normal path
│
▼
AFL++ changes
│
▼
HELLO
│
▼
New path
│
▼
AFL++ changes again
│
▼
HELLO!
│
▼
Newer path
Seed or Corpus
What does it mean
AFL++
Usually starts with a series of initial inputs, we call these initial inputs Seed
For example, a simple file
project
├── input
│ └── seed1
└── output
Inside seed1 can be just this
AAAA
Then AFL++ changes this input repeatedly
deletes
adds
changes bytes
and checks which change caused a new path to be found
Inputs that are worth it can be placed in Corpus
Corpus
means a set of interesting inputs that the Fuzzer can use to continue Fuzzing
So we can imagine this process like this
Seed
│
▼
Mutation
│
├── Input A ──► Not a new path
│
├── Input B ──► New path
│ │
│ ▼
│ Corpus
│
└── Input C ──► Path Newer
│
▼
Corpus
An example of file structure
Suppose we have this folder
project
├── input
│ └── seed1
└── output
Inside seed1 can be just this
AAAA
Then AFL++ starts with this input and gradually generates new inputs
An important point
When AFL++ finds a Crash
The work is not over yet, the interesting part begins
We need to find out
What input caused the Crash
Where exactly did the Crash happen
What function was involved
Is the problem really a Memory Bug or not
For this step, we usually go to tools like
So the general path of the work can be like this
Seed Input
│
▼
AFL++
│
▼
New Paths
│
▼
Interesting Input
│
▼
Crash
│
▼
GDB
│
▼
Root Cause Analysis
That is, the Fuzzer finds an interesting input
Then this input may cause a Crash
From here on, we get into the story and we need to find out what the real cause of the problem was
AFL++
By sending different inputs, it does not only look for Crash, it also follows new paths of the program
Instrumentation
helps us understand what parts of the program an input has executed
Each new path can be a new way to reach a hidden bug
And when a Crash is found
The Fuzzer's work is finished
And it's our turn to find the real cause of the problem
Exercise:
Change the same program with a third condition
For example, add a new condition after !HELLO On paper, think about what inputs AFL++ might find in order to cover all three paths of the program
@reverseengine
GDB
Ghidra
IDA
ASan
So the general path of the work can be like this
Seed Input
│
▼
AFL++
│
▼
New Paths
│
▼
Interesting Input
│
▼
Crash
│
▼
GDB
│
▼
Root Cause Analysis
That is, the Fuzzer finds an interesting input
Then this input may cause a Crash
From here on, we get into the story and we need to find out what the real cause of the problem was
AFL++
By sending different inputs, it does not only look for Crash, it also follows new paths of the program
Instrumentation
helps us understand what parts of the program an input has executed
Each new path can be a new way to reach a hidden bug
And when a Crash is found
The Fuzzer's work is finished
And it's our turn to find the real cause of the problem
Exercise:
Change the same program with a third condition
For example, add a new condition after !HELLO On paper, think about what inputs AFL++ might find in order to cover all three paths of the program
@reverseengine
From Chrome renderer code exec to kernel with MSG_OOB
https://projectzero.google/2025/08/from-chrome-renderer-code-exec-to-kernel.html
@reverseengine
https://projectzero.google/2025/08/from-chrome-renderer-code-exec-to-kernel.html
@reverseengine
projectzero.google
From Chrome renderer code exec to kernel with MSG_OOB
IntroductionIn early June, I was reviewing a new Linux kernel feature when I learned about the MS...
State divergence enables unauthorized access
https://blog.trailofbits.com/2026/08/25/state-divergence-enables-unauthorized-access
@reverseengine
https://blog.trailofbits.com/2026/08/25/state-divergence-enables-unauthorized-access
@reverseengine
The Trail of Bits Blog
State divergence enables unauthorized access
We found and reported a bug in Provenance Blockchain, a public proof-of-stake chain built on Cosmos SDK, that lets any user grant themselves admin control over marker accounts without holding a single token.
From P-Code to GNN: extract binary code semantics
https://blog.quarkslab.com/from-p-code-to-gnn-extract-binary-code-semantics.html
@reverseengine
https://blog.quarkslab.com/from-p-code-to-gnn-extract-binary-code-semantics.html
@reverseengine
Quarkslab
From P-Code to GNN: extract binary code semantics - Quarkslab's blog
pcode_graph is a Python library, published by Quarkslab, suitable to build semantic graphs from binary code. We present how to use it to detect function similarities in binaries.
Kernel Shield: Reversing the NSecKrnl (Malops.io) Rootkit Driver
https://medium.com/@sushant.m.mane/kernel-shield-reversing-the-nseckrnl-malops-io-rootkit-driver-fb4af6bcb19c
https://medium.com/@sushant.m.mane/kernel-shield-reversing-the-nseckrnl-malops-io-rootkit-driver-fb4af6bcb19c
Medium
Kernel Shield: Reversing the NSecKrnl (Malops.io) Rootkit Driver
Challenge URL: https://malops.io/challenges/kernel-shield
Reverse Engineering a 0day used Against EDRs
https://medium.com/@jehadbudagga/reverse-engineering-a-0day-used-against-crowdstrike-edr-a5ea1fbe3fd4
https://medium.com/@jehadbudagga/reverse-engineering-a-0day-used-against-crowdstrike-edr-a5ea1fbe3fd4
Medium
Reverse Engineering a 0day used Against EDRs
Hello again…
😁3
Forwarded from DarkBit
📌 مقاله جدید منتشر شد
⚔️ The Art of DLL Hijacking
در این مقاله به بررسی عمیق مکانیزمهای DLL Hijacking در ویندوز پرداختهام؛ از مبانی نحوه بارگذاری DLLها و DLL Search Order Hijacking تا تکنیکهای پیشرفتهتر مانند:
◾️ DLL Search Order Hijacking
◾️ DLL Substitution (Replacement)
◾️ DLL Side-Loading
◾️ DLL Proxying
◾️ Phantom DLL Hijacking
در این مقاله بررسی میکنیم که Windows Loader چگونه DLLها را resolve و بارگذاری میکند، چگونه شرایط مناسب برای DLL Hijacking شناسایی میشوند، و چرا برخی برنامههای معتبر میتوانند به بخشی از زنجیره بارگذاری تبدیل شوند.
همچنین یک رویکرد عملی برای تحلیل و شناسایی Phantom DLLها ارائه شده است.
مطالعه مقاله:
https://darkbitx.github.io/posts/the-art-of-dll-hijacking/
✍️ نویسنده: دارکبیت | DarkBit
💬 Forum
📣 DarkBit
#CyberSecurity #WindowsInternals #Persistence #Maldev #RedTeam
⚔️ The Art of DLL Hijacking
در این مقاله به بررسی عمیق مکانیزمهای DLL Hijacking در ویندوز پرداختهام؛ از مبانی نحوه بارگذاری DLLها و DLL Search Order Hijacking تا تکنیکهای پیشرفتهتر مانند:
◾️ DLL Search Order Hijacking
◾️ DLL Substitution (Replacement)
◾️ DLL Side-Loading
◾️ DLL Proxying
◾️ Phantom DLL Hijacking
در این مقاله بررسی میکنیم که Windows Loader چگونه DLLها را resolve و بارگذاری میکند، چگونه شرایط مناسب برای DLL Hijacking شناسایی میشوند، و چرا برخی برنامههای معتبر میتوانند به بخشی از زنجیره بارگذاری تبدیل شوند.
همچنین یک رویکرد عملی برای تحلیل و شناسایی Phantom DLLها ارائه شده است.
مطالعه مقاله:
https://darkbitx.github.io/posts/the-art-of-dll-hijacking/
✍️ نویسنده: دارکبیت | DarkBit
💬 Forum
📣 DarkBit
#CyberSecurity #WindowsInternals #Persistence #Maldev #RedTeam
DarkBit
The Art of DLL Hijacking
An in-depth exploration of DLL Hijacking and related techniques, covering how DLL loading works, how vulnerable applications can be identified, and the differences between classic DLL Hijacking, DLL Sideloading, DLL Proxying, and Phantom DLL Hijacking.
DarkBit
📌 مقاله جدید منتشر شد ⚔️ The Art of DLL Hijacking در این مقاله به بررسی عمیق مکانیزمهای DLL Hijacking در ویندوز پرداختهام؛ از مبانی نحوه بارگذاری DLLها و DLL Search Order Hijacking تا تکنیکهای پیشرفتهتر مانند: ◾️ DLL Search Order Hijacking ◾️ DLL…
📌 New article released
⚔️ The Art of DLL Hijacking
In this article, I have taken an in-depth look at the mechanisms of DLL Hijacking in Windows; from the basics of how DLLs are loaded and DLL Search Order Hijacking to more advanced techniques such as:
◾️ DLL Search Order Hijacking
◾️ DLL Substitution (Replacement)
◾️ DLL Side-Loading
◾️ DLL Proxying
◾️ Phantom DLL Hijacking
In this article, we will examine how the Windows Loader resolves and loads DLLs, how suitable conditions for DLL Hijacking are identified, and why some legitimate programs can become part of the loading chain.
A practical approach to analyzing and identifying Phantom DLLs is also presented.
Read the article:
https://darkbitx.github.io/posts/the-art-of-dll-hijacking
✍️ Author: Darkbit | DarkBit
💬 Forum
📣 DarkBit
#CyberSecurity #WindowsInternals #Persistence #Maldev #RedTeam
⚔️ The Art of DLL Hijacking
In this article, I have taken an in-depth look at the mechanisms of DLL Hijacking in Windows; from the basics of how DLLs are loaded and DLL Search Order Hijacking to more advanced techniques such as:
◾️ DLL Search Order Hijacking
◾️ DLL Substitution (Replacement)
◾️ DLL Side-Loading
◾️ DLL Proxying
◾️ Phantom DLL Hijacking
In this article, we will examine how the Windows Loader resolves and loads DLLs, how suitable conditions for DLL Hijacking are identified, and why some legitimate programs can become part of the loading chain.
A practical approach to analyzing and identifying Phantom DLLs is also presented.
Read the article:
https://darkbitx.github.io/posts/the-art-of-dll-hijacking
✍️ Author: Darkbit | DarkBit
💬 Forum
📣 DarkBit
#CyberSecurity #WindowsInternals #Persistence #Maldev #RedTeam
DarkBit
The Art of DLL Hijacking
An in-depth exploration of DLL Hijacking and related techniques, covering how DLL loading works, how vulnerable applications can be identified, and the differences between classic DLL Hijacking, DLL Sideloading, DLL Proxying, and Phantom DLL Hijacking.
❤3
exit
وقتی یک Process کارش تموم میشه چه اتفاقی میوفته
تا اینجا با fork و exec و wait و حتی Zombie Process آشنا شدیم
حالا فرض کنید یه Process داریم که کارش رو انجام داده و دیگه کاری برای انجام دادن نداره
مثلا یه برنامه خیلی ساده داریم:
وقتی main تموم میشه برنامه هم باید به سیستم عامل بفهمونه که
کار من تموم شده
اینجاست که مفهوم exit وارد داستان میشه
exit دقیقا چیکار میکنه
خیلی ساده بخوایم بگیم
exit
به سیستم عامل میگه این Process دیگه کاری نداره و میخواد اجرای خودش رو تموم کنه
مثلا:
اون عدد 0 معمولا یعنی برنامه با موفقیت تموم شده
یعنی
exit(0)
↓
اجرای موفق
ولی اگه مقدار غیر صفر بدیم معمولا یعنی یه وضعیت دیگه اتفاق افتاده
مثلا یه خطا
البته معنی دقیق اون عدد رو خود برنامه مشخص میکنه
پس قرار نیست Kernel بفهمه عدد 1 دقیقا یعنی چه خطایی
این عدد بیشتر یه اطلاعاتیه که Process به Parent میده
بعد از exit چی میشه
اینجا یه نکته مهم داریم
وقتی Process به exit میرسه دیگه قرار نیست دستورهای معمول برنامه رو ادامه بده
یعنی اجرای برنامه تموم شده
ولی Kernel هنوز باید چندتا کار انجام بده
مثلا منابعی که Process استفاده میکرد باید مدیریت بشن
مثل:
منابع مربوط به I O
و اطلاعات مربوط به خود Process
ولی یه نکته خیلی مهم
همه چیز همون لحظه کامل تموم نمیشه
این همون جاییه که Zombie Process دوباره وارد داستان میشه
فرض کنید این وضعیت رو داریم
Parent
│
└──── Child
│
▼
exit()
│
▼
اجرای Child تموم شد
│
▼
Zombie
Child دیگه اجرا نمیشه
CPU
هم دیگه بهش برای اجرای برنامه CPU Time نمیده
ولی یه مقدار اطلاعات مربوط به پایانش هنوز توسط Kernel نگه داشته میشه
چرا
چون Parent باید بتونه بفهمه Child چطوری تموم شده
مثلا Exit Status اون چی بوده
بعد Parent میاد و wait میکنه
Child
│
▼
exit()
│
▼
Zombie
│
▼
Parent calls wait()
│
▼
Exit Status دریافت میشه
│
▼
Reaping
│
▼
Zombie از بین میره
به این مرحله آخر میگیم
Reaping
یعنی Parent وضعیت پایان Child رو میگیره و Kernel دیگه اطلاعات باقی مونده مربوط به اون Child رو لازم نداره
پس یه نکته خیلی مهم رو یادتون باشه
Termination با Reaping یکی نیست
Termination
یعنی Process دیگه اجرا نمیشه
Reaping
یعنی Parent وضعیت پایان Process رو دریافت میکنه و اطلاعات باقی مونده مربوط به اون Process از ساختارهای مدیریت Process جمع میشه
این تفاوت دقیقاً دلیل وجود Zombie Process هست
Exit Status چیه
وقتی یه Process تموم میشه میتونه یه مقدار به عنوان وضعیت پایان خودش داشته باشه
مثلا
یعنی
یا
یعنی
توی Unix و Linux معمولا 0 یعنی همه چیز خوب تموم شده
و مقدارهای غیر صفر معمولا برای وضعیتهای دیگه استفاده میشن
Parent
میتونه این مقدار رو با wait یا waitpid دریافت کنه
حالا کل داستان رو کنار هم بذاریم
تا الان چندتا تکه مهم از چرخه Process رو یاد گرفتیم
Parent
│
fork()
│
▼
Child
│
exec()
│
▼
New Program
│
exit()
│
▼
Zombie
│
wait()
│
▼
Reaped
یعنی اول Parent یه Child میسازه
بعد Child میتونه با exec یه Program جدید رو اجرا کنه
Program کارش رو انجام میده
بعد exit میکنه
Process دیگه اجرا نمیشه
ممکنه برای مدت کوتاهی Zombie بشه
بعد Parent با wait وضعیتش رو میگیره
و در نهایت Child Reap میشه
حالا یه سوال مهم
آیا exit یعنی Process همون لحظه از همه جا پاک میشه؟
نه
اینجا یکی از اون جاهاییه که سیستم عامل یه کم از چیزی که در نگاه اول به نظر میاد پیچیده تره
وقتی Process terminate میشه
دیگه اجرا نمیشه
ولی Kernel ممکنه یه مقدار اطلاعات محدود از اون رو نگه داره
مهمترین دلیلش هم اینه که Parent باید بتونه وضعیت پایان Child رو بفهمه
پس این دوتا رو قاطی نکنید
Termination
↓
Process دیگه اجرا نمیشه
Reaping
↓
Parent
وضعیت Process رو میگیره
↓
اطلاعات باقی مونده جمع میشه
یه نکته دیگه درباره منابع
وقتی Process تموم میشه Kernel منابعی که دیگه لازم نیستن رو Cleanup میکنه
وقتی یک Process کارش تموم میشه چه اتفاقی میوفته
تا اینجا با fork و exec و wait و حتی Zombie Process آشنا شدیم
حالا فرض کنید یه Process داریم که کارش رو انجام داده و دیگه کاری برای انجام دادن نداره
مثلا یه برنامه خیلی ساده داریم:
int main() {
printf("Hello");
return 0;
}
وقتی main تموم میشه برنامه هم باید به سیستم عامل بفهمونه که
کار من تموم شده
اینجاست که مفهوم exit وارد داستان میشه
exit دقیقا چیکار میکنه
خیلی ساده بخوایم بگیم
exit
به سیستم عامل میگه این Process دیگه کاری نداره و میخواد اجرای خودش رو تموم کنه
مثلا:
exit(0);
اون عدد 0 معمولا یعنی برنامه با موفقیت تموم شده
یعنی
exit(0)
↓
اجرای موفق
ولی اگه مقدار غیر صفر بدیم معمولا یعنی یه وضعیت دیگه اتفاق افتاده
مثلا یه خطا
البته معنی دقیق اون عدد رو خود برنامه مشخص میکنه
پس قرار نیست Kernel بفهمه عدد 1 دقیقا یعنی چه خطایی
این عدد بیشتر یه اطلاعاتیه که Process به Parent میده
بعد از exit چی میشه
اینجا یه نکته مهم داریم
وقتی Process به exit میرسه دیگه قرار نیست دستورهای معمول برنامه رو ادامه بده
یعنی اجرای برنامه تموم شده
ولی Kernel هنوز باید چندتا کار انجام بده
مثلا منابعی که Process استفاده میکرد باید مدیریت بشن
مثل:
Memory
File Descriptor
منابع مربوط به I O
و اطلاعات مربوط به خود Process
ولی یه نکته خیلی مهم
همه چیز همون لحظه کامل تموم نمیشه
این همون جاییه که Zombie Process دوباره وارد داستان میشه
فرض کنید این وضعیت رو داریم
Parent
│
└──── Child
│
▼
exit()
│
▼
اجرای Child تموم شد
│
▼
Zombie
Child دیگه اجرا نمیشه
CPU
هم دیگه بهش برای اجرای برنامه CPU Time نمیده
ولی یه مقدار اطلاعات مربوط به پایانش هنوز توسط Kernel نگه داشته میشه
چرا
چون Parent باید بتونه بفهمه Child چطوری تموم شده
مثلا Exit Status اون چی بوده
بعد Parent میاد و wait میکنه
Child
│
▼
exit()
│
▼
Zombie
│
▼
Parent calls wait()
│
▼
Exit Status دریافت میشه
│
▼
Reaping
│
▼
Zombie از بین میره
به این مرحله آخر میگیم
Reaping
یعنی Parent وضعیت پایان Child رو میگیره و Kernel دیگه اطلاعات باقی مونده مربوط به اون Child رو لازم نداره
پس یه نکته خیلی مهم رو یادتون باشه
Termination با Reaping یکی نیست
Termination
یعنی Process دیگه اجرا نمیشه
Reaping
یعنی Parent وضعیت پایان Process رو دریافت میکنه و اطلاعات باقی مونده مربوط به اون Process از ساختارهای مدیریت Process جمع میشه
این تفاوت دقیقاً دلیل وجود Zombie Process هست
Exit Status چیه
وقتی یه Process تموم میشه میتونه یه مقدار به عنوان وضعیت پایان خودش داشته باشه
مثلا
exit(0);
یعنی
Exit Status = 0
یا
exit(1);
یعنی
Exit Status = 1
توی Unix و Linux معمولا 0 یعنی همه چیز خوب تموم شده
و مقدارهای غیر صفر معمولا برای وضعیتهای دیگه استفاده میشن
Parent
میتونه این مقدار رو با wait یا waitpid دریافت کنه
حالا کل داستان رو کنار هم بذاریم
تا الان چندتا تکه مهم از چرخه Process رو یاد گرفتیم
Parent
│
fork()
│
▼
Child
│
exec()
│
▼
New Program
│
exit()
│
▼
Zombie
│
wait()
│
▼
Reaped
یعنی اول Parent یه Child میسازه
بعد Child میتونه با exec یه Program جدید رو اجرا کنه
Program کارش رو انجام میده
بعد exit میکنه
Process دیگه اجرا نمیشه
ممکنه برای مدت کوتاهی Zombie بشه
بعد Parent با wait وضعیتش رو میگیره
و در نهایت Child Reap میشه
حالا یه سوال مهم
آیا exit یعنی Process همون لحظه از همه جا پاک میشه؟
نه
اینجا یکی از اون جاهاییه که سیستم عامل یه کم از چیزی که در نگاه اول به نظر میاد پیچیده تره
وقتی Process terminate میشه
دیگه اجرا نمیشه
ولی Kernel ممکنه یه مقدار اطلاعات محدود از اون رو نگه داره
مهمترین دلیلش هم اینه که Parent باید بتونه وضعیت پایان Child رو بفهمه
پس این دوتا رو قاطی نکنید
Termination
↓
Process دیگه اجرا نمیشه
Reaping
↓
Parent
وضعیت Process رو میگیره
↓
اطلاعات باقی مونده جمع میشه
یه نکته دیگه درباره منابع
وقتی Process تموم میشه Kernel منابعی که دیگه لازم نیستن رو Cleanup میکنه
مثلا Address Space مربوط به Process دیگه مورد استفاده اون Process نیست
File Descriptor
های باز هم در جریان پایان Process بسته میشن
ولی این به معنی این نیست که خود Resource حتماً همون لحظه نابود میشه
مثلا ممکنه یه File توسط Process دیگه هم باز باشه
پس File Descriptor با خود Resource یکی نیست
این موضوع رو بعدا توی Linux Internals خیلی بیشتر میبینیم
یه نکته جالب درباره return و exit
مثلا اگه بنویسیم:
وقتی main تموم بشه برنامه هم به شکل عادی به پایان میرسه
اما اگه بنویسیم
exit(0);
اینجا مستقیما درخواست پایان برنامه داده شده
برای همین توی برنامه های ساده ممکنه نتیجه هر دو تقریبا یکی به نظر برسه
ولی از نظر مسیر اجرای برنامه دقیقا یکی نیستن
یه تفاوت جالب دیگه هم داریم
exit
با exit_ یکی نیست
توی C تابع exit قبل از پایان برنامه میتونه بعضی Cleanupهای مربوط به User Space رو انجام بده
مثلا Handlerهایی که با atexit ثبت شدن رو اجرا کنه
یا Bufferهای stdio رو Flush کنه
ولی exit_ این Cleanup های User Space رو انجام نمیده و مستقیم تر Process رو terminate میکنه
این تفاوت وقتی وارد بحث fork و Buffering و System Call بشیم خیلی مهم میشه
یه نکته دیگه هم اینه که Process فقط با exit تموم نمیشه
ممکنه Process در اثر Signal هم terminate بشه
پس داستان کلی میتونه این شکلی باشه
Process
│
┌─────────┴─────────┐
│ │
exit() Signal
│ │
└─────────┬─────────┘
▼
Termination
│
▼
Exit Information
│
▼
Zombie
│
wait()
│
▼
Reaped
حالا بریم سمت Reverse Engineering
فرض کنید داری یه Binary رو بررسی میکنید
توی Trace میبینید یه اتفاقاتی شبیه این افتاده
fork()
↓
exec()
↓
Program Execution
↓
exit()
حالا دیگه میتونید یه تصویر ذهنی از چیزی که اتفاق افتاده داشته باشید
Process جدید ساخته شد
↓
Child شروع به اجرا کرد
↓
Program جدید اجرا شد
↓
Program کار خودش رو انجام داد
↓
Process تموم شد
اگه بعدش Parent رو ببینی که wait میکنه
Child
│
▼
exit()
│
▼
Zombie
│
▼
Parent
│
wait()
│
▼
Exit Status
│
▼
Reaping
میتونید بفهمید که Parent احتمالا منتظر نتیجه اجرای Child بوده
اینجاست که چیزایی که تا الان یاد گرفتیم کم کم دارن به درد Reverse Engineering میخورن
حالا یه تصویر کامل تر از چیزایی که تا اینجا یاد گرفتیم داشته باشید
Parent Process
│
fork()
│
▼
Child Process
│
exec()
│
▼
Program
│
▼
Execution
│
exit()
│
▼
Termination
│
▼
Zombie
│
wait()
│
▼
Reaping
exit
خیلی ساده یعنی
Process
میگه کار من تموم شد
بعد از اون
Process
↓
Termination
↓
Exit Status
↓
Parent با wait وضعیت رو میگیره
↓
Reaping
↓
Zombie از بین میره
پس تا اینجا چهار مفهوم خیلی مهم Process API رو داریم
fork()
↓
ساخت Child Process
exec()
↓
اجرای Program جدید داخل Process
wait()
↓
منتظر موندن یا گرفتن وضعیت Child
exit()
↓
تموم کردن اجرای Process
این چهار مفهوم رو خوب یاد بگیرید
چون وقتی بعدا وارد Linux Internals و System Call ها و Reverse Engineering باینری های ELF بشیم دوباره بارها به همین مفاهیم برمیخوریم
فقط یه نکته رو همیشه یادتون باشه
ما اینجا داریم مدل Unix و Linux رو دنبال میکنیم
جزئیات داخلی Process و PCB و Process Descriptor ممکنه توی سیستم عامل های مختلف فرق داشته باشهن
فعلا همین مدل رو مدل اصلیمون قرار میدیم
@reverseengine
File Descriptor
های باز هم در جریان پایان Process بسته میشن
ولی این به معنی این نیست که خود Resource حتماً همون لحظه نابود میشه
مثلا ممکنه یه File توسط Process دیگه هم باز باشه
پس File Descriptor با خود Resource یکی نیست
این موضوع رو بعدا توی Linux Internals خیلی بیشتر میبینیم
یه نکته جالب درباره return و exit
مثلا اگه بنویسیم:
int main() {
return 0;
}
وقتی main تموم بشه برنامه هم به شکل عادی به پایان میرسه
اما اگه بنویسیم
exit(0);
اینجا مستقیما درخواست پایان برنامه داده شده
برای همین توی برنامه های ساده ممکنه نتیجه هر دو تقریبا یکی به نظر برسه
ولی از نظر مسیر اجرای برنامه دقیقا یکی نیستن
یه تفاوت جالب دیگه هم داریم
exit
با exit_ یکی نیست
توی C تابع exit قبل از پایان برنامه میتونه بعضی Cleanupهای مربوط به User Space رو انجام بده
مثلا Handlerهایی که با atexit ثبت شدن رو اجرا کنه
یا Bufferهای stdio رو Flush کنه
ولی exit_ این Cleanup های User Space رو انجام نمیده و مستقیم تر Process رو terminate میکنه
این تفاوت وقتی وارد بحث fork و Buffering و System Call بشیم خیلی مهم میشه
یه نکته دیگه هم اینه که Process فقط با exit تموم نمیشه
ممکنه Process در اثر Signal هم terminate بشه
پس داستان کلی میتونه این شکلی باشه
Process
│
┌─────────┴─────────┐
│ │
exit() Signal
│ │
└─────────┬─────────┘
▼
Termination
│
▼
Exit Information
│
▼
Zombie
│
wait()
│
▼
Reaped
حالا بریم سمت Reverse Engineering
فرض کنید داری یه Binary رو بررسی میکنید
توی Trace میبینید یه اتفاقاتی شبیه این افتاده
fork()
↓
exec()
↓
Program Execution
↓
exit()
حالا دیگه میتونید یه تصویر ذهنی از چیزی که اتفاق افتاده داشته باشید
Process جدید ساخته شد
↓
Child شروع به اجرا کرد
↓
Program جدید اجرا شد
↓
Program کار خودش رو انجام داد
↓
Process تموم شد
اگه بعدش Parent رو ببینی که wait میکنه
Child
│
▼
exit()
│
▼
Zombie
│
▼
Parent
│
wait()
│
▼
Exit Status
│
▼
Reaping
میتونید بفهمید که Parent احتمالا منتظر نتیجه اجرای Child بوده
اینجاست که چیزایی که تا الان یاد گرفتیم کم کم دارن به درد Reverse Engineering میخورن
حالا یه تصویر کامل تر از چیزایی که تا اینجا یاد گرفتیم داشته باشید
Parent Process
│
fork()
│
▼
Child Process
│
exec()
│
▼
Program
│
▼
Execution
│
exit()
│
▼
Termination
│
▼
Zombie
│
wait()
│
▼
Reaping
exit
خیلی ساده یعنی
Process
میگه کار من تموم شد
بعد از اون
Process
↓
Termination
↓
Exit Status
↓
Parent با wait وضعیت رو میگیره
↓
Reaping
↓
Zombie از بین میره
پس تا اینجا چهار مفهوم خیلی مهم Process API رو داریم
fork()
↓
ساخت Child Process
exec()
↓
اجرای Program جدید داخل Process
wait()
↓
منتظر موندن یا گرفتن وضعیت Child
exit()
↓
تموم کردن اجرای Process
این چهار مفهوم رو خوب یاد بگیرید
چون وقتی بعدا وارد Linux Internals و System Call ها و Reverse Engineering باینری های ELF بشیم دوباره بارها به همین مفاهیم برمیخوریم
فقط یه نکته رو همیشه یادتون باشه
ما اینجا داریم مدل Unix و Linux رو دنبال میکنیم
جزئیات داخلی Process و PCB و Process Descriptor ممکنه توی سیستم عامل های مختلف فرق داشته باشهن
فعلا همین مدل رو مدل اصلیمون قرار میدیم
@reverseengine
ReverseEngineering
exit وقتی یک Process کارش تموم میشه چه اتفاقی میوفته تا اینجا با fork و exec و wait و حتی Zombie Process آشنا شدیم حالا فرض کنید یه Process داریم که کارش رو انجام داده و دیگه کاری برای انجام دادن نداره مثلا یه برنامه خیلی ساده داریم: int main() { …
exit
What happens when a Process finishes its work
So far we have been introduced to fork, exec, wait and even Zombie Process
Now suppose we have a Process that has done its work and has nothing more to do
For example, we have a very simple program:
When main finishes, the program must also tell the operating system that
My work is finished
This is where the concept of exit comes into play
What exactly does exit do
To put it simply,
exit
tells the operating system that this Process has nothing more to do and wants to finish its execution
For example:
exit(0);
That number 0 usually means that the program has completed successfully
That is
exit(0)
↓
Successful execution
But if we give a non-zero value, it usually means that another situation has occurred
For example, an error
Of course, the exact meaning of that number is determined by the program itself
So the Kernel is not supposed to understand what exactly the number 1 means
This number is more of an information that the Process gives to the Parent
What happens after exit
Here we have an important point
When the Process reaches exit, it is no longer supposed to continue the normal program commands
That means the program execution is finished
But the Kernel still has to do a few things
For example, the resources that the Process used must be managed
Such as:
Memory
File Descriptor
I O resources
And information about the Process itself
But a very important point
Not everything is finished right away
This is where the Zombie Process enters the story again
Suppose we have this situation
Parent
│
└──── Child
│
▼
exit()
│
▼
Child execution is finished
│
▼
Zombie
Child is no longer running
CPU
does not give it CPU Time to run the program
But some information about its termination is still kept by the Kernel
Why
Because Parent needs to be able to understand how Child terminated
For example, what was its Exit Status
Then Parent comes and waits
Child
│
▼
exit()
│
▼
Zombie
│
▼
Parent calls wait()
│
▼
Exit Status is received
│
▼
Reaping
│
▼
Zombie is destroyed
We call this last step
Reaping
That is, Parent gets the termination status of Child and Kernel no longer needs the remaining information about that Child
So remember one very important point
Termination is not the same as Reaping
Termination
That is, Process is no longer running
Reaping
That is, Parent gets the termination status of Process and the remaining information about That Process is collected from Process Management Structures
This difference is exactly why Zombie Process exists
What is Exit Status
When a Process terminates, it can have a value as its exit status
For example
or
That is
In Unix and Linux, it is usually 0, meaning everything is fine
And non-zero values are usually used for other states
Parent
can get this value with wait or waitpid
Now let's put the whole story together
So far, we have learned some important parts of the Process cycle
Parent
│
fork()
│
▼
Child
│
exec()
│
▼
New Program
│
exit()
│
▼
Zombie
│
wait()
│
▼
Reaped
That is, first Parent creates a Child
Then Child can execute a new Program with exec
Program does its job
Then exits
Process is no longer running
It may become Zombie for a short time
Then Parent gets its state with wait
And finally Child Reaps
Now an important question
Does exit mean that Process is deleted from everywhere at once?
No
This is one of those places where the operating system is a little more complicated than it seems at first glance
When a process terminates
it no longer runs
but the kernel may retain a limited amount of information about it
The most important reason is that the parent needs to be able to understand the termination status of the child
so don't mix the two
Termination
↓
Process no longer runs
Reaping
↓
Parent
gets the state of the process
↓
Remaining information is collected
Another point about resources
What happens when a Process finishes its work
So far we have been introduced to fork, exec, wait and even Zombie Process
Now suppose we have a Process that has done its work and has nothing more to do
For example, we have a very simple program:
int main() {
printf("Hello");
return 0;
}
When main finishes, the program must also tell the operating system that
My work is finished
This is where the concept of exit comes into play
What exactly does exit do
To put it simply,
exit
tells the operating system that this Process has nothing more to do and wants to finish its execution
For example:
exit(0);
That number 0 usually means that the program has completed successfully
That is
exit(0)
↓
Successful execution
But if we give a non-zero value, it usually means that another situation has occurred
For example, an error
Of course, the exact meaning of that number is determined by the program itself
So the Kernel is not supposed to understand what exactly the number 1 means
This number is more of an information that the Process gives to the Parent
What happens after exit
Here we have an important point
When the Process reaches exit, it is no longer supposed to continue the normal program commands
That means the program execution is finished
But the Kernel still has to do a few things
For example, the resources that the Process used must be managed
Such as:
Memory
File Descriptor
I O resources
And information about the Process itself
But a very important point
Not everything is finished right away
This is where the Zombie Process enters the story again
Suppose we have this situation
Parent
│
└──── Child
│
▼
exit()
│
▼
Child execution is finished
│
▼
Zombie
Child is no longer running
CPU
does not give it CPU Time to run the program
But some information about its termination is still kept by the Kernel
Why
Because Parent needs to be able to understand how Child terminated
For example, what was its Exit Status
Then Parent comes and waits
Child
│
▼
exit()
│
▼
Zombie
│
▼
Parent calls wait()
│
▼
Exit Status is received
│
▼
Reaping
│
▼
Zombie is destroyed
We call this last step
Reaping
That is, Parent gets the termination status of Child and Kernel no longer needs the remaining information about that Child
So remember one very important point
Termination is not the same as Reaping
Termination
That is, Process is no longer running
Reaping
That is, Parent gets the termination status of Process and the remaining information about That Process is collected from Process Management Structures
This difference is exactly why Zombie Process exists
What is Exit Status
When a Process terminates, it can have a value as its exit status
For example
exit(0);
Exit Status = 0
or
exit(1);
That is
Exit Status = 1
In Unix and Linux, it is usually 0, meaning everything is fine
And non-zero values are usually used for other states
Parent
can get this value with wait or waitpid
Now let's put the whole story together
So far, we have learned some important parts of the Process cycle
Parent
│
fork()
│
▼
Child
│
exec()
│
▼
New Program
│
exit()
│
▼
Zombie
│
wait()
│
▼
Reaped
That is, first Parent creates a Child
Then Child can execute a new Program with exec
Program does its job
Then exits
Process is no longer running
It may become Zombie for a short time
Then Parent gets its state with wait
And finally Child Reaps
Now an important question
Does exit mean that Process is deleted from everywhere at once?
No
This is one of those places where the operating system is a little more complicated than it seems at first glance
When a process terminates
it no longer runs
but the kernel may retain a limited amount of information about it
The most important reason is that the parent needs to be able to understand the termination status of the child
so don't mix the two
Termination
↓
Process no longer runs
Reaping
↓
Parent
gets the state of the process
↓
Remaining information is collected
Another point about resources
ReverseEngineering
exit وقتی یک Process کارش تموم میشه چه اتفاقی میوفته تا اینجا با fork و exec و wait و حتی Zombie Process آشنا شدیم حالا فرض کنید یه Process داریم که کارش رو انجام داده و دیگه کاری برای انجام دادن نداره مثلا یه برنامه خیلی ساده داریم: int main() { …
When a process terminates, the kernel cleans up resources that are no longer needed
For example, the Address Space of a Process is no longer used by that Process
File Descriptors
are also closed during the termination of the Process
But this does not mean that the Resource itself is necessarily destroyed at that moment
For example, a File may be open by another Process
So the File Descriptor is not the same as the Resource itself
We will see this issue much more later in Linux Internals
An interesting point about return and exit
For example, if we write:
When main ends, the program ends normally
But if we write
exit(0);
Here, the program termination request is given directly
That's why in simple programs, the result of both may seem almost the same
But in terms of the program execution path, they are not exactly the same
We also have another interesting difference
exit
is not the same as exit_
In C, the exit function can perform some User Space cleanups before the program ends
For example, it can execute Handlers registered with atexit
Or flush the stdio buffers
But exit_ does not perform these User Space cleanups and directly terminates the Process
This difference becomes very important when we get into the discussion of fork, buffering, and system calls
Another point is that a Process does not end only with exit
A Process may also terminate due to a Signal
So the overall story could be like this
Process
│
┌───────────────┐
│ │
exit() Signal
│ │
└─�
▼
Termination
│
▼
Exit Information
│
▼
Zombie
│
wait()
│
▼
Reaped
Now let's move on to Reverse Engineering
Suppose you are examining a Binary
In the Trace you see something like this happen
fork()
↓
exec()
↓
Program Execution
↓
exit()
Now you can have a mental picture of what happened
New Process created
↓
Child started executing
↓
New Program executed
↓
Program did its job
↓
Process terminated
If you then see Parent waiting
Child
│
▼
exit()
│
▼
Zombie
│
▼
Parent
│
wait()
│
▼
Exit Status
│
▼
Reaping
You can understand that Parent was probably waiting for the result of Child execution
This is where the things we have learned so far are starting to come in handy for Reverse Engineering
Now a picture Have more complete than what we have learned so far
Parent Process
│
fork()
│
▼
Child Process
│
exec()
│
▼
Program
│
▼
Execution
│
exit()
│
▼
Termination
│
▼
Zombie
│
wait()
│
▼
Reaping
exit
Very simply,
Process
says I am done
After that
Process
↓
Termination
↓
Exit Status
↓
Parent gets the status with wait
↓
Reaping
↓
Zombie dies
So far we have four very important concepts of Process API
fork()
↓
Creating Child Process
exec()
↓
Executing a new program inside a Process
wait()
↓
Waiting or getting the status of Child
exit()
↓
Completing the execution of Process
Learn these four concepts well
Because when you later enter Linux Internals and System Calls And Reverse Engineering ELF binaries, we will come across the same concepts again and again
Just one thing to always remember
We are following the Unix and Linux model here
The internal details of Process, PCB and Process Descriptor may differ in different operating systems
For now, we will make this model our main model
@reverseengine
File Descriptors
are also closed during the termination of the Process
But this does not mean that the Resource itself is necessarily destroyed at that moment
For example, a File may be open by another Process
So the File Descriptor is not the same as the Resource itself
We will see this issue much more later in Linux Internals
An interesting point about return and exit
For example, if we write:
int main() {
return 0;
}
When main ends, the program ends normally
But if we write
exit(0);
Here, the program termination request is given directly
That's why in simple programs, the result of both may seem almost the same
But in terms of the program execution path, they are not exactly the same
We also have another interesting difference
exit
is not the same as exit_
In C, the exit function can perform some User Space cleanups before the program ends
For example, it can execute Handlers registered with atexit
Or flush the stdio buffers
But exit_ does not perform these User Space cleanups and directly terminates the Process
This difference becomes very important when we get into the discussion of fork, buffering, and system calls
Another point is that a Process does not end only with exit
A Process may also terminate due to a Signal
So the overall story could be like this
Process
│
┌───────────────┐
│ │
exit() Signal
│ │
└─�
▼
Termination
│
▼
Exit Information
│
▼
Zombie
│
wait()
│
▼
Reaped
Now let's move on to Reverse Engineering
Suppose you are examining a Binary
In the Trace you see something like this happen
fork()
↓
exec()
↓
Program Execution
↓
exit()
Now you can have a mental picture of what happened
New Process created
↓
Child started executing
↓
New Program executed
↓
Program did its job
↓
Process terminated
If you then see Parent waiting
Child
│
▼
exit()
│
▼
Zombie
│
▼
Parent
│
wait()
│
▼
Exit Status
│
▼
Reaping
You can understand that Parent was probably waiting for the result of Child execution
This is where the things we have learned so far are starting to come in handy for Reverse Engineering
Now a picture Have more complete than what we have learned so far
Parent Process
│
fork()
│
▼
Child Process
│
exec()
│
▼
Program
│
▼
Execution
│
exit()
│
▼
Termination
│
▼
Zombie
│
wait()
│
▼
Reaping
exit
Very simply,
Process
says I am done
After that
Process
↓
Termination
↓
Exit Status
↓
Parent gets the status with wait
↓
Reaping
↓
Zombie dies
So far we have four very important concepts of Process API
fork()
↓
Creating Child Process
exec()
↓
Executing a new program inside a Process
wait()
↓
Waiting or getting the status of Child
exit()
↓
Completing the execution of Process
Learn these four concepts well
Because when you later enter Linux Internals and System Calls And Reverse Engineering ELF binaries, we will come across the same concepts again and again
Just one thing to always remember
We are following the Unix and Linux model here
The internal details of Process, PCB and Process Descriptor may differ in different operating systems
For now, we will make this model our main model
@reverseengine