برای طرح سوال و دسترسی به آموزش ها  کانال ما در تلگرام بپویندید  

چگونه یک متغیر مقداردهی نشده، باگ ایجاد می کند؟

سلامی دوباره، بعد از مدتها استراحت و بی حوصلگی در راستای تجزیه نشدن و همچنین جزیی از طبیعت قرار نگرفتن یه پستی توی بلاگ میزارم تا این پست رو همانند پکت های Keep-Alive در نظر بگیرید.  :دایی

خوب، قرار نیست نحوه اکسپلویت کردن و تکنیک هارو توضیح بدیم فقط میخواهیم کمی درباره خطرات مقداردهی نکردن متغیرها در برنامه و نحوه بوجود آمدن باگ صحبت کنیم، واس خیلی ها از جمله خودم پیش اومده که در زبان C/C++ متغیری مانند Integer رو مقداردهی اولیه انجام ندادیم ، یادمون رفته و یا برحسب عادت در زبان های برنامه نویسی دیگر مثل VB6.0 اینکار رو انجام ندادیم. بنظرم تنها دلیلی که بعضی ها هنوزم که هنوزه طرفدار VB6.0 هستند! همین مقداردهی اولیه نکردن متغیرهاست، اگر شما حتی نوع متغیر رو تعریف نکنین بطور خودکار متغیر از نوع Variant در نظر گرفته میشه و هر DATA Type بخواهید میتونید درون متغیر بریزید!!! اینها انسانهای خاص هستند و خاص هم که فحش نیست !؟

خوب از بحث اصلیمون دور نشیم،اگر استاد همون درس برنامه نویسی و یا افراد دیگری که ازشون این سوال “متغییر مقدار دهی نشده ولی دارای مقدار هست و خروجی به ما نشون میده” رو پرسیدین بهمون گفتن که وقتی یک نوع Integer رو مقدار دهی نمیکنیم ، مقداری  Random درون متغیر ریخته میشه؟!؟ و اینم جز خیلی از حرفاس که وارد مغزمون کردن تا بیشتر ندونیم و نپرسیم. (انگاری دلم خیلی پره :دایی) بایک مثال خیلی ساده و راحت میخواهیم به صحت و سقم این دلیل پی ببریم. خوب سورس زیر رو کامپایل و اجرا میکنیم:

int main(void)
{
	int uninit_var ;
	printf("\n your uninitialized variable is :\t %x\n",uninit_var); 
	return 0;
}

بعد از اجرا مقدار این متغیر رو داریم میبینیم که بایستی این یک مقدار تصادفی باشه و در هر بار اجرا مقداری مختلف ببینید، من درون سیستم خودم فقط این مقدار رو میبینیم و نتیجه میگیرم که مقدار Random نیست:

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

debugging

همانطور که ملاحظه میکنین ابتدا مقدار ECX درون پشته Push  میشه و بنابراین عنصر بالای پشته خواهد بود و مقدار ESP یا همون Stack Pointer هم به عنصر بالای پشته اشاره میکنه. حال مقدار بالای پشته وارد رجیستر EAX میشه یعنی درواقع همون مقدار رجیستر ECX . نتیجه اینکه مقدار بالای پشته یا همون مقدار رجیستر ECX ، مقدار تصادفی در درون uninit_var  (متغیر مقدار دهی نشده مان) می باشد.

وقتی که تعداد خط کدها زیادمیشن برنامه نویس ها فراموش می کنند که کدام متغییر مقداردهی نشده است و دلایل دیگر.شاید در لفظ و در نظر برنامه نویس مقدار دهی نکردن متغیر مشکل خاصی پیش نیاره و به هیچ وجه فکرشون به اکسپلویت شدن برنامه شون برسه! میتونن به  CVE-2015-0240 باگ Samba که متغییر cred  یک اشاره گرمقدار دهی نشده در تابع _netr_ServerPasswordSet() است، مراجعه کنند و مطالعه ای داشته باشند که نمونه ها زیاد است. نکته قابل ذکر در اکسپلویتینگ که جا داره در اینجا تکرار کنم : هر اکسپلویت از یک باگ سو استفاده میکنه ولی هر باگ قابل اکسپلویت نیست.
چون هدف فقط آشنایی با این باگ هست از یک مثال قدیمی و خوب استفاده میکنیم تا بتونیم بهتر این موضوع رو درک کنیم ( اصل کار مفهوم و نحوه رخ دادن باگ درون برنامه هاست و لینوکس و ویندوزی فرق نداره ).

 

