دی بلاگ

دسته بندی لینوکس

۱۰ تیر ۱۳۹۷

افزودن امکان انتخاب ورژن PHP به دایرکت ادمین

سلام دوستان

در این مطلب قصد داریم به شما آموزش بدیم که چطوری نسخه Apache و PHP را در Directadmin ارتقاء دهید و همچنین به شما آموزش خواهیم داد که چطوری امکان انتخاب ‌ورژن PHP را به دایرکت اضافه کنید.
(با توجه به تفاوت در ورژن های مختلف و احتمال به وجود آمدن مشکل لطفا موارد گفته شده در این مقاله را مستقیما بر روی سرور اصلی خود اجرا ننمایید و قبل از آن بر روی یک سرور تست اجرا کنید)
ابتدا ذکر این نکته ضروری هست که PHP 5.6 توسط CustomBuild 1.1 پشتیبانی نمی شود. پس اگر قصد ارتقاء دارید باید از CustomBuild 1.1 به بالا استفاده کنید.

برای ارتقاء در دایرکت ادمین باید از CustomBuild استفاده کنیم که در آدرس زیر قرار دارد:

/usr/local/directadmin/custombuild

پس بعد از اینکه با ssh به خط فرمان لینوکس خودتون وصل شدید ابتدا به آدرس بالا برید
cd /usr/local/directadmin/custombuild
خب حالا اولین کاری که میکنید با دستور زیر ورژن custombuild را پیدا کنید:

./build version

اگر ورژن آن پایین هست باید آن را ارتقاء دهید..

 

حالا اگر قصد دارید فقط نسخه php را ارتقاء دهید باید از دستورات زیر استفاده کنید:

./build update
./build set php5_ver 5.6
./build php n

اگر بعد از بالا بردن نسخه سرویس httpd استارت نشد و خطا داد احتمالا نیاز به ارتقاء نسخه آپاچی هم پیدا میکنید که با دستورات زیر میتونید ورژن آپاچی و php را ارتقاء دهید:

cd /usr/local/directadmin/custombuild
./build update
./build apache
./build php n
./build mod_ruid2
./build rewrite_confs

حالا اگر دوست دارید امکان انتخاب نسخه php را در تنظیمات دیرکت ادمین برای یوزرهاتون داشته باشید باید به کمک دستورات زیر هم ورژن ۷.۱ و هم ورژن ۵.۶ را کنار هم نصب کنید.
برای این کار از دستورات زیر استفاده کنید

cd /usr/local/directadmin/custombuild
./build set php1_mode php-fpm
./build set php2_mode php-fpm
./build set php1_release 7.1
./build set php2_release 5.6

با اجرای دستورات بالا فایل options.conf تغییراتی خواهد کرد و موارد زیر به این فایل اضافه خواهند شد.

php1_release=7.1
php1_mode=php-fpm
php2_release=5.6
php2_mode=php-fpm

خب حالا وقت کامپایل مجدد PHP هست و بعد از اون تنظیم مجدد.. برای این کارها دستورات زیر را اجرا کنید

./build php n
./build rewrite_confs

خب کار اینجا تمام هست و باید الان در دایرکت ادمین امکان انتخاب ورژن را برای یوزر داشته باشیدphp-version-selector-directadmin php-version-selector-directadmin2

 

نکته:
این نکته مربوط به نصب ماژول های PHP هست که به موارد بالا بسیار ربط داره برای همین بهتر بود قبل از مطالب بالا میاوردمش اما اینجا میارمش دیگه..
اگر میخواهید مازول های PHP را هم نصب کنید باید –with-module را به تنظیمات اضافه کنید.. کجا و چطوریش را الان توضیح میدم.

 

با دستور زیر میتونید آدرس فایل کانفیگ را بدست بیارید

cd /usr/local/directadmin/custombuild
./build used_configs | grep configure.php

اگر دو ورژن PHP داشته باشید احتمالا دوتا آدرس میبینید

PHP (default) configuration file: /usr/local/directadmin/custombuild/configure/ap2/configure.php56

حالا بهتره اول یک بکاپ از فایل تنظیمات بگیرید و بعد اون را ویرایش کنید و –with-module را به آخرش اضافه کنید

--with-module

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

بعد از اینکه فایل های تنظیمات را برای نسخه مورد نظرتون تغییر دادید حالا دوباره دستور زیر را اجرا کنید تا یکبار دیگه php با ماژولهاش نصب بشه

 

./build php n

 

اگر با خطای زیر روبرو شدید بدونید که توی \ گذاشتن انتهای خط ها اشتباه کردید

/usr/local/directadmin/custombuild/custom/ap2/configure.php56: line 32: --with-module: command not found

به همین خوشمزگی.

 

پی نوشت.. دقت کنید که اگر بعد از آپدیت php به نسخه ۵.۶ هنگام اجرای آپاچی با خطای زیر روبرو شدید باید آپاچی را هم ارتقاء بدید و بعدش فایل های کانفیگ را بازنویسی کنید

 

Invalid command 'Mutex', perhaps misspelled or defined by a module not included in the server configuration
Action 'configtest' failed.  

گاهی هم بعد از ارتقاء آپاچی از ورژن ۲.۲ به ۲.۴ ممکنه به

unixd_config

گیر بده که با ارتقاء همزمان آپاچی و ‌php و بازنویسی کانفیگ ها مشکل حل میشه..

۱۹ اسفند ۱۳۹۶

حملات Stack Overflow

اگر به صورت جدي با زبان‌هاي C/C++‌ برنامه نوشته باشيد، قطعا با خطاي Segmentation Fault‌ در زمان دسترسي خارج از محدوده‌ي آرايه‌ها و يا کار با اشاره‌گر نامعتبر و تلاش براي دسترسي به فضاي حافظه‌ي غير مجاز، مواجه شده‌ايد. همچنين در زمان بکارگيري توابعي مثل gets نيز warningهاي کامپايلر مبني بر آسيب‌پذير بودن استفاده از اين توابع را مشاهده کرده‌ايد، ولي آيا به دليل آسيب‌پذير بودن اين توابع فکر کرده‌ايد؟ آيا ارتباط بين Overflow Buffer و اين توابع را مي‌دانيد؟

در حالت کلي Overflow ‌به معني نوشتن بيش از حد مجاز در محلي از حافظه است و در صورت استفاده‌ي هوشمندانه مي‌تواند باعث تغيير مسير اجراي برنامه، و در نهايت اجراي کد ديگري مثل /bin/bash و دسترسي به Shell شود. در اين مقاله ابتدا با ساختار پروسه‌ها و نگاشت حافظه در آن‌ها آشنا شده و پس از بررسي ساختار پشته در زمان اجراي توابع، به بررسي Stack Overflow خواهيم پرداخت.

نکته: در اين مقاله از نسخه‌ي ۳۲ بيتي Ubuntu 14.04 استفاده شده است، ولي مطالب آن بر روي نسخه‌ي ۳۲ بيتي توزيع‌هاي ديگر نيز قابل پياده‌سازي است.

برنامه‌هاي نوشته شده پس از کامپايل، کد زبان ماشين توليد مي‌کنند که قابل فهم براي CPU بوده و قادر به اجراي آن‌ها مي‌باشد. براي اجراي يک برنامه، بايد کد و داده‌ي آن در حافظه بارگذاري شده و سپس CPU با خواندن کد برنامه از حافظه، شروع به اجراي آن کند. پس براي تمامي عملياتي که در يک برنامه داريم، مثل انتساب‌ها، دستورات شرطي، حلقه‌ها، فراخواني توابع و… بايد چيزي در حافظه وجود داشته باشد که عمليات و داده‌ي مورد نياز آنرا مشخص مي‌کند. به عنوان مثال a=5 بايد مشخص کند که a به چه بخشي از حافظه اشاره کرده و عمليات مورد نظر ما اين است که مقدار ثابتي را در آن قرار دهيم و قصد جمع کردن، انتساب مقدار متغير ديگر در آن و… را نداريم. به داده‌اي که عمليات CPU و کاري که بايد انجام دهد را مشخص مي‌کند، Opcode مي‌گويند که توسط سازنده‌ي CPU و براي تمامي عمليات پشتيباني شده توسط آن تعريف شده و براي نوشتن برنامه از آن استفاده مي‌شود. در زبان‌هاي برنامه‌نويسي، ما با عبارت‌ها و دستورات سطح بالا که درک آن براي انسان ساده‌تر مي‌باشد کار مي‌کنيم. حتي در زبان اسمبلي که سطح پايين‌ترين حالت برنامه نويسي است، ما براي راحتي با عبارت‌هايي مثل mov, add, push, pop کار مي‌کنيم ولي پردازنده درکي از اين عبارات نداشته و در نهايت بايد وظيه‌اش به صورت مجموعه‌اي از Opcodeها مشخص شود.

نکته:‌ مثال‌ّهاي ما بر روي پردازنده‌هاي Intel‌ مي‌باشد. براي اطلاع از Opcode دستورات مختلف، دستورات پشتيباني شده توسط پردازنده، ساختار پردازنده و… به Intel Developer’s Manual مراجعه کنيد.

زبان‌هاي برنامه نويسي مختلف، طرز کار متفاوتي براي توليد کد زبان ماشين (کد قابل فهم توسط CPU) دارند ولي در نهايت نتيجه‌ي کار و خروجي مورد نظر براي CPU يکسان خواهد بود. زبان‌هاي سطح پاييني مثل Assembly, C, C++ پس از کامپايل، مستقيم کد زبان ماشين توليد مي‌کنند ولي زبان‌هايي مثل Java, .Net به اين صورت عمل نکرده و کدي توليد مي‌کنند که توسط ماشين مجازي آن زبان پردازش شده و در نهايت تبديل به کد زبان ماشين شده و اجرا مي‌گردد. داده و کد يک برنامه در بخش‌هاي مختلفي در حافظه قرار مي‌گيرند که امکان تعيين مجوز خواندن، نوشتن، اجرا کردن و دسترسي به داده و کد به صورت مجزا را فراهم مي‌کند.

نکته: مجوز Segmentهاي مختلف قابل تغيير است که در ادامه به توضيح بخشي از آن خواهيم پرداخت.

کد برنامه در text/code segment قرار داده مي‌شود که مجوز خواندن، اجرا کردن دارد. داده‌هاي global برنامه در صورتيکه مقدار اوليه داشته باشند، در data segment‌ و در صورتيکه مقداردهي براي آن‌ها انجام نشده باشد در bss قرار داده مي‌شوند. پارامترهاي ورودي توابع و متغيرهاي تعريف شده در آن‌ها بر روي stack قرار داده مي‌شوند. براي جلوگيري از تزريق کد در برنامه و اجرا کردن آن، بخش stack برنامه‌ها مجوز اجرا نداشته و تنها مي‌توان از آن اطلاعاتي خوانده و بر روي آن نوشت. در صورتيکه حافظه به صورت پويا و در زمان اجراي برنامه اختصاص داده شود، از بخشي از حافظه به نام heap استفاده خواهد شد. اين ساختارها در شکل ۱ مشاهده مي‌شوند.

ProgramMemory

شکل ۱) بخش‌ّ‌بندي حافظه‌ي برنامه‌ها

نکته‌اي که در اين تصوير اهميت بسيار زيادي دارد، نحوه‌ي رشد stack و heap است. دقت کنيد که stack از آدرس‌هاي بالاتر به سمت آدرس پايين‌تر رشد کرده و heap برعکس آن، از آدرس کمتر به سمت آدرس بيشتر رشد مي‌کند.

براي بررسي بخش‌بندي حافظه‌ي برنامه‌ّها و آشنايي دقيق با کاربرد stack‌ در ارسال پارامترها و تعريف متغيرهاي محلي، برنامه‌ي شکل ۲ را در نظر بگيريد. همانطور که مشاهده مي‌شود دو متغير سراسري که يکي داراي مقدار اوليه بوده و ديگري مقداري ندارد به همراه دو متغير محلي عددي تعريف شده و تابع printf نيز براي نمايش مقدار متغيرهاي محلي استفاده شده است.

Program1

شکل ۲) برنامه‌ي شماره يک

براي مشاهده‌ي بخش‌هاي مختلف برنامه‌ها و تحليل آن‌ها در لينوکس مي‌توان از ابزارهايي مثل objdump, readelf استفاده نمود. در شکل ۳ نحوه‌ي کامپايل برنامه‌ي شکل ۲ و بررسي segment‌ مربوط به some_value, channel_id نمايش داده شده است. در ستون چهارم segment دو متغير سراسري مشاهده مي‌شود که some_value که مقداري نداشت در bss و channel_id که مقداردهي شده بود در data قرار داده شده است.

Objdump

شکل ۳) استفاده از objdump براي تعيين offset و segment متغيرها

براي اطلاع از ساختار کلي، هدرها و segmentهاي يک برنامه مي‌توانيم مشابه شکل ۴ از دستور readelf استفاده نماييم. همانطور که در ستون چهارم از راست مشاهده مي‌شود، text داراي X براي اجرا مي‌باشد ولي داراي مجوز W نبوده و امکان تغيير کد در زمان اجرا وجود ندارد. همچنين بخش data, rodata مجوز اجراي کد ندارند.

readelf

شکل ۴) اجراي دستور readelf براي مشاهده‌ي segmentها

با مشاهده‌ي شکل ۵ و نحوه‌ي استفاده از دستور execstack نيز مشخص است که stack داراي مجوز X نبوده و امکان اجراي کد از روي آن وجود ندارد. هر چند مي‌توانيم در زمان کامپايل برنامه و يا همانطور که در شکل ۵ مشخص است، با دستور execstack آنرا تغيير دهيم.

execstack

شکل ۵) اجراي دستور execstack

همانطور که اشاره شد از stack در فراخواني توابع، ارسال پارامترهاي مورد نياز آن‌ّها، تعيين حافظه براي متغيرهاي محلي و همچنين ذخيره‌ي مقدار رجيسترهايي که در تابع تغيير کرده و مقدار آن‌ها در آينده مورد نياز است، استفاده مي‌شود. در برنامه‌نويسي اسمبلي دو رجيستر BP, SP براي کار با پشته در نظرگرفته شده‌اند. SP‌ هميشه به بالاي پشته و BP به ابتداي فضاي پشته مربوط به تابع فعلي اشاره مي‌کند. در زمان فراخواني يک تابع، براي از بين نرفتن فضاي پشته تابع قبلي (تابع فراخوان يا caller) مقدار BP توسط تابع فعلي (تابع فرخواني شده يا callee) بر روي پشته ذخيره شده و در انتهاي کار تابع و قبل از بازگشت به تابع فراخوان، از روي پشته برداشته شده و در BP قرار مي‌گيرد (تغيير مقدار رجيسترهاي پشته در انتهاي کار توابع توسط دستور LEAVE انجام مي‌شود).

توجه شود که به دليل رشد پشته از آدرس بيشتر به آدرس کمتر، بالاي پشته در پايين حافظه قرار دارد.

نکته: رجيسترهاي BP, SP در حالت ۱۶ بيتي که زمان شروع به کار کامپيوتر بوده و Real Mode ناميده مي‌شود استفاده مي‌شوند. در اين حالت حافظه‌ي قابل دسترس ۱MB است. پس از شروع به کار هسته‌ي سيستم‌عامل، تغيير وضعيتي به Protected Mode انجام مي‌شود که در آن امکان استفاده از حافظه‌ي ۴GB فراهم بوده و رجيسترها نيز ۳۲ بيتي مي‌باشند و به عنوان مثال رجيسترهاي پشته EBP, ESP هستند. در حالت ۶۴ بيتي نيز رجيسترهاي پشته RBP, RSP بوده و البته مدل فراخواني توابع نيز با مدل ۳۲ بيتي متفاوت است که در مقاله‌ي ديگري به آن خواهيم پرداخت.

پارامترهاي تابع از راست به چپ بر روي پشته قرار داده مي‌شوند. اينکار باعث مي‌شود که با قرار دادن EBP به عنوان مبنا، با اضافه کردن به مقدار آن به پارامترهاي تابع و به ترتيب از اولين پارامتر تا آخرين آن‌ها و با کم کردن از EBP به متغيرهاي محلي دسترسي داشته باشيم. در نهايت نکته‌ي آخر در مورد بازگشت از تابع است. انتظاري که مي‌رود اين است که پس از اتمام کار يک تابع، دستوري که بلافاصله پس از محل فراخواني قرار دارد اجرا شده و برنامه ادامه يابد. همانطور که مي‌دانيد رجيستر EIP حاوي آدرس دستور بعدي است که بايد توسط CPU اجرا شود. اگر در زمان فراخواني يک تابع، ابتدا آدرس دستور بعد از فراخواني تابع بر روي پشته قرار داده شده و سپس پرش به ابتداي تابع انجام شده و EIP برابر آدرس ابتداي تابع شود، اجراي تابع شروع خواهد شد (اينکار توسط call انجام مي‌شود) در انتهاي کار تابع نيز، کافي است مقدار آدرس برگشت ذخيره شده، از روي پشته برداشته شده و در EIP قرار داده شود و به اين صورت ادامه‌ي کار از جايي که فراخواني تابع انجام شده بود پيگيري خواهد شد (اينکار توسط دستور ret انجام مي‌شود). توضيحات ارائه شده در مورد فراخواني توابع در شکل ۶ به تصوير کشيده شده‌اند.

callstack

شکل ۶) ساختار پشته در زمان فراخواني توابع

با دقت در شکل ۶ مشاهده مي‌شود که اندازه‌ي هر خانه از پشته برابر ۴ بايت يا ۳۲ بيت است (از %ebp براي دسترسي به پارامترهاي ديگر جمع‌هاي ۴تايي انجام گرفته است). يعني اگر به عنوان مثال يک متغر محلي به صورت char s[10] نيز داشته باشيد، مقدار فضاي تخصيص داده شده براي آن بر روي پشته ۱۲ بايت مي‌باشد و نه ۱۰ بايتي که شما تعريف کرده‌ايد.

با جمع بندي مطالب بيان شده در مورد پشته در زمانيکه در يک تابع قرار داشته باشيد، ساختار پشته از ديد تابع به صورت شکل ۷ مي‌باشد.

callstack_summary

شکل ۷) ساختار کلي پشته در زمان فراخواني تابع

براي بررسي ساختار پشته به صورت عملي برنامه‌ي شکل ۲ را در gdb (GNU Debugger) باز کرده و کد اسمبلي و ساختار اجرايي آنرا بررسي مي‌کنيم. براي باز کردن يک برنامه در محيط gdb کافي است مسير برنامه را به عنوان پارامتر براي آن ارسال نمود: gdb prog

اين محيط به صورت پيش فرض از مدل اسمبلي AT&T استفاده مي‌کند که نسبت به مدل اينتل کمي عجيب است!!! در شکل ۸ ساختار تابع main برنامه‌ي شکل ۲ به صورت اسمبلي نمايش داده شده است. همانطور که در خط +۰ مشاهده مي‌شود، اولين کاري که در تابع انجام شده است، ذخيره کردن مقدار EBP است و پس از آن در خط +۱ مقدار ESP (بالاي پشته در ابتداي تابع) در EBP قرار گرفته و به اين شکل EBP مرز بين پارامترهاي تابع و آدرس برگشت با متغيرهاي محلي خواهد شد.

main_assembly

شکل ۸) ساختار اسمبلي تابع main در gdb

