یکی از مشکلات امنیتی که در بعضی از نرم‌افزارها مشاهده می‌شود، سرریزی‌بافر یا Buffer Overflow است. سرریزی بافر عموما در زمان اجرا و بسته به ورودی‌های مختلف برنامه رخ می‌دهد و حتی می‌تواند به هکرها کمک کند تا برنامه را تغییر داده و کدهای مخرب در آن وارد کنند. در این مقاله قصد داریم این موضوع را بررسی کنیم و ببینیم سرریزی بافر چیست و چه‌زمانی اتفاق می‌افتد.

بافر چیست؟

بافر، حافظه موقتی است که به‌صورت نرم‌افزاری و سخت‌افزاری پیاده‌سازی می‌شود، در روش سخت‌افزاری مثل بافر کیبورد، اطلاعات کلیدهایی که شما روی صفحه کلید فشار داده‌اید در جایی ذخیره می‌شود و سیستم‌عامل آنها را می‌خواند و پردازش می‌کند. بافر نرم‌افزاری به دو صورت قابل پیاده‌سازی است.

1 در سطح سیستم‌عامل: سیستم‌عامل مقداری از حافظه را جهت بافرکردن به‌خود‌اختصاص می‌دهد. این حافظه برای یکسان‌سازی سرعت دیسک سخت و پردازشگر استفاده می‌شود.

2 در سطح نرم‌افزار کاربردی: این حافظه را ما به‌عنوان برنامه‌نویس تعریف می‌کنیم تا امور مختلفی را انجام دهیم.

قطعه کد زیر را در نظر بگیرید:‌

void overflow_function (char *str){

    char buffer[10];

    strcpy(buffer, str);

}

int main(){

    char big_string[14];

    strcpy(big_string,"BufferOverflow");

    overflow_function(big_string);

    return 0;

}

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

Stack around the variable ‘buffer’ was corrupted

بسیار خب، این خطا به‌ ما می‌گوید كه متغیر بافر خراب شده است.

بیایید ببینیم این کد دقیقا چه ‌کار می‌کند:

ابتدا یک متغیر از نوع رشته به طول 14کاراکتر تعریف کرده و مقدار BufferOverflow را با استفاده از دستور strcpy در آن کپی می‌کنیم. تا اینجا مشکل خاصی نیست و کد درست است. حال مقدار big_string به تابع overflow_function پاس داده می‌شود. این تابع سعی دارد مقدار big_string که به آن داده شده است را درbuffer  کپی کند اما این عمل ناموفق می‌ماند و در نتیجه برنامه خطا می‌دهد. چرا خطا می‌دهد؟

این ‌تابع می‌خواهد مقدار 14کاراکتر را در یک رشته 10کاراکتری کپی کند، طبیعی است که این کار را نمی‌تواند درست انجام دهد، برای همین خطا صادر می‌شود. کمی دقیق‌تر به مساله نگاه می‌کنیم: وقتی تابع overflow_function فراخوانی می‌شود آدرس برگشت به تابع فراخواننده در ثبات SP قرار می‌گیرد.

سپس این تابع سعی می‌کند مقدار 14کاراکتر را در 10کاراکتر کپی کند و این عمل با موفقیت انجام می‌شود. فرض کنید بافر در 10خانه اول بعد از آدرس 100H قرار داد و ثبات SP مقدار خانه 10DH را به‌عنوان آدرس برگشتی تابع overflow_function در خود دارد. حال مقدار 14کاراکتر در متغیر بافر، کپی می‌شود و در نتیجه خانه‌های 100H تا 10EH بازنویسی می‌شوند و سپس کار تابع به پایان می‌رسد و سیستم‌عامل قصد دارد با استفاده از آدرس ذخیره شده در SP به فراخواننده تابع overflow_function بر گردد اما از آن‌جایی که آدرس 10DH بازنویسی شده است، پردازشگر نمی‌تواند دستوری را اجرا کند و خطای BufferOverflow صادر می‌شود. خب، هکرها با استفاده از همین خطا به سیستم‌های دیگران حمله می‌کنند. آنها یکسری دستورات به زبان اسمبلی می‌نویسند که در اصطلاح به آنها ShellCode یا Exploit گفته می‌شود.

به‌مثال بالا برمی‌گردیم، فرض کنید به‌جای مقدار “BufferOverflow” یک شل کد به تابع overflow_function داده شود. وقتی کار تابع تمام شد، پردازشگر به آدرس 10DH می‌رود. مقدار این آدرس دیگر یک مقدار نامعتبر نیست بلکه به یک قطعه کد اشاره دارد و پردازشگر، آن قطعه کد را اجرا می‌کند و سبب می‌شود برنامه اصلی، کار خودش را درست انجام ندهد‌. این یک روش برای سوءاستفاده از سرریزی است. روش دیگر دسترسی به بخش‌های محرمانه حافظه است که اطلاعات اساسی سیستم در آن قرار دارد. هکر با تزریق کد خود به برنامه می‌تواند به بخش‌های محرمانه حافظه دسترسی پیدا کند و اطلاعات حیاتی سیستم را مورد سو‌ءاستفاده قرار دهد.

چگونه جلوی سرریزی را بگیریم؟

تنها کاری كه لازم است انجام دهیم، بررسی مقادیر ورودی برنامه است تا دقیقا مطابق با اندازه متغیر داده‌ها درون آن ریخته شوند. به‌طور مثال؛ کد بالا را با تغییر کوچکی اصلاح می‌کنیم و از سرریزی بافر جلوگیری می‌کنیم. کد بازنویسی‌شده به‌صورت زیر خواهد بود:

void overflow_function (char *str){

    char buffer[10];

    strncpy(buffer, str,10);

}

int main(){

    char big_string[14];

    strncpy(big_string,"BufferOverflow",14);

    overflow_function(big_string);

    return 0;

}