بخشی از مقاله


حمله های سرریز بافر

چکیده

سرریز بافر یک باگ است که بروی کد سطح پایینی که بطور معمول توسط C و C++ نوشته شده را با پیامد های قابل توجه اثر می گذارد . به طور معمول یک برنامه با این باگ به سادگی Crash می شود ، اما مهاجم می تواند وضعیت را به گونه ای تغییر دهد که برنامه بسیار بدتر انجام شود مطالعه. سرریز بافر به چند دلیل نیاز است . اولاً C و C++ هنوز هم برای نوشتن نرم افزار های زیادی استفاده می شوند و این نرم افزار ها اغلب آسیب پذیری سرریز بافر را دارند . وماًد در طول تاریخ طولانی مهاجم و مدافع یک بازی موش و گربه را انجام داده اند . مدافعان یک نقطه ضعف ایجاد می کنند و مهاجمان برای کار اطراف آن پیدا می شوند .

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

کلمات کلیدی

حمله ، حافظه ، فراخوانی تابع ، بافر ، پشته ، هیپ .

-1 مقدمه

زمانی که یکی از پنج سـرویس امنیتـی محرمـانگی ، احـراز هویت ، سلامت داده ها ، کنترل دسترسی و یا در دسترس بودن نقـض شود می گوییم به سیستم حمله شده است. حمله هجـومی بـر امنیـت سیستم است که از یک تهدید هوشمند سرچشمه میگیرد. یعنی عملی هوشمندانه است که تلاشی زیرکانه برای حمله به سرویسهای امنیتی و نقض سیاستهای امنیتی سیستم دارد. معمولا شبکه هـای کـامپیوتری در معرض چهار نوع حمله قرار دارند. وقفه که بدین معناست که حمله کننده باعث شود بخشی از شبکه یا سرویس دهنده ای مختـل شـود و مبادله اطلاعات و سرویس دهی امکان پذیر نباشد . به این نـوع حملـه ،» حمله DoS یا اختلال در سـرویس دهـی « نیـز گفتـه مـی شـود. استراق سمع : بدین معنا که حمله کننده به نحوی توانسـته اطلاعـات در حال تبادل روی شبکه را گوش داده و از آن سـوء اسـتفاده نمایـد .
دستکاری داده ها یعنی حمله کننده توانسته به نحـوی اطلاعـاتی کـه روی شبکه مبادله می شود را تغییر دهد . به عبارت دیگر داده هایی که در مقصد دریافت می شود متفاوت با آن چیزی باشد که از مبدأ ارسال شده است . افزودن اطلاعات یعنی حملـه کننـده اطلاعـاتی را کـه در حال تبادل روی شبکه است تغییر نمی دهد بلکه اطلاعـات دیگـری را که می تواند مخرب یا بنیانگذار حمـلات بعـدی باشـند ، بـه اطلاعـات اضافه می نماید [8].

ما امنیت سطح پـایین نـرم افـزار را ، کـه یـک نگرانـی بـرای سیستم های نوشته شده به زبان های برنامه نویسی C و C++ است را در نظر می گیریم . با توصیف حمله بدنام سرریز بافر از نوع DoS ، کـه بخصوص نرم افزار های سطح پایین در معرض آن هسـتند شـروع مـی کنیم . سرریز بافر یک باگ است که بروی کد سطح پـایینی کـه بطـور معمول توسط C و C++ نوشته شده را با پیامد های قابـل توجـه اثـر می گذارد . به طور معمول یک برنامه با این بـاگ بـه سـادگی Crash می شود ، اما مهاجم می تواند وضعیت را به گونـه ای تغییـر دهـد کـه برنامه بسیار بدتر انجام شـود . بـه مهـاجم اجـازه مـی دهـد اطلاعـات خصوصی را سرقت یا فاسد کند و حتی می تواند کدی به اختیار خـود اجراء کند . مطالعه سرریز بافر به چند دلیل نیاز اسـت . اولاً C و C++ هنوز هم برای نوشتن نرم افزار های زیادی استفاده می شوند و این نرم افزار ها اغلب آسیب پذیری سرریز بافر را دارند . دومـاً در طـول تـاریخ

طولانی مهاجم و مدافع یک بازی موش و گربه را انجام داده انـد . مـدا فعان یک نقطه ضعف ایجاد می کنند و مهاجمان برای کـار اطـراف آن پیدا می شوند .

25 ســال پــیش، در روز 2 نــوامبر 1988 ، بخــش عمــدهای از اینترنت - که هنوز چنـدان بـزرگ نبـود - از کـار افتـاد. ایـن مسـأله نتیجه ی آزمایش خودخواهانه ای بود که توسـط یـک دانشـجو بـه نـام رابرت موریس انجام شده بود .در آن زمان اینترنت هنوز در ابتـدای راه خود بود و اغلب، مهندسان و دانشجویان به آن متصل بودند . استفاده از اینترنت، مبحثی دانشگاهی بود و هنوز فعالیتی در زمینـه ی امنیـت آن صورت نگرفته بود. رابرت موریس، که در آن زمـان دانشـجوی دانشـگاه کرنل بود، با ایجاد اولین رخداد بدافزاری مهم تاریخ قصد »حملـه« بـه دیگر رایانه ها را نداشت. اما آنچه با نام کرم موریس شناخته شـد، همـه چیز را در دنیای اینترنـت تغییـر داد. ایـن کـرم، »محتـوای مخربـی« نداشــت. تنهــا هــدف آن، تکثیــر خــود بــود. مــوریس بــرای اجــرای موفقیت آمیز بدافزار خود بر روی رایانه های دیگران از روش های نوین - نسبت به زمان خود- برای پنهان ساختن و سـرریز بـافر پشـته بـرای اجرای آن استفاده کرده بود.

در مورد اینکه چه تعداد رایانه به کرم مـوریس آلـوده شـدند هنـوز توافقی وجود ندارد. عدد حدس زده شده در مورد سامانه های آلوده بـه این بدافزار در اینترنت از 6000 تا 60000 متغیـر اسـت. پـل گراهـام، دوست و همکار موریس، می نویسد که این اعداد تنهـا حدسـیات افـراد مختلف است. مسأله اینجا است که تنها راه خلاصی از بدافزار مـوریس، راه اندازی مجدد رایانه ها بوده و این کار تمام ردپاهای به جا مانده از آن را از بین می برد. در آن زمان کسی اطلاع نداشت چه تعداد میزبـان بـه این بدافزار آلوده شده و چه تعداد رایانه تحت تأثیر آن قرار گرفتهاند .

تنها کافی است که بگوییم همه در اینترنـت از ایـن بـدافزار اطـلاع داشتند، اما اینترنت در آن زمان چندان بزرگ نبود . موریس که اکنـون در دانشگاه MIT به تدریس و پژوهش در زمینهی سـامانههـای عامـل موازی و توزیعشده (PDOS) مشغول است، اولین فردی بود که تحـت قانون تازه تصویب شده ی سوءاستفاده و کلاهبرداری رایانه ای، بـه سـه سال آزادی مشروط و پرداخت جریمه نقدی محکوم شد. دادگاه تجدید نظر نیز به این نتیجه رسید که قصد نداشتن وی برای ایجـاد خسـارت، اهمیتی نداشته و تنها مسأله ی مهم قصد وی برای دسترسی بـه دیگـر رایانه ها، بدون اجازه ی مالکان آن ها اسـت. کـد منبـع بـدافزار مـوریس درون یک فلاپی دیسک در موزهی علوم بوستون نگهداری میشود .

کرم CodeRed در 15 جولای 2001 بر روی اینترنت مشاهده شد
. این کرم آسیب پذیری وب سرور MS-IIS را مورد استفاده قرار داد و برای رسیدن به اهداف خود حجم گسـترده اطلاعـات بـیش از تـوان پردازشی سیستم عامل را به رایانه ارسال می کرد تا ظرف چنـد دقیقـه دستگاه از کار بیفتد و حافظه رایانه به طور کامل از مدار فعالیت خـارج شود. این حمله در 14 ساعت 300000 هزار سیستم را آلود کرد .

