ReverseEngineering
1.33K subscribers
50 photos
11 videos
112 files
916 links
Download Telegram
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 gets its DNS telemetry from the Microsoft-Windows-DNS-Client ETW provider.
Let's hide in plain sight.

#sysmon #bypass

Your Notes are HERE
❤2🔥1
_mastering-system-programming-with-c-files-processes-and-ipc.pdf
1.3 MB
Mastering System Programming with C Files, Processes, and IPC
Introduction to Exploit Development

https://samsclass.info/127/ED_2020.shtml
Scheduling Metrics

از کجا بفهمیم Scheduler خوبه؟

توی قسمت قبل فهمیدیم Scheduler مسئول اینه که مشخص کنه کدوم Thread یا Process نوبتش بشه و روی CPU اجرا بشه

اما یه سؤال مهم داریم:

از کجا بفهمیم یک الگوریتم Scheduling واقعا خوبه؟

فرض کنید سه تا Process داریم:

A
B
C

Scheduler
میتونه این‌ها رو با ترتیب‌های مختلف اجرا کنه

پس باید یه سری معیار داشته باشیم تا بتونیم عملکرد الگوریتم‌های مختلف رو با هم مقایسه کنیم

اینجاست که میرسیم به Scheduling Metrics



Turnaround Time

اولین مفهوم:

Turnaround Time

به زبان ساده یعنی:

از لحظه‌ای که یک Job وارد سیستم میشه تا لحظه‌ای که کارش کاملا تموم میشه چقدر زمان گذشته؟

فرمولش:

Turnaround Time =

Completion Time - Arrival Time

مثلا:

Arrival Time = 0
Completion Time = 10

پس:

Turnaround Time = 10 - 0
= 10

یعنی این Process از لحظه ورود تا پایان 10 واحد زمانی داخل سیستم بوده



Waiting Time

حالا یه سؤال دیگه:

Process
چه مقدار از زمان رو فقط منتظر CPU بوده؟

اینجا میرسیم به:

Waiting Time

فرض کنید وضعیت یک Process این شکلی باشه:

Arrival
↓
Waiting
↓
Running
↓
Waiting
↓
Running
↓
Finished

هر مدتی که Process توی Ready Queue منتظر CPU باشه، جزو Waiting Time حساب میشه

پس خیلی ساده:

Waiting Time =
مدت زمانی که Process منتظر CPU بوده



Response Time

این یکی مخصوصا برای برنامه‌های تعاملی خیلی مهمه

Response Time
از وقتی Job وارد سیستم میشه تا اولین باری که CPU بهش میرسه چقدر طول کشیده؟

مثلا:

Arrival
│
│ 5ms
▼
First Run

پس:

Response Time = 5ms

حالا تفاوتش با Turnaround Time رو خوب دقت کنید

Response Time

فقط تا اولین اجرا:

Arrival → First Run

Turnaround Time

تا اخر کامل کار:

Arrival → Completion

پس ممکنه یک Process خیلی سریع اولین بار اجرا بشه ولی برای تمام شدن کارش مدت زیادی زمان لازم داشته باشه


یک مثال کامل

فرض کنیم سه تا Job داریم:

Job A
Arrival = 0
Runtime = 5

Job B
Arrival = 0
Runtime = 3

Job C
Arrival = 0
Runtime = 2

فرض کنیم Scheduler تصمیم گرفته این ترتیب رو اجرا کنه:

A → B → C

پس Timeline میشه:

0 5 8 10
|--------|--------|--------|
A B C

حالا یکی‌یکی حساب کنیم


Job A

A
از زمان:

0

شروع شده و در:

5

تموم شده

پس:

Turnaround = 5 - 0
= 5

چون A بلافاصله اجرا شده:

Response = 0



Job B

B
در زمان:

5

شروع شده و در:

8

تموم شده.

پس:

Turnaround = 8 - 0
= 8

و چون B از زمان 0 تا 5 منتظر CPU بوده:

Response = 5



Job C

C
در زمان:

8

شروع شده و در:

10

تموم شده

