en
Feedback

Don't get caught by a cheater! Telemetrio finds and tags such channels 👉 If you want to see the tag, subscribe

ReverseEngineering

ReverseEngineering

Open in Telegram
1 442
Subscribers
No data24 hours
+1087 days
+11030 days
Posts Archive
For this step, we usually go to tools like
GDB Ghidra IDA ASan
So the general path of the work can be like this Seed Input │ ▼ AFL++ │ ▼ New Paths │ ▼ Interesting Input │ ▼ Crash │ ▼ GDB │ ▼ Root Cause Analysis That is, the Fuzzer finds an interesting input Then this input may cause a Crash From here on, we get into the story and we need to find out what the real cause of the problem was AFL++ By sending different inputs, it does not only look for Crash, it also follows new paths of the program Instrumentation helps us understand what parts of the program an input has executed Each new path can be a new way to reach a hidden bug And when a Crash is found The Fuzzer's work is finished And it's our turn to find the real cause of the problem Exercise: Change the same program with a third condition For example, add a new condition after !HELLO On paper, think about what inputs AFL++ might find in order to cover all three paths of the program @reverseengine

Part 28 Buffer Overflow
AFL++ and Instrumentation How does a Fuzzer understand what is going on inside a program In the previous section, we learned about libFuzzer and Coverage Guided Fuzzing Now let's move on to AFL++ AFL++ is one of the most famous fuzzing tools, which is especially used for testing C, C++, and binary programs. We want to understand how AFL++ understands that a program input has entered a new path AFL++ does not just send random input Suppose we have a program that has different paths Input │ ▼ ┌────────┐ │ Check A │ └────┬────┘ │ ┌─────┴───┐ ▼ ▼ Path 1 Path 2 │ ▼ ┌───────┐ │ Check B │ └──────┬───┘ │ ┌──────┐ │ ┌──────┐ ▼ ▼ Path 3 Path 4 If AFL++ sends an input and the program enters Path 1 That path is recorded Then AFL++ changes the input and tries it again If the new input causes the program to enter Path 2 AFL++ notices that a new path has been found This new input becomes valuable and keeps it What does Instrumentation mean Here we come to the important part Instrumentation means adding a series of mechanisms to the program so that we can understand what happened inside it during execution For example, AFL++ can collect information about the path of the program execution with Instrumentation In simple terms: Before Instrumentation Input │ ▼ Program │ ▼ Result After Instrumentation Input │ ▼ Program │ ▼ Path Tracking │ ▼ Result That means the program is still doing its job, but now someone is also checking which paths the program has taken I named this task Instrumentation to make it seem a little more scientific An example Simple: Suppose we have this program C
#include <stdio.h> #include <string.h> int main(void) { char input[32]; if (!fgets(input, sizeof(input), stdin)) return 0; if (strncmp(input, "HELLO", 5) == 0) { puts("First check passed"); if (input[5] == '!') { puts("Second check passed"); } } return 0; }
What is AFL++ looking for here? First it might try simple inputs AAAA This input just executes a normal path Then AFL++ starts modifying the input If it reaches HELLO a new branch is executed So AFL++ realizes that this input is interesting Then it modifies this input further For example: HELLO! Now the second condition is also met So another new path has been found Simply we can see this process like this AAAA │ ▼ Normal path │ ▼ AFL++ changes │ ▼ HELLO │ ▼ New path │ ▼ AFL++ changes again │ ▼ HELLO! │ ▼ Newer path Seed or Corpus What does it mean AFL++ Usually starts with a series of initial inputs, we call these initial inputs Seed For example, a simple file project ├── input │ └── seed1 └── output Inside seed1 can be just this AAAA Then AFL++ changes this input repeatedly deletes adds changes bytes and checks which change caused a new path to be found Inputs that are worth it can be placed in Corpus Corpus means a set of interesting inputs that the Fuzzer can use to continue Fuzzing So we can imagine this process like this Seed │ ▼ Mutation │ ├── Input A ──► Not a new path │ ├── Input B ──► New path │ │ │ ▼ │ Corpus │ └── Input C ──► Path Newer │ ▼ Corpus An example of file structure Suppose we have this folder project ├── input │ └── seed1 └── output Inside seed1 can be just this AAAA Then AFL++ starts with this input and gradually generates new inputs An important point When AFL++ finds a Crash The work is not over yet, the interesting part begins We need to find out What input caused the Crash Where exactly did the Crash happen What function was involved Is the problem really a Memory Bug or not

