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
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
Struct Reconstruction
وقتی یه فایل باینری رو مهندسی معکوس میکنی خیلی وقت ها سورس کد اصلی رو ندارید یعنی نمیدونید برنامه نویس چه ساختار هایی تعریف کرده و هر کدوم چه فیلدهایی داشتن
اینجا باید از روی نحوه دسترسی برنامه به حافظه ساختار داده ها رو دوباره بازسازی کنید به این کار میگیم Struct Reconstruction
فرض کنید برنامه اصلی همچین ساختاری داشته باشه
C
struct User {
int id;
int age;
char active;
};
این ساختار سه تا فیلد داره یکی برای شناسه کاربر یکی برای سن و یکی هم برای وضعیت فعال بودن.
ولی وقتی فایل کامپایل شده رو بررسی میکنی ممکنه اصلا اسم
حالا ببین چطور میتونیم ساختار رو از روی همین دستورها تشخیص بدیم
پیدا کردن آدرس فیلد ها
فرض کنید توی اسمبلی به دستورهای زیر رسیدید
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]
یک بایت از هشت بایت بعد از آدرس اولیه میخونه
حالا اطلاعاتی که داریم این شکلیه
آدرس پایه + 0 چهار بایت
آدرس پایه + 4 چهار بایت
آدرس پایه + 8 یک بایت
این الگو میتونه نشون بده که برنامه داره به سه فیلد مختلف از یه ساختار دسترسی پیدا میکنه
تشخیص اندازه و نوع فیلد ها
از روی اندازه دسترسی ها میتونیم حدس بزنیم هر فیلد چه نوعی داره
دسترسی چهار بایتی ممکنه مربوط به
دسترسی یک بایتی ممکنه مربوط به
اما حواستون باشه که اندازه به تنهایی نوع دقیق داده رو مشخص نمیکنه مثلا یه مقدار چهار بایتی میتونه بخشی از یه عدد بزرگتر هم باشه
برای تشخیص بهتر باید ببینید مقدار کجا استفاده میشه
اگه مقدار با دستورهای محاسباتی عددی استفاده بشه ممکنه یه عدد معمولی باشه اگه با دستور های مربوط به عملیات ممیز شناور کار کنه احتمال
بازسازی ساختار
با توجه به دسترسی هایی که دیدیم میتونیم یه ساختار احتمالی بسازیم
C
struct Unknown {
int field_0;
int field_4;
char 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 یه ساختار جدید بسازی و فیلدهاش رو تعریف کنید
بعد در صورت مشخص بودن نوع متغیر و آدرس پایه میتونید نوع ساختار رو به متغیر مربوط اختصاص بدید تا به جای آفستهای خام دسترسیها خواناتر بشن
مثلا بهجای اینکه فقط ببینید برنامه از
وقتی یه فایل باینری رو مهندسی معکوس میکنی خیلی وقت ها سورس کد اصلی رو ندارید یعنی نمیدونید برنامه نویس چه ساختار هایی تعریف کرده و هر کدوم چه فیلدهایی داشتن
اینجا باید از روی نحوه دسترسی برنامه به حافظه ساختار داده ها رو دوباره بازسازی کنید به این کار میگیم 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] میخونه بتونید تشخیص بدید که داره فیلد دوم ساختار رو میخونهاین کار تحلیل توابع بعدی رو هم ساده تر میکنه چون لازم نیست هر بار از اول محاسبه کنید هر آفست به کدوم بخش حافظه اشاره داره
تمرین:
فرض کنید توی یه فایل باینری این دستورها رو پیدا کردید
Asm
mov eax, dword ptr [rbx]
cmp eax, 0
mov ecx, dword ptr [rbx+8]
mov byte ptr [rbx+12], 1
با توجه به این دستورها مشخص کنید
چه آفست هایی از حافظه استفاده شدن و اندازه هر دسترسی چقدره
چه نوع فیلد هایی ممکنه در این آفست ها وجود داشته باشن
چرا هنوز نمیتونیم با اطمینان بگیم ساختار اصلی دقیقا چه فیلدهایی داشته
نکته اصلی این قسمت اینه که توی مهندسی معکوس لازم نیست از همون اول ساختار اصلی رو بدونید با جمع کردن شواهد از آفستها اندازه دسترسی ها و نحوه استفاده از دادهها میتونید قدم به قدم یه مدل قابل قبول از ساختار حافظه بسازید
@reverseengine
تمرین:
فرض کنید توی یه فایل باینری این دستورها رو پیدا کردید
Asm
mov eax, dword ptr [rbx]
cmp eax, 0
mov ecx, dword ptr [rbx+8]
mov byte ptr [rbx+12], 1
با توجه به این دستورها مشخص کنید
چه آفست هایی از حافظه استفاده شدن و اندازه هر دسترسی چقدره
چه نوع فیلد هایی ممکنه در این آفست ها وجود داشته باشن
چرا هنوز نمیتونیم با اطمینان بگیم ساختار اصلی دقیقا چه فیلدهایی داشته
نکته اصلی این قسمت اینه که توی مهندسی معکوس لازم نیست از همون اول ساختار اصلی رو بدونید با جمع کردن شواهد از آفستها اندازه دسترسی ها و نحوه استفاده از دادهها میتونید قدم به قدم یه مدل قابل قبول از ساختار حافظه بسازید
@reverseengine
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.
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.
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
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
قسمت سی و پنجم بافر اورفلو
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 استفاده میشه نه بهعنوان جایگزین کامل اونها
تفاوت سه روش
روش ایدهی اصلی
Symbolic Execution
تحلیل مسیرها با مقادیر نمادین و محدودیتها
Concrete Execution
اجرای برنامه با مقادیر واقعی
Concolic Execution
اجرای واقعی همراه با تحلیل نمادین مسیر
در Concolic Execution اجرای واقعی برنامه نقطهی شروعه شرط های مسیر ثبت میشن یکی از شرط ها برای بررسی مسیر جدید تغییر میکنه و حل کننده تلاش میکنه ورودی مناسبی پیدا کنه
این روش در تحلیل باینری تست خودکار کشف مسیر های کمتر بررسی شده و کمک به Fuzzing کاربرد داره
@reverseengine
Symbolic Execution
تحلیل مسیرها با مقادیر نمادین و محدودیتها
Concrete Execution
اجرای برنامه با مقادیر واقعی
Concolic Execution
اجرای واقعی همراه با تحلیل نمادین مسیر
در Concolic Execution اجرای واقعی برنامه نقطهی شروعه شرط های مسیر ثبت میشن یکی از شرط ها برای بررسی مسیر جدید تغییر میکنه و حل کننده تلاش میکنه ورودی مناسبی پیدا کنه
این روش در تحلیل باینری تست خودکار کشف مسیر های کمتر بررسی شده و کمک به Fuzzing کاربرد داره
@reverseengine
ReverseEngineering
قسمت سی و پنجم بافر اورفلو Concolic Execution ترکیب اجرای واقعی و نمادین برای کشف مسیرهای جدید Concolic Execution ترکیبی از دو روشه: Concrete Execution: اجرای برنامه با ورودی واقعی Symbolic Execution: بررسی نمادین داده ها و شرط های مسیر اجرا هدف…
Part 35 Buffer Overflow
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
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
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
Image Loading
در برابر Manual Mapping
Windows Loader
چه کاری انجام میده و چرا تفاوت روش های بارگذاری برای تحلیل Memory مهمه؟
تا اینجا با ساختار PE مفهوم
حالا میرسیم به یکی از موضوعات مهم در مهندسی معکوس ویندوز:
تفاوت بین بارگذاری معمول یک فایل اجرایی و قرار گرفتن یک Image در حافظه به روشی متفاوت از مسیر عادی Loader
بارگذاری معمولی PE چطوری انجام میشه؟
وقتی Windows یک فایل اجرایی رو اجرا میکنه یا یک DLL رو بهصورت معمول بارگذاری میکنه بخش های مختلف سیستم عامل در مدیریت این فرایند نقش دارن
نمای ساده شده:
PE File
|
v
Windows Loader
|
├── بررسی ساختار PE
├── آمادهسازی Image
├── مدیریت وابستگیها
├── پردازش Relocation در صورت نیاز
├── آمادهسازی Importها
└── تکمیل مراحل لازم برای بارگذاری
|
v
Image in Memory
این نمودار ساده شده است
جزئیات واقعی به نوع Image وضعیت Process و مسیر بارگذاری بستگی دارن
نکته مهم اینه که بارگذاری PE فقط کپی کردن چند Byte از فایل به RAM نیست ساختار فایل باید برای استفاده در فضای آدرس مجازی آماده بشه و مراحل لازم برای وابستگیها دسترسی حافظه و مقداردهی اولیه انجام بشن
Windows Loader
چه چیزهایی رو مدیریت میکنه؟
چند مورد مهم:
سکشن ها: بخشهای مختلف Image در فضای آدرس مجازی نگاشت میشن
Relocation:
در صورت نیاز آدرسهای وابسته به محل بارگذاری اصلاح میشن
Import
ها:
وابستگی های مورد نیاز برنامه مدیریت و آدرس توابع موردنیاز مشخص میشن
Memory Protection:
محدوده های مختلف حافظه Permission های متناسب با کاربردشون میگیرن
Initialization:
مراحل لازم برای آماده شدن Module برای استفاده انجام میشن
البته تمام مسئولیت ها در یک تابع واحد جمع نشده Windows Loadr Memory Manager و اجزای دیگهی سیستم عامل در این فرایند نقش دارن
Manual Mapping
اصطلاح
بهصورت مفهومی:
PE File
|
v
Custom Loading Logic
|
├── آمادهسازی بخشهای Image
├── مدیریت آدرسها
├── رسیدگی به وابستگیها
└── آمادهسازی ساختار حافظه
|
v
Memory Region
این نمودار مفهوم کلی رو نشون میده نه یک پیادهسازی کامل جزئیات به نوع PE و روش مورد استفاده وابستهان
Manual Mapping
بهخودیخود مترادف بدافزار نیست بعضی ابزارهای تخصصی Runtime ها و محیط های آزمایشی ممکنه از روشهای بارگذاری غیرمعمول استفاده کنن با این حال این مفهوم در تحلیل بعضی تکنیکهای مخرب هم اهمیت داره
نکته مهم اینه که آماده کردن یک PE در حافظه به تنهایی به معنی کامل شدن تمام مراحل بارگذاری معمول ویندوز نیست مدیریت وابستگیها Relocation ، Initialization و سایر جزئیات ممکنه به شکل متفاوتی انجام بشن یا نیازمند پیادهسازی جداگانه باشن
تفاوت اصلی از دید تحلیلگر چیه؟
فرض کنید دو Process داریم
در اولی یک DLL به شکل معمول بارگذاری شده:
Process A
|
└── Loaded Module
|
├── Module Information
├── Image Layout
└── Memory Permissions
در دومی یک ناحیه از حافظه وجود داره که محتوای اون شبیه یک Image اجراییه اما ارتباط واضحی با یک Module معمولی و قابل شناسایی نداره:
Process B
|
└── Memory Region
|
├── PE-like Structure
├── Memory Permissions
└── Unusual Module Relationship
این تفاوت میتونه برای بررسی بیشتر مهم باشه اما بهتنهایی چیزی رو ثابت نمیکنه
برای مثال ممکنه یک ابزار تخصصی از روش بارگذاری متفاوتی استفاده کرده باشه یا اطلاعات Memory Dump برای شناسایی دقیق Module کافی نباشه
چرا فهرست Module ها همیشه کل داستان نیست؟
در ویندوز ابزارهای مختلفی وجود دارن که Module های یک Process رو نمایش میدن با این حال فهرست Module ها و تمام Memory های یک Process دو مفهوم متفاوت هستن
به این مدل توجه کنید:
Process Virtual Address Space
│
├── Executable Image
├── Loaded DLLs
├── Heap Allocations
├── Thread Stacks
├── Memory-Mapped Files
└── Other Memory Regions
هرکدوم از این بخش ها کاربرد متفاوتی دارن
در برابر Manual Mapping
Windows Loader
چه کاری انجام میده و چرا تفاوت روش های بارگذاری برای تحلیل Memory مهمه؟
تا اینجا با ساختار PE مفهوم
ImageBase جدول IAT و تفاوت Raw Offset با RVA آشنا شدیمحالا میرسیم به یکی از موضوعات مهم در مهندسی معکوس ویندوز:
تفاوت بین بارگذاری معمول یک فایل اجرایی و قرار گرفتن یک Image در حافظه به روشی متفاوت از مسیر عادی Loader
بارگذاری معمولی PE چطوری انجام میشه؟
وقتی Windows یک فایل اجرایی رو اجرا میکنه یا یک DLL رو بهصورت معمول بارگذاری میکنه بخش های مختلف سیستم عامل در مدیریت این فرایند نقش دارن
نمای ساده شده:
PE File
|
v
Windows Loader
|
├── بررسی ساختار PE
├── آمادهسازی Image
├── مدیریت وابستگیها
├── پردازش Relocation در صورت نیاز
├── آمادهسازی Importها
└── تکمیل مراحل لازم برای بارگذاری
|
v
Image in Memory
این نمودار ساده شده است
جزئیات واقعی به نوع Image وضعیت Process و مسیر بارگذاری بستگی دارن
نکته مهم اینه که بارگذاری PE فقط کپی کردن چند Byte از فایل به RAM نیست ساختار فایل باید برای استفاده در فضای آدرس مجازی آماده بشه و مراحل لازم برای وابستگیها دسترسی حافظه و مقداردهی اولیه انجام بشن
Windows Loader
چه چیزهایی رو مدیریت میکنه؟
چند مورد مهم:
سکشن ها: بخشهای مختلف Image در فضای آدرس مجازی نگاشت میشن
Relocation:
در صورت نیاز آدرسهای وابسته به محل بارگذاری اصلاح میشن
Import
ها:
وابستگی های مورد نیاز برنامه مدیریت و آدرس توابع موردنیاز مشخص میشن
Memory Protection:
محدوده های مختلف حافظه Permission های متناسب با کاربردشون میگیرن
Initialization:
مراحل لازم برای آماده شدن Module برای استفاده انجام میشن
البته تمام مسئولیت ها در یک تابع واحد جمع نشده Windows Loadr Memory Manager و اجزای دیگهی سیستم عامل در این فرایند نقش دارن
Manual Mapping
اصطلاح
Manual Mapping به روشی اشاره میکنه که در اون یک PE بهجای اینکه تمام مسیر معمول بارگذاری Module رو طی کنه با منطق سفارشی در حافظه آماده میشهبهصورت مفهومی:
PE File
|
v
Custom Loading Logic
|
├── آمادهسازی بخشهای Image
├── مدیریت آدرسها
├── رسیدگی به وابستگیها
└── آمادهسازی ساختار حافظه
|
v
Memory Region
این نمودار مفهوم کلی رو نشون میده نه یک پیادهسازی کامل جزئیات به نوع PE و روش مورد استفاده وابستهان
Manual Mapping
بهخودیخود مترادف بدافزار نیست بعضی ابزارهای تخصصی Runtime ها و محیط های آزمایشی ممکنه از روشهای بارگذاری غیرمعمول استفاده کنن با این حال این مفهوم در تحلیل بعضی تکنیکهای مخرب هم اهمیت داره
نکته مهم اینه که آماده کردن یک PE در حافظه به تنهایی به معنی کامل شدن تمام مراحل بارگذاری معمول ویندوز نیست مدیریت وابستگیها Relocation ، Initialization و سایر جزئیات ممکنه به شکل متفاوتی انجام بشن یا نیازمند پیادهسازی جداگانه باشن
تفاوت اصلی از دید تحلیلگر چیه؟
فرض کنید دو Process داریم
در اولی یک DLL به شکل معمول بارگذاری شده:
Process A
|
└── Loaded Module
|
├── Module Information
├── Image Layout
└── Memory Permissions
در دومی یک ناحیه از حافظه وجود داره که محتوای اون شبیه یک Image اجراییه اما ارتباط واضحی با یک Module معمولی و قابل شناسایی نداره:
Process B
|
└── Memory Region
|
├── PE-like Structure
├── Memory Permissions
└── Unusual Module Relationship
این تفاوت میتونه برای بررسی بیشتر مهم باشه اما بهتنهایی چیزی رو ثابت نمیکنه
برای مثال ممکنه یک ابزار تخصصی از روش بارگذاری متفاوتی استفاده کرده باشه یا اطلاعات Memory Dump برای شناسایی دقیق Module کافی نباشه
چرا فهرست Module ها همیشه کل داستان نیست؟
در ویندوز ابزارهای مختلفی وجود دارن که Module های یک Process رو نمایش میدن با این حال فهرست Module ها و تمام Memory های یک Process دو مفهوم متفاوت هستن
به این مدل توجه کنید:
Process Virtual Address Space
│
├── Executable Image
├── Loaded DLLs
├── Heap Allocations
├── Thread Stacks
├── Memory-Mapped Files
└── Other Memory Regions
هرکدوم از این بخش ها کاربرد متفاوتی دارن
بنابراین:
برای رسیدن به نتیجه باید ساختار حافظه منشا Region و رفتار Process رو کنار هم بررسی کنیم
تحلیلگر چه شواهدی رو بررسی میکنه؟
در بررسی یک Region غیرمعمول این دسته اطلاعات میتونن مفید باشن:
هیچکدوم از این موارد بهتنهایی حکم قطعی نیستن ارزش اصلی زمانی ایجاد میشه که چند شاهد مستقل با هم تطبیق داده بشن
ارتباطش با EDR و Memory Forensics
از دید دفاعی یک سؤال مهم اینه:
آیا میشه با بررسی صرفا فهرست Module ها تمام رفتارهای مرتبط با اجرای کد رو شناخت؟ نه
فهرست Module ها فقط یک نمای محدود از وضعیت Process ارائه میده برای بررسی دقیقتر ممکنه به اطلاعاتی از این جنس نیاز باشه:
Process
|
├── Module Inventory
├── Virtual Memory Map
├── Memory Protection
├── Thread Information
├── Call Stack
└── Event Timeline
|
v
Behavior Analysis
این رویکرد کمک میکنه تفاوت بین رفتار طبیعی برنامه و فعالیت غیرمعمول بهتر بررسی بشه
یک نکته هم مهمه:
همهی EDR ها به یک اندازه به این اطلاعات دسترسی ندارن یا همهی اونها رو به یک شکل تحلیل نمیکنن نتیجه به قابلیت محصول تنظیمات و Telemetry موجود بستگی داره
تمرین مفهومی:
دو وضعیت زیر رو مقایسه کنید
وضعیت اول:
Region Type: Image
Module: Known DLL
Protection: RX
وضعیت دوم:
Region Type: Private
Module: No obvious association
Protection: RX
صرفا از روی این اطلاعات نمیشه گفت وضعیت دوم مخربه اما در وضعیت دوم ممکنه لازم باشه اطلاعات بیشتری جمع کنید
برای هر دو وضعیت این سؤالها رو مطرح کنید:
آیا ساختار حافظه با نوع Region سازگاره؟
Thread ها و Call Stack اطلاعات مرتبطی ارائه میکنن؟
رویداد های قبل و بعد از ایجاد Region توضیح قابل قبولی دارن؟
سه مفهوم رو از هم جدا نگه دارید:
Normal Image Loading:
اپلود از مسیر معمول ویندوز و مدیریت مراحل مرتبط با آن
Manual Mapping:
آمادهسازی یک Image در حافظه با منطق بارگذاری متفاوت از مسیر معمول
Memory Forensics:
بررسی ساختار و وضعیت حافظه برای فهمیدن اتفاقاتی که داخل Process افتادن
این سه مفهوم به هم مرتبطن اما یکی نیستن
@reverseengine
اگر یک Region در فهرست Module ها دیده نشد نمیتونیم نتیجه بگیریم که حتما مخربه یا هیچ ارتباطی با یک فایل اجرایی نداره.
برای رسیدن به نتیجه باید ساختار حافظه منشا Region و رفتار Process رو کنار هم بررسی کنیم
تحلیلگر چه شواهدی رو بررسی میکنه؟
در بررسی یک Region غیرمعمول این دسته اطلاعات میتونن مفید باشن:
هیچکدوم از این موارد بهتنهایی حکم قطعی نیستن ارزش اصلی زمانی ایجاد میشه که چند شاهد مستقل با هم تطبیق داده بشن
ارتباطش با EDR و Memory Forensics
از دید دفاعی یک سؤال مهم اینه:
آیا میشه با بررسی صرفا فهرست Module ها تمام رفتارهای مرتبط با اجرای کد رو شناخت؟ نه
فهرست Module ها فقط یک نمای محدود از وضعیت Process ارائه میده برای بررسی دقیقتر ممکنه به اطلاعاتی از این جنس نیاز باشه:
Process
|
├── Module Inventory
├── Virtual Memory Map
├── Memory Protection
├── Thread Information
├── Call Stack
└── Event Timeline
|
v
Behavior Analysis
این رویکرد کمک میکنه تفاوت بین رفتار طبیعی برنامه و فعالیت غیرمعمول بهتر بررسی بشه
یک نکته هم مهمه:
همهی EDR ها به یک اندازه به این اطلاعات دسترسی ندارن یا همهی اونها رو به یک شکل تحلیل نمیکنن نتیجه به قابلیت محصول تنظیمات و Telemetry موجود بستگی داره
تمرین مفهومی:
دو وضعیت زیر رو مقایسه کنید
وضعیت اول:
Region Type: Image
Module: Known DLL
Protection: RX
وضعیت دوم:
Region Type: Private
Module: No obvious association
Protection: RX
صرفا از روی این اطلاعات نمیشه گفت وضعیت دوم مخربه اما در وضعیت دوم ممکنه لازم باشه اطلاعات بیشتری جمع کنید
برای هر دو وضعیت این سؤالها رو مطرح کنید:
آیا ساختار حافظه با نوع Region سازگاره؟
Thread ها و Call Stack اطلاعات مرتبطی ارائه میکنن؟
رویداد های قبل و بعد از ایجاد Region توضیح قابل قبولی دارن؟
سه مفهوم رو از هم جدا نگه دارید:
Normal Image Loading:
اپلود از مسیر معمول ویندوز و مدیریت مراحل مرتبط با آن
Manual Mapping:
آمادهسازی یک Image در حافظه با منطق بارگذاری متفاوت از مسیر معمول
Memory Forensics:
بررسی ساختار و وضعیت حافظه برای فهمیدن اتفاقاتی که داخل Process افتادن
این سه مفهوم به هم مرتبطن اما یکی نیستن
@reverseengine
ReverseEngineering
Image Loading در برابر Manual Mapping Windows Loader چه کاری انجام میده و چرا تفاوت روش های بارگذاری برای تحلیل Memory مهمه؟ تا اینجا با ساختار PE مفهوم ImageBase جدول IAT و تفاوت Raw Offset با RVA آشنا شدیم حالا میرسیم به یکی از موضوعات مهم در مهندسی…
Image Loading
vs. Manual Mapping
What does Windows Loader do and why are the differences in loading methods important for memory analysis?
So far, we have learned about the PE structure, the concept of the ImageBase table, and the difference between Raw Offset and RVA
Now we come to one of the important topics in Windows reverse engineering:
The difference between normal loading of an executable file and placing an Image in memory in a different way from the normal Loader path
How is normal PE loading done?
When Windows runs an executable file or loads a DLL normally, various parts of the operating system play a role in managing this process
Simplified view:
PE File
|
v
Windows Loader
|
├── Examine the PE structure
├── Prepare the Image
├── Manage dependencies
├── Process relocation if necessary
├── Prepare imports
└── Complete the steps required for loading
|
v
Image in Memory
This diagram is simplified
The actual details depend on the Image type, Process state, and loading path
The important thing is that PE loading is not just copying a few bytes from the file to RAM. The file structure must be prepared for use in the virtual address space, and the necessary steps for dependencies, memory access, and initialization must be performed
What does the Windows Loader handle?
Some important things:
Sections: Different parts of the image are mapped to the virtual address space
Relocation:
If necessary, the addresses dependent on the loading location are modified
Imports:
The dependencies required by the management program and the addresses of the required functions are specified
Memory Protection:
Different memory areas receive permissions appropriate to their use
Initialization:
The steps necessary to prepare the module for use are performed
Of course, not all responsibilities are collected in a single function. The Windows Loader Memory Manager and other operating system components play a role in this process
Manual Mapping
The term Manual Mapping refers to a method in which a PE is prepared in memory with custom logic instead of going through the entire usual module loading path
Conceptually:
PE File
|
v
Custom Loading Logic
|
├── Preparing Image Sections
├── Address Management
├── Handling Dependencies
└── Preparing the Memory Structure
|
v
Memory Region
This diagram shows the general concept, not a complete implementation, details the type of PE and the method used by the dependencies
Manual Mapping
It is not synonymous with malware in itself. Some specialized tools, runtimes and test environments may use unusual loading methods. However, this concept is also important in analyzing some malicious techniques
The important point is that preparing a PE in memory alone does not mean that all the usual Windows loading steps have been completed. Dependency management, relocation, initialization and other details may be done differently or require separate implementations
What is the main difference from the analyst's point of view?
Suppose we have two processes
In the first, a DLL is loaded in the usual way:
Process A
|
└── Loaded Module
|
├── Module Information
├── Image Layout
└── Memory Permissions
In the second, there is a region of memory whose content resembles an executable image but has no clear relationship to a regular and identifiable module:
Process B
|
└── Memory Region
|
├── PE-like Structure
├── Memory Permissions
└── Unusual Module Relationship
This difference can be important for further investigation, but it does not prove anything by itself
For example, a specialized tool may have used a different loading method, or the Memory Dump information may not be enough to accurately identify the Module
Why is the Module List not always the whole story?
In Windows, there are various tools that display the Modules of a Process, however, the Module List and the Total Memory of a Process are two different concepts
Pay attention to this model:
vs. Manual Mapping
What does Windows Loader do and why are the differences in loading methods important for memory analysis?
So far, we have learned about the PE structure, the concept of the ImageBase table, and the difference between Raw Offset and RVA
Now we come to one of the important topics in Windows reverse engineering:
The difference between normal loading of an executable file and placing an Image in memory in a different way from the normal Loader path
How is normal PE loading done?
When Windows runs an executable file or loads a DLL normally, various parts of the operating system play a role in managing this process
Simplified view:
PE File
|
v
Windows Loader
|
├── Examine the PE structure
├── Prepare the Image
├── Manage dependencies
├── Process relocation if necessary
├── Prepare imports
└── Complete the steps required for loading
|
v
Image in Memory
This diagram is simplified
The actual details depend on the Image type, Process state, and loading path
The important thing is that PE loading is not just copying a few bytes from the file to RAM. The file structure must be prepared for use in the virtual address space, and the necessary steps for dependencies, memory access, and initialization must be performed
What does the Windows Loader handle?
Some important things:
Sections: Different parts of the image are mapped to the virtual address space
Relocation:
If necessary, the addresses dependent on the loading location are modified
Imports:
The dependencies required by the management program and the addresses of the required functions are specified
Memory Protection:
Different memory areas receive permissions appropriate to their use
Initialization:
The steps necessary to prepare the module for use are performed
Of course, not all responsibilities are collected in a single function. The Windows Loader Memory Manager and other operating system components play a role in this process
Manual Mapping
The term Manual Mapping refers to a method in which a PE is prepared in memory with custom logic instead of going through the entire usual module loading path
Conceptually:
PE File
|
v
Custom Loading Logic
|
├── Preparing Image Sections
├── Address Management
├── Handling Dependencies
└── Preparing the Memory Structure
|
v
Memory Region
This diagram shows the general concept, not a complete implementation, details the type of PE and the method used by the dependencies
Manual Mapping
It is not synonymous with malware in itself. Some specialized tools, runtimes and test environments may use unusual loading methods. However, this concept is also important in analyzing some malicious techniques
The important point is that preparing a PE in memory alone does not mean that all the usual Windows loading steps have been completed. Dependency management, relocation, initialization and other details may be done differently or require separate implementations
What is the main difference from the analyst's point of view?
Suppose we have two processes
In the first, a DLL is loaded in the usual way:
Process A
|
└── Loaded Module
|
├── Module Information
├── Image Layout
└── Memory Permissions
In the second, there is a region of memory whose content resembles an executable image but has no clear relationship to a regular and identifiable module:
Process B
|
└── Memory Region
|
├── PE-like Structure
├── Memory Permissions
└── Unusual Module Relationship
This difference can be important for further investigation, but it does not prove anything by itself
For example, a specialized tool may have used a different loading method, or the Memory Dump information may not be enough to accurately identify the Module
Why is the Module List not always the whole story?
In Windows, there are various tools that display the Modules of a Process, however, the Module List and the Total Memory of a Process are two different concepts
Pay attention to this model:
ReverseEngineering
Image Loading در برابر Manual Mapping Windows Loader چه کاری انجام میده و چرا تفاوت روش های بارگذاری برای تحلیل Memory مهمه؟ تا اینجا با ساختار PE مفهوم ImageBase جدول IAT و تفاوت Raw Offset با RVA آشنا شدیم حالا میرسیم به یکی از موضوعات مهم در مهندسی…
Process Virtual Address Space
│
├── Executable Image
├── Loaded DLLs
├── Heap Allocations
├── Thread Stacks
├── Memory-Mapped Files
└── Other Memory Regions
Each of these sections has a different purpose
│
├── Executable Image
├── Loaded DLLs
├── Heap Allocations
├── Thread Stacks
├── Memory-Mapped Files
└── Other Memory Regions
Each of these sections has a different purpose
Consider this:
If a memory region does not appear in the list of loaded modules, should we conclude that it is necessarily malicious or unrelated to any executable file?
To reach a conclusion, we must examine the memory structure, the origin of the region, and the process's behavior in conjunction with one another.
What evidence does an analyst examine?
When investigating an unusual memory region, the following types of information can be useful:
None of these factors, taken in isolation, constitute definitive proof; the real value lies in how they are correlated with one another.
Relevance to EDR and Memory Forensics
From a defensive perspective, a key question arises:
Is it possible to identify all behaviors associated with code execution solely by examining the list of loaded modules? No.
The module list provides only a limited view of the process's state. A more thorough investigation may require data such as:
Process
|
├── Module Inventory
├── Virtual Memory Map
├── Memory Protection
├── Thread Information
├── Call Stack
└── Event Timeline
|
v
Behavioral Analysis
This facilitates a better distinction between the application's normal behavior and anomalous activity.
An important caveat:
Not all EDR (Endpoint Detection and Response) solutions have equal access to this information, nor do they all analyze it in the same way; the outcome depends on the quality of the product and the available telemetry.
Conceptual Exercise:
Compare the following two scenarios:
Scenario 1:
Region Type: Image
Module: Known DLL
Protection: RX (Read/Execute)
Scenario 2:
Region Type: Private
Module: No clear association
Protection: RX (Read/Execute)
Based solely on this information, one cannot definitively label the second scenario as malicious; however, it may warrant further data collection.
For both scenarios, ask the following question:
Is the memory structure consistent with the region type? Do the threads and the call stack provide relevant information?
Are there events occurring before and after the creation of a memory region?
Keep these three concepts distinct:
Standard Image Loading:
Loading via the conventional Windows path and managing the associated steps.
Manual Mapping:
Preparing an image in memory using loading logic that differs from the standard path.
Memory Forensics:
Examining the structure and state of memory to understand the events that took place within a process.
While related, these three concepts are not the same.
@reverseengine
If a memory region does not appear in the list of loaded modules, should we conclude that it is necessarily malicious or unrelated to any executable file?
To reach a conclusion, we must examine the memory structure, the origin of the region, and the process's behavior in conjunction with one another.
What evidence does an analyst examine?
When investigating an unusual memory region, the following types of information can be useful:
None of these factors, taken in isolation, constitute definitive proof; the real value lies in how they are correlated with one another.
Relevance to EDR and Memory Forensics
From a defensive perspective, a key question arises:
Is it possible to identify all behaviors associated with code execution solely by examining the list of loaded modules? No.
The module list provides only a limited view of the process's state. A more thorough investigation may require data such as:
Process
|
├── Module Inventory
├── Virtual Memory Map
├── Memory Protection
├── Thread Information
├── Call Stack
└── Event Timeline
|
v
Behavioral Analysis
This facilitates a better distinction between the application's normal behavior and anomalous activity.
An important caveat:
Not all EDR (Endpoint Detection and Response) solutions have equal access to this information, nor do they all analyze it in the same way; the outcome depends on the quality of the product and the available telemetry.
Conceptual Exercise:
Compare the following two scenarios:
Scenario 1:
Region Type: Image
Module: Known DLL
Protection: RX (Read/Execute)
Scenario 2:
Region Type: Private
Module: No clear association
Protection: RX (Read/Execute)
Based solely on this information, one cannot definitively label the second scenario as malicious; however, it may warrant further data collection.
For both scenarios, ask the following question:
Is the memory structure consistent with the region type? Do the threads and the call stack provide relevant information?
Are there events occurring before and after the creation of a memory region?
Keep these three concepts distinct:
Standard Image Loading:
Loading via the conventional Windows path and managing the associated steps.
Manual Mapping:
Preparing an image in memory using loading logic that differs from the standard path.
Memory Forensics:
Examining the structure and state of memory to understand the events that took place within a process.
While related, these three concepts are not the same.
@reverseengine
Writing a Hyper-V Bridge for Fuzzing
https://www.alex-ionescu.com/writing-a-hyper-v-bridge-for-fuzzing-part-1-wdf
@reverseengine
https://www.alex-ionescu.com/writing-a-hyper-v-bridge-for-fuzzing-part-1-wdf
@reverseengine
REA Is Impressive. But Reverse Engineering Was Never the New Part
https://crizantlai.medium.com/rea-is-impressive-but-reverse-engineering-was-never-the-new-part-ae5b26b958a1
https://crizantlai.medium.com/rea-is-impressive-but-reverse-engineering-was-never-the-new-part-ae5b26b958a1
How Dangerous Is REA (Reverse Engineer Anything) for Your App?
https://21zerixpm.medium.com/how-dangerous-is-rea-reverse-engineer-anything-for-your-app-2fce588968f1
@reverseengine
https://21zerixpm.medium.com/how-dangerous-is-rea-reverse-engineer-anything-for-your-app-2fce588968f1
@reverseengine
Medium
How Dangerous Is REA (Reverse Engineer Anything) for Your App?
An iOS developer’s look at the AI tool that took over GitHub Trending, and what it means for your app and your business.