ReverseEngineering
1.32K subscribers
50 photos
11 videos
106 files
888 links
Download Telegram
Hook Detection
و مفهوم EDR Hooking

یکی از پایه‌ ای ترین چیز هایی که برای فهم بایپس EDR باید بدونید Hook هست وقتی یک EDR بخواد رفتار یک برنامه رو زیر نظر بگیره یکی از مکانیزم‌ هایی که ممکنه استفاده کنه اینه که مسیر اجرای بعضی Function ها رو تحت نظر بگیره

مثلا به‌صورت مفهومی:

Application
↓
Windows API
↓
EDR Monitoring
↓
Native API
↓
Kernel

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

Inline Hook

در Inline Hook ابتدای یک Function تغییر میکنه تا اجرای برنامه به مسیر دیگه ای هدایت بشه:

Original:

Function


├── Instruction 1
├── Instruction 2
├── Instruction 3

Hooked:

Function

│
└── Jump ──────► Monitoring Code
│
▼
Original Code

به همین دلیل وقتی درباره تکنیک‌ هایی مثل Unhooking, Direct Syscall و Indirect Syscall میخونید باید اول دقیقا بفهمید Hook چه مشکلی ایجاد میکنه

نکته مهم

Hook
شدن به معنای اینه که EDR تمام سیستم رو از طریق همین روش میبینه نیست

EDR
های مدرن فقط به یک Hook وابسته نیستن و میتونن از چندین منبع telemetry استفاده کنن

برای همین این زنجیره رو باید اینطوری ببینید:

API Hook
+
ETW
+
Kernel Telemetry
+
Process Behavior
+
Memory Analysis
+
Network Telemetry

و این دقیقا دلیل پیچیده شدن بحث AV/EDR Evasion در سال‌ های اخیر شده

@reverseengine


Hook Detection And the Concept of EDR Hooking

One of the most basic things you need to know to understand EDR bypass is Hook. When an EDR wants to monitor the behavior of an application, one of the mechanisms it may use is to monitor the execution path of some Function

For example, conceptually:

Application
↓
Windows API
↓
EDR Monitoring
↓
Native API
↓
Kernel

That is, the application thinks it has called a normal function, but before reaching the main path, a monitoring layer can check the activity

Inline Hook

In an Inline Hook, the beginning of a Function is changed to redirect the execution of the application to another path:

Original:

Function

├── Instruction 1
├── Instruction 2
├── Instruction 3

Hooked:

Function

│
└── Jump ──────► Monitoring Code
│
▼
Original Code

That is why when you read about techniques such as Unhooking, Direct Syscall and Indirect Syscall, you should First, understand exactly what the problem is with Hooks

Important point

Being Hooked
does not mean that EDR sees the entire system in this way

Modern EDRs do not rely on just one Hook and can use multiple telemetry sources

So you should think of this chain as:

API Hook
+
ETW
+
Kernel Telemetry
+
Process Behavior
+
Memory Analysis
+
Network Telemetry

And this is exactly why the AV/EDR Evasion discussion has become so complicated in recent years

@reverseengine
Opaque Predicate
شرط‌ هایی که ظاهرشون گمراه‌ کننده ست

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

Opaque Predicate
یعنی برنامه یک شرط میسازه که از ظاهرش مشخص نیست نتیجه همیشه چی میشه ولی سازنده از قبل میدونه نتیجه چیه

مثلا از نظر منطقی ممکنه چنین چیزی داشته باشیم:

int x = 10;

if ((x * x) % 2 == 0)
{
A();
}
else
{
B();
}


اگه مقدار x همیشه 10 باشه شرط همیشه درست میشه ولی وقتی همین منطق به اسمبلی تبدیل و با محاسبات اضافه قاطی بشه تشخیصش سخت تر میشه
مثلا ممکنه به جای یک شرط ساده چند عملیات پشت سر هم ببینید:

محاسبه یک مقدار

XOR

ADD

CMP

JZ

Decompiler
هم ممکنه از این عملیات یک شرط خیلی پیچیده بسازه اینجاست که تحلیلگر باید فقط به ظاهر شرط اعتماد نکنه

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

فرض کنید به چنین ساختاری رسیدید:

شرط:
/ \ مسیرA
/ \ مسیر B
\ / ادامه


اولین سوال این نیست که کدوم مسیر بهتر به نظر میرسه

سوال اینه:

مسیر B اصلا قابل رسیدنه؟

اگر جواب نه باشه احتمالا با یک Opaque Predicate یا یک شرط غیرقابل‌ وقوع طرف هستیم

یکی از کاربردهای این تکنیک شلوغ کردن Control Flow Graph هست

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

اما بخشی از این مسیرها فقط برای گمراه کردن تحلیل ایجاد شدن

نکته مهم اینه که Opaque Predicate همیشه به معنی یک شرط ثابت مثل true یا false نیست

بعضی وقتا نتیجه شرط به یک invariant وابسته ست

یعنی مقداری که با وجود تغییرات مختلف برنامه یک ویژگی مشخص رو حفظ میکنه

برای همین شما باید منطق ریاضی و رفتار داده‌ ها رو هم بررسی کنید

تمرین

این شرط رو بررسی کنید:

int x = 10;

if ((x * 2) % 2 == 0)
{
printf("A");
}
else
{
printf("B");
}


بدون اجرای برنامه مشخص کنید کدوم مسیر میتونه اجرا بشه بعد همین ایده رو با یک شرط پیچیده‌ تر امتحان کنید و ببینید میتونید از روی منطق ریاضی تشخیص بدید کدوم شاخه عملا غیرقابل‌اجراست

این دقیقا همون ذهنیتی هست که برای تحلیل Obfuscation لازم داریم

@reverseengine
ReverseEngineering
Opaque Predicate شرط‌ هایی که ظاهرشون گمراه‌ کننده ست حالا یکی از تکنیک‌ هایی رو که میخایم بررسی کنیم که معمولا کنار Obfuscation استفاده میشه Opaque Predicate یعنی برنامه یک شرط میسازه که از ظاهرش مشخص نیست نتیجه همیشه چی میشه ولی سازنده از قبل میدونه نتیجه…
Opaque Predicate

Conditions that are deceptive in appearance

Now we will examine one of the techniques that is usually used alongside Obfuscation

Opaque Predicate
That is, the program creates a condition that is not always clear from its appearance what the result will be, but the constructor already knows what the result will be

For example, logically we might have something like this:

int x = 10;

if ((x * x) % 2 == 0)
{
A();
}
else
{
B();
}


If the value of x is always 10, the condition will always be true, but when this logic is converted to assembly and mixed with additional calculations, it becomes more difficult to detect. For example, instead of a simple condition, you may see several operations in a row:

Calculate a value

XOR

ADD

CMP

JZ


Decompiler
may also make a very complex condition out of this operation. This is where the analyst should not just trust the appearance of the condition

He should understand whether both paths are actually feasible or whether one of the paths has never actually happened.

Suppose you get this structure:

Condition:

/ \ PathA
/ \ PathB
\ / continue

The first question is not which path looks better

The question is:

Is path B even feasible?

If the answer is no, we are probably dealing with an Opaque Predicate or an unfulfilled condition

One of the uses of this technique is to clutter the Control Flow Graph

When the analyst opens the graph, he sees a lot of paths

But some of these paths are created just to mislead the analysis

The important point is that Opaque Predicate does not always mean a fixed condition such as true or false

Sometimes the result of the condition depends on an invariant

That is, a value that maintains a certain property despite various program changes

For this reason, you should also examine the mathematical logic and data behavior

Exercise:

Examine this condition:

int x = 10;

if ((x * 2) % 2 == 0)

{
printf("A");
}
else

{
printf("B");
}


Determine which path can be executed without executing the program. Then try the same idea with a more complex condition and see if you can mathematically determine which branch is practically infeasible.

This is exactly the mindset we need for Obfuscation Analysis.

@reverseengine
Tcache

در پست قبل گفتیم که glibc برای مدیریت Chunk های آزاد از ساختار های مختلفی مثل Bin ها استفاده میکنه اما یک ساختار مهم وجود داره که باید جداگانه بشناسیم:

Tcache
اصلا چرا ساخته شد؟
برای پیدا کردن و استفاده مجدد از Chunk های آزاد Allocator بیشتر به ساختار های عمومی Heap متکی بود
مشکل اینجا بود که در برنامه‌های چند نخی چند Thread میتونستن همزمان بخوان از همین ساختار ها استفاده کنن

پس:

مدیریت حافظه میتونست هزینه و پیچیدگی بیشتری داشته باشه


glibc
برای سریع‌ تر کردن تخصیص حافظه Thread Cache یا همون Tcache رو معرفی کرد

اسم Tcache از کجا میاد؟

Tcache Thread Local Cache
هست

یعنی هر Thread یک Cache مخصوص خودش داره

در نتیجه وقتی یک Thread حافظه‌ ای رو آزاد میکنه در بسیاری از موارد میتونه اون Chunk رو در Cache خودش نگه داره
بعد اگر همون

Thread


دوباره حافظه‌ ای با اندازه مناسب بخواد Allocator میتونه خیلی سریع از Tcache استفاده کنه

ساختارش چطوریه؟

اگر خیلی ساده تصورش کنیم:

Thread
│
└── Tcache
├── Chunk
├── Chunk
├── Chunk

Tcache
برای اندازه‌های مختلف مجموعه‌ ای از لیست‌های آزاد داره

مثلا میتونید ذهنی تصور کنید:

size A → chunk → chunk → chunk

size B → chunk → chunk

size C → chunk → chunk → chunk

یعنی Chunk ها بر اساس اندازه در دسته‌ های مختلف قرار میگیرن

وقتی malloc اجرا میشه چه اتفاقی میوفته؟

فرض کنید برنامه درخواست حافظه میکنه:

malloc(size);

Allocator
اول بررسی میکنه آیا Chunk مناسبی در مسیر های سریع خودش وجود داره یا نه
اگر Chunk مناسب در Tcache موجود باشه میتونه همون رو در اختیار برنامه قرار بده در این حالت لازم نیست هر بار سراغ ساختار های پیچیده‌تر Heap بره

نتیجه؟

سرعت بیشتر

وقتی free اجرا میشه چی؟
برعکس همین اتفاق هم ممکنه رخ بده
وقتی برنامه مینویسه:

free(ptr);

Chunk
آزاد شده میتونه در Tcache قرار بگیره تا بعدا دوباره مورد استفاده قرار بگیره

پس:

malloc()
↓
استفاده از Chunk
↓
free()
↓
Tcache
↓
malloc()
↓
استفاده مجدد

این چرخه یکی از پایه‌های مهم رفتار Heap در glibc محسوب میشه

چرا Tcache برای ما در Binary Exploitation مهمه؟

اینجا قضیه جالب میشه
هر چیزی که حافظه رو سریع‌ تر مدیریت کنه در صورت وجود یک Memory Corruption Bug میتونه رفتار متفاوتی نسبت به ساختارهای قدیمی‌ تر Heap داشته باشه

به همین دلیل وقتی یک Heap Bug رو بررسی میکنیم باید بدونیم:

Chunk
موردنظر الان کجاست؟


ممکنه:
داخل Tcache باشه
داخل یکی از Binهای دیگه باشه
هنوز در اختیار برنامه باشه
یا بخشی از Heap بزرگ‌تر شده باشه
پس بدون شناخت Tcache تحلیل Heap مدرن ناقص میمونه

یک نکته مهم
Tcache
خودش یک آسیب‌پذیری نیست
اتفاقا برای بهبود عملکرد Allocator طراحی شده
اما وقتی یک برنامه دچار باگ‌هایی مثل:

Use-After-Free
یا
Double Free

میشه رفتار Tcache میتونه روی نحوه‌ ی بروز و تحلیل مشکل تاثیر بذاره به همین دلیل در مباحث Heap Exploitation اسم Tcache رو زیاد میبینیم


Tcache

یک Thread Local Cache در glibc که برای سریع‌ تر کردن مدیریت Chunk های کوچیک طراحی شده هر Thread میتونه Chunk های آزاد شده رو داخل Cache خودش نگه داره و در درخواست‌ های بعدی دوباره از اونها استفاده کنه
برای فهم Heap مدرن شناخت Tcache خیلی مهمه چون رفتار malloc() و free() در حضور Tcache با مدل‌ های قدیمی‌ تر Heap تفاوت‌ هایی داره
@reverseengine
ReverseEngineering
Tcache در پست قبل گفتیم که glibc برای مدیریت Chunk های آزاد از ساختار های مختلفی مثل Bin ها استفاده میکنه اما یک ساختار مهم وجود داره که باید جداگانه بشناسیم: Tcache اصلا چرا ساخته شد؟ برای پیدا کردن و استفاده مجدد از Chunk های آزاد Allocator بیشتر به…
Tcache In the previous post, we said that glibc uses various structures such as Bins to manage free Chunks, but there is an important structure that we need to know separately:

Why was Tcache created at all?

Allocator relied more on general Heap structures to find and reuse free Chunks

The problem was that in multi-threaded programs, multiple threads could read from the same structures at the same time

So:

Memory management could be more expensive and complex

glibc introduced Thread Cache, or Tcache, to speed up memory allocation

Where does the name Tcache come from?

Tcache is Thread Local Cache

That is, each Thread has its own Cache

As a result, when a Thread frees memory, in many cases it can keep that Chunk in its Cache

Then, if the same

Thread

requests memory of the appropriate size again, the Allocator can use Tcache very quickly

What is its structure?

If we imagine it very simply:

Thread
│
└── Tcache
├── Chunk
├── Chunk
├── Chunk

Tcache
has a set of free lists for different sizes

For example, you can imagine:

size A → chunk → chunk → chunk

size B → chunk → chunk

size C → chunk → chunk → chunk → chunk

That is, the Chunks are placed in different categories based on size

What happens when malloc is executed?

Suppose the program requests memory:

malloc(size);


Allocator
First, it checks whether there is a suitable Chunk in its fast paths or not. If a suitable Chunk is available in Tcache, it can make it available to the program. In this case, it does not need to resort to more complex Heap structures every time.

Result?

Higher speed

What happens when free is executed?
The opposite can also happen
When the program writes:

free(ptr);

The freed chunk can be placed in Tcache to be reused later

Then:

malloc()
↓
Using Chunk
↓
free()
↓
Tcache
↓
malloc()
↓
Reuse


This cycle is one of the important foundations of the Heap behavior in glibc

Why is Tcache important to us in Binary Exploitation?

Here's where it gets interesting
Anything that manages memory faster can behave differently than older Heap structures if there is a Memory Corruption Bug

That's why when we investigate a Heap Bug we need to know:

Where is the Chunk
in question now?

It may be:

Inside Tcache
Inside one of the other bins
Still in the hands of the program
Or part of the Heap has grown bigger
So without knowing Tcache, the analysis of the modern Heap remains incomplete

An important point

Tcache
Itself is not a vulnerability
It is designed to improve the performance of the Allocator
However, when a program has bugs such as:

Use-After-Free
Or
Double Free

The behavior of Tcache can affect the way the problem occurs and is analyzed, which is why we often see the name Tcache in Heap Exploitation discussions

Tcache

A Thread Local Cache in glibc that is designed to speed up the management of small Chunks. Each Thread can keep the freed Chunks in its own Cache and reuse them in subsequent requests
To understand the modern Heap, it is very important to understand Tcache because the behavior of malloc() and free() in the presence of Tcache is different from older models of Heap

@reverseengine
بخش بیست و پنجم بافر اورفلو

