ReverseEngineering
1.32K subscribers
50 photos
11 videos
106 files
888 links
Download Telegram
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
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 رخ میده
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
ReverseEngineering
Trap و ورود از User Mode به Kernel Mode تا اینجا فهمیدیم برنامه ها توی User Mode اجرا میشن و وقتی بخوان از سیستم عامل یه کار حساس بخوان از System Call استفاده میکنن ولی یه سوال مهم داریم CPU دقیقا چطوری از اجرای عادی برنامه توی User Mode میره داخل Kernel…
Trap

and Entering from User Mode to Kernel Mode

So far we have understood that programs run in User Mode and when they ask the operating system for a sensitive task, they use System Call

But we have an important question

How exactly does the CPU go from normal program execution in User Mode to Kernel Mode

Here the concept of Trap comes into play

Trap

means a controlled transition from normal program execution to Kernel, meaning the program is running in User Mode

User Mode
│
│ Service Request
▼
Trap
│
▼
Kernel Mode

After the Kernel has done its job, control returns to the program

What is the difference between System Call and Trap

These two are not the same

System Call

Actually, the program requests the operating system

For example, the program says I want to read from a file

or write something

or get memory

For example:

read()
write()
mmap()

But Trap is a mechanism that causes the CPU to transfer execution control to the Kernel

So very simply
Program
│
│ System Call
▼
Trap / System call entry
│
▼
Kernel

That is, the System Call tells what service I want

Trap

or the entry path causes the program to enter the Kernel

Example x86 64:

In Linux on the x86 64 architecture, a System Call is usually executed with the
syscall

command

Before executing this command, the program places the necessary information into specific registers

Concept:

Registers
│
├── System Call Number

├── Argument 1
├── Argument 2
└──
│
▼
syscall
│
▼
Kernel

Kernel

When it enters, it checks the System Call number to find out exactly what the program requested. For example, the program may request to read information from a file. So the Kernel needs to know which System Call this request is related to.

How does the Kernel know what System Call was requested? Here, one of the important things is the System Call Number. Each System Call in Linux has a specific number. Of course, these numbers depend on the architecture and ABI. There are
That is, we should not think that the number of a System Call is the same on all systems

Conceptually:

System Call Number
+
Arguments
│
▼
syscall
│
▼
Kernel


Kernel

Using this information, it finds the path to that request

Kernel

What does it do when it enters
When the CPU enters the Kernel, the Kernel must check the program request

Simply
User Code
│
▼
syscall
│
▼
Kernel Entry
│
▼
Check the request
│
▼
Execution of the operation
│
▼
Return Value
│
▼
User Mode

Kernel

It may check things like access permissions and the validity of some information

For example, a User Space program should not be able to manipulate any memory address it wants without control
The operating system plays the role of a watchdog here because if any program could do anything with system resources, the operating system would practically become useless

Trap

Is not just for System Calls
Here is an important point We have

When we say Trap, we should not think that it is only used for System Call
In the operating system and CPU architecture, control transfer to the Kernel can occur for various reasons

For example:

System Call

Exception

Some hardware events via Interrupt, of course, these concepts are not exactly the same and should be examined separately

What is Exception

Exception

It happens when the CPU encounters a special condition while executing an instruction

For example:

Program
│
▼
Execution of instruction
│
▼
Special conditions
│
▼
Exception
│
▼
Kernel Handler

One of its famous examples is

Page Fault

Suppose the program accesses a memory page, but that page does not have the necessary conditions for access at that moment

The CPU
creates a Page Fault

Then the Kernel enters the story and checks what should be done

An important point:

Page Fault

does not necessarily mean Crash

The operating system may be able to handle it completely normally

For example, it can prepare the desired page and the program can continue again Give

Interrupt

Usually generated by hardware or hardware controller, for example, a device wants to notify the CPU of an event