برای این مرحله معمولا میریم سراغ ابزارهایی مثل
GDB Ghidra IDA ASan
پس مسیر کلی کار میتونه این شکلی باشه Seed Input │ ▼ AFL++ │ ▼ New Paths │ ▼ Interesting Input │ ▼ Crash │ ▼ GDB │ ▼ Root Cause Analysis یعنی Fuzzer یه ورودی جالب پیدا میکنه بعد ممکنه همین ورودی باعث Crash بشه از اینجا به بعد ما وارد ماجرا میشیم و باید بفهمیم علت واقعی مشکل چی بوده AFL++ با فرستادن ورودی های مختلف فقط دنبال Crash نیست مسیرهای جدید برنامه رو هم دنبال میکنه Instrumentation کمک میکنه بفهمیم یه ورودی چه بخش هایی از برنامه رو اجرا کرده هر مسیر جدید میتونه یه راه جدید برای رسیدن به یه باگ پنهان باشه و وقتی Crash پیدا شد کار Fuzzer تموم میشه و نوبت ما میرسه که علت واقعی مشکل رو پیدا کنیم تمرین: همین برنامه رو با یه شرط سوم تغییر بدید مثلا بعد از !HELLO یه شرط جدید اضافه کنید بعد روی کاغذ فکر کنید AFL++ چه ورودی هایی ممکنه به ترتیب پیدا کنه تا هر سه مسیر برنامه رو پوشش بده @reverseengine

بخش بیست و هشتم بافر اورفلو
AFL++ و Instrumentation چطور Fuzzer میفهمه داخل برنامه چه خبره توی بخش قبل با libFuzzer و Coverage Guided Fuzzing آشنا شدیم حالا میریم سراغ AFL++ AFL++ یکی از معروف ترین ابزارهای Fuzzing هست که مخصوصا برای تست برنامه های C و ++C و Binary ها خیلی استفاده میشه میخوایم بفهمیم AFL++ چطور متوجه میشه یه ورودی برنامه رو وارد یه مسیر جدید کرده AFL++ فقط ورودی تصادفی نمیفرسته فرض کن یه برنامه داریم که مسیرهای مختلفی داره Input │ ▼ ┌─────────┐ │ Check A │ └────┬────┘ │ ┌────┴────┐ ▼ ▼ Path 1 Path 2 │ ▼ ┌─────────┐ │ Check B │ └────┬────┘ │ ┌────┴────┐ ▼ ▼ Path 3 Path 4 اگر AFL++ یه ورودی بفرسته و برنامه وارد Path 1 بشه اون مسیر ثبت میشه بعد AFL++ ورودی رو تغییر میده و دوباره امتحانش میکنه اگر ورودی جدید باعث بشه برنامه وارد Path 2 بشه AFL++ متوجه میشه یه مسیر جدید پیدا شده همین ورودی جدید ارزشمند میشه و نگهش میداره Instrumentation یعنی چی اینجا میرسیم به بخش مهم Instrumentation یعنی اضافه کردن یه سری مکانیزم به برنامه تا بتونیم بفهمیم موقع اجرا چه اتفاقی داخلش افتاده مثلا AFL++ میتونه با Instrumentation اطلاعاتی درباره مسیر اجرای برنامه جمع کنه به زبون ساده: قبل از Instrumentation Input │ ▼ Program │ ▼ Result بعد از Instrumentation Input │ ▼ Program │ ▼ Path Tracking │ ▼ Result یعنی برنامه همچنان کار خودش رو انجام میده ولی حالا یه نفر هم داره بررسی میکنه برنامه از چه مسیرهایی رد شده اسم این کار رو گذاشتن Instrumentation تا قضیه یکم علمی تر به نظر بیاد یه مثال ساده: فرض کنید این برنامه رو داریم C
#include <stdio.h> #include <string.h> int main(void) { char input[32]; if (!fgets(input, sizeof(input), stdin)) return 0; if (strncmp(input, "HELLO", 5) == 0) { puts("First check passed"); if (input[5] == '!') { puts("Second check passed"); } } return 0; }
AFL++ اینجا دنبال چیه اول ممکنه ورودی های ساده رو امتحان کنه AAAA این ورودی فقط یه مسیر معمولی رو اجرا میکنه بعد AFL++ شروع میکنه ورودی رو تغییر دادن اگر به این برسه HELLO یه شاخه جدید اجرا میشه پس AFL++ متوجه میشه این ورودی جالبه بعد همین ورودی رو بیشتر تغییر میده مثلا: HELLO! حالا شرط دوم هم رد شده پس یه مسیر جدید دیگه پیدا شده به صورت ساده میتونیم این روند رو اینطوری ببینیم AAAA │ ▼ مسیر معمولی │ ▼ AFL++ تغییر میده │ ▼ HELLO │ ▼ مسیر جدید │ ▼ AFL++ دوباره تغییر میده │ ▼ HELLO! │ ▼ مسیر جدیدتر Seed یا Corpus یعنی چی AFL++ معمولا با یه سری ورودی اولیه شروع میکنه به این ورودی های اولیه میگیم Seed مثلا یه فایل ساده project ├── input │ └── seed1 └── output داخل seed1 میتونه فقط این باشه AAAA بعد AFL++ همین ورودی رو بارها تغییر میده حذف میکنه اضافه میکنه بایت ها رو تغییر میده و بررسی میکنه کدوم تغییر باعث شده یه مسیر جدید پیدا بشه ورودی هایی که ارزش داشته باشن میتونن در Corpus قرار بگیرن Corpus یعنی مجموعه ای از ورودی های جالب که Fuzzer میتونه از اونها برای ادامه Fuzzing استفاده کنه پس میتونیم این روند رو اینطوری تصور کنیم Seed │ ▼ Mutation │ ├── Input A ──► مسیر جدید نیست │ ├── Input B ──► مسیر جدید │ │ │ ▼ │ Corpus │ └── Input C ──► مسیر جدیدتر │ ▼ Corpus یک مثال از ساختار فایل ها فرض کنید این پوشه رو داریم project ├── input │ └── seed1 └── output داخل seed1 میتونه فقط این باشه AAAA بعد AFL++ با همین ورودی شروع میکنه و به مرور ورودی های جدید تولید میکنه یک نکته مهم وقتی AFL++ یه Crash پیدا میکنه کار تموم نشده تازه قسمت جذاب ماجرا شروع میشه باید بفهمیم چه ورودی باعث Crash شده Crash دقیقا کجا اتفاق افتاده چه تابعی درگیر بوده آیا مشکل واقعا یه Memory Bug هست یا نه