Address Sanitizer
یا ASan شکار باگ‌ های حافظه


تا اینجا یاد گرفتیم با Fuzzing میشه ورودی‌ های زیادی به برنامه داد و کرش‌ ها رو پیدا کرد

نکته مهم
وقتی برنامه کرش کرد از کجا بفهمیم دقیقا چه اتفاقی افتاده

اینجاست که AddressSanitizer یا ASan وارد میشه
ASan
یک ابزار برای پیدا کردن خطا های مربوط به حافظه در زمان اجرای برنامه است

ASan
چیکار میکنه
برنامه رو با یک سری بررسی‌ های اضافه اجرا میکنه

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

Stack Buffer Overflow
Heap Buffer Overflow
Use After Free
Out of Bounds Access

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

یک مثال ساده:

فایل
C
#include <stdio.h>
#include <string.h>

int main()
{
char buf[8];

strcpy(buf,"AAAAAAAAAAAAAAAA");

printf("%s\n",buf);

return 0;
}



اینجا بافر فقط 8 بایت جا داره
ولی رشته خیلی بزرگ‌ تره

کامپایل با ASan
shell

gcc -g -fsanitize=address file21_demo.c -o file21_demo



حالا اجراش میکنیم
shell

./file21_demo

ASan
متوجه میشه که برنامه خارج از محدوده buf نوشته
و یک گزارش خطا نمایش میده

نکته مهم گزارش ASan

یکی از جذاب‌ ترین قسمت‌ های ASan اینه که معمولا اطلاعات مفیدی درباره خطا میده
مثلا مشخص میکنه

ERROR: AddressSanitizer
stack-buffer-overflow

بعد پایین‌ تر معمولا Stack Trace رو هم میبینید یعنی میفهمید خطا از چه تابعی و در چه خطی ایجاد شده

چرا برای Reverse Engineering مهمه؟

فرض کنید سورس کد ندارید یا برنامه خیلی پیچیده است اگر نسخه‌ای از برنامه رو با Instrumentation مناسب داشته باشید

ASan
میتونه بهتون کمک کنه بفهمید یک ورودی خاص دقیقا چه نوع Memory Bug ایجاد کرده
بعد همون نقطه رو میبرید داخل Ghidra یا IDA و از روی Assembly بررسیش کنید

یعنی مسیرمون میشه

Fuzzing
↓
Crash
↓
ASan
↓
محل خطا
↓
Ghidra / IDA
↓
تحلیل Assembly

این ترکیب برای پیدا کردن و تحلیل Memory Bug خیلی قدرتمنده

یک نکته مهم

ASan
خودش جلوی همه آسیب‌پذیری‌ ها رو نمیگیره کار اصلیش اینه که در محیط توسعه و تست

خطا های حافظه رو سریع‌ تر پیدا کنیم
برای همین معمولا کنار Fuzzing استفاده میشه


Fuzzer
برنامه با این ورودی خراب شد

ASan
کمک میکنه بفهمید
دقیقا چه نوع خطای حافظه‌ ای اتفاق افتاده و کجا

بعد Reverse Engineer میاد همون

قسمت رو با Ghidra یا IDA بررسی میکنه

تمرین این قسمت:

همین file21_demo.c رو با ASan کامپایل کنید
بعد گزارش ASan رو نگاه کنید و سه چیز رو پیدا کنید

نوع خطا

تابعی که خطا داخلش اتفاق افتاده

خط کدی که باعث خطا شده

@reverseengine
ReverseEngineering
بخش بیست و پنجم بافر اورفلو Address Sanitizer یا ASan شکار باگ‌ های حافظه تا اینجا یاد گرفتیم با Fuzzing میشه ورودی‌ های زیادی به برنامه داد و کرش‌ ها رو پیدا کرد نکته مهم وقتی برنامه کرش کرد از کجا بفهمیم دقیقا چه اتفاقی افتاده اینجاست که AddressSanitizer…
Part 25 Buffer Overflow


Address Sanitizer
or ASan Hunting for Memory Bugs

So far, we have learned that with Fuzzing, you can give a lot of input to the program and find crashes

Important point
When the program crashes, how do we find out exactly what happened

This is where AddressSanitizer or ASan comes in

ASan
is a tool for finding memory-related errors during program execution

What ASan does
It runs the program with a series of additional checks

For example, it can find things like:

Stack Buffer Overflow

Heap Buffer Overflow

Use After Free

Out of Bounds Access

That is, instead of just saying that the program crashed, you can find out where in the program the problem occurred

A simple example:

C

#include <stdio.h>

#include <string.h>

int main()

{
char buf[8];

strcpy(buf,"AAAAAAAAAAAAAA");

printf("%s\n",buf);

return 0;
}


Here the buffer is only 8 bytes

But the string is much larger

Compile with ASan

shell

gcc -g -fsanitize=address file21_demo.c -o file21_demo

Now we run it
shell

./file21_demo

ASan
notices that the program wrote outside the buf range

And displays an error report

Important point of ASan report

One of the most interesting parts of ASan is that it usually gives useful information about the error

For example, it specifies

ERROR: AddressSanitizer

stack-buffer-overflow

Then below you usually see the Stack Trace, which means you understand which function and line the error occurred

Why is it important for Reverse Engineering?

Suppose you don't have the source code or the program is very complex. If you have a version of the program with proper instrumentation,

ASan
can help you find out exactly what kind of memory bug a particular input caused.

Then you take that point into Ghidra or IDA and examine it from the assembly.

That is, our path will be

Fuzzing
↓
Crash
↓
ASan
↓
Error location
↓
Ghidra / IDA
↓
Assembly analysis

This combination is very powerful for finding and analyzing memory bugs.

An important point

ASan
itself does not prevent all vulnerabilities. Its main job is to find memory errors faster in the development and test environment.

That is why it is usually used together with Fuzzing.

Fuzzer
The program crashed with this input.

ASan
helps you find out exactly what kind of memory error occurred and where.

Then the reverse engineer comes and examines the same part with Ghidra or IDA.

Exercise of this part:

This is Compile file21_demo.c with ASan

Then look at the ASan report and find three things

Type of error

Function where the error occurred

Line of code that caused the error

@reverseengine
Fork
چطور یه Process جدید ساخته میشه؟
تا اینجا درباره Process API حرف زدیم و گفتیم برنامه‌ ها چطوری میتونن با Process ها کار کنن

یکی از مهم‌ ترین چیز هایی که اینجا باهاش سروکار داریم fork() هست

ولی اصلا fork() چیکار میکنه؟

خیلی ساده بخوایم بگیم:

fork()
از Process فعلی یه Process جدید به اسم Child میسازه


یعنی قبل از fork() فقط یه Process داریم:
Parent
بعد از fork():
Parent

+

Child
حالا دو تا Process داریم که هر دو از همون جایی که fork() اجرا شده به اجرای برنامه ادامه میدن
اما یه نکته مهم این وسط هست

Child
صرفا یه کپی ساده از فایل برنامه نیست

سیستم‌ عامل برای Child یه Process مستقل ایجاد میکنه
Child
معمولا:

فضای آدرس مجازی خودش رو داره

PID
متفاوتی داره
وضعیت اجرای خودش رو داره
و منابع قابل مدیریت خودش رو داره
ولی در لحظه‌ای که ساخته میشه وضعیت حافظه و اجرای اون خیلی شبیه Parent هست

پس چرا میگیم Child شبیه کپی Parent هست

چون وقتی Child ساخته میشه سیستم‌عامل کاری می‌کنه که Child تقریبا همون وضعیت Parent رو داشته باشه
مثلا فرض کنید Parent قبل از fork() این متغیر رو داشته باشه:


int x = 10;

Child
هم در فضای آدرس خودش مقدار مشابه ای برای x داره
ولی این به این معنی نیست که Parent و Child دارن از یه متغیر معمولی مشترک استفاده میکنن

هر Process فضای حافظه مجازی خودش رو داره

یه ویژگی خیلی جالب fork()

fork()
توی Parent و Child مقدار برگشتی یکسانی نداره

توی Parent مقدار برگشتی معمولا PID مربوط به Child

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

پس برنامه میتونه بفهمه:

من Parent ام؟

یا Child؟

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

مثلا:

pid = fork();

if (pid == 0)
// Child

else
// Parent

بعد از اجرای fork() هر دو Process از همون نقطه به اجرای برنامه ادامه میدن
ولی مقدار pid بهشون میگه که الان داخل Parent هستن یا Child

حالا یه سوال مهم‌تر:

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

اینجاست که exec() وارد داستان میشه
معمولا توی سیستم‌های Unix یه الگوی خیلی معروف داریم:
Parent
↓
fork()
↓
Child
↓
exec()
↓
New Program
یعنی:
fork() → ساختن Child

و بعد:

exec() → جایگزین کردن برنامه داخل
Child با برنامه‌ای که میخوایم اجرا بشه

مثلا یه Shell رو در نظر بگیرید

Shell
میتونه یه Child بسازه و بعد Child رو با exec() تبدیل کنه به برنامه‌ای که کاربر درخواست کرده

حالا این موضوع چرا برای مهندسی معکوس مهمه؟

وقتی توی یه برنامه لینوکسی به fork() برخورد میکنید باید حواستون باشه که از اینجا به بعد دیگه فقط با یه مسیر اجرای برنامه طرف نیستید

یه Process جدید وارد ماجرا شده

مثلا:
Parent

│

├── fork()

│

├────────► Child

│

▼

ادامه Parent
یعنی ممکنه رفتار برنامه بین Parent و Child تقسیم شده باشه پس موقع تحلیل یه برنامه اگه به fork() رسیدید حتما باید این احتمال رو در نظر بگیرید که از این نقطه به بعد دو تا Process جداگانه دارید که ممکنه هرکدوم رفتار متفاوتی داشته باشن
این موضوع بعدا توی تحلیل Process ها Debugging و بررسی رفتار برنامه‌ها خیلی به دردتون میخوره


fork()
یه Child Process جدید میسازه

Parent
و Child هر دو به اجرای برنامه ادامه میدن
Child
یه PID متفاوت داره
هر Process فضای آدرس مجازی خودش رو داره مقدار برگشتی fork() کمک میکنه بفهمیم داخل Parent هستیم یا Child و معمولا fork() رو در کنار exec() میبینیم

و اینجا یکی از ایده‌های مهم رو خیلی خوب میشه دید:

سیستم‌عامل فقط برنامه‌ها رو اجرا نمیکنه بلکه یه محیط میسازه که چند تا Process بتونن مستقل از هم اجرا بشن

@reverseengine
ReverseEngineering
Fork چطور یه Process جدید ساخته میشه؟ تا اینجا درباره Process API حرف زدیم و گفتیم برنامه‌ ها چطوری میتونن با Process ها کار کنن یکی از مهم‌ ترین چیز هایی که اینجا باهاش سروکار داریم fork() هست ولی اصلا fork() چیکار میکنه؟ خیلی ساده بخوایم بگیم: fork()…
Fork

How do you create a new Process?
So far we've talked about the Process API and how programs can work with Processes

One of the most important things we're dealing with here is fork()

But what does fork() do?

To put it very simply:

fork()
creates a new Process called Child from the current Process

That is, before fork() we only have one Process:
Parent

After fork():
Parent

+
Child
Now we have two Processes, both of which continue to execute the program from the same place where fork() was executed

But there is an important point here

Child
is not just a simple copy of the program file

The operating system creates an independent Process for Child

Child
usually:

It has its own virtual address space

It has a different PID

It has its own execution state

And it has its own manageable resources

But at the moment it is created, its memory and execution state are very similar to Parent

So why do we say that Child is like a copy of Parent

Because when Child is created, the operating system makes Child have almost the same state as Parent

For example, suppose Parent has this variable before fork():

int x = 10;

Child
also has the same value for x in its address space
But this does not mean that Parent and Child are using a common common variable
Each Process has its own virtual memory space
A very interesting feature of fork()

fork()
does not have the same return value in Parent and Child

In Parent the return value is usually the PID of Child

In Child the return value is 0
If there is a problem while creating Child, Parent gets a negative value

So the program can understand:

Am I Parent?

Or Child?

And based on that execute a different path

For example:

pid = fork();

if (pid == 0) // Child

else // Parent

After executing fork() both Processes continue executing the program from the same point
But the pid value tells them whether they are now inside Parent or Child

Now a more important question:

If Child is created almost exactly like Parent, how can it execute a completely different program?

This is where exec() comes into play.
Usually, in Unix systems, we have a very famous pattern:
Parent
↓
fork()
↓
Child
↓
exec()
↓
New Program

That is:
fork() → create Child

And then:

exec() → replace the program inside
Child with the program we want to run
For example, consider a Shell

The Shell
can create a Child and then use exec() to convert the Child into the program requested by the user

Now why is this important for reverse engineering?

When you encounter fork() in a Linux program, you should be aware that from here on, you are no longer dealing with just one path of program execution

A new process has entered the story

For example:
Parent

│

├── fork()

│

├───────► Child

│

▼

Continuation of Parent
This means that the behavior of the program may be divided between Parent and Child
So when analyzing a program, if you reach fork(), you must definitely consider the possibility that from this point on, you have two separate processes, each of which may have different behavior. This will be very useful later in analyzing processes, debugging, and examining program behavior

fork()
creates a new Child Process

Parent
and Child
both continue executing the program
Child
has a different PID
Each Process has its own virtual address space
The return value of fork() helps us know whether we are inside Parent or Child
And we usually see fork() next to exec()

And here one of the important ideas is very important It's easy to see:

The operating system doesn't just run programs, it creates an environment where multiple processes can run independently

@reverseengine
User Mode
در برابر Kernel Mode

برای اینکه بفهمید چرا تکنیک‌ هایی مثل Direct Syscall اصلا تعریف شدن اول باید بدونید ویندوز دو تعریف مهم داره:

┌─────────────────────────┐
│ User Mode │
│ Applications / DLLs │
└───────────┬─────────────┘
│
System Call
│
▼
┌─────────────────────────┐
│ Kernel Mode │
│ Windows Kernel / Drivers│
└─────────────────────────┘

User Mode

برنامه‌های معمولی اینجا اجرا میشن:

Browser
PowerShell
Game
Your Program
↓
kernel32.dll
↓
ntdll.dll
↓
System Call

EDR
ها میتونن در این لایه telemetry جمع‌ آوری کنن و رفتار برنامه رو بررسی کنن




Kernel Mode

اینجا بخش‌ های حساس سیستم‌ عامل قرار دارن

مثلا مدیریت:

Processes

Threads

Memory

Drivers

File System

Network


به همین دلیل اگر فقط یک لایه از User Mode رو دور بزنید به معنی نامرئی شدن نیست.


-

Direct Syscall
چه مفهومی داره؟

ایده اصلی اینه که به‌جای طی کردن مسیر معمول User-Mode API برنامه مستقیما به مرز System Call نزدیک بشید


مفهوم:

Normal:

Application
↓
Win32 API
↓
ntdll
↓
System Call
↓
Kernel


Direct Syscall:

Application
↓
System Call
↓
Kernel

اما نکته مهم همینجاست:

Direct Syscall
به معنی دور زدن کامل EDR نیست

چون Kernel و سایر منابع telemetry همچنان میتونن رفتار اتفاق‌ افتاده رو ببینن




پس چرا هکر ها بهش علاقه دارن؟

چون اگر یک مکانیزم دفاعی مشخص در User Mode قرار گرفته باشه تغییر مسیر اجرای برنامه میتونه روی همان مکانیزم اثر بذاره

ولی EDR مدرن فقط به یک نقطه وابسته نیست:

┌── User Mode
│
Process ─────┼── Memory
│
├── ETW / Telemetry
│
├── Kernel
│
└── Network

بنابراین Evasion واقعی یک بازی چند لایه ست نه پیدا کردن یک API عجیب و غریب

نکته‌ای که باید یاد بگیرید

اگر بخواید AV/EDR Evasion رو واقعا بفهمید نباید از حفظ کردن تکنیک‌ها شروع کنید

باید بفهمید:

دفاع کجاست
چه چیزی رو میتونه ببینه
چه telemetry دریافت میکنه
مهاجم تلاش میکنه کدوم visibility رو کاهش بده





User Mode vs Kernel Mode

To understand why techniques like Direct Syscall were defined at all, you first need to know that Windows has two important definitions:

┌───────────────────────────┐
│ User Mode │
│ Applications / DLLs │
└─
│
System Call
│
▼
┌────────────� Management:

Processes

Threads

Memory

Drivers

File System

Network

That's why bypassing just one layer of User Mode doesn't mean you'll be invisible.



Direct Syscall
What does it mean?

The main idea is to approach the System Call boundary directly instead of going through the usual User-Mode API path.

Conceptual:

Normal:

Application
↓
Win32 API
↓
ntdll
↓
System Call
↓
Kernel

Direct Syscall:

Application
↓
System Call
↓
Kernel

But here's the important point:

Direct Syscall
does not mean bypassing EDR completely

Because the Kernel and other telemetry sources can still see the behavior that happened

So why are hackers interested in it?

Because if a specific defense mechanism is in User Mode, rerouting the program can affect that mechanism

But modern EDR is not just about one point:

┌── User Mode
│
Process ──────┼── Memory
│
├── ETW / Telemetry
│
├── Kernel
│
└── Network

So real evasion is a multi-layered game, not about finding a weird API

What you need to learn

If you really want to understand AV/EDR evasion, you shouldn't start by memorizing techniques

You need to understand:

Where is the defense
What can it see
What telemetry is it receiving
What visibility is the attacker trying to reduce

@reverseengine
Heap Overflow

یکی از معروف‌ترین باگ‌های Heap:

Heap Overflow
,
ایده‌اش خیلی شبیه Buffer Overflow روی Stack هست با این تفاوت که این بار سر ریز داخل Heap اتفاق میوفته

Heap Overflow
فرض کنید برنامه از Heap یک Chunk با ظرفیت مشخص میگیره:

C
char *buffer = malloc(64);


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

به این اتفاق میگیم:
Heap Overflow
به شکل ساده:

[ Buffer A ][ Buffer B ]

████████████████████
↓
نوشتن بیش از ظرفیت A
↓
[ Buffer A ][AAAAAAA...]
↑
وارد محدوده B


چرا این اتفاق خطرناکه؟

چون Chunk ها معمولا کنار هم قرار میگیرن پس اگر برنامه از محدوده‌ ی Chunk خودش خارج بشه ممکنه داده‌ های مربوط به حافظه‌ ی مجاور رو تغییر بده
اون حافظه‌ ی مجاور میتونه متعلق به:

یک Object دیگه
یک ساختار داده
اطلاعات مدیریتی Heap
یا داده‌های مهم برنامه باشه
در نتیجه یک اشتباه ساده در اندازه‌ ی داده میتونه روی بخش دیگری از برنامه تاثیر بذاره


تفاوت Heap Overflow و Stack Overflow

هر دو از یک ایده‌ی کلی پیروی میکنن:

نوشتن بیشتر از ظرفیت حافظه‌ ای که در اختیار برنامه قرار گرفته

اما محل اتفاق فرق داره

Stack Overflow:

Stack
↓
Buffer
↓
داده بیشتر از ظرفیت
↓
اطلاعات اطراف Buffer

Heap Overflow:

Heap
↓
Chunk
↓
داده بیشتر از ظرفیت
↓
Chunk یا Metadata مجاور

پس نباید این دو رو یکی بدونیم

چرا Chunkهای مجاور اهمیت دارن؟

فرض کنید Heap این شکلیه:

+----------------+
| Chunk A |
+----------------+
| Chunk B |
+----------------+
| Chunk C |
+----------------+

اگر برنامه داخل Chunk A بیشتر از ظرفیتش بنویسه ممکنه نوشتن وارد محدوده‌ ی Chunk B بشه در نتیجه ممکنه داده‌ی B تغییر کنه بدون اینکه برنامه مستقیما قصد تغییر B رو داشته باشه این دقیقا همون چیزی هست که Heap Overflow رو خطرناک میکنه

Metadata
هم میتونه مهم باشه یادتون هست گفتیم Chunk علاوه بر User Data اطلاعات مدیریتی هم داره اگر یک Overflow از محدوده‌ ی خودش خارج بشه در بعضی شرایط ممکنه به Metadata مربوط به بخش‌های مجاور هم برسه اینجاست که موضوع پیچیده‌ تر میشه چون دیگه فقط داده‌ی برنامه تغییر نکرده ممکنه اطلاعاتی که Allocator برای مدیریت Heap استفاده میکنه هم تحت تاثیر قرار گرفته باشه البته Allocator های مدرن بررسی‌ های مختلفی دارن و خیلی از روش‌ های قدیمی Heap Exploitation دیگه به سادگی گذشته قابل استفاده نیستن

یک نکته خیلی مهم

هر Heap Overflow حتما قابل تبدیل شدن به Exploit نیست
ممکنه فقط باعث بشه:
برنامه Crash کنه
یک مقدار اشتباه تغییر کنه
داده‌ای خراب بشه
یا رفتار برنامه غیرقابل‌ پیش‌بینی بشه

برای اینکه یک Memory Corruption واقعا قابل سواستفاده بشه باید بررسی کنیم چه چیزی قابل تغییره و این تغییر چه اثری روی برنامه داره

پس:
Bug ≠ Exploit


وجود باگ فقط نقطه‌ی شروع تحلیلمونه


Heap Overflow
زمانی اتفاق میوفته که برنامه بیشتر از ظرفیت یک بخش اختصاص‌ یافته روی Heap بنویسه و از محدوده‌ی خودش خارج بشه چون Chunk ها در Heap کنار هم قرار میگیرن این Overflow ممکنه داده‌ها یا ساختارهای مجاور رو تحت تاثیر قرار بده.برای تحلیل Heap Exploitation باید بفهمیم این Overflow دقیقا چه چیزی رو میتونه تغییر بده و اون تغییر چه اثری روی رفتار برنامه داره


@reverseengine
ReverseEngineering
Heap Overflow یکی از معروف‌ترین باگ‌های Heap: Heap Overflow , ایده‌اش خیلی شبیه Buffer Overflow روی Stack هست با این تفاوت که این بار سر ریز داخل Heap اتفاق میوفته Heap Overflow فرض کنید برنامه از Heap یک Chunk با ظرفیت مشخص میگیره: C char *buffer = malloc(64);…
Heap Overflow

One of the most famous Heap bugs:

Heap Overflow

Its idea is very similar to Buffer Overflow on Stack except that this time the overflow occurs inside the Heap

Heap Overflow
Suppose the program gets a Chunk with a certain capacity from the Heap:

C
char *buffer = malloc(64);

That is, the program has space to store data. Now if the program writes more than the capacity intended for this Chunk, the data will go out of its bounds

We call this event:
Heap Overflow
Simply put:

[ Buffer A ][ Buffer B ]

████████████████████
↓
Writing more than the capacity of A
↓
[ Buffer A ][AAAAAAAA...]
↑
Entering B range

Why is this dangerous?

Because chunks are usually placed next to each other, if the program goes out of its own chunk, it may change the data in the adjacent memory. That adjacent memory could belong to:

Another object

A data structure

Heap management information

Or important program data

As a result, a simple mistake in the size of the data can affect another part of the program

Difference between Heap Overflow and Stack Overflow

Both follow the same general idea:

Writing more than the memory capacity provided to the program

But the location of the event is different

Stack Overflow:

Stack
↓
Buffer
↓
Data more than capacity
↓
Information around Buffer

Heap Overflow:

Heap
↓
Chunk
↓
Data more than capacity
↓
Adjacent Chunk or Metadata

So we should not consider these two as the same

Why are adjacent chunks important?

Suppose the Heap looks like this:

+----------------+
| Chunk A |
+----------------+
| Chunk B |
+----------------+
| Chunk C |
+----------------+

If the program writes more than its capacity into Chunk A, the write may enter the Chunk B area, as a result, the data in B may change without the program directly intending to change B. This is exactly what makes Heap Overflow dangerous

Metadata
can also be important. Remember that we said that Chunk has management information in addition to User Data. If an Overflow goes out of its own scope, in some situations it may also reach the Metadata related to adjacent sections. This is where the matter becomes more complicated because not only the program data has changed, the information that the Allocator uses to manage the Heap may also be affected. Of course, modern Allocators have different checks and many of the old Heap Exploitation methods are no longer as simple as they used to be.

A very important point

Not every Heap Overflow can be turned into an Exploit. It may only cause:
The program to crash
A wrong value to change
Data to be corrupted
Or the program to behave unpredictable

For a Memory Corruption to really To be exploitable, we need to examine what can be changed and what effect this change has on the program

So:

Bug ≠ Exploit


The existence of a bug is only the starting point for our analysis

Heap Overflow

Occurs when a program writes more than the capacity of an allocated section on the Heap and goes out of its bounds because the Chunks in the Heap are placed next to each other. This Overflow may affect adjacent data or structures. To analyze Heap Exploitation, we need to understand what exactly this Overflow can change and what effect that change has on the behavior of the program.

@reverseengine
Forwarded from club1337
Devman-ArticleXakep.txt
17.9 KB
Вымогатель-болтун. Как Devman прошел путь от новичка до преступника в розыске Интерпола

👑 Статья для подписчиков

31 июля 2025 года Джон Ди Маджо открыл сообщение в зашифрованном мессенджере. Преступники обычно не любят, когда их деятельность расследуют, но этот написал сам. К сообщению была приложена фотография: дорогие часы, спортивные автомобили. Отправителя Ди Маджо знал.

https://xakep.ru/2026/08/13/devman/

Telegram ✉️ @club1337
X (Twitter) 🕊 @club31337
Please open Telegram to view this post
VIEW IN TELEGRAM