TGViewer
Channel Public Channel
ReverseEngineering

ReverseEngineering

@reverseengine

Subscribers
1.33K
Photos
50
Videos
11
Links
907
Recent Posts 20 shown
Post #1911 6
ReverseEngineering Concolic Execution ترکیب اجرای واقعی و نمادین برای کشف مسیرهای جدید Concolic Execution ترکیبی از دو روشه: Concrete Execution: اجرای برنامه با ورودی واقعی Symbolic Execution: بررسی نمادین داده‌ ها و شرط‌ های مسیر اجرا هدف اینه که از یک اجرای واقعی…
Execution Environment:
Interaction with network files, operating system, or external code can complicate the analysis

For this reason, this method is usually used alongside Fuzzing and Static Analysis, not as a complete replacement for them

Differences between the three methods

Methods Main Idea
Symbolic Execution
Path analysis with symbolic values ​​and constraints

Concrete Execution
Execution of the program with real values

Concolic Execution
Real execution with symbolic path analysis

In Concolic Execution, the actual execution of the program is the starting point. Path conditions are recorded. One of the conditions is changed to check the new path, and the solver tries to find a suitable input

This method is used in binary analysis, automatic testing, discovery of less-examined paths, and assistance in Fuzzing

@reverseengine
Post #1910 5
ReverseEngineering Concolic Execution ترکیب اجرای واقعی و نمادین برای کشف مسیرهای جدید Concolic Execution ترکیبی از دو روشه: Concrete Execution: اجرای برنامه با ورودی واقعی Symbolic Execution: بررسی نمادین داده‌ ها و شرط‌ های مسیر اجرا هدف اینه که از یک اجرای واقعی…
Concolic Execution

Combining Real and Symbolic Execution to Discover New Paths

Concolic Execution
is a combination of two methods:

Concrete Execution:
Executing the program with real input

Symbolic Execution:
Symbolically examining the data and conditions of the execution path

The goal is to start from a real execution, extract the conditions that the program has passed and create new inputs so that other paths can be examined

A simple example

Suppose the program is:

int check(int x)
{
if (x > 10)
{
if (x < 20)
return 1;
}

return 0;
}

We execute the program with this input:

x = 5

Since the condition "x > 10" is not true, the program goes directly to "return 0"

Execution path:

x = 5
|
x > 10 ?
|
False
|
return 0

So far we have only examined one path

But Concolic Execution can use the same execution to find new inputs

Extracting Path Condition

In the above execution, the following condition is examined:

if (x > 10)

The actual value of "x" is 5, so the condition is false

The symbolic constraint of the path taken is:

x <= 10

Now, to examine the opposite branch, we consider this condition:

x > 10

A constraint solver such as SMT Solver can suggest a suitable value:

x = 15

By re-executing the program:

x = 15
|
x > 10 ?
|
True
|
x < 20 ?

This time we reached the second condition, a path that we had not reached in the previous execution

Create a new input for the next path

The second condition is:

if (x < 20)

For input 15, this condition is also met and the program reaches this section:

return 1;

The constraints of the successful path of this program are:

x > 10
AND
x < 20

That is:

10 < x < 20

So "x = 15" is a valid input to reach this path

The important point is that the new input is not chosen randomly but is created using path constraints

In practice, the tool can record the executed paths and create a new input to reach a different branch by changing one of the conditions

General Algorithm of Concolic Execution

Program Execution

The program is executed with a real input

Recording Path Constraints

The tool records the conditions of the branches that the program has crossed. These conditions are called "Path Constraints".
Changing a Path Condition

Changes one of the conditions to check the opposite branch while still considering the previous path conditions

Resolving Constraints

An "SMT Solver" tries to find an input that satisfies the new conditions

Rerun with new input

The program is re-executed with the given input to check a different path

This cycle can be repeated many times to cover more paths

Relationship of Concolic Execution to Fuzzing

In typical Fuzzing, inputs are often created by mutating or changing previous inputs

For example:

Input A
↓
Mutation
↓
Input B
↓
New Execution

But sometimes the program has a condition that makes it difficult to reach the desired branch

For example:

if (input[0] == 'R' &&
input[1] == 'E' &&
input[2] == 'V')
{
process_special_case();

}

Fuzzer
must find an input that satisfies all three conditions

Concolic Execution
can analyze the conditions examined and suggest suitable inputs to traverse the branches

Input
↓
Execution
↓
Path Constraints
↓
Solver
↓
New Input
↓
New Path

This idea is used in Concolic Testing and some intelligent input guidance methods in software testing

Of course, modern fuzzers can also use techniques such as "Coverage-Guided Fuzzing" and reach new paths with targeted jumps without solving the conditions symbolically

Limitations of this method

Concolic Execution
is not magic, although its name seems to be explaining its program to us

It has several important problems:

Path Explosion:
The number of possible paths can increase rapidly

Solver Limitations:
Some conditions, especially complex calculations or operations Unsupported may be difficult to solve

Execution Dependency:
Analysis usually starts with paths that require proper inputs to be generated with the initial execution to reach other paths
Post #1909 3
روش ایده‌ی اصلی
Symbolic Execution
تحلیل مسیرها با مقادیر نمادین و محدودیت‌ها