As a result, less RAM and CPU are consumed To put it very simply in a sentence Copy on Write means that the operating system does not copy anything until it is forced to, and this is one of the good examples that shows how interdependent Process and Memory are @reverseengine

Copy on Write or COW How does the operating system create a fork without copying everything In previous posts, we said that when a fork is executed, a Child Process is created At first glance, we might think that the operating system does this Parent Process │ ▼ Copying the entire memory │ ▼ Child Process That is, it takes the entire memory of the Parent and copies it again for the Child But this has a big problem Suppose how many gigabytes of memory the Parent has used If the operating system copies the entire memory every time a fork is executed It takes a lot of time It uses a lot of RAM The CPU also gets unnecessarily busy copying information But this time the operating system acts a little more wisely Copy on Write or COW solution The operating system says Don't copy anything for now After the fork, at first the Parent and Child can use the same memory pages Simply Physical Memory │ ┌───────────────┐ │ │ Parent Child │ │ └─� │ Shared Page As long as neither of them has changed anything, there is no need to create a separate copy Now what happens if one of them wants to change something Suppose Parent and Child initially have this value int x = 10 Now Child wants to do this x = 20 Here the operating system understands that Child wants to change the shared Page So this is what happens Before changing Parent │ ▼ ┌──────────────┐ │ Page x = 10 │ └───────────┘ ▲ │ Child That is, both of them have access to the same Page for now Now Child wants to write something to that Page The operating system does not copy the entire memory It copies only the Page that is needed Then the result will be something like this Physical Memory ┌─ ┌─────────────────────────────────── │ Page x = 20 │ │ Child │ └──────────────────────────────────────────� This is how fork │ ▼ Currently, the entire memory is not copied │ ▼ Child is executed │ ▼ exec │ ▼ New program is replaced As a result, a lot of time and memory is saved What happens behind the scenes After fork, the operating system can keep the Parent and Child Pages as Shared But it protects these Pages for writing Now if one of the Processes wants to write to such a Page Write Attempt │ ▼ CPU detects protected write │ ▼ Page Fault │ ▼ Kernel handles it │ ▼ Copy the Page │ ▼ Give the writer its own Page │ ▼ Continue execution That is, Page Fault does not always mean that the program is broken Sometimes Page Fault is a completely normal occurrence in memory management This will be much more useful when we get to Virtual Memory and Paging An important point Suppose a program changes a variable in Parent and you expect Child to see the new value too If Parent and Child are two independent processes. Usually this expectation is not correct. After one of the processes changes that page due to COW, each has its own version. That is, Parent Memory│ ▼ Separate Page Child Memory│ ▼ Separate Page So: Parent Memory ≠ Child Memory This is important for analyzing multiprocess programs. For example, when debugging, Parent and Child may initially behave similarly. But after one of them changes its memory, their paths diverge. Don't confuse COW with Shared Memory. These two look similar, but they are not the same. In COW, the data may be shared. One wants to write. Separate versions are created. But in Shared Memory, Parent│ ▼ Shared Memory▲ │ Child If Parent changes the data, Child can see the change. So the purpose of these two is different. The purpose of COW is to make fork more efficient. And the operating system does not have to copy the entire memory for no reason The purpose of Shared Memory is that several processes can actually use a shared memory space Copy on Write or COW After fork, the entire memory does not have to be copied immediately Parent and Child can use shared physical pages first Pages are protected to prevent direct writing When one of the processes tries to change something, a Page Fault occurs Kernel handles this event A separate copy of that page is created The process that has changed the information uses its own copy

