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
+124 hours
+1087 days
+11130 days
Posts Archive
Scheduling Metrics از کجا بفهمیم Scheduler خوبه؟ توی قسمت قبل فهمیدیم Scheduler مسئول اینه که مشخص کنه کدوم Thread یا Process نوبتش بشه و روی CPU اجرا بشه اما یه سؤال مهم داریم: از کجا بفهمیم یک الگوریتم Scheduling واقعا خوبه؟ فرض کنید سه تا Process داریم: A B C Scheduler میتونه این‌ها رو با ترتیب‌های مختلف اجرا کنه پس باید یه سری معیار داشته باشیم تا بتونیم عملکرد الگوریتم‌های مختلف رو با هم مقایسه کنیم اینجاست که میرسیم به Scheduling Metrics Turnaround Time اولین مفهوم: Turnaround Time به زبان ساده یعنی: از لحظه‌ای که یک Job وارد سیستم میشه تا لحظه‌ای که کارش کاملا تموم میشه چقدر زمان گذشته؟ فرمولش: Turnaround Time = Completion Time - Arrival Time مثلا: Arrival Time = 0 Completion Time = 10 پس: Turnaround Time = 10 - 0 = 10 یعنی این Process از لحظه ورود تا پایان 10 واحد زمانی داخل سیستم بوده Waiting Time حالا یه سؤال دیگه: Process چه مقدار از زمان رو فقط منتظر CPU بوده؟ اینجا میرسیم به: Waiting Time فرض کنید وضعیت یک Process این شکلی باشه: Arrival ↓ Waiting ↓ Running ↓ Waiting ↓ Running ↓ Finished هر مدتی که Process توی Ready Queue منتظر CPU باشه، جزو Waiting Time حساب میشه پس خیلی ساده: Waiting Time = مدت زمانی که Process منتظر CPU بوده Response Time این یکی مخصوصا برای برنامه‌های تعاملی خیلی مهمه Response Time از وقتی Job وارد سیستم میشه تا اولین باری که CPU بهش میرسه چقدر طول کشیده؟ مثلا: Arrival │ │ 5ms ▼ First Run پس: Response Time = 5ms حالا تفاوتش با Turnaround Time رو خوب دقت کنید Response Time فقط تا اولین اجرا: Arrival → First Run Turnaround Time تا اخر کامل کار: Arrival → Completion پس ممکنه یک Process خیلی سریع اولین بار اجرا بشه ولی برای تمام شدن کارش مدت زیادی زمان لازم داشته باشه یک مثال کامل فرض کنیم سه تا Job داریم: Job A Arrival = 0 Runtime = 5 Job B Arrival = 0 Runtime = 3 Job C Arrival = 0 Runtime = 2 فرض کنیم Scheduler تصمیم گرفته این ترتیب رو اجرا کنه: A → B → C پس Timeline میشه: 0 5 8 10 |--------|--------|--------| A B C حالا یکی‌یکی حساب کنیم Job A A از زمان: 0 شروع شده و در: 5 تموم شده پس: Turnaround = 5 - 0 = 5 چون A بلافاصله اجرا شده: Response = 0 Job B B در زمان: 5 شروع شده و در: 8 تموم شده. پس: Turnaround = 8 - 0 = 8 و چون B از زمان 0 تا 5 منتظر CPU بوده: Response = 5 Job C C در زمان: 8 شروع شده و در: 10 تموم شده پس: Turnaround = 10 - 0 = 10 و: Response = 8 چون C تا زمان 8 منتظر مونده تا بالاخره CPU بهش برسه میانگین هم مهمه معمولا قرار نیست فقط یک Process رو بررسی کنیم میخوایم ببینیم الگوریتم ما در مجموع چه عملکردی داشته پس میانگین میگیریم مثلا: Turnaround: A = 5 B = 8 C = 10 میانگین: (5 + 8 + 10) / 3 = 7.67 پس: Average Turnaround Time = 7.67 همین کار رو میتونیم برای معیارهای دیگه مثل Response Time هم انجام بدیم مشکل یک Scheduler ساده حالا یه حالت جالب‌تر رو ببین. فرض کنید داریم: A = 100ms B = 2ms C = 2ms و Scheduler هم بگه: A → B → C Timeline میشه: 0 100 102 104 |------------------------------|------|------| A B C یعنی B و C باید منتظر بمونن تا A کارش تموم بشه در نتیجه: Response Time B = 100ms Response Time C = 102ms در حالی که خود B و C فقط 2ms کار دارن یعنی یه Job سنگین میتونه باعث بشه Jobهای کوچیک مدت زیادی منتظر بمونن اینجا Shortest Job First جالب میشه اگر از قبل بدونیم مدت اجرای Job ها تقریبا چقدره می‌تونیم اول Job های کوتاه‌تر رو اجرا کنیم مثلا: A = 100ms B = 2ms C = 2ms پس به‌ جای: A → B → C میتونیم داشته باشیم: B → C → A Timeline: 0 2 4 104 |----|----|--------------------------------| B C A حالا B و C خیلی سریع اولین پاسخ خودشون رو میگیرن این ایده پایه الگوریتم: Shortest Job First (SJF) هست ولی اینجا یک مشکل داریم سیستم‌عامل معمولا از قبل دقیقا نمیدونه: این Process قراره چند میلی‌ثانیه CPU مصرف کنه؟ مثلا یک برنامه ممکنه مدام بین CPU و I/O جا به‌ جا بشه:

Introduction to Exploit Development https://samsclass.info/127/ED_2020.shtml

Mastering System Programming with C Files, Processes, and IPC

jailbrack.zip0.17 KB

Repost from N/a
My New Blog Post Evading Sysmon Dns Monitoring In 2026 | binary-win DNSevade : https://github.com/binary-win/DNSevade.git Sys
My New Blog Post Evading Sysmon Dns Monitoring In 2026 | binary-win DNSevade : https://github.com/binary-win/DNSevade.git
Sysmon gets its DNS telemetry from the Microsoft-Windows-DNS-Client ETW provider. Let's hide in plain sight.
#sysmon #bypass Your Notes are HERE

│ ▼ Context Switch │ ▼ Thread B │ ├── Instruction X ├── Instruction Y │ ▼ Context Switch │ ▼ Thread A │ └── Instruction 3 So, the execution timing of a program doesn't depend solely on its own code. The Scheduler and the overall system state also influence the scheduling of thread execution. Process or Thread There is a crucial point here: In modern systems, the scheduler typically schedules the entity that actually runs on the CPU at the *thread* level, rather than always treating the process as an indivisible unit. For example: Process A ├── Thread 1 ├── Thread 2 └── Thread 3 Process B ├── Thread 1 └── Thread 2 The Scheduler can select ready threads for execution; this becomes even more significant when we delve deeper into the topic of threads later. A point regarding CPU Cores: If we have a single core, only one thread can actually execute on that core at any given moment; however, through rapid switching between threads, it appears as though multiple programs are running simultaneously. For example: Time │ ├── Thread A ├── Thread B ├── Thread C ├── Thread A └── Thread B If we have multiple cores, multiple threads can actually execute simultaneously on different cores. This is where the distinction between Concurrency and Parallelism becomes important. CPU Scheduling refers to the kernel deciding which ready thread or process should be allocated the CPU. Simply put: Ready │ ▼ Scheduler │ ▼ Running │ ├── Time slice expires │ ▼ Preemption │ ▼ Context Switch │ ▼ Another thread or process Key concepts in this section: Scheduler The component that selects the executable unit. Ready Queue The conceptual location for units ready to execute. Preemption Taking the CPU away from the currently executing unit. Timer Interrupt One of the mechanisms... ...that allow the kernel to check the status and reschedule Context Switch Swapping the execution state of the current unit and preparing the next one Thread A unit that, in modern systems, is typically scheduled directly for execution @reverseengine

CPU Scheduling Kernel Decides what to run So far we have understood that multiple processes can exist on the system at the same time But we have an important question If we only have one CPU Core and 10p to Processes are ready to run Which one should get the CPU This is where CPU Scheduling comes into play Scheduler There is a part in the Kernel whose job is to decide which Process or Thread to choose to run We call this part Scheduler Very simply Ready Processes │ ├── Process A ├── Process B ├── Process C └── Process D │ ▼ Scheduler │ ▼ CPU Scheduler Basically, it is finding the answer to this question What should be run on the CPU now Ready Queue Processes that are ready to run but do not currently have the CPU are conceptually considered in something called the Ready Queue For example: Ready Queue ┌─ │ ▼ Scheduler │ ▼ CPU Of course, in a real operating system, the story is much more complicated than a simple queue Scheduler must consider different things and its internal structures also vary depending on the operating system and scheduling policy Can a Process take the CPU forever Suppose we have a program while (1) { // do something } This program will never finish If the operating system had no control over the CPU, this Process could occupy the CPU for a very long time and other programs would not have a chance to run That is why modern operating systems use Preemptive Scheduling Preemption means that the operating system can stop the current unit from executing and give the CPU to another unit Give For example: Process A │ │ Running ▼ Scheduler decides │ ▼ Context Switch │ ▼ Process B │ │ Running ▼ Here Process A is not necessarily finished It just lost the CPU for now It can continue its execution from where it stopped later Timer Interrupt comes in here One of the things that helps the operating system regain control of the CPU is Timer The operating system sets a hardware Timer After a certain period of time, the Timer can create an Interrupt Simply CPU │ │ Running Process A │ ▼ Timer │ │ Time is up ▼ Interrupt │ ▼ Kernel │ ▼ Scheduler │ ▼ Process B So a Process can't simply say The CPU is mine and I'm not giving it back😁 The Kernel can finally get involved What is the role of Context Switch here Yes Remember we said earlier that Context Switch means saving the state of the current unit and preparing the state of the next unit for execution Now put these together Timer Interrupt ↓ Kernel ↓ Scheduler ↓ Selecting a new unit ↓ Context Switch ↓ A new thread or process running This chain is one of the important foundations for understanding Scheduling On what basis does the Scheduler choose Which unit to run can depend on various policies and algorithms Let's look at some classic examples FIFO or FCFS Any process that enters earlier will run earlier A → B → C → D It's simple But it doesn't always lead to the best performance Shortest Job First If we know the execution time of the tasks, we can execute the shorter task first For example: A = 10ms B = 2ms C = 7ms B → C → A It's interesting in theory But the operating system usually doesn't know in advance exactly how much CPU each process or thread is going to consume Do Round Robin Here each thread gets CPU for a certain amount of time For example A → B → C → A → B → C Assume each has 10ms of time A 10ms B 10ms C 10ms A 10ms B 10ms This model may be more suitable for interactive systems Why is Scheduling important for Reverse Engineering? When you are debugging a program, you may think that the execution of the program should be completely linear For example Instruction 1 Instruction 2 Instruction 3 Instruction 4 But the operating system can take the CPU from that thread in the middle of its execution and execute another thread For example: Thread A │ ├── Instruction 1 ├── Instruction 2

│ ▼ Context Switch │ ▼ Thread B │ ├── Instruction X ├── Instruction Y │ ▼ Context Switch │ ▼ Thread A │ └── Instruction 3 پس ترتیب زمانی اجرای یک برنامه فقط به کد خودش بستگی نداره Scheduler و وضعیت کل سیستم هم روی زمان بندی اجرای Threadها تاثیر میذارن Process یا Thread اینجا یه نکته خیلی مهم داریم در سیستم های مدرن معمولا Scheduler چیزی رو که واقعا باید روی CPU اجرا بشه در سطح Thread زمان بندی میکنه نه اینکه Process رو همیشه به عنوان یک واحد غیرقابل تقسیم در نظر بگیره مثلا: Process A ├── Thread 1 ├── Thread 2 └── Thread 3 Process B ├── Thread 1 └── Thread 2 Scheduler میتونه Thread های آماده رو برای اجرا انتخاب کنه یعنی وقتی بعداً وارد مبحث Thread بشیم این موضوع خیلی مهم تر میشه یک نکته درباره CPU Core اگه یک Core داشته باشیم در هر لحظه فقط یک Thread میتونه واقعا روی اون Core اجرا بشه ولی با جابه جایی سریع بین Threadها اینطور به نظر میرسه که چند برنامه همزمان دارن اجرا میشن مثلا: Time │ ├── Thread A ├── Thread B ├── Thread C ├── Thread A └── Thread B اگه چند Core داشته باشیم چند Thread میتونن واقعا به صورت همزمان روی Coreهای مختلف اجرا بشن اینجاست که تفاوت بین Concurrency و Parallelism هم مهم میشه CPU Scheduling یعنی Kernel تصمیم میگیره کدوم Thread یا Process آماده باید CPU رو دریافت کنه به صورت ساده Ready │ ▼ Scheduler │ ▼ Running │ ├── Time Slice تموم میشه │ ▼ Preemption │ ▼ Context Switch │ ▼ Thread یا Process دیگه مفاهیم مهم این قسمت Scheduler انتخاب کننده واحد قابل اجرا Ready Queue محل مفهومی واحدهای آماده اجرا Preemption گرفتن CPU از واحدی که در حال اجراست Timer Interrupt یکی از مکانیزم هایی که میتونه باعث بشه Kernel فرصت بررسی وضعیت و زمان بندی دوباره رو پیدا کنه Context Switch جابه جایی وضعیت اجرای واحد فعلی و آماده کردن واحد بعدی Thread واحدی که در سیستم های مدرن معمولا مستقیما برای اجرا زمان بندی میشه @reverseengine

CPU Scheduling Kernel تصمیم میگیره چی اجرا بشه تا اینجا فهمیدیم چندین Process میتونن همزمان روی سیستم وجود داشته باشن ولی یه سوال مهم داریم اگه فقط یه CPU Core داشته باشیم و 10p تا Process آماده اجرا باشن کدومشون باید CPU رو بگیره اینجاست که CPU Scheduling وارد ماجرا میشه Scheduler توی Kernel بخشی وجود داره که وظیفه اش اینه تصمیم بگیره کدوم Process یا Thread برای اجرا انتخاب بشه به این بخش میگیم Scheduler خیلی ساده Ready Processes │ ├── Process A ├── Process B ├── Process C └── Process D │ ▼ Scheduler │ ▼ CPU Scheduler در اصل داره جواب این سوال رو پیدا میکنه الان چه چیزی باید روی CPU اجرا بشه Ready Queue Process هایی که آماده اجرا هستن ولی فعلا CPU در اختیارشون نیست رو به صورت مفهومی داخل چیزی به اسم Ready Queue در نظر میگیریم مثلا: Ready Queue ┌─────────┐ │ Process A│ ├─────────┤ │ Process B│ ├─────────┤ │ Process C│ ├─────────┤ │ Process D│ └─────────┘ │ ▼ Scheduler │ ▼ CPU البته توی سیستم عامل واقعی داستان خیلی پیچیده تر از یه صف ساده است Scheduler باید چیزهای مختلفی رو در نظر بگیره و ساختارهای داخلیش هم بسته به سیستم عامل و سیاست زمان بندی فرق میکنه آیا یک Process میتونه برای همیشه CPU رو بگیره فرض کنید یه برنامه داشته باشیم while (1) { // do something } این برنامه هیچ وقت تموم نمیشه اگه سیستم عامل هیچ کنترلی روی CPU نداشت ممکن بود این Process تا مدت خیلی زیادی CPU رو اشغال کنه و برنامه های دیگه فرصت اجرا پیدا نکنن برای همین سیستم عامل های مدرن از Preemptive Scheduling استفاده میکنن Preemption یعنی سیستم عامل بتونه اجرای واحد فعلی رو متوقف کنه و CPU رو به یه واحد دیگه بده مثلا: Process A │ │ Running ▼ Scheduler تصمیم میگیرد │ ▼ Context Switch │ ▼ Process B │ │ Running ▼ اینجا Process A لزوما تموم نشده فقط فعلا CPU رو از دست داده بعدا میتونه دوباره ادامه اجراش رو از همون جایی که متوقف شده دنبال کنه Timer Interrupt اینجا وارد میشه یکی از چیزهایی که به سیستم عامل کمک میکنه کنترل CPU رو پس بگیره Timer هست سیستم عامل یک Timer سخت افزاری رو تنظیم میکنه بعد از گذشت یک بازه مشخص Timer میتونه یک Interrupt ایجاد کنه به صورت ساده CPU │ │ اجرای Process A │ ▼ Timer │ │ زمان تموم شد ▼ Interrupt │ ▼ Kernel │ ▼ Scheduler │ ▼ Process B پس یه Process نمیتونه به سادگی بگه CPU مال منه و پسش نمیدم😁 Kernel بالاخره میتونه وارد ماجرا بشه Context Switch اینجا چه نقشی داره یادتون هست قبلا گفتیم Context Switch یعنی وضعیت اجرای واحد فعلی ذخیره بشه و وضعیت واحد بعدی برای اجرا آماده بشه حالا اینا رو کنار هم بذارید Timer Interrupt ↓ Kernel ↓ Scheduler ↓ انتخاب واحد جدید ↓ Context Switch ↓ Thread یا Process Running جدید این زنجیره یکی از پایه های مهم برای فهمیدن Scheduling هست Scheduler بر چه اساسی انتخاب میکنه اینکه کدوم واحد اجرا بشه میتونه به سیاست ها و الگوریتم های مختلفی بستگی داشته باشه چند نمونه کلاسیک رو ببینیم FIFO یا FCFS هر Process که زودتر وارد شده زودتر اجرا میشه A → B → C → D ساده است ولی همیشه باعث بهترین عملکرد نمیشه Shortest Job First اگه مدت اجرای کارها رو بدونیم میتونیم کار کوتاه تر رو زودتر اجرا کنیم مثلا: A = 10ms B = 2ms C = 7ms B → C → A از نظر تئوری جالبه ولی سیستم عامل معمولا از قبل دقیقا نمیدونه هر Process یا Thread قراره چقدر CPU مصرف کنه Round Robin اینجا هر واحد برای یه مقدار زمان مشخص CPU میگیره مثلا A → B → C → A → B → C فرض کنید هرکدوم 10ms زمان داشته باشن A 10ms B 10ms C 10ms A 10ms B 10ms این مدل برای سیستم های تعاملی میتونه مناسب تر باشه چرا Scheduling برای Reverse Engineering مهمه؟ وقتی دارید یه برنامه رو Debug میکنید ممکنه فکر کنید اجرای برنامه باید کاملا خطی باشه مثلا Instruction 1 Instruction 2 Instruction 3 Instruction 4 ولی سیستم عامل میتونه وسط اجرای اون Thread اجرای CPU رو ازش بگیره و واحد دیگه ای رو اجرا کنه مثلا: Thread A │ ├── Instruction 1 ├── Instruction 2

Taint Analysis On Memory and Pointers So far we have done Taint Analysis on simple variables But in a real program the story gets a bit more complicated because data is not just moved between a few variables It may be entered into Memory It may be placed into a Buffer It may be moved through a Pointer And then several different functions may use the same Memory This is where Taint Analysis gets a bit more difficult Suppose we have this C char buffer[32]; buffer[0] = input; Here the data that came from input is entered into Memory So we can say input ↓ buffer[0] Now suppose we have a Pointer to this Buffer C char *p = buffer; And then char x = p[0]; Here the data is read from Memory again So our path becomes input ↓ buffer[0] ↓ p[0] ↓ x So Taint doesn't just move between Register and Variable Its path may go through several different stages Register ↓ Memory ↓ Pointer ↓ Memory ↓ Register This is very important in Reverse Engineering Because when you look at the Assembly you may not see anything called input or buffer at all For example, you may see something like this mov [rbp-40h], eax lea rcx, [rbp-40h] movzx eax, byte ptr [rcx] Here you have to find the relationship between these instructions yourself That is EAX ↓ [rbp-40h] ↓ RCX ↓ [RCX] ↓ EAX That is, the data is entered into Memory once Then a Pointer is created to that part And the data is read again through the same Pointer Pointer Itself Is it Tainted or the data it points to Here we have to be very careful Suppose C char *p = buffer; char x = *p; Buffer may contain Tainted data But Pointer itself, i.e. p, is not necessarily Tainted For example: Address = Clean Data at Address = Tainted These two are different So when you analyze Memory, always ask yourself two separate questions Where did this Address come from Where did the content of this Address come from For example, we may have Pointer 0x500000 This Address itself may be completely normal and reliable But the content of Memory[0x500000] may have come from user input So Address can be Clean But Data inside that Address is Tainted A simpler example: C int process(char *input) { char buffer[32]; buffer[0] = input[0]; int x = buffer[0] + 10; return x; } If we Tainted the input the data path will be input[0] ↓ buffer[0] ↓ x ↓ return So even if the data is in the Stack for a while Taint itself will not go away as long as the new data is still attached to that value Now let's see the same in Assembly We may see something like this movzx eax, byte ptr [rcx] mov byte ptr [rbp-20h], al movzx eax, byte ptr [rbp-20h] add eax, 10 Here we have to create the path ourselves from the instructions [RCX] ↓ AL ↓ [rbp-20h] ↓ EAX ↓ ADD 10 If [RCX] contains Tainted data then this path should also be followed in our analysis Why Pointer Analysis is important Because in large programs, data may be passed directly from one Variable to the next Variable No The path may be like this Input ↓ Buffer ↓ Pointer ↓ Function A ↓ Memory ↓ Pointer ↓ Function B ↓ Comparison If you only look at the registers, you may miss the data connection in the middle of the path That is why it is very important in Reverse Engineering Data Flow and Memory Analysis are very interrelated An important point Not every Memory Access means transferring Taint For example: C++ int x = 100; int y = x + 20; Here no data came from Input So x = Clean y = Clean But if we have C++ int x = input; int y = x + 20; Here we have x = Tainted y = Tainted So we always need to trace the origin of the data Just because a value is entered into Memory is not enough We need to see where that value came from and where it was used Exercise: Examine this code C int process(char *input) { char buffer[32]; buffer[0] = input[0]; int x = buffer[0] ^ 0x55; int clean = 500; clean += 20; int result = x + 10; return result; } Assume input[0] is the starting point of Taint Find the path input[0] ↓ ? ↓ ? ↓ ? ↓ return Then specify why clean should not enter this path @reverseengine

Taint Analysis روی Memory و Pointer ها تا اینجا Taint Analysis رو روی متغیرهای ساده انجام دادیم ولی توی برنامه واقعی داستان یکم پیچیده تر میشه چون داده فقط بین چند تا متغیر جابه جا نمیشه ممکنه وارد Memory بشه داخل یه Buffer قرار بگیره از طریق Pointer جابه جا بشه و بعد چند تا تابع مختلف از همون Memory استفاده کنن اینجاست که Taint Analysis یکم سخت تر میشه فرض کنید اینو داریم C char buffer[32]; buffer[0] = input; اینجا داده ای که از input اومده وارد Memory شده پس میتونیم بگیم input ↓ buffer[0] حالا فرض کنید یه Pointer به این Buffer داریم C char *p = buffer; بعد هم char x = p[0]; اینجا داده دوباره از Memory خونده شده پس مسیرمون میشه input ↓ buffer[0] ↓ p[0] ↓ x پس Taint فقط بین Register و Variable حرکت نمیکنه ممکنه مسیرش از چند مرحله مختلف رد بشه Register ↓ Memory ↓ Pointer ↓ Memory ↓ Register این موضوع توی Reverse Engineering خیلی مهمه چون وقتی Assembly رو نگاه میکنید ممکنه اصلا چیزی به اسم input یا buffer نبینید مثلا ممکنه همچین چیزی ببینید mov [rbp-40h], eax lea rcx, [rbp-40h] movzx eax, byte ptr [rcx] اینجا باید خودتون رابطه بین این دستورها رو پیدا کنید یعنی EAX ↓ [rbp-40h] ↓ RCX ↓ [RCX] ↓ EAX یعنی داده یه بار وارد Memory شده بعد یه Pointer به اون قسمت ساخته شده و دوباره داده از طریق همون Pointer خونده شده Pointer خودش Tainted هست یا داده ای که بهش اشاره میکنه اینجا باید خیلی دقت کنیم فرض کنید C char *p = buffer; char x = *p; ممکنه Buffer حاوی داده Tainted باشه ولی خود Pointer یعنی p لزوما Tainted نیست مثلا: Address = Clean Data at Address = Tainted این دوتا با هم فرق دارن پس وقتی Memory رو تحلیل میکنید همیشه دو تا سوال جدا از خودتون بپرسید این Address از کجا اومده محتوای این Address از کجا اومده مثلا ممکنه داشته باشیم Pointer 0x500000 خود این Address ممکنه کاملا عادی و قابل اعتماد باشه ولی محتوای Memory[0x500000] ممکنه از ورودی کاربر اومده باشه پس Address میتونه Clean باشه ولی Data داخل اون Address Tainted باشه یه مثال ساده تر: C int process(char *input) { char buffer[32]; buffer[0] = input[0]; int x = buffer[0] + 10; return x; } اگر input رو Tainted کنیم مسیر داده میشه input[0] ↓ buffer[0] ↓ x ↓ return پس حتی اگر داده برای یه مدت داخل Stack قرار گرفته باشه Taint خودش از بین نمیره تا وقتی که داده جدید همچنان به اون مقدار وابسته باشه حالا همین رو توی Assembly ببینیم ممکنه چیزی شبیه این ببینیم movzx eax, byte ptr [rcx] mov byte ptr [rbp-20h], al movzx eax, byte ptr [rbp-20h] add eax, 10 اینجا باید مسیر رو خودمون از روی دستورها بسازیم [RCX] ↓ AL ↓ [rbp-20h] ↓ EAX ↓ ADD 10 اگر [RCX] حاوی داده Tainted باشه پس این مسیر هم باید توی تحلیل ما دنبال بشه چرا Pointer Analysis مهم میشه چون توی برنامه های بزرگ ممکنه داده مستقیم از یه Variable به Variable بعدی نره ممکنه مسیر این شکلی باشه Input ↓ Buffer ↓ Pointer ↓ Function A ↓ Memory ↓ Pointer ↓ Function B ↓ Comparison اگر فقط Registerها رو نگاه کنی ممکنه وسط مسیر ارتباط داده رو گم کنی برای همین توی Reverse Engineering خیلی مهم Data Flow و Memory Analysis خیلی به هم وابسته هستن یه نکته مهم هر Memory Access به معنی انتقال Taint نیست مثلا: C++ int x = 100; int y = x + 20; اینجا هیچ داده ای از Input نیومده پس x = Clean y = Clean ولی اگر داشته باشیم C++ int x = input; int y = x + 20; اینجا داریم x = Tainted y = Tainted پس همیشه باید منشا داده رو دنبال کنیم فقط اینکه یه مقدار وارد Memory شده به تنهایی کافی نیست باید ببینیم اون مقدار از کجا اومده و بعد کجا استفاده شده تمرین: این کد رو بررسی کنید C int process(char *input) { char buffer[32]; buffer[0] = input[0]; int x = buffer[0] ^ 0x55; int clean = 500; clean += 20; int result = x + 10; return result; } فرض کنید input[0] نقطه شروع Taint باشه مسیر رو پیدا کنید input[0] ↓ ? ↓ ? ↓ ? ↓ return بعد مشخص کنید clean چرا نباید وارد این مسیر بشه @reverseengine

Part 32 Buffer Overflow
Program Slicing Focusing only on the code relevant to the analysis. It involves tracing a piece of data from its entry point to where it is used. Imagine a program with thousands of instructions; we don't need to read through every single line. Program Slicing helps isolate only those parts of the program that affect a specific value. Program Slicing Suppose we have a value: user_input and we want to know which parts of the program affect this value. For example: Input ↓ Function A ↓ Function B ↓ Function C ↓ Crash Instead of examining the entire program, we examine only this specific path. Two main types: Forward Slice We start with a value and move forward. The key question: Where is this value used? For example: input ↓ buffer ↓ memcpy ↓ target Backward Slice We start from a critical point and trace back. For example, we have a crash: Crash ↑ memcpy ↑ buffer ↑ input The key question: Where did this value come from? A simple example: Suppose the code is: C int process(int input) { int a = input; int b = a + 10; int c = b * 2; int unrelated = 500; return c; } If our goal is the value of c, we don't need to examine unrelated, because it has no effect on c. The slice consists roughly of this part: input ↓ a ↓ b ↓ c Why is it important in Reverse Engineering? Suppose you have a crash, and the stack trace points to a large function— for example: process_packet() This function might contain hundreds of lines of code, yet the crash depends on only one specific value— for instance: length Now, instead of examining the entire function, we trace the path of length: length ↓ check ↓ calculation ↓ buffer operation ↓ Crash Data dependency For example: C int size = input_length; int copy_size = size + 8; memcpy(buffer, input, copy_size); Here, copy_size depends on size, and size depends on input_length; so, the data path is: input_length ↓ size ↓ copy_size ↓ memcpy This is precisely what we aim to identify during slicing. Control dependency is also important; sometimes a value isn't passed directly but influences a program decision— for example: C++ if (size > 100) { process_large_input(); } Here, the size determines which execution path is taken; so, in addition to Data Dependency, we also have a Control Dependency. In tools like Ghidra or IDA, once you identify a significant variable or register, you can examine its references and usage paths: For example: Variable ↓ References ↓ Functions ↓ Instructions You then gradually construct the desired slice; in more advanced tools, this process can be automated. Difference between Data Flow and Slicing Simply put: Data Flow describes how data moves. Program Slicing identifies exactly which parts of the program are relevant to analyzing a specific value. So: Data Flow ↓ Data movement path Program Slicing ↓ The portion of the code related to a specific piece of data or result When dealing with a large binary, there is no need to examine the entire program; we can select a key value—such as Input Length Pointer Crash Value and trace its dependency paths. Consequently, large sections of code unrelated to our specific problem are excluded, making the analysis much faster. @reverseengine

قسمت سی و دوم بافر اورفلو
Program Slicing فقط کدهایی که واقعا به درد تحلیل میخورن یعنی یک داده رو از نقطه ورود تا محل استفاده دنبال کردیم حالا فرض کنید برنامه چند هزار دستور داره قرار نیست همه رو خط به خط بخونیم Program Slicing کمک میکنه فقط قسمت‌ هایی از برنامه رو که روی یک مقدار مشخص تأثیر دارن جدا کنیم Program Slicing فرض کنید یک مقدار داریم user_input و میخوایم بدونیم چه قسمت‌هایی از برنامه روی این مقدار تأثیر میذارن مثلا: Input ↓ Function A ↓ Function B ↓ Function C ↓ Crash به جای بررسی کل برنامه فقط همین مسیر رو بررسی میکنیم دو نوع اصلی Forward Slice از یک مقدار شروع میکنیم و جلو میریم سؤال اصلی این مقدار کجا استفاده میشه مثلا: input ↓ buffer ↓ memcpy ↓ target Backward Slice از یک نقطه مهم شروع میکنیم و به عقب برمیگردیم مثلا Crash داریم Crash ↑ memcpy ↑ buffer ↑ input سوال اصلی این مقدار از کجا اومده یک مثال ساده: فرض کنید کد اینه C int process(int input) { int a = input; int b = a + 10; int c = b * 2; int unrelated = 500; return c; } اگر هدف ما مقدار c باشه لازم نیست unrelated رو بررسی کنیم چون روی c تاثیری نداره Slice تقریبا این بخشه input ↓ a ↓ b ↓ c در Reverse Engineering چرا مهمه؟ فرض کنید یک Crash دارید و Stack Trace به یک تابع بزرگ میرسه مثلا: process_packet() این تابع ممکنه صدها خط کد داشته باشه ولی Crash فقط به یک مقدار خاص وابسته باشه مثلا: length حالا به جای بررسی کل تابع مسیر length رو دنبال میکنیم length ↓ check ↓ calculation ↓ buffer operation ↓ Crash وابستگی داده‌ای مثلا: C int size = input_length; int copy_size = size + 8; memcpy(buffer, input, copy_size); اینجا copy_size به size وابسته است و size هم به input_length پس مسیر داده اینه input_length ↓ size ↓ copy_size ↓ memcpy این دقیقا چیزی هست که موقع Slicing می‌خوایم پیدا کنیم وابستگی کنترلی هم مهمه گاهی مقدار مستقیما منتقل نمیشه ولی روی تصمیم برنامه تاثیر میذاره مثلا: C++ if (size > 100) { process_large_input(); } اینجا size تعیین میکنه کدوم مسیر اجرا بشه پس علاوه بر Data Dependency یک Control Dependency هم داریم در Ghidra یا IDA وقتی یک متغیر یا Register مهم پیدا کردید می‌تونی References و مسیرهای استفاده از اون رو بررسی کنید مثلا: Variable ↓ References ↓ Functions ↓ Instructions بعد کم‌ کم Slice موردنظر رو تشکیل میدید در ابزارهای پیشرفته‌تر هم میشه این کار رو به شکل خودکارتر انجام داد تفاوت Data Flow و Slicing خیلی ساده Data Flow میگه داده چطور حرکت میکنه Program Slicing میگه برای تحلیل یک مقدار خاص دقیقا کدوم قسمت‌های برنامه مهم هستن پس Data Flow ↓ مسیر حرکت داده Program Slicing ↓ بخش مرتبط با یک داده یا نتیجه خاص وقتی با یک باینری بزرگ روبه‌رو شدیم لازم نیست کل برنامه رو بررسی کنیم می‌تونیم یک مقدار مهم مثل Input Length Pointer Crash Value رو انتخاب کنیم بعد مسیر وابستگی‌های اون رو پیدا کنیم در نتیجه حجم زیادی از کد که ارتباطی با مسئله ما نداره کنار گذاشته میشه و تحلیل خیلی سریعتر میشه @reverseengine

The Rust Programming Language 2-ed @reverseengine

Mastering Rust Programming @reverseengine