در25 ژانویــه 2003 یــک کــرم جدیــد بــرای ســرورهای اینترنتــی شناسایی شد که از سرریز بافر در سرور MS-SQL استفاده می کرد و فقط در 10 دقیقه 75000 سیستم را آلوده کرد .

امروزه نیز حملات سرریز بافر بسیار خطرناک هستتند . نمودار شکل 1 از نتیجه مطالعات اخیر انجام شده توسط مجله IEEE spectrum که به پروژه های منبع باز فعال و جدید میزبان در سایت ها شبیه GitHub نگاه کرده اند بدست آمده است . این رتبه بندی نشان می دهد که زبان های C و C++ دو تا از سه زبانی هستند که امروز بیشترین استفاده از آنها می شود . نرم افزار های زیادی از جمله هسته های سیستم عامل ، سرور های با کارایی بالا (مثل سرور های وب و سرور های پایگاه داده )، سیستم های تعبیه شده مثلاً( در ماشین و هواپیما ) ، سیستم ها کنترل صنعتی و حتی MarsRover توسط زبان های C و C++ نوشته شده اند . بنابراین ، از آنجایی که این سیستم ها اهمیت بحرانی دارند ، هر خصوصیت آسیب پذیر از این زبان ها (مثلا سرریز بافر ) و حمله موفقیت به آن خصوصیت می تواند پیامد های عظیمی به همراه داشته باشد.

در روز هفتم آوریل 2014، نیل مهتا یکی از مهندسین تیم امنیتی گوگل اعلام کرد که OpenSSL ویرایش beta_1.0.2 ، و همچنین تمام ویرایشهای سری 1.0.1 این نرمافزار تا قبل از ویرایش 1.0.1g دارای اشکال امنیتی خطرناکی مرتبط با مدیریت حافظه در ماژول Heartbeats این برنامه هستند.این مشکل امنیتی میتواند در هر تپش اکستنشن OpenSSL Heartbeat ، تا 64 کیلو بایت اطلاعات از حافظه مربوط به همین نرمافزار در RAM رایانه را در اختیار مهاجم قرار دهد. این باگ درون ماژول Heartbeats از پکیج OpenSSL قرار دارد که به همین دلیل این باگ را خونریزی قلبی (Heartbleed)نام گذاری کردهاند .

عمده ی سایت هایی که بطور روزمره با آنها سر و کار داشته و از https برای دسترسی امن و تبـادل اطلاعـات رمزگـذاری شـده استفاده می کنند (مانند سـرویس هـای ایمیـل، چـت، بانکـداری اینترنتی، خرید آنلاین، شبکه های اجتماعی و ...) و همچنین عمده ی سرویس های VPN که در اصل به منظور کد کردن اطلاعات و ایجاد امنیت ارتباطی برای حذف امکان شنود/سـرقت اطلاعـات در مسیر ارتباطی کاربر و سرویس دهنده مورد استفاده قرار میگیرنـد، به سادگی کلیدهای کدگذاری و رمزکردن اطلاعات تبادلی کاربران خود را به همراه کلیه اطلاعات شخصی کاربرها از جملـه رمزهـای

عبور، اطلاعات حساب ها و کارت های بانکی و … در اختیار هکـر ها قرار می دهد.


شکل1 .رتبه بندی 2014 زبان های برنامه نویسی توسط مجله IEEE spectrum[9]

-2 لایه بندی حافظه

پیش از آنکه به چگونگی کار سر ریز بافر کـه از نـوع حمـلات DoS است بپردازیم ،در ایـن قسـمت مـروری بـر بعضـی از جزئیـات چگونگی اجرای یک برنامه روی کامپیوتر های مدرن انجام می دهـیم. یک برنامه زمانی که شروع به اجرا می کند پروسس نامیده مـی شـود. اگر یک برنامه کامپایل و اجرا شود، توسط سیستم عامل به آن حافظـه به منظور اجرا داده می شود . بحث ما بر روی سیستم عامـل لینـوکس است که روی پردازنده intel86 ، 32 یا 64 بیت اجـرا مـی شـود . در حالی که جزئیات برای سیستم عامـل هـای مختلـف متفـاوت اسـت ، مفاهیمی که در نظر گرفته ایم بسیار مشابه اند . شکل 2 چگونگی قرار گرفتن قسمت های مختلف برنامه شامل کد برنامه، ثابت ها و داده ها در قسمت های مختلفی از حافظه را نشان می دهد .


شکل .2 ساختار حافظه ی اختصاص داده شده به یک فایل اجرایی[2]

در شـــکل 2 آدرس 0 پـــایین تـــرین آدرس و در بـــالا آدرس 4GByte بالاترین آدرس بر روی یک سیستم 32 بیتـی اسـت . دیـد پروسس از حافظه این است که همه ی آن در اختیارش قـرار دارد . در واقع این آدرس مجازی است که سیسـتم عامـل و پردازنـده بـه آدرس فیزیکی برای حافظه بر روی ماشین نگاشت کرده اند .
اولین قسمت مربوط به متن است که کد برنامه در ایـن قسـمت قرار می گیرد و حاوی تمام آن الگوهای عجیب بایتی است کـه فقـط برای CPU معنی دارد . این قسمت از حافظه فقط خواندنی است کـه این قابلیت را فراهم می کند که بین تمام کاربرانی که این فایل اجرایی را اجرا می کنند به اشتراک گذاشته شـود .بنـابراین، قسـمت کـد در حافظه قطعا مورد هدف سرریزی بافر نیست .هر تلاشی برای نوشـتن در این قسمت منجر به رخ دادن خطای تجاوز دسترسی به حافظـه و خاتمه اجرای برنامه می شود.

بالای قسمت متن قسمت داده قرار دارد کـه بـه آن هـم بـه دو دسته تقسیم می شود . ناحیه اول داده مقدار دهـی اولیـه شـده مثـل ثابت ها و ناحیه دوم ناحیه متغیر های سراسری هستند که مقدار دهی اولیه نشده اند . قسمت متن و داده در زمان کامپایل مشخص می شوند

. و از آنجا که متغیر ها حاوی کدهای قابل اجرا نمی باشند، این قسمت های حافظه قابل اجرا نیستند. یعنی هدایت اشاره گر دستورالعمل هـر فایل اجرایی به هر وسیله ای به سمت آن ها، منجر به خاتمه زودهنگام اجرای برنامه می شود.

در بالای فضای آدرس آرگومـان خـط دسـتور و متغییـر هـای محلی و جود دارند و زمانی مستقر می شوند که پروسس شروع به اجرا کند . دو قسمت پشته و هیپ کـه هـدف حملـه سـرریز بـافر هستند در زیر آن ها قرار دارند که در زیر بررسی می کنیم .[2]

-1-2 هیپ

هیپ قسمتی از حافظه است که می تـوانیم در صـورت نیـاز در حین اجرای یک فایل اجرایی، از این قسمت به برنامه حافظه به صورت پویا اختصاص دهیم. یک برنامه نویس فقط کافی است که بگویید: "من احتیاج به 5000 بایت حافظه دارم" و این مقدار حافظه توسط سیستم عامل برایش فراهم می شود. این قابلیت هنگامی خیلی مفید است کـه نمی توانیم پیش بینی کنیم واقعا به چه مقداری حافظه احتیاج داریـم و مقدار مورد نیاز حافظـه وابسـته بـه داده هـای ورودی اسـت. ایـن تخصیص با استفاده از یک تابع سیستمی به نام malloc() انجـام مـی شود . در مقابل malloc() که به برنامه حافظـه اختصـاص مـی دهـد ، تابع free() قرار دارد که حافظه گرفته شده را به سیسـتم عامـل پـس می دهد.

برای فهمیدن دقیق تر ساختار هیپ بهتر است کمی به مفهـوم اشار ه گر در زبان C را در نظر بگیریم . اشاره گر متغیـری اسـت کـه یک مقدار واقعی را در خود ندارد، بلکه حاوی آدرس یـک حافظـه ی دیگر است . نکته ی جالب تابع malloc() در این است کـه ایـن تـابع کوچکترین آدرس ممکن را در حافظه ی اختصاص داده شده بـه مـا را برمی گرداند. متغیرهایی که آدرس حافظه را در خود نگه می دارند از نوع اشاره گر هستند .بنـابراین، آدرسـی کـه توسـط تـابع malloc() برگردانده می شود در چنین متغیری نگه داشته می شود .این موضوع از یک طرف بسیار مفید است اما از جهاتی دیگر منبع ثابت بسیاری از مشکلات در برنامه های C بوده است .به طور خاص، اگر اشاره گر هـا به طور مناسبی در برنامه مقدار اولیه نگیرند، عملیاتی کـه روی آ نهـا انجام می شود، منجر به خروجی اشتباه می شود.[1]


my_func(param1,param2,param3);
فرض کنید تابع my_func() اجرا می شود و تابع از متغیرهـای محلــی var1,var2 اســتفاده مــی کنــد و بــا مقــادیر مشخصــی بــرای

پارامترهایش فراخوانی می شود، قبـل از فراخـوانی تـابع وضعیت پشته به صورت نشان داده شده در شکل 3 است.


-2-2 پشته
نقطه ی مقابل متغیر های سراسری، متغیرهای محلی هسـتند که تنهـا در یـک تـابع اسـتفاده مـی شـوند و بنـابراین تنهـا توسـط دستورالعمل های همان تابع قابل دسترسی مـی باشـند . متغیرهـای محلی در محلی از حافظه به نام پشته قرار می گیرند و اینکار دسترسی و کار با آ نها را راحت می کند.
نام های دیگری که برای پشته به کار می روند عبارتند از لیست هـای LIFO و لیسـت هـای Push-Down ایـن اصـطلاحات یکـی از کارکردهای اساسی پشته را توصیف می کنند . در پشته اولـین ورودی آخرین خروجی و آخرین ورودی اولین خروجی است . ما فقط به عنصر بالای پشته دسترسی دارید و چنان چه در برخـی مـوارد احتیـاج بـه عنصر زیـر آن داشـته باشـیم، بایـد عنصـر بـالای پشـته را بـرداریم . همچنین، فقط می توانیم به بالای پشته عنصر اضـافه کنـیم .ایـن دو عملیات را به تر تیب Pop و Push می نامند . برای این کار Pop بـه عنوان آرگومان ، یک آدرس را برای قراردادن عنصـر بازیـابی شـده از بالای پشته دریافت می کند و Push مقداری که بایـد بـر روی پشـته قرار بگیرد را دریافت می کند.

در این گزارش فرض می شود که پشته در انتهـای حافظـه ای که توسط برنامه قابل دسترس است ، قرار دارد (شکل (2 و به سمت پایین رشد می کند . بنابراین ، عنصـر بـالای پشـته دارای کـوچکترین آدرس است . این عنصر top نامیده می شود . CPU یک ثبـات خـاص برای نگهداری آن آدرس بالاترین عنصر پشته دارد و هنگامی که پشته تغییر می کند، این ثبات به روز می شود. این ثبات اشاره گر پشته یـا آدرس بالاترین عنصر پشته را دارد و هنگامی که پشته تغییر می کنـد، این ثبات به روز می شود. این ثبات اشاره گر پشته یـا SP نامیـده مـی شود . در واقع، علاوه بر متغیر های محلی چیزهای دیگری هـم روی پشته وجود دارند که در بخش سرریزی بافر مبتنی بر پشـته آن هـا را توضیح خواهیم داد .[1]

پشته و فراخوانی تابع ها

برای فهمیدن شیوه های اصلی سرریز بافر مبتنی بـر پشـته باید بدانیم که وقتی یک تابع توسط تابعی دیگر صـدا زده مـی شـود، دقیقا چه فرآیندی رخ می دهد .


شکل .3 وضعیت پشته قبل از فراخوانی تابع[1]

طبق شکل 3 ثبـات SP ،حـاوی آدرس Y کـه بـالاترین عنصـر پشته است ، می باشد . ثبات (%eip) IP ، آدرس Z کـه کـد ماشـین دستورالعمل بعدی است را نگهداری می کند . در این مثال ، Z حـاوی آدرس دستورالعملی است که برای اولین بار my_func() را فراخـوانی می کند. پشته علاوه بر متغیرهای محلی، در حقیقت حـاوی اطلاعـات دیگری نیز می باشد. در واقع تمام فضای یـک تـابع روی پشـته Push می شود . این فضا شامل پارامتر ها، متغیرهای محلی و برخی اطلاعات دیگر است که در ادامه آنها را توضیح می دهیم. به تمام قسمتی که بـه یک تابع در پشته اختصاص داده می شـود، فـریم پشـته ی تـابع مـی گویند. حال پردازنده احتیاج به وسیله ای دارد که به کمـک آن بتوانـد هم متغیرهای محلی و هم پارامترها را آدرس دهی کند . توضیح اضافه این که برنامه ها متغیرها را به صورت منطقی آدرس دهی مـی کننـد . پردازنده برای تبدیل این آدرس ها بـه آدرس هـای فیزیکـی (واقعـی) احتیاج به سازوکار خاصی دارد . یک روش هوشمندانه این است که در داخل هر فریم پشته محل ثـابتی را بشناسـیم . ایـن نقطـه ی ارجـاع، محتوای ثبات (%ebp) FP است که به آن اشاره گر فریم می گویند. در واقع در زمان اجرای برنامه، آدرس اختصاص داده شده به متغیـر در برنامه با FP جمع می شود و بدین ترتیب آدرس فیزیکی متغیـر بـرای پردازش داده ها توسط پردازنده به دست می آید.
حال به مثال فراخوانی تابع my_func() برگردیم. اولـین کـاری که باید انجام دهیم ارسال آرگومـان هـای واقعـی بـه عنـوان مقـادیر param1,param2,param3 به تابع است. مشخصا این کـار وظیفـه ی تابعی است که my_func() را فراخوانی می کنـد. بـه طـور قـراردادی پارامتر ها با ترتیب عکس به تابع ارسال می شـوند، بنـابراین ارسـال از param3 شروع و سـپس param2 و بعـد از آن param1 ارسـال مـی شود .

مساله ی دیگری که با آن روبرو هستیم ایـن اسـت کـه پـس از اتمام کار تابع my_func() تمایل دارد اجرای برنامه را از ادامه ی تابع قبلــی از ســر بگیــریم. بــرای انجــام دادن ایــن کــار آدرس Z+1 ، دستورالعمل بعد از کد فراخوانی تابع در تـابع فراخواننـده را در پشـته Push مــی کنــیم . شــکل 4 وضــعیت پشــته را پــس از Push کــردن پارامترها و آدرس برگشت نمایش می دهد .توجه داشته باشید که این شکل در ابعاد واقعی نمی باشد و در واقع اندازه ی مورد نیاز بـرای هـر پارامتر، بسته به نوع پارامتر با بقیه فرق دارد.

آدرس U، آدرس اولین دستورالعمل تابع my_func() است کـه در ثبات IP کپی می شود . بلاخره نوبت به تابع فراخوانی شده رسـید . البته قبل از اینکه تابع کارش را شروع کند باید از مـوارد زیـر مطمـئن باشد:

در متن اصلی مقاله به هم ریختگی وجود ندارد. برای مطالعه بیشتر مقاله آن را خریداری کنید