ReverseEngineering – Telegram
ReverseEngineering
1.24K subscribers
40 photos
10 videos
55 files
666 links
Download Telegram
🧠 Devirtualization (شکستن Virtual Machine Protection)

وقتی یه نرم‌افزار با VMProtect / Themida / Custom VM محافظت شده باشه مهندسی معکوس میره سمت Devirtualization
اینجاست که تحلیلگر باید VM رو مهندسی معکوس کنه




1️⃣ شناسایی VM Entry Points

اولین قدم اینه که بفهمیم برنامه از کجا وارد ماشین مجازی میشه
📌 معمولاً یک سری Prologue ثابت وجود داره:

Push کردن یه سری مقادیر روی استک

پرش به یک Interpreter مرکزی





2️⃣ پیدا کردن VM Handlers

هر دستور VM توسط یه Handler اجرا میشه
این Handlerها مثل opcodeهای CPU واقعی هستن
مثال:

VM_OP_01 → ADD

VM_OP_02 → MOV

VM_OP_03 → JMP


اینجا باید همه Handlerها شناسایی بشن معمولا صدها تا هستن




3️⃣ ساخت Mapping بین VM و CPU واقعی

وقتی Handlerها رو شناسایی کردی باید یک جدول معادل‌سازی درست کنی
مثلا:

VM Opcode Real Instruction توضیح

0x12 MOV EAX, EBX کپی مقدار رجیستر
0x34 ADD ECX, 5 جمع ثابت با رجیستر
0x56 JMP addr پرش به آدرس






4️⃣ ابزارها و روش‌ها برای Devirtualization

Dynamic Tracing → اجرای VM و لاگ گرفتن از دستورها

Static Analysis → پیدا کردن Handlerها در دیس‌اسمبلر

Plugins → مثل [IDA Hex-Rays Devirtualizer Plugins]

Custom Tools → نوشتن Decompiler اختصاصی برای بازسازی کد اصلی





5️⃣ چالش‌ها

Obfuscation روی VM Handlerها

استفاده از چند VM مختلف در یک برنامه

درهم‌ریختگی عمدی Junk Code, Fake Opcodes

Self-Modifying Code
داخل خود VM




🧠 Devirtualization (Breaking VM-Based Protections)

When a binary is protected by VMProtect / Themida / custom VM, reverse engineering becomes about understanding and emulating a fake CPU inside the program



1️⃣ Identify VM Entry Points

The first step is to locate where the real code switches into virtualized code execution

🔍 Typical signs:

A prologue pattern: pushing multiple values/contexts on the stack

A jump or call into a central interpreter function

Execution stops making sense in x86 and looks like dispatcher loops





2️⃣ Locate VM Handlers

Each VM opcode is processed by a handler just like x86 instructions are decoded by the CPU

Example mapping idea:

VM_OP_01 → ADD
VM_OP_02 → MOV
VM_OP_03 → JMP


👉 In practice, there may be hundreds of handlers, sometimes with junk code or mixed execution




3️⃣ Build an Opcode → Real Instruction Mapping

Once you identify handlers you need to build a translation table between VM opcodes and real CPU instructions

📖 Example mapping table:

VM Opcode Real Instruction Meaning

0x12 MOV EAX, EBX copy register
0x34 ADD ECX, 5 add immediate constant
0x56 JMP addr unconditional jump


This mapping becomes the foundation of your decompiler/devirtualizer




4️⃣ Methods & Tools for Devirtualization

Dynamic Tracing → Run the VM, log executed opcodes + their effects

Static Analysis → Identify handler functions inside the disassembler

Plugins → Tools like IDA/Ghidra devirtualizer plugins research versions exist

Custom Tools → Write your own noscript/decompiler to rebuild the original logic from VM bytecode





5️⃣ Key Challenges

Obfuscated Handlers → Each handler may have junk code, opaque predicates

Multiple VMs → Some binaries use different VM engines for different functions

Fake Opcodes → Inserted to confuse analysis no-ops, misleading behavior

Self-Modifying Code → The VM engine may rewrite itself in memory at runtime





🔹 Realistic Workflow for an Analyst

1 Find VM entry → identify the dispatcher loop


2 Extract VM bytecode stream


3 Reverse engineer handler functions → document them


4 Build mapping table (opcode → real instruction)


5 Automate devirtualization with a noscript/decompiler


6 Reconstruct original program logic






In practice:

Analysts often combine dynamic tracing + static reversing

Full automation is rare—usually a mix of noscripting and manual work

VMProtect / Themida add anti-debugging and obfuscation layers on top making this even harder
3🦄1
وقتی سورس کد رو ببینی یه switch ساده خیلی راحت و خوناس ولی وقتی کامپایل میشه کامپایلر معمولا برای سرعت یه جدول پرش Jump Table درست میکنه برای کسی که داره مهندسی معکوس میکنه اگه ندونه این چیه کد خیلی عجیب و ترسناک به نظر میاد




شکل سورس کد

switch(x) {
case 1: func1(); break;
case 2: func2(); break;
case 3: func3(); break;
default: func_default(); break;
}




شکل اسمبلی ساده‌سازی شده

cmp eax, 3 ; x <= 3 ?
ja default_case ; اگر بزرگتر بود برو به default

jmp dword ptr [eax*4 + jump_table] ; بپر به آدرس مورد نظر


و جدول پرش jump_table چیزی شبیه اینه:


jump_table:
dd loc_case1
dd loc_case2
dd loc_case3

یعنی:

اگه eax = 0 → پرش به case1

اگه eax = 1 → پرش به case2

اگه eax = 2 → پرش به case3





نکته در مهندسی معکوس

وقتی توی IDA یا Ghidra می‌ری و یه jmp [eax*4 + something] میبینی این تقریبا قطعیه که switch-case هست

IDA معمولا خودش تشخیص میده و نشون میده switch jump ولی اگه نشناسه باید دستی جدول رو بازسازی کنی





4 Default Case

اگه مقدار خارج از بازه باشه مثلا x = 1000 یه پرش شرطی قبلش هست که میفرسته به default case




چرا مهمه توی RE؟

خیلی وقتا توابع مهم برنامه توی همین switch ها صدا زده میشن مثلا پروتکل شبکه یا پارسر فایل اگه ندونی این switch بوده فکر میکنی یه سری پرش عجیب‌غریب توی کده




🔹 تمرین پیشنهادی:

یه کد C با switch بزرگ مثلا 10 case بنویس

با optimization روشن مثلا gcc -O2 کامپایل کن

توی IDA یا Ghidra ببین چطور Jump Table ساخته میشه



🎛 Switch-Case → Jump Table in Assembly

1 High-Level C Code

switch (x) {
case 1: func1(); break;
case 2: func2(); break;
case 3: func3(); break;
default: func_default(); break;
}



At source level this is very readable




2 Compiled Assembly Pattern

A compiler (with optimization enabled) usually transforms this into a jump table for speed:

cmp eax, 3 ; is x <= 3 ?
ja default_case ; if greater, jump to default
jmp dword ptr [eax*4 + jump_table] ; indirect jump


And the jump table looks like:

jump_table:
dd loc_case1
dd loc_case2
dd loc_case3


So execution flow is:

eax = 0 → jump to case1

eax = 1 → jump to case2

eax = 2 → jump to case3


The default_case is handled by the ja check before the jump




3 Why It Looks Scary in RE

When you see something like:

jmp dword ptr [eax*4 + 0x123456]


in IDA/Ghidra/x64dbg, it may look like black magic if you don’t know jump tables.
But in reality:

eax = switch variable

*4 = size of each entry (32-bit addresses)

0x123456 = base address of the jump table


IDA often auto-detects this and shows it as a switch jump
But sometimes it fails → then you need to manually reconstruct the table




4 Why It Matters in RE

Switch tables often dispatch major program logic

Protocol parsers, interpreters, network handlers, file format decoders → almost always implemented as switches

If you don’t recognize it, the function looks like a random mess of indirect jumps





🔹 Practical Exercise

1 Write a C program with a large switch 10+ cases


2 Compile with optimizations e.g. gcc -O2 test.c -o test


3 Open in IDA or Ghidra


4 Look for the jmp [reg*4 + offset] pattern


5 Try to map back cases manually to confirm you can identify jump tables
💋3
Tools you should know :

1 - radare2 :
official website :
https://rada.re/n/
github :
https://github.com/radareorg

2 - Ghidra :
official website :
https://ghidra-sre.org/
github :
https://github.com/NationalSecurityAgency/ghidra

3 - IDA Free / IDA Pro (Hex-Rays) :
https://hex-rays.com

4 - Cutter :
official website :
https://cutter.re/
github :
https://github.com/rizinorg/cutter

5 - iaito :
official website :
https://rada.re/n/iaito.html
github :
https://github.com/radareorg/iaito

6 - reflutter :
https://github.com/ptswarm/reFlutter

7 - Blutter :
https://github.com/worawit/blutter

8 - Apktool :
official website :
https://apktool.org/
github :
https://github.com/iBotPeaches/Apktool

9 - Jadx-gui :
https://github.com/skylot/jadx

10 - dnSpy :
https://github.com/dnSpy/dnSpy

11 - Binary Ninja :
https://binary.ninja/

12 - x64dbg :
https://x64dbg.com/
1
وقتی یه برنامه رو بدون Optimization کامپایل کنید همه‌چیز مرتب و واضح میاد بالا:

توابع جدا هستن شرط‌ها همون‌طور که نوشتید دیده میشن

ولی وقتی کامپایلر رو با O2- یا O3- اجرا کنید همه‌چیز به هم می‌ریزه:

بعضی توابع کلاً ناپدید می‌شن Inlining

شرط‌ها ساده میشن یا حتی حذف میشن

متغیرها میرن روی رجیستر و دیگه ردپای Stack کمتر میبینن





Inlining

تابع کوچیک رو کامپایلر میتونه مستقیم بیاره وسط کد
📖 مثال سورس:

inline int square(int x) {
return x * x;
}

int main() {
int a = square(5);
}


📖 دیس‌اسمبلی (تقریبی):

mov eax, 5
imul eax, eax ; به جای call تابع


اینجا دیگه خبری از call square نیست




Constant Folding

وقتی آرگومان ثابت باشه کامپایلر جواب رو از قبل حساب میکنه

📖 مثال سورس:

int a = 5 * 4;


📖 اسمبلی:

mov eax, 20 ; به جای ضرب





Loop Unrolling

برای سرعت بیشتر حلقه‌ها باز میشن
📖 سورس:

for (i=0; i<4; i++) arr[i] = 0;


📖 اسمبلی (ممکنه بشه):

mov [arr], 0
mov [arr+4], 0
mov [arr+8], 0
mov [arr+12], 0



Register Allocation

متغیرها به جای حافظه مستقیم روی رجیستر میرن. یعنی دیگه [ebp-4] یا [esp+8] نمی‌بینید فقط eax, ecx, edx




اثر روی RE:

وقتی یه تابع ناپدید میشه ممکنه فکر کنید سورسش وجود نداره در واقع inline شده

شرطی که تو سورس نوشتید ممکنه توی اسمبلی نباشه چون Compiler مطمئن بوده نتیجه همیشه مثبته

این تغییرات باعث میشه تحلیل سخت‌تر شه چون کد با چیزی که انتظار دارید فرق داره





🔹 تمرین پیشنهادی:

یه برنامه ساده با چند تابع و یه حلقه بنویسید

همون برنامه رو با -O0 بدون optimization و O2- کامپایل کنید

توی IDA/Ghidra هر دو رو مقایسه کنید و ببینید چه بخش‌هایی حذف یا تغییر داده شدن




Compiler Optimizations and Their Impact on Reverse Engineering

When you compile with no optimization (-O0) everything looks clean:

Functions stay separate

Conditions appear exactly as written

Variables live on the stack ([ebp-4], [esp+8])


But with -O2 or -O3 enabled, the compiler rewrites reality:

Some functions disappear completely inlining

Conditions are simplified or removed

Variables move into registers
no stack traces

Loops get transformed beyond recognition





Function Inlining

Small functions may be expanded directly into the caller

C Source:

inline int square(int x) {
return x * x;
}

int main() {
int a = square(5);
}



