ReverseEngineering
1.33K subscribers
50 photos
11 videos
112 files
916 links
Download Telegram
قسمت سی و چهارم بافر اورفلو


Dynamic Taint Analysis دنبال کردن داده در زمان اجرای واقعی برنامه

تا اینجا بیشتر از روی Assembly و ساختار برنامه مسیر داده رو بررسی کردیم
یعنی برنامه رو اجرا نکردیم و سعی کردیم از روی کد بفهمیم داده ممکنه از کجا وارد بشه و به کجا برسه

این میشه
Static Analysis

حالا میخوایم یک قدم جلوتر بریم
برنامه رو واقعا اجرا کنیم و ببینیم داده در همین اجرای مشخص دقیقا
از چه مسیرهایی عبور میکنه

اینجا می‌رسیم به
Dynamic Taint Analysis

یا DTA
فرق Static و Dynamic

در Static Analysis برنامه اجرا نمیشه
ما از روی کد و Assembly بررسی میکنیم


Source
↓
Instruction
↓
Function
↓
Sink

ولی در Dynamic Analysis برنامه واقعا اجرا میشه

Program Running
↓
Input
↓
Execution
↓
Track Data
↓
Sink

یعنی به جای اینکه فقط بگیم این مسیر ممکنه وجود داشته باشه
میبینیم در این اجرای مشخص واقعا چه اتفاقی افتاده

یک مثال ساده

فرض کنید برنامه اینه
C

#include <stdio.h>
#include <string.h>

void process(char *input)
{
char buffer[32];

strcpy(buffer, input);

printf("%s\n", buffer);
}

برنامه اجرا میشه و این ورودی رو دریافت میکنه
hello

حالا سوال ما اینه
این hello از لحظه ورود به برنامه چه مسیری رو طی کرده؟

اینجاست که Taint Analysis وارد میشه

Source
اول باید مشخص کنیم داده از کجا وارد برنامه شده
مثلا:

stdin

یا مثلا

read()
recv()
fread()

پس میتونیم بگیم

stdin
↓
TAINTED DATA

یعنی داده‌ای که از Source وارد شده فعلا به عنوان Tainted علامت‌گذاری شده

Taint Propagation
حالا فرض کنید برنامه این کار رو انجام بده

C
char *input = get_input();

char *data = input;

char *ptr = data;

از دید Taint Tracking مسیر وابستگی داده میتونه مفهومی شبیه این باشه

input
↓
Tainted
↓
data
↓
Tainted
↓
ptr
↓
Tainted

یعنی مقدار موجود در ptr از داده‌ای میاد که از Source وارد شده
البته در ابزارهای واقعی داستان کمی پیچیده‌ تره
چون Taint فقط یک برچسب ساده روی متغیر نیست
ابزار باید انتقال و تغییر داده رو در Registerها و Memory هم دنبال کنه

Taint
روی بخشی از داده
یک نکته مهم اینه که همیشه کل داده Tainted نیست

فرض کنید ورودی اینه

ABCDEFGH

ممکنه یک عملیات فقط به چهار بایت اول وابسته باشه

ABCD

در این حالت میتونیم مفهومی شبیه این داشته باشیم

A → Tainted
B → Tainted
C → Tainted
D → Tainted

E → Clean
F → Clean
G → Clean
H → Clean

یعنی Taint میتونه در سطح بخشی از یک مقدار هم دنبال بشه

این موضوع مخصوصا در تحلیل Parser ها و Protocol ها مهمه

چون میتونیم بفهمیم کدوم قسمت از Input واقعا روی یک عملیات خاص تأثیر گذاشته

Taint
در Register ها
حالا بریم سراغ چیزی که در Reverse Engineering بیشتر باهاش سر و کار داریم

Assembly
فرض کنید هنگام اجرای برنامه این دستور اجرا میشه

mov rax,[rbp-40h]

و Memory در آدرس

[rbp-40h]

حاوی داده Tainted باشه
پس بعد از اجرای دستور

[rbp-40h]
↓
RAX

حالا دستور بعدی

mov rcx,rax

باعث میشه وابستگی Taint هم دنبال بشه

Memory
↓
RAX
↓
RCX

بعد فرض کنید داریم

mov [rdi],rcx

حالا مسیر داده میشه

Input
↓
Memory
↓
RAX
↓
RCX
↓
Memory Write

اینجا دیگه فقط از روی Assembly حدس نزدیم
داریم مسیر داده رو در یک اجرای واقعی دنبال میکنیم

یک نکته مهم درباره Taint و Pointer
اینجا باید حواسمون باشه

Taint
کردن یک Pointer حتما به معنی Taint شدن داده‌ای که Pointer بهش اشاره می‌کنه نیست

مثلا:

Pointer
↓
0x12345678

ممکنه خود مقدار Pointer تحت تاثیر Input باشه

ولی محتوای آدرس

0x12345678

حتما به همون شکل Tainted نیست
این تفاوت در تحلیل Memory خیلی مهمه
چون

Pointer Taint

و
Pointee Data Taint

دو مفهوم متفاوت هستن

Dynamic Analysis
چه مزیتی داره؟

فرض کنید یک تابع 50 مسیر مختلف داره
در Static Analysis ممکنه لازم باشه تعداد زیادی از این مسیرها رو بررسی کنیم
ولی Dynamic Analysis روی اجرای فعلی فقط چیزی رو میبینه که واقعا اجرا شده

مثلا:

Function
├── Path A
├── Path B
├── Path C
└── Path D

اگر Input ما باعث اجرای Path C بشه

Input
↓
Path C
↓
Sink

همین مسیر واقعی رو مشاهده میکنیم
ولی اینجا یک مشکل داریم

Dynamic Analysis همه چیز رو نمیبینه
فرض کنید برنامه این شکلیه


Input
↓
if (condition)
↓
Path B

اگر شرط برقرار نشه

Path B

اصلا اجرا نمیشه
پس Dynamic Analysis نمیتونه درباره مسیری که اجرا نشده با همان قطعیت مسیر اجراشده اطلاعات بده

به همین دلیل معمولا این دو روش کنار هم ارزش بیشتری دارن

Static Analysis
+
Dynamic Analysis

Static

کمک میکنه بفهمیم چه مسیر ها و وابستگی‌ هایی ممکنه وجود داشته باشن

Dynamic
کمک میکنه بفهمیم در یک

اجرای مشخص واقعا چه اتفاقی افتاده

ابزارها

برای Dynamic Binary Analysis و Taint Tracking ابزارها و Framework های مختلفی وجود دارن

Intel Pin
DynamoRIO
Triton
angr
PANDA

ولی نباید فکر کنیم همه این ابزارها دقیقا یک کار رو انجام میدن مدل Instrumentation و قابلیت‌ های هرکدوم متفاوته

مثلا:
Triton

برای Dynamic Binary Analysis امکاناتی مثل Taint Analysis و Symbolic Execution داره

ارتباط با Fuzzing

اینجا داستان جالب‌ تر میشه

ما قبلا داشتیم

Fuzzer
↓
Interesting Input
↓
Crash

حالا میتونیم از Dynamic Taint Analysis برای بررسی مسیر داده استفاده کنیم

مثلا:

Interesting Input
↓
Tainted Data
↓
Parser
↓
Variable
↓
Memory Operation
↓
Crash

پس فقط نمیگیم برنامه Crash کرد
بلکه میتونیم بررسی کنیم داده‌ای که از Input وارد شده در طول اجرای برنامه چه مسیر هایی رو طی کرده و کجا در ایجاد مشکل نقش داشته

Static + Dynamic

یک Workflow منطقی برای تحلیل میتونه این شکلی باشه

Fuzzing
↓
Crash
↓
Crash Triage
↓
Static Analysis
↓
Find Source / Sink
↓
Dynamic Analysis
↓
Trace Data
↓
Root Cause

اینجا کم‌ کم از یک Crash ساده میرسیم به درک بهتر مسیر ایجاد Bug



Dynamic Taint Analysis
یعنی داده یا وابستگی داده‌ ای مشخص رو هنگام اجرای واقعی برنامه دنبال کنیم

مفاهیم اصلی:

Source
↓
Taint
↓
Propagation
↓
Register / Memory
↓
Control Flow
↓
Sink

و نکته مهم اینه که Static Analysis و Dynamic Analysis رقیب هم نیستن

Static
بیشتر جواب این سوال رو میده

چه مسیر هایی ممکنه وجود داشته باشه؟



Dynamic
بیشتر جواب این سوال رو میده

در این اجرای مشخص واقعا چه مسیری طی شد؟

وقتی این دو رو کنار Fuzzing و Crash Analysis بذاریم

میتونیم از

Crash

بریم به

Input
↓
Data Flow
↓
Instruction
↓
Memory Operation
↓
Crash
↓
Root Cause


و این دقیقا همون جاییه که تحلیل یک Crash از یک اتفاق ساده تبدیل میشه به فهمیدن علت واقعی Bug

@reverseengine
ReverseEngineering
قسمت سی و چهارم بافر اورفلو Dynamic Taint Analysis دنبال کردن داده در زمان اجرای واقعی برنامه تا اینجا بیشتر از روی Assembly و ساختار برنامه مسیر داده رو بررسی کردیم یعنی برنامه رو اجرا نکردیم و سعی کردیم از روی کد بفهمیم داده ممکنه از کجا وارد بشه و به…
Part 34 Buffer Overflow


Dynamic Taint Analysis Tracking Data During Actual Program Execution

So far, we have mostly examined the data path based on the assembly and program structure

That is, we did not execute the program and tried to understand from the code where the data might come from and where it might end up

This is called
Static Analysis

Now we want to go one step further
to actually execute the program and see exactly what paths the data takes in this specific execution

Here we come to
Dynamic Taint Analysis

or DTA
Difference between Static and Dynamic

In Static Analysis, the program is not executed
We examine from the code and assembly

Source
↓
Instruction
↓
Function
↓
Sink

But in Dynamic Analysis, the program is actually executed

Program Running
↓
Input
↓
Execution
↓
Track Data
↓
Sink

That is, instead of just saying that this path might exist
We see what actually happened in this specific execution

A simple example

Suppose the program is
C

#include <stdio.h>
#include <string.h>

void process(char *input)
{
char buffer[32];

strcpy(buffer, input);

printf("%s\n", buffer);
}

The program runs and receives this input
hello

Now our question is
What path has this hello taken since entering the program?

This is where Taint Analysis comes in

Source
First we need to specify where the data entered the program from
For example:

stdin

Or for example

read()
recv()
fread()

Then we can say

stdin
↓
TAINTED DATA

That is, the data entered from Source is currently marked as Tainted

Taint Propagation
Now suppose the program does this

C
char *input = get_input();

char *data = input;

char *ptr = data;

From the perspective of Taint Tracking, the data dependency path can be a concept like this

input
↓
Tainted
↓
data
↓
Tainted
↓
ptr
↓
Tainted

That is, the value in ptr comes from data that was entered from Source

Of course, in real tools, the story is a little more complicated

Because Taint is not just a simple label on a variable

The tool must also track data transfers and changes in Registers and Memory

Taint
on a part of the data

An important point is that not always the entire data is Tainted

Suppose the input is

ABCDEFGH

An operation may depend only on the first four bytes

ABCD

In this case, we can have a concept like this

A → Tainted
B → Tainted
C → Tainted
D → Tainted

E → Clean
F → Clean
G → Clean
H → Clean

That is, Taint can be tracked at the level of a part of a value

This is especially true in the analysis of Parsers and Protocols Important

Because we can understand which part of the Input actually affected a particular operation

Taint
in Registers

Now let's move on to what we deal with most in Reverse Engineering

Assembly
Suppose that when the program is executed, this instruction is executed

mov rax,[rbp-40h]

and the Memory at the address

[rbp-40h]

contains Tainted data

So after executing the instruction

[rbp-40h]
↓
RAX

Now the next instruction

mov rcx,rax

will also follow the Taint dependency

Memory
↓
RAX
↓
RCX

Then suppose we have

mov [rdi],rcx

Now the data path is

Input
↓
Memory
↓
RAX
↓
RCX
↓
Memory Write

Here we are not just guessing from the Assembly

We are following the data path in a real execution

An important point about Taint and Pointer
Here we need to be careful Okay

Taint
a Pointer does not necessarily mean that the data that the Pointer points to is tainted

For example:

Pointer
↓
0x12345678

The value of the Pointer itself may be affected by Input

But the content of the address

0x12345678

is not necessarily tainted in the same way

This difference is very important in Memory Analysis

Because

Pointer Taint

and
Pointee Data Taint

are two different concepts

What is the advantage of Dynamic Analysis

?

Suppose a function has 50 different paths
In Static Analysis we may need to examine a large number of these paths
But Dynamic Analysis only sees what is actually executed on the current execution
For example:

Function
├── Path A
├── Path B
├── Path C
└── Path D

If our Input causes Path C to be executed

Input
↓
Path C
↓
Sink
Therefore, dynamic analysis cannot provide information about an unexecuted path with the same level of certainty as it does for an executed one.

For this reason, these two methods are generally more valuable when used together:

Static Analysis
+
Dynamic Analysis

Static

Helps us understand what paths and dependencies might exist.

Dynamic

Helps us understand what actually happened during a

specific execution.

Tools

There are various tools and frameworks available for Dynamic Binary Analysis and Taint Tracking:

Intel Pin
DynamoRIO
Triton
angr
PANDA

However, we shouldn't assume all these tools do exactly the same thing; their instrumentation models and capabilities differ.

For example:

Triton

Offers features like Taint Analysis and Symbolic Execution for Dynamic Binary Analysis.

Connection to Fuzzing

This is where it gets interesting.

Previously, we had:

Fuzzer
↓
Interesting Input
↓
Crash

Now, we can use Dynamic Taint Analysis to examine the data path.

For example:

Interesting Input
↓
Tainted Data
↓
Parser
↓
Variable
↓
Memory Operation
↓
Crash

So, we don't just say the program crashed;

we can also trace the path the input data took during execution and identify where it contributed to the issue.

Static + Dynamic

A logical analysis workflow might look like this:

Fuzzing
↓
Crash
↓
Crash Triage
↓
Static Analysis
↓
Find Source / Sink
↓
Dynamic Analysis
↓
Trace Data
↓
Root Cause

Here, we gradually move from a simple crash to a better understanding of the path that caused the bug.

Dynamic Taint Analysis

Means tracking specific data or data dependencies during the actual execution of the program.

Key concepts:

Source
↓
Taint
↓
Propagation
↓
Register / Memory ↓
Control Flow
↓
Sink

An important point is that static analysis and dynamic analysis are not rivals.

Static analysis
primarily answers this question:

What possible paths might exist?

Dynamic analysis
primarily answers this question:

What path was actually taken during this specific execution?

When we combine these two with fuzzing and crash analysis,

we can trace a path from

Crash

to

Input
↓
Data Flow
↓
Instruction
↓
Memory Operation
↓
Crash
↓
Root Cause

And this is precisely the point where analyzing a crash shifts from observing a simple event to understanding the bug's root cause.

@reverseengine
Pointer Aliasing

تا اینجا فهمیدیم داده میتونه از Register بره داخل Memory و دوباره از طریق Pointer برگرده

حالا یک مشکل مهم تر داریم

ممکنه دو Pointer مختلف به یک قسمت از Memory اشاره کنن

مثلا:
int value = 100;

int *p = &value;
int *q = &value;

اینجا p و q دو Pointer مختلف هستن

ولی هر دو به یک Memory اشاره میکنن

value
|
0x5000
/ \
/ \
p q

پس اگر این کار رو انجام بدیم

*p = 200;

مقدار value تغییر میکنه

و وقتی بعدا این رو بخونیم

printf("%d", *q);

مقدار 200 رو میبینیم

چون p و q در واقع به یک محل اشاره میکنن

به این وضعیت میگیم

Pointer Aliasing

این موضوع برای Reverse Engineering خیلی مهمه

چون ممکنه داخل یک تابع ببینید

mov [rax], 200

و چند خط پایین‌ تر ببینید

mov ecx, [rbx]

در نگاه اول ممکنه فکر کنید این دو دستور هیچ ارتباطی ندارن

ولی اگر

RAX = 0x5000
RBX = 0x5000

باشه

هر دو دارن با یک Memory مشترک کار میکنن

RAX ─────┐
↓
0x5000
↑
|
RBX ─────┘

پس تغییر از طریق RAX میتونه مستقیما روی چیزی که از طریق RBX خونده میشه اثر بذاره

اینجا Data Flow دیگه فقط بین Register ها نیست

باید رابطه Pointer ها و Memory Address ها رو هم دنبال کنید

یک مثال ساده‌ تر

int value = 10;

int *p = &value;
int *q = p;

*p = 50;

int x = *q;

مسیر واقعی داده

value
↓
p
↓
q
↓
Memory
↓
50
↓
x

اما از نظر Address

p ──┐
├──> value
q ──┘

پس p و q دو مسیر متفاوت برای رسیدن به یک داده هستن

در Reverse Engineering

وقتی با Pointerها کار میکنید فقط نپرسد

این Pointer کجاست؟

باید بپرسید

این Pointer به کجا اشاره میکنه؟

چه Pointer های دیگه‌ ای ممکنه به همین Address اشاره کنن؟

چه کسی مقدار اون Memory رو تغییر داده؟

چه کسی بعدا اون مقدار رو خونده؟

این سوال‌ها کمک میکنن ارتباط‌ هایی رو پیدا کنی که از روی Control Flow معمولی مشخص نیستن

تمرین:

این کد رو بررسی کنید

int value = 10;

int *p = &value;
int *q = p;

*p = 100;

int result = *q;

مسیر Data Flow رو رسم کنید

value
↓
p
↓
q
↓
*p = 100
↓
*q
↓
result

بعد مشخص کن چرا result برابر 100 میشه

در مرحله بعد میریم سراغ یک موضوع مهم‌تر


Memory Aliasing
در توابع مختلف

اونجا دیگه دو Pointer داخل یک تابع نداریم

یک تابع Memory رو تغییر میده و یک تابع دیگه از طریق Pointer متفاوت همون Memory رو میخونه

و دقیقا همینجا تحلیل Data Flow در باینری‌ های واقعی شروع میکنه به دردسر درست کردن

@reverseengine
ReverseEngineering
Pointer Aliasing تا اینجا فهمیدیم داده میتونه از Register بره داخل Memory و دوباره از طریق Pointer برگرده حالا یک مشکل مهم تر داریم ممکنه دو Pointer مختلف به یک قسمت از Memory اشاره کنن مثلا: int value = 100; int *p = &value; int *q = &value; اینجا p…
Pointer Aliasing

So far we have understood that data can go from Register to Memory and back again through Pointer

Now we have a more important problem

Two different Pointers may point to the same part of Memory

For example:
int value = 100;

int *p = &value;

int *q = &value;

Here p and q are two different Pointers

But both point to the same Memory

value
|
0x5000
/ \
/ \
p q

So if we do this

*p = 200;

the value of value will change

And when we read this later

printf("%d", *q);

We see the value 200

Because p and q actually point to the same location

We call this situation

Pointer Aliasing

This is very important for Reverse Engineering

Because you might see inside a function

mov [rax], 200

And a few lines down

mov ecx, [rbx]

At first glance you might think these two instructions have nothing to do

But if

RAX = 0x5000

RBX = 0x5000

Well

They are both working with the same memory

RAX ─────┐
↓
0x5000
↑
|
RBX ─────┘

So a change through RAX can directly affect what is read through RBX

Here the Data Flow is not just between Registers

You should also follow the relationship between Pointers and Memory Addresses

A simpler example

int value = 10;

int *p = &value;
int *q = p;

*p = 50;

int x = *q;

Actual data path

value
↓
p
↓
q
↓
Memory
↓
50
↓
x

But in terms of Address

p ──┐
├──> value
q ──┘

So p and q are two different paths to reach the same data

In Reverse Engineering

When working with Pointers, don't just ask

Where is this Pointer?

You should ask

Where does this Pointer point to?

What other Pointers might point to the same Address?

Who changed the value of that Memory?

Who later read that value?

These questions help you find connections that are not obvious from normal Control Flow

Exercise:

Examine this code

int value = 10;

int *p = &value;

int *q = p;

*p = 100;

int result = *q;

Draw the Data Flow path

value
↓
p
↓
q
↓
*p = 100
↓
*q
↓
result

Then determine why result is equal to 100

Next, we will move on to a more important topic

Memory Aliasing
In different functions

There we no longer have two pointers inside a function

One function modifies the Memory and another function reads the same Memory through a different Pointer

And this is exactly where Data Flow analysis in real binaries starts to cause trouble

@reverseengine
exploit_reversing_10_ios.pdf
25.5 MB
Exploiting Reversing (ER) series, Article 10:
iOS Security Research (part 01)
, Sep 2026.

Opens a multi-part sequence on iOS, and the first article in the series aimed at iOS itself rather than macOS. It first sets out the stack a kernel chain must cross in sequence - KASLR, Pointer Authentication, kalloc_type segregation with zone_require assertions, PPL and its successors SPTM at GL2 and TXM at GL0, Exclaves and trust-cache code signing - and why each layer pushes exploitation further toward data-only technique
Bluetooth Headphone Jacking: Full Disclosure of Airoha RACE Vulnerabilities

https://insinuator.net/2025/12/bluetooth-headphone-jacking-full-disclosure-of-airoha-race-vulnerabilities/
قسمت سی و چهارم بافر اورفلو


Symbolic Execution

از ورودی واقعی تا ورودی نماد ها

تا اینجا یاد گرفتیم چطور داده رو داخل برنامه دنبال کنیم و بفهمیم چطور به یک Crash میرسه

حالا میخوایم یک قدم جلوتر بریم
فرض کنید یک برنامه شرطی داره که فقط وقتی ورودی مقدار خاصی داشته باشه وارد یک مسیر مشخص میشه
میتونیم هزاران ورودی مختلف امتحان کنیم تا بالاخره به اون مسیر برسیم
ولی راه بهتری هم وجود داره
به جای اینکه فقط مقدارهای واقعی رو امتحان کنیم میتونیم از Symbolic Execution استفاده کنیم تا بفهمیم چه شرط‌ هایی باید برقرار باشن تا برنامه وارد مسیر موردنظر بشه

اینجا به جای حدس زدن ورودی‌ها سعی میکنیم محدودیت‌ های مسیر رو استخراج کنیم و اون‌ها رو حل کنیم

ایده اصلی چیه

فرض کنید برنامه اینه
C

int check(int x)
{
if (x == 1337)
return 1;

return 0;
}

در اجرای معمولی ممکنه این مقدار رو به برنامه بدیم

x = 10

برنامه شرط رو بررسی میکنه

10 == 1337

نتیجه غلطه و برنامه وارد مسیر دوم میشه
حالا فرض کن به جای مقدار واقعی از یک مقدار نمادین استفاده کنیم

x = X

در این حالت شرط برنامه میشه

X == 1337

حل‌ کننده می‌تونه نتیجه بگیره که برای ورود به مسیر اول باید مقدار X برابر 1337 باشه
پس به جای امتحان کردن مقدارهای مختلف می‌تونیم شرط لازم برای رسیدن به مسیر رو به دست بیاریم