Concrete Execution
اجرای برنامه با مقادیر واقعی

Concolic Execution
اجرای واقعی همراه با تحلیل نمادین مسیر


در Concolic Execution اجرای واقعی برنامه نقطه‌ی شروعه شرط‌ های مسیر ثبت میشن یکی از شرط‌ ها برای بررسی مسیر جدید تغییر میکنه و حل‌ کننده تلاش میکنه ورودی مناسبی پیدا کنه

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

@reverseengine
Post #1908 3
Concolic Execution

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



Concolic Execution
ترکیبی از دو روشه:

Concrete Execution:
اجرای برنامه با ورودی واقعی

Symbolic Execution:
بررسی نمادین داده‌ ها و شرط‌ های مسیر اجرا

هدف اینه که از یک اجرای واقعی شروع کنیم شرط‌ هایی رو که برنامه طی کرده استخراج کنیم و ورودی‌ های جدیدی بسازیم تا مسیر های دیگه هم بررسی بشن

یک مثال ساده

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

int check(int x)
{
if (x > 10)
{
if (x < 20)
return 1;
}

return 0;
}

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

x = 5

چون شرط "x > 10" برقرار نیست برنامه مستقیما به "return 0" میرسه

مسیر اجرا:

x = 5
|
x > 10 ?
|
False
|
return 0

تا اینجا فقط یک مسیر رو بررسی کردیم

اما Concolic Execution میتونه از همین اجرا برای پیدا کردن ورودی جدید استفاده کنه

استخراج شرط مسیر

در اجرای بالا شرط زیر بررسی شده:

if (x > 10)

مقدار واقعی "x" برابر 5 بوده پس شرط غلط شده

محدودیت نمادین مسیر طی‌ شده اینه:

x <= 10

حالا برای بررسی شاخه‌ی مخالف این شرط رو در نظر میگیریم:

x > 10

حل‌کننده‌ی محدودیت‌ها مثل SMT Solver میتونه یک مقدار مناسب پیشنهاد بده:

x = 15

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

x = 15
|
x > 10 ?
|
True
|
x < 20 ?

این بار به شرط دوم رسیدیم مسیری که اجرای قبلی بهش نرسیده بودیم

ساخت ورودی جدید برای مسیر بعدی

شرط دوم اینه:

if (x < 20)

برای ورودی 15 این شرط هم برقرار میشه و برنامه به این بخش میرسه:

return 1;

محدودیت‌های مسیر موفق این برنامه عبارت‌اند از:

x > 10
AND
x < 20

یعنی:

10 < x < 20

پس "x = 15" یک ورودی معتبر برای رسیدن به این مسیر محسوب میشه

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

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

الگوریتم کلی Concolic Execution

اجرای برنامه

برنامه با یک ورودی واقعی اجرا میشه

ثبت شرط‌های مسیر

ابزار شرط‌ های شاخه‌ هایی رو که برنامه ازشون عبور کرده ثبت میکنه به این شرط‌ ها "Path Constraints" گفته میشه.
تغییر یک شرط مسیر

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

حل محدودیت‌ها

یک "SMT Solver" تلاش میکنه ورودی‌ ای پیدا کنه که شرط‌ های جدید رو برآورده کنه

اجرای مجدد با ورودی جدید

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

این چرخه میتونه بارها تکرار بشه تا مسیرهای بیشتری پوشش داده بشن

ارتباط Concolic Execution با Fuzzing

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

مثلا:

Input A
↓
Mutation
↓
Input B
↓
New Execution

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

مثلا:

if (input[0] == 'R' &&
input[1] == 'E' &&
input[2] == 'V')
{
process_special_case();
}

Fuzzer
باید ورودی‌ ای پیدا کنه که هر سه شرط رو برقرار کنه

Concolic Execution
میتونه شرط‌ های بررسی‌ شده رو تحلیل کنه و برای عبور از شاخه‌ ها ورودی مناسب پیشنهاد بده

Input
↓
Execution
↓
Path Constraints
↓
Solver
↓
New Input
↓
New Path

این ایده در Concolic Testing و بعضی روش‌های هدایت هوشمند ورودی در تست نرم‌افزار کاربرد داره

البته Fuzzer های مدرن هم میتونن از تکنیک‌هایی مثل "Coverage-Guided Fuzzing" استفاده کنن و بدون حل نمادین شرط‌ ها با جهش‌ های هدفمند به مسیرهای جدید برسن

محدودیت‌ های این روش

Concolic Execution
جادو نیست هرچند اسمش طوریه که انگار قراره برنامه خودش رو برای ما توضیح بده

چند مشکل مهم داره:

Path Explosion:
تعداد مسیرهای ممکن میتونه به‌سرعت زیاد بشه

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

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

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

به همین دلیل این روش معمولا در کنار Fuzzing و Static Analysis استفاده میشه نه به‌عنوان جایگزین کامل اون‌ها

تفاوت سه روش
Post #1907 13
Then, if the variable type and base address are known, you can assign the structure type to the relevant variable so that accesses are more readable instead of raw offsets.

For example, instead of just seeing that the program reads from [rcx+4], you can recognize that it is reading the second field of the structure.