داده ممکنه مشترک باشه │ ▼ یکی میخواد بنویسه │ ▼ نسخه جدا ساخته میشه ولی در Shared Memory Parent │ ▼ Shared Memory ▲ │ Child اگه Parent اطلاعات رو تغییر بده Child میتونه تغییر رو ببینه پس هدف این دوتا فرق داره هدف COW اینه که fork بهینه تر انجام بشه و سیستم عامل مجبور نشه بی دلیل کل حافظه رو کپی کنه هدف Shared Memory اینه که چند Process بتونن واقعا از یه فضای حافظه مشترک استفاده کنن Copy on Write یا COW بعد از fork لازم نیست کل حافظه فورا کپی بشه Parent و Child میتونن اول از Page های فیزیکی مشترک استفاده کنن Page ها برای جلوگیری از نوشتن مستقیم محافظت میشن وقتی یکی از Process ها بخواد چیزی رو تغییر بده Page Fault اتفاق میفته Kernel این اتفاق رو مدیریت میکنه یه نسخه جدا از اون Page ساخته میشه Process ی که اطلاعات رو تغییر داده از نسخه خودش استفاده میکنه در نتیجه RAM و CPU کمتری مصرف میشه اگه بخوایم خیلی ساده توی یه جمله بگیم Copy on Write یعنی سیستم عامل تا وقتی مجبور نشده چیزی رو کپی نمیکنه و این یکی از مثال های خوبیه که نشون میده Process و Memory چقدر به هم وابسته هستن @reverseengine

Copy on Write یا COW سیستم عامل چطور بدون کپی کردن همه چیز fork میسازه توی پست های قبلی گفتیم وقتی fork اجرا میشه یک Child Process ساخته میشه در نگاه اول شاید فکر کنیم سیستم عامل این کارو میکنه Parent Process │ ▼ کپی کامل حافظه │ ▼ Child Process یعنی کل حافظه Parent رو برمیداره و دوباره برای Child کپی میکنه ولی این کار یه مشکل بزرگ داره فرض کن Parent چند گیگابایت حافظه استفاده کرده اگه سیستم عامل هر بار که fork اجرا میشه کل حافظه رو کپی کنه زمان زیادی مصرف میشه RAM زیادی مصرف میشه CPU هم بیخودی درگیر کپی کردن اطلاعات میشه ولی این بار سیستم عامل یه کم عاقل تر عمل میکنه راه حل Copy on Write یا COW سیستم عامل میگه فعلا چیزی رو کپی نکنید بعد از fork در ابتدا Parent و Child میتونن از همون Page های حافظه استفاده کنن به صورت ساده Physical Memory │ ┌─────────┴─────────┐ │ │ Parent Child │ │ └─────────┬─────────┘ │ Shared Page تا وقتی هیچکدوم چیزی رو تغییر نداده باشن لازم نیست یه نسخه جدا ساخته بشه حالا اگه یکی از اونها بخواد چیزی رو تغییر بده چی میشه فرض کنید Parent و Child اول کار این مقدار رو دارن int x = 10 حالا Child میخواد این کارو انجام بده x = 20 اینجا سیستم عامل متوجه میشه Child میخواد Page مشترک رو تغییر بده پس تقریبا این اتفاق میفته قبل از تغییر Parent │ ▼ ┌──────────────┐ │ Page x = 10 │ └──────────────┘ ▲ │ Child یعنی فعلا هر دو به همون Page دسترسی دارن حالا Child میخواد روی اون Page چیزی بنویسه سیستم عامل کل حافظه رو کپی نمیکنه فقط همون Page ای که لازم شده رو کپی میکنه بعد نتیجه تقریبا این شکلی میشه Physical Memory ┌──────────────────────┐ │ Page x = 10 │ │ Parent │ └──────────────────────┘ ┌──────────────────────┐ │ Page x = 20 │ │ Child │ └──────────────────────┘ پس Parent همچنان x برابر 10 داره و Child نسخه خودش رو داره که x برابر 20 شده به همین دلیل اسمش شده Copy on Write یعنی وقتی نیاز به نوشتن پیش اومد کپی کن چرا این روش خیلی مهمه چون توی خیلی از برنامه ها Child بلافاصله بعد از fork میره سراغ exec یعنی fork │ ▼ Child │ ▼ exec یادتون هست exec چیکار میکرد برنامه فعلی رو با یه برنامه جدید جایگزین میکرد پس اگه سیستم عامل قبل از exec کل حافظه Parent رو کپی میکرد ممکن بود مقدار زیادی از اون حافظه اصلا هیچ وقت استفاده نشه با COW اتفاق به شکل ساده اینطوریه fork │ ▼ فعلا حافظه کامل کپی نمیشه │ ▼ Child اجرا میشه │ ▼ exec │ ▼ Program جدید جایگزین میشه در نتیجه مقدار زیادی زمان و حافظه ذخیره میشه پشت صحنه چه اتفاقی میفته بعد از fork سیستم عامل میتونه Page های Parent و Child رو به صورت Shared نگه داره ولی این Page ها رو برای نوشتن محافظت میکنه حالا اگه یکی از Process ها بخواد روی همچین Page ای بنویسه Write Attempt │ ▼ CPU detects protected write │ ▼ Page Fault │ ▼ Kernel handles it │ ▼ Copy the Page │ ▼ Give the writer its own Page │ ▼ Continue execution یعنی Page Fault همیشه به معنی خراب شدن برنامه نیست گاهی Page Fault کاملا یه اتفاق طبیعی توی مدیریت حافظه است این موضوع وقتی برسیم به Virtual Memory و Paging خیلی بیشتر به کارمون میاد یه نکته مهم فرض کنید یه برنامه توی Parent یه متغیر رو تغییر میده و انتظار دارید Child هم مقدار جدید رو ببینه اگه Parent و Child دو Process مستقل باشن معمولا این انتظار درست نیست بعد از اینکه یکی از Process ها به خاطر COW اون Page رو تغییر بده هر کدوم نسخه خودشون رو دارن یعنی Parent Memory │ ▼ Separate Page Child Memory │ ▼ Separate Page پس: Parent Memory ≠ Child Memory این موضوع برای تحلیل برنامه های چندپردازه ای مهمه مثلا موقع Debugging ممکنه Parent و Child اول کار رفتار مشابهی داشته باشن ولی بعد از اینکه یکی از اونها حافظه خودش رو تغییر داد مسیرشون از هم جدا میشه COW رو با Shared Memory اشتباه نگیرید این دوتا شبیه هم به نظر میان ولی یکی نیستن در COW اول │ ▼

Telemetry Context Timeline Correlation
All of these, when considered together, provide more accurate detection And that's exactly why AV and EDR Evasion is not just about changing an API New defense systems try to see the effect and outcome of an operation Even if the path to that operation has changed @reverseengine

For example, a part of memory may have changed but it is not yet clear whether this change was actually used or not But if a new execution path appears later and a Thread starts running from that path, the case becomes more meaningful for the analyst To analyze the behavior of a Process, you need to look at these things together
Process Tree Cross Process Access Memory Regions Memory Permission Changes Thread Start Address Call Stack Timeline
None of these are enough on their own But when they are all put together, the detection is stronger

Thread Analysis and Start Address Sometimes the Process itself doesn't show anything special, but its Threads reveal the real story So far we have seen that EDR doesn't just look at the file name or Process One of the things that is examined more deeply is the Threads inside the Process Each Process can have several Threads Each Thread has its own execution path Therefore, when EDR or an analyst wants to understand what exactly is going on inside a Process, examining the Threads can give him important information Start Address Very simply, each Thread must start from a point to execute That point is called the Start Address That is, EDR can check where in memory the Thread started executing For example, the Start Address may be inside a known DLL Or it may be inside an unknown part of memory If it is inside a regular DLL, the Context must still be checked If it is inside an unknown part of memory, it may look more suspicious But there is a very important point here Every unknown Memory Region is not necessarily malicious Some programs generate code themselves when they run or map memory in a special way They manage For example, JIT Compilers, browsers, and some runtimes may have behaviors that may seem strange from the outside Because humans obviously don't accept a simple False Positive and prefer the security system to kill their healthy program sometim😁 EDR doesn't just look at the Start Address In real analysis, several things are usually checked together First, when the Thread was created For example, a program is executed A few minutes later, an unusual event occurs in memory Immediately after that, a new Thread is created The timing of events is important here Because an Event alone may not be anything special, but when several Events occur in succession, the story is different Second, who caused the Thread to be created For example, a Process communicates with another Process Then a new Thread appears inside that Process This connection between the two Processes can be very important for Detection Sometimes the Thread itself is not strange, but the events that occurred before its creation are suspicious Third, where exactly the Thread is executed EDR can look at the Memory Region around the Start Check the address For example, this memory may be related to a Module It may be Private Memory It may be related to an Image Or another Memory Region For example, if a Thread starts executing from a Private Executable Region, it may need further investigation Especially if an unusual event has occurred on the same part of memory before that The fourth is the Call Stack The Call Stack Can show to some extent the path and functions through which the Thread reached its current state That is, EDR does not only see the current point of the Thread and may also examine the path to reach that point If the execution path does not match the usual behavior of that program, it can be a suspicious sign But still, a strange Call Stack alone means that the program is not malicious Debugging Instrumentation And various runtimes can also create structures that do not seem normal Detection mental model Suppose this event is only seen A new Thread is created It does not give us much information on its own But now suppose these events happened one after the other Process A is executed Then It communicates with Process B A memory event occurs inside Process B Then an executable appears in memory Then a Thread starts executing Then network activity starts Here the situation is different Because EDR does not see just one event It sees the relationship between events And this is the main point EDR is not looking for a magic Event It looks for the relationship between Events Why are Threads important to the analyst Because they can establish a connection between the changes that occurred in memory and the actual execution

