قسمت سی و چهارم بافر اورفلو
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
اصلا اجرا نمیشه