قسمت سی و چهارم بافر اورفلو
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
به همین دلیل معمولا این دو روش کنار هم ارزش بیشتری دارن
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
ReverseEngineering
قسمت سی و چهارم بافر اورفلو Dynamic Taint Analysis دنبال کردن داده در زمان اجرای واقعی برنامه تا اینجا بیشتر از روی Assembly و ساختار برنامه مسیر داده رو بررسی کردیم یعنی برنامه رو اجرا نکردیم و سعی کردیم از روی کد بفهمیم داده ممکنه از کجا وارد بشه و به…
We see the same actual path
But here we have a problem
Dynamic Analysis does not see everything
Suppose the program looks like this
Input
↓
if (condition)
↓
Path B
If the condition is not met
Path B
will not be executed at all
But here we have a problem
Dynamic Analysis does not see everything
Suppose the program looks like this
Input
↓
if (condition)
↓
Path B
If the condition is not met
Path B
will not be executed at all
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
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
تا اینجا فهمیدیم داده میتونه از 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
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
_assembly-language-for-x86-processors-8-ed.pdf
11.5 MB
Assembly Language for x86 Processors
_security_automation_with_python_practical_python_solutions_for.pdf
40 MB
Security Automation with Python
From BlackCat to Panda Workshop: Inside the Evolving C2 Panel Behind RATHat
https://www.cleafy.com/cleafy-labs/from-blackcat-to-panda-workshop-inside-the-evolving-c2-panel-behind-rathat
@reverseengine
https://www.cleafy.com/cleafy-labs/from-blackcat-to-panda-workshop-inside-the-evolving-c2-panel-behind-rathat
@reverseengine
Cleafy
From BlackCat to Panda Workshop: Inside the Evolving C2 Panel Behind RATHat | Cleafy
Cleafy's TIR team analyzed the operator infrastructure behind RATHat, an Android banking trojan that abuses Accessibility Services to enable wireless debugging on the victim's phone, obtain a shell, and deploy a hidden native service outside the app. The…
NeedyMantis: Unpacking a post-compromise malware family used in targeted operations
https://microsoft.com/en-us/security/blog/2026/09/28/needymantis-unpacking-a-post-compromise-malware-family-used-in-targeted-operations
@reverseengine
https://microsoft.com/en-us/security/blog/2026/09/28/needymantis-unpacking-a-post-compromise-malware-family-used-in-targeted-operations
@reverseengine
Microsoft News
NeedyMantis: Unpacking a post-compromise malware family used in targeted operations
Microsoft Threat Intelligence identified NeedyMantis, a modular post-compromise malware framework used in targeted intrusions that combines custom loaders, encrypted archives, and extensible components to maintain long-term access and support follow-on operations.
An AI-Assisted Cyber Attack: Inside a Unit 42 Investigation
injecting backdoor in to terraform
https://unit42.paloaltonetworks.com/ai-assisted-cyber-attack-inside-a-unit-42-investigation
@reverseengine
injecting backdoor in to terraform
https://unit42.paloaltonetworks.com/ai-assisted-cyber-attack-inside-a-unit-42-investigation
@reverseengine
Unit 42
An AI-Assisted Cyber Attack: Inside a Unit 42 Investigation
Using autonomous AI agents, an attacker breached an enterprise network in a matter of hours. Understand how to address and defend against agentic attacks.
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
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
From BlackCat to Panda Workshop: Inside the Evolving C2 Panel Behind RATHat
https://www.cleafy.com/cleafy-labs/from-blackcat-to-panda-workshop-inside-the-evolving-c2-panel-behind-rathat
https://www.cleafy.com/cleafy-labs/from-blackcat-to-panda-workshop-inside-the-evolving-c2-panel-behind-rathat
Cleafy
From BlackCat to Panda Workshop: Inside the Evolving C2 Panel Behind RATHat | Cleafy
Cleafy's TIR team analyzed the operator infrastructure behind RATHat, an Android banking trojan that abuses Accessibility Services to enable wireless debugging on the victim's phone, obtain a shell, and deploy a hidden native service outside the app. The…
FortiBleed Is Still Active, Now Locking Orgs Out
https://socradar.io/blog/fortibleed-still-active-locking-organizations-out/
https://socradar.io/blog/fortibleed-still-active-locking-organizations-out/
SOCRadar® Cyber Intelligence Inc.
FortiBleed Is Still Active, Locking Organizations Out
On October 6, 2026, the FBI and the U.S. Secret Service published a joint Cybersecurity Advisory on FortiBleed, the active global credential compromise,,,
Threat Actors Abuse Trusted Software Lures and Cloudflare Tunnels to Deploy ScreenConnect RMM Agents Across Windows and macOSScreenConnect RMM Agents Across Windows and macOS
Analyzing SMOKE#SCREEN:
https://www.securonix.com/blog/smoke-screen-screenconnect-rmm-abuse-cloudflare-tunnels/
Analyzing SMOKE#SCREEN:
https://www.securonix.com/blog/smoke-screen-screenconnect-rmm-abuse-cloudflare-tunnels/
Securonix
SMOKE#SCREEN: ScreenConnect RMM Abuse, Cloudflare Tunnels, and Trusted Software Lures
Securonix Threat Research: Analyze the SMOKE#SCREEN campaign abusing ScreenConnect RMM, Cloudflare Tunnels, and trusted software lures to gain persistent access across Windows and macOS.
Cursor 0day: When Full Disclosure Becomes the Only Protection Left
https://mindgard.ai/blog/cursor-0day-when-full-disclosure-becomes-the-only-protection-left
https://mindgard.ai/blog/cursor-0day-when-full-disclosure-becomes-the-only-protection-left
mindgard.ai
Cursor 0day: When Full Disclosure Becomes the Only Protection Left - Mindgard
The vulnerability nobody seems interested in fixing
Bluetooth Headphone Jacking: Full Disclosure of Airoha RACE Vulnerabilities
https://insinuator.net/2025/12/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;