خط +۳ باعث مي‌شود که Alignment پشته به صورت مضربي از ۱۶بايت قرار داده شود (کاري که gcc انجام داده است و علت آن اين است که CPU در هر مرحله خواندن از حافظه ۱۶ بايت بارگذاري مي‌کند، هر چند اين عدد قابل تغيير است) و در خط بعدي يعني +۶ فضايي برابر ۳۲ بايت براي پشته‌ي داخل تابع main در نظر گرفته مي‌شود. اگر به ميزان فضاي مورد نياز دقت کنيم، مي‌بينيم که در اين تابع دو متغير a, b تعريف شده‌اند که ۸ بايت براي آن‌ها بر روي پشته مورد نياز است. از طرف ديگر پس از آن، يک فراخواني تابع printf وجود دارد که سه پارامتر براي آن ارسال شده است. براي پارامتر اول که يک رشته است، آدرس رشته ارسال مي‌شود (۴ بايت) و دو پارامتر ديگر که عددي مي‌باشند مقدارشان بر روي پشته کپي مي‌شود (در نهايت ۱۲ بايت براي پارامترهاي printf) که در مجموع ۸+۱۲=۲۰ بايت در اين تابع بر روي پشته نياز است و در خط +۶ به اندازه‌ي نزديکترين عدد مضرب ۱۶ يعني ۳۲ بايت فضا رزرو شده است.

خطوط +۹, +۱۷ براي مقداردهي اوليه به متغيرهاي a, b استفاده شده‌ و از خط +۲۵ تا خط +۴۱ براي قرار دادن پارامترهاي تابع printf بر روي پشته استفاده شده است. همانطور که مشاهده مي‌شود بلافاصله زير EBP روي پشته (خط +۱۷ و آدرس esp+0x1c) فضاي حافظه براي متغير b بوده و بعد از آن متغير a (آدرس esp+0x18) بر روي پشته قرار داده مي‌شود. يعني مشابه قرار دادن پارامترها که از راست به چپ بر روي پشته قرار داده مي‌شوند، متغيرهاي محلي نيز هرچه ديرتر تعريف شوند، زودتر بر روي پشته قرار داده مي‌شوند. از خط +۲۵ تا +۴۱ نيز پارامترهاي تابع printf بر روي پشته قرار داده مي‌شوند. در اين خطوط دقت شود که به دليل عدم امکان تبادل اطلاعات بين دو قسمت از حافظه، ابتدا مقدار متغيرها از حافظه خوانده شده، در رجيستر eax قرار داده شده و سپس در محل ديگر حافظه که مربوط به پارامترهاي printf مي‌باشد قرار داده مي‌شوند. از راست به چپ، ابتدا از آدرس esp+0x1c مقدار متغير b، سپس از esp+0x18 مقدار متغير a و درنهايت آدرس رشته‌ي format قرار داده مي‌شوند. در شکل ۹ ساختار پشته قبل از فراخواني تابع printf نمايش داده شده است.

stack_before_printf

شکل ۹) ساختار پشته قبل از فراخواني printf

براي بررسي آدرس بازگشت main مي‌توانيم در بخشي از کد يک break-point گذاشته و با اجرا کردن برنامه محتويات حافظه در آدرس EBP+4 را مشاهده کرده و آنرا disassemble کنيم. با انجام اينکار مشاهده مي‌شود که تابع main پس از اجرا شدن به کتابخانه‌ي libc باز مي‌گردد و شروع اجراي آن از اين کتابخانه بوده است. در شکل ۱۰  يک break-point پس از اجراي تابع printf قرار داده شده و محتويات آدرس ارسال شده به عنوان پارامتر اول printf و آدرس بازگشت main نمايش داده شده است. دستور x در محيط gdb محتويات بخشي از حافظه را نمايش مي‌دهد. دستور x/s براي نمايش به صورت رشته و x/wx براي نمايش يک کلمه (۳۲ بيت) به صورت عدد مبناي ۱۶ بکار مي‌رود.

main_return_address

شکل ۱۰) نمايش آدرس بازگشت تابع main

اميدوارم تا اينجاي بحث خسته نشده باشيد و جذابيت بحث براتون حفظ شده باشه، چونکه تازه پس از اين مقدمه‌ي طولاني داريم به مبحث Stack Overflow نزديک مي‌شيم!‌ 😀

بحث Stack Overflow در اين خلاصه مي‌شود که با نوشتن بيش از حد در يک بخش از حافظه، آدرس بازگشت تابع را تغيير داده و باعث اجراي کد ديگري شد. به عنوان مثال برنامه‌ي شکل ۱۱ را در نظر بگيريد.

Program2

شکل ۱۱) برنامه‌ي شماره دو

در اين برنامه به صورت خيلي ساده يک آرايه‌ي کاراکتري تعريف شده و با استفاده از تابع gets اطلاعاتي از ورودي دريافت شده و در اين آرايه قرار داده مي‌شود. در صورت کامپايل اين برنامه با خطايي مبني بر منسوخ شدن و خطرناک بودن تابع gets مواجه خواهيد شد. علت اين است که همانطور که در شکل مشخص است، اين تابع اندازه‌ي ورودي را چک نمي‌کند. ساختار پشته (البته با درنظر گرفتن boundary پشته برابر ۴ بايت بجاي ۱۶ بايتي که در مثال قبلي مشاهده کرديم) براي اين تابع main و فضايي که براي آرايه در نظر گرفته شده است، در شکل ۱۲ مشاهده مي‌شود. دقت کنيد که اسم آرايه ابتداي آدرس ذخيره کردن داده را مشخص کرده و با اضافه کردن عددي به آن، به خانه‌هاي بعدي آرايه دسترسي پيدا کرده و به سمت آدرس‌هاي بالاتر پيش خواهيم رفت و اين به معني اين است که در صورت نوشتن بيش از حد در اين آرايه (بيش‌تر از ۱۲۸ کاراکتر) امکان نوشتن در محل ذخيره‌ي EBP و آدرس بازگشت وجود دارد. آدرس بازگشت از main، نسبت به ابتداي آرايه در str+132 قرار دارد.

stack_array

شکل ۱۲) ساختار پشته براي آرايه

براي کامپايل کردن اين برنامه به صورت زير عمل مي‌کنيم:

gcc

در اين دستور کامپايل چند نکته وجود دارد:

  • -fno-stack-protector مکانيزمي به نام canary براي تشخيص Overflow وجود دارد که با اين سوئيچ آنرا غير فعال مي‌کنيم.
  • -zexecstack براي دادن مجوز اجرا به پشته است.
  • -mpreferred-stack-boundary=2 باعث مي‌شود که alignment پشته بجاي ۱۶ بايتي برابر ۴ بايت باشد.

با اين توضيحات بياييد تست کنيم و ببينيم اگر بجاي آدرس بازگشت BBBB قرار دهيم چه اتفاقي رخ خواهد داد. براي اينکار بايد ۱۳۶ کاراکتر (۱۲۸ کاراکتر براي آرايه، ۴ کاراکتر براي مقدار EBP و در نهايت ۴ بايت براي آدرس بازگشت) در بافر بنويسيم که چهار کاراکتر آخر آن BBBB بوده و ۱۳۲ کاراکتر ابتدايي آن اهميتي ندارد. براي توليد اين رشته از پايتون به صورت زير استفاده مي‌کنيم:

python

اين دستور ۱۳۲ کاراکتر A و چهار کاراکتر B را پشت سرهم قرار داده و در فايل /tmp/inp ذخيره مي‌کند. در محيط gdb از اين فايل به عنوان ورودي استفاده کرده و نتيجه را در شکل ۱۳ مشاهده مي‌کنيم.

changed_return_address

شکل ۱۳) تغيير آدرس بازگشت main

واقعا اين نتيجه لذت بخش نيست؟؟! 😀 با اجراي برنامه در محيط gdb امکان مشاهده‌ي آدرس بازگشت وجود دارد و همانطور که در تصوير مشخص است، اين آدرس برابر ۰x42424242 است که ASCII همان BBBB مي‌باشد. به دليل اينکه اين آدرس به جاي درستي اشاره نکرده و پس از اتمام کار main امکان بازگشت به محل خاصي وجود ندارد، پس از اجراي برنامه Segmentation Fault داده مي‌شود.

الان که موفق به اجراي موفقيت آميز Overflow Stack شديم، بياييد يک برنامه را اجرا کنيم. براي اجراي يک برنامه‌ي خارجي معمولا بهترين گزينه /bin/bash مي‌باشد که امکان دسترسي به کليه‌ي دستورات را فراهم مي‌کند. براي اينکار بايد اين برنامه در جايي از حافظه بارگذاري شده و سپس آدرس آن بجاي آدرس بازگشت از main قرار گيرد. با کمي دقت مشخص است که بهترين محل ذخيره‌ي برنامه‌ي جديد، در ادامه‌ي پشته و بعد از آدرس بازگشت مي‌باشد. برنامه بايد به صورت باينري که همان Opcode است ذخيره شود. مثلا در صورت نياز به NOP (که البته نياز هم خواهد شد!) بايد ۰x90 و به عبارت دقيق‌تر “\x90” بر روي پشته قرار داده شود. اما سوال مهم اين است که آدرسي که بايد به آن پرش صورت بگيرد چه آدرسي است؟ براي پيدا کردن اين آدرس، در ابتداي main و پس از قرار دادن مقدار ESP در EBP مي‌توانيم اين مقدار را برداشته و از آن براي آدرس بازگشت جديد استفاده نمود. ولي هنوز يک مشکل باقي است! براي اطلاع از اين مشکل من در شکل ۱۴ آدرس stack را در سه بار اجراي يک برنامه نمايش داده‌ام. عدد بعد از /proc شناسه يا PID پروسه است و maps نگاشت حافظه را دارد.

ASLR

شکل ۱۴) آدرس stack در سه بار اجراي يک برنامه

همانطوري که مشاهده مي‌شود، بخشي از حافظه که stack در آن قرار دارد، در هر اجرا متفاوت است.

در سيستم‌عامل‌ّهاي فعلي براي جلوگيري از نفوذ به برنامه‌ها و تشخيص آدرس دقيق ساختارهاي پروسه‌ها از ASLR (Address Space Layout Randomization) استفاده مي‌شود. اين مورد باعث مي‌شود که ساختارهاي برنامه در هر اجرا، آدرس‌هاي (البته آدرس‌دهي مجازي است که شيوه‌ي کار آن خود داستاني دارد!) متفاوتي داشته باشد. در لينوکس سه حالت براي آن وجود دارد:

  • ۰: غير فعال
  • ۱: فعال بودن براي کتابخانه‌ها و پشته
  • ۲: فعال بودن براي کتابخانه‌ها، پشته و heap