Simply:
ReverseEngineering
Trap و ورود از User Mode به Kernel Mode تا اینجا فهمیدیم برنامه ها توی User Mode اجرا میشن و وقتی بخوان از سیستم عامل یه کار حساس بخوان از System Call استفاده میکنن ولی یه سوال مهم داریم CPU دقیقا چطوری از اجرای عادی برنامه توی User Mode میره داخل Kernel…
Hardware
│
▼
Interrupt
│
▼
CPU
│
▼
Kernel

For example, an I O operation is completed
Hardware can notify the CPU with an Interrupt that the operation is completed
Then the Kernel can manage this event

The difference between these three concepts:

System Call

A program request to get a service from the operating system

Exception

An event that occurs as a result of executing a command or condition related to the CPU

Interrupt

Declaration of an event that usually comes from hardware

Trap

Is also an architectural and operating system term that is used to transfer control to a Handler. Depending on the source you read, the exact definition of this term may vary slightly, so it's better not to just memorize one definition. The important thing is to understand what causes the CPU to change the execution path and enter the corresponding Handler

Why is this important for Reverse Engineering

Here the topic becomes more interesting for us
When you are reversing a binary

You may see instructions like
syscall
or behaviors related to Exception and Page Fault
If you have only memorized the assembly you will see
syscall
But if you understand the OS you will understand what happened behind this instruction

User Code
↓
System Call
↓
CPU / Kernel Entry
↓
Kernel Handler
↓
Operation
↓
Return

That is, there is a big difference between just seeing an instruction and understanding what happens behind that instruction in the operating system

These are the basic concepts of many things that we will deal with later in OS and Reverse Engineering

@reverseengine
Taint Analysis

دنبال کردن یک داده در کل برنامه
تا اینجا با Forward Slicing و Backward Slicing یاد گرفتیم چطور مسیر داده رو پیدا کنیم

حالا یه قدم جلوتر میریم

Taint Analysis
یعنی یه داده مشخص رو علامت گذاری کنیم و بعد ببینیم این داده توی ادامه برنامه کجاها میره و روی چه چیزهایی تاثیر میذاره

فرض کنید برنامه یه ورودی میگیره:

C
int input = get_input();


ما input رو Tainted در نظر میگیریم
یعنی فعلا فرض میکنیم

input = TAINTED

حالا اگه این مقدار وارد یه محاسبه بشه

int x = input + 10;

پس

input
↓
x

x هم Tainted حساب میشه
اگه بعدا داشته باشیم
C
int y = x * 5;

مسیر میشه

input
↓
x
↓
y

پس y هم تحت تاثیر ورودی اولیه قرار گرفته

حالا اگه داشته باشیم
C
int result = y ^ 0x55;

مسیر میشه

input
↓
x
↓
y
↓
result

و اگه آخرش داشته باشیم

if (result == 100)

میفهمیم ورودی اولیه روی این شرط تاثیر گذاشته

این یکی از کاربردهای مهم Taint Analysis هست

یک مثال ساده:

C
int check(int input)
{
int x = input ^ 0x55;
int y = x + 10;

int temp = 500;
temp *= 20;

if (y == 100)
return 1;

return 0;
}

ما input رو Tainted میکنیم
پس مسیرش میشه

input
↓
x
↓
y
↓
comparison

ولی این قسمت

temp
↓
temp * 20

به شرط نهایی وصل نیست
پس Taint هم بهش منتقل نمیشه
یعنی temp همچنان Clean هست

Taint
توی Assembly
وقتی وارد Reverse Engineering میشیم معمولا دیگه متغیری به اسم input نداریم
ممکنه چیزی شبیه این ببینیم

mov eax, [rbp-20h]
xor eax, 55h
add eax, 10
cmp eax, 100

فرض کنیم
json
[rbp-20h] = ورودی

پس مسیر داده میشه

[rbp-20h]
↓
EAX
↓
XOR 55h
↓
ADD 10
↓
CMP 100

اینجا میتونیم ببینیم ورودی مستقیم وارد محاسبات شده و در نهایت به مقایسه رسیده
Taint
فقط برای ورودی کاربر نیست
هر داده ای میتونه نقطه شروع Taint باشه
مثلا:

Powershell
File Data
Network Data
Configuration
Function Argument
Registry Value
Environment Variable

مثلا اگه یه تابع اینو بگیره
C
void process(char *data)

میتونیم data رو نقطه شروع قرار بدیم
بعد دنبال کنیم که این داده توی ادامه برنامه وارد چه توابعی میشه
ممکنه مسیرش این شکلی باشه

Input
↓
Parser
↓
Transformation
↓
Function A
↓
Function B
↓
Comparison

یا حتی

Input
↓
Buffer
↓
Memory
↓
Function
↓
Output

یه نکته مهم
Taint Analysis
یعنی لزوما مقدار دقیق اولیه همونطوری باقی مونده نه
ممکنه مقدار چند بار تغییر کنه

مثلا:


Input
↓
XOR
↓
ADD
↓
SHIFT
↓
SUB
↓
Comparison

چیزی که دنبال میکنیم اینه که ببینیم مقدار جدید هنوز به داده اولیه وابسته هست یا نه
یعنی سؤال اصلی اینه

آیا این مقدار تحت تاثیر داده Tainted قرار گرفته

نه اینکه

آیا مقدار دقیقا همون مقدار اولیه عسا


به انتقال Taint از یه مقدار به مقدار دیگه میگیم
Taint Propagation
مثلا

a = input;
b = a + 5;
c = b ^ 0x44;

مسیرش میشه

input
↓
a
↓
b
↓
c

یعنی Taint همراه داده حرکت میکنه
ولی اگه داشته باشیم
C++
int x = 100;
int y = x + 20;

چون x از Input نیومده

x = CLEAN
y = CLEAN

پس Taint وارد این مسیر نمیشه
حالا یه مثال واقعی تر
فرض کنید یه تابع پیچیده داریم

Function
│
├── Input
├── Calculation A
├── Junk Code
├── Function B
├── Calculation C
├── Comparison
└── Return

اگه ورودی رو Tainted کنیم ممکنه بعد از تحلیل به این نتیجه برسیم

Input
↓
Calculation A
↓
Function B
↓
Calculation C
↓
Comparison

و بخش Junk Code اصلاً توی مسیر Taint نباشه
اینجا دقیقا میبینیم چرا Taint Analysis در کنار Slicing خیلی قدرتمنده

Slicing میگه

چه چیزهایی به این نقطه مربوط میشن

Taint Analysis میگه

این داده مشخص کجاها رفته و روی چه چیزهایی تاثیر گذاشته

این دوتا خیلی به هم نزدیکن ولی دقیقا یکی نیستن

تمرین:

این تابع رو بررسی کنید
C
int verify(int input)
{
int a = input ^ 0x31;

int junk = 900;
junk *= 5;

int b = a + 20;

int temp = 100;
temp ^= 55;

int result = b * 3;

return result == 300;
}

فرض کنید فقط input رو Tainted کردیم
حالا مسیر Taint رو مشخص کنید

input
↓
?
↓
?
↓
?
↓
comparison

و مشخص کنید کدوم متغیرها اصلا نباید Tainted بشن

@reverseengine
ReverseEngineering
Taint Analysis دنبال کردن یک داده در کل برنامه تا اینجا با Forward Slicing و Backward Slicing یاد گرفتیم چطور مسیر داده رو پیدا کنیم حالا یه قدم جلوتر میریم Taint Analysis یعنی یه داده مشخص رو علامت گذاری کنیم و بعد ببینیم این داده توی ادامه برنامه کجاها…
Taint Analysis

Tracking data throughout the program
So far, we have learned how to trace data paths using Forward Slicing and Backward Slicing.

Now, let's take it a step further.

Taint Analysis
This involves marking a specific piece of data and then observing where it travels within the program and what it affects.

Suppose the program receives an input:

C
int input = get_input();

We consider input to be "Tainted"—
meaning, for the time being, we assume:

input = TAINTED

Now, if this value enters a calculation:

int x = input + 10;

Then:

input
↓
x

x is also considered Tainted.
If we subsequently have:

C
int y = x * 5;

The path becomes:

input
↓
x
↓
y

Thus, y is also influenced by the initial input.

Now, if we have:

C
int result = y ^ 0x55;

The path becomes:

input
↓
x
↓
y
↓
result

And if, finally, we have:

if (result == 100)

We realize that the initial input has influenced this condition.

This is one of the key applications of Taint Analysis.

A simple example:

C
int check(int input)
{
int x = input ^ 0x55;
int y = x + 10;

int temp = 500;
temp *= 20;

if (y == 100)
return 1;

return 0; }

We mark the input as "Tainted."
So, its path becomes:

input
↓
x
↓
y
↓
comparison

However, this part:

temp
↓
temp * 20

...is not connected to the final condition.
Therefore, the taint is not propagated to it;
meaning temp remains "Clean."

Taint in Assembly:
When we delve into reverse engineering, we usually don't have a variable explicitly named input.
We might see something like this:

mov eax, [rbp-20h]
xor eax, 55h
add eax, 10
cmp eax, 100

Let's assume:
[rbp-20h] = input

So, the data path becomes:

[rbp-20h]
↓
EAX
↓
XOR 55h
↓
ADD 10
↓
CMP 100

Here, we can see that the input enters the calculations directly and eventually reaches the comparison.
Taint analysis isn't limited to user input;
any data can serve as a taint starting point.
For example:

PowerShell
File Data
Network Data
Configuration
Function Argument
Registry Value
Environment Variable

For instance, if a function takes this:
C
void process(char *data)

We can designate data as the starting point
and then trace which functions this data enters as the program proceeds.
The path might look like this:

Input
↓
Parser
↓
Transformation
↓
Function A
↓
Function B
↓
Comparison

Or even:

Input
↓
Buffer
↓
Memory
↓
Function
↓
Output

An important point:
Taint analysis
does not necessarily imply that the exact initial value remains unchanged;
the value might be modified multiple times.

For example:

Input
↓
XOR
↓
ADD
↓
SHIFT
↓
SUB
↓
Comparison

What we track is whether the new value still depends on the initial data.
In other words, the core question is:

Is this value influenced by the tainted data? ...is situated...

It is not the case that...

Is the value exactly the same as the initial value?

We call the transfer of a "taint" from one value to another "Taint Propagation."
For example:

a = input;
b = a + 5;
c = b ^ 0x44;

The path looks like this:

input
↓
a
↓
b
↓
c

This means the taint moves along with the data.
However, if we have:

C++
int x = 100;
int y = x + 20;

Since x did not come from input:

x = CLEAN
y = CLEAN

So, the taint does not enter this path.
Now, for a more realistic example: suppose we have a complex function:

Function
│
├── Input
├── Calculation A
├── Junk Code
├── Function B
├── Calculation C
├── Comparison
└── Return

If we mark the input as "tainted," analysis might lead us to this conclusion:

Input
↓
Calculation A
↓
Function B
↓
Calculation C
↓
Comparison

...and the "Junk Code" section might not be part of the taint path at all.
This is precisely where we see why Taint Analysis is so powerful when combined with Slicing.

Slicing tells us:

What elements are related to this specific point?

Taint Analysis tells us:

Where this specific piece of data went and what it affected.

The two concepts are closely related but not exactly the same.

Exercise:

Examine this function:
ReverseEngineering
Taint Analysis دنبال کردن یک داده در کل برنامه تا اینجا با Forward Slicing و Backward Slicing یاد گرفتیم چطور مسیر داده رو پیدا کنیم حالا یه قدم جلوتر میریم Taint Analysis یعنی یه داده مشخص رو علامت گذاری کنیم و بعد ببینیم این داده توی ادامه برنامه کجاها…
C
int verify(int input)
{
int a = input ^ 0x31;

int junk = 900;
junk *= 5;

int b = a + 20;

int temp = 100;
temp ^= 55;

int result = b * 3;

return result == 300;
}

Assume only input is tainted.
Now, determine the taint path:

input
↓
?
↓
?
↓
?
↓
comparison

And identify which variables should not be tainted at all.

@reverseengine