//www.aha.4xmen.ir
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_USER 1024
#define MAX_PASS MAX_USER

#define ERR_CRITIC 0x01
#define ERR_AUTH   0x02

int do_auth(void)
{
	char username[MAX_USER], password[MAX_PASS];

	fprintf(stdout, "Please enter your username: ");
	fgets(username, MAX_USER, stdin);

	fflush(stdin);

	fprintf(stdout, "Please enter your password: ");
	fgets(password, MAX_PASS, stdin);

	printf("username at: 0x%x\n", &username);
	printf("password at 0x%x\n", &password);


#ifdef DEBUG
	fprintf(stderr, "Username is at: 0x%08x (%d)\n", &username, strlen(username));
	fprintf(stderr, "Password is at: 0x%08x (%d)\n", &password, strlen(password));

#endif
	if(!strcmp(username, "user") && !strcmp(password, "washere"))
	{
		return 0;
	}

	return -1;
}

int log_error(int farray, char *msg)
{
	char *err, *mesg;
	char buffer[24];

#ifdef DEBUG
	fprintf(stderr, "Mesg is at: 0x%08x\n", &mesg);
	fprintf(stderr, "Mesg is pointing at: 0x%08x\n", mesg);
#endif
	printf("mesg: 0x%x\n", mesg);
	memset(buffer, 0x00, sizeof(buffer));
	sprintf(buffer, "Error: %s", mesg);

	fprintf(stdout, "%s\n", buffer);
	return 0;
}

int main(void)                   
{
	switch(do_auth())
	{
	case -1:
		log_error(ERR_CRITIC | ERR_AUTH, "Unable to login");
		break;
	default:
		break;
	}
	return 0;
}

برنامه مورد نظرمون رو اجرا میکنیم :

آدرس متغیر ها را در حافظه نشان داده می شوند، تا به اینجا مشکل خاصی نیس. ورودی های برنامه User و Password است که آنها هم بدرستی دریافت می شوند. به تابع زیر از سورس کد برنامه توجه کنید:

int log_error(int farray, char *msg)
{
	char *err, *mesg;
	char buffer[24];

#ifdef DEBUG
	fprintf(stderr, "Mesg is at: 0x%08x\n", &mesg);
	fprintf(stderr, "Mesg is pointing at: 0x%08x\n", mesg);
#endif
	printf("mesg: 0x%x\n", mesg);
	memset(buffer, 0x00, sizeof(buffer));
	sprintf(buffer, "Error: %s", mesg);

	fprintf(stdout, "%s\n", buffer);
	return 0;
}

متغیر mesg از نوع لوکال و مقدار دهی نشده، ولی توسط تابع sprintf مورد استفاده قرار گرفته است. خوب این متغیر چه ربطی به ما دارد؟ فرض کنیم که این متغیر مقدار دهی نشده است، چگونه می تواند باعث ایجاد باگ شود؟
طول رشته ورودی برای User و Password حداکثر ۱۰۲۴ کاراکتر است. حال حداکثر کاراکتر را بعنوان ورودی به برنامه میدهیم و عکس العمل برنامه را نسبت به ورودی ها مشاهده می کنیم:

مقدار mesg با کاراکترهای ورودی ما بازنویسی شده است (همان مقدارهای تصادفی) ! ! !

