السلام عليكم و رحمة الله و بركاته
رابط النسخة الفرنسية من المقالة على موقعي الخاص في منتديات developpez :
Apprendre à utiliser la fonction system
سنتعرف في هذه المقالة على كيفية استخدام الدالة system في سي++.
لتوضيح أهمية هذه الدالة سنقدم عدداً من الأمثلة التي تعمل في بيئة الويندوز مثل التعامل مع حسابات المستخدمين (User Accounts) و إصلاح إدارة المهام (Task Manager) بالإضافة إلى كيفية إصلاح أداة الريجستري (register). بعد ذلك, سننتقل إلى المعيار POSIX لنناقش الحالات التي تكون فيها system غير آمنة و قد تؤدي إلى فتح ثغرات لسباب معينة, قبل أن نتحدث عن البدائل المقترحة لهذه لدالة في أنظمة Unix-like.
لنلق نظرة على الكود التالي :
#include <cstdlib> #include <iostream> int main(int argc, char** argv) { std::cout << "Apprendre à utiliser la fonction system"<<std::endl; int value_returned = std::system("PAUSE"); std::cout << "La valeur retournée est : "<<value_returned<< std::endl; return EXIT_SUCCESS; }
قمنا بالتحقق من القيمة المُعادة من طرف الدالة system هذه المرة, لكن في بقية المقالة لن نقوم بمثل هذا التحقق اختصاراً للوقت و تجبنا للتكرار.
عند ترجمة الكود و تنفيذ البرنامج سيتم إظهار الجملة التالية في شاشة الـ console :
Apprendre à utiliser la fonction system
لنعد قليلا إلى السطر السادس :
int value_returned = std::system("PAUSE");
لماذا كتبنا العبارة السابقة ؟
الهدف من العبارة السابقة هو إيقاف البرنامج حتى نَتَمَكن مِن مُشاهدة مخرجات البرنامج .. فكما تعلم أن الحاسوب بإمكانه القيام بآلاف العمليات الحسابية و المنطقية للدقيقة الواحدة بل أكثر! و نظرا لبطء الإنسان مقارنة مع الكومبيوتر فإننا نحتاج إلى إيقاف البرنامج حتى نشاهد النتائج.
ما هي الدالة system ؟
الدالة system هي دالة تعمل كوسيط بين المستخدم و نظام التشغيل أيا كان.
ملاحظة:
الأمر PAUSE يعمل في بيئة الـ Windows فقط, إذا كنت تعمل على Linux فيمكنك استخدام الأمر read كالتالي :
read -rsp $'Appuyer sur une touche pour continuer ...\n'
ما هي فائدة هذه الدالة؟
لنفرض أنك تستعمل نظام تشغيل لينكس أو ويندوز و تريد إدخال بعض أوامر DOS أو Shell في برنامجك, عندها ستتولى الدالة system هذه المهمة. لكن كيف؟ إليك المثال:
لنفترض أننا نريد مسح شاشة البـرنامج, طبعا بإمكاننا استخدام الدالة clrscr الموجودة في المكتبة conio.h لكننا هذه المرة سنستخدم الدالة system مع الأمر المناسب و هو cls إذا كُنتَ في الويندوز أو clear إذا كُنتَ في لينكس كالتالي:
system("cls"); system("clear");
ملاحظة:
conio.h هو أحد الـ header files الخاصة بلغة سي, كان يُستخدم في التسعينيات من القرن الماضي حيث كان يتواجد أساسا في المترجمات التي تعمل في بيئة الويندوز غير أنه لم يرد في الكتاب الأب The C Programming Language و لا ينتمي إلى المكتبة القياسية للغة السي كما أن المعيار POSIX أيضا لا يدعمه.
ما هي وسائط هذه الدالة ؟
الدالة system تستقبل وسيط واحد عبارة عن الأمر (system command) المُراد تنفيذه, الــ prototype الخاص بها هو :
#include<cstdlib> int system(const char* command);
مثلا في المثال الأول قمنا بإدخال الأمر PAUSE و هو أحد أوامر DOS مسؤول عن إظهار الجملة :
Appuyer sur une touche pour continuer ...
حيث يظل البرنامج ينتظر… حتى يضغط المستخدم على زر من لوحة المفاتيح حينها يتم الانتقال إلى الخطوة القادمة وهي:
return 0;
و تعني الخروج من البرنامج.
الآن أصبح بإمكاننا كتابة الكود المكافئ للعبارة الموجودة في السطر السادس من المثال الأول :
std::cout << "Appuyer sur une touche pour continuer ..." << std::endl; std::cin.ignore(std::numeric_limits<int>::max(), '\n');
باختصار:
تلعب الدالة system دور الوسيط بين المبرمج و أوامر الـ console حيث تُعتبر مسؤولة عن تنفيذ أوامر النظام.
لا شك أن الاستخدامات البدائية لهذه الدالة بسيطة جدا لكن مع ذلك, من الضروري جدا الانتباه جيدا أثناء استخدام هذه الدالة و التحقق من الأخطاء التي قد تنتج أثناء استخدامها.
من ضمن الملاحظات الهامة التي تجب مراعاتها عند استخدام هذه الدالة :
- يجب التحقق من أن الـ control processor مُتاح و على أتم الاستعداد.ا (1)
- بشكل عام, يُؤدي استخدام الـ system functions إلى كود لا يتوافق مع مختلف المنصات لأن أوامر النظام تختلف من نظام لآخر و قد تختلف أيضا في نفس النسخة من نفس النظام.ا (2)
- في الأصل, لا تُتيح مثل هذه الدوال تنفيذ عدة أوامر في آن واحد.
- القيمة المُعادة من طرف الدالة ليست سوى تلك التي أعادها الأمر الذي تم تنفيذه, أيضا القيمة المُعادة تختلف باختلاف الـ implementation.
- لا تسمح الدالة system بالتأكد من أن الأمر المُراد تنفيذه تم تنفيذه بنجاح أو لا.
- أثناء تنفيذ الأمر, يتم تعطيل SIGCHLD و تجاهل كلا من SIGINT و SIGQUIT. ا (3)
- لا تسمح system بفصل الـ controller عن الـ view إذا أردنا العمل وفقا لـ MVC.ا (4)
- احذر من استخدام system في برنامج بصلاحيات set-user-ID أو set-group-ID.ا (5)
توضيحات :
- (1) : باستخدام system(NULL)l : إذا كان الوسيط المُمرر عبارة عن مؤشر NULL, تقوم الدالة system بالتحقق من استعداد معالج الأوامر (control processor) دون أن تستدعي أي أمر آخر للتنفيذ.
- (2) : على سبيل المثال, يمكن للمستخدم إذا كان root تغيير كافة أوامر النظام بأخرى أو تعريف alias جديدة.
- (3) : SIGCHLD, SIGINT و SIGQUIT عبارة عن مجموعة signals مُعرفة في الملف signal.h. هذه الـ signals أو الإشارات توجد في الأنظمة التي تدعم المعيار POSIX.
- (4) : على سبيل المثال, سيتم الإنتقال مباشرة من الـ execution أو التنفيذ (controller) إلى إظهار الناتج (view) دون إمكانية تغيير الأخير أو تغييره بشكل محدود جداً.
- (5) : أي برنامج يعمل بصلاحيات set-user-ID , من المُحتمل جداً أن يكون سبب في التعرض لهجمات معينة أو الوقوع في ثغرات خصوصا إذا كان البرنامج يتفاعل (و لو يسيراً) مع مستخدم يختلف عن الذي قام ببرمجته و بالتالي يجب دائما أخذ مثل هذه الإعتبارات عند التعامل مع برنامج بصلاحيات مرتفعة.
سنقوم الآن بإعطاء بعض الأمثلة (الخاصة بالـ Windows) توضيح كيفية استخدام هذه الدالة حتى تتعرف أكثر على مدى أهميتها.
المثال الأول:
اكتب برنامج يُظهر كلا من الوقت و التاريخ الحاليين مع إمكانية تغيـيـرهما.
في هذا المثال سنستعمل الأمرين time و date, الأمر الأول مسؤول عن الوقت أما الثاني فمسؤول عن التاريخ و الكود الآتي يوضح ما سبق:
#include <cstdlib> #include <iostream> using namespace std; int main(int argc, char** argv) { system("TIME"); system("DATE"); system("CLS"); return EXIT_SUCCESS; }
أما إذا أردنا عدم تغيير الوقت فيمكننا استخدام الــ switch المخصص وهو /t هكذا:
time /t
ملاحظة:
السي++ القياسية لا تحتوي على دوال لمعالجة الوقت و التاريخ لكن مع ذلك, إضافة إلى الدالة system توجد عدة طرق أخرى لمعرفة الوقت و التاريخ, نذكر منها على سبيل المثال (1):
- الدالة time الموجودة في الملف الرأسي ctime.
- الدالة GetSystemTime الموجودة في الملف الرأسي windows.h.
- الفئتين CTime و CTimeSpan بالنسبة لمبرمجي Visual CPP و MFC.
- الفئتين TDate و TDateTime بالنسبة لمبرمجي C++ Builder و VCL.
- المكتبة boost::date_time بالنسبة لمن يريد الحفاظ على الــ code portability.
المثال الثاني:
اكتب برنامج يُظهر أربع خيارات للمستخدم على الشكل التالي:
- إيقاف تشغيل الجهاز
- إعادة التشغيل
- إنهاء جلسة المستخدم
- الخروج من البرنامج
وعند الضغط على 1 يتم إيقاف الكومبيوتر, 2 تتم إعادة التشغيل و هكذا بالنسبة لبقية الأوامـر..
في هذا المثال سنحتاج إلى الأوامر التالية :
SHUTDOWN /S /T 00 SHUTDOWN /R /T 00 SHUTDOWN /L EXIT
ماهو الأمر SHUTDOWN ؟
الأمر SHUTDOWN يسمح بالانتقال بين مختلف حالات نظام التشغيل (shutdown, restart, standby, hibernate) و يملك العديد من الــ switches منها على سبيل المثال:
- /i : التعامل مع مختلف وضعيات نظام التشغيل باستخدام واجهة رسومية.
- /l : اغلاق جلسة المستخدم.
- /s : ايقاف نظام التشغيل.
- /r : إعادة التشغيل.
- /h : وضع في حالة hibernate
- /t xxx : تحديد الفترة الزمنية قبل تنفيذ الأمـر حيث تُحسب هذه الفترة بالثواني.
- /a : الغاء ايقاف الجهاز.
- /f : يُجبر جميع التطبيقات القيد التشغيل على التوقف و يقوم بإغلاق الجهاز بشكل فوري دون تحذير المستخدمين.
لمعرفة المزيد استعمل الأمر SHUTDOWN /HELP.
لم يبقى إلا الأمر EXIT و أعتقد أن الجميع يعرفه. الآن صار بإمكاننا كتابة الكود:
#include <cstdlib> #include <iostream> using namespace std; int main(int argc, char** argv) { char choix; cout <<endl <<"\t\t***************************************"<<endl <<endl <<"\t\t ******* La commande SHUTDOWN ******* "<<endl <<endl <<"\t\t***************************************"<<endl; cout <<endl <<"1 - Arreter l'ordinateur."<<endl <<endl <<"2 - Redermarrer l'ordinateur."<<endl <<endl <<"3 - Fermer la session."<<endl <<endl <<"1 - Quitter le programme."<<endl; cout <<endl <<"Donnez votre choix : "<<endl; cin >>choix; switch (choix) { case '1':system("SHUTDOWN /S /T 00"); break; case '2':system("SHUTDOWN /R /T 00"); break; case '3':system("SHUTDOWN /L"); break; case '4':system("EXIT"); break; default:cout <<"Erreur, choix incorrect !"<<endl; } return EXIT_SUCCESS; }
الآن سنقوم بإضافة بسيطة للكود السابق… وهي تغيير لون الكتابة و لون الخلفية أيضا. سنستخدم الدالة system و بالضبط مع الأمر color الذي يملك وسيطين يُمثلان لون الخلفية و لون الكتابة على التوالي, هذين الوسيطين يُكتبان على شكل ست عشري أو Hexadecimal و الجدول التالي يوضح كل رقم مع اللون الذي يقابله :
طيب الآن سنقوم بإضافة السطر:
system("COLOR 2E");
ليصبح الكود هكذا:
char choix; system("COLOR 2E"); cout <<endl <<"\t\t***************************************"<<endl <<endl <<"\t\t ******* La commande SHUTDOWN ******* "<<endl <<endl <<"\t\t***************************************"<<endl;
بالنسبة لي, اخترت اللونين الأصفر و الأخضر لأنهما يمثلان علم دولتي: موريتانيا و أنت يمكنك اختيار ما يعجبك, فقط راجع الجدول.و هذه واجهة البرنامج :
ملاحظة:
في معظم الأحيان ستلاحظ أن استخدام الدالة system لتلوين الواجهة غير مجدي لأنها تسمح بظهور لون واحد فقط و بالتالي باستخدامها لا يمكنك تلوين عدة أسطر بألوان مختلفة. لفعل هذا الشيء يمكنك استخدام الدالة SetConsoleTextAttribute الموجودة في المكتبة windows.h و تأخذ وسيطين, الأول هو الــ HANDLE و الثاني هو الرقم الست عشري الذي يمثل اللون. و هذا مثال بسيط على استدعاء الدالة:
SetConsoleTextAttribute(GetStHandle(STD_OUTPUT_HANDLE), 14);
المثال الثالث :
اكتب برنامج يقوم بتغيير باسوورد المستخدم.
في هذا المثال سنحتاج إلى الأمر net user و هذا الأمر في الحقيقة هو أحد وسائط الأمر net لاي يسمح بإرسال أوامر عن طريقة الشبكة مع العلم أن الأمر NET يملك العديد من subcommands منها:
- NET SEND : لارسال رسالة إلى جهاز عن بعد.
- NET START لتشغيل خدمة.
- NETSTAT : تعرض اتصالات الشبكة الواردة والصادرة و جداول التوجيه وعدد من الاحصاءات الأخرى.
- NET TIME : عرض الوقت و التاريخ الحالي حسب توقيت الــ time server domain.
- NET FILE : اغلاق ملف تم اشتراكه على الشبكة و تحرير مصادر النظام التي كان يحجزها.
لنعد الآن إلى الأمر NET USER الذي يسمح بالتعامل مع حسابات المستخدمين في الويندوز. هذه بعض الــ switches الاكثر استعمالا :
- /ADD : اضافة حساب جديد
- /DELETE : حذف حساب معين
- ACTIVE:{YES | NO : تعطيل أو تفعيل الحساب
- EXPIRES:date | NEVER : امكانية تحديد تاريخ انتهاء صلاحية الحساب.
- PASSWORDCHG:YES | NO : تحديد ما إذا كان المستخدم يمكنه تغيير كلمة المرور الخاصة به.
- PASSWORDREQ:YES | NO : تحديد ما إذا كان المستخدم يمكنه تعريف كلمة مرور لحسابه أو لا.
- LOGONPASSWORDCHG:YES|NO : هل يجب يجب على المستخدم تغيير كلمة مروره عند تسجيل الدخول القادم.
للمزيد من المعلومات أدخل الأمر NET USER /HELP.
بالنسبة للتابع user فبإمكانه إضافة مستخدم جديد أو حذفه أو تغيير كلمة المرور الخاصة به لكننا هنا سنقتصر على تغيير الباسوورد . الشكل العام للأمر هكذا:
NET USER [user_name] [password]
في هذه الحالة فإن الأمر يستقبل واسطين الأول هو اسم الحساب و الثاني هو كلمة المرور الجديدة. الآن صار بإمكاننا الآن تغيير الباسوورد.. كما في الكود الآتي:
#include <cstdlib> #include <iostream> using namespace std; int main(int argc, char** argv) { system("USER NET %USERNAME% 123456"); return EXIT_SUCCESS; }
ملاحظة:
الحصول على system error 5 عند تنفيذ الكود يعني أنك لا تملك الصلاحيات الكافية لتنفيذ الأمر NET USER من أجل تغيير كلمة المرور لذا قم بتشغيل الملف التنفيذي كــ administrator.
المثال الرابع:
معظم الفيروسات الصغيرة المنتشرة الآن تقوم بتعطيل إدارة المهمام حتى لا يتمكن المستخدم من الوصول إلى الــ process الخاص بالفيروس و توقيفه, نفس الشيء يحدث مع registry خوفا من حذف القيمة التي تُمكن الفيروس من العمل عند اقلاع الجهاز. طبعا توجد فيروسات أكثر ذكاء حيث تقوم بإخفاء الــ process من إدارة المهام و بالتالي قد يصعب التعامل معها يدويا في هذه الحالة.
في هذا المثال, سنقوم بكتابة كود يقوم بتفعي أو تعطيل Task Manager و registry و الهدف من هذا المثال هو امكانية اصلاح هذه الأدوات عند تعرضها للهجوم الذي ذكرناه سابقا.
الأمر الذي يُمكن من اصلاح ادارة المهام هو:
REG ADD hkcu\Software\Microsoft\Windows\CurrentVersion\policies\system /v DisableTaklMgr /t reg_dword /d 1
الأمر REG ADD يمكن من إضافة مفتاح إلى الرجيسرتي.
- hkcu\Software\Microsoft\Windows\CurrentVersion\policies\system يُمثل مسار المفتاح المُضاف.
- /v Disable TaskMgr : اسم المفتاح.
- /t reg_dword : نوع المفتاح.
- /d 1 : فيمة المفتاح (1 تعني تعطيل و 0 تعني تفعيل)
و هذا هو الكود :
#include <cstdlib> using namespace std; int main(int argc, char** argv) { system("REG ADD hkcu\\Software\\Microsoft\\Windows\\CurrentVersion" "\\policies\\system /v DisableTaklMgr /t reg_dword /d 1"); system("PAUSE"); return EXIT_SUCCESS; }
نفس الشيء بالنسبة للرجيستري:
#include <cstdlib> using namespace std; int main(int argc, char** argv) { system("REG ADD hkcu\\Software\\Microsoft\\Windows\\CurrentVersion" "\\policies\\system /v DisableRegistryTools /t reg_dword /d 0"); system("PAUSE"); return EXIT_SUCCESS; }
خدعة:
باستخدام الأمرين REG ADD و SHUTDOWN معا يمكننا إنشاء فيروس خطير و مزعج. كالتالي:
- نقوم بإنشاء ملف BATCH لإعادة تشغيل الجهاز باستخدام الأمر SHUTDOWN .
- ننشئ ملف BATCH آخر لإضافة الملف السابق إلى الرجيستري من أجل تشغيله عند اقلاع الجهاز.
- نحذف الملف السابق حتى لا نترك أثرا للفيروس.
- نستخدم الأداة Iexpress لدمج الملفين في ملف واحد تنفيذي.
خطر استخدام الدالة system في المعيار POSIX:
لا شك أن جميع مستخدمي أنظمة لينكس يعرفون جيدا هذه الدالة, لكن على عكس عائلة exec (مثل execve()) تستخدم الدالة system الــ environment variables من أجل البحث عن الملف المُراد تنفيذه, أيضا, system تعتمد خصوصا على $PATH الذي يحتوي على لائحة المجلدات التي تحتوي الملفات التي يمكن استخدامها دون تحديد المسار الكامل للملف.(2) على سبيل المثال, PATH يحتوي على /bin و بالتالي إذا اردت تنفيذ الامر /bin/commande يمكنك كتابة ما يلي داخل الشل :
$ commande
لتنفيذ أمر أو استدعاء برنامج بلغة C يُنصح دائما باستخدام أحد افراد العائلة exec لأن الدالة system تستخدم الــ environment variables لتحديد مسار الملف المُراد تنفيذه, السبب في الثغرة يعود إلى أن هذه المتغيرات يمكن للمستخدم تغييرها !
على سبيل المثال إذا كان لدينا كود يحوي الأمر التالي:
system("cat /etc/passwd");
يمكننا ببساطة تغيير الــ PATH بحيث يتم تحويل استدعاءات النظام إلى المسار الجديد:
export PATH=/home/snacker:$PATH
في هذه الحالة, سيقوم الشل بالبحث عن الملف cat داخل المجلد
لآن سنقوم بإنشاء برنامج ثاني يقوم باستدعاء cat الموجود داخل المجلد البرنامج الجديد يحتوي على الأمر التالي :
std::cout << "La fonction system est vulnérable"<<std::endl;
مخرجات الكود ستكون “La fonction system est vulnérable” بدلا من محتوى الملف و بالتالي نلاحظ جيدا أن الدالة system قامت بتنفيذ cat الموجود في في الــ PATH الجديد.
عائلة exec و الدالة system, أيهما أفضل ؟
يقوم أعضاء هذه العائلة بعمل covering للـ current process بمعنى أنه يتم استبدال الـ process الذي قام بعملية الإستدعاء بآخر دون تغيير الـ PID. الوسيط الأول لكافة أفراد هذه العائلة عادة ما يكون مسار الملف المُراد تنفيبذه. (3)
تحتوي العائلة exec على مجموعة من الدوال تختلف باختلاف طبيعة عملها :
#include <unistd.h> extern char ** environ; int execl (const char * chemin, const char * arg, ...); int execlp (const char * fichier, const char * arg, ...); int execle (const char * chemin, const char * arg , ..., char * const envp[]); int execv (const char * chemin, char * const argv []); int execvp (const char * fichier, char * const argv []); int execve (const char * chemin, char * const argv [], char * const envp[]);
تستقبل دوال execl اسم البرنامج الذي سيتم تنفيذه كوسيط أول بينما يكون الوسيط الثاني : الأمر المُراد تنفيذه ثم تأتي بعد ذلك مجموعة الـ arguments التابعة لهذا الأخير و تنتهي بـ NULL. الوسيط الأول و الثاني يجب أن يكونا متطابقين بالنسبة لدوال execl.
مثال :
execl("/usr/bin/ls", "/usr/bin/ls", "-l", NULL);
أما بالنسبة لـ execv و execvp فيستخدمان مصفوفة مؤشرات تنتهي بـ NULL و ينتهي كل مؤشر فيها بالمحرف NUL. هذه المصفوفة تُمثل الوسائط الممكنة للأمر المُراد تنفيذه.
اتُفق على أن الوسيط الأول يُشير دائما إلى مسار الملف أو البرنامج الذي سيتم استدعاءه.
عندما يُعيد أحد أفراد exec قيمة معينة, فهذا يعني أنه قد حدث خطأ ما. عادة ما تكون القيمة المُعادة -1 حيث تحتوي errno على الكود المعاد من طرف الدالة.
بقي أن نذكر أن عائلة exec خاصة بأنظمة Unix-like, في الويندوز لا أعلم تحديدا ماالذي قد تُعطيه, لأنني أعتقد أنه لا يمكننا ملائمة عمل أفراد هذه العائلة في منصة الـ Windows نظرا إلى أن الـ managing processes تختلف بشكل كبير جداً.
إذا كنتَ لا ترغب في استخدام الدالة system في الـ Windows فيمكنك استدعاء الدالة createProcess. (في الحقيقة, فإن system تستدعي createProcess في بيئة الـ Windows)
أخيرا, عند استدعاء system سيتم اتباع الخطوات التالية : (للأسف, كتتبُتها بالفرنسية لأنني لم أجد ترجمة دقيقة للكلمات التي وضعتُها في المخطط)
بينما يتم اتباع الخطوات الآتية عند استدعاء fork+exec :
مالحل ؟
ليس من السهل ايجاد بديل حقيقي للدالة system, أحد الحلول الممكنة هو استخدام execl أو execle مع العلم أن طريقة العمل تختلف بشكل كامل لأن الملف المُستدعى لن يتم تنفيذه كــ subroutine و لكن سيحل محل الــ running processes.ا(4)
بشكل عام, يُنصح باستخدام المكتبات التي توفرها سي++ إن وُجدت و إلا فيمكن استخدام أحد أفراد العائلة exec خصوصا في الأنظمة التي تدعم POSIX و system في حالات معدودة و مُحددة.
روابط مفيدة
(1) Comment gérer les dates et les heures en CPP
(2) La faille de system
(3) Man page 3 : exec
(4) Éviter les failles de sécurité dès le développement d’une application
أرجو أن أكون قد وُفقت في الشرح و أرجو لكم الاستفادة أيضا و لا تنسوني من صالح دعائكم.
تحياتي
مقالة مفيدة جداً .. مشكور ويعطيك الف عافية 🙂
شكرا أخي : )
شكرا يا أخى على المقال الشامل..
لدي سؤال … ما الفرق بين إستدعاء أوامر النظام كما فصلت أنت و بين إستدعائها ك System calls ؟
أهلا استاذي هويدي, لك وحشة : )
لم أفهم سؤالك جيدا, تقصد مثلا الفرق بين طريقة عمل الدالة system و عائلة exec ؟
شكرا .. اود الاستفسار عن كود معين يعمل على تخيير المستخدم بين امرين في حالة اختيار الامر الاول يتم اعادة البرنامج وفي حالة اختيار الامر الثاني يتم الخروج من النظام