پس:

Turnaround = 10 - 0
= 10

و:

Response = 8

چون C تا زمان 8 منتظر مونده تا بالاخره CPU بهش برسه


میانگین هم مهمه

معمولا قرار نیست فقط یک Process رو بررسی کنیم

میخوایم ببینیم الگوریتم ما در مجموع چه عملکردی داشته

پس میانگین میگیریم

مثلا:

Turnaround:

A = 5
B = 8
C = 10

میانگین:

(5 + 8 + 10) / 3
= 7.67

پس:

Average Turnaround Time = 7.67

همین کار رو میتونیم برای معیارهای دیگه مثل Response Time هم انجام بدیم



مشکل یک Scheduler ساده

حالا یه حالت جالب‌تر رو ببین.

فرض کنید داریم:

A = 100ms
B = 2ms
C = 2ms

و Scheduler هم بگه:

A → B → C

Timeline
میشه:

0 100 102 104
|------------------------------|------|------|
A B C

یعنی B و C باید منتظر بمونن تا A کارش تموم بشه

در نتیجه:

Response Time B = 100ms
Response Time C = 102ms

در حالی که خود B و C فقط 2ms کار دارن

یعنی یه Job سنگین میتونه باعث بشه Jobهای کوچیک مدت زیادی منتظر بمونن



اینجا Shortest Job First جالب میشه

اگر از قبل بدونیم مدت اجرای Job ها تقریبا چقدره می‌تونیم اول Job های کوتاه‌تر رو اجرا کنیم

مثلا:

A = 100ms
B = 2ms
C = 2ms

پس به‌ جای:

A → B → C

میتونیم داشته باشیم:

B → C → A

Timeline:

0 2 4 104
|----|----|--------------------------------|
B C A

حالا B و C خیلی سریع اولین پاسخ خودشون رو میگیرن

این ایده پایه الگوریتم:

Shortest Job First (SJF)

هست


ولی اینجا یک مشکل داریم

سیستم‌عامل معمولا از قبل دقیقا نمیدونه:

این Process قراره چند میلی‌ثانیه CPU مصرف کنه؟

مثلا یک برنامه ممکنه مدام بین CPU و I/O جا به‌ جا بشه:
CPU
↓
I/O
↓
CPU
↓
I/O
↓
CPU

یا مدت اجرای برنامه به ورودی کاربر وابسته باشه

پس اینکه از قبل دقیقا بدونیم هر Job چقدر CPU لازم داره همیشه ممکن نیست

اینجاست که Scheduling از یک صف ساده خیلی پیچیده‌ تر میشه



چرا این موضوع برای ما مهمه؟

شاید اولش فکر کنید این بحث چه ربطی به Reverse Engineering داره

ولی وقتی یک برنامه چند تا Thread داشته باشه اجرای واقعی برنامه فقط به ترتیب دستورهای داخل Binary بستگی نداره

مثلا:

Thread A
↓
Running
↓
Preempted

همزمان:

Thread B
↓
Running
↓
Blocked

بعد دوباره:

Thread A
↓
Running

یعنی اجرای برنامه میتونه بین Thread های مختلف جابه‌جا بشه

این موضوع وقتی مهم‌ تر میشه که با چیزهایی مثل این‌ها طرف باشیم:

Multi-threading
Race Condition
Deadlock
Timing Bug
Synchronization

مثلا ممکنه یک Bug فقط زمانی اتفاق بیفته که دو Thread تقریبا همزمان به یک بخش خاص از برنامه برسن

پس فقط نباید بپرسیم:

این دستور چی کار میکنه

باید بپرسیم:

این Thread کی اجرا شده؟ قبلش چه Thread ی اجرا شده؟ چرا اجرای این Thread متوقف شده و بعد دوباره برگشته؟

این دید بعدا برای تحلیل Bug های مربوط به Concurrency خیلی مهم میشه




فعلاً این سه معیار رو خوب یاد بگیرید:

Turnaround Time

Arrival → Completion

یعنی کل زمانی که Job از ورود تا پایان داخل سیستم بوده

Waiting Time

مدت انتظار برای CPU

یعنی چه مقدار از زمان Process در Ready Queue منتظر CPU بوده

Response Time


Arrival → First Run

یعنی چقدر طول کشیده تا Process برای اولین بار CPU بگیره.

به شکل خلاصه:

Arrival
│
├──── Waiting ────┐
│ │
▼ ▼
First Run Running
│ │
│ │
└── Response │
│
▼
Completion

Turnaround = Arrival → Completion

پس وقتی میخوایم دو الگوریتم Scheduling رو با هم مقایسه کنیم فقط نمیگیم:

کدوم سریع‌تره؟

باید ببینیم:

چقدر منتظر موندیم؟
چقدر سریع اولین جواب رو گرفتیم؟
چقدر طول کشید کار کامل بشه؟

این‌ها همون معیار هایی هستن که کمک میکنن بفهمیم یک Scheduler واقعا چقدر خوب عمل میکنه

@reverseengine
ReverseEngineering
Scheduling Metrics از کجا بفهمیم Scheduler خوبه؟ توی قسمت قبل فهمیدیم Scheduler مسئول اینه که مشخص کنه کدوم Thread یا Process نوبتش بشه و روی CPU اجرا بشه اما یه سؤال مهم داریم: از کجا بفهمیم یک الگوریتم Scheduling واقعا خوبه؟ فرض کنید سه تا Process داریم:…
Scheduling Metrics

How do we know if a Scheduler is good?

In the previous section, we learned that the Scheduler is responsible for determining which Thread or Process will get its turn and run on the CPU

But we have an important question:

How do we know if a Scheduling algorithm is really good?

Suppose we have three Processes:

A
B
C

Scheduler
can run these in different orders

So we need to have a set of criteria so that we can compare the performance of different algorithms

This is where we come to Scheduling Metrics

Turnaround Time

The first concept:

Turnaround Time

In simple terms, it means:

How much time has passed from the moment a Job enters the system to the moment its work is completely completed?

The formula is:

Turnaround Time =

Completion Time - Arrival Time

For example:

Arrival Time = 0

Completion Time = 10

So:

Turnaround Time = 10 - 0
= 10

That means this Process has been in the system for 10 time units from the moment it entered until the end

Waiting Time

Now another question:

How much time did the Process
just wait for the CPU?

Here we come to:

Waiting Time

Assume the state of a Process is as follows:

Arrival
↓
Waiting
↓
Running
↓
Waiting
↓
Running
↓
Finished

Any time the Process waits in the Ready Queue for the CPU is counted as Waiting Time

So very simple:

Waiting Time =
The amount of time the Process has been waiting for the CPU

Response Time

This one is especially important for interactive programs

Response Time
How long did it take from the time the Job entered the system until the first time the CPU reached it?

For example:

Arrival
│
│ 5ms
▼
First Run

So:

Response Time = 5ms

Now pay close attention to the difference with Turnaround Time

Response Time

Only until the first run:

Arrival → First Run

Turnaround Time

Until the complete end of the job:

Arrival → Completion

So a Process may run very quickly the first time, but it may take a long time to finish its work

A complete example

Let's assume we have three Jobs:

Job A
Arrival = 0
Runtime = 5

Job B
Arrival = 0
Runtime = 3

Job C
Arrival = 0
Runtime = 2

Let's assume the Scheduler decides to run in this order:

A → B → C

So the Timeline becomes:

0 5 8 10
|--------|--------|--------|
A B C

Now let's calculate one by one

Job A

A
started at:

0

and finished at:

5

So:

Turnaround = 5 - 0

= 5

Since A was executed immediately:

Response = 0

Job B

B
started at:

5

and finished at:

8

.

So:

Turnaround = 8 - 0
= 8

And since B waited for the CPU from time 0 to 5:

Response = 5

Job C

C
started at time:

8

and finished at:

10

So:

Turnaround = 10 - 0
= 10

And:

Response = 8

Because C waited until time 8 to finally get the CPU

The average is also important

Usually we are not going to examine just one Process

We want to see how our algorithm performed overall

So we take the average

For example:

Turnaround:

A = 5

B = 8

C = 10

Average:

(5 + 8 + 10) / 3
= 7.67

So:

Average Turnaround Time = 7.67

We can do the same for other metrics like Let's do Response Time too

A Simple Scheduler Problem

Now let's look at a more interesting case.

Suppose we have:

A = 100ms
B = 2ms
C = 2ms

And the Scheduler says:

A → B → C

Timeline
It becomes:

0 100 102 104
|-------------------------|------|------|
A B C

That means B and C have to wait until A finishes its work

As a result:

Response Time B = 100ms
Response Time C = 102ms

While B and C themselves only have 2ms of work

That means a heavy job can make small jobs wait for a long time

This is where Shortest Job First comes in handy

If we know in advance how long the jobs will take, we can execute the shorter jobs first

For example:

A = 100ms
B = 2ms
C = 2ms

So instead of:

A → B → C

we can have:

B → C → A

Timeline:

0 2 4 104
|----|----|--------------------------------|
B C A

Now B and C get their first response very quickly

This is the basic idea of ​​the algorithm:

Shortest Job First (SJF)

But here we have a problem

The operating system usually does not know in advance exactly:
CPU
↓
I/O
↓
CPU
↓
I/O
↓
CPU

Or the execution time of the program depends on user input

So it is not always possible to know in advance exactly how much CPU each job will need

This is where Scheduling becomes much more complex than a simple queue

9. Why is this important to us?

At first you might think what does this have to do with Reverse Engineering

But when a program has multiple threads, the actual execution of the program does not depend only on the order of the instructions in the binary

For example:

Thread A
↓
Running
↓
Preempted

Simultaneously:

Thread B
↓
Running
↓
Blocked

Then again:

Thread A
↓
Running

That is, the execution of the program can be switched between different threads

This becomes more important when we are dealing with things like:

Multi-threading

Race Condition

Deadlock

Timing Bug

Synchronization

For example, a bug may only occur when two threads reach a specific part of the program almost simultaneously

So we should not just ask:

What does this instruction do

We should ask:

When did this thread run? What thread ran before it? Why did this thread stop and then start again?

This view will be very important later for analyzing concurrency-related bugs

For now, learn these three metrics well:

Turnaround Time

Arrival → Completion

The total time the Job spent in the system from entry to completion

Waiting Time

The waiting time for CPU

How much time the Process spent in the Ready Queue waiting for the CPU

Response Time

Arrival → First Run

How long it took for the Process to get the CPU for the first time.

In summary:

Arrival
│
├─── Waiting ─────┐
│ │
▼ ▼
First Run Running
│ │
│ │
└── Response │
│
▼
Completion

Turnaround = Arrival → Completion

So when we want to compare two scheduling algorithms, we don't just say:

Which one is faster?

We need to see:

How long did we wait?

How fast did we get the first response?

How long did it take to complete the task?

These are the metrics that help us understand how well a Scheduler really performs

@reverseengine
قسمت سی و سوم بافر اورفلو


Taint Analysis on Binary

در Assembly از کجا بفهمیم داده کجا میره

میخوایم Taint Analysis رو روی باینری اجرا کنیم
اینجا دیگه خبری از اسم متغیر های مثل input و buffer نیست
باید با Registerها و ادرس‌های حافظه کار کنیم


پیدا کردن Source
اول باید بفهمیم داده از کجا وارد برنامه شده مثلا داخل یک برنامه لینوکسی ممکنه به این برسیم

call read

یا

call recv

فرض کنید قبلش اینو داریم

lea rsi,[rbp-40h]
mov edx,40h
call read

اینجا میتونیم مسیر ساده‌ای داشته باشیم

External Input
↓
read()
↓
[rbp-40h]

پس داده ورودی داخل [rbp-40h] قرار میگیره
این نقطه میتونه Source ما باشه


دنبال کردن داده

حالا چند دستور پایین‌ تر میبینیم

mov rax,[rbp-40h]
mov rcx,rax

پس مقدار از حافظه اومده داخل RAX
بعد وارد RCX شده
مسیر فعلا اینه

[rbp-40h]
↓
RAX
↓
RCX


دنبال کردن انتقال داده

فرض کنید بعدش اینو ببینیم

mov rdx,rcx
call process_data

حالا

[rbp-40h]
↓
RAX
↓
RCX
↓
RDX
↓
process_data()

یعنی داده‌ای که از بیرون اومده وارد process_data شده
حالا باید داخل اون تابع ادامه مسیر رو دنبال کنیم



رسیدن به Sink
فرض کنید داخل تابع به این برسیم

mov [rdi],rax

اینجا یک Memory Write داریم
پس مسیر کلی ما میشه

Input
↓
read()
↓
Stack / Memory
↓
Register
↓
Function
↓
Memory Write

این نقطه میتونه Sink موردنظر ما باشه
البته رسیدن داده به یک Write به تنهایی به معنی وجود آسیب‌ پذیری نیست
باید اندازه و محدوده دسترسی هم بررسی بشه

Taint
کردن Register ها

فرض کنید

mov rax,[rbp-40h]
و [rbp-40h] شامل داده خارجی باشه
پس
RAX = Tainted
بعد
mov rcx,rax
حالا
RCX = Tainted
بعد
add rcx,8

مقدار RCX تغییر کرده ولی همچنان به داده اولیه وابسته است
پس مسیر Taint قطع نشده
اما یک نکته مهم
همه دستورها باعث انتقال ساده Taint نمیشن

مثلا:
cmp rax,10h
اینجا داده مستقیما به Register دیگری منتقل نشده
ولی مقدار RAX روی تصمیم برنامه تاثیر گذاشته

مثلا:

cmp rax,10h
ja invalid

پس اینجا یک Control Dependency داریم یعنی داده خارجی روی مسیر اجرای برنامه اثر گذاشته

یک مثال کامل تر:
lea rsi,[rbp-40h]
mov edx,40h
call read

movzx eax,byte ptr [rbp-40h]

cmp eax,20h
ja invalid

mov edi,eax
call process_data

مسیر رو مرحله به مرحله ببینید

read()
↓
[rbp-40h]
↓
EAX
↓
cmp
↓
Control Flow
↓
process_data()

اینجا مقدار ورودی هم روی داده تاثیر گذاشته
هم روی مسیر اجرای برنامه

در Ghidra چه کار میکنیم
وقتی به یک مقدار مشکوک رسیدیم
اول References اون مقدار رو بررسی میکنیم بعد استفاده‌های بعدی رو پیدا میکنیم

مثلا:
Source
↓
Reference
↓
Register
↓
Memory Access
↓
Function Call

بعد همین کار رو برای تابع بعدی ادامه میدیم تا مسیر کامل مشخص بشه یک نکته مهم لازم نیست همیشه کل Taint Analysis رو دستی انجام بدید
در برنامه‌ های بزرگ این کار خیلی سخت میشه برای همین ابزارهای مختلفی وجود دارن که میتونن این کار رو تا حدی خودکار کنن

مثلا:
angr
Triton
DynamoRIO
Intel Pin

هرکدوم روش و کاربرد خودشون رو دارن

@reverseengine
ReverseEngineering
قسمت سی و سوم بافر اورفلو Taint Analysis on Binary در Assembly از کجا بفهمیم داده کجا میره میخوایم Taint Analysis رو روی باینری اجرا کنیم اینجا دیگه خبری از اسم متغیر های مثل input و buffer نیست باید با Registerها و ادرس‌های حافظه کار کنیم پیدا کردن…
Part 33 Buffer Overflow


Taint Analysis on Binary

In Assembly, how do we know where the data goes

We want to run Taint Analysis on binary
Here, there is no more mention of variable names like input and buffer

We have to work with registers and memory addresses

Finding the Source
First, we need to find out where the data entered the program from, for example, in a Linux program, we might get this

call read

or

call recv

Suppose we have this before

lea rsi,[rbp-40h]

mov edx,40h
call read

Here we can have a simple path

External Input
↓
read()
↓
[rbp-40h]

So the input data is placed in [rbp-40h]

This point can be our Source

Following the data

Now we see a few commands below

mov rax,[rbp-40h]

mov rcx,rax

So the value came from memory Inside RAX
Then entered RCX
The current path is

[rbp-40h]
↓
RAX
↓
RCX

Following data transfer

Suppose we see this later

mov rdx,rcx
call process_data

Now

[rbp-40h]
↓
RAX
↓
RCX
↓
RDX
↓
process_data()

That is, the data that came from outside entered process_data
Now we have to continue the path inside that function

Reaching Sink
Suppose we reach this inside the function

mov [rdi],rax

Here we have a Memory Write
So our general path becomes

Input
↓
read()
↓
Stack / Memory
↓
Register
↓
Function
↓
Memory Write

This point can be our desired Sink
Of course, reaching a Write alone does not mean there is a vulnerability
The size and access range should also be checked

Taint
Registers

Assuming

mov rax,[rbp-40h]

and [rbp-40h] contains external data

so
RAX = Tainted

then
mov rcx,rax

now
RCX = Tainted

then
add rcx,8

The RCX value has changed but is still dependent on the original data

so the Taint path is not broken

but one important point

not all instructions cause a simple Taint transfer

for example:

cmp rax,10h

here the data is not directly transferred to another register

but the RAX value affects the program's decision

for example:

cmp rax,10h
ja invalid

so here we have a Control Dependency, that is, the external data affects the program's execution path

A more complete example:

lea rsi,[rbp-40h]

mov edx,40h
call read

movzx eax,byte ptr [rbp-40h]

cmp eax,20h
ja invalid

mov edi,eax
call process_data

See the path step by step

read()
↓
[rbp-40h]
↓
EAX
↓
cmp
↓
Control Flow
↓
process_data()

Here the input value affects both the data and the execution path

What do we do in Ghidra
When we reach a suspicious value
First we check the References of that value, then we find the next uses

For example:
Source
↓
Reference
↓
Register
↓
Memory Access
↓
Function Call

Then we continue the same for the next function to determine the full path. One important point is that you don't always have to do the entire Taint Analysis manually
In large programs, this can be very difficult, so there are various tools that can automate this task to some extent

For example:

angr
Triton
DynamoRIO
Intel Pin

Each has its own method and application

@reverseengine
Memory Allocation
و تغییر RW → RX

تا اینجا درباره‌ی Call Stack گفتیم حالا یک لایه پایین‌ تر میریم سراغ خود حافظه

یکی از چیزهایی که EDR ها در تحلیل رفتار Processها زیر نظر میگیرن

Permission
حافظه
و تغییراتشه

اصلا RW و RX یعنی چی؟

هر بخش از حافظه میتونه Permission های مختلفی داشته باشه:

R = Read
W = Write
X = Execute

مثلا:

RW

یعنی:

Read + Write

برنامه میتونه اطلاعات داخل این قسمت رو بخونه و تغییر بده ولی قرار نیست از همون قسمت کد اجرا کنه

یا:

RX

یعنی:

Read + Execute

میتونه اطلاعات رو بخونه و از اون قسمت دستورهای CPU رو اجرا کنه ولی نباید بتونه محتوای اون صفحه رو تغییر بده

به‌صورت مفهومی ممکنه چنین زنجیره‌ای ببینیم:

Memory Allocation
↓
RW
↓
Write Data
↓
Permission Change
↓
RX
↓
Execution

یعنی یک بخش از حافظه اول قابل نوشتن بوده و بعد Permission اون به حالتی تغییر کرده که قابلیت Execute داشته باشه

همین تغییر به‌تنهایی به معنی Malware بودن نیست مثلا بعضی نرم‌افزارهای کاملا legitimate هم به دلایل مختلف کد تولید یا کامپایل میکنن

JIT
یکی از مثال‌های مهمه

بعضی Runtime ها از تکنولوژی‌ به اسم:

JIT = Just-In-Time Compilation

استفاده میکنن اینجا برنامه ممکنه در زمان اجرا بخشی از کد رو تولید کنه و برای اجرای اون نیاز به حافظه‌ی مناسب داشته باشه

پس:

RW → RX

میتونه در یک نرم‌ افزار سالم هم اتفاق بیفته

بنابراین یک EDR خیلی خوب نباید بگه:

RW → RX = Malware

چون این مدل منطق تقریبا همونقدر درسته که بگیم هر کسی هودی پوشید هکره 😂

چیزی که مهمه Context هست

EDR
معمولا قط یک Event رو نگاه نمیکنه
مثلا فرض کنید چنین زنجیره‌ای وجود داشته باشه:

Process
↓
Memory Allocation
↓
Write
↓
Protection Change
↓
RX Memory
↓
Thread Activity
↓
Call Stack
↓
Network Activity

هرکدوم به‌ تنهایی ممکنه کاملا عادی باشه
ولی وقتی چند رفتار غیرمعمول کنار هم قرار میگیرن ارزش تحلیلی بیشتری پیدا میکنن

به همین ددلیل Detection مدرن بیشتر شبیه اینه:

Event
+
Context
+
Process History
+
Memory Information
+
Thread Information
+
Call Stack


RWX
بعضی وقتا ممکنه Permission یک بخش به شکل زیر باشه:

RWX

یعنی:

Read
Write
Execute

در این حالت همون Memory Region هم قابل تغییر و هم قابل اجراست

از دید امنیتی این وضعیت معمولا حساس‌ تره چون اصل مهمی به نام:

W^X

وجود داره

یعنی:

Writable XOR Executable


به زبان ساده:

یک Memory Region بهتره همزمان هم Writable و هم Executable نباشه


البته باز هم:

RWX ≠ Malware

و:

RW → RX ≠ Malware

این‌ها فقط Signal هستن نه کار پایانی

DEP
یکی از مکانیزم‌ های مهم Windows:

Data Execution Prevention

هدفش اینه که بخش‌هایی از Memory که نباید کد اجرا کنن قابل Execute نباشن

مثلا اگر یک بخش فقط:

RW

باشه انتظار نداریم CPU از اونجا مثل یک Code Region معمولی دستور اجرا کنه
پس Permission های Memory بخشی از مدل امنیتی سیستم‌ عامل هستن نه صرفا یک عدد داخل Debugger

EDR
دقیقا به چه چیزهایی توجه میکنه؟
برای تحلیل یک Memory Region ممکنه اطلاعاتی مثل اینها اهمیت داشته باشه:


Process
├── Memory Type
├── Protection
├── Allocation History
├── Thread
├── Thread Start Address
├── Call Stack
├── Loaded Modules
├── Parent Process
└── Process Behavior

مثلا این دو وضعیت از نظر تحلیلی یکی نیستن:

Application
↓
JIT Activity
↓
RW → RX

در مقایسه با:

Unknown Process
↓
Private Memory
↓
Write
↓
RW → RX
↓
Suspicious Thread Activity

EDR
دقیقا به همین تفاوت Context اهمیت میده


نکته مهم
وقتی داخل Debugger یا Memory Viewer یک Region میبینید فقط به Address نگاه نکنید

این موارد رو هم بررسی کنید:

Address
Size
Protection
Type
State
Backing
Owner Process
Thread Relation

مثلا:

0x000001A...
Size: 0x5000
Protection: RX
Type: ...
State: ...

این اطلاعات کمک میکنن بفهمید با چه نوع Memory Region طرف هستید


پس فعلا این مدل ذهنی رو داشته باشید:

Memory
│
├── R
├── W
└── X

و یک تغییر Permission به‌ تنهایی چیزی رو ثابت نمیکنه
چیزی که برای تحلیل مهم‌ تره اینه:

Memory
+
Permission Change
+
Thread
+
Call Stack
+
Process Context
+
Behavior

@reverseengine
ReverseEngineering
Memory Allocation و تغییر RW → RX تا اینجا درباره‌ی Call Stack گفتیم حالا یک لایه پایین‌ تر میریم سراغ خود حافظه یکی از چیزهایی که EDR ها در تحلیل رفتار Processها زیر نظر میگیرن Permission حافظه و تغییراتشه اصلا RW و RX یعنی چی؟ هر بخش از حافظه میتونه…
Memory Allocation and RW → RX Transitions

We have discussed the call stack; now, let’s go a layer deeper and look at memory itself.

One of the things EDRs monitor when analyzing process behavior is memory permissions and their changes.

What exactly do RW and RX mean? Each memory segment can have different permissions:

R = Read
W = Write
X = Execute

For example:

RW

Means:

Read + Write

The program can read and modify the data in this section but is not intended to execute code from it.

Or:

RX

Means:

Read + Execute

It can read data and execute CPU instructions from that section but should not be able to modify the page's content.

Conceptually, we might see a sequence like this:

Memory Allocation
↓
RW
↓
Write Data
↓
Permission Change
↓
RX
↓
Execution

This means a memory segment was initially writable, and then its permission was changed to allow execution.

This change alone does not imply the presence of malware; for instance, some entirely legitimate software applications generate or compile code for various reasons.

JIT
is a key example.

Some runtimes utilize a technology called:

JIT = Just-In-Time Compilation

Here, the program might generate a portion of code during runtime and require suitable memory to execute it.

So:

RW → RX

can occur even in legitimate software.

Therefore, a high-quality EDR should not simply conclude:

RW → RX = Malware

Because that logic is about as accurate as saying anyone wearing a hoodie is a hacker 😂

What matters is the context.

An EDR
usually doesn't look at just a single event.
For example, imagine a sequence like this:

Process
↓
Memory Allocation
↓
Write
↓
Protection Change
↓
RX Memory
↓
Thread Activity
↓
Call Stack
↓
Network Activity

Each step might be perfectly normal on its own,
but when several unusual behaviors... ...they acquire greater analytical value.

For this reason, modern detection looks more like this:

Event
+
Context
+
Process History
+
Memory Information
+
Thread Information
+
Call Stack

RWX
Sometimes, a memory segment's permissions might look like this:

RWX

Meaning:

Read
Write
Execute

In this case, the memory region is both modifiable and executable.

From a security perspective, this situation is usually more critical because of an important principle known as:

W^X

Meaning:

Writable XOR Executable

Simply put:

It is best for a memory region not to be both Writable and Executable at the same time.

Of course:

RWX ≠ Malware

And:

RW → RX ≠ Malware

These are merely signals, not a definitive conclusion.

DEP
One of Windows' key mechanisms:

Data Execution Prevention

Its goal is to ensure that memory segments not intended to run code cannot be executed.

For instance, if a segment is only:

RW

...we do not expect the CPU to execute instructions from it as it would a standard code region.
Thus, memory permissions are part of the operating system's security model, not just a value within a debugger.

EDR
What exactly does it focus on? When analyzing a memory region, information such as the following can be significant:

Process
├── Memory Type
├── Protection
├── Allocation History
├── Thread
├── Thread Start Address
├── Call Stack
├── Loaded Modules
├── Parent Process
└── Process Behavior

For instance, from an analytical standpoint, these two scenarios are not the same:

Application
↓
JIT Activity
↓
RW → RX

Compared to:

Unknown Process
↓
Private Memory
↓
Write
↓
RW → RX
↓
Suspicious Thread Activity

EDR systems focus precisely on this contextual difference.

Important Note:
When viewing a memory region in a debugger or memory viewer, do not look solely at the address.

Examine these attributes as well:

Address
Size
Protection
Type
State
Backing
Owner Process
Thread Relation

For example:

0x000001A...
Size: 0x5000
Protection: RX
Type: ...
State: ...

This information helps you understand the nature of the memory region you are dealing with.

So, for now, keep this mental model in mind:
What is more important for analysis is:

Memory
+
Permission Change
+
Thread
+
Call Stack
+
Process Context
+
Behavior

@reverseengine