دوباره برنامه را درون یار همیشه همراه اجرا می کنیم و ورودی ها را حداکثر کاراکتر خواهیم داد. اینجا نحوه ساختار پشته در هنگام Call شدن یک function رو توضیح نمیدم، اگر دوستان نیاز دیدن میتونن از لینک زیر کتاب سرریز بافر رو دانلود و مطالعه کنید:

https://www.exploit-db.com/docs/30179.zip

قبل از اینکه وارد تابع log_error بشویم مقادیر پشته رو نگاهی میاندازیم، میبینیم که متغیرهای User و Pass مقدار دهی شده اند و درون پشته ذخیره گردیده اند (Local Variable).

پشته رو به بالا رشد پیدا میکنه، حال وارد  Call میشویم تا ادامه روند اجرا را بررسی نماییم:

مقدار ESP منهای ۲۰ می شود! یعنی درون پشته Overlap رخ خواهد داد.

۰۰۴۰۱۱۳۰  /$ ۸۳E>SUB ESP,20

 

در ادامه نیز مقدار رجیستر ESI نیز تغییر می کند و آدرس mesg درون آن قرار می گیرد:

۰۰۴۰۱۱۳F  |. 8B7>MOV ESI,DWORD PTR SS:[ESP+4]

خوب میبینیم که استک Overlap شده است و به دلیل اینکه متغیر mesg مقدار دهی نشده است همان مقداری که از قبل (پشته تابع قبلی) درون استک قرار دارد را مدنظر قرار می گیرد و ما می توانیم براحتی مقدار این متغیر را تحت کنترل داشته باشیم. حال روند اجرا را ادامه می دهیم. در تصویر زیر میبینیم که ۲۴ بایت در پشته با مقدار 0x00 پر میشود و این همان متغیر char buffer[24]; است. خوب حال توسط دستور sprintf مقادیر آدرس ESI در آدرس EAX کپی می شود. پس دلیل رخ دادن خطا در برنامه را فهمیدیم که آدرس نا معتبر است.

سوال: اگر مقدار ESI یک آدرس معتبر باشد چه اتفاقی می افتد؟
جواب: مقادیر آن آدرس درون متغیر Buffer کپی می شوند.
سوال: آیا طول مقادیر چک می شود؟
جواب: خیر، طول مقادیر چک نمی شود و شرط پایان کپی نمودن مقادیر در درون Buffer وجود کاراکتر Null است.

از این سوال و جواب به این نتیجه میرسیم که براحتی می توانیم با کپی نمودن داده بیشتر از ۲۴ کاراکتر در درون متغیر Buffer آدرس بازگشت (Return Address) را بازنویسی نمایم.بدین صورت که من بجای مقدار 0xDDDD آدرس بخشی از پشته که مقدار متغیر User است را میدهم و نتیجه را میبینیم :

آدرس ESI رو مقدار  0x0012FA00قرار دادم و چون طول کاراکتر ها بیش از ۲۴ بود آدرس بازگشت بازنویسی شده و مقدار EIP هم 0x41414141 شده است. کمی آدرس های پشته درون تصاویر رو مقایسه کنین میبینین که چه اتفاقی رخ داده است.

موفق باشید.


انتشار

در

, , ,

توسط

برچسب‌ها:

نظرات

4 پاسخ به “چگونه یک متغیر مقداردهی نشده، باگ ایجاد می کند؟”
  1. samira

    سلام آقای باهوش ،وبلاگتون که محشره ، مطالب واضح ، بدون کمو کاستی مطالبتونو صادقانه در اختیار دوستانتون قرار دادید
    نهایت لطف و بزرگی یه انسان با معلومات تو جامعه ای که خیلیا برا انتشار علمشون دنبال کسب درآمدند

    1. AHA

      سلام. مرسی.

      اعتقاد ما براین جمله است:

      الزکات العلم نشرهُ / زکات علم نشر آن است. مولی علی ( ع)

  2. Mahdiam

    فقط میتونم بگم Just Perfect مثل همیشه استاد حمید

    1. AHA

      ممنون، شما خودت استاد مایی 😉

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *