Packt.Mobile.App.Reverse.Engineering.pdf
17.3 MB
Mobile App Reverse Engineering: Get started with discovering, analyzing, and exploring the internals of Android and iOS apps
@reverseengine
@reverseengine
Working, tested code for every chapter of the book The Art of Exploit Development: Vulnerability Research and Exploitation on Today's Hardened Systems.
https://github.com/KazamaDono/taoxd
https://github.com/KazamaDono/taoxd
GitHub
GitHub - KazamaDono/taoxd: The Art of Exploit Development companion code.
The Art of Exploit Development companion code. Contribute to KazamaDono/taoxd development by creating an account on GitHub.
Windows Exploitation Techniques: Dangling COM Object Registrations
https://projectzero.google/2026/09/windows-dangling-com.html
https://projectzero.google/2026/09/windows-dangling-com.html
projectzero.google
Windows Exploitation Techniques: Dangling COM Object Registrations
This short blog post is about abusing a privilege escalation bug that Microsoft recently fixed in...
BigDiskBuste
Windows Defender Update Denial of Service Vulnerability.
https://github.com/MSNightmare/BigDiskBuster
Windows Defender Update Denial of Service Vulnerability.
https://github.com/MSNightmare/BigDiskBuster
GitHub
GitHub - MSNightmare/BigDiskBuster: Windows Defender Update Denial of Service Vulnerability
Windows Defender Update Denial of Service Vulnerability - MSNightmare/BigDiskBuster
قسمت سی و دوم بافر اورفلو
Program Slicing
فقط کدهایی که واقعا به درد تحلیل میخورن
یعنی یک داده رو از نقطه ورود تا محل استفاده دنبال کردیم
حالا فرض کنید برنامه چند هزار دستور داره
قرار نیست همه رو خط به خط بخونیم
Program Slicing
کمک میکنه فقط قسمت هایی از برنامه رو که روی یک مقدار مشخص تأثیر دارن جدا کنیم
Program Slicing
فرض کنید یک مقدار داریم
user_input
و میخوایم بدونیم چه قسمتهایی از برنامه روی این مقدار تأثیر میذارن
مثلا:
Input
↓
Function A
↓
Function B
↓
Function C
↓
Crash
به جای بررسی کل برنامه
فقط همین مسیر رو بررسی میکنیم
دو نوع اصلی
Forward Slice
از یک مقدار شروع میکنیم و جلو میریم
سؤال اصلی
این مقدار کجا استفاده میشه
مثلا:
input
↓
buffer
↓
memcpy
↓
target
Backward Slice
از یک نقطه مهم شروع میکنیم و به عقب برمیگردیم
مثلا Crash داریم
Crash
↑
memcpy
↑
buffer
↑
input
سوال اصلی
این مقدار از کجا اومده
یک مثال ساده:
فرض کنید کد اینه
C
int process(int input)
{
int a = input;
int b = a + 10;
int c = b * 2;
int unrelated = 500;
return c;
}
اگر هدف ما مقدار
c باشهلازم نیست
unrelated رو بررسی کنیمچون روی
c تاثیری ندارهSlice
تقریبا این بخشه
input
↓
a
↓
b
↓
c
در Reverse Engineering چرا مهمه؟
فرض کنید یک Crash دارید
و Stack Trace به یک تابع بزرگ میرسه
مثلا:
process_packet()
این تابع ممکنه صدها خط کد داشته باشه
ولی Crash فقط به یک مقدار خاص وابسته باشه
مثلا:
length
حالا به جای بررسی کل تابع
مسیر
length رو دنبال میکنیمlength
↓
check
↓
calculation
↓
buffer operation
↓
Crash
وابستگی دادهای
مثلا:
C
int size = input_length;
int copy_size = size + 8;
memcpy(buffer, input, copy_size);
اینجا
copy_size به size وابسته استو
size هم به input_lengthپس مسیر داده اینه
input_length
↓
size
↓
copy_size
↓
memcpy
این دقیقا چیزی هست که موقع Slicing میخوایم پیدا کنیم
وابستگی کنترلی هم مهمه
گاهی مقدار مستقیما منتقل نمیشه ولی روی تصمیم برنامه تاثیر میذاره
مثلا:
C++
if (size > 100)
{
process_large_input();
}
اینجا
size تعیین میکنه کدوم مسیر اجرا بشهپس علاوه بر Data Dependency
یک Control Dependency هم داریم
در Ghidra یا IDA
وقتی یک متغیر یا Register مهم پیدا کردید
میتونی References و مسیرهای
استفاده از اون رو بررسی کنید
مثلا:
Variable
↓
References
↓
Functions
↓
Instructions
بعد کم کم Slice موردنظر رو تشکیل میدید
در ابزارهای پیشرفتهتر هم میشه این کار رو به شکل خودکارتر انجام داد
تفاوت Data Flow و Slicing
خیلی ساده
Data Flow
میگه داده چطور حرکت میکنه
Program Slicing
میگه برای تحلیل یک مقدار خاص دقیقا کدوم قسمتهای برنامه مهم هستن
پس
Data Flow
↓
مسیر حرکت داده
Program Slicing
↓
بخش مرتبط با یک داده یا نتیجه خاص
وقتی با یک باینری بزرگ روبهرو شدیم
لازم نیست کل برنامه رو بررسی کنیم
میتونیم یک مقدار مهم مثل
Input
Length
Pointer
Crash Value
رو انتخاب کنیم
بعد مسیر وابستگیهای اون رو پیدا کنیم
در نتیجه حجم زیادی از کد که ارتباطی با مسئله ما نداره کنار گذاشته میشه
و تحلیل خیلی سریعتر میشه
@reverseengine
ReverseEngineering
قسمت سی و دوم بافر اورفلو Program Slicing فقط کدهایی که واقعا به درد تحلیل میخورن یعنی یک داده رو از نقطه ورود تا محل استفاده دنبال کردیم حالا فرض کنید برنامه چند هزار دستور داره قرار نیست همه رو خط به خط بخونیم Program Slicing کمک میکنه فقط قسمت هایی…
Part 32 Buffer Overflow
Program Slicing
Focusing only on the code relevant to the analysis.
It involves tracing a piece of data from its entry point to where it is used.
Imagine a program with thousands of instructions;
we don't need to read through every single line.
Program Slicing
helps isolate only those parts of the program that affect a specific value.
Program Slicing
Suppose we have a value:
user_input
and we want to know which parts of the program affect this value.
For example:
Input
↓
Function A
↓
Function B
↓
Function C
↓
Crash
Instead of examining the entire program,
we examine only this specific path.
Two main types:
Forward Slice
We start with a value and move forward.
The key question:
Where is this value used?
For example:
input
↓
buffer
↓
memcpy
↓
target
Backward Slice
We start from a critical point and trace back.
For example, we have a crash:
Crash
↑
memcpy
↑
buffer
↑
input
The key question:
Where did this value come from?
A simple example:
Suppose the code is:
C
int process(int input)
{
int a = input;
int b = a + 10;
int c = b * 2;
int unrelated = 500;
return c;
}
If our goal is the value of
c,we don't need to examine
unrelated,because it has no effect on
c.The slice
consists roughly of this part:
input
↓
a
↓
b
↓
c
Why is it important in Reverse Engineering? Suppose you have a crash,
and the stack trace points to a large function—
for example:
process_packet()
This function might contain hundreds of lines of code,
yet the crash depends on only one specific value—
for instance:
length
Now, instead of examining the entire function,
we trace the path of
length:length
↓
check
↓
calculation
↓
buffer operation
↓
Crash
Data dependency
For example:
C
int size = input_length;
int copy_size = size + 8;
memcpy(buffer, input, copy_size);
Here,
copy_size depends on size,and
size depends on input_length;so, the data path is:
input_length
↓
size
↓
copy_size
↓
memcpy
This is precisely what we aim to identify during slicing.
Control dependency is also important;
sometimes a value isn't passed directly but influences a program decision—
for example:
C++
if (size > 100)
{
process_large_input(); }
Here, the
size determines which execution path is taken;so, in addition to Data Dependency,
we also have a Control Dependency.
In tools like Ghidra or IDA,
once you identify a significant variable or register,
you can examine its references and
usage paths:
For example:
Variable
↓
References
↓
Functions
↓
Instructions
You then gradually construct the desired slice;
in more advanced tools, this process can be automated.
Difference between Data Flow and Slicing
Simply put:
Data Flow
describes how data moves.
Program Slicing
identifies exactly which parts of the program are relevant
to analyzing a specific value.
So:
Data Flow
↓
Data movement path
Program Slicing
↓
The portion of the code related to a specific piece of data or result
When dealing with a large binary,
there is no need to examine the entire program;
we can select a key value—such as
Input
Length
Pointer
Crash Value
and trace its dependency paths.
Consequently, large sections of code unrelated to our specific problem are excluded,
making the analysis much faster.
@reverseengine
Taint Analysis
روی Memory و Pointer ها
تا اینجا Taint Analysis رو روی متغیرهای ساده انجام دادیم
ولی توی برنامه واقعی داستان یکم پیچیده تر میشه چون داده فقط بین چند تا متغیر جابه جا نمیشه
ممکنه وارد Memory بشه
داخل یه Buffer قرار بگیره
از طریق Pointer جابه جا بشه
و بعد چند تا تابع مختلف از همون Memory استفاده کنن
اینجاست که Taint Analysis یکم سخت تر میشه
فرض کنید اینو داریم
C
char buffer[32];
buffer[0] = input;
اینجا داده ای که از input اومده وارد Memory شده
پس میتونیم بگیم
input
↓
buffer[0]
حالا فرض کنید یه Pointer به این Buffer داریم
C
char *p = buffer;
بعد هم
char x = p[0];
اینجا داده دوباره از Memory خونده شده
پس مسیرمون میشه
input
↓
buffer[0]
↓
p[0]
↓
x
پس Taint فقط بین Register و Variable حرکت نمیکنه
ممکنه مسیرش از چند مرحله مختلف رد بشه
Register
↓
Memory
↓
Pointer
↓
Memory
↓
Register
این موضوع توی Reverse Engineering خیلی مهمه
چون وقتی Assembly رو نگاه میکنید ممکنه اصلا چیزی به اسم input یا buffer نبینید
مثلا ممکنه همچین چیزی ببینید
mov [rbp-40h], eax
lea rcx, [rbp-40h]
movzx eax, byte ptr [rcx]
اینجا باید خودتون رابطه بین این دستورها رو پیدا کنید
یعنی
EAX
↓
[rbp-40h]
↓
RCX
↓
[RCX]
↓
EAX
یعنی داده یه بار وارد Memory شده
بعد یه Pointer به اون قسمت ساخته شده
و دوباره داده از طریق همون Pointer خونده شده
Pointer
خودش Tainted هست یا داده ای که بهش اشاره میکنه
اینجا باید خیلی دقت کنیم
فرض کنید
C
char *p = buffer;
char x = *p;
ممکنه Buffer حاوی داده Tainted باشه
ولی خود Pointer یعنی p لزوما Tainted نیست
مثلا:
Address = Clean
Data at Address = Tainted
این دوتا با هم فرق دارن
پس وقتی Memory رو تحلیل میکنید همیشه دو تا سوال جدا از خودتون بپرسید
این Address از کجا اومده
محتوای این Address از کجا اومده
مثلا ممکنه داشته باشیم
Pointer
0x500000
خود این Address ممکنه کاملا عادی و قابل اعتماد باشه
ولی محتوای
Memory[0x500000]
ممکنه از ورودی کاربر اومده باشه
پس Address میتونه Clean باشه
ولی Data داخل اون Address Tainted باشه
یه مثال ساده تر:
C
int process(char *input)
{
char buffer[32];
buffer[0] = input[0];
int x = buffer[0] + 10;
return x;
}
اگر input رو Tainted کنیم
مسیر داده میشه
input[0]
↓
buffer[0]
↓
x
↓
return
پس حتی اگر داده برای یه مدت داخل Stack قرار گرفته باشه
Taint
خودش از بین نمیره
تا وقتی که داده جدید همچنان به اون مقدار وابسته باشه
حالا همین رو توی Assembly ببینیم
ممکنه چیزی شبیه این ببینیم
movzx eax, byte ptr [rcx]
mov byte ptr [rbp-20h], al
movzx eax, byte ptr [rbp-20h]
add eax, 10
اینجا باید مسیر رو خودمون از روی دستورها بسازیم
[RCX]
↓
AL
↓
[rbp-20h]
↓
EAX
↓
ADD 10
اگر
پس این مسیر هم باید توی تحلیل ما دنبال بشه
چرا Pointer Analysis مهم میشه
چون توی برنامه های بزرگ ممکنه داده مستقیم از یه Variable به Variable بعدی نره
ممکنه مسیر این شکلی باشه
Input
↓
Buffer
↓
Pointer
↓
Function A
↓
Memory
↓
Pointer
↓
Function B
↓
Comparison
اگر فقط Registerها رو نگاه کنی ممکنه وسط مسیر ارتباط داده رو گم کنی
برای همین توی Reverse Engineering خیلی مهم
Data Flow و Memory Analysis
خیلی به هم وابسته هستن
یه نکته مهم
هر Memory Access به معنی انتقال Taint نیست
مثلا:
C++
int x = 100;
int y = x + 20;
اینجا هیچ داده ای از Input نیومده
پس
x = Clean
y = Clean
ولی اگر داشته باشیم
C++
int x = input;
int y = x + 20;
اینجا داریم
x = Tainted
y = Tainted
پس همیشه باید منشا داده رو دنبال کنیم
فقط اینکه یه مقدار وارد Memory شده به تنهایی کافی نیست
باید ببینیم اون مقدار از کجا اومده و بعد کجا استفاده شده
تمرین:
این کد رو بررسی کنید
C
int process(char *input)
{
char buffer[32];
buffer[0] = input[0];
int x = buffer[0] ^ 0x55;
int clean = 500;
clean += 20;
int result = x + 10;
return result;
}
فرض کنید
مسیر رو پیدا کنید
input[0]
↓
?
↓
?
↓
?
↓
return
بعد مشخص کنید
@reverseengine
روی Memory و Pointer ها
تا اینجا Taint Analysis رو روی متغیرهای ساده انجام دادیم
ولی توی برنامه واقعی داستان یکم پیچیده تر میشه چون داده فقط بین چند تا متغیر جابه جا نمیشه
ممکنه وارد Memory بشه
داخل یه Buffer قرار بگیره
از طریق Pointer جابه جا بشه
و بعد چند تا تابع مختلف از همون Memory استفاده کنن
اینجاست که Taint Analysis یکم سخت تر میشه
فرض کنید اینو داریم
C
char buffer[32];
buffer[0] = input;
اینجا داده ای که از input اومده وارد Memory شده
پس میتونیم بگیم
input
↓
buffer[0]
حالا فرض کنید یه Pointer به این Buffer داریم
C
char *p = buffer;
بعد هم
char x = p[0];
اینجا داده دوباره از Memory خونده شده
پس مسیرمون میشه
input
↓
buffer[0]
↓
p[0]
↓
x
پس Taint فقط بین Register و Variable حرکت نمیکنه
ممکنه مسیرش از چند مرحله مختلف رد بشه
Register
↓
Memory
↓
Pointer
↓
Memory
↓
Register
این موضوع توی Reverse Engineering خیلی مهمه
چون وقتی Assembly رو نگاه میکنید ممکنه اصلا چیزی به اسم input یا buffer نبینید
مثلا ممکنه همچین چیزی ببینید
mov [rbp-40h], eax
lea rcx, [rbp-40h]
movzx eax, byte ptr [rcx]
اینجا باید خودتون رابطه بین این دستورها رو پیدا کنید
یعنی
EAX
↓
[rbp-40h]
↓
RCX
↓
[RCX]
↓
EAX
یعنی داده یه بار وارد Memory شده
بعد یه Pointer به اون قسمت ساخته شده
و دوباره داده از طریق همون Pointer خونده شده
Pointer
خودش Tainted هست یا داده ای که بهش اشاره میکنه
اینجا باید خیلی دقت کنیم
فرض کنید
C
char *p = buffer;
char x = *p;
ممکنه Buffer حاوی داده Tainted باشه
ولی خود Pointer یعنی p لزوما Tainted نیست
مثلا:
Address = Clean
Data at Address = Tainted
این دوتا با هم فرق دارن
پس وقتی Memory رو تحلیل میکنید همیشه دو تا سوال جدا از خودتون بپرسید
این Address از کجا اومده
محتوای این Address از کجا اومده
مثلا ممکنه داشته باشیم
Pointer
0x500000
خود این Address ممکنه کاملا عادی و قابل اعتماد باشه
ولی محتوای
Memory[0x500000]
ممکنه از ورودی کاربر اومده باشه
پس Address میتونه Clean باشه
ولی Data داخل اون Address Tainted باشه
یه مثال ساده تر:
C
int process(char *input)
{
char buffer[32];
buffer[0] = input[0];
int x = buffer[0] + 10;
return x;
}
اگر input رو Tainted کنیم
مسیر داده میشه
input[0]
↓
buffer[0]
↓
x
↓
return
پس حتی اگر داده برای یه مدت داخل Stack قرار گرفته باشه
Taint
خودش از بین نمیره
تا وقتی که داده جدید همچنان به اون مقدار وابسته باشه
حالا همین رو توی Assembly ببینیم
ممکنه چیزی شبیه این ببینیم
movzx eax, byte ptr [rcx]
mov byte ptr [rbp-20h], al
movzx eax, byte ptr [rbp-20h]
add eax, 10
اینجا باید مسیر رو خودمون از روی دستورها بسازیم
[RCX]
↓
AL
↓
[rbp-20h]
↓
EAX
↓
ADD 10
اگر
[RCX] حاوی داده Tainted باشهپس این مسیر هم باید توی تحلیل ما دنبال بشه
چرا Pointer Analysis مهم میشه
چون توی برنامه های بزرگ ممکنه داده مستقیم از یه Variable به Variable بعدی نره
ممکنه مسیر این شکلی باشه
Input
↓
Buffer
↓
Pointer
↓
Function A
↓
Memory
↓
Pointer
↓
Function B
↓
Comparison
اگر فقط Registerها رو نگاه کنی ممکنه وسط مسیر ارتباط داده رو گم کنی
برای همین توی Reverse Engineering خیلی مهم
Data Flow و Memory Analysis
خیلی به هم وابسته هستن
یه نکته مهم
هر Memory Access به معنی انتقال Taint نیست
مثلا:
C++
int x = 100;
int y = x + 20;
اینجا هیچ داده ای از Input نیومده
پس
x = Clean
y = Clean
ولی اگر داشته باشیم
C++
int x = input;
int y = x + 20;
اینجا داریم
x = Tainted
y = Tainted
پس همیشه باید منشا داده رو دنبال کنیم
فقط اینکه یه مقدار وارد Memory شده به تنهایی کافی نیست
باید ببینیم اون مقدار از کجا اومده و بعد کجا استفاده شده
تمرین:
این کد رو بررسی کنید
C
int process(char *input)
{
char buffer[32];
buffer[0] = input[0];
int x = buffer[0] ^ 0x55;
int clean = 500;
clean += 20;
int result = x + 10;
return result;
}
فرض کنید
input[0] نقطه شروع Taint باشهمسیر رو پیدا کنید
input[0]
↓
?
↓
?
↓
?
↓
return
بعد مشخص کنید
clean چرا نباید وارد این مسیر بشه@reverseengine
ReverseEngineering
Taint Analysis روی Memory و Pointer ها تا اینجا Taint Analysis رو روی متغیرهای ساده انجام دادیم ولی توی برنامه واقعی داستان یکم پیچیده تر میشه چون داده فقط بین چند تا متغیر جابه جا نمیشه ممکنه وارد Memory بشه داخل یه Buffer قرار بگیره از طریق Pointer جابه…
Taint Analysis On Memory and Pointers
So far we have done Taint Analysis on simple variables
But in a real program the story gets a bit more complicated because data is not just moved between a few variables
It may be entered into Memory
It may be placed into a Buffer
It may be moved through a Pointer
And then several different functions may use the same Memory
This is where Taint Analysis gets a bit more difficult
Suppose we have this
C
char buffer[32];
buffer[0] = input;
Here the data that came from input is entered into Memory
So we can say
input
↓
buffer[0]
Now suppose we have a Pointer to this Buffer
C
char *p = buffer;
And then
char x = p[0];
Here the data is read from Memory again
So our path becomes
input
↓
buffer[0]
↓
p[0]
↓
x
So Taint doesn't just move between Register and Variable
Its path may go through several different stages
Register
↓
Memory
↓
Pointer
↓
Memory
↓
Register
This is very important in Reverse Engineering
Because when you look at the Assembly you may not see anything called input or buffer at all
For example, you may see something like this
mov [rbp-40h], eax
lea rcx, [rbp-40h]
movzx eax, byte ptr [rcx]
Here you have to find the relationship between these instructions yourself
That is
EAX
↓
[rbp-40h]
↓
RCX
↓
[RCX]
↓
EAX
That is, the data is entered into Memory once
Then a Pointer is created to that part
And the data is read again through the same Pointer
Pointer
Itself Is it Tainted or the data it points to
Here we have to be very careful
Suppose
C
char *p = buffer;
char x = *p;
Buffer may contain Tainted data
But Pointer itself, i.e. p, is not necessarily Tainted
For example:
Address = Clean
Data at Address = Tainted
These two are different
So when you analyze Memory, always ask yourself two separate questions
Where did this Address come from
Where did the content of this Address come from
For example, we may have
Pointer
0x500000
This Address itself may be completely normal and reliable
But the content of
Memory[0x500000]
may have come from user input
So Address can be Clean
But Data inside that Address is Tainted
A simpler example:
C
int process(char *input)
{
char buffer[32];
buffer[0] = input[0];
int x = buffer[0] + 10;
return x;
}
If we Tainted the input
the data path will be
input[0]
↓
buffer[0]
↓
x
↓
return
So even if the data is in the Stack for a while
Taint
itself will not go away
as long as the new data is still attached to that value
Now let's see the same in Assembly
We may see something like this
movzx eax, byte ptr [rcx]
mov byte ptr [rbp-20h], al
movzx eax, byte ptr [rbp-20h]
add eax, 10
Here we have to create the path ourselves from the instructions
[RCX]
↓
AL
↓
[rbp-20h]
↓
EAX
↓
ADD 10
If [RCX] contains Tainted data
then this path should also be followed in our analysis
Why Pointer Analysis is important
Because in large programs, data may be passed directly from one Variable to the next Variable No
The path may be like this
Input
↓
Buffer
↓
Pointer
↓
Function A
↓
Memory
↓
Pointer
↓
Function B
↓
Comparison
If you only look at the registers, you may miss the data connection in the middle of the path
That is why it is very important in Reverse Engineering
Data Flow and Memory Analysis
are very interrelated
An important point
Not every Memory Access means transferring Taint
For example:
C++
int x = 100;
int y = x + 20;
Here no data came from Input
So
x = Clean
y = Clean
But if we have
C++
int x = input;
int y = x + 20;
Here we have
x = Tainted
y = Tainted
So we always need to trace the origin of the data
Just because a value is entered into Memory is not enough
We need to see where that value came from and where it was used
Exercise:
Examine this code
C
int process(char *input)
{
char buffer[32];
buffer[0] = input[0];
int x = buffer[0] ^ 0x55;
int clean = 500;
clean += 20;
int result = x + 10;
return result;
}
Assume input[0] is the starting point of Taint
Find the path
input[0]
↓
?
↓
?
↓
?
↓
return
Then specify why clean should not enter this path
@reverseengine
So far we have done Taint Analysis on simple variables
But in a real program the story gets a bit more complicated because data is not just moved between a few variables
It may be entered into Memory
It may be placed into a Buffer
It may be moved through a Pointer
And then several different functions may use the same Memory
This is where Taint Analysis gets a bit more difficult
Suppose we have this
C
char buffer[32];
buffer[0] = input;
Here the data that came from input is entered into Memory
So we can say
input
↓
buffer[0]
Now suppose we have a Pointer to this Buffer
C
char *p = buffer;
And then
char x = p[0];
Here the data is read from Memory again
So our path becomes
input
↓
buffer[0]
↓
p[0]
↓
x
So Taint doesn't just move between Register and Variable
Its path may go through several different stages
Register
↓
Memory
↓
Pointer
↓
Memory
↓
Register
This is very important in Reverse Engineering
Because when you look at the Assembly you may not see anything called input or buffer at all
For example, you may see something like this
mov [rbp-40h], eax
lea rcx, [rbp-40h]
movzx eax, byte ptr [rcx]
Here you have to find the relationship between these instructions yourself
That is
EAX
↓
[rbp-40h]
↓
RCX
↓
[RCX]
↓
EAX
That is, the data is entered into Memory once
Then a Pointer is created to that part
And the data is read again through the same Pointer
Pointer
Itself Is it Tainted or the data it points to
Here we have to be very careful
Suppose
C
char *p = buffer;
char x = *p;
Buffer may contain Tainted data
But Pointer itself, i.e. p, is not necessarily Tainted
For example:
Address = Clean
Data at Address = Tainted
These two are different
So when you analyze Memory, always ask yourself two separate questions
Where did this Address come from
Where did the content of this Address come from
For example, we may have
Pointer
0x500000
This Address itself may be completely normal and reliable
But the content of
Memory[0x500000]
may have come from user input
So Address can be Clean
But Data inside that Address is Tainted
A simpler example:
C
int process(char *input)
{
char buffer[32];
buffer[0] = input[0];
int x = buffer[0] + 10;
return x;
}
If we Tainted the input
the data path will be
input[0]
↓
buffer[0]
↓
x
↓
return
So even if the data is in the Stack for a while
Taint
itself will not go away
as long as the new data is still attached to that value
Now let's see the same in Assembly
We may see something like this
movzx eax, byte ptr [rcx]
mov byte ptr [rbp-20h], al
movzx eax, byte ptr [rbp-20h]
add eax, 10
Here we have to create the path ourselves from the instructions
[RCX]
↓
AL
↓
[rbp-20h]
↓
EAX
↓
ADD 10
If [RCX] contains Tainted data
then this path should also be followed in our analysis
Why Pointer Analysis is important
Because in large programs, data may be passed directly from one Variable to the next Variable No
The path may be like this
Input
↓
Buffer
↓
Pointer
↓
Function A
↓
Memory
↓
Pointer
↓
Function B
↓
Comparison
If you only look at the registers, you may miss the data connection in the middle of the path
That is why it is very important in Reverse Engineering
Data Flow and Memory Analysis
are very interrelated
An important point
Not every Memory Access means transferring Taint
For example:
C++
int x = 100;
int y = x + 20;
Here no data came from Input
So
x = Clean
y = Clean
But if we have
C++
int x = input;
int y = x + 20;
Here we have
x = Tainted
y = Tainted
So we always need to trace the origin of the data
Just because a value is entered into Memory is not enough
We need to see where that value came from and where it was used
Exercise:
Examine this code
C
int process(char *input)
{
char buffer[32];
buffer[0] = input[0];
int x = buffer[0] ^ 0x55;
int clean = 500;
clean += 20;
int result = x + 10;
return result;
}
Assume input[0] is the starting point of Taint
Find the path
input[0]
↓
?
↓
?
↓
?
↓
return
Then specify why clean should not enter this path
@reverseengine
CPU Scheduling
Kernel
تصمیم میگیره چی اجرا بشه
تا اینجا فهمیدیم چندین Process میتونن همزمان روی سیستم وجود داشته باشن
ولی یه سوال مهم داریم
اگه فقط یه CPU Core داشته باشیم و 10p تا Process آماده اجرا باشن
کدومشون باید CPU رو بگیره
اینجاست که CPU Scheduling وارد ماجرا میشه
Scheduler
توی Kernel بخشی وجود داره که وظیفه اش اینه تصمیم بگیره کدوم Process یا Thread برای اجرا انتخاب بشه
به این بخش میگیم
Scheduler
خیلی ساده
Ready Processes
│
├── Process A
├── Process B
├── Process C
└── Process D
│
▼
Scheduler
│
▼
CPU
Scheduler
در اصل داره جواب این سوال رو پیدا میکنه
الان چه چیزی باید روی CPU اجرا بشه
Ready Queue
Process
هایی که آماده اجرا هستن ولی فعلا CPU در اختیارشون نیست رو به صورت مفهومی داخل چیزی به اسم Ready Queue در نظر میگیریم
مثلا:
Ready Queue
┌─────────┐
│ Process A│
├─────────┤
│ Process B│
├─────────┤
│ Process C│
├─────────┤
│ Process D│
└─────────┘
│
▼
Scheduler
│
▼
CPU
البته توی سیستم عامل واقعی داستان خیلی پیچیده تر از یه صف ساده است
Scheduler
باید چیزهای مختلفی رو در نظر بگیره و ساختارهای داخلیش هم بسته به سیستم عامل و سیاست زمان بندی فرق میکنه
آیا یک Process میتونه برای همیشه CPU رو بگیره
فرض کنید یه برنامه داشته باشیم
while (1) {
// do something
}
این برنامه هیچ وقت تموم نمیشه
اگه سیستم عامل هیچ کنترلی روی CPU نداشت ممکن بود این Process تا مدت خیلی زیادی CPU رو اشغال کنه و برنامه های دیگه فرصت اجرا پیدا نکنن
برای همین سیستم عامل های مدرن از Preemptive Scheduling استفاده میکنن
Preemption
یعنی سیستم عامل بتونه اجرای واحد فعلی رو متوقف کنه و CPU رو به یه واحد دیگه بده
مثلا:
Process A
│
│ Running
▼
Scheduler تصمیم میگیرد
│
▼
Context Switch
│
▼
Process B
│
│ Running
▼
اینجا Process A لزوما تموم نشده
فقط فعلا CPU رو از دست داده
بعدا میتونه دوباره ادامه اجراش رو از همون جایی که متوقف شده دنبال کنه
Timer Interrupt
اینجا وارد میشه
یکی از چیزهایی که به سیستم عامل کمک میکنه کنترل CPU رو پس بگیره Timer هست
سیستم عامل یک Timer سخت افزاری رو تنظیم میکنه
بعد از گذشت یک بازه مشخص Timer میتونه یک Interrupt ایجاد کنه
به صورت ساده
CPU
│
│ اجرای Process A
│
▼
Timer
│
│ زمان تموم شد
▼
Interrupt
│
▼
Kernel
│
▼
Scheduler
│
▼
Process B
پس یه Process نمیتونه به سادگی بگه
CPU
مال منه و پسش نمیدم😁
Kernel
بالاخره میتونه وارد ماجرا بشه
Context Switch اینجا چه نقشی داره
یادتون هست قبلا گفتیم Context Switch یعنی وضعیت اجرای واحد فعلی ذخیره بشه و وضعیت واحد بعدی برای اجرا آماده بشه
حالا اینا رو کنار هم بذارید
Timer Interrupt
↓
Kernel
↓
Scheduler
↓
انتخاب واحد جدید
↓
Context Switch
↓
Thread یا Process Running جدید
این زنجیره یکی از پایه های مهم برای فهمیدن Scheduling هست
Scheduler
بر چه اساسی انتخاب میکنه
اینکه کدوم واحد اجرا بشه میتونه به سیاست ها و الگوریتم های مختلفی بستگی داشته باشه
چند نمونه کلاسیک رو ببینیم
FIFO یا FCFS
هر Process که زودتر وارد شده زودتر اجرا میشه
A → B → C → D
ساده است
ولی همیشه باعث بهترین عملکرد نمیشه
Shortest Job First
اگه مدت اجرای کارها رو بدونیم میتونیم کار کوتاه تر رو زودتر اجرا کنیم
مثلا:
A = 10ms
B = 2ms
C = 7ms
B → C → A
از نظر تئوری جالبه
ولی سیستم عامل معمولا از قبل دقیقا نمیدونه هر Process یا Thread قراره چقدر CPU مصرف کنه
Round Robin
اینجا هر واحد برای یه مقدار زمان مشخص CPU میگیره
مثلا
A → B → C → A → B → C
فرض کنید هرکدوم 10ms زمان داشته باشن
A 10ms
B 10ms
C 10ms
A 10ms
B 10ms
این مدل برای سیستم های تعاملی میتونه مناسب تر باشه
چرا Scheduling برای Reverse Engineering مهمه؟
وقتی دارید یه برنامه رو Debug میکنید ممکنه فکر کنید اجرای برنامه باید کاملا خطی باشه
مثلا
Instruction 1
Instruction 2
Instruction 3
Instruction 4
ولی سیستم عامل میتونه وسط اجرای اون Thread اجرای CPU رو ازش بگیره و واحد دیگه ای رو اجرا کنه
مثلا:
Thread A
│
├── Instruction 1
├── Instruction 2
Kernel
تصمیم میگیره چی اجرا بشه
تا اینجا فهمیدیم چندین Process میتونن همزمان روی سیستم وجود داشته باشن
ولی یه سوال مهم داریم
اگه فقط یه CPU Core داشته باشیم و 10p تا Process آماده اجرا باشن
کدومشون باید CPU رو بگیره
اینجاست که CPU Scheduling وارد ماجرا میشه
Scheduler
توی Kernel بخشی وجود داره که وظیفه اش اینه تصمیم بگیره کدوم Process یا Thread برای اجرا انتخاب بشه
به این بخش میگیم
Scheduler
خیلی ساده
Ready Processes
│
├── Process A
├── Process B
├── Process C
└── Process D
│
▼
Scheduler
│
▼
CPU
Scheduler
در اصل داره جواب این سوال رو پیدا میکنه
الان چه چیزی باید روی CPU اجرا بشه
Ready Queue
Process
هایی که آماده اجرا هستن ولی فعلا CPU در اختیارشون نیست رو به صورت مفهومی داخل چیزی به اسم Ready Queue در نظر میگیریم
مثلا:
Ready Queue
┌─────────┐
│ Process A│
├─────────┤
│ Process B│
├─────────┤
│ Process C│
├─────────┤
│ Process D│
└─────────┘
│
▼
Scheduler
│
▼
CPU
البته توی سیستم عامل واقعی داستان خیلی پیچیده تر از یه صف ساده است
Scheduler
باید چیزهای مختلفی رو در نظر بگیره و ساختارهای داخلیش هم بسته به سیستم عامل و سیاست زمان بندی فرق میکنه
آیا یک Process میتونه برای همیشه CPU رو بگیره
فرض کنید یه برنامه داشته باشیم
while (1) {
// do something
}
این برنامه هیچ وقت تموم نمیشه
اگه سیستم عامل هیچ کنترلی روی CPU نداشت ممکن بود این Process تا مدت خیلی زیادی CPU رو اشغال کنه و برنامه های دیگه فرصت اجرا پیدا نکنن
برای همین سیستم عامل های مدرن از Preemptive Scheduling استفاده میکنن
Preemption
یعنی سیستم عامل بتونه اجرای واحد فعلی رو متوقف کنه و CPU رو به یه واحد دیگه بده
مثلا:
Process A
│
│ Running
▼
Scheduler تصمیم میگیرد
│
▼
Context Switch
│
▼
Process B
│
│ Running
▼
اینجا Process A لزوما تموم نشده
فقط فعلا CPU رو از دست داده
بعدا میتونه دوباره ادامه اجراش رو از همون جایی که متوقف شده دنبال کنه
Timer Interrupt
اینجا وارد میشه
یکی از چیزهایی که به سیستم عامل کمک میکنه کنترل CPU رو پس بگیره Timer هست
سیستم عامل یک Timer سخت افزاری رو تنظیم میکنه
بعد از گذشت یک بازه مشخص Timer میتونه یک Interrupt ایجاد کنه
به صورت ساده
CPU
│
│ اجرای Process A
│
▼
Timer
│
│ زمان تموم شد
▼
Interrupt
│
▼
Kernel
│
▼
Scheduler
│
▼
Process B
پس یه Process نمیتونه به سادگی بگه
CPU
مال منه و پسش نمیدم😁
Kernel
بالاخره میتونه وارد ماجرا بشه
Context Switch اینجا چه نقشی داره
یادتون هست قبلا گفتیم Context Switch یعنی وضعیت اجرای واحد فعلی ذخیره بشه و وضعیت واحد بعدی برای اجرا آماده بشه
حالا اینا رو کنار هم بذارید
Timer Interrupt
↓
Kernel
↓
Scheduler
↓
انتخاب واحد جدید
↓
Context Switch
↓
Thread یا Process Running جدید
این زنجیره یکی از پایه های مهم برای فهمیدن Scheduling هست
Scheduler
بر چه اساسی انتخاب میکنه
اینکه کدوم واحد اجرا بشه میتونه به سیاست ها و الگوریتم های مختلفی بستگی داشته باشه
چند نمونه کلاسیک رو ببینیم
FIFO یا FCFS
هر Process که زودتر وارد شده زودتر اجرا میشه
A → B → C → D
ساده است
ولی همیشه باعث بهترین عملکرد نمیشه
Shortest Job First
اگه مدت اجرای کارها رو بدونیم میتونیم کار کوتاه تر رو زودتر اجرا کنیم
مثلا:
A = 10ms
B = 2ms
C = 7ms
B → C → A
از نظر تئوری جالبه
ولی سیستم عامل معمولا از قبل دقیقا نمیدونه هر Process یا Thread قراره چقدر CPU مصرف کنه
Round Robin
اینجا هر واحد برای یه مقدار زمان مشخص CPU میگیره
مثلا
A → B → C → A → B → C
فرض کنید هرکدوم 10ms زمان داشته باشن
A 10ms
B 10ms
C 10ms
A 10ms
B 10ms
این مدل برای سیستم های تعاملی میتونه مناسب تر باشه
چرا Scheduling برای Reverse Engineering مهمه؟
وقتی دارید یه برنامه رو Debug میکنید ممکنه فکر کنید اجرای برنامه باید کاملا خطی باشه
مثلا
Instruction 1
Instruction 2
Instruction 3
Instruction 4
ولی سیستم عامل میتونه وسط اجرای اون Thread اجرای CPU رو ازش بگیره و واحد دیگه ای رو اجرا کنه
مثلا:
Thread A
│
├── Instruction 1
├── Instruction 2
│
▼
Context Switch
│
▼
Thread B
│
├── Instruction X
├── Instruction Y
│
▼
Context Switch
│
▼
Thread A
│
└── Instruction 3
پس ترتیب زمانی اجرای یک برنامه فقط به کد خودش بستگی نداره
Scheduler
و وضعیت کل سیستم هم روی زمان بندی اجرای Threadها تاثیر میذارن
Process یا Thread
اینجا یه نکته خیلی مهم داریم
در سیستم های مدرن معمولا Scheduler چیزی رو که واقعا باید روی CPU اجرا بشه در سطح Thread زمان بندی میکنه
نه اینکه Process رو همیشه به عنوان یک واحد غیرقابل تقسیم در نظر بگیره
مثلا:
Process A
├── Thread 1
├── Thread 2
└── Thread 3
Process B
├── Thread 1
└── Thread 2
Scheduler
میتونه Thread های آماده رو برای اجرا انتخاب کنه یعنی وقتی بعداً وارد مبحث Thread بشیم این موضوع خیلی مهم تر میشه
یک نکته درباره CPU Core
اگه یک Core داشته باشیم در هر لحظه فقط یک Thread میتونه واقعا روی اون Core اجرا بشه ولی با جابه جایی سریع بین Threadها اینطور به نظر میرسه که چند برنامه همزمان دارن اجرا میشن
مثلا:
Time
│
├── Thread A
├── Thread B
├── Thread C
├── Thread A
└── Thread B
اگه چند Core داشته باشیم چند Thread میتونن واقعا به صورت همزمان روی Coreهای مختلف اجرا بشن
اینجاست که تفاوت بین
Concurrency و Parallelism هم مهم میشه
CPU Scheduling
یعنی Kernel تصمیم میگیره کدوم Thread یا Process آماده باید CPU رو دریافت کنه
به صورت ساده
Ready
│
▼
Scheduler
│
▼
Running
│
├── Time Slice تموم میشه
│
▼
Preemption
│
▼
Context Switch
│
▼
Thread یا Process دیگه
مفاهیم مهم این قسمت
Scheduler
انتخاب کننده واحد قابل اجرا
Ready Queue
محل مفهومی واحدهای آماده اجرا
Preemption
گرفتن CPU از واحدی که در حال اجراست
Timer Interrupt
یکی از مکانیزم هایی که میتونه باعث بشه Kernel فرصت بررسی وضعیت و زمان بندی دوباره رو پیدا کنه
Context Switch
جابه جایی وضعیت اجرای واحد فعلی و آماده کردن واحد بعدی
Thread
واحدی که در سیستم های مدرن معمولا مستقیما برای اجرا زمان بندی میشه
@reverseengine
▼
Context Switch
│
▼
Thread B
│
├── Instruction X
├── Instruction Y
│
▼
Context Switch
│
▼
Thread A
│
└── Instruction 3
پس ترتیب زمانی اجرای یک برنامه فقط به کد خودش بستگی نداره
Scheduler
و وضعیت کل سیستم هم روی زمان بندی اجرای Threadها تاثیر میذارن
Process یا Thread
اینجا یه نکته خیلی مهم داریم
در سیستم های مدرن معمولا Scheduler چیزی رو که واقعا باید روی CPU اجرا بشه در سطح Thread زمان بندی میکنه
نه اینکه Process رو همیشه به عنوان یک واحد غیرقابل تقسیم در نظر بگیره
مثلا:
Process A
├── Thread 1
├── Thread 2
└── Thread 3
Process B
├── Thread 1
└── Thread 2
Scheduler
میتونه Thread های آماده رو برای اجرا انتخاب کنه یعنی وقتی بعداً وارد مبحث Thread بشیم این موضوع خیلی مهم تر میشه
یک نکته درباره CPU Core
اگه یک Core داشته باشیم در هر لحظه فقط یک Thread میتونه واقعا روی اون Core اجرا بشه ولی با جابه جایی سریع بین Threadها اینطور به نظر میرسه که چند برنامه همزمان دارن اجرا میشن
مثلا:
Time
│
├── Thread A
├── Thread B
├── Thread C
├── Thread A
└── Thread B
اگه چند Core داشته باشیم چند Thread میتونن واقعا به صورت همزمان روی Coreهای مختلف اجرا بشن
اینجاست که تفاوت بین
Concurrency و Parallelism هم مهم میشه
CPU Scheduling
یعنی Kernel تصمیم میگیره کدوم Thread یا Process آماده باید CPU رو دریافت کنه
به صورت ساده
Ready
│
▼
Scheduler
│
▼
Running
│
├── Time Slice تموم میشه
│
▼
Preemption
│
▼
Context Switch
│
▼
Thread یا Process دیگه
مفاهیم مهم این قسمت
Scheduler
انتخاب کننده واحد قابل اجرا
Ready Queue
محل مفهومی واحدهای آماده اجرا
Preemption
گرفتن CPU از واحدی که در حال اجراست
Timer Interrupt
یکی از مکانیزم هایی که میتونه باعث بشه Kernel فرصت بررسی وضعیت و زمان بندی دوباره رو پیدا کنه
Context Switch
جابه جایی وضعیت اجرای واحد فعلی و آماده کردن واحد بعدی
Thread
واحدی که در سیستم های مدرن معمولا مستقیما برای اجرا زمان بندی میشه
@reverseengine
ReverseEngineering
CPU Scheduling Kernel تصمیم میگیره چی اجرا بشه تا اینجا فهمیدیم چندین Process میتونن همزمان روی سیستم وجود داشته باشن ولی یه سوال مهم داریم اگه فقط یه CPU Core داشته باشیم و 10p تا Process آماده اجرا باشن کدومشون باید CPU رو بگیره اینجاست که CPU Scheduling…
CPU Scheduling
Kernel
Decides what to run
So far we have understood that multiple processes can exist on the system at the same time
But we have an important question
If we only have one CPU Core and 10p to Processes are ready to run
Which one should get the CPU
This is where CPU Scheduling comes into play
Scheduler
There is a part in the Kernel whose job is to decide which Process or Thread to choose to run
We call this part
Scheduler
Very simply
Ready Processes
│
├── Process A
├── Process B
├── Process C
└── Process D
│
▼
Scheduler
│
▼
CPU
Scheduler
Basically, it is finding the answer to this question
What should be run on the CPU now
Ready Queue
Processes that are ready to run but do not currently have the CPU are conceptually considered in something called the Ready Queue
For example:
Ready Queue
┌─
│
▼
Scheduler
│
▼
CPU
Of course, in a real operating system, the story is much more complicated than a simple queue
Scheduler
must consider different things and its internal structures also vary depending on the operating system and scheduling policy
Can a Process take the CPU forever
Suppose we have a program
while (1) {
// do something
}
This program will never finish
If the operating system had no control over the CPU, this Process could occupy the CPU for a very long time and other programs would not have a chance to run
That is why modern operating systems use Preemptive Scheduling
Preemption
means that the operating system can stop the current unit from executing and give the CPU to another unit Give
For example:
Process A
│
│ Running
▼
Scheduler decides
│
▼
Context Switch
│
▼
Process B
│
│ Running
▼
Here Process A is not necessarily finished
It just lost the CPU for now
It can continue its execution from where it stopped later
Timer Interrupt
comes in here
One of the things that helps the operating system regain control of the CPU is Timer
The operating system sets a hardware Timer
After a certain period of time, the Timer can create an Interrupt
Simply
CPU
│
│ Running Process A
│
▼
Timer
│
│ Time is up
▼
Interrupt
│
▼
Kernel
│
▼
Scheduler
│
▼
Process B
So a Process can't simply say
The CPU is mine and I'm not giving it back😁
The Kernel
can finally get involved
What is the role of Context Switch here Yes
Remember we said earlier that Context Switch means saving the state of the current unit and preparing the state of the next unit for execution
Now put these together
Timer Interrupt
↓
Kernel
↓
Scheduler
↓
Selecting a new unit
↓
Context Switch
↓
A new thread or process running
This chain is one of the important foundations for understanding Scheduling
On what basis does the Scheduler choose
Which unit to run can depend on various policies and algorithms
Let's look at some classic examples
FIFO or FCFS
Any process that enters earlier will run earlier
A → B → C → D
It's simple
But it doesn't always lead to the best performance
Shortest Job First
If we know the execution time of the tasks, we can execute the shorter task first
For example:
A = 10ms
B = 2ms
C = 7ms
B → C → A
It's interesting in theory
But the operating system usually doesn't know in advance exactly how much CPU each process or thread is going to consume Do
Round Robin
Here each thread gets CPU for a certain amount of time
For example
A → B → C → A → B → C
Assume each has 10ms of time
A 10ms
B 10ms
C 10ms
A 10ms
B 10ms
This model may be more suitable for interactive systems
Why is Scheduling important for Reverse Engineering?
When you are debugging a program, you may think that the execution of the program should be completely linear
For example
Instruction 1
Instruction 2
Instruction 3
Instruction 4
But the operating system can take the CPU from that thread in the middle of its execution and execute another thread
For example:
Thread A
│
├── Instruction 1
├── Instruction 2
Kernel
Decides what to run
So far we have understood that multiple processes can exist on the system at the same time
But we have an important question
If we only have one CPU Core and 10p to Processes are ready to run
Which one should get the CPU
This is where CPU Scheduling comes into play
Scheduler
There is a part in the Kernel whose job is to decide which Process or Thread to choose to run
We call this part
Scheduler
Very simply
Ready Processes
│
├── Process A
├── Process B
├── Process C
└── Process D
│
▼
Scheduler
│
▼
CPU
Scheduler
Basically, it is finding the answer to this question
What should be run on the CPU now
Ready Queue
Processes that are ready to run but do not currently have the CPU are conceptually considered in something called the Ready Queue
For example:
Ready Queue
┌─
│
▼
Scheduler
│
▼
CPU
Of course, in a real operating system, the story is much more complicated than a simple queue
Scheduler
must consider different things and its internal structures also vary depending on the operating system and scheduling policy
Can a Process take the CPU forever
Suppose we have a program
while (1) {
// do something
}
This program will never finish
If the operating system had no control over the CPU, this Process could occupy the CPU for a very long time and other programs would not have a chance to run
That is why modern operating systems use Preemptive Scheduling
Preemption
means that the operating system can stop the current unit from executing and give the CPU to another unit Give
For example:
Process A
│
│ Running
▼
Scheduler decides
│
▼
Context Switch
│
▼
Process B
│
│ Running
▼
Here Process A is not necessarily finished
It just lost the CPU for now
It can continue its execution from where it stopped later
Timer Interrupt
comes in here
One of the things that helps the operating system regain control of the CPU is Timer
The operating system sets a hardware Timer
After a certain period of time, the Timer can create an Interrupt
Simply
CPU
│
│ Running Process A
│
▼
Timer
│
│ Time is up
▼
Interrupt
│
▼
Kernel
│
▼
Scheduler
│
▼
Process B
So a Process can't simply say
The CPU is mine and I'm not giving it back😁
The Kernel
can finally get involved
What is the role of Context Switch here Yes
Remember we said earlier that Context Switch means saving the state of the current unit and preparing the state of the next unit for execution
Now put these together
Timer Interrupt
↓
Kernel
↓
Scheduler
↓
Selecting a new unit
↓
Context Switch
↓
A new thread or process running
This chain is one of the important foundations for understanding Scheduling
On what basis does the Scheduler choose
Which unit to run can depend on various policies and algorithms
Let's look at some classic examples
FIFO or FCFS
Any process that enters earlier will run earlier
A → B → C → D
It's simple
But it doesn't always lead to the best performance
Shortest Job First
If we know the execution time of the tasks, we can execute the shorter task first
For example:
A = 10ms
B = 2ms
C = 7ms
B → C → A
It's interesting in theory
But the operating system usually doesn't know in advance exactly how much CPU each process or thread is going to consume Do
Round Robin
Here each thread gets CPU for a certain amount of time
For example
A → B → C → A → B → C
Assume each has 10ms of time
A 10ms
B 10ms
C 10ms
A 10ms
B 10ms
This model may be more suitable for interactive systems
Why is Scheduling important for Reverse Engineering?
When you are debugging a program, you may think that the execution of the program should be completely linear
For example
Instruction 1
Instruction 2
Instruction 3
Instruction 4
But the operating system can take the CPU from that thread in the middle of its execution and execute another thread
For example:
Thread A
│
├── Instruction 1
├── Instruction 2
│
▼
Context Switch
│
▼
Thread B
│
├── Instruction X
├── Instruction Y
│
▼
Context Switch
│
▼
Thread A
│
└── Instruction 3
So, the execution timing of a program doesn't depend solely on its own code.
The Scheduler
and the overall system state also influence the scheduling of thread execution.
Process or Thread
There is a crucial point here:
In modern systems, the scheduler typically schedules the entity that actually runs on the CPU at the *thread* level,
rather than always treating the process as an indivisible unit.
For example:
Process A
├── Thread 1
├── Thread 2
└── Thread 3
Process B
├── Thread 1
└── Thread 2
The Scheduler
can select ready threads for execution; this becomes even more significant when we delve deeper into the topic of threads later.
A point regarding CPU Cores:
If we have a single core, only one thread can actually execute on that core at any given moment; however, through rapid switching between threads, it appears as though multiple programs are running simultaneously.
For example:
Time
│
├── Thread A
├── Thread B
├── Thread C
├── Thread A
└── Thread B
If we have multiple cores, multiple threads can actually execute simultaneously on different cores.
This is where the distinction between
Concurrency and Parallelism becomes important.
CPU Scheduling
refers to the kernel deciding which ready thread or process should be allocated the CPU.
Simply put:
Ready
│
▼
Scheduler
│
▼
Running
│
├── Time slice expires
│
▼
Preemption
│
▼
Context Switch
│
▼
Another thread or process
Key concepts in this section:
Scheduler
The component that selects the executable unit.
Ready Queue
The conceptual location for units ready to execute.
Preemption
Taking the CPU away from the currently executing unit.
Timer Interrupt
One of the mechanisms... ...that allow the kernel to check the status and reschedule
Context Switch
Swapping the execution state of the current unit and preparing the next one
Thread
A unit that, in modern systems, is typically scheduled directly for execution
@reverseengine
▼
Context Switch
│
▼
Thread B
│
├── Instruction X
├── Instruction Y
│
▼
Context Switch
│
▼
Thread A
│
└── Instruction 3
So, the execution timing of a program doesn't depend solely on its own code.
The Scheduler
and the overall system state also influence the scheduling of thread execution.
Process or Thread
There is a crucial point here:
In modern systems, the scheduler typically schedules the entity that actually runs on the CPU at the *thread* level,
rather than always treating the process as an indivisible unit.
For example:
Process A
├── Thread 1
├── Thread 2
└── Thread 3
Process B
├── Thread 1
└── Thread 2
The Scheduler
can select ready threads for execution; this becomes even more significant when we delve deeper into the topic of threads later.
A point regarding CPU Cores:
If we have a single core, only one thread can actually execute on that core at any given moment; however, through rapid switching between threads, it appears as though multiple programs are running simultaneously.
For example:
Time
│
├── Thread A
├── Thread B
├── Thread C
├── Thread A
└── Thread B
If we have multiple cores, multiple threads can actually execute simultaneously on different cores.
This is where the distinction between
Concurrency and Parallelism becomes important.
CPU Scheduling
refers to the kernel deciding which ready thread or process should be allocated the CPU.
Simply put:
Ready
│
▼
Scheduler
│
▼
Running
│
├── Time slice expires
│
▼
Preemption
│
▼
Context Switch
│
▼
Another thread or process
Key concepts in this section:
Scheduler
The component that selects the executable unit.
Ready Queue
The conceptual location for units ready to execute.
Preemption
Taking the CPU away from the currently executing unit.
Timer Interrupt
One of the mechanisms... ...that allow the kernel to check the status and reschedule
Context Switch
Swapping the execution state of the current unit and preparing the next one
Thread
A unit that, in modern systems, is typically scheduled directly for execution
@reverseengine
❤2
Forwarded from Sec Note
My New Blog Post
Evading Sysmon Dns Monitoring In 2026 | binary-win
DNSevade : https://github.com/binary-win/DNSevade.git
#sysmon #bypass
Your Notes are HERE
Evading Sysmon Dns Monitoring In 2026 | binary-win
DNSevade : https://github.com/binary-win/DNSevade.git
Sysmon gets its DNS telemetry from theMicrosoft-Windows-DNS-ClientETW provider.
Let's hide in plain sight.
#sysmon #bypass
Your Notes are HERE
❤2🔥1