পাঠ ৩২ · ৬০-এর মধ্যে · মডিউল ৬
Home / Courses / Cybersecurity & Ethical Hacking / বাফার ওভারফ্লো

বাফার ওভারফ্লো পরিচিতি

Buffer overflow introduction
৯ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • বাফার ওভারফ্লো আসলে কী এবং কেন এটি ঘটে
  • কীভাবে এটি C/C++-এ রিটার্ন অ্যাড্রেস ওভাররাইট করে কোড এক্সিকিউশন পর্যন্ত নিতে পারে (ধারণাগত স্তরে)
  • Python-এর মতো memory-safe ভাষা কীভাবে এই সমস্যা ডিজাইন দিয়ে সমাধান করে
  • stack canary, ASLR, DEP/NX-এর মতো আধুনিক মিটিগেশন কী এবং কীভাবে কাজ করে

১ · বাফার আসলে কী, এবং ওভারফ্লো কীভাবে ঘটে

একটি বাফার (Buffer)Bufferমেমরির একটি ফিক্সড-সাইজ ব্লক, নির্দিষ্ট পরিমাণ ডেটা রাখার জন্য প্রোগ্রাম দ্বারা সংরক্ষিত। মেমরির একটি ফিক্সড-সাইজ ব্লক — যেমন C-তে char buffer[8] লিখলে ঠিক ৮ বাইট সংরক্ষিত হয়। যদি প্রোগ্রামটি সেই বাফারে ২০ বাইট লেখার চেষ্টা করে কোনো দৈর্ঘ্য যাচাই ছাড়াই, তাহলে অতিরিক্ত ১২ বাইট পাশের মেমরিতে "উপচে পড়ে" — এটিই বাফার ওভারফ্লো (Buffer Overflow)Buffer Overflowএকটি ফিক্সড-সাইজ বাফারে তার ধারণক্ষমতার চেয়ে বেশি ডেটা লেখা হলে যে মেমরি-করাপশন ঘটে।।

কেন C/C++-এ এটা সম্ভব

C ও C++ প্রোগ্রামারকে নিজে দৈর্ঘ্য যাচাই করার দায়িত্ব দেয় — ভাষাটি নিজে থেকে কোনো রানটাইম bounds-check করে না। যদি প্রোগ্রামার সেই চেক লিখতে ভুলে যায় (একটি অত্যন্ত সাধারণ ভুল), তাহলে ওভারফ্লো নিঃশব্দে ঘটে যায় — কোনো এরর বা ক্র্যাশ ছাড়াই, যতক্ষণ না এটি অন্য কোনো গুরুত্বপূর্ণ ডেটা নষ্ট করে।

২ · কীভাবে এটি কোড এক্সিকিউশন পর্যন্ত যেতে পারে (ধারণাগত)

একটি ফাংশন কল হলে, প্রোগ্রামের স্ট্যাক (Stack)Stackমেমরির একটি অঞ্চল যেখানে ফাংশন কল হওয়ার সময় লোকাল ভেরিয়েবল এবং ফাংশনটি শেষে কোথায় ফিরে যাবে তার ঠিকানা (return address) সংরক্ষিত থাকে। -এ লোকাল ভেরিয়েবল (যেমন আমাদের বাফার) এবং তার ঠিক পরেই রিটার্ন অ্যাড্রেস — অর্থাৎ ফাংশনটি শেষ হওয়ার পর প্রোগ্রামের কোথায় ফিরে যাওয়া উচিত তার ঠিকানা — সংরক্ষিত থাকে। যদি বাফার ওভারফ্লো যথেষ্ট বড় হয়, তাহলে এটি এই রিটার্ন অ্যাড্রেসকেও ওভাররাইট করে দিতে পারে। ফাংশনটি রিটার্ন করার সময়, CPU বৈধ কলারের বদলে আক্রমণকারীর নির্ধারিত ঠিকানায় জাম্প করে — এভাবেই একটি মেমরি-করাপশন বাগ সম্পূর্ণ কোড এক্সিকিউশন পর্যন্ত পৌঁছাতে পারে।

বাফারে ডেটা লেখা Writing to buffer দৈর্ঘ্য যাচাই হলো = নিরাপদ, বাফারের মধ্যেই দৈর্ঘ্য যাচাই নেই = return address করাপ্ট
একই ওভারসাইজড ইনপুট — শুধু bounds-check-এর উপস্থিতিই ঠিক করে দেয় প্রোগ্রাম নিরাপদ থাকবে নাকি করাপ্ট হবে।
এই পাঠে কোনো বাস্তব মেমরি-করাপশন এক্সপ্লয়েট কোড দেখানো হচ্ছে না — বাফার ওভারফ্লো একটি নিম্ন-স্তরের/নেটিভ-কোড কনসেপ্ট, এবং এখানে ধারণা বোঝার স্তরেই সীমাবদ্ধ থাকা হচ্ছে। একটি বাস্তব সিস্টেমে অনুমোদন ছাড়া এই ধরনের কৌশল প্রয়োগ করা ফৌজদারি অপরাধ (L01 দেখুন) — অনুশীলনের জন্য শুধুমাত্র নিজের ল্যাব বা অনুমোদিত CTF প্ল্যাটফর্ম ব্যবহার করুন।

৩ · Memory-safe ভাষা কীভাবে এই পুরো বাগ ক্লাস দূর করে

Python, Java, Rust ও Go প্রতিটি অ্যারে/লিস্ট/স্ট্রিং অ্যাক্সেসের সময় স্বয়ংক্রিয়ভাবে bounds-checking করে। সীমার বাইরে অ্যাক্সেস করার চেষ্টা করলে মেমরি নিঃশব্দে করাপ্ট হয় না — বরং একটি এক্সেপশন (Python-এ IndexError) তোলা হয়। এই ডিজাইন সিদ্ধান্তই ক্লাসিক বাফার ওভারফ্লো বাগকে সম্পূর্ণভাবে অসম্ভব করে তোলে এই ভাষাগুলোতে।

Python / Java
প্রতিটি লিস্ট/অ্যারে অ্যাক্সেসে রানটাইম bounds-check — সীমার বাইরে গেলে এক্সেপশন।
Rust
ওনারশিপ/বরো-চেকার সিস্টেমের মাধ্যমে কম্পাইল-টাইমেই অনেক মেমরি-সেফটি বাগ ধরে ফেলে, গার্বেজ কালেক্টর ছাড়াই।
Go
স্লাইস/অ্যারে অ্যাক্সেসে বিল্ট-ইন bounds-checking, C-এর মতো raw পয়েন্টার অ্যারিথমেটিক নেই।

৪ · আধুনিক OS/কম্পাইলার মিটিগেশন (মেনশন-স্তরে)

C/C++ কোডে বাফার ওভারফ্লো এখনও সম্ভব হলেও, আধুনিক সিস্টেম একাধিক স্তরের মিটিগেশন যোগ করেছে যা এক্সপ্লয়েট করা কঠিন করে তোলে:

  • Stack canary — রিটার্ন অ্যাড্রেসের ঠিক আগে একটি র‍্যান্ডম মান বসানো হয়; ফাংশন রিটার্ন করার আগে সেই মান পরীক্ষা করা হয়। ওভারফ্লো এই canary-কেও ওভাররাইট করলে, প্রোগ্রাম রিটার্ন অ্যাড্রেস ব্যবহার করার আগেই বন্ধ হয়ে যায়।
  • ASLR (Address Space Layout Randomization) — প্রতিবার প্রোগ্রাম চালু হওয়ার সময় মেমরি অ্যাড্রেস র‍্যান্ডমাইজ করে, ফলে আক্রমণকারী জানে না ঠিক কোথায় জাম্প করতে হবে।
  • DEP/NX (Data Execution Prevention / No-eXecute) — স্ট্যাকের মতো মেমরি অঞ্চলকে non-executable চিহ্নিত করে — আক্রমণকারী কোড সেখানে বসাতে পারলেও, CPU সেটি চালাতে অস্বীকার করে।
এই মিটিগেশনগুলো ঝুঁকি কমায়, দূর করে না

stack canary, ASLR ও DEP/NX — প্রতিটিই একটি অতিরিক্ত প্রতিরক্ষা স্তর (defense-in-depth), কোনোটিই একক সমাধান নয়। প্রকৃত সমাধান হলো memory-safe ভাষা ব্যবহার করা, অথবা C/C++-এ bounds-checked ফাংশন (যেমন strncpy) সচেতনভাবে ব্যবহার করা এবং প্রতিটি বাফার লেখার আগে দৈর্ঘ্য যাচাই করা।

৫ · Python-এ একটি নিরাপদ সিমুলেশন

নিচের কোড সেলটি সম্পূর্ণ নিরাপদ — এটি একটি ফিক্সড-সাইজ Python লিস্টকে "বাফার" হিসেবে ব্যবহার করে, দেখাচ্ছে দৈর্ঘ্য যাচাই থাকলে কী হয় বনাম না থাকলে কী হতো। কোনো বাস্তব মেমরি কখনো স্পর্শ করা হচ্ছে না — Python-এ এটি আসলে সম্ভবই নয়।

Python
BUFFER_SIZE = 8

def write_to_buffer_safe(buffer, input_data):
    """সঠিক পদ্ধতি: লেখার আগে দৈর্ঘ্য যাচাই করে।"""
    if len(input_data) > len(buffer):
        raise ValueError(
            f"প্রত্যাখ্যাত: {len(input_data)} বাইট ডেটা {len(buffer)}-বাইট বাফারে ধরবে না"
        )
    for i in range(len(input_data)):
        buffer[i] = input_data[i]
    return buffer

def unsafe_bounds_check_missing(buffer, input_data):
    """C-স্টাইল বাগ সিমুলেশন: length check অনুপস্থিত (আসল মেমরি স্পর্শ করা হচ্ছে না)।"""
    check_performed = len(input_data) <= len(buffer)
    return check_performed

buffer = [0] * BUFFER_SIZE
safe_input = list(range(1, 6))          # ৫ বাইট — বাফারে ধরবে
oversized_input = list(range(1, 15))    # ১৪ বাইট — ৮-বাইট বাফারে ধরবে না

print("বাফারের সাইজ:", BUFFER_SIZE)
print()

try:
    result = write_to_buffer_safe(buffer, safe_input)
    print("নিরাপদ ইনপুট গৃহীত:", result)
except ValueError as e:
    print("প্রত্যাখ্যাত:", e)

print()

try:
    result = write_to_buffer_safe(buffer, oversized_input)
    print("ওভারসাইজড ইনপুট গৃহীত:", result)
except ValueError as e:
    print("নিরাপদ সংস্করণ প্রত্যাখ্যান করল:", e)

print()
check = unsafe_bounds_check_missing(buffer, oversized_input)
print("আনসেফ (C-স্টাইল) সংস্করণে bounds-check পাস হতো?", check)
print("(বাস্তব C কোডে check অনুপস্থিত থাকলে, এই ১৪ বাইট নিঃশব্দে বাফারের বাইরে লেখা হতো)")

    
মূল কথা · Key takeaway

বাফার ওভারফ্লো একটি "ভাষা-নির্দিষ্ট" বাগ ক্লাস — এটি C/C++-এর মতো ম্যানুয়াল-মেমরি-ম্যানেজমেন্ট ভাষার একটি মৌলিক ডিজাইন ট্রেড-অফের ফলাফল, কোনো নির্দিষ্ট প্রোগ্রামারের ভুলের চেয়ে ভাষাটির নিজের ডিজাইনের একটি সুপরিচিত ঝুঁকি। memory-safe ভাষা ব্যবহার করা যেখানে সম্ভব, এবং legacy C/C++ কোডে কঠোর bounds-checking অনুশীলন করা — এই দুটোই বাস্তব-জগতের প্রতিরক্ষার মূল ভিত্তি।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ কেন Python-এ এই ক্লাসিক অর্থে বাফার ওভারফ্লো সম্ভব নয়?

Python প্রতিটি লিস্ট/স্ট্রিং অ্যাক্সেসে স্বয়ংক্রিয়ভাবে bounds-check করে। সীমার বাইরে অ্যাক্সেস বা লেখার চেষ্টা করলে মেমরি নিঃশব্দে করাপ্ট না হয়ে একটি IndexError এক্সেপশন তোলা হয়। সাধারণ Python কোডে raw মেমরি অ্যাক্সেসের কোনো উপায়ই নেই, তাই এই পুরো বাগ ক্লাসটি ডিজাইন দিয়েই বাদ পড়ে যায়।

প্র ০২ Stack canary কীভাবে কাজ করে, এবং কেন এটি ওভারফ্লোকে "প্রতিরোধ" নয় বরং "সনাক্ত" করে?

Canary হলো একটি র‍্যান্ডম মান যা রিটার্ন অ্যাড্রেসের ঠিক আগে বসানো হয়। রিটার্ন অ্যাড্রেস পর্যন্ত পৌঁছাতে একটি ওভারফ্লোকে অবশ্যই canary-র উপর দিয়ে লিখতে হবে। ফাংশন রিটার্ন করার ঠিক আগে canary-র মান পরীক্ষা করা হয় — অমিল পাওয়া গেলে প্রোগ্রাম অবিলম্বে বন্ধ হয়ে যায়। লক্ষ্য করুন এটি ওভারফ্লো লেখা হওয়া থামায় না, বরং করাপ্ট রিটার্ন অ্যাড্রেস ব্যবহার হওয়ার আগেই এক্সপ্লয়েটেশন থামিয়ে দেয়।

প্র ০৩ ASLR ও DEP/NX একসাথে ব্যবহার করা হয় কেন — একটি একা কেন যথেষ্ট নয়?

ASLR মেমরি অ্যাড্রেস র‍্যান্ডমাইজ করে (আক্রমণকারী জানে না কোথায় জাম্প করতে হবে), কিন্তু অ্যাড্রেস কোনোভাবে জানা/লিক হয়ে গেলে এটি একাই কোড এক্সিকিউশন থামায় না। DEP/NX নতুন ইনজেক্ট করা কোড চালানো প্রতিরোধ করে, কিন্তু আক্রমণকারী যদি অ্যাড্রেস জানে তাহলে ইতিমধ্যে-বিদ্যমান executable কোড পুনরায় ব্যবহার করতে পারে (return-oriented programming — মেনশন-স্তরে)। একসাথে ব্যবহার করলে একটি স্তর অন্যটির দুর্বলতা পূরণ করে।

অনুশীলন

  1. চিন্তা করুন: কেন memory-safe ভাষায় (যেমন Python) লেখা একটি ওয়েব অ্যাপ্লিকেশন সরাসরি বাফার ওভারফ্লো ঝুঁকিতে নেই, কিন্তু তার নিচের কোনো C/C++ লাইব্রেরি (যেমন একটি ইমেজ-প্রসেসিং লাইব্রেরি) এখনও ঝুঁকিতে থাকতে পারে?

    অনেক "নিরাপদ" ভাষার রানটাইম/স্ট্যান্ডার্ড লাইব্রেরি পারফরম্যান্সের জন্য নেটিভ C/C++ লাইব্রেরি (ইমেজ পার্সার, কোডেক ইত্যাদি) ব্যবহার করে। সেই নেটিভ dependency-তে একটি বাফার ওভারফ্লো দুর্বলতা থাকলে, অ্যাপ্লিকেশনের নিজের কোড memory-safe হলেও পুরো সিস্টেম এখনও ঝুঁকিতে থাকে — এটি L24-এর "vulnerable and outdated components" ধারণার সাথে সরাসরি সম্পর্কিত।

  2. পরীক্ষা করুন: উপরের কোডে BUFFER_SIZE ৪-এ পরিবর্তন করুন এবং safe_input-কে ৩ বাইটে ছোট করুন, তারপর Run চেপে দেখুন নিরাপদ সংস্করণ কীভাবে আচরণ করে।

    বাফারের সাইজ ছোট হলেও, ইনপুট এখনও তার মধ্যে ধরায় নিরাপদ সংস্করণ স্বাভাবিকভাবে ইনপুট গ্রহণ করবে — দেখাচ্ছে যে bounds-check লজিক নির্দিষ্ট কোনো সংখ্যার উপর নির্ভর করে না, বরং বাফারের প্রকৃত দৈর্ঘ্যের সাথে ইনপুটের দৈর্ঘ্য তুলনা করে, তাই যেকোনো সাইজের বাফারে সমানভাবে কাজ করে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
প্রিভিলেজ এস্কেলেশন বেসিকস