Telemetry Context Timeline Correlation
همه اینا وقتی با هم بررسی بشن Detection دقیق تری میدن و دقیقا به همین دلیله که بحث AV و EDR Evasion فقط عوض کردن یک API نیست سیستم های دفاعی جدید سعی میکنن اثر و نتیجه عملیات رو ببینن حتی اگر مسیر انجام اون عملیات تغییر کرده باشه @reverseengine

Thread Analysis و Start Address بعضی وقتا خود Process چیز خاصی نشون نمیده ولی Thread هاش داستان اصلی رو لو میدن تا اینجا دیدیم که EDR فقط اسم فایل یا Process رو نگاه نمیکنه یکی از چیزایی که عمیق تر بررسی میشه Thread های داخل Process هستن هر Process میتونه چند تا Thread داشته باشه هر Thread هم مسیر اجرای خودش رو داره برای همین وقتی EDR یا یک تحلیلگر میخواد بفهمه داخل یک Process دقیقا چه خبره بررسی Thread ها میتونه اطلاعات مهمی بهش بده Start Address خیلی ساده هر Thread برای اجرا باید از یک نقطه شروع کنه به اون نقطه میگن Start Address یعنی EDR میتونه بررسی کنه که Thread از کجای حافظه شروع به اجرا کرده مثلا ممکنه Start Address داخل یک DLL شناخته شده باشه یا ممکنه داخل یک قسمت ناشناس از حافظه باشه اگر داخل یک DLL معمولی باشه باز هم باید Context بررسی بشه اگر داخل یک قسمت ناشناس از حافظه باشه ممکنه مشکوک تر به نظر برسه ولی اینجا یک نکته خیلی مهم وجود داره هر Memory Region ناشناسی لزوما مخرب نیست بعضی برنامه ها خودشون موقع اجرا کد تولید میکنن یا حافظه رو به شکل خاصی مدیریت میکنن مثلا JIT Compiler ها مرورگرها و بعضی Runtime ها ممکنه رفتارهایی داشته باشن که از بیرون عجیب به نظر برسه چون مشخصا انسان ها یک False Positive ساده رو نمیپذیرن و ترجیح میدن سیستم امنیتی برنامه سالمشون رو هم گاهی بکشه😁 EDR فقط Start Address رو نگاه نمیکنه در تحلیل واقعی معمولا چند تا چیز با هم بررسی میشه اول اینکه Thread چه زمانی ساخته شده مثلا یک برنامه اجرا میشه چند دقیقه بعد یک اتفاق غیرعادی در حافظه میفته بلافاصله بعدش یک Thread جدید ساخته میشه اینجا زمان بندی اتفاق ها مهمه چون یک Event به تنهایی ممکنه چیز خاصی نباشه ولی وقتی چند Event پشت سر هم اتفاق میفتن داستان فرق میکنه دوم اینکه چه کسی باعث ایجاد Thread شده مثلا یک Process با Process دیگه ارتباط برقرار میکنه بعد داخل اون Process یک Thread جدید ظاهر میشه همین ارتباط بین دو Process میتونه برای Detection خیلی مهم باشه بعضی وقت ها خود Thread چیز عجیبی نیست ولی اتفاقاتی که قبل از ایجادش افتاده مشکوک هستن سوم اینکه Thread دقیقاً از کجا اجرا میشه EDR میتونه Memory Region اطراف Start Address رو بررسی کنه مثلا این حافظه ممکنه مربوط به یک Module باشه ممکنه Private Memory باشه ممکنه مربوط به یک Image باشه یا یک Memory Region دیگه باشه مثلا اگر Thread از یک Private Executable Region شروع به اجرا کنه ممکنه نیاز به بررسی بیشتری داشته باشه مخصوصاً اگر قبل از اون یک اتفاق غیرعادی روی همون قسمت از حافظه افتاده باشه چهارم Call Stack هست Call Stack میتونه تا حدی نشون بده Thread از چه مسیر و چه Function هایی به وضعیت الانش رسیده یعنی EDR فقط نقطه فعلی Thread رو نمیبینه و ممکنه مسیر رسیدن به اون نقطه رو هم بررسی کنه اگر مسیر اجرا با رفتار معمول اون برنامه جور درنیاد میتونه یک نشونه مشکوک باشه ولی باز هم یک Call Stack عجیب به تنهایی یعنی برنامه مخرب نیست ابزارهای Debugging Instrumentation و Runtime های مختلف هم میتونن ساختارهایی ایجاد کنن که معمولی به نظر نرسن مدل فکری Detection فرض کنید فقط این اتفاق دیده بشه یک Thread جدید ساخته شده به تنهایی اطلاعات زیادی بهمون نمیده ولی حالا فرض کنید این اتفاق ها پشت سر هم افتاده Process A اجرا میشه بعد با Process B ارتباط برقرار میکنه داخل Process B یک اتفاق مربوط به حافظه رخ میده بعد یک قسمت قابل اجرا در حافظه ظاهر میشه بعد یک Thread شروع به اجرا میکنه بعد فعالیت شبکه شروع میشه اینجا قضیه فرق میکنه چون EDR فقط یک اتفاق رو نمیبینه داره رابطه بین اتفاق ها رو میبینه و این نکته اصلیه EDR دنبال یک Event جادویی نیست دنبال ارتباط بین Event هاست Thread ها چرا برای تحلیلگر مهمن چون میتونن بین تغییراتی که در حافظه اتفاق افتاده و اجرای واقعی ارتباط ایجاد کنن مثلا ممکنه یک قسمت از حافظه تغییر کرده باشه ولی هنوز مشخص نباشه این تغییر واقعاً استفاده شده یا نه اما اگر بعدش یک مسیر اجرایی جدید ظاهر بشه و یک Thread از اون مسیر شروع به کار کنه قضیه برای تحلیلگر معنی بیشتری پیدا میکنه برای تحلیل رفتار یک Process باید این موارد رو کنار هم دید
Process Tree Cross Process Access Memory Regions Memory Permission Changes Thread Start Address Call Stack Timeline
هیچ کدوم از اینا به تنهایی کافی نیست ولی وقتی همه کنار هم قرار میگیرن Detection قوی تر میشه

Data Flow Analysis Following the actual data path So far, we have been mostly focused on what instructions the program executes But from here on, we ask a more important question: Where does the data come from and where does it end up? This is exactly what we call Data Flow Analysis Suppose we have this code: C++
int calculate(int a, int b) {     int x = a + b;     int y = x * 2;     return y; }
If we just look at the order of the instructions, we would say: First the addition is done Then the multiplication is done Then the result is returned But in Data Flow Analysis, we look at it like this:
a ─┐ ├── ADD ──> x ──> MUL ──> y ──> Return b ─┘
That is, we follow the path of the data itself Now suppose there are these instructions in the middle of the program: C++
int temp = 500; temp ^= 123; temp += 20;
If temp never affects y or the output of the function, it will not be in the main data path. Here Data Flow Analysis helps a lot to separate Junk Code from the real logic. When we see a complex function in IDA or Ghidra, we do not need to memorize everything from beginning to end. Choose an important value and follow it. For example, if the input of the function is entered into a register, see: Where is it copied? Where is it changed? Is it stored in memory or not? Is it passed to another function or not? And what does it affect in the end? For example, in assembly, you might see something like this: Asm
mov eax, edi add eax, esi imul eax, 2
If we assume that EDI and ESI are inputs, the data path looks like this:
EDI ─┐ ├──> EAX ──> ADD ──> IMUL ESI ─┘
The important thing is that the name of the register does not mean the data path. A value from RAX may be entered into RBX, then stored in memory, and then again into RCX. Back If you just look at the register names, you'll get lost very quickly😅 You have to follow the value itself Sometimes we also call this Data Provenance That is, find out where a value comes from For example, a value might come from:
User Input ↓ Buffer ↓ Function A ↓ Function B ↓ Comparison
 This approach is especially important when analyzing obfuscated programs Because obfuscation may clutter the program's execution path, but the data still has to come from somewhere and go somewhere Exercise: Examine this function: C
int process(int a) { int x = a; x = x ^ 0x55; x = x + 10; int junk = 100; junk *= 5; return x; }
Draw a path from a to the output on paper Then determine whether junk enters the main data path The goal of this exercise is to gradually see not just the instructions when you open a function See the data moving between registers, memory, and functions @reverseengine

Data Flow Analysis دنبال کردن مسیر واقعی داده تا اینجا بیشتر تمرکزمون روی این بود که برنامه چه دستور هایی اجرا میکنه ولی از اینجا به بعد یک سؤال مهمتر میپرسیم: داده از کجا میاد و آخرش کجا میره؟ این دقیقا همون چیزیه که بهش Data Flow Analysis میگیم فرض کنید این کد رو داریم: C++
int calculate(int a, int b)
{
    int x = a + b;
    int y = x * 2;
    return y;
}
اگر فقط به ترتیب دستورها نگاه کنیم میگیم: اول جمع انجام میشه بعد ضرب انجام میشه بعد نتیجه برمیگرده ولی در Data Flow Analysis این شکلی نگاه میکنیم:
a ─┐ ├── ADD ──> x ──> MUL ──> y ──> Return b ─┘
یعنی مسیر خود داده رو دنبال میکنیم حالا فرض کنید وسط برنامه این دستورها هم وجود داشته باشن: C++
int temp = 500; temp ^= 123; temp += 20;
اگر temp هیچوقت روی y یا خروجی تابع تأثیر نذاره توی مسیر اصلی داده قرار نمیگیره اینجا Data Flow Analysis خیلی کمک میکنه Junk Code رو از منطق واقعی جدا کنیم وقتی داخل IDA یا Ghidra یک تابع پیچیده میبینیم لازم نیست از اول تا آخر همه چیز رو حفظ کنیم یک مقدار مهم رو انتخاب کنید و دنبالش کنید مثلا اگر ورودی تابع داخل یک رجیستر وارد شده ببینید: کجا کپی میشه کجا تغییر میکنه داخل حافظه ذخیره میشه یا نه به تابع دیگه‌ ای می‌ره یا نه و در اخر روی چه چیزی تأثیر میذاره مثلا در اسمبلی ممکنه چنین چیزی ببینید: Asm
mov eax, edi add eax, esi imul eax, 2
اگر فرض کنیم EDI و ESI ورودی هستن مسیر داده این شکلیه:
EDI ─┐ ├──> EAX ──> ADD ──> IMUL ESI ─┘
نکته مهم اینه که اسم رجیستر به معنی مسیر داده نیست ممکنه یک مقدار از RAX وارد RBX بشه بعد داخل حافظه ذخیره بشه و دوباره داخل RCX برگرده اگر فقط اسم رجیسترها رو نگاه کنید خیلی زود گم میشید😅 باید خود مقدار رو دنبال کنید به این مفهوم بعصی وقتا Data Provenance هم میگیم یعنی بفهمید منشا یک مقدار کجاست مثلا یک مقدار ممکنه از اینجا اومده باشه:
User Input ↓ Buffer ↓ Function A ↓ Function B ↓ Comparison
این مدل نگاه مخصوصا موقع تحلیل برنامه‌ های Obfuscate شده خیلی مهمه چون Obfuscation ممکنه مسیر اجرای برنامه رو شلوغ کنه ولی داده هنوز باید از یک جایی وارد بشه و به یک جایی برسه تمرین: این تابع رو بررسی کنید: C
int process(int a) { int x = a; x = x ^ 0x55; x = x + 10; int junk = 100; junk *= 5; return x; }
روی کاغذ مسیر a تا خروجی رو بکشید بعد مشخص کنید junk وارد مسیر اصلی داده میشه یا نه هدف این تمرین اینه که کم‌ کم وقتی یک تابع رو باز میکنید فقط دستورها رو نبینید داده رو ببینید که داره بین رجیسترها حافظه و توابع حرکت میکنه @reverseengine

An Object usually first points to a table, and that table contains the addresses of functions: Object │ ▼ vptr │ ▼ Vtable │ ├── Function A ├── Function B └── Function C So Vtable provides a more structured level for managing Virtual functions Vtable Is it really an official C++ standard? An important note: The concept of Vtable itself is not precisely defined in the C++ standard Vtable is actually a common way for compilers to implement Polymorphism That is, the implementation details may differ between: GCC Clang MSVC But the general idea of ​​using information to find the right function at runtime is very common in practice That is why when reversing, we should always pay attention to the ABI and the compiler with which the program was built Vtable Usually a table containing the addresses of Virtual Functions of a class in C++ Objects that use Virtual Functions often have an internal Pointer such as vptr that relates them to the Vtable. When executing a Virtual Function, the program uses this structure to find the appropriate function Understanding Vtable is important both in reversing C++ programs and in understanding how Objects are organized in memory @reverseengine