Disassembly (approx):

mov eax, 5
imul eax, eax ; inlined, no

call square

➡️ The function square() completely disappears!




Constant Folding

Constant expressions are precomputed

C Source:

int a = 5 * 4

;

Assembly:

mov eax, 20 ; compiler did the ma

th




Loop Unrolling

Loops may be expanded for speed.

C Source
:

for (int i=0; i<4; i++) arr[i] =

0;

Assemb
ly:

mov [arr], 0
mov [arr+4], 0
mov [arr+8], 0
mov [arr+1
2], 0

-

Register Allocation

Instead of stack-based variables, registers are used

At -O0 → you see [ebp-4], [esp+8]

At -O2 → just eax, ecx, edx


This removes the "breadcrumbs" that RE analysts love




Reverse Engineering Impact:

Missing functions → inlined, not gone

Missing conditions → compiler proved the result always true/false

Loop transformations → code looks nothing like source

Registers instead of stack → harder to track variables


Optimizations don’t just speed up execution — they also obfuscate code naturally, which makes RE a lot tougher




🔹 Practical Exercise

Write a small C program with multiple functions and a loop.


Compile twice:

gcc -O0 prog.c -o prog_O0

gcc -O2 prog.c -o prog_O2



Open both in IDA/Ghidra


Compare:

What functions got inlined?

Did loops unroll?

Where did conditions vanish?
2
APK Repacking Tutorial is available now !

Repacking, hooking with frida, using Jadx-gui and apktool, and a general concept for repacking protected apks

Watch the playlist 👇
https://youtube.com/playlist?list=PLMhLyvuk51v5Y8_8Ga9PV16Bnv7jYNfcd
8
Repacking Tutorial.zip
793.5 KB
Needed files and Frida noscripts
8
تا حالا باینری باز کردی که هیچی توش نبوده باشه جز یه مشت بایت الکی؟ بعد وقتی اجراش کردی وسط RAM یهو کد اصلی ظاهر بشه؟ این همون Self-Modifying Code هست


2 توضیح ساده:

بعضی بدافزارها خودشون رو رمزگذاری میکنن

وقتی اجرا میشن یه Loader کوچیک بخش اصلی رو تو حافظه Decrypt میکنه

نتیجه → فایل روی دیسک چیزی نشون نمیده ولی وسط RAM همه‌ چی اتفاق میوفته



3 نمونه عملی (ساده):

start:
lea esi, [encrypted_data]
lea edi, [decrypted_data]
mov ecx, size
decrypt_loop:
xor byte [esi], 0xAA
movsb
loop decrypt_loop
jmp decrypted_data

👉 اینجا Payload اصلی فقط بعد از XOR توی RAM زنده میشه


4 بخش جذاب:

مهندس معکوس باید وسط اجرا با Memory Dump یا Breakpoint هوشمند اون لحظه‌ای که کد اصلی ظاهر میشه رو شکار کنه

ابزارهایی مثل Scylla یا x64dbg Dump اینجا هستن


👀 به طور خلاصه: برای همینه که بدافزار نویسا عاشقشن ولی مهندسان معکوس ازش متنفرن



🌀 Self-Modifying Code (SMC)

Ever opened a binary and found nothing but junk bytes or meaningless data?
But once you run it… suddenly the real code magically appears in RAM?
That’s self-modifying code




1️⃣ What’s going on?

Malware (or a protector/packer) encrypts its main code

On disk → the file looks useless

At runtime → a tiny loader decrypts the hidden payload directly into memory

Result → nothing suspicious in the file, everything happens inside RAM





2️⃣ Simple example (Assembly)

start:
lea esi, [encrypted_data]
lea edi, [decrypted_data]
mov ecx, size
decrypt_loop:
xor byte [esi], 0xAA
movsb
loop decrypt_loop
jmp decrypted_data
👉 Here, the payload only exists after XOR — in memory, never on disk




3️⃣ Why it’s painful for reverse engineers

Static analysis tools (IDA, Ghidra) only see junk

The real logic only appears after runtime decryption

The analyst has to catch the moment the code is unpacked





4️⃣ How REs fight back

Use breakpoints at decryption loops

Pause execution right after unpacking finishes

Dump the real memory image with tools like:

🔹 x64dbg (Dump Memory feature)

🔹 Scylla / ScyllaHide

🔹 OllyDump / Process Hacker






💡 In short:
Self-modifying code = nothing on disk, everything in RAM
That’s why it’s loved by malware authors and hated by reverse engineers

#BlueTeam
#RedTeam
#ReverseEngineering
#Self_Modifying_Code
7
💣 "Bypassing EDR Hooks with Reverse Engineering"

📌 چرا این بمبه؟
چون EDRها (مثل CrowdStrike SentinelOne ) برای شناسایی فعالیت مشکوک روی APIهای حساس (مثل CreateRemoteProcess, NtWriteVirtualMemory, NtOpenProcess) Inline Hook میزنن
این یعنی وقتی تو بدافزارت از این APIها استفاده کنی اول میری تو کد EDR → بعد تازه سیستم‌کال اصلی




🔎 ساختار :
فکر میکنی وقتی OpenProcess صدا میزنی واقعا داری مستقیماً با Windows حرف میزنی؟ نه!
اول میری تو تور EDR و اونجاست که همه چی لو میره


2 توضیح ساده:

EDR با Inline Hook
اولین چند بایت تابع رو با یه jmp به خودش عوض میکنه


مثال (شکل ساده) :

; اصل تابع NtOpenProcess
mov eax, 23h
mov edx, offset args
syscall
ret


بعد از Hook شدن 👇

jmp EDR_Handler
بقیه کد بازنویسی شده



3 ترفند :

مهندس معکوس با بررسی DLL (مثلا ntdll.dll) متوجه Hook میشه

راه بایپس: مستقیما با syscall کار کنه یا ntdll clean copy لود کنه



4 نمونه کد (C + Inline ASM) :

__asm {
mov eax, 0x23 ; NtOpenProcess syscall number
mov edx, args
syscall
}


👉 اینجا دیگه EDR هیچی نمی‌فهمه چون Hook فقط رو API سطح بالا بوده


💣 "Bypassing EDR Hooks with Reverse Engineering"


🚨 Why EDR Hooking is a Problem

EDRs (CrowdStrike, SentinelOne, Defender ATP, etc...) need visibility into sensitive APIs :

CreateRemoteThread

NtWriteVirtualMemory

NtOpenProcess

NtCreateSection


They inline-hook these APIs :

The first few bytes of the function (in ntdll.dll) are overwritten with a jmp into the EDR’s handler

This means: whenever your malware (or tool) calls OpenProcess, you first land inside EDR code, not Windows


So you think you’re talking to Windows → actually you’re talking to CrowdStrike first




🔎 Structure :

Disassembling hooked functions reveals this:

Before hook clean ntdll :

mov eax, 23h ; syscall number NtOpenProcess
mov edx, offset args
syscall
ret


After hook (with EDR) :

jmp EDR_Handler ; patched jump by EDR
; original bytes are gone





Why bypassing hooks works

Inline hooks live in user-mode DLLs (ntdll.dll, kernel32.dll)
But the actual system call still exists in the kernel

If you:

1 Call the syscall directly syscall instruction


2 Or load a clean, unhooked ntdll.dll (from disk or from a suspended process)



→ You skip the EDR trampoline and go straight to the kernel



🧑‍💻 Minimal Example (C + Inline ASM)

__asm {
mov eax, 0x23 ; NtOpenProcess syscall number
mov edx, args ; pointer to arguments struct
syscall ; direct transition to kernel
}


👉 EDR hook is bypassed, because it only patched ntdll!NtOpenProcess, not the raw syscall




🎯 Why this is a bomb

It exposes a fundamental design weakness in user-mode monitoring

Reverse engineers can detect hooks by scanning DLLs (memcmp with clean disk copy)

Attackers can patch back the original bytes or just issue syscalls directly

EDR vendors try to move detection deeper kernel callbacks ETW hypervisor tricks but inline hooks are still everywhere
🔥7
از کانال راضی هستید ؟

Are you satisfied with the channel ?
Anonymous Poll
94%
👍🏻
6%
👎🏻
🔥91