Path Constraint
فرض کنید این کد رو داریم
C

int process(int x)
{
if (x > 10)
{
if (x < 20)
return 1;
}

return 0;
}

برای رسیدن به return 1 باید هر دو شرط برقرار باشن


x > 10
AND
x < 20

به مجموعه شرط‌هایی که برای رسیدن به یک مسیر مشخص باید برقرار باشن میگیم

Path Constraint
پس محدودیت مسیر موردنظر اینه

10 < x < 20

چون x از نوع int هست جواب‌های ممکن شامل این مقدارها میشن

11, 12, 13, 14, 15, 16, 17, 18, 19

حل‌ کننده میتونه یکی از جواب‌های معتبر رو پیدا کنه

مثلا:

x = 15

نکته مهم اینه که Symbolic Execution فقط شرط فعلی رو در نظر نمیگیره
بلکه شرط‌های قبلی مسیر رو هم نگه میداره
چون برای رسیدن به یک نقطه از برنامه باید تمام شرط‌های لازم در مسیر برقرار باشن

وقتی مسیرها چند شاخه میشن

این برنامه رو ببینید
C

int process(int x)
{
if (x > 10)
return 1;

if (x == 5)
return 2;

return 0;
}

از دید Symbolic Execution میتونیم مسیرهای مختلف برنامه رو بررسی کنیم


x = X
|
x > 10 ?
/ \
Yes No
| |
return 1 x == 5 ?
/ \
Yes No
| |
return 2 return 0

حالا فرض کنید میخوایم به return 2 برسیم

اول باید شرط اول برقرار نباشه

X <= 10

بعد باید شرط دوم برقرار باشه

X == 5

پس Path Constraint ما میشه

X <= 10
AND
X == 5

در نتیجه

X = 5

یک ورودی معتبر برای رسیدن به این مسیر محسوب میشه
این همون ایده اصلی Symbolic Execution هست

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

نقش SMT Solver

خب تا اینجا فهمیدیم که میتونیم شرط‌های مسیر رو استخراج کنیم ولی وقتی تعداد شرط‌ها زیاد بشه حل کردن اون‌ها با دست سخت میشه

اینجا از ابزاری به اسم
SMT Solver
استفاده میکنیم

یکی از ابزارهای شناخته‌شده در این زمینه

Z3
هست که توسط Microsoft Research توسعه داده شده

فرض کنید شرط‌های ما اینا باشن

X > 10
X < 20
X != 15

حل‌ کننده باید مقداری پیدا کنه که تموم شرط‌ ها رو همزمان برقرار کنه

مثلا:

X = 11


این مقدار معتبره چون هر سه شرط رو برقرار میکنه

ولی اگر شرطها این باشن


X > 20
X < 10

هیچ مقدار حقیقی نمی‌تونه هر دو شرط رو همزمان برقرار کنه

در نتیجه مسئله
Unsatisfiable
هست
یعنی جواب معتبری وجود نداره
در مقابل اگر حداقل یک جواب وجود داشته باشه مسئله

Satisfiable
محسوب میشه
البته SMT Solver فقط برای شرطهای ساده عددی نیست

بسته به منطق و قابلیت‌ های حل‌ کننده میتونه محدودیت‌های مربوط به Bit Vectorها و عملیات بیتی رو هم بررسی کنه

این موضوع برای تحلیل باینری‌ ها خیلی مهمه چون پردازنده با مقدارهایی با اندازه مشخص مثل 8 و 32 و 64 بیت کار میکنه و رفتار اون‌ها همیشه مثل اعداد صحیح ریاضی نیست

ارتباط Symbolic Execution با Reverse Engineering

فرض کنید یک باینری دارید و داخلش چنین منطقی وجود داره

C++

int validate(int value)
{
if (value < 100)
return 0;

if (value > 200)
return 0;

if (value == 150)
return 1;