This also simplifies the analysis of subsequent functions because you do not have to calculate from the beginning which memory section each offset refers to each time.

Exercise:

Suppose you find these instructions in a binary file.

Asm

mov eax, dword ptr [rbx]

cmp eax, 0

mov ecx, dword ptr [rbx+8]

mov byte ptr [rbx+12], 1

Based on these instructions, determine

what memory offsets are used and what is the size of each access.

What types of fields are possible in this Offsets exist

Why can't we still say with certainty what fields the original structure has?

The main point of this section is that in reverse engineering you don't need to know the original structure from the start. By gathering evidence from offsets, access sizes, and how data is used, you can build a plausible model of the memory structure step by step

@reverseengine
Post #1906 12
Struct Reconstruction

When you reverse engineer a binary file, you often don't have the original source code, meaning you don't know what structures the programmer defined and what fields each one has.

Here you have to reconstruct the data structure based on how the program accesses memory. We call this Struct Reconstruction.

Assume the original program has a structure like this:

C

struct User {
int id;
int age;
char active;
};

This structure has three fields, one for the user ID, one for age, and one for the active status.

But when you examine the compiled file, you may not see the User name or the id and age fields at all. You only have assembly instructions that work with memory addresses.

Now let's see how we can identify the structure from these instructions.

Finding the field addresses.

Suppose you come across the following instructions in assembly.

Asm

mov eax, dword ptr [rcx]

mov edx, dword ptr [rcx+4]

movzx eax, byte ptr [rcx+8]

Let's examine them one by one.

Asm

mov eax, dword ptr [rcx]

Reads four bytes from the address in RCX.

Asm

mov edx, dword ptr [rcx+4]

Reads four bytes from the next four bytes.

Asm

movzx eax, byte ptr [rcx+8]

Reads one byte from the eight bytes after the initial address.

Now, the information that We have this form

Text

Base address + 0 four bytes
Base address + 4 four bytes
Base address + 8 one byte

This pattern can show that the program is accessing three different fields of a structure

Determining the size and type of fields

Based on the size of the accesses, we can guess what type each field has

A four-byte access may be related to int or float

A one-byte access may be related to char or a boolean value

But be aware that size alone does not determine the exact type of data, for example, a four-byte value can also be part of a larger number

To better identify, you need to see where the value is used

If the value is used with numerical calculation instructions, it may be a regular number. If it works with instructions related to floating-point operations, it is more likely to be a float

Reconstructing the structure

Given the accesses we saw, we can create a possible structure

C

struct Unknown {
int field_0;
int field_4;
char field_8;
};

We named the fields based on their memory offsets for now because we don't know their actual use yet

After examining more parts of the program, we might find out that field_0 is the user ID, field_4 is the user's age, and field_8 is the activation status

The labeling is done based on the meaning of the data, not just the appearance of the assembly

Checking for writing to fields

Don't just check the read instructions, writing to memory also gives us important information

For example:

Asm

mov dword ptr [rcx], 123
mov dword ptr [rcx+4], 25
mov byte ptr [rcx+8], 1

These instructions write to offsets zero, four, and eight, respectively

If you see the same offsets used for reading and writing in different parts of the program, the likelihood that you are dealing with fields of a structure increases

Of course, you still need to check whether RCX is really the base address of a shared structure or just a point to a part of memory at that moment

Finding the actual size of the structure

There is an important point here

The largest offset you find is not necessarily the full size of the structure

For example, if the last field starts at offset eight and is one byte in size, the minimum space required for these fields is 9 bytes, but the final size of the structure may be larger due to memory alignment rules.

For example, the compiler may round the structure to 12 bytes to meet the required alignment.

The structure may also have other fields that we have not yet accessed in the paths examined.

So you should estimate the size of the structure by examining the memory allocation, how pointers are used, and all visible offsets.

How is this done in Ghidra?

Once you have found a possible structure, you can create a new structure from the Data Type Manager and define its fields.
Post #1905 11
این کار تحلیل توابع بعدی رو هم ساده‌ تر میکنه چون لازم نیست هر بار از اول محاسبه کنید هر آفست به کدوم بخش حافظه اشاره داره

تمرین:

فرض کنید توی یه فایل باینری این دستورها رو پیدا کردید

Asm

mov eax, dword ptr [rbx]
cmp eax, 0
mov ecx, dword ptr [rbx+8]
mov byte ptr [rbx+12], 1

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

چه آفست‌ هایی از حافظه استفاده شدن و اندازه هر دسترسی چقدره

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

چرا هنوز نمیتونیم با اطمینان بگیم ساختار اصلی دقیقا چه فیلدهایی داشته

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

@reverseengine
Post #1904 12
Struct Reconstruction


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

اینجا باید از روی نحوه دسترسی برنامه به حافظه ساختار داده‌ ها رو دوباره بازسازی کنید به این کار میگیم Struct Reconstruction

فرض کنید برنامه اصلی همچین ساختاری داشته باشه

C
struct User {
int id;
int age;
char active;
};

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

ولی وقتی فایل کامپایل‌ شده رو بررسی میکنی ممکنه اصلا اسم User یا فیلد های id و age رو نبینید فقط دستور های اسمبلی رو دارید که با آدرس‌ های حافظه کار میکنن