براي راحتي کار، اين مورد را به صورت زير غير فعال مي‌کنيم.

ASLR_proc

از طرف ديگر به دليل اينکه اجراي برنامه‌ّهاي مختلف متغير بوده و پشته در هر بار اجراي يک برنامه ساختار دقيقا يکساني ندارد، و ممکن چند بايت کمتر يا بيشتر از دفعه‌ي قبل بر روي آن قرار داشته باشد، پس نمي‌توانيم يک آدرس دقيق براي ابتداي برنامه‌ي مورد نظر خود (همان /bin/bash) در نظر بگيريم. براي رفع اين مشکل نيز قبل از داده‌ي مربوط به /bin/bash يکسري NOP قرار داده و به وسط آن‌ها اشاره مي‌کنيم. به اين صورت اجراي برنامه مختل نشده و با اجرا کردن تعداد کمتر يا بيشتري از دستورات NOP به ابتداي shellcode خواهيم رسيد. به دليل اينکه نوشتن يک Shellcode براي اجراي /bin/bash توضيحات جداگانه‌اي لازم دارد فعلا در مورد اين قسمت توضيحاتي ارائه نشده و مي‌توانيد يک Shellcode آماده از يکي از سايت‌هاي shell-storm.org و يا www.exploit-db.com دانلود کنيد.

براي بدست آوردن آدرس بازگشت از روي پشته، برنامه را در gdb باز کرده، پس از تغيير ebp يک break-point گذاشته و آنرا اجرا مي‌کنيم. آدرس $ebp+4 همان محل ذخيره‌ شدن آدرس بازگشت از main  که با اضافه کردن عددي به آن، به وسط دستورات NOP مي‌رسيم.

return_location

شکل ۱۵) بدست آوردن محل آدرس بازگشت از main

با کنار هم قرار دادن موارد ذکر شده در يک اسکريپت پايتون امکان اجراي Shellcode وجود خواهد داشت. در  شکل ۱۶ اسکريپت نوشته شده مشاهده مي‌شود.

shellcode

شکل ۱۶) اسکريپت نوشته شده براي اجراي shellcode

در اين اسکريپت ۳۰۰ بار NOP قبل از shellcode قرار داده شده و آدرس بازگشت از main برابر با ۲۰۰ بايت بعد از محل آدرس بازگشت تنظيم شده است. در شکل ۱۷ فضاي پشته پس از ارسال خروجي اين اسکريپت به عنوان ورودي برنامه‌ي شکل ۱۱ مشاهده مي‌شود.

shellcode_stack

شکل ۱۷) ساختار پشته پس از ارسال خروجي پايتون به عنوان ورودي برنامه دوم

با ذخيره کردن خروجي پايتون در فايل /tmp/inp_shell و اجراي برنامه‌ي دوم در محيط gdb و با اين ورودي، مي‌توانيم اجرا شدن /bin/bash را مشابه شکل ۱۸ مشاهده نماييم.

shellcode_execution_gdb

شکل ۱۸) اجرا شدن موفقيت آميز Shellcode

سوالي که ممکن است برايتان پيش آيد اين است که خب اين چه مشکل امنيتي مي‌تواند داشته باشد؟ و با اجرا شدن اين bash چه اتفاقي رخ خواهد داد؟ براي پاسخ به اين سوال و اتمام اين مقاله، فرض کنيد که نرم‌افزاري داريد که setuid بر روي آن تنظيم شده و owner فايل آن نيز root باشد (يا حتي از آن ساده‌تر، کلا با کاربر root اجرا شده باشد!!!!). معني اين مورد اين است که Effective User در زمان اجرا شدن اين برنامه کاربر root بوده و برنامه تحت مجوزهاي آن اجرا مي‌شود. اگر چنين فايلي آسيب پذير بوده و امکان نفوذ به آن فراهم شده باشد، شما مي‌توانيد به يک root shell دسترسي پيدا کرده و کنترل کامل سيستم را پيدا کنيد. براي تست اين موضوع من setuid را بر روي برنامه‌ي دوم تست خودمان فعال کرده و Stack Overflow براي اجراي bash را خارج از gdb  اجرا مي‌کنم. اين مورد در شکل ۱۹مشاهده مي‌شود.

shellcode-execution

شکل ۱۹) تنظيم کردن setuid و اجراي shellcode

در شکل مشخص است که انجام Overflow با خطاي Segmentation Fault مواجه نشده و برنامه اجرا شده است، ولي چرا Shell نمايش داده نشد؟!

موضوع اين است که به دليل بسته شدن پروسه‌ي پدر که همان برنامه‌ي ovf2 مي‌باشد، bash فرزند نيز بسته شده است! براي جلوگيري از اين مورد مي‌توانيم از دستور cat استفاده کنيم (اين دستور ورودي دريافت شده را به خروجي ارسال کرده و تا زمان عدم دريافت signal با خواهد ماند) و با باز نگه داشتن يک stream امکان تايپ دستور و دريافت نتيجه را داشته باشيم. در شکل ۲۰ نحوه‌ي استفاده نمايش داده شده است. دقت کنيد که امکان استفاده از تمامي دستورات لينوکس وجود داشته و کاربر نيز root گزارش داده مي‌شود! عالي نيست؟!!! 😀

root-shellcode

شکل ۲۰) اجراي موفقيت آميز shellcode خارج از gdb

در اين مقاله سعي کردم حملات Stack Overflow را به صورت کامل و با پيش‌نيازهايي که براي درک نحوه‌ي کار آن لازم است ذکر کرده و مثالي عملي از نحوه‌ي اجراي آن نمايش دهم.

اميدوارم مفيد بوده باشه، موفق باشيد.

آدرس کانال تلگرام

۱۲ بهمن ۱۳۹۶

معرفی فراخوانی سیستمی

در این مقاله قصد داریم نحوه‌ی برقراری ارتباط نرم‌افزارها با سیستم‌عامل و ارسال درخواست برای ارتباط با بخش‌ّهای مختلف آن و یا ارتباط با سخت افزارها را بیان کرده و کمی با طرز کار سیستم‌عامل آشنا شویم.

سیستم‌عامل از دو بخش کلی تشکیل می‌شود، یکی کرنل (هسته) که وظیفه‌ی ارتباط با سخت‌افزارها، فراهم کردن driver برای ارسال فرامین به آن‌ها، مدیریت حافظه، مدیریت پروسه‌ها، مدیریت ورودی/خروجی، زمان‌بندی دسترسی به پردازنده و… را داشته و بخش دوم نرم‌افزارها و کتابخانه‌های سطح کاربر می‌باشد که ظاهر سیستم‌عامل و محیط کار را فراهم می‌کنند. البته جزئیات زیادی وجود دارد ولی با یک دید خیلی بالا و حذف جزئیات می‌توانیم این دسته بندی را داشته باشیم. در شکل ۱ این ساختار مشاهده می‌شود.

OperatingSystem

شکل ۱) ساختار سیستم‌عامل

فضای حافظه‌ی اشغال شده توسط کرنل از فضای برنامه‌های سطح کاربر جدا بوده و هیچ برنامه‌ی سطح کاربری مجوز دسترسی به آنرا ندارد. اگر کمی این قضیه را بسط دهیم باید بگوییم که علاوه بر مجزا بودن فضای هسته از فضای سطح کاربر، حافظه‌ی برنامه‌ها نیز کاملا از هم جدا بوده و هیچ برنامه‌ای به صورت مستقیم و بدون استفاده از مکانیزم‌ّهای (IPC) Communication  Inter-Processحق دسترسی به فضای برنامه‌های دیگر را نداشته و در صورتیکه درخواستی برای آن ارسال کند از طرف سیستم‌عامل با یک Fault‌ مواجه خواهد شد. در کنار این جداسازی فضای حافظه، همچنین دستورات زبان‌ماشینی وجود دارند که استفاده از آن‌ها تنها در فضای کرنل امکان‌پذیر بوده و  Privilege بالاتری دارند. در مورد ارتباط مستقیم با سخت‌افزارها نیز به همین شکل است. برای کار با سخت‌افزارها می‌توان به دو صورت Memory Mapped I/O (MMIO) و یا I/O Ports ارتباط برقرار کرده و داده و دستوری برای آن‌ها ارسال کرده و یا اطلاعاتی از آن‌ها دریافت نمود. در مدل MMIO آدرس‌هایی از حافظه به صورت مستقیم برای یک سخت افزار رزرو شده و درصورتیکه در این آدرس اطلاعاتی نوشته شود، سخت‌افزار مورد نظر آنرا برداشته و کاری انجام می‌دهد. به عنوان مثال در صورتیکه یک کرنل ابتدایی بنویسید، برای نمایش اطلاعات در صفحه‌ی نمایش ۸۰×۲۵ متنی باید رنگ متن و کاراکتر مورد نظر را در فضای حافظه‌ای که از آدرس ۰x000B8000 شروع می‌شود درج کنید تا متن مورد نظر نمایش داده شود. (در مقاله‌های آینده به شیوه‌ی انجام اینکار خواهیم پرداخت) در مدل I/O Ports آدرس ارتباطی با سخت‌افزار مشخص شده و سپس داده‌ای برای آن ارسال شده و یا فرمانی به آن داده شده و یا اطلاعاتی از آن خوانده می‌شود. به عنوان مثال برای ارتباط با حافظه‌ی CMOS (وظیفه‌ی این حافظه نگهداری اطلاعات BIOS‌ در زمان خاموش شدن کامپیوتر است) می‌توان از شماره‌ی پورت‌های ۰x70, 0x71 استفاده نمود. در ارتباط با I/O Port از دستورات اسمبلی in, out استفاده می‌شود. (در ادامه یک مثال از کاربرد این حافظه و نحوه‌ی کار با I/O Ports ارائه خواهد شد.) پس کرنل علاوه بر چیزهایی که فراهم می‌کند، از سه چیز محافظت نیز می‌کند: حافظه، پورت‌های I/O و مجموعه‌ای از دستورات زبان ماشین (مثل دستور فعال/غیر فعال کردن وقفه). برای این جداسازی و حفاظت، باید هم سیستم‌عامل و هم پردازنده کارهایی انجام دهند. در پردازنده‌های اینتل هر دستور بر روی پردازنده می‌تواند در چهار حلقه اجرا شود که از ۰ تا ۳ شماره‌گذاری شده‌اند. حلقه‌ی صفر اولویت بالاتری را داشته و کد کرنل در این حلقه اجرا می‌شود. حلقه‌ی ۳ کمترین اولویت را داشته و کد سطح کاربر در این حلقه اجرا می‌شود. بسیاری از سیستم‌عامل‌ها از جمله ویندوز و لینوکس تنها از دوحلقه‌ی ۰و۳ استفاده کرده و از ۱و۲ استفاده نمی‌کنند. این مورد را نشان می‌دهد. شکل ۲ استفاده از حلقه‌های ۰و۳ را نشان می‌دهد.

KernelRings

شکل ۲) نحوه‌ی استفاده از ring در سیستم‌عامل

نکته: در محصولات مجازی‌سازی مثل VirtualBox بخش هسته‌ی سیستم‌عامل میهمان در Ring1 اجرا شده و خود محصول مجازی‌سازی در Ring0 قرار داده می‌شود و به این صورت اجرای سیستم‌عامل از hypervisor کنترل می‌شود.

شیوه‌ی کار به زبان ساده (و کمی غیر دقیق ولی کافی برای بیان منظور) به این صورت است که در Descriptor های مربوط به داده و کد، دوبیت (برای ۰تا۳) اولویت نیز نگهداری شده و در زمانیکه درخواستی ارسال می‌شوند این مقدار با مقدار اولویت جاری CPU مقایسه شده و در صورتیکه مطابقت وجود نداشت یک General Protection Fault ارسال می‌شود. تست کردن این موضوع به سادگی قابل انجام است. برای اینکار کافی است خارج از محیط کرنل دستوری مثل cli (برای غیر فعال کردن وقفه) را اجرا کنیم تا نتیجه و ایجاد Exception را مشاهده نماییم. اما در صورتیکه به عنوان مثال در یک Linux Kernel Module این دستور اجرا شود خطایی دریافت نشده و به دلیل اجرا شدن کد در محیط کرنل و Ring0 مشکلی بوجود نخواهد آمد. در شکل ۳ اجرای دستور در محیط Visual Studio و استثنای تولید شده را مشاهده می‌کنید.

GPF

شکل ۳) استثنای اجرای Privileged Instruction

در خلال توضیحات به این موضوع اشاره نمودیم که برای پشتیبانی از این موضوع و عدم دسترسی به فضای حافظه، پورت‌های I/O‌ و اجرای دستورات دارای اولویت بالا، CPU‌ مکانیزم حلقه‌ها را داشته ولی از طرف دیگر سیستم‌عامل هم باید پشتیبانی لازم را فراهم آورد. برای تست پشتیبانی سیستم‌عامل‌ّهای مختلف می‌توانیم از ویندوز ۹۸ استفاده نماییم!!!! علی‌رغم اینکه این نسخه از ویندوز خیلی قدیمی بوده و الان سخت می‌توان جایی را پیدا کرد که از آن استفاده می‌کنند ولی برای بررسی ما گزینه‌ی خوبی است. احتمال زیاد به این قضیه برخورده‌اید که رمز BIOS رو فراموش کرده باشید و نیاز داشته باشید که با در آوردن باتری، تنظیمات را ریست کرده و رمز را حذف کنید. این مورد کاری است که می‌توان با دسترسی به پورت‌های I/O مربوط به CMOS نیز انجام داده و با استفاده از همان دو پورت ۰x70, 0x71 که معرفی شدند تنظیمات BIOS را ریست نمود. CMOS از اطلاعاتی که نگه می‌دارد یک Checksum ایجاد کرده و بخشی از آنرا در اندیس ۰x2E‌ نگه می‌دارد، اگر این مقدار بهم بریزد، باعث می‌شود که تنظیمات ریست شده و رمز نیز حذف گردد. البته به دلیل حفاظت سیستم عامل و عدم دسترسی به پورت‌های I/O این امکان در ویندوزهای XP‌ به بعد (البته من ویندوز ۲۰۰۰ را تست نکرده‌ام!) این امکان وجود ندارد. ولی بر روی ویندوز ۹۸ و با استفاده از دستور debug (که در واقع امکان نوشتن و trace کد اسمبلی را فراهم می‌کند و می‌توان بوسیله‌ی آن فایل COM‌ نیز ساخت) می‌توانید از دستور out استفاده کرده و در پورت‌ّای CMOS و در اندیس ۰x2E مقدار نامعتبری (مثلا ۰xFF) ریخته و باعث حذف رمز BIOS‌ شوید. این مورد بر روی کامپیوتری که ویندوز ۹۸ داشته باشد (یا حتی با یک محصولی مثلBoot CD  Hiren’s دارای بوت ویندوز ۹۸ بوت شود) و یا محصولات مجازی‌سازی مثل VMWare قابل تست کردن است. در شکل ۴ می‌توانید اجرا بر روی VMWare را مشاهده کنید. از طریق ۰x70 جایی که باید داده در آن قرار گیرد مشخص شده و از طریق ۰x71 خود داده ارسال می‌شود.

debug98

شکل ۴) ریست رمز BIOS در ویندوز ۹۸

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

کرنل (هسته‌) سیستم‌عامل واسط‌هایی برای ارتباط با بخش‌ّهای مختلف فراهم می‌کند که به آن فراخوانی سیستم System Call گفته می‌شود. هر نرم‌افزاری برای انجام وظایفی که تحت کنترل هسته‌ی سیستم‌عامل می‌باشد از این واسط‌ها استفاده کرده و درخواست خود را به کرنل ارسال می‌کند. سپس پردازنده کد تحت کرنل را اجرا کرده و نتیجه را به بخش سطح کاربر ارسال می‌نماید. دقت کنید که در یک برنامه بسیاری از وظایف نیازی به دخالت کرنل نداشته و کد سطح کاربر به اجرای آن می‌پردازد. کلیه‌ی محاسبات، اجرای الگوریتم‌ّها، پردازش داده‌ها، آماده کردن داده برای نمایش و… در سطح کاربر انجام می‌شود ولی برای وظایفی مثل ذخیره و بازیابی داده روی دیسک، ارسال برای پروسه‌ی دیگر (درون سیستم‌عامل یا تحت شبکه)، نمایش نتایج، از واسط‌های کرنل کمک گرفته می‌شود. در شکل ۵ ارتباط با کرنل از طریق فراخوانی سیستم مشاهده می‌شود.

syscall

شکل ۵) فراخوانی سیستمی

برای بررسی دقیتر روال اجرای برنامه‌ها و بررسی فراخوانی‌های سیستمی انجام شده یک برنامه‌ی بسیار ساده را تصور کنید! این برنامه‌ی سلام به دنیا! در شکل ۶ مشاهده می‌شود.

hello

شکل ۶) برنامه سلام به دنیا!

در این برنامه اجرای تابع printf مشاهده می‌شود. این تابع با دریافت پارامترها و فرمت رشته، آنرا چاپ می‌کند. آماده‌سازی رشته‌ی نهایی از وظایف این تابع در سطح کاربر می‌باشد ولی به دلیل اینکه این تابع، در کتابخانه‌ی libc تعریف شده است، دسترسی به این کتابخانه، انجام وظایف مختلف دیگر مثل بررسی حافظه، چک کردن رجیستری (در ویندوز البته) و بسیاری کارهای دیگر از جمله موارد دیگری است که انجام گرفته و سپس رشته چاپ شده و در نهایت نیز برنامه خاتمه می‌یابد. پس فراخوانی های سیستمی مختلفی انجام می‌گیرد تا این دستور کتابخانه‌ای بارگذاری، اجرا شده و نتیجه را نمایش دهد. در ویندوز با استفاده از نرم‌افزارهای drstrace.exe و nttrace.exe (باید جداگانه دانلود کنید و پیش‌فرض با ویندوز وجود ندارند) و در لینوکس با استفاده از strace می‌توانید فراخوانی‌های سیستمی انجام گرفته توسط یک نرم‌افزار را مشاهده نمایید. در شکل ۷ بخشی از فراخوانی‌های سیستمی انجام گرفته تحت ویندوز در اجرای برنامه‌ی شکل ۶ مشاهده می‌شود.

WinSystrace

شکل ۷) فراخوانی‌های سیستمی در ویندوز

دقت کنید که توابع از فایل ntdll.dll بارگذاری شده و نام تمامی آن‌ها با Nt شروع شده است. در این شکل تنها ۴خط ابتدایی یک فایل ۴۱۳ خطی نمایش داده شده است! در ویندوز به دلیل تغییرات ایجاد شده در نسخه‌های مختلف، استفاده‌ی مستقیم از فراخوانی‌های سیستمی توصیه نشده و بجای آن باید از APIهای تعریف شده استفاده شود. (یک لایه‌ی Abstraction ایجاد شده بر روی فراخوانی‌های سیستمی) این توابع حتی به صورت رسمی مستند نیز نشده‌اند و تمامی مستندات به APIهای رسمی اشاره کرده‌اند.

در لینوکس قضیه کمی متفاوت بوده و استفاده‌ی مستقیم از فراخوانی‌ّهای سیستم متداول است. حتی امکان نوشتن فراخوانی سیستمی جدید و اضافه کردن آن به کرنل لینوکس وجود دارد. (در مقاله‌ای نحوه‌ی انجام اینکار را توضیح خواهیم داد) در شکل ۸ فراخوانی‌ّهای سیستمی انجام شده در اجرای برنامه‌ی شکل ۶ نمایش داده شده‌ است. درک نحوه‌ی کار و پیدا کردن بخشی که واقعا عملیات چاپ رشته در آن انجام می‌پذیر در این خروجی کوتاه بسیار ساده‌تر از خروجی ویندوز است. در این خروجی نیز مشخص است که دسترسی به فایل‌های کتابخانه‌ای برای دسترسی به توابع انجام گرفته است. همچنین در خط اول استفاده‌ از تابع execve برای ایجاد پروسه و اجرا کردن کد hello (توسط دستور strace) مشاهده می‌شود. بخش اصلی که نمایش رشته در آن انجام می‌پذیرد نیز در خط ۲۶ مشاهده می‌شود. در این خط از write برای نوشتن در فایلی با شماره‌ی ۱ استفاده شده است. (در لینوکس STDOUT==1‌ و STDERROR==2 می‌باشند)

LinuxStrace

شکل ۸) فراخوانی‌ّهای سیستمی در لینوکس

در مقاله‌ی دیگری استفاده از اسمبلی برای فراخوانی سیستمی و عدم نیاز به کتابخانه‌ها و درنتیجه استفاده از تنها ۲ فراخوانی سیستمی برای نمایش رشته و خروج از برنامه را توضیح خواهیم داد.

امیدوارم مفید بوده باشه، موفق باشید.

کانال تلگرام

۷ بهمن ۱۳۹۶

ساختار پارتیشین‌ها در MBR

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

داده‌های ذخیره شده بر روی هارددیسک در بلوک‌هایی به اندازه‌ی ۵۱۲بایت ذخیره می‌شوند که به این بلوک‌ها sector گفته می‌شود. این مقدار، کوچکترین میزان فضایی است که داده‌ای بر روی هارددیسک اشغال می‌کند. یعنی اگر شما حتی یک حرف یک بایتی مثل A‌ را ذخیره کنید، مقدار فضایی که اشغال کرده‌اید برابر با ۵۱۲بایت می‌باشد. البته قضیه به اینجا ختم نمی‌شود و به دلیل اینکه شما به صورت مستقیم و با استفاده از interrupt داده بر روی دیسک ذخیره نکرده و از سیستم‌عامل (در واقع با استفاده از SystemCall، به صورت مستقیم یا غیر مستقیم) کمک گرفته و از لایه‌ی File System عبور کرده و سپس داده را بر روی دیسک و در یک کلاستر ذخیره می‌کنید، مقدار فضای اشغال شده از این نیز بیشتر شده و مضربی از ۵۱۲بایت خواهد بود. در بسیاری از سیستم‌های فایل امروزی مثل NTFS, Ext4 مقدار پیش‌فرض اندازه‌ی کلاستر برابر ۴KB می‌باشد. (البته این مقدار برای پارتیشن‌ّها با اندازه‌های مختلف می‌تواند متفاوت می‌باشد)

اولین sector هارددیسک (اولین ۵۱۲bytes اشغال شده) که مهمترین سکتور نیز است، با عنوان Master Boot Record (MBR) شناخته می‌شود که پس از روشن شدن کامپیوتر و بارگذاری BIOS خوانده شده و اطلاعاتی دارد که برای پیدا کردن سیستم‌عامل، بارگذاری آن و دسترسی به اطلاعات پارتیشن‌ها مورد نیاز است و در صورتیکه این سکتور خراب شده و یا قابل دسترسی نباشد، با خطایی مواجه شده و امکان شروع به کار سیستم‌عامل وجود نخواهد داشت. در این ۵۱۲بایت مقدار ۶۴بایت برای ذخیره‌ی اطلاعات پارتیشن‌ها در نظر گرفته شده است که با استفاده از آن امکان تعریف ۴ پارتیشن وجود داشته که برای هر پارتیشن ۱۶بایت اطلاعات ذخیره می‌شود. پس با توضیحات ارائه شده مشخص می‌شود که بر روی هر دیسک ۴ پارتیشن بیشتر نمی‌توان ایجاد نمود. (مگر در حالت استفاده از درایو منطقی که در ادامه در مورد آن صحبت خواهد شد) از این ۴ پارتیشن یکی می‌تواند به عنوان پارتیشن Extended معرفی شده (و یا اصلا وجود نداشته باشد) و مابقی پارتیشن‌های Primary می‌باشند.

نکته: این ساختار در کامپیوترهایی وجود دارد که از BIOS و MBR استفاده می‌کنند و در کامپیوترهایی که UEFI و GPT دارند وجود ندارد. در مقاله‌ای دیگر به ساختار GPT خواهیم پرداخت.

در MBR کد مربوط به پیدا کردن سیستم‌عامل و بارگذاری BootLoader آن در ۴۴۶  بایت ابتدایی سکتور نوشته شده و از Offset شماره ۴۴۶ مقدار ۶۴ بایت اطلاعات پارتیشن‌ها را در بر می‌گیرد. دوبایت آخر MBR نیز (۴۴۶+۶۴==۵۱۰) همیشه مقدار ۰x55AA را در بر می‌گیرند. (در مقاله‌ای دیگر طریقه‌ی نوشتن برنامه اسمبلی برای MBR‌ را توضیح خواهیم داد)

نکته: فاصله نسبت به ابتدای محلی را Offset می‌نامند. در مثال ما فاصله از ابتدای سکتور مدنظر است.

در شکل زیر داده‌ی یک MBR‌ به صورت Hex نمایش داده شده است. دستور dd امکان کپی‌برداری از اطلاعات دیسک را فراهم می‌کند و همانطور که در شکل نمایش داده شده است مقدار ۵۱۲بایت از ابتدای دیسک اول کپی شده و در فایلی به نام mbr.img ذخیره می‌شود. (نام دیسک‌های sata, scsi در لینوکس با sd شروع شده و پس از آن، اولین دیسک sda، دومی sdb و… نام‌گذاری می‌شوند)

دستور xxd نیز برای hex dump استفاده می‌شود که بایت‌های یک فایل را در مبنای ۱۶ نمایش می‌دهد. در سمت چپ Offset هرخط، در وسط مقدار در مبنای ۱۶ و در سمت راست مقدار ASCII اطلاعات نمایش داده شده است. در اطلاعات نمایش داده شده عبارت GRUB‌ مشاهده می‌شود که از آن می‌توان برداشت کرد که این MBR مربوط به سیستمی لینوکسی می‌باشد! در خط آخر (از Offset شماره ۱f4==500 تا ۱ff==511) و در مکان دو بایت آخر (یعنی در مکان‌های ۵۱۰ و ۵۱۱) مقدار ۰x55AA مشاهده می‌شود! در صورت عدم وجود این مقدار در انتهای MBR خطای Missing Operating System داده خواهد شد.

1

پس از مشاهده کردن محتویات یک MBR باز گردیم به بررسی ساختار پارتیشن‌ها در آن و اطلاعاتی که در ۱۶بایت مربوط به هر پارتیشن ذخیره می‌شود. از جمله اطلاعاتی که در ۱۶بایت مربوط به هر پارتیشن قرار می‌گیرد و برای ما اهمیت دارد موارد زیر می‌باشند:

  • بایت اول (شماره‌ی صفر) Active بودن پارتیشن را مشخص کرده و می‌تواند دو مقدار۰x80 و ۰ را داشته باشد. مقدار ۰x80 که تنها به پارتیشن‌های Primary داده می‌شود، بیانگر پارتیشنی است که سیستم‌عامل بر روی آن نصب شده و باید Boot Loader سیستم‌عامل از روی آن بارگذاری شود. این مقدار تنها برای یک پارتیشن باید ست شده و در غیر این صورت در زمان بارگذاری سیستم‌عامل با خطای Invalid Boot Partition مواجه می‌شویم.
  • بایت شماره‌ی ۴ نوع پارتیشن را مشخص می‌کند. به عنوان مثال مقدار ۰ برای خالی بودن، ۰x05 برای Extended، ۰x07 برای NTFS و ۰x83 برای Linux‌ مورد استفاده قرار می‌گیرند.
  • بایت‌های شماره‌ی ۸ تا ۱۱ برای Logical Block Addressing (LBA) مورد استفاده قرار می‌گیرند. (چهاربایت که اولین بایت، کمترین ارزش و آخرین بایت، بیشترین ارزش را دارد) از این مقدار برای Offset شروع پارتیشن استفاده می‌شود و تعداد سکتوری (دقت شود که سکتور و نه بایت) را مشخص می‌کند که از ابتدای دیسک باید رد کنیم تا به ابتدای پارتیشن برسیم.
  • بایت‌های شماره‌ی ۱۲ تا ۱۵ نیز اندازه‌ی پارتیشن به تعداد سکتور را مشخص می‌کنند.

در شکل زیر بخش‌های توضیح داده شده و مابقی بخش‌های تشکیل دهنده‌ی MBR نمایش داده شده است.

2

نکته: در این شکل برای Signature مقدار ۰xAA55 ذکر شده است در حالیکه با دیدن Hex مربوط به MBR‌ ، عبارت ۰x55AA در خروجی وجود داشت که یکی نمایش Big-Endian و دیگری Little-Endian است.

برای درک دقیق‌تر موضوع، مقدار LBA‌ و اندازه‌ی پارتیشن اول MBR نمایش داده شده را محاسبه کرده و با خروجی دستور fdisk مقایسه می‌کنیم. شروع اولین پارتیشن از Offset با مقدار ۴۴۶ می‌باشد که برای مقدار LBA باید بایت‌های ۸ تا ۱۱ را برداشته و مقدار آنرا با بدست آوردن یک عدد ۳۲ بیتی محاسبه نماییم. پس باید از آدرس ۴۴۶+۸==۱C6 مقدار ۴بایت را برداریم. در شکل MBR خط سوم از پایین از آدرس ۱C2 شروع می‌شود که با اضافه کردن ۴بایت به آن به آدرس ۱C6 در ابتدای ستون سوم خواهیم رسید. پس ۳۲ بیتی که مقدار LBA را مشخص می‌کنند به صورت زیر بوده که با تبدیل به مبنای ۱۰ به عدد ۲۰۴۸ خواهیم رسید:

۰x00080000 = 0x00 + 0x08 * 256 + 0x00 * 2562 + 0x00 * 2563 = 2048

چون ۳۲ بیت این عدد را به بخش‌های ۸ بیتی تقسیم نمودیم، (هر بایت برابر ۸ بیت می‌باشد) پس ارزش هربایت ۲۸==۲۵۶ برابر ارزش بایت قبلی می‌باشد.

به طریق مشابه برای بدست آوردن اندازه‌ی پارتیشن باید به ابتدای بایت ۴۴۶+۱۲==۱CA رفته و ۴بایت را برداشته و اندازه‌ی پارتیشن را بدست آورد. (۴ بایت خط سوم از پایین، ستون‌های ۵و۶)

۰x00008018 = 0x00 + 0x00 * 256 + 0x80 * 2562 + 0x18 * 2563 = 411041792  

با اجرای دستور fdisk بر روی فایل mbr.img صحت این اطلاعات مشخص می‌شود. دقت شود که به دلیل کپی نگرفتن از تمامی اطلاعات دیسک، اطلاعات پارتیشن‌های Extended کامل نبوده و fdisk خطایی می‌دهد. (در ادامه با ساختار کامل این پارتیشن‌ها و دلیل این خطا آشنا می‌شویم)

3

تا اینجای بحث توضیح دادیم که ۴ بخش برای اطلاعات ۴ پارتیشن وجود دارد که یکی از این ۴تا می‌تواند از نوع Extended باشد. این نوع پارتیشن امکان ایجاد Logical Drive (از دید سیستم‌عامل و کاربر نهایی همان پارتیشن و افزودن تعداد درایوها و رسیدن به تعداد درایور بیشتر از ۴) را فراهم می‌کند. روال کار به این شکل است که یک لیست پیوندی ایجاد شده که این پارتیشن Extended به عنوان Head عمل کرده و اشاره کننده به اولین Logical Drive است. (مقدار LBA این پارتیشن به ابتدای اولین سکتور از اولین درایو اشاره کرده و Size اندازه‌ی کلیه‌ی درایوها در مجموع و فضایی که تشکیل می‌دهند را مشخص می‌کند)

در هر Logical Drive اولین سکتور با نام Extended Boot Record (EBR) شناخته شده و ساختاری مشابه MBR دارد که از ۴ بخش مربوط به پارتیشن‌ها، تنها ۲ بخش استفاده شده که بخش مربوط به پارتیشن اول، اطلاعات درایو فعلی را در بر گرفته و بخش دوم اشاره‌گر به Logical Drive بعدی می‌باشد. در انتهای کار و پس از رسیدن به آخرین درایو، مقدار فیلد type (نوع پارتیشن در بایت شماره‌ی ۴) پارتیشن دوم EBR (اشاره‌گر به درایو بعدی) برابر صفر خواهد بود. (همان NULL در انتهای لیست پیوندی ساده) این ساختار در شکل زیر نمایش داده شده است.

4

نکته مهم: در Logical Driverها مقدار LBA مشخص شده در پارتیشن اول EBR، نسبت به درایو قبلی بوده و از ابتدای محل Extended نیست.

 

برای بررسی عملی توضیحات داده شده، با استفاده از دستور dd سکتور مربوط به EBR را خوانده و اطلاعات نمایش داده شده توسط آنرا بررسی می‌نماییم. اولین مرحله پیدا کردن آدرس EBR است. برای اینکار از شکل خروجی دستور fdisk -l mbr.img و در خط دوم که نوع پارتیشن Extended ذکر شده است، مقدار start را برداشته و این تعداد سکتور skip کرده و سپس ۵۱۲ بایت می‌خوانیم. سپس دستور fdisk را برای فایل جدید ایجاد شده، اجرا کرده و LBA, Size درایو را بدست می‌آوریم.

5

در این شکل مشخص است که درایومنطقی که درون Extended ایجاد شده است از نوع Swap بوده و شروع آن ۲سکتور بعد از شروع Extended می‌باشد (یعنی از سکتور ۴۱۱۰۴۵۸۸۸ که برابر است با skip+start(ebr.img)) جمع بندی نهایی و نمایش کلیه‌ی اطلاعات دیسک با استفاده از اجرای fdisk  بر روی sda به صورت زیر است:

6

امیدوارم مفید بوده باشه، موفق باشید.

کانال تلگرام

۱۰ دی ۱۳۹۵

مدیریت کاربران در لینوکس

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

دو فايلي که براي نگهداري اطلاعات کاربران و همچنين رمز آن‌ها (به صورت کد شده)‌ استفاده مي‌شوند، يکي /etc/passwd‌ بوده که اطلاعات کاربران از جمله نام کاربر، آدرس home آن، shell‌ پيش فرض کاربر را نگهداري مي‌کند. در نسخه‌هاي قديمي لينوکس رمز کاربر نيز در اين فايل نگهداري مي‌شد ولي در نسخه‌هايي که الان استفاده مي‌شود، فيلد دوم اين فايل x بوده و بيانگر اين نکته است که اطلاعات رمز کاربر در فايل ديگري نگهداري مي‌شود. اين فايل /etc/shadow مي‌باشد که کد رمز به همراه اطلاعات ديگري از جمله آخرين باري که رمز کاربر تغيير کرده، مدت زماني که رمز مي‌تواند استفاده شود و بعد از آن بايد تغيير کند، حداقل مدت زمان نگهداري رمز، در آن نگهداري مي‌شود.

نکته: دقت شود که کد رمز به صورت hash بوده و به صورت encrypt نمي‌باشد و معني اين موضوع اين است که قابليت پيدا کردن رمز از روي اين کد تنها به صورت brute force‌ وجود داشته و اين کد بازگشت پذير نمي‌باشد.

در اين فايل‌ها هر سطر بيانگر اطلاعات يک کاربر مي‌باشد. هر سطر به ستون‌هايي تقسيم مي‌شود که با : از يکديگر جدا شده و هر ستون اطلاعات خاصي در مورد کاربر را در خود نگه مي‌دارد. براي مشاهده کردن اطلاعات اين دو فايل مي‌توان به صفحه‌ي ۵ از manual لينوکس مراجعه نمود.

man 5 passwd

man 5 shadow

به عنوان مثال اگر خط اول فايل passwd‌ را مشاهده کنيم و با اطلاعات مشاهده شده در manual مقايسه کنيم به اطلاعات زير خواهيم رسيد.

passwd_line1

محيط shell : مسير home‌ : اطلاعات تکميلي : شناسه‌گروه : شناسه‌کاربر : کد رمز : نام‌کاربر

همچنين با مشاهده کردن خط اول فايل shadow مي‌توان اطلاعاتي در مورد رمز کاربر بدست آورد که از چپ به راست برخي آن‌ها به صورت زير مي‌باشند.

shadow_line1

….. حداکثر طول عمر رمز :‌ حداقل طول عمر رمز : زمان آخرين تغيير رمز : کد رمز : نام کاربر

در عکس‌ّهاي مربوط به دو فايل معرفي شده چند نکته حائز اهميت است. اولين نکته اينکه زمان آخرين تغيير رمز کاربر به صورت يک عدد بيان شده است و نه به صورت يک تاريخ کامل. اين عدد تعداد روز گذشته از ۱ ژانويه سال ۱۹۷۰ را مشخص مي‌کند و با محاسبه زماني که از اين تاريخ مي‌گذرد مي‌توان به تاريخ دقيق تغيير رمز پي برد.

نکته‌ي بعدي مربوط به نوع کاربران و مجوز‌هاي آن‌ها در لينوکس مي‌باشد. اگر به دستوراتي که براي نمايش خط اول فايل‌ها استفاده شده‌اند دقت نماييد متوجه خواهيد شد که براي نمايش اطلاعات از فايل shadow از دستور sudo استفاده شده است. در صورت استفاده از sudo‌ در ابتداي اجراي دستور، آن دستور با مجوزهاي کاربر root اجرا مي‌شود که در لينوکس مدير سيستم بوده و توانايي اجراي هر دستوري و اعمال هر تغييري در سيستم را دارد. کاربران عادي توانايي اجراي دستوراتي که تغييري در سيستم بوجود مي‌آورند و يا حتي دسترسي و خواندن اطلاعات فايل‌هايي که مالک آن‌ها کاربر root مي‌باشد را ندارند. اما در شرايطي که کاربري عضو گروه sudo باشد (يا اجازه‌ي استفاده از دستورات براي آن در فايل /etc/sudoers تنظيم شده باشد) مي‌تواند مجوز‌هاي خود را بالاتر برده و به استفاده از دستوري بپردازد که در شرايط عادي امکان استفاده از آن را ندارد. عضويت در گروه sudo (که البته براي آن تنظيمي در فايل /etc/sudoers ‌انجام گرفته که کار ساده‌تر شده و با عضويت در يک گروه بجاي تغيير در فايل امکان استفاده از مجوزهاي root فراهم شود) توانايي استفاده از دستور sudo‌ را به کاربران خواهد داد که command نوشته شده را با مجوز‌هاي root اجرا خواهد نمود.

اکنون که دليل استفاده از sudo‌ را متوجه شديم به بررسي مجوز‌هاي دو فايل و مقايسه‌ي آن‌ّها با يکديگر مي‌پردازيم که دليل نياز به استفاده از sudo براي خواندن محتويات فايل shadow را متوجه شويم.

ls_l

در خروجي ls -l‌ ستون‌هاي نمايش داده شده (از چپ به راست) به ترتيب مجوز‌هاي فايل، تعداد لينک‌ها، کاربر مالک فايل، گروه‌ مالک فايل، اندازه‌ي فايل به بايت، زمان آخرين تغيير فايل و در نهايت نام فايل آورده مي‌شود.

مجوز‌هاي فايل (به همراه نوع فايل) يک رشته‌ي ۱۰ کاراکتري مي‌باشد که اولين کاراکتر آن (از چپ) ‌نوع فايل را مشخص نموده و ۹تاي باقي‌مانده مجوز‌هاي دسترسي به فايل توسط کاربر مالک، گروه مالک و مابقي افراد مي‌باشد. نحوه‌ي تعيين مجوز ها با سه کاراکتر rwx براي خواندن، نوشتن و اجرا کردن فايل و يا – براي عدم وجود مجوز مي‌باشد. در صورتيکه به خروجي دقت نماييد، متوجه مي‌شويد که مالک اين دو فايل کاربر root بوده و ديگران (بخش سوم مجوزها) اجازه‌ي خواندن passwd را داشته (با مجوز r–) ولي اين مجوز را براي فايل shadow‌ ندارند که دليل استفاده از sudo براي خواندن خط اول فايل مي‌باشد، که به اين صورت باعث اجراي دستور head (براي خواندن يک خط از ابتداي فايل) با مجوزهاي کاربر root و امکان خواندن فايل مي‌شود.

نکته‌ي آخري که در خروجي خطوط اول فايل‌ّها وجود دارد اين است که کد رمزي براي کاربر root وجود نداشته و بجاي آن از ! استفاده شده است. در لينوکس در صورتيکه نياز به منع کاربري از ورودي به سيستم باشد، مي‌توان بجاي رمز آن (يا حتي در صورت داشتن رمز، در ابتداي رشته‌ي کد رمز) از ! استفاده نموده و امکان ورود به سيستم را از کاربر گرفت.

کاربر root به دليل مسائل امنيتي از ورود به سيستم منع شده و در صورت انتساب رمز نيز ممکن است امکان ورود به سيستم را نداشته باشد. در صورت نياز به استفاده از آن (هر چند توصيه نمي‌شود) مي‌توان امکان ورود به سيستم از طريق shell ‌با کاربر root‌ را توسط دو مرحله‌ي زير فعال نمود.

$sudo passwd root

۱) براي انتساب رمز به کاربر root‌

$sudo passwd –u root

۲)‌ براي unlock کردن کاربر root

در صورتيکه اين کاربر را فعال کرده و بعد از مدتي مجددا تصميم به غير فعال کردن آن گرفته شود مي‌توان با استفاده از –l بجاي –u  در دستور passwd اينکار را انجام داد. غير فعال کردن root و تغييري که در محتويات فايل shadowايجاد مي‌شود را مي‌توان در شکل زير مشاهده نمود.

lock_unlock

اکنون که با نحوه‌ي ذخيره‌ي اطلاعات کاربران در لينوکس‌ آشنا شده‌ايم به ساخت کاربر جديد پرداخته و نکاتي که بيان شد را مرور کرده و چند نکته‌ي مهم ديگر را نيز بيان خواهيم نمود.

دستوري که براي ساخت کاربر در لينوکس استفاده مي‌شود، useradd‌ مي‌باشد. اين دستور پارامترهاي مختلفي دريافت کرده و اطلاعات کاربر را در فايل passwd ذخيره خواهد نمود. براي تست کاربر جديد نيز مي‌توانيم با زدن دستور su –l يک ورود جديد به لينوکس انجام داده و کاربر جديد را تست نمود. البته دقت نماييد که بايد رمزي براي کاربر تنظيم کنيد تا بتوانيد با آن وارد شويد. در شکل زير ساخت و تست کاربر جديد نمايش داده شده است.

useradd

در اين شکل اعلاني که براي کاربر مشخص شده را با يک $ تنها مشاهده مي‌کنيد (دستور whoami استفاده شده که نشان دهد با کاربر جديد وارد شده‌ايم) که اين اعلان بر خلاف چيزي است که در لينوکس به صورت پيش فرض وجود دارد. اعلان پيش فرض لينوکس (حداقل در Ubuntu) به صورت زير مي‌باشد:

username@hostname:path$

که در صورت ورود با کاربر root‌ بجاي $ يک ‌# نمايش داده خواهد شد.

اگر نگاهي به خط مربوط به کاربر جديد در فايل passwd‌ بياندازيد مي‌بينيد که براي اين کاربر home‌ تنظيم شده ولي ساخته نشده است و shell پيش فرض نيز ندارد.

useradd_res

تاثير shell را مي‌توان در نحوه‌ي اعلان مشاهده نمود و تاثير home را در مواردي مثل سابقه‌ي دستورات. به اين شکل که اگر دستوراتي را تايپ کرده، خارج شده و مجدد وارد شويد، هيچ سابقه‌اي از دستورات نشست قبل نخواهيد داشت. علت اين است که محيط کاربر از طريق home تنظيم مي‌شود و سابقه‌ي دستورات که مثال زدم در فايلي به نام .bash_history  که در home مي‌باشد ذخيره مي‌گردد. اگر دستور useradd با پارامتر‌هاي –m  و –s استفاده شود، هم home‌ ساخته شده و هم shell تنظيم مي‌گردد. اما براي کاربر جديد که ساخته‌شده است چطور مي‌توان home را ساخته و shell را تنظيم نمود؟ تغيير در ساختار تنظيمات کاربر با usermod امکان‌پذير مي‌باشد که پارامترهايي مشابه useradd دارد (مثل –s که در هر دو وجود دارد) و براي ساخت home نيز مي‌توان از يک دستور کمکي به نام mkhomedir_helper استفاده نمود.

نکته: در صورت استفاده از دستور adduser‌ بجاي useradd براي ساخت کاربر، home ساخته شده و shell‌ نيز تنظيم مي‌گردد.

usermod

کاربر جديد اکنون مي‌تواند مشابه يک کاربر عادي به کار در سيستم بپردازد ولي هنوز يک مساله براي اين کاربر وجود دارد. اگر ليست گرو‌ه‌هاي اين کاربر را با دستور groups مشاهده کنيد، مي‌بينيد که گروه sudo در آن نبوده و تاثير را وقتي متوجه خواهيد شد که مثلا به اجراي دستوري به کمک sudo بپردازيد.

groups

پيغامي که مشاهده مي‌کنيد در مورد نبود اين کاربر در فايل sudoers‌ مي‌باشد. در صورتيکه از کاربر اصلي سيستم که اجازه‌ي استفاده از دستور sudo را دارد استفاده کرده و با usermod گروه sudo را به گرو‌ه‌هاي کاربر جديد اضافه کنيد (با –aG که براي اضافه کردن گروه به ليست گروه‌هاي فعلي است) اين مشکل برطرف شده و امکان استفاده از دستور sudo فراهم خواهد شد.

using_sudo

مبحث آخري که در اين مقاله به آن خواهيم پرداخت نحوه‌ي توليد کد از روي رمز کاربر مي‌باشد. فيلد دوم در فايل shadow که حاوي کد رمز مي‌باشد، به صورت زير تعيين مي‌شود.

$algorithm_id$salt_string$encrypted_password

الگوريتم‌هاي مختلفي مي‌توانند براي توليد کد از روي رمز استفاده شوند (مثل md5 يا sha) و هر الگوريتم مي‌تواند تعداد بيت مختلفي استفاده کرده و کد رمز با طول متفاوتي توليد نمايد. در نسخه‌هاي فعلي لينوکس (Ubuntu 16 در مثال‌هاي اين مقاله شده) الگوريتم پيش فرض SHA-512 مي‌باشد. براي شناسايي الگوريتم استفاده شده در توليد کد رمز و مقايسه‌ي رمز وارد شده با رمز صحيح و احرازهويت کاربران، شناسه‌اي براي مشخص نمودن الگوريتم در اين بخش نگهداري مي‌شود که براي الگوريتم SHA-512 (الگوريتم پيش فرض) ۶ مي‌باشد. براي اطلاع از الگوريتم‌ّهاي مخلف و شناسه‌ي آن‌ها مي‌توان از صفحه ۳ فايل‌هاي کمکي مربوط به crypt با دستور man 3 crypt استفاده نمود. روش کار به اين صورت است که در زمان ورود کاربر، با خواندن اين فايل و بدست آوردن الگوريتم استفاده شده، کد رمزي از روي رمز وارد شده توسط کاربر توليد مي‌شود که با کد رمز ذخيره شده در اين فايل مقايسه شده و در صورت يکسان بودن، به کاربر اجازه‌ي ورود داده مي‌شود. تا اينجاي کار با دو بخش از سه بخش جدا شده با $ در فيلد دوم فايل shadow آشنا شده‌ايم و يک بخش که رشته‌ي salt مي‌باشد باقي مانده است. براي درک کاربرد آن فرض کنيد که اين رشته استفاده نشده و دو کاربر رمز يکساني داشته باشند. به دليل اينکه‌ الگوريتم‌هاي توليد hash از روي رمز، در هر بار استفاده، رشته‌ي يکساني به عنوان کد رمز توليد مي‌کنند، در فايل shadow مقدار يکساني براي کاربراني که رمز يکساني دارند خواهيم داشت که ممکن است به لو رفتن رمز کاربران منجر شود و در شکستن رمزها نيز استفاده خواهد شد. حال اگر تابع توليد hash‌ بجاي دريافت يک مقدار رمز، دو مقدار دريافت نمايد و مقدار دوم يک رشته‌ي تصادفي بوده و در هر باز عوض کردن رمز مقدار جديدي خواهد داشت، ديگر براي رمز‌هاي يکسان نيز مقدار کد متفاوتي ذخيره شده و مشکلي پيش نخواهد آمد. اين تعريف و کاربرد رشته‌ي salt است که يک رشته‌ي تصادفي بين ۸ تا ۱۶ کاراکتر بوده و به عنوان ورودي دوم در کنار رمز به تابع hash ‌ارسال مي‌شود تا کد رمز توليدي هر دفعه تغيير کرده و با دفعه قبل و براي کاربران مختلف يکسان نباشد.

براي توليد کد رمز با ارسال salt و رمز مي‌توانيد به صورت زير از دستور mkpasswd استفاده نماييد. (براي نصب آن در صورت عدم وجود از دستور sudo apt-get install whois استفاده کنيد)

mkpasswd

با دقت در شکل بالا مي‌بينيد که اگر salt ذخيره شده در فايل shadow‌ را استفاده کرده و رمز کاربر root (که من ۱۲ گذاشته بودمJ) را بکار بريم، دقيقا همان مقداري که در فيلد دوم shadow‌ ذخيره شده است را بدست خواهيم آورد.

در نهايت براي حذف کاربر از دستور userdel‌ استفاده کنيد. در صورتيکه بخواهيد فايل‌ّهاي موجود در home‌ نيز به همراه کاربر حذف گردند مي‌توانيد از –r به همراه دستور استفاده نمايد.

اميدوارم مفيد بوده باشه، موفق باشيد.

کانال تلگرام

کپی رایت © 2018 دی بلاگ

طراحی توسط Anders Norenبالا ↑