حالا ببین چطور میتونیم ساختار رو از روی همین دستورها تشخیص بدیم

پیدا کردن آدرس فیلد ها

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

Asm

mov eax, dword ptr [rcx]
mov edx, dword ptr [rcx+4]
movzx eax, byte ptr [rcx+8]

بیایم یکی یکی بررسیشون کنیم



Asm

mov eax, dword ptr [rcx]

چهار بایت از آدرسی که داخل RCX قرار داره میخونه


Asm

mov edx, dword ptr [rcx+4]

چهار بایت از چهار بایت بعدی میخونه



Asm

movzx eax, byte ptr [rcx+8]

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

حالا اطلاعاتی که داریم این شکلیه

`text
آدرس پایه + 0 چهار بایت
آدرس پایه + 4 چهار بایت
آدرس پایه + 8 یک بایت

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

تشخیص اندازه و نوع فیلد ها

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

دسترسی چهار بایتی ممکنه مربوط به int یا float باشه

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

اما حواستون باشه که اندازه به‌ تنهایی نوع دقیق داده رو مشخص نمیکنه مثلا یه مقدار چهار بایتی میتونه بخشی از یه عدد بزرگ‌تر هم باشه

برای تشخیص بهتر باید ببینید مقدار کجا استفاده میشه

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


بازسازی ساختار

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

C
struct Unknown {
int field_0;
int field_4;
char field_8;
};

اسم فیلد ها رو فعلا بر اساس آفست حافظه گذاشتیم چون هنوز کاربرد واقعی اون‌ها رو نمیدونیم

بعد از بررسی بخش‌ های بیشتری از برنامه ممکنه بفهمیم field_0 شناسه کاربره field_4 سن کاربره و field_8 وضعیت فعال بودنشه

سم‌ گذاری بر اساس معنی داده‌ ها انجام میشه نه صرفا ظاهر اسمبلی

بررسی نوشتن روی فیلدها

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

مثلا:

Asm
mov dword ptr [rcx], 123
mov dword ptr [rcx+4], 25
mov byte ptr [rcx+8], 1

این دستورها به‌ ترتیب روی آفست‌ های صفر چهار و هشت مینویسن

اگه جاهای مختلف برنامه ببینید که همین آفست‌ ها برای خوندن و نوشتن استفاده میشن احتمال اینکه با فیلد های یه ساختار طرف باشی بیشتر میشه

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

پیدا کردن اندازه واقعی ساختار

اینجا یه نکته مهم وجود داره

بزرگ‌ ترین آفستی که پیدا میکنید لزوما اندازه کامل ساختار نیست

مثلا اگه آخرین فیلد از آفست هشت شروع بشه و یک بایت اندازه داشته باشه حداقل فضای مورد نیاز برای این فیلدها 9 بایته اما اندازه نهایی ساختار ممکنه به خاطر قوانین هم‌ترازی حافظه یا همون Alignment بزرگ‌تر باشه

برای مثال کامپایلر ممکنه ساختار رو به 12 بایت گرد کنه تا همترازی مورد نیاز رعایت بشه

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

پس اندازه ساختار رو باید با بررسی تخصیص حافظه نحوه استفاده از اشاره‌گرها و تمام آفست‌ های قابل مشاهده تخمین بزنید

این کار توی Ghidra چطور انجام میشه؟

وقتی ساختار احتمالی رو پیدا کردید میتونید از بخش Data Type Manager یه ساختار جدید بسازی و فیلدهاش رو تعریف کنید

بعد در صورت مشخص بودن نوع متغیر و آدرس پایه میتونید نوع ساختار رو به متغیر مربوط اختصاص بدید تا به‌ جای آفست‌های خام دسترسی‌ها خواناتر بشن

مثلا به‌جای اینکه فقط ببینید برنامه از [rcx+4] میخونه بتونید تشخیص بدید که داره فیلد دوم ساختار رو میخونه
Post #1900 103
Simple view:

Program.exe
|
v
Import Table
|
v
Required DLLs
|
v
Resolve Functions
|
v
Import Address Table (IAT)

The IAT table contains the addresses used to access imported functions

That is why the contents of some PE parts in Memory can differ from the corresponding initial values ​​in the file on disk

So why might the PE on disk and the memory be different?

There are several important reasons:

Relocation:
Some addresses are modified depending on the loading location

Import Resolution:
The address of imported functions is determined during the loading process

Memory Layout:
The structure of sections in memory is not necessarily the same as the raw layout of the file

Runtime Changes:
The program itself may change some memory data during execution

Zero Initialization:
Some sections may have allocated space in memory but have no corresponding data in the file and are initialized to zero

So if an analyst wants to compare a file on disk with Memory, he should not expect all the bytes to be exactly the same

How is this useful in Memory Forensics?

Suppose you are examining a Memory Dump
and you find a section whose content looks like the structure of a PE

A few questions to ask yourself:

Is the header valid?

Are the sections recognizable?

Are the addresses consistent with the Image structure?

Memory
Related to a known module?

Can the differences between file and memory be explained?

It is important to note that finding an MZ signature alone does not prove that a valid or executable PE exists in that area. A complete set of evidence must be examined for proper analysis. A PE may also not be easily recoverable due to the way it was uploaded, runtime changes, or even incomplete memory dump data.

How this relates to EDR. EDR can use information about memory modules and process behavior to investigate suspicious activity.

For example, we might put this information together:

Process Information

+ Loaded Modules + Memory Regions + Thread Activity
+ Call Stack + Execution Timeline

If the memory information is inconsistent with the expected state of a program, further investigation may be warranted.

However, it is important to note that: A difference in memory from a file on disk alone is not a definitive indication of malicious manipulation.

Some differences are a natural consequence of program upload and execution.

So far, we have been familiar with some important concepts. We are:

These concepts are very, very important to us in the discussion of memory analysis and program behavior analysis

@reverseengine
  • 🔥 1
Post #1899 79
ReverseEngineering PE در حافظه و تفاوتش با فایل روی دیسک تا اینجا درباره‌ ی Private Memory Image-backed Memory و VAD صحبت کردیم حالا میخوایم ببینیم وقتی یک فایل اجرایی ویندوز وارد حافظه میشه چه اتفاقی براش میوفته و چرا نسخه‌ی داخل Memory معمولا با نسخه‌ی روی دیسک یکی نیست…
The Loader
processes these dependencies and places the addresses of the required functions in the associated structures
Post #1898 75
ReverseEngineering PE در حافظه و تفاوتش با فایل روی دیسک تا اینجا درباره‌ ی Private Memory Image-backed Memory و VAD صحبت کردیم حالا میخوایم ببینیم وقتی یک فایل اجرایی ویندوز وارد حافظه میشه چه اتفاقی براش میوفته و چرا نسخه‌ی داخل Memory معمولا با نسخه‌ی روی دیسک یکی نیست…
PE
In memory and its difference from a file on disk

So far we have talked about Private Memory Image-backed Memory and VAD

Now we want to see what happens to a Windows executable file when it enters memory and why the version in memory is usually not the same as the version on disk

First, let's see what PE is

The main executable format of many Windows programs is PE or Portable Executable

Consider files like these:

program.exe
library.dll

The general structure of a PE file is approximately as follows:

PE File
│
├── DOS Header
├── PE Signature
├── COFF File Header
├── Optional Header
├── Section Headers
│
├── .text
├── .rdata
├── .data
├── .pdata
└── Other Sections

Not all files necessarily have all of these sections, and their order and details may also vary

Each section has specific information, and Windows uses the PE structure to recognize and load the executable file. Uses

What happens when PE enters Memory?

Suppose this file is on disk:

C:\Program Files\App\program.exe

When Windows runs it, the operating system Loader performs the upload process

Simple view:

PE File on Disk
|
v
Windows Loader
|
v
Virtual Address Space
|
v
Program Execution

But the point is that Windows usually does not copy the file into memory as a raw, unmodified piece

The Loader must process the file structures and place the different parts in the appropriate places

The difference between Raw Offset and RVA

Here we come to one of the basic concepts

Raw Offset

Is the address used to find the information inside the file itself on the disk

For example:
File Offset = 0x600

That is, the desired information starts from Offset equal to 0x600 in the file

RVA
is the abbreviation for this phrase:

RVA = Relative Virtual Address

That is, the address of a section relative to ImageBase in the virtual address space

For example:

ImageBase = 0x140000000

RVA = 0x2000

In this example:

VA = ImageBase + RVA

So:

VA = 0x140002000

This calculation calculates the desired virtual address It is obtained provided that we use the actual
ImageBase
of the loaded Image

The main difference

Raw Offset
|
+--> Location in the file on disk

RVA
|
+--> Location relative to
ImageBase
in the virtual address space

Raw Offset and RVA are not necessarily equal
To convert between them, we must also consider the relevant Section information such as
PointerToRawData and VirtualAddress

In addition, not all virtual addresses
necessarily correspond to a section of the data in the file, for example, a section in memory may be larger than its corresponding raw data

ImageBase

Each Image in the uploaded state has a Base Address, which we call ImageBase

For example:

Preferred ImageBase:
0x140000000

This address is the preferred location defined for loading the Image in the PE file

But Windows does not always have to place the Image exactly there

Why?

Because that range of virtual address space may not be available or there may be other conditions

In this situation, the Image may be loaded to another address

Here we go to the next concept

Base Relocation

Suppose the program expects the Image to be located at this address:

Preferred Base = 0x140000000

But in practice it is uploaded to another address:

Actual Base = 0x180000000

The difference between these two addresses:

Delta = Actual Base - Preferred Base

If the Image needs Relocation and the necessary information is available, the Loader uses the Relocation information to correct the addresses dependent on the loading location

We call this process Base Relocation

The important point is that not all addresses in the file require Relocation, only those that are dependent on the loading location according to the PE structure and have appropriate Relocation information must be corrected

What does Import Table
do?

Programs often use functions from other libraries

For example, a program might need functions from these modules:

KERNEL32.dll

USER32.dll

ADVAPI32.dll

Information about external dependencies is placed in the Import structures of the PE file
Post #1897 41
نمای ساده :

Program.exe
|
v
Import Table
|
v
Required DLLs
|
v
Resolve Functions
|
v
Import Address Table (IAT)

جدول IAT شامل آدرس‌ هایی میشه که برای دسترسی به توابع Import شده استفاده میشن

به همین دلیل محتوای بعضی قسمت‌ های PE در Memory میتونه با مقادیر اولیه‌ ی متناظر در فایل روی دیسک متفاوت باشه

پس چرا PE روی دیسک و Memory ممکنه فرق داشته باشن؟

چند دلیل مهم وجود داره:

Relocation:
بعضی آدرس‌های وابسته به محل بارگذاری اصلاح میشن

Import Resolution:
آدرس توابع Importشده در فرایند بارگذاری مشخص میشه

Memory Layout:
ساختار Section ها در حافظه لزوما مثل چیدمان خام فایل نیست

Runtime Changes:
خود برنامه ممکنه هنگام اجرا بعضی داده‌ های حافظه رو تغییر بده

Zero Initialization:
بعضی قسمتها ممکنه در حافظه فضای اختصاص‌ یافته داشته باشن ولی داده‌ی متناظری در فایل نداشته باشن و با صفر مقداردهی بشن

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

این موضوع در Memory Forensics چه کاربردی داره؟

فرض کنی در حال بررسی یک Memory Dump هستید
یک بخش پیدا میکنید که محتوای اون شبیه ساختار یک PE به نظر میرسه

چند سوال از خودتون میپرسید:

Header معتبره؟

Section
ها قابل شناسایی هستن؟

آدرس‌های موجود با ساختار Image سازگارن؟

Memory
به یک Module شناخت ه‌شده مربوطه؟

تفاوت‌ های فایل و Memory قابل توضیح هستن؟

نکته‌ی مهم اینه که پیدا کردن امضای MZ به‌تنهایی اثبات نمیکنه که یک PE معتبر یا قابل اجرا در اون ناحیه وجود داره
برای تحلیل درست باید مجموعه‌ ای از شواهد رو بررسی کرد

همچنین یک PE ممکنه به دلیل نحوه‌ ی اپلود تغییرات زمان اجرا یا حتی ناقص بودن داده‌های Memory Dump به‌سادگی قابل بازیابی نباشه

ارتباط این با EDR

EDR
میتونه از اطلاعات مربوط به Module ها حافظه و رفتار Process برای بررسی فعالیت‌ های مشکوک استفاده کنه
برای مثال ممکنه این اطلاعات رو کنار هم قرار بدیم:

Process Information
+
Loaded Modules
+
Memory Regions
+
Thread Activity
+
Call Stack
+
Execution Timeline

اگر اطلاعات Memory با وضعیت مورد انتظار یک برنامه سازگار نباشه ممکنه نیاز به بررسی بیشتری وجود داشته باشه

ولی باید دقت کنیم که:

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

بعضی تفاوت‌ ها نتیجه‌ی طبیعی اپلود و اجرای برنامه هستن


تا اینجا با چند مفهوم مهم آشنا شدیم:

این مفاهیم برای ما داخل بحث تحلیل حافظه و بررسی رفتار برنامه‌ ها خیلی خیلی مهمن

@reverseengine
Post #1896 34
PE
در حافظه و تفاوتش با فایل روی دیسک

تا اینجا درباره‌ ی Private Memory Image-backed Memory و VAD صحبت کردیم

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

اول ببینیم PE چیه

فرمت اجرایی اصلی بسیاری از برنامه‌های ویندوز PE یا Portable Executable هست

فایل‌هایی مثل اینها رو در نظر بگیرید:

program.exe
library.dll

ساختار کلی یک فایل PE تقریبا به این شکله:

PE File
│
├── DOS Header
├── PE Signature
├── COFF File Header
├── Optional Header
├── Section Headers
│
├── .text
├── .rdata
├── .data
├── .pdata
└── Other Sections

همه‌ ی فایل‌ها لزوما تمام این Section ها رو ندارن و ترتیب و جزئیاتشون هم ممکنه متفاوت باشه

هر بخش اطلاعات مشخصی داره و Windows از ساختار PE برای شناخت و بارگذاری فایل اجرایی استفاده میکنه

وقتی PE وارد Memory میشه چه اتفاقی میوفته؟

فرض کنید این فایل روی دیسک وجود داره:


C:\Program Files\App\program.exe

وقتی Windows اون رو اجرا میکنه Loader سیستم‌ عامل فرایند اپلود رو انجام میده

نمای ساده‌ :

PE File on Disk
|
v
Windows Loader
|
v
Virtual Address Space
|
v
Program Execution

اما نکته اینجاست که Windows معمولا فایل رو به شکل یک تیکه ی خام و بدون تغییر داخل Memory کپی نمیکنه
Loader باید ساختارهای فایل رو پردازش کنه و بخش‌های مختلف رو در جای مناسب قرار بده

تفاوت Raw Offset و RVA

اینجا به یکی از مفاهیم پایه‌ای میرسیم

Raw Offset
آدرسیه که برای پیدا کردن اطلاعات داخل خود فایل روی دیسک استفاده میشه

مثلا:
File Offset = 0x600

یعنی اطلاعات موردنظر از Offset برابر با 0x600 در فایل شروع میشه

RVA
مخفف این عبارته:

RVA = Relative Virtual Address

یعنی آدرس یک بخش نسبت به ImageBase در فضای آدرس مجازی

مثلا:

ImageBase = 0x140000000
RVA = 0x2000

در این مثال:

VA = ImageBase + RVA

پس:

VA = 0x140002000

این محاسبه آدرس مجازی موردنظر رو به دست میاره به شرطی که از
ImageBase
واقعی Image بارگذاری‌ شده استفاده کنیم

تفاوت اصلی

Raw Offset
|
+--> موقعیت در فایل روی دیسک

RVA
|
+--> موقعیت نسبت به
ImageBase
در فضای آدرس مجازی

Raw Offset و RVA
لزوما برابر نیستن

برای تبدیل بین اونها باید اطلاعات Section مربوطه مثل
PointerToRawData و VirtualAddress
رو هم در نظر بگیریم

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

ImageBase

هر Image در حالت اپلود شده یک Base Address داره که بهش ImageBase میگیم

برای مثال:

Preferred ImageBase:
0x140000000

این آدرس محل ترجیحی تعریف‌ شده برای بارگذاری Image در فایل PE هست
ولی Windows همیشه مجبور نیست Image رو دقیقا همون‌جا قرار بده

چرا؟

چون ممکنه اون محدوده از فضای آدرس مجازی در دسترس نباشه یا شرایط دیگه‌ای وجود داشته باشه

در این شرایط Image ممکنه در آدرس دیگری بارگذاری بشه

اینجا میریم سراغ مفهوم بعدی

Base Relocation

فرض کنید برنامه انتظار داره Image در این آدرس قرار بگیره:


Preferred Base = 0x140000000

اما در عمل در آدرس دیگه ای اپلود میشه:

Actual Base = 0x180000000

اختلاف این دو آدرس:

Delta = Actual Base - Preferred Base

در صورتی که Image به Relocation نیاز داشته باشه و اطلاعات لازم موجود باشه Loader از اطلاعات Relocation برای اصلاح آدرس‌های وابسته به محل بارگذاری استفاده میکنه

به این فرایند Base Relocation میگیم
نکته‌ی مهم اینه که تمام آدرس‌ های داخل فایل نیازمند Relocation نیستن فقط مواردی که طبق ساختار PE به محل بارگذاری وابسته هستن و اطلاعات Relocation مناسب براشون وجود داره باید درست بشن

Import Table
چه کاری انجام میده؟

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

برای مثال یک برنامه ممکنه به توابعی از این Moduleها نیاز داشته باشه:

KERNEL32.dll
USER32.dll
ADVAPI32.dll

اطلاعات مربوط به وابستگی‌های خارجی در ساختارهای Import فایل PE قرار میگیره

Loader
این وابستگی‌ ها رو پردازش میکنه و آدرس توابع موردنیاز رو در ساختار های مرتبط قرار میده
  • ❤ 1
Post #1895 35
ReverseEngineering Waiting Time Process چه مقدار از زمان رو فقط منتظر CPU بوده؟ این میشه: Waiting Time مثلا Process وارد سیستم میشه: Arrival ↓ Waiting ↓ Running ↓ Waiting ↓ Running ↓ Finished تمام زمانی که Process در صف آماده اجرا منتظر CPU بوده در Waiting Time…
Waiting Time

Process How much time did it spend just waiting for the CPU?

This is:

Waiting Time

For example, a process enters the system:

Arrival
↓
Waiting
↓
Running
↓
Waiting
↓
Running
↓
Finished

The entire time the process spent waiting for the CPU in the ready queue counts as Waiting Time.

Waiting Time = Time spent waiting for the CPU

@reverseengine
Post #1894 37
Waiting Time

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

این میشه:
Waiting Time

مثلا Process وارد سیستم میشه:

Arrival
↓
Waiting
↓
Running
↓
Waiting
↓
Running
↓
Finished

تمام زمانی که Process در صف آماده اجرا منتظر CPU بوده در Waiting Time حساب میشه

Waiting Time =
زمان انتظار برای CPU

@reverseengine
Post #1893 37
ReverseEngineering Turnaround Time از زمانی که یک Job وارد سیستم میشه تا زمانی که کاملا تموم بشه چقدر زمان گذشته؟ فرمول: Turnaround Time = Completion Time - Arrival Time مثلا: Arrival Time = 0 Completion = 10 پس: Turnaround Time = 10 یعنی Process از اول تا اخر 10…
Turnaround Time

How long does it take for a job to complete from the time it is entered into the system until it is completely finished?

Formula:

Turnaround Time = Completion Time - Arrival Time

For example:

Arrival Time = 0

Completion = 10

So:

Turnaround Time = 10

This means that the process has been in the system for 10 time units from start to finish

@reverseengine
Post #1892 32
Turnaround Time
از زمانی که یک Job وارد سیستم میشه تا زمانی که کاملا تموم بشه چقدر زمان گذشته؟

فرمول:

Turnaround Time = Completion Time - Arrival Time

مثلا:
Arrival Time = 0

Completion = 10

پس:

Turnaround Time = 10

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

@reverseengine
Post #1891 35
C

change(&a);
read_value(&b);
read_value(&a);
}

Determine which function call reads the value modified by

change and why.

Note that the read_value

function reads the value but does not return it, nor does it store it elsewhere.

A key takeaway from this section:

When analyzing data flow, it is not always necessary for two functions to interact directly.

Sometimes, one function modifies a value in memory, and another function—with no direct connection to the first—accesses that same location via a pointer.

In reverse engineering, tracing these very indirect connections can reveal the program's actual behavior.

@reverseengine
Post #1890 37
ReverseEngineering Memory Aliasing بین توابع تا اینجا دیدیم که چطور ممکنه دو تا Pointer به یه آدرس از حافظه اشاره کنن حالا میخوایم یه حالت مهم‌تر رو بررسی کنیم فرض کنید یه تابع داریم که یه مقدار رو توی حافظه تغییر میده و یه تابع دیگه همون مقدار رو از حافظه میخونه نکته اینجاست…
If we mistakenly assume that two pointers are the same, we may analyze the order of memory changes and, as a result, the actual path of program execution incorrectly. Exercise: Examine the following code.

C

void change(int *p) { *p = 50

; }

void read_value(int *q)

{ int x = *q;

int main() int a = 10;

int b = 20;
Post #1889 29
ReverseEngineering Memory Aliasing بین توابع تا اینجا دیدیم که چطور ممکنه دو تا Pointer به یه آدرس از حافظه اشاره کنن حالا میخوایم یه حالت مهم‌تر رو بررسی کنیم فرض کنید یه تابع داریم که یه مقدار رو توی حافظه تغییر میده و یه تابع دیگه همون مقدار رو از حافظه میخونه نکته اینجاست…
Memory Aliasing Between Functions

So far we have seen how two pointers can point to the same memory address. Now we want to examine a more important case.

Suppose we have a function that modifies a value in memory and another function reads the same value from memory.

The point here is that these two functions may use different pointers, but both access the same address.

This is very important in reverse engineering, because we may not notice the relationship between them by examining each function alone.

A simple example with

C

void set_value(int *p)
{
*p = 100;
}

int get_value(int *q)
{
return *q;
}

int main()
{
int value = 10;

set_value(&value);

int result = get_value(&value);

return result;
}

What is happening here?

First, the value is 10
Then the address of value is given to the set_value function
The set_value function changes the value at that address to 100
Then the same address is given to the get_value function and the new value is read from memory
Finally, the result is 100
The main point is that the pointers p and q have different names and variables, but in this example they both access the same memory location

So the connection between these two functions is formed through memory, not through the return value of one of them

Now, how does this look in assembly?

Suppose the set_value function is compiled like this

set_value:
mov dword ptr [rdi], 100
ret

This example is written according to the System V AMD64 calling convention, which is commonly used in 64-bit Linux programs

According to this convention, the first argument of the function is placed in the RDI register

Now let's examine this instruction

mov dword ptr [rdi], 100

This instruction writes the number 100 to the address located in RDI
Note that the value of RDI is different from the value stored in memory

RDI
holds the address and the expression [rdi] refers to the data located at that address

Now look at the get_value function

get_value:
mov eax, dword ptr [rdi]
ret

Here the four-byte value located at the address RDI is read and placed in EAX. Since the return value of the function is of type int, this value is stored in the register EAX is returned, so one function writes to memory and another function reads from the same memory, provided that the address given to both functions points to the same memory location.

Now how do we find it in reverse engineering?

When you are examining a binary file in Ghidra or Disassembler, follow these steps

Step 1:
Examine the function arguments

See what pointers the function receives and whether it uses them to read or write memory

Step 2:
Look for memory write instructions

For example, consider the following command

mov dword ptr [rdi], 100

This command shows that the function uses the address in RDI to write data

Step 3:
Examine other functions

See if other functions receive the same address or associated pointer and later read something from that memory

Don't just look for identical variable or register names - the address may be passed through another register, stored in a structure, or obtained with several calculation steps

Step 4:
Look for relationships between functions

Don't just look at the function name or its return value - a function may change memory It does not return any value, but the effect of its change can be seen in another function a few steps later.

Therefore, you must follow the path of transferring pointers and reading and writing memory. An important point about Must Alias ​​and May Alias.

We cannot always be sure that two pointers point to the same address based on the similarity of the arguments. Here we have two important concepts.

Must Alias ​​means that according to the information and conditions we have examined, two pointers must point to the same memory location.

May Alias ​​means that two pointers may point to the same memory location, but the available information is not enough to prove this definitively. This difference is very important in program analysis.
Older posts →

About this channel

How can I read @reverseengine without a Telegram account?
TGViewer shows the public web preview Telegram publishes for ReverseEngineering: recent posts, photos, videos and the subscriber count, with no app, login or account.
How many subscribers does ReverseEngineering have?
ReverseEngineering (@reverseengine) has 1.33K subscribers on Telegram, refreshed roughly every 30 minutes.
Does ReverseEngineering know I viewed it here?
No. Public channel previews carry no viewer identity, and TGViewer has no accounts or tracking of what you look up.
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →