مبهم سازی کد در .NET و Java

چكیده

در این مقاله به بررسی چگونگی تولید٬ کامپایل و اجرای نرم افزارهای مبتنی بر .NET و Java در مقایسه با نرم افزارهای تولید شده با زبان های برنامه نویسی سنتی به علاوه تشریح کامل ساختار آن می پردازیم. همچنین به بحث پیرامون چگونگی Reverse Enginering و Decompile این دست از نرم افزارها خواهیم پرداخت.

در پایان راه کارهائی را جهت مقابله با موضوعات مطرح شده ارائه می نمائیم.


مقاله ی حاضر پیش از این در شماره ی ۲ مجله ی Snoop – فروردین ماه ۱۳۸۸ – به چاپ رسیده است.
تاریخ نگارش مقاله: اسفندماه ۱۳۸۷


مقدمه

مدت ها پیش دوستی از سر لطف کتابخانه ای را که برای تبدیل تاریخ میلادی به شمسی با .NET طراحی و کامپایل کرده بود را برای من فرستاده بود (غافل از این که کتابخانه ای بس کامل تر توسط مایکروسافت در .NET 2.0 به بعد گنجانده شده است). پس از چند دقیقه و انجام چند کلیک در کامپیوترم در پاسخ ایمیلی که ارسال کرده بود سورس کد کامل کتابخانه اش را به همراه توضیحات کافی در کمال ناباوری و ناراحتی دریافت کرد! البته تنها این مورد نیست تا به حال هر فردی که نسخه ای کامپایل شده از برنامه ای که مبتنی بر .NET یا Java طراحی کرده را برای من ارسال نموده در پاسخ با سورس کد برنامه اش مواجه شده است.

براستی٬ آیا .NET/Java == Open Source؟؟؟ جهت دریافت پاسخ٬ ابتدا به بررسی ساختار این دست از برنامه ها می پردازیم تا به چگونگی فرآیند Compile/Decompile و در نهایت ساختار آن ها پی ببریم.

Unmanaged Code

اجازه دهید پیش از بررسی فرآیند اجرای کدها در .NET/Java به بررسی فرآیند تولید و اجرای یک نرم افزار در زبان های سنتی مانند C/C++ بپردازیم.

تصویر1.A. – شمای کلی یک برنامه مدیریت نشده

تصویر1.A. – شمای کلی یک برنامه مدیریت نشده

همانطور که مشاهده می نمائید (تصویر 1.A.) شمای کلی تولید و اجرای یک محصول نرم افزاری عبارتست از:

  • طراحی و برنامه نویسی محصول توسط یک یا چند برنامه نویس
  • کامپایل کد به زبانی که برای CPU و سیستم عامل مقصد قابل فهم باشد
  • اجرای مستقیم برنامه در کامپیوتر مقصد

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

از آنجا که هر CPU و سیستم عاملی مجموعه دستورالعمل های (کد محلی) خود را دارا می باشد پس جهت اجرای یک کد منبع مشترک بر روی پلتفرم (CPU یا سیستم عامل) های متعدد لازم است کدها مجددا ]اصلاح و[ کامپایل شوند.

در واقع از سیستم عامل به سیستم عامل و از پردازنده به پردازنده به دلیل تفاوت در معماری٬ کد محلی متفاوت می باشد. برای مثال کدی که برای یک PowerPC با پردازنده IBM و سیستم عامل Mac OS X کامپایل شده است برای یک Intel Mac با پردازنده Intel Core 2 Duo با سیستم عامل یکسان کاملا متفاوت است. یا کد محلی سیستم عامل Linux بر روی یک Pentium IV با کد محلی سیستم عامل Windows بر روی همان پردازنده متفاوت می باشد.

عیب عمده این روش عدم پرتابل بودن برنامه است که برای هر سیستم عامل یا پردازنده خاص٬ برنامه نیازمند کامپایل مجدد می باشد. البته در اکثر مواقع نیاز به دوباره نویسی کد برنامه نیز می باشد.

یکی دیگر از معایب این روش این است که عمدتا در زمان کامپایل کدهای برنامه٬ کامپایلر توانائی بهینه سازی کد محلی را فقط برای یک دسته خاص از پردازنده های سازگار با هم دارا می باشد. برای مثال ممکن است کمپانی Adobe در زمان کامپایل نرم افزار Flash Player آن را برای پردازنده های Pentium III (دارای معماری x86) بهینه کرده باشد٬ در این حالت کاربری که برای مثال از یک پردازنده جدیدتر مانند Core 2 Duo و یا AMD (دارای معماری x86_64) استفاده می نماید قطعا نمی تواند حداکثر کارائی را در زمان اجرای این نرم افزار از پردازنده خود انتظار داشته باشد.

برنامه هائی که به این روش تولید می شوند Unmanaged Code یا کد مدیریت نشده نامیده می شوند.

Managed Code

در این قسمت از توضیح ماشین مجازی Java خودداری نموده٬ در عوض به دلیل تسلط نگارنده بر .NET به توضیح ساختار ماشین مجازی آن می پردازیم (یادآور می شویم که در واقع بسیاری از تکنیک های بکار رفته در تکنولوژی .NET و .NET Framework (به اختصار .NET FX) اقتباسی از Java و Java Virtual Machine (به اختصار JVM) می باشد٬ پس بسیاری از مطالبی که در ادامه می آید در مورد Java نیز صادق است).

تصویر2.A. – شمای کلی یک برنامه مدیریت شده

تصویر2.A. – شمای کلی یک برنامه مدیریت شده

تصویر2.B. – ساختار کلی .NET FX

تصویر2.B. – ساختار کلی .NET FX

همانطور که از تصاویر پیداست (تصاویر 2.A. و 2.B.) پروسه تولید Native Code یا همان کد محلی و اجرای آن در سیستم در تکنولوژی هائی مانند .NET یا Java به مراتب پیچیده تر است.

و اما .NET چیست؟ در یک جمله: .NET یک پلتفرم و نگرش جدید جهت طراحی و توسعه نرم افزار می باشد. دقت کنید که در تعریف ارائه شده .NET به عنوان یک زبان برنامه نویسی تعریف نشده است. در واقع .NET یک کتابخانه مشترک است که توسط هر زبان برنامه نویسی که کامپایلری برای .NET داشته باشد قابل استفاده خواهد بود. در حال حاضر بیش از ۳۰ زبان برنامه نویسی توانائی بهره برداری از پلتفرم .NET را دارا می باشند. زبان هائی از قبیل C#, C++, Visual Basic, Delphi, Perl, PHP. حتی زبان های قدیمی تر مانند Cobol, Fortran و Ada با نام A#. در این میان برای برخی زبان ها نیز چندین کامپایلر وجود دارد مثلا برای Python با نام های Cobra, IronPython, Boo٬ و یا برای Ruby نسخه CLR1 به نام Ruby.NET و نسخه DLR2 به نام IronRuby.

یقینا بهترین زبان برنامه نویسی برای کار با .NET٬ زبان C# می باشد. چرا که این زبان بر خلاف سایر زبان های برنامه نویسی قابل پشتیبانی توسط .NET از ابتدا برای استفاده در این چارچوب طراحی و بهینه شده است. برای مثال Visual Basic .NET و یا JScript .NET از زبان هائی اقتباس شده اند که پیش از ظهور .NET وجود داشته اند و برای حفظ سازگاری با نسخ پیشین ناگزیر قادر به استفاده از تمامی بخش های آن نیستند. تنها زبان برنامه نویسی که در پاره ای از موارد – به دلیل توانائی تولید همزمان Managed Code و Unmanaged Code – جهت استفاده در .NET بر C# پیشی می گیرد C++/CLI می باشد. ضعف C# در مقابل C++ در این نکته نهفته است که C# تنها قادر به تولید Managed Code می باشد در حالی که C++ علاوه بر آن قادر به تولید Unmanaged Code نیز می باشد. متاسفانه بوسیله Managed Code به هیچ وجه امکان دسترسی به مجموعه دستورات CPU به نام SSE 1-4.23 و یا دسترسی به Pipeline کارت گرافیک و هسته آن به نام GPU وجود ندارد (این قابلیت ها در اکثر نرم افزارهای Multimedia و بازی ها نیاز می باشد). خاطر نشان می شویم که در صورت نیاز می توان از دسترسی مستقیم به API سیستم عامل و یا دسترسی مستقیم به حافظه توسط اشاره گرها که جزو ویژگی های ذاتی Unmanaged Code می باشد در C# نیز همانند C++ بهره جست (این قابلیت ها به صورت پیش فرض غیر فعال می باشد).

علاوه بر موضوع مستقل بودن از زبان برنامه نویسی٬ پلتفرم .NET تنها مختص به ویندوز نمی باشد٬ هر چند که به صورت رسمی از سوی Microsoft تنها Windows پشتیبانی می شود. البته تلاش هائی نظیر Mono4 و DotGNU5 در جهت پیاده سازی پلتفرم .NET در سایر سیستم عامل ها توسط جامعه کد باز صورت گرفته است. در حال حاضر نرم افزارهای گوناگونی براساس پلتفرم .NET برای سایر سیستم عامل ها و دستگاه های قابل حمل از جمله تعدادی بازی برای iPhone بوسیله Mono توسعه یافته اند6. همچنین نسخه ای از .NET FX به نامMicrosoft .NET Compact Framework یا به اختصار .NET CF ویژه دستگاه های قابل حملی که از Windows CE استفاده می نمایند با امکانات محدودتر در دسترس می باشد.

.NET FX از دو بخش عمده تشکیل شده است:

  • CLR
  • کتابخانه یکپارچه کلاس یا UCL7

کتابخانه کلاس در .NET FX شامل مجموعه فضاهای نامی (Namespace) و کلاس ها و توابعی است که تمامی زبان های .NET برای مثال جهت دسترسی به پایگاه داده٬ کار با فایل ها٬ استفاده از منابع شبکه٬ دسترسی به وب سرویس ها٬ پردازش XML و… از آن استفاده می کنند. به همین دلیل این بخش٬ کتابخانه کلاس یکپارچه نامیده می شود.

این کتابخانه خود از دو بخش کلی به نام های BCL8 و FCL9 تشکیل شده است. بخش BCL که در واقع زیر مجموعه ای از FCL می باشد موظف به فراهم نمودن کلاس های عمومی و اصلی٬ در واقع هسته .NET FX٬ از قبیل خواندن و نوشتن فایل ها٬ پردازش XML٬ پایگاه داده و رندر گرافیک می باشد. در واقع اکثر کلاس ها و فضاهای نامی که زیر مجموعه فضای نام System می باشد٬ مانند System.IO, System.NET, System.Security, System.Reflection, etc.

FCL نیز مجموعه ای از تمامی کلاس ها و فضاهای نامی ارائه شده در .NET FX می باشد. به بیان بهتر FCL = BCL + All System.* + Microsoft.*.

و اما CLR که بخش عمده ای از .NET FX را به خود اختصاص داده است اساسی ترین بخش درگیر .NET FX از ابتدای شروع به اجرای یک برنامه مبتنی بر .NET تا خاتمه اجرای آن است. سایر مولفه هائی که شالوده .NET FX را تشکیل می دهند زیر مجموعه CLR می باشند. CLR مراقب اجرای صحیح کدها و مدیریت آن در زمان اجرای یک برنامه می باشد. به علاوه سرویس های گوناگون مفیدی از قبیل مدیریت حافظه٬ مدیریت رشته ها٬ تضمین امنیت برنامه٬ صحت اجرای کد٬ تولید کد محلی مناسب و سایر خدمات سیستمی که یک برنامه برای اجرا نیازمند آن است را فراهم می نماید. برنامه هائی که مبتنی بر CLR هستند از قابلیت هائی نظیر امنیت پیشرفته٬ کنترل خطا٬ سیستم خطایابی و … نیز بهره مند خواهند شد.

CLR نه تنها قلب تپنده .NET FX می باشد بلکه هر برنامه مبتنی بر .NET جهت اجرا نیازمند وجود آن می باشد. هر برنامه ای که تحت کنترل CLR اجرا شود Managed Code یا کد مدیریت شده نامیده می شود.

یکی از برجسته ترین ویژگی های Managed Code استفاده ی از بخشی از CLR به نام Garbage Collection یا به اختصار GC است. عمده ترین مشکلی که برنامه نویسان سایر زبان های برنامه نویسی مانند C++ با آن روبرو هستند عمل تخصیص دستی حافظه و رها سازی آن پس از اتمام عملیاتی خاص است (مدیریت ناگزیر حافظه به شکل دستی). GC بخش خودکار تخصیص و رهاسازی حافظه در CLR می باشد که در زمان مناسب با توجه به نیاز برنامه به شکلی زیبا و بدون نقص منابع حافظه را میان برنامه های .NET مدیریت می کند. برای مثال چنانچه یک Memory Stream یا یک شی نگهدارنده اطلاعات پایگاه داده درون یک تابع تعریف شده باشد با خروج از تابع نیازی به رها سازی دستی آن نیست٬ زمانی که GC از صحت اجرای کد و عدم استفاده از شی مورد نظر اطمینان حاصل کند حافظه تخصیص یافته به شی بطور کامل بازیابی خواهد شد.

همانطور که در تصاویر 2.A. و 2.B. آمده است٬ مراحل تولید تا اجرای یک برنامه از نوع Managed Code به شرح ذیل می باشد:

  • تهیه کد برنامه به یکی از زبان های مبتنی بر .NET
  • کامپایل کد برنامه به زبان میانی در قالب یک فایل به ظاهر قابل اجرا (.exe, .dll) که یک .NET Assembly10 نامیده می شود (دقت کنید که این مفهوم کاملا با زبان اسمبلی متفاوت است). این فایل به عنوان محصول نهائی در اختیار کاربران قرار خواهد گرفت.
  • اولین اجرای برنامه در سیستم مقصد و ترجمه نهائی کد میانی به کد محلی قابل اجرا در کامپیوتر مقصد توسط بخشی از CLR به نام JIT11.

همانطور که تاکید شد با کامپایل اولیه کدی که مبتنی بر .NET نوشته شده است٬ Native Code تولید نخواهد شد بلکه کد برنامه به کدی با نام CIL12 و یا MSIL13 – به معنای زبان میانی – ترجمه و در یک فایل اسمبلی ذخیره خواهد شد14. این فایل اسمبلی با وجود این که – بسته به نوع پروژه – دارای پسوند .exe یا .dll می باشد به هیچ وجه قابل اجرا نخواهد بود. چنانچه برنامه در سیستمی اجرا شود که فاقد .NET FX باشد با وجود حضور Header یک فایل اجرائی در ابتدای فایل با پیغام خطای ذیل مواجه خواهیم شد (تصویر 2.C.):

تصویر2.C. – اجرای یک برنامه مبتنی بر .NET بدون حضور .NET FX

تصویر2.C. – اجرای یک برنامه مبتنی بر .NET بدون حضور .NET FX

در پاره ای از موارد نیز ممکن است با پیام Inavalid Win32 application مواجه شویم. این بدان دلیل است که محتویات فایل اسمبلی فقط برای CLR قابل فهم می باشد و در فقدان آن کامپیوتر هرگز به منظور کدهای ذخیره شده در فایل پی نخواهد برد.

یک اسمبلی علاوه بر کدهای CIL شامل Metadata (اطلاعاتی که سایر اطلاعات موجود در اسمبلی را توصیف می کنند) به علاوه Resource های موردنظر برنامه مانند تصاویر٬ آیکن ها و …. می باشد. در واقع با وجود اطلاعات متا دیگر نیازی به رجیستر کردن فایل های کتابخانه ای (.dll) مبتنی بر .NET در سیستم نمی باشد؛ برخلاف برنامه های Unmanaged Code که بایستی توسط regsvr32.exe تمامی فایل های کتابخانه را به سیستم معرفی نمود. در واقع هر اسمبلی در اولین بار اجرای خود٬ خود را کاملا به سیستم معرفی می نماید.

با وجود این ویژگی در اسمبلی ها دیگر نیازی به ساخت برنامه نصب برای نرم افزارها احساس نمی شود و می توان با کپی نمودن فایل های برنامه در صورت وجود .NET FX برنامه ها را مستقیما و بدون هیچ گونه اشکالی اجرا نمود. چرا که خصوصیت کلیدی همه اسمبلی ها قابلیت خود شرح بودن آنهاست. به بیان بهتر تمامی اطلاعاتی که برای درک چگونگی استفاده از یک اسمبلی مورد نیاز است در داخل خود اسمبلی قرار دارد. در داخل هر اسمبلی دو گونه Metadata موجود است: اطلاعات مربوط به خود اسمبلی و اطلاعات الگو. این اطلاعات توسط فضای نام System.Reflection قابل استخراج خواهد بود.

اسمبلی ها به صورت خصوصی و اشتراکی مورد استفاده قرار می گیرند. اسمبلی های خصوصی معمولا در پوشه ی برنامه و یا زیر پوشه های آن قرار می گیرند. از سوی دیگر اسمبلی های اشتراکی که توسط برنامه های متعددی مورد استفاده قرار می گیرند در مکانی تحت عنوان GAC15 نگهداری می شوند. GAC در واقع چیزی بیش از یک پوشه مجازی که آدرس٬ مشخصات و ساختار اطلاعاتی اسمبلی های مشترک میان برنامه های CLR را نگهداری و ارائه می کند نیست. در ضمن تمامی فضاهای نام .NET در قالب .dll به علاوه BCL که در فایل mscorlib.dll می باشد در این مکان قرار گرفته اند (تصویر 2.D.).

تصویر2.D. – پوشه GAC و محتویات آن

تصویر2.D. – پوشه GAC و محتویات آن

در واقع فایل های داخل این پوشه در مسیرهای دیگری از هاردیسک ذخیره شده اند اما توسط این پوشه مورد دسترسی قرار می گیرند. یکی از خصوصیات ارزشمند GAC قابلیت ذخیره اسمبلی های هم نام با نگارش های مختلف می باشد. چنانچه قصد مقیم نمودن اسمبلی خاصی را در این مکان دارید بایستی از یکی از ابزارهای .NET FX به نام gacutil.exe استفاده نمائید.

همانطور که اشاره شد کد CIL موجود در اسمبلی که در زمان کامپایل اولیه توسط برنامه نویس تولید می شود بدون وجود CLR هرگز قابل اجرا نخواهد بود چرا که برای پردازنده و سیستم عامل این کدها غریب و نا آشنا هستند. کد CIL نوعی کد اسمبلی شی گرا می باشد که آخرین سطح زبان برنامه نویسی قابل درک برای انسان در CLI0 می باشد. زبان CIL یک زبان کاملا Stack-Based است. همچنین این زبان مستقل از پلتفرم می باشد. به علاوه٬ سورس کد برنامه به هر زبانی که باشد در نهایت تبدیل به CIL خواهد شد و از لحاظ سرعت اجرا هیچ زبانی بر زبان دیگر در این پلتفرم پیشی نمی گیرد. البته در پاره ای از موارد به دلیل این که زبانی مانند C# برای مثال نسبت به VB .NET از ساختار بهتری برخوردار است کدهای CIL بهینه تری تولید می نماید که در مجموع تا حدودی سریعتر اجرا می شوند.

دلیل این امر این است که لزوما همه زبان های .NET از یک استاندارد صد در صد مشخص استفاده نمی نمایند. برای مثال C# یک زبان Type-Safe است اما در مقابل C++/CLI این گونه نیست. البته مجموعه حداقل استانداردهائی تحت عنوان CLS16 در .NET در نظر گرفته شده است که هر زبان برنامه نویسی که داری یک کامپایلر .NET است از آن پیروی می نماید. CLS زیر مجموعه استاندارد دیگری تحت عنوان CTS17 می باشد. CTS چگونگی تعریف داده ها٬ کاربرد و مدیریت آن ها به علاوه یکسان سازی داده ها با داده های زبان میانی٬ امنیت نوع داده و کارائی آن ها را جهت اجرا توصیف می نماید. به زبان ساده تر CTS موظف به تبدیل داده های درونی زبان به نوع داده های قابل استفاده در CIL و ایجاد همگونی میان انواع داده ها در تمامی زبان های .NET می باشد. برای مثال داده های درونی C# مانند short, int, long, double به ترتیب در CIL تبدیل به Syetm.Int16, System.Int32, System.Int64, System.Double می شوند. در مقابل داده ای درونی در JScript .NET به نام Number وجود دارد که تبدیل به نوع System.Double می شود پس یک برنامه JScript .NET که از یک فایل کتابخانه به زبان C# استفاده می نماید نیازی به تبدیل مستقیم داده ندارد و در سطح CIL این تبدیل بصورت خودکار – به دلیل ترجمه نوع داده ها به نوع داده CIL – انجام شده است.

از سوی دیگر CLS قراردادی است میان طراحان زبان ها و کتابخانه های کلاس جهت ایجاد حداقل استانداردهائی که همه ی زبان ها موظف به پیروی از آن می باشند تا یک زبان تحت .NET شناخته شود. این حداقل استانداردها تضمین کننده اجرای موفقیت آمیز سورس کد تبدیل شده به CIL در .NET FX می باشند. این بدان معناست که هر زبان برنامه نویسی که برای .NET عرضه می شود می تواند بخش هائی از ساختار آن را به شرط پیاده نمودن حداقل ها نادیده بگیرد. بنابراین طبیعی است اگر یک زبان برنامه نویسی در تولید کد CIL بهینه تر عمل نماید و دیگری – به نسبت سایرین – در این مورد ضعف هائی را بروز دهد.

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

این بدان دلیل است که در اولین بار اجرای برنامه مبتنی بر .NET بخش جدائی ناپذیر برنامه های مبتنی بر .NET FX یعنی CLR پس از بارگذاری کد در حافظه ابتدا چک می کند که این برنامه قبلا اجرا شده است یا خیر. اگر برنامه برای اولین بار در این ماشین اجرا شده باشد CLR آن را یه یکی از زیر مجموعه های خود به نام JIT که در واقع یک کامپایلر است می سپرد. وظیفه JIT همانطور که از نام آن پیداست (Just-in-time)٬ ترجمه CIL – یا کد میانی – به Native Code – یا کد محلی قابل اجرا – در زمان اولین اجرا می باشد. در این مرحله JIT با تشخیص مشخصات پلتفرمی که بر روی آن اجرا می شود بهینه ترین کد ممکن را برای اجرا تولید می نماید. برای مثال بسته به نوع پردازنده و سیستم عامل ممکن است JIT کد x86 یا x64 را که به ترتیب ۳۲ و ۶۴ بیتی می باشد٬ تولید نماید. کد محلی تولید شده توسط JIT٬ از یک کد میانی یکسان در پردازنده ای که برای مثال از دستورات SSE2 پشتیبانی می نماید با پردازنده ای که از این دستورات پشتیبانی نمی کند کاملا متفاوت است. اتخاذ این تصمیمات در زمان اجرا بر عهده کامپایلر JIT است.

نکته مهمی که در این میان خود نمائی می کند این است که برنامه نویس فارغ از پلتفرمی که برای آن برنامه می نویسد با ذهن و فکر باز بدون نگرانی از نتیجه اجرای کد و یا دوباره نویسی آن برای پلتفرم دیگر٬ تمام توجه خود را معطوف به چگونگی طراحی و توسعه توانائی های برنامه می نماید.

علاوه بر مسائلی که شرح آن گذشت٬ جهت جلوگیری از تکرار عمل کامپایل و در نتیجه کاهش محسوس سرعت اجرا در دفعات بعد کد محلی تولید شده توسط JIT در فایل هائی تحت عنوان Native Images در مکانی تحت عنوان Native Image Cache ذخیره خواهد شد. در دفعات بعد که فایل اسمبلی حاوی کد میانی توسط کاربر اجرا می شود CLR هرگز به محتویات آن توجهی نمی نماید و در عوض نسخه کد محلی آن را از مخفی گاه آن بدون متوجه ساختن کاربر اجرا می نماید. بنابراین در دفعات بعد با حداکثر سرعت (گاه سریعتر از Unmanaged Code) و به صورت بلادرنگ اجرا خواهند شد. البته این تبدیل هرگز باعث بی نیاز شدن برنامه از کتابخانه های .NET و CLR نخواهد شد.

ذکر این نکته ضروری است که در .NET FX ابزاری تحت عنوان ngen.exe جهت تولید دستی Native Image وجود دارد. بسیاری از نرم افزارهای مبتنی بر پلتفرم .NET در هنگام نصب با استفاده از برنامه نصب خود (Installer/Setup) اقدام به کامپایل دستی فایل های برنامه با استفاده از این ابزار می نمایند. در نتیجه در اولین بار اجرا به دلیل وجود Native Image از قبل٬ CLR از ارجاع کد میانی به JIT صرف نظر و مستقیما کد محلی را اجرا می نمایند. بدین ترتیب ضعف اجرای کند برنامه های .NET در اولین بار اجرا نیز مرتفع می گردد.

در پایان این بخش جهت درک بهتر ساختارهای تشریح شده در این مطلب توصیه می نمائیم نگاهی مجدد به نمودارهائی (تصاویر 2.A. و 2.B.) که در ابتدای این بخش ارائه شده اند بیاندازید.

Using the scenario in action

بسیار خب٬ در این مرحله بر اساس شرحی که گذشت قصد داریم فرآیند کامپایل برنامه های .NET و چگونگی تولید کد CIL را در عمل نظاره گر باشیم. بدین منظور کد Hello, World! را در ۵ زبان .NET تولید نموده٬ سپس اقدام به استخراج٬ مشاهده و مقایسه کد CIL تولیدی می نمائیم.

زبان هائی که قصد استفاده از آن ها را داریم – به جز C++ که نیاز به کامپایلری جداگانه دارد – بصورت رسمی و مستقیما از سوی .NET FX بدون نصب هیچ گونه نرم افزار اضافی پشتیبانی و کامپایل می شوند (در واقع کامپایلر این زبان ها به همراه .NET FX در سیستم نصب می شود). برای کدنویسی٬ کامپایل و اجرای این برنامه های بسیار کوچک فقط به .NET FX 2.0 و Command Prompt ویندوز نیاز داریم. ما نیز در ادامه جهت طراحی و تولید برنامه ها از حداقل امکانات جهت درک بهتر فرآیند استفاده می نمائیم.

۱. ابتدا وارد کادر Run ویندوز می شویم. سپس دستور cmd را جهت ورد به Command Prompt ویندوز وارد می نمائیم. پس از ورد به پنجره آن مطابق تصویر مراحل را دنبال می نمائیم (تصاویر 3.A. و 3.B.).

تصویر3.A. – کامپایل و اجرای برنامه های Hello, World!

تصویر3.A. – کامپایل و اجرای برنامه های Hello, World!

تصویر3.B. – کامپایل و اجرای برنامه های Hello, World!

تصویر 3.B. – کامپایل و اجرای برنامه های Hello, World!

۲. پوشه ای با نام دلخواه در درایو مورد نظرمان ایجاد نموده سپس به آنجا مراجعه می نمائیم. برای اطمینان از خالی بودن پوشه می توانیم از آن لیست بگیریم.

۳. سپس با استفاده از دستور copy اقدام به ایجاد فایلی های مورد نظرمان نموده٬ پس از وارد نمودن کدهای ذیل و درج کلیدهای Ctrl+Z جهت ذخیره فایل کلید Enter را می فشاریم.


C# زبان برنامه نویسی
hello_cs.cs نام فایل
csc hello_cs.cs دستور کامپایل
hello_cs.cs

/// <summary>
///   (C) Snoop-Security.com
/// </summary>

using System;

class Hello
{
    static void Main()
    {
        Console.WriteLine("Hello, World!");
    }
}


C++/CLI زبان برنامه نویسی
hello_cpp_cli.cpp نام فایل
cl /clr /Fehello_cpp_cli_mixed hello_cpp_cli.cpp
cl /clr:pure /Fehello_cpp_cli_pure hello_cpp_cli.cpp
cl /clr:safe /Fehello_cpp_cli_verifiable hello_cpp_cli.cpp
دستور کامپایل
hello_cpp_cli.cpp

/// <summary>
///   (C) Snoop-Security.com
/// </summary>

using namespace System;

void main()
{
    Console::WriteLine("Hello, World!");
}


Managed C++ زبان برنامه نویسی
hello_cpp_managed.cpp نام فایل
cl hello_cpp_managed.cpp /clr:oldSyntax دستور کامپایل
hello_cpp_managed.cpp

/// <summary>
///   (C) Snoop-Security.com
/// </summary>

#using <mscorlib.dll>

using namespace System;

void main()
{
    Console::WriteLine("Hello, World!");
}


JScript .NET زبان برنامه نویسی
hello_js.js نام فایل
jsc hello_js.js دستور کامپایل
hello_js.js

/// <summary>
///   (C) Snoop-Security.com
/// </summary>

import System;

Console.WriteLine("Hello, World!");


Visual Basic .NET زبان برنامه نویسی
hello_vb.vb نام فایل
vbc hello_vb.vb دستور کامپایل
hello_vb.vb

'@ <summary>
'@   (C) Snoop-Security.com
'@ </summary>

Imports System

Module Hello
    Sub Main()
        Console.WriteLine("Hello, World!")
    End Sub
End Module

۴. در مرحله بعد با استفاده از دستورات داده شده اقدام به کامپایل تمامی کدها می نمائیم. نکته حائز اهمیت در این جا مسیر فایل های اجرائی هر یک از کامپایلرهای مورد نظر می باشد. سه کامپایلر مربوط به C#, JScript .NET, Visual Basic.NET به ترتیب به نام های csc.exe, jsc.exe, vbc.exe در مسیر نصب .NET Framework قرار دارند. همچنین کامپایلر مربوط به C++ به نام cl.exe فقط با نصب Visual Studio و یا Microsoft Windows SDK در سیستم شما نصب خواهد شد و بسته به نوع بسته نصب شده در مسیری متفاوت قرار خواهد گرفت. برای مثال مسیر کامپایلرها ممکن است به شرح ذیل باشد:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\
C:\Program Files\Microsoft Visual Studio 9.0\VC\bin\

در این مرحله می توان جهت جلوگیری از تایپ مسیرهای طولانی در صورت نصب Visual Studio از Visual Studio Command Prompt استفاده نمود (تصویر 3.C.).

تصویر3.C. – مسیر اجرای Visual Studio Command Prompt

تصویر3.C. – مسیر اجرای Visual Studio Command Prompt

مزیت استفاده از این ابزار نسبت به Command Prompt ویندوز در این است که تمامی متغیرهای محیطی مورد نیاز جهت کار با کامپایلرها به دقت تنظیم شده اند (البته این ابزار چیزی بیش از مجموعه ای از چند Batch File نیست که با استفاده از دستورات Command Prompt ویندوز تمامی متغیرهای محیطی آن را تنظیم می نماید. تصویر 3.D.).

تصویر3.D. – Visual Studio Command Prompt به همراه نمایشی از متغیرهای محیطی آن

تصویر3.D. – Visual Studio Command Prompt به همراه نمایشی از متغیرهای محیطی آن

مطابق با تصاویر 3.A. و 3.B.٬ به جای استفاده از این ابزار در محیط Command Prompt با استفاده از دستور Set متغیر محیطی Path را جهت ردیابی خودکار فایل های موردنیاز تنطیم می نمائیم.

ذکر نکته ای در مورد C++ در اینجا ضروری به نظر می رسد. مایکروسافت دو نسخه از C++ را برای اجرا بر روی CLR بهینه نموده است که به ترتیب به نام های Managed Extensions for C++ و C++/CLI شناسائی می شوند. Managed Extensions for C++ که از این به بعد آن را به نام Managed C++ می شناسیم اولین تلاش مایکروسافت بدین منظور بوده است و در .NET نسخ 1.0 و 1.1 پشتیبانی شده و در حال حاضر منسوخ شده است (البته هنوز هم امکان کامپایل آن با کامپایلر جدیدتر وجود دارد). C++/CLI که در .NET 2.0 به بالا پشتیبانی می شود از اساس با Managed C++ متفاوت است. برای مثال در تعریف و استفاده از کلاس ها٬ در فراخوانی فضاهای نام و … کاملا متفاوت می باشند و جهت ارتقا برنامه ای که با Managed C++ نوشته شده است به C++/CLI٬ نیاز به بازنویسی عمده ی کد برنامه می باشد.

در ادامه ذکر پارامترهای کامپایلر cl.exe خالی از لطف نمی باشد. شما می توانید کد C++/CLI را در سه حالت متفاوت کامپایل نمائید:

  • Mixed: با استفاده از پارامتر /clr٬ در این حالت برنامه C++ همزمان حاوی Unmanaged Code و Managed Code می باشد. علاوه بر آن می تواند بدون بازنویسی مجدد از مزایای .NET نیز بهرمند گردد.
  • Pure: با استفاده از پارامتر و مقدار /clr:pure٬ در این حالت برنامه فقط شامل Managed Code می باشد اما توانائی استفاده از انواع داده ای Unmanaged Code را دارا می باشد.
  • Verifiable: با استفاده از پارامتر و مقدار /clr:safe٬ در این حالت برنامه مانند کدی که به سایر زبان های .NET نوشته و کامپایل می شود فقط دارای Managed Code می باشد و صد در صد بر روی CLR اجرا می شود. بدین ترتیب این انتخاب به CLR اجازه می دهد که عدم تخلف برنامه از تنظیمات امنیتی را تصمین نماید.

همچنین پارامتر clr دارای اختیاراتی دیگر با مقادیر oldSyntax, noAssembly, initialAppDomain که به ترتیب جهت شناسائی و کامپایل Managed C++٬ تولید یک فایل DLL و در نهایت تولید برنامه ای که توانائی اجرا بر روی .NET 1.0 را داشته باشد به کار می رود.18

۵. در نهایت اقدام به اجرای کدها به منظور تائید صحت کامپایل موفقیت آمیز آن ها می نمائیم (مطابق با تصاویر 3.A. و 3.B.).

۶. اولین مقایسه ای که انجام می دهیم مربوط به حجم فایل های تولیدی است (تصویر 3.E.).

تصویر3.E. – اختلاف قابل توجه کد CIL تولید شده توسط کامپایلرهای مختلف

تصویر3.E. – اختلاف قابل توجه کد CIL تولید شده توسط کامپایلرهای مختلف

۷. با در نظر گرفتن اختلاف حجم قابل توجه میان اسمبلی ها٬ حال نوبت مشاهده کد CIL می باشد. با استفاده از ابزاری به نام IL Disassembler (ildasm.exe) که با نصب Microsoft Windows SDK بر روی سیستم نصب می شود می توان کد CIL را مشاهده و بررسی نمود (تصویر 3.F.). این ابزار در نسخ قدیمی تر Visual Studio برای مثال 8.0 با نصب Microsoft .NET Framework SDK به نام MSIL Disassembler بر روی سیستم نصب می شود.

تصویر3.F. – اجرای IL Disassembler

تصویر3.F. – اجرای IL Disassembler

جهت مشاهده ساختار فایل اسمبلی به همراه فضاهای نامی٬ کلاس ها و کد IL به علاوه سایر اطلاعات آن کافی است فایل اسمبلی را داخل پنجره این ابزار کشیده و رها نمائید (تصاویر 3.G. تا 3.M.).

تصویر3.G. – کد CIL تولید شده توسط کامپایلر C#

تصویر3.G. – کد CIL تولید شده توسط کامپایلر C#

تصویر3.H. – کد CIL تولید شده توسط کامپایلر C++ CLI با سوئیچ clr

تصویر3.H. – کد CIL تولید شده توسط کامپایلر C++ CLI با سوئیچ clr

تصویر3.I. – کد CIL تولید شده توسط کامپایلر C++ CLI با سوئیچ clr:pure

تصویر3.I. – کد CIL تولید شده توسط کامپایلر C++ CLI با سوئیچ clr:pure

تصویر3.J. – کد CIL تولید شده توسط کامپایلر C++ CLI با سوئیچ clr:safe

تصویر3.J. – کد CIL تولید شده توسط کامپایلر C++ CLI با سوئیچ clr:safe

تصویر3.K. – کد CIL تولید شده توسط کامپایلر Managed C++ با سوئیچ clr:oldSyntax

تصویر3.K. – کد CIL تولید شده توسط کامپایلر Managed C++ با سوئیچ clr:oldSyntax

تصویر3.L. – کد CIL تولید شده توسط کامپایلر JScript .NET

تصویر3.L. – کد CIL تولید شده توسط کامپایلر JScript .NET

تصویر3.M. – کد CIL تولید شده توسط کامپایلر Visual Basic .NET

تصویر3.M. – کد CIL تولید شده توسط کامپایلر Visual Basic .NET

همانطور که از تصاویر پیداست کد C# و C++/CLI با سوئیچ /clr:safe بهینه ترین کد ممکن را تولید نموده اند. به علاوه٬ اولین نکته ای که از تصاویر دریافت می شود نمایش صریح عبارت Hello, World! می باشد.

Reverse Engineering and Assembly Decompilation

بسیار خب٬ قصد داریم در ادامه برنامه نسبتا پیچیده ای را که از فضاهای نام و کلاس های گوناگونی از .NET استفاده می کند را به زبان C# نوشته و کامپایل نمائیم. پس از آن با استفاده از متدهای مختلف اقدام به کشف مقادیر متغیرهای برنامه (شامل پسورد و …) و در نهایت بازگردانی کامل کد آن نمائیم.

ابتدا کدی را که در انتهای مقاله و در بخش ضمیمه قرار دارد را در غالب فایلی تحت عنوان snooplogin.cs ذخیره نموده٬ سپس با دستور csc snooplogin.cs آن را کامپایل می نمائیم. این برنامه به خوبی با .NET نسخ 2.0 تا 3.5 کامپایل می شود.

کد نسبتا پیچیده ای که در این بخش جهت تشریح هر چه بهتر مقاله به کار گرفته می شود برنامه کنسولی می باشد که کاربر از طریق خط فرمان و وارد نمودن نام کاربری و کلمه عبور به همراه سوئیچ هائی به خط فرمان آن وارد می شود. کاربر از طریق این برنامه می تواند مانند یک سیستم عامل (یا بهتر بگوئیم محیط عامل) عملیات هائی نظیر اجرای یک برنامه٬ تغییر درصد استفاده از پردازنده٬ دانلود یک فایل یا صفحه وب٬ استخراج اطلاعاتی در مورد سیستم عامل (شامل مدت زمان بوت٬ نسخه .NET FX٬ کشف مشخصات پردازنده٬ سایر اطلاعات سیستم عامل به خصوص ویستا و اطلاعاتی از پیکربندی کارت شبکه)٬ رمزنگاری و گشودن یک متن یا فایل٬ فشردن و باز کردن فایل ها٬ تعمیر و فشرده سازی یک فایل پایگاه داده اکسس و در نهایت تبدیل فرمت و تغییر سایز انواع فرمت های تصویر به علاوه درج نشان اختصاصی یا واترمارک بر روی تصویر می باشد. تمامی این قابلیت ها در کدی که کامپایل کردیم گنجانده شده است. در تهیه این برنامه کوشش بر آن بوده است که از بسیاری از کلاس های .NET استفاده شود. البته از برخی کلاس های پر استفاده نظیر کلاس کار با پایگاه داده و وب سرویس ها به دلیل نیاز به منابع بیش تر صرفنظر نموده اما نتایج حاصل از استفاده از آن ها را نیز تشریح می نمائیم. دلیل انتخاب این کد پیچیده و استفاده از بسیاری از قابلیت های .NET در ازای استفاده از یک کد ساده تر را در ادامه مقاله متوجه خواهیم شد. همچنین در این اسمبلی مدیریت شده دست به استفاده از COM Marshaling و استفاده از حافظه بصورت مدیریت نشده زده ایم. نکته ای که ذکر آن ضروری به نظر می رسد این است که در زمان کامپایل برنامه یقینا با هشداری مواجه خواهید شد. دلیل هشدار کامپایلر استفاده از متغیری است که از نظر کامپایلر در طول اجرای برنامه هیچ کاربردی ندارد. جای هیچ گونه نگرانی وجود ندارد و برنامه به خوبی کامپایل و اجرا می شود. این متغیر را جهت مصارف آموزشی در سطح CIL نیاز خواهیم داشت. (تصویر 4.A.)

تصویر4.A. – کامپایل و اجرای برنامه

تصویر4.A. – کامپایل و اجرای برنامه

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

فرض کنید به عنوان کسی که هیچ گونه دسترسی به کد برنامه نداشته است قصد ورود به سیستم را داریم. پس بار دیگر به بررسی پارامترهای اعلام شده می پردازیم. برای برنامه همانطور که خود اعلام می کند دو حالت فراخوانی تجسم شده است. حالت اول وارد نمودن نام کاربری به همراه کلمه عبور به علاوه انتخاب نحوه ایجاد اتصال می باشد. حالت دوم پارامتری برای ورود مهمان بدون ورود نام کاربری و کلمه عبور می باشد. پس نزدیک ترین راه حل حالت دوم است٬ امتحان می کنیم (تصویر 4.B.). گویا ورود کاربران مهمان در حال حاضر توسط مدیر سیستم ملغی شده است. پس یک نام کاربری دلخواه را امتحان می نمائیم (تصویر 4.B.):

تصویر4.B. – تست ورود به برنامه برای اولین بار

تصویر4.B. – تست ورود به برنامه برای اولین بار

متاسفانه ورود امکان پذیر نیست؛ پس راهی جز مقداری شیطنت باقی نمانده است!

خب در حال حاضر تمامی حالات ممکن برای برنامه را در نحوه دسترسی به حساب کاربری و کلمات عبور حدس می زنیم.

  • برنامه کلمات عبور را در جائی ذخیره می نماید (مانند یک فایل٬ پایگاه داده٬ رجیستری ویندوز).
  • کلمات عبور مستقیما داخل برنامه قرار گرفته اند.
  • برنامه دارای کاربر یا کلمه عبوری ویژه (مخفی) می باشد که برنامه نویس سیستم آن را به عنوان درب پشتی (Backdoor) تعبیه کرده است (بسیاری از برنامه نویسان نرم افزارهای کد بسته از این حالت استفاده می نمایند).

برنامه نویس از هر کدام از حالات بالا استفاده نموده باشد سرنخ اصلی در کد برنامه نهفته است (برای مثال کلمات عبور خود پایگاه داده یا مسیر کلید رجیستری موردنظر و یا نام فایل تنظیمات و ….). بنابر این با توجه به دانشی که در بخش های قبل بدست آورده ایم به سراغ IL Disassembler می رویم (تصویر 4.C.).

تصویر4.C. – مشاهده نام کلاس ها و فضای نام برنامه در IL Disassembler

تصویر4.C. – مشاهده نام کلاس ها و فضای نام برنامه در IL Disassembler

خب در این مرحله اسامی تمامی کلاس ها به علاوه فضای نام اصلی قابل رویت می باشد. با وجود شلوغی و تعداد زیاد کلاس ها٬ در این میان اسامی چند کلاس بخصوص Boot و Login بیشتر از همه جلب توجه می نماید. با فرض این که اول Boot اتفاق می افتد و سپس Login به سراغ این دو کلاس می رویم تا نگاهی عمیق تر به این دو داشته باشیم (تصویر 4.D.).

تصویر4.D. – ساختار کلاس های Boot و Login

تصویر4.D. – ساختار کلاس های Boot و Login

بسیار عالی! کلاس اصلی برنامه کشف شد. کلاس Boot کلاسی است که تابع Main٬ تابع اصلی برنامه در C# را دارا می باشد. این تابع یک ورودی آرایه از نوع string دارد که همانطور که مشخص است کار انتقال پارامترهای خط فرمان به برنامه را انجام می دهد (این یک قانون کلی برای برنامه های C# است). در کلاس Login نیز تمامی خصوصیات٬ توابع و متغیرهای عمومی آن بخوبی مشخص می باشند. اما چند تابع دیگر اینجا با اسامی نامانوس از جمله .cctor به - همراه نشان S - جلب توجه می نماید. حس کنجکاوی باعث می شود این تابع را بررسی بنمائیم (تصویر 4.E.).

تصویر4.E. – کشف محل ذخیره متغیرهای عمومی به همراه مقادیر آن ها

تصویر4.E. – کشف محل ذخیره متغیرهای عمومی به همراه مقادیر آن ها

در اینجا جمله The quick brown fox jumps over the lazy dog. را مشاهده می نمائیم. علاوه بر این جمله نام متغیر آن یعنی pangram نیز قابل رويت می باشد. این همان متغیری است که در زمان کامپایل برنامه هشداری را از سوی کامپایلر C# مبنی بر عدم استفاده از آن در منطق برنامه را مشاهده نمودیم. این متغیر یک متغیر عمومی است. گویا CIL متغیرهای عمومی که سریعا پس از تعریف مقداردهی می شوند را توسط این تابع مقدار دهی می نماید (هشدار امنیتی: اکثر برنامه نویسان معمولا کلمات عبور پایگاه داده را در متغیرهای عمومی ذخیره می نمایند تا توسط توابع مختلف برنامه در دسترس باشد. پس کلمات عبوری که با این روش درون کد برنامه نگهداری می شوند همیشه در این ناحیه قابل دسترسی است).

در این میان تابع Verify از کلاس Login با توجه به نام آن اولین چیزی است که نگاهمان را می رباید. اما ترجیح می دهیم با تابع Main کلاس Boot شروع کنیم چرا که قصد داریم کد برنامه را از ابتدا زیر نظر داشته باشیم. پس نگاهی به کد آن می اندازیم. (تصویر 4.F.).

تصویر4.F. – کشف سوئیچ های خط فرمان

تصویر4.F. – کشف سوئیچ های خط فرمان

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

تصویر4.G. – کشف کلمات عبور

تصویر4.G. – کشف کلمات عبور

تا اینجا به نظر عالی بود. در ادامه به سراغ تابع وسوسه انگیز Verify می رویم. به نظر سه کاربر و کلمه عبور را کشف نموده ایم که دو نمونه آن در تائید یافته های قبلی موثر است. در واقع آن دو٬ کاربران ویژه هستند root و guest. (تصاویر 4.H. و 4.I. و 4.J.).

تصویر4.H. – کشف تمامی کلمات عبور در کلاس Login

تصویر4.H. – کشف تمامی کلمات عبور در کلاس Login

تصویر4.H. – کشف تمامی کلمات عبور در کلاس Login

تصویر4.H. – کشف تمامی کلمات عبور در کلاس Login

تصویر4.J. – کشف تمامی کلمات عبور در کلاس Login

تصویر4.J. – کشف تمامی کلمات عبور در کلاس Login

در این میان پیغام خطای مربوط به guest را که در زمان اجرا دریافت نمودیم نیز یافته ایم. اگر به جستجو ادامه دهیم پیغام های دیگر برای تمامی حالات را به علاوه نکاتی دیگر درمی یابیم. جالب تر این که ساختارهای try-catch-finally به خوبی در CIL قابل رويت هست. حتی انواع داده ای به راحتی یافت می شوند. در واقع CIL به دلیل شباهت آن به اسمبلی زبانی است هر چند سطح پائین اما قابل درک و یادگیری. با کمی تحقیق و تمرین به خوبی توانائی چیره شدن بر دستورات و ساختار آن را نیز خواهید داشت.

به نظر کشفیات مان جهت نفوذ به برنامه کافی به نظر می رسد. نمونه ای از تجربه کاربر نفوذگر٬ در استفاده از برنامه را مشاهده می نمائید (تصویر 4.K.):

تصویر4.K. – نفوذ موفقیت آمیز به برنامه

تصویر4.K. – نفوذ موفقیت آمیز به برنامه

خب همانطور که مشاهده می نمائید عمل نفوذ به جز برای کاربر guest – به دلیل غیر فعال بودن– در مابقی موارد بسیار موفقیت آمیز صورت گرفته است. همچنین مشخص شد برنامه نویس این کد٬ دو سوئیچ را به عنوان Backdoor جهت مواقع اضطراری در نظر گرفته است (دو سوئیچی که در کنار سایر سوئیچ ها در کد CIL یافته بودیم).

با توجه به شواهد حاصل از تست برنامه و این نکته که همه ی حساب های کاربری در کلاس Login ثبت شده است٬ همچنین دو کاربر guest و root که صریحا در کلاس Boot به همراه کلمات عبورشان رویت می شوند می توان دریافت که سوئیچ –guest نام کاربری و کلمه عبور کاربر مهمان را به کلاس Login پاس می دهد و هر کدام از سوئیچ های –mag1cd00r و –f0rc3r00t نام کاربری و کلمه عبور مدیر سیستم را به این کلاس پاس می دهند.

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

خانم ها و آقایان! قصد داریم سه نرم افزار بسیار کم حجم (که دو تای آن کمتر از 1 MB حجم دارند) اما قدرتمند را که اتفاقا خود با .NET و زبان C# نوشته شده اند را معرفی کنیم. کار این دو نرم افزار Decompile اسمبلی های .NET به سورس کد اصلی آنهاست. البته نرم افزارهای بسیاری از این دست برای .NET یا Java در دسترس می باشند. این نرم افزارها معمولا توانائی Decompile کد اسمبلی .NET را به چندیدن زبان دارند. با وجود تفاوت کد با نمونه اصلی بازهم کد٬ قابل کامپایل مجدد و قابل استفاده است. این نرم افزارها حتی توانائی تولید Comment برای کدها را دارا می باشند. برخی نیز توانائی بهینه سازی کد جهت هرچه نزدیکتر شدن به کد اصلی را نیز دارند. با داشتن نمونه کد موجود در ضمیمه٬ خود می توانید تمامی جنبه های موردنظرتان را بررسی نمائید.

به Dis#19 سلام کنید! این نرم افزار که قیمت آن نیز نسبتا بالاست به زبان C# (!!!) نوشته شده است (تصویر 4.L.).

تصویر4.L. – Decompile اسمبلی snooplogin.exe در Dis#

تصویر 4.L. – Decompile اسمبلی snooplogin.exe در Dis#

.NET Reflector20 این ابزار رایگان اما قدرتمند توسط تولید کننده نرم افزار مشهور ANTS Profiler21 تولید شده است. این ابزار نیز بر اساس C# توسعه داده شده است (تصویر 4.M.).

تصویر4.M. – Decompile اسمبلی snooplogin.exe در .NET Reflector

تصویر4.M. – Decompile اسمبلی snooplogin.exe در .NET Reflector

بازهم C#! Decompiler.NET که گرانقیمت تر نیز هست توسط C# نوشته شده است. علاوه برآن این ابزار به نحو هوشمندانه تری قابلیت تشخیص ساختار switch/case را داشته است (تصویر 4.N.).

تصویر4.N. – Decompile اسمبلی snooplogin.exe در Decompiler.NET

تصویر4.N. – Decompile اسمبلی snooplogin.exe در Decompiler.NET

تلخ تر از همه مسائل این است که سورس کد اسمبلی های خود .NET نیز به همین روش قابل دستیابی است!

آیا با .NET یا Java به پایان خط رسیده اید! کاملا قابل درک است. پس تکلیف سال ها صرف زمان٬ یادگیری و سرمایه گذاری بر اساس آن برای آینده تان چه می شود؟ تکلیف هزینه های گوناگونی که برای پروژه هایتان پرداخته اید چه می شود؟

یک سناریوی بهتر: در آمدتان از راه توسعه سیستم حسابداری یا شاید یک CMS اختصاصی که مدت ها در حال توسعه اش بودید می گذرد؟ ممکن است تا حالا شرکت های رقیب به سورس کد برنامه تان ]که برای مثال از طریق مشتری که پشتیبانی را به شرکت دیگر سپرده است و یا سروری که پروژه ASP.NET شما را پشتیبانی می نماید[ دسترسی پیدا کرده باشند٬ کسی چه می داند؟ بعید است! فکر نمی کنم! شاید همین مقاله باعث کشف اسرارتان توسط دیگران شود!

در حال حاضر دیگران سورس کدهای شما را در اختیار دارند٬ فقط کافیست یک نفر…

البته این موضوع جدیدی نیست. برای مثال از سال ها پیش در عهد DOS نرم افزاری به نام Refox وجود داشت که کار آن Decompile برنامه های نوشته شده در زبان Fox Pro به سورس کد٬ آن هم به خوبی بود.

آیا .NET/Java == Open Source؟ آیا طراحان .NET یا Java در زمان طراحی آن به این موضوع نیاندیشیده اند؟ پس چگونه است که بسیاری از کمپانی های بزرگ بر اساس این دو پلتفرم نرم افزارهای خود را با خیالی آسوده توسعه می دهند؟

به منظور گرفتن پاسخ سوال هایتان بخش پایانی را از دست ندهید!

Code Obfuscation

شاید در پایان بخش قبل با خود اندیشیده اید که اگر Decompiler های .NET با خود .NET نوشته شده اند چگونه است که این محصولات گرانقیمت از گزند خود در اماند؟ پاسخ این سوال در این عبارت نهفته است: Code Obfuscation.

Obfuscation در یک جمله عملی است در جهت ناخوانا و مبهم کردن هرچه بیشتر کد. این عمل حتی برای زبان های اسکریپتی نیز کاربرد دارد. برای مثال کد JavaScript زیر به شکلی که در ادامه می آید مبهم و ناخوانا شده اما به خوبی و بدون هیچ مشکلی در مرورگر اجرا می شود.

سورس کد اصلی:

<script language="javascript">
    // (C) Snoop-Security.com
    for (i in navigator)
        document.write(i + ": " + navigator[i] + "<br />");
</script>

سورس کد ناخوانا و مبهم٬ اما قابل اجرا:

<script language="javascript">
eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('2(0 3 1)4.5(0+": "+1[0]+"<6 />");',7,7,'i|navigator|for|in|document|write|br'.split('|'),0,{}))
</script>

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

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

جهت انجام عمل Obfuscation هم برای .NET و هم برای Java نرم افزارهای متعددی در دسترس می باشد22. برخی از این نرم افزارها Open Source٬ برخی رایگان و برخی دیگر بسیار قدرتمند اما قیمتی تا چندین برابر Visual Studio Professional دارند.

البته همراه Visual Studio ابزاری با نام Dotfuscator محصول کمپانی PreEmptive Solutions23 از سوی Microsoft گنجانده شده است. این ابزار در سه نسخه بر حسب قیمت و امکانات عرضه می شود که در Visual Studio٬ ارزان ترین و ساده ترین نسخه آن یعنی Community Edition گنجانده شده است (تصاویر 5.A. و 5.B.).

تصویر5.A. – نصب نسخه محدودی از Dotfuscator در زمان نصب Visual Studio

تصویر5.A. – نصب نسخه محدودی از Dotfuscator در زمان نصب Visual Studio

تصویر5.B. – اجرای Dotfuscator

تصویر5.B. – اجرای Dotfuscator

در این مقاله قصد پرداختن به این ابزار را نداریم. در عوض قصد داریم به ابزاری بسیار قدرتمند به نام Xenocode Postbuild Professional24 بپردازیم. علاوه بر این ابزار کمپانی Code Systems محصولات قدرتمندی همانند Virtual Application Studio0 جهت پرتابل نمودن نرم افزارها و توزیع نرم افزارها بدون نیاز به نصب٬ همچنین Fox Code Analyzer25 جهت Decompile٬ آنالیز سرعت اجرای برنامه های .NET و رفع مشکلات آنها را ارائه می نماید (تصویر 5.C.).

تصویر5.C.  – Decompile اسمبلی snooplogin.exe در Fox Code Analyzer

تصویر5.C. – Decompile اسمبلی snooplogin.exe در Fox Code Analyzer

هر یک از نرم افزارهای مجموعه Xenocode معمولا قیمتی تا چندین برابر Visual Studio دارند و قیمت آن ها بر اساس تعداد توسعه دهندگان تیم محاسبه می شود.

در این مقاله از Postbuild 2006 Professional نسخه Retail که به خوبی از .NET 2.0 پشتیبانی می نماید استفاده می نمائیم. این در حالیست که نسخه Retail حجمی تا چند برابر نسخه های آزمایشی آن دارد و بر خلاف نسخ آزمایشی و غیره نیازی به ورد سریال و یا رجیستر و مسائلی از این دست ندارد. بنابراین ممکن است مسائلی که در ادامه بیان می شود در نسخه های آزمایشی آن قابل اجرا نباشد.

متاسفانه این نسخه از Postbuild با نصب Security Update for CAPICOM26 دچار مشکلاتی می شود و تا حذف آن از سیستم توانائی اجرا نخواهد داشت (تصویر 5.D.). از آنجا که این وصله امنیتی در XP SP3 و Vista SP1 به صورت پیش فرض نصب می باشد از Windows 2000 Professional بر روی VMWare Fusion جهت تشریح ادامه مقاله استفاده می نمائیم. همچنین با نصب NET FX 2.0 SP1/SP2 به دلیل مسئله یاد شده این نرم افزار مجددا از کار باز می ماند. این نرم افزار به نسخه اولیه .NET FX 2.0 جهت اجرا و ادامه کار نیازمند است.

تصویر5.D. – عدم اجرای  Postbuild 2006 Professional در صورت وجود Security Update for CAPICOM

تصویر5.D. – عدم اجرای Postbuild 2006 Professional در صورت وجود Security Update for CAPICOM

پس از اجرای Postbuild (تصویر 5.E.) با نرم افزاری خوش دست جهت توزیع برنامه های .NET مواجه خواهیم شد (تصویر 5.F.). توزیع یک برنامه .NET در این نرم افزار شامل چهار مرحله است.

تصویر5.E. – SplashScreen نرم افزار Postbuild

تصویر5.E. – SplashScreen نرم افزار Postbuild

تصویر5.F. – مرحله اول: افزودن اسمبلی ها و انتخاب پروفایل نوع برنامه

تصویر5.F. – مرحله اول: افزودن اسمبلی ها و انتخاب پروفایل نوع برنامه

در زمان افزودن اسمبلی ها بایستی توجه داشت که می توان تمامی اسمبلی ها را با هم ترکیب نموده و در نهایت یک فایل اسمبلی داشت. در قسمت Preset می توانید نوع پروژه را تعیین نمائید. این انتخاب بر نحوه برخورد با فایل اسمبلی کاملا تاثیرگذار خواهد بود.

در زبانه دوم تنظیمات مربوط به تغییر نام Object ها٬ جلوگیری از Decompile٬ و کدگذاری رشته ها انجام می شود (تصویر 5.G.).

تصویر5.G. – مرحله دوم: مهم ترین تنظیمات اسمبلی

تصویر5.G. – مرحله دوم: مهم ترین تنظیمات اسمبلی

در قسمت Symbol Renaming می توان چگونگی تعیین و تغییر نام تمامی Object های برنامه اعم از کلاس ها تا متغیرها را تنظیم نمود.

در بخش Disassembler Suppression می توان راهکارهائی را برای مقابله و ایجاد خلل در کار Decompiler ها از جمله IL Disassembler٬ Fox Code Analyzer و سایرین در نظر گرفت.

در قسمت Control Flow Obfuscation سطح مبهم نمودن کد را تعیین می نمائیم. هر چه این عدد بزرگتر باشد میزان ناخوانائی و مبهم شدن بیشتر اما حجم اسمبلی نیز بیشتر خواهد شد.

در نهایت در قسمت String Encryption می توان دست به Cryptography (کد گذاری) رشته های داخل برنامه زد. با فشردن Select Strings کادر Select Encrypted String جهت انتخاب رشته های مورد نظر باز می شود (تصویر 5.H.).

تصویر5.H. – انتخاب رشته های مورد استفاده برنامه جهت Cryptography

تصویر5.H. – انتخاب رشته های مورد استفاده برنامه جهت Cryptography

مرحله سوم که مرحله بهینه سازی اسمبلی است٬ در صورت انتخاب هر گزینه ای از دو بخش اول آن جزو حساس ترین مرحله هاست. چرا که به سادگی در بسیاری از موارد این گزینه ها باعث تخریب فایل نهائی خواهند شد (تصویر 5.I.).

تصویر5.I. – مرحله سوم: بهینه سازی اسمبلی

تصویر5.I. – مرحله سوم: بهینه سازی اسمبلی

در قسمت Metadata Reduction می توان با استفاده از فشرده سازی Metadata موجود در اسمبلی باعث کاهش حجم آن شد. تنظیمات این بخش وابسته به تنظیمات زبانه Protect یعنی مرحله قبل می باشد که در صورت سهل انگاری باعث تخریب اسمبلی نهائی خواهد شد.

تنظیمات قسمت Dead Code Elimination می تواند باعث حذف Metadata یا کدهای غیرقابل استفاده باشد. این عمل در سرنوشت اسمبلی نهائی فوق العاده تاثیرگذار خواهد بود و ممکن است فایل اسمبلی غیر قابل استفاده شود.

قسمت Code Profiling تنها زمانی مفید است که قصد اندازه گیری سرعت و کارائی اجرای برنامه را داشته باشید. این بخش در صورت فعال بودن باعث افزوده شدن کدهائی جهت مانیتور کردن بخش های مختلف برنامه خواهد شد. پس از اجرای برنامه فایلی تحت عنوان XCProfile.txt در کنار اسمبلی ایجاد خواهد شد که نتایج تست در آن ثبت شده است. معمولا این قابلیت در حالتی مورد استفاده قرار می گیرد که کدها هنوز Obfuscate نشده باشند (به دلیل مشخص بودن اسامی درون کد). البته می توان پس از Obfuscate نیز٬ از آن جهت مقایسه کارائی برنامه با نسخه اصلی بهره جست. نکته مهم این است که به دلیل کند شدن برنامه در این حالت و جنبه تست آن٬ از این گزینه در محصول نهائی استفاده نمی شود. در بسیاری از موارد به جای استفاده از این گزینه از ابزار جامع تر Fox Code Analyzer استفاده می شود.

در نهایت تنظیمات بخش Output Compression باعث فشرده سازی و در نتیجه کاهش حجم خروجی خواهد شد. بدیهی است به دلیل نیاز به از فشرده خارج شدن اطلاعات در زمان بارگذاری به حافظه٬ این گزینه باعث افزایش زمان بارگذاری و پاسخگوئی برنامه خواهد شد. در ضمن این گزینه فقط به همراه اسمبلی های .exe به کار می رود.

در مرحله پایانی تنظیمات مربوط به اسمبلی خروجی تعیین می گردد.

تصویر5.J. – مرحله چهارم: تنظیمات خروجی

تصویر5.J. – مرحله چهارم: تنظیمات خروجی

در بخش Output Type می توانید تعیین کنید که اسمبلی ها جداگانه صادر شوند یا همگی در قالب یک فایل اسمبلی ترکیب و صادر شوند.

در ناحیه Link and Code Generation تنظیم بسیار مهمی وجود دارد که باعث بی نیاز شدن برنامه شما از .NET FX خواهد شد. با انتخاب نسخه .NET FX از کادر Embed .NET Framwrok library dependencies گزینه Generate native x86 executable image انتخاب خواهد شد (و بالعکس). با انتخاب این گزینه لایسنس .NET FX نمایان خواهد شد. در این حالت اساسی ترین بخش های .NET FX به علاوه سایر کتابخانه های مورد نیاز برنامه به فایل نهائی الحاق و با آن همراه خواهد شد. بدلیل استفاده از ngen.exe در تولید این فایل احتمال اجرا شدن و پرتابل بودن آن بر روی سایر سیستم ها تقلیل می یابد (چرا که ابزار ngen.exe فایلی صد در صد سازگار با پلتفرم فعلی را تهیه می نماید که با سیستم های دیگر سازگار نخواهد بود). همچنین به دلیل تعبیه .NET FX در فایل اجرائی افزایش حجم چشمگیری خواهیم داشت. برای پروژه ما حجم فایل خروجی 11.60 MB خواهد بود. چنانچه فایل خروجی را درون پنجره IL Disassembler درگ کنید با پیغام خطائی مبنی بر اسمبلی و تحت CLR نبودن برنامه مواجه خواهید شد (تصویر 5.K.).

تصویر5.K. – خطا از سوی IL Disassmbler مبنی بر عدم شناسائی کدهای CIL

تصویر5.K. – خطا از سوی IL Disassmbler مبنی بر عدم شناسائی کدهای CIL

پس با استفاده از این ویژگی فایل شما از سوی تمامی نرم افزارها یک کد مدیریت نشده در نظر گرفته می شود. آخرین نکته در مورد این ویژگی این است که در پروژه هائی که از کلاس های پایگاه داده استفاده نموده اند ایجاد خطا می نماید.

در این بخش گزینه دیگری وجود دارد که مانع از دیده شدن اطلاعات درون اسمبلی به اشتراک گذاشته شده (مانند یک Component) توسط سایر اسمبلی ها خواهد شد.

در نهایت در ناحیه Output Settings پوشه مقصد را جهت تولید اسمبلی محافظت شده تعیین می نمائیم. بدیهی است این مسیر بایستی با مسیر اسمبلی اصلی متفاوت باشد.

در نهایت پس از کلیک بر روی Xenocode Assemblies کادری ظاهر و عملیات آغاز خواهد شد. بسته به نوع پروژه از چند ثانیه تا چند دقیقه زمان نیاز خواهد بود.

تصویر5.L. – آغاز پردازش اسمبلی و تبدیل آن

تصویر5.L. – آغاز پردازش اسمبلی و تبدیل آن

پس از تبدیل اسمبلی اولین تغییر چشمگیر حجم آن می باشد. اسمبلی اصلی 36.0 KB حجم دارد٬ این در حالی است که حجم اسمبلی محافظت شده با تنظیمات موجود در اسلایدها 268.0 KB می باشد. علاوه بر آن کد به خوبی اجرا می شود. در نهایت جهت مشاهده تغییرات اساسی٬ آن را در IL Disassembler باز می نمائیم. نتیجه کار خیره کننده است (تصویر 5.M.).

تصویر5.M. – اسمبلی محافظت شده و مشاهده تغییرات ساختاری آن

تصویر5.M. – اسمبلی محافظت شده و مشاهده تغییرات ساختاری آن

اسامی درون اسمبلی به شدت تغییر یافته اند. علاوه بر آن کاملا مشخص است که فضای نام و کلاس هائی به اسمبلی اضافه شده است. با توجه به شناختی که قبلا از اسمبلی داشتیم به دنبال متغیر pangram و مقدارش که قبلا شرح آن گذشت، اسمبلی را جستجو می نمائیم (تصویر 5.N.).

تصویر5.N. – مشاهده تغییرات مربوط به متغیر pangram و اطلاعات آن  در کلاس Login

تصویر5.N. – مشاهده تغییرات مربوط به متغیر pangram و اطلاعات آن در کلاس Login

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

در نهایت اقدام به گشودن کد در یک Decompiler می نمائیم. در مقایسه با کد قبلی به طرز قابل ملاحظه ای از خوانائی کد کاسته شده است (تصویر 5.O.).

تصویر5.O. – پیچیدگی کد پس از باز کردن آن در یک Decompiler (مقایسه کنید با تصویر 5.C.)

تصویر5.O. – پیچیدگی کد پس از باز کردن آن در یک Decompiler (مقایسه کنید با تصویر 5.C.)

برخی دیگر از Decompiler ها مانند Dis# در زمان خواندن اطلاعات داخل اسمبلی در قسمتی هائی از کد دچار مشکل می شوند و آن را با کامنت مشخص می نمایند.

در پایان ذکر این نکته ضروری به نظر می رسد که هرگز برای Code Obfuscation جایگاه ویژه ای باز نکنید! چرا که این عمل با وجود ضریب ایمنی قابل قبول، در پاره ای از موارد تا حدودی قابل خنثی سازی می باشد. شاید بعدها در مقاله ای دیگر بدان پرداختیم.

جمع بندی

با توجه به آنچه که گذشت نیاز به محافظت از کد در پروژه های مبتنی بر .NET/Java امری است که به شدت بدان احساس نیاز می شود.

در صورت سهل انگاری در این امر بایستی به زودی منتظر رخنه و نفوذ همه جانبه دیگران به برنامه هایتان باشید٬ بخصوص در مورد برنامه هائی که در سطوح وسیع تر مانند وب سایت ها و CMS ها٬ نرم افزارهای تحت شبکه و … اجرا می شوند.

پس هرگز استفاده از Code Obfuscation به عنوان متدی با ضریب ایمنی قابل قبول را فراموش ننمائید.

دریافت سورس کد کامل از GitLab

دریافت سورس کد کامل از GitHub

snooplogin.cs

/// <summary>
///   (The MIT License)
///   
///   Copyright (c) 2009 Mohammad S. Babaei
///   
///   Permission is hereby granted, free of charge, to any person obtaining a copy
///   of this software and associated documentation files (the "Software"), to deal
///   in the Software without restriction, including without limitation the rights
///   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
///   copies of the Software, and to permit persons to whom the Software is
///   furnished to do so, subject to the following conditions:
///   
///   The above copyright notice and this permission notice shall be included in
///   all copies or substantial portions of the Software.
///   
///   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
///   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
///   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
///   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
///   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
///   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
///   THE SOFTWARE.
/// </summary>


using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.IO.Compression;
using System.Management;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

namespace SnoopSecurity
{
    #region Kernel, Login

    class Login
    {
        #region Variables & Properties

        private static string pangram = "The quick brown fox jumps over the lazy dog.";

        private string _user = string.Empty, _pw = string.Empty, _result = string.Empty;
        
        public string result
        {
            get
            {
                return _result;
            }
        }

        public string user
        {
            get
            {
                return _user;
            }
            set
            {
                _user = value;
            }
        }

        public string pw
        {
            get
            {
                return _pw;
            }
            set
            {
                _pw = value;
            }
        }

        #endregion

        public Login()
        {
            Random rnd = new Random();

            Console.Write("Establishing secure tunnel");

            for (int i = 0; i < rnd.Next(3, 8); i++)
            {
                for (int j = 0; j < rnd.Next(2, 5); j++)
                    Console.Write(".");
                Thread.Sleep(rnd.Next(250, 950));
            }

            Console.WriteLine(string.Empty);
        }

        public Login(string user, string pw)
        {
            _user = user;
            _pw = pw;
        }

        public bool Verify()
        {
            bool isValid = false;
            string[][] users = { new string[] { "root", "t00r", "{1}Hello, {0}!{1}You're always welcomed...{1}", "" }, new string[] { "NuLL3rr0r", "3rr0R/.", "{1}Hello, {0}!{1}Have a lot of fun...{1}", "" }, new string[] { "guest", "please", "{1}Nope, {0}!{1}There's no room here for you, ask the root...{1}", "banned" } };

            foreach (string[] pair in users)
            {
                if (pair[0].Equals(_user) && pair[1].Equals(_pw))
                {
                    if (pair[3] == "banned")
                    {
                        _result = string.Format(pair[2], "Dear", Environment.NewLine);
                        return false;
                    }

                    _result = string.Format(pair[2], _user, Environment.NewLine);
                    isValid = true;
                    break;
                }
            }

            if (!isValid)
                _result = "Permission denied.";

            Core.Initialize(1500);

            return isValid;
        }
    }

    #endregion


    #region Kernel, Core

    class Core
    {
        private static string[] cmdList = {
                                              "?",
                                              "clear",
                                              "logout",
                                              "set_process_priority",
                                              "run_external_app",
                                              "sysinfo_brief",
                                              "sysinfo_cpu",
                                              "sysinfo_os",
                                              "sysinfo_netdevice",
                                              "sysinfo_vistaranks",
                                              "download_url",
                                              "encrypt_text",
                                              "decrypt_text",
                                              "encrypt_file",
                                              "decrypt_file",
                                              "compress_file",
                                              "decompress_file",
                                              "repair_access_db",
                                              "img_convert_resize",
                                              "img_watermark"
                                          };

        public static void Initialize(int ms)
        {
            Console.WriteLine("Initializing...");
            Thread.Sleep(ms);
        }

        private void RunCommand(string cmd)
        {
            switch (cmd)
            {
                case "?":
                    foreach (string c in cmdList)
                        Console.WriteLine(c);
                    break;
                case "clear":
                    Console.Clear();
                    break;
                case "logout":
                    break;
                case "set_process_priority":
                    Console.WriteLine("0:Idle  1:BelowNormal  2:Normal  3:AboveNormal  4:High  5:RealTime");
                    string level = Console.ReadLine();
                    Console.WriteLine(OSTools.SetPriority(level));
                    break;
                case "run_external_app":
                    Console.Write("Path to executable: ");
                    string app = Console.ReadLine();
                    Console.Write("Arguments [Return:no arguments]: ");
                    string args = Console.ReadLine();
                    Console.WriteLine(OSTools.ExternalCommand(app, args));
                    break;
                case "sysinfo_brief":
                    Console.WriteLine(SysInfo.Brief());
                    break;
                case "sysinfo_cpu":
                    Console.WriteLine(SysInfo.CPU());
                    break;
                case "sysinfo_os":
                    Console.WriteLine(SysInfo.OS());
                    break;
                case "sysinfo_netdevice":
                    Console.WriteLine(SysInfo.NetDevice());
                    break;
                case "sysinfo_vistaranks":
                    Console.WriteLine(SysInfo.VistaRanks());
                    break;
                case "download_url":
                    Console.Write("URL: ");
                    string url = Console.ReadLine();
                    Console.Write("Save path: ");
                    string dwndPath = Console.ReadLine();
                    Console.Write("{PROXY_ADDR}:{PROXY_PORT} [Return::IE proxy settings]: ");
                    string proxy = Console.ReadLine();
                    Console.WriteLine(WebUtils.DownloadURL(url, dwndPath, proxy));
                    break;
                case "encrypt_text":
                    Console.Write("Text: ");
                    string encText = Console.ReadLine();
                    Console.Write("Key: ");
                    string encKey = Console.ReadLine();
                    Console.WriteLine(EncDec.Encrypt(encText, encKey));
                    break;
                case "decrypt_text":
                    Console.Write("Text: ");
                    string decText = Console.ReadLine();
                    Console.Write("Key: ");
                    string decKey = Console.ReadLine();
                    Console.WriteLine(EncDec.Decrypt(decText, decKey));
                    break;
                case "encrypt_file":
                    Console.Write("Source: ");
                    string encSource = Console.ReadLine();
                    Console.Write("Target: ");
                    string encTarget = Console.ReadLine();
                    Console.Write("Key: ");
                    string encFileKey = Console.ReadLine();
                    Console.WriteLine(EncDec.Encrypt(encSource, encTarget, encFileKey));
                    break;
                case "decrypt_file":
                    Console.Write("Source: ");
                    string decSource = Console.ReadLine();
                    Console.Write("Target: ");
                    string decTarget = Console.ReadLine();
                    Console.Write("Key: ");
                    string decFileKey = Console.ReadLine();
                    Console.WriteLine(EncDec.Decrypt(decSource, decTarget, decFileKey));
                    break;
                case "compress_file":
                    Console.Write("Source: ");
                    string cmpSource = Console.ReadLine();
                    Console.Write("Target: ");
                    string cmpTarget = Console.ReadLine();
                    Console.Write("Mode(fast/solid): ");
                    string cmpMode = Console.ReadLine();
                    Console.WriteLine(Zipper.Compress(cmpSource, cmpTarget, cmpMode));
                    break;
                case "decompress_file":
                    Console.Write("Source: ");
                    string dcmpSource = Console.ReadLine();
                    Console.Write("Target: ");
                    string dcmpTarget = Console.ReadLine();
                    Console.Write("Mode(fast/solid): ");
                    string dcmpMode = Console.ReadLine();
                    Console.WriteLine(Zipper.Decompress(dcmpSource, dcmpTarget, dcmpMode));
                    break;
                case "repair_access_db":
                    Console.Write(".mdb File: ");
                    string dbSource = Console.ReadLine();
                    Console.Write(".mdb Pw [Return::blank Pw]: ");
                    string dbPw = Console.ReadLine();
                    Console.WriteLine(DBTools.CompactRepairJetDB(dbSource, dbPw));
                    break;
                case "img_convert_resize":
                    Console.Write("Source Image: ");
                    string imgSource = Console.ReadLine();
                    Console.Write("Target Image: ");
                    string imgTarget = Console.ReadLine();
                    Console.Write("{WIDTH}{px|%} [Return::Original Width]: ");
                    string imgW = Console.ReadLine();
                    Console.Write("{HEIGHT}{px|%} [Return::Original Height]: ");
                    string imgH = Console.ReadLine();
                    Console.WriteLine(ImageMan.GenThumb(imgSource, imgTarget, imgW, imgH));
                    break;
                case "img_watermark":
                    Console.Write("Source Image: ");
                    string wmSource = Console.ReadLine();
                    Console.Write("Target Image: ");
                    string wmTarget = Console.ReadLine();
                    Console.Write("Watermark Text: ");
                    string wmText = Console.ReadLine();
                    Console.WriteLine(ImageMan.GenWatermark(wmSource, wmTarget, wmText));
                    break;
                default:
                    Console.WriteLine("Unknown or bad command...");
                    break;
            }

            if (cmd != "clear")
                Console.WriteLine(string.Empty);
        }

        public void GoShell()
        {
            Console.WriteLine("Type ? for help...{0}", Environment.NewLine);

            string cmd = string.Empty;
            do
            {
                Console.Write("# ");
                cmd = Console.ReadLine();

                RunCommand(cmd);
            } while (!cmd.Equals("logout"));
        }
    }

    #endregion


    #region Kernel, Boot

    static class Boot
    {
        static void Main(string[] args)
        {
            Login login;
            bool loggedIn = false;
            
            try
            {
                switch (args[0])
                {
                    case "--secure":
                        login = new Login();
                        login.user = args[1];
                        login.pw = args[2];
                        loggedIn = login.Verify();
                        Console.WriteLine(login.result);
                        break;
                    case "--fast":
                        login = new Login(args[1], args[2]);
                        loggedIn = login.Verify();
                        Console.WriteLine(login.result);
                        break;
                    case "--guest":
                        login = new Login("guest", "please");
                        loggedIn = login.Verify();
                        Console.WriteLine(login.result);
                        break;
                    case "--f0rc3r00t":
                    case "--mag1cd00r":
                        login = new Login("root", "t00r");
                        loggedIn = login.Verify();
                        Console.WriteLine(login.result);
                        break;
                    default:
                        Console.WriteLine("{0}Invalid options detected!{0}", Environment.NewLine);
                        break;
                }

                if (loggedIn)
                {
                    Core core = new Core();
                    core.GoShell();
                }
            }
            catch (Exception ex)
            {
                StringBuilder sb = new StringBuilder(string.Empty);
                sb.Append(Environment.NewLine);
                sb.Append("NuLL3rr0r! => Hang! => B00M! => B1GBANG! => [ Hello, World! ]");
                sb.Append(Environment.NewLine);
                sb.Append(Environment.NewLine);
                sb.Append(Environment.NewLine);
                sb.Append(string.Format("Kernel panic - {0}", ex.Message));
                sb.Append(Environment.NewLine);
                sb.Append(Environment.NewLine);
                sb.Append("    Usage :   snooplogin.exe option[--secure|--fast] user pw");
                sb.Append(Environment.NewLine);
                sb.Append("              snooplogin.exe --guest");
                sb.Append(Environment.NewLine);
                sb.Append(Environment.NewLine);
                Console.WriteLine(sb.ToString());
            }
            finally
            {
                Console.WriteLine(
                                    Environment.NewLine +
                                    "Snoop(R) GNU/FakeOS(TM) - (C)2009 Snoop-Security.com" +
                                    Environment.NewLine +
                                    Environment.NewLine +
                                    "You know? It's not really an Operating System;" +
                                    Environment.NewLine +
                                    "But it's an Operating Environment!!!" +
                                    " :D"
                                 );
            }
        }
    }

    #endregion


    #region Get SysInfo

    class SysInfo
    {
        public static string[] GetRawInfo(string query, string[] items)
        {
            string[] info = new string[items.Length];

            for (int i = 0; i < info.Length; i++)
                info[i] = "Not Available...";

            try
            {
                ManagementScope ms;
                ObjectQuery oq;
                ManagementObjectSearcher mos;
                ManagementObjectCollection moc;

                if (!query.Contains("NetworkAdapterConfiguration"))
                    mos = new ManagementObjectSearcher(query);
                else
                {
                    ms = new ManagementScope(@"\\localhost\root\cimv2");
                    oq = new ObjectQuery(query);
                    mos = new ManagementObjectSearcher(ms, oq);
                }

                moc = mos.Get();

                if (!query.Contains("NetworkAdapterConfiguration"))
                    foreach (ManagementBaseObject mbo in moc)
                    {
                        for (int i = 0; i < items.Length; i++)
                        {
                            try
                            {
                                info[i] = mbo[items[i]].ToString();
                            }
                            catch
                            {
                            }
                            finally
                            {
                            }
                        }
                        break;
                    }
                else
                    foreach (ManagementObject mo in moc)
                    {
                        for (int i = 0; i < items.Length; i++)
                        {
                            try
                            {
                                if (items[i] != "IPAddress" && items[i] != "DefaultIPGateway" && items[i] != "DNSServerSearchOrder")
                                    info[i] = mo[items[i]].ToString();
                                else
                                    info[i] = string.Join(", ", (string[])(mo[items[i]]));
                            }
                            catch
                            {
                            }
                            finally
                            {
                            }
                        }
                        break;
                    }
            }
            catch
            {
            }
            finally
            {
            }

            return info;
        }

        private static string GetLayoutSpace(int len, int baseSpaceNum)
        {
            string space = string.Empty;

            for (int i = 0; i < baseSpaceNum - len; i++)
                space += " ";

            return space;
        }

        private static string FormatLayout(string title, string[] items, string[] values)
        {
            string info = string.Format("{1}[{0}]", title, Environment.NewLine);

            int maxLen = 0;

            foreach (string s in items)
                if (maxLen < s.Length)
                    maxLen = s.Length;

            maxLen += 1;

            for (int i = 0; i < items.Length; i++)
                info += string.Format("{2}{0}{3}:  {1}", items[i], values[i], Environment.NewLine, GetLayoutSpace(items[i].Length, maxLen));

            return info;
        }

        public static string CPU()
        {
            string[] items = new string[] { "Name", "NumberOfLogicalProcessors", "NumberOfCores", "MaxClockSpeed" };
            string[] values = GetRawInfo("SELECT * FROM Win32_Processor", items);

            values[3] += " MHz";

            return FormatLayout("Processor", items, values);
        }

        public static string OS()
        {
            string[] items = new string[] { "Caption", "Version", "CSDVersion", "OSArchitecture", "FreePhysicalMemory" };
            string[] values = GetRawInfo("SELECT * FROM Win32_OperatingSystem", items);

            values[4] = String.Format("{0} MB", (Convert.ToUInt64(values[4]) / 1024).ToString());

            return FormatLayout("Operating System", items, values);
        }

        public static string NetDevice()
        {
            string[] items = new string[] { "Description", "DNSHostName", "IPAddress", "DefaultIPGateway", "DNSServerSearchOrder", "MACAddress" };
            string[] values = GetRawInfo("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = 'TRUE'", items);

            return FormatLayout("Network Adapter Configuration", items, values);
        }

        public static string VistaRanks()
        {
            string[] items = new string[] { "CPUScore", "MemoryScore", "GraphicsScore", "D3DScore", "DiskScore" };
            string[] values = GetRawInfo("SELECT * FROM Win32_WinSAT", items);

            return FormatLayout("Windows Experience Index", items, values);
        }

        public static string Brief()
        {
            StringBuilder sb = new StringBuilder(string.Empty);

            sb.Append(Environment.NewLine);
            sb.Append(string.Format("Up-Time           : {0}min", (Int32)((Environment.TickCount / 1000) / 60)));
            sb.Append(Environment.NewLine);
            sb.Append(string.Format("OS Version        : {0}", Environment.OSVersion));
            sb.Append(Environment.NewLine);
            sb.Append(string.Format("Framework Version : {0}", Environment.Version));
            sb.Append(Environment.NewLine);
            sb.Append(string.Format("Time Zone         : {0}", TimeZone.CurrentTimeZone.IsDaylightSavingTime(DateTime.Now) ? TimeZone.CurrentTimeZone.DaylightName : TimeZone.CurrentTimeZone.StandardName));

            return sb.ToString();
        }
    }

    #endregion


    #region Encryption / Decryption

    public class EncDec
    {
        /// <summary>
        /// Based on code at:
        /// http://www.codeproject.com/KB/security/DotNetCrypto.aspx
        /// </summary>

        public static byte[] Encrypt(byte[] clearData, byte[] Key, byte[] IV)
        {
            try
            {
                MemoryStream ms = new MemoryStream();

                Rijndael alg = Rijndael.Create();

                alg.Key = Key;
                alg.IV = IV;

                CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);

                cs.Write(clearData, 0, clearData.Length);

                cs.Close();

                byte[] encryptedData = ms.ToArray();

                return encryptedData;
            }
            catch (Exception ex)
            {
                Console.Write(ex.Message);
                return (new byte[] { });
            }
            finally
            {
            }
        }

        public static string Encrypt(string clearText, string Password)
        {
            byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);

            PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            byte[] encryptedData = Encrypt(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16));

            return Convert.ToBase64String(encryptedData);
        }

        public static byte[] Encrypt(byte[] clearData, string Password)
        {
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            return Encrypt(clearData, pdb.GetBytes(32), pdb.GetBytes(16));
        }

        public static string Encrypt(string fileIn, string fileOut, string Password)
        {
            try
            {
                FileStream fsIn = new FileStream(fileIn, FileMode.Open, FileAccess.Read);
                FileStream fsOut = new FileStream(fileOut, FileMode.OpenOrCreate, FileAccess.Write);

                PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

                Rijndael alg = Rijndael.Create();
                alg.Key = pdb.GetBytes(32);
                alg.IV = pdb.GetBytes(16);

                CryptoStream cs = new CryptoStream(fsOut, alg.CreateEncryptor(), CryptoStreamMode.Write);

                int bufferLen = 4096;
                byte[] buffer = new byte[bufferLen];
                int bytesRead;

                do
                {
                    bytesRead = fsIn.Read(buffer, 0, bufferLen);

                    cs.Write(buffer, 0, bytesRead);
                } while (bytesRead != 0);

                cs.Close();
                fsIn.Close();

                return "Encrypted!";
            }
            catch (FileNotFoundException ex)
            {
                return ex.Message;
            }
            catch (IOException ex)
            {
                return ex.Message;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
            finally
            {
            }
        }

        public static byte[] Decrypt(byte[] cipherData, byte[] Key, byte[] IV)
        {
            try
            {
                MemoryStream ms = new MemoryStream();

                Rijndael alg = Rijndael.Create();

                alg.Key = Key;
                alg.IV = IV;

                CryptoStream cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);

                cs.Write(cipherData, 0, cipherData.Length);

                cs.Close();

                byte[] decryptedData = ms.ToArray();

                return decryptedData;
            }
            catch (Exception ex)
            {
                Console.Write(ex.Message);
                return (new byte[] { });
            }
            finally
            {
            }
        }

        public static string Decrypt(string cipherText, string Password)
        {
            byte[] cipherBytes = Convert.FromBase64String(cipherText);

            PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            byte[] decryptedData = Decrypt(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));

            return System.Text.Encoding.Unicode.GetString(decryptedData);
        }

        public static byte[] Decrypt(byte[] cipherData, string Password)
        {
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            return Decrypt(cipherData, pdb.GetBytes(32), pdb.GetBytes(16));
        }

        public static string Decrypt(string fileIn, string fileOut, string Password)
        {
            try
            {
                FileStream fsIn = new FileStream(fileIn, FileMode.Open, FileAccess.Read);
                FileStream fsOut = new FileStream(fileOut, FileMode.OpenOrCreate, FileAccess.Write);

                PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
                Rijndael alg = Rijndael.Create();

                alg.Key = pdb.GetBytes(32);
                alg.IV = pdb.GetBytes(16);

                CryptoStream cs = new CryptoStream(fsOut, alg.CreateDecryptor(), CryptoStreamMode.Write);

                int bufferLen = 4096;
                byte[] buffer = new byte[bufferLen];
                int bytesRead;

                do
                {
                    bytesRead = fsIn.Read(buffer, 0, bufferLen);

                    cs.Write(buffer, 0, bytesRead);

                } while (bytesRead != 0);

                cs.Close();
                fsIn.Close();

                return "Decrypted!";
            }
            catch (FileNotFoundException ex)
            {
                return ex.Message;
            }
            catch (IOException ex)
            {
                return ex.Message;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }

            finally
            {
            }
        }
    }

    #endregion


    #region Compresssion / Decompression

    public class Zipper
    {
        public static byte[] Compress(string data, string mode)
        {
            return Compress(System.Text.Encoding.Unicode.GetBytes(data), mode);
        }

        public static string DecompressToString(byte[] data, string mode)
        {
            return System.Text.Encoding.Unicode.GetString(Decompress(data, mode));
        }

        public static byte[] Compress(byte[] data, string mode)
        {
            try
            {
                MemoryStream ms = new MemoryStream();
                Stream s;

                switch (mode)
                {
                    case "solid":
                        s = new DeflateStream(ms, CompressionMode.Compress);
                        break;
                    case "fast":
                        s = new GZipStream(ms, CompressionMode.Compress);
                        break;
                    default:
                        s = new GZipStream(ms, CompressionMode.Compress);
                        break;
                }

                s.Write(data, 0, data.Length);
                s.Close();

                return ms.ToArray();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        public static byte[] Decompress(byte[] data, string mode)
        {
            try
            {
                string result = string.Empty;
                byte[] buffer = { };

                MemoryStream ms = new MemoryStream(data);
                Stream s;

                switch (mode)
                {
                    case "solid":
                        s = new DeflateStream(ms, CompressionMode.Decompress);
                        break;
                    case "fast":
                        s = new GZipStream(ms, CompressionMode.Decompress);
                        break;
                    default:
                        s = new GZipStream(ms, CompressionMode.Decompress);
                        break;
                }

                int len = 4096;

                while (true)
                {
                    int oldLen = buffer.Length;
                    Array.Resize(ref buffer, oldLen + len);
                    int size = s.Read(buffer, oldLen, len);
                    if (size != len)
                    {
                        Array.Resize(ref buffer, buffer.Length - (len - size));
                        break;
                    }
                    if (size <= 0)
                        break;
                }
                s.Close();

                return buffer;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        public static string Compress(string fileIn, string fileOut, string mode)
        {
            if (BasicIO.WriteStreamToFile(fileOut, Compress(BasicIO.ReadStreamFromFile(fileIn), mode)))
            {
                int sSize = BasicIO.GetFileSize(fileIn);
                int tSize = BasicIO.GetFileSize(fileOut);
                float ratio = ((sSize - tSize) / (float)sSize) * 100;

                return string.Format("Compressed Successfully! Ratio: {0}%", ratio.ToString().Substring(0, ratio.ToString().LastIndexOf(".") + 3));
            }
            else
                return "Operation Interruppted!";
        }

        public static string Decompress(string fileIn, string fileOut, string mode)
        {
            if (BasicIO.WriteStreamToFile(fileOut, Decompress(BasicIO.ReadStreamFromFile(fileIn), mode)))
                return "UnCompressed Successfully!";
            else
                return "Operation Interruppted!";
        }
    }

    #endregion


    #region Low-Level OS Operations and Tools

    public class OSTools
    {
        public static string SetPriority(string level)
        {
            string priority = string.Empty;

            Process p = Process.GetCurrentProcess();

            switch (level)
            {
                case "0":
                    p.PriorityClass = ProcessPriorityClass.Idle;
                    priority = "Idle";
                    break;
                case "1":
                    p.PriorityClass = ProcessPriorityClass.BelowNormal;
                    priority = "BelowNormal";
                    break;
                case "2":
                    p.PriorityClass = ProcessPriorityClass.Normal;
                    priority = "Normal";
                    break;
                case "3":
                    p.PriorityClass = ProcessPriorityClass.AboveNormal;
                    priority = "AboveNormal";
                    break;
                case "4":
                    p.PriorityClass = ProcessPriorityClass.High;
                    priority = "High";
                    break;
                case "5":
                    p.PriorityClass = ProcessPriorityClass.RealTime;
                    priority = "RealTime";
                    break;
                default:
                    p.PriorityClass = ProcessPriorityClass.Normal;
                    priority = "Normal";
                    break;
            }

            return string.Format("Process priority set to {0}. Just look at the Task Manager.", priority);
        }

        public static string ExternalCommand(string app, string args)
        {
            try
            {
                Process p = new Process();
                p.StartInfo.Arguments = args;
                p.StartInfo.FileName = app;
                p.Start();
                Console.WriteLine("Close the external app to comeback here....");
                p.WaitForExit();
            }
            catch (ObjectDisposedException ex)
            {
                return ex.Message;
            }
            catch (InvalidOperationException ex)
            {
                return ex.Message;
            }
            catch (System.ComponentModel.Win32Exception ex)
            {
                return ex.Message;
            }
            catch (SystemException ex)
            {
                return ex.Message;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
            finally
            {
            }

            return "done!";
        }
    }

    #endregion


    #region Web Tools and Utilities

    public class WebUtils
    {
        public static string DownloadURL(string url, string target, string proxy)
        {
            try
            {
                Console.WriteLine("Connecting...");

                if (proxy != string.Empty)
                {
                    Uri proxyURI = new System.Uri(String.Format("http://{0}/", proxy));
                    WebRequest.DefaultWebProxy = new WebProxy(proxyURI);
                }

                using (WebClient client = new WebClient())
                {
                    client.DownloadFile(url, target);
                    client.Dispose();

                    WebRequest.DefaultWebProxy = null;
                }

                return String.Format("Successfully saved to: {0}", target);
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
            finally
            {
            }
        }
    }

    #endregion


    #region Image Manipulation

    class ImageMan
    {
        private static ImageFormat GetImageFormat(string ext)
        {
            ImageFormat format;

            switch (ext)
            {
                case ".png":
                    format = ImageFormat.Png;
                    break;
                case ".jpg":
                    format = ImageFormat.Jpeg;
                    break;
                case ".jpeg":
                    format = ImageFormat.Jpeg;
                    break;
                case ".jpe":
                    format = ImageFormat.Jpeg;
                    break;
                case ".gif":
                    format = ImageFormat.Gif;
                    break;
                case ".tif":
                    format = ImageFormat.Tiff;
                    break;
                case ".tiff":
                    format = ImageFormat.Tiff;
                    break;
                case ".bmp":
                    format = ImageFormat.Bmp;
                    break;
                case ".dib":
                    format = ImageFormat.Bmp;
                    break;
                case ".rle":
                    format = ImageFormat.Bmp;
                    break;
                case ".ico":
                    format = ImageFormat.Icon;
                    break;
                case ".wmf":
                    format = ImageFormat.Wmf;
                    break;
                case ".emf":
                    format = ImageFormat.Emf;
                    break;
                default:
                    format = ImageFormat.Jpeg;
                    break;
            }

            return format;
        }

        private static int GetImageSize(string size, int orgSize)
        {
            try
            {
                if (size.Trim() == string.Empty)
                    return orgSize;

                int res;

                if (size.EndsWith("px"))
                    res = Convert.ToInt32(size.Substring(0, size.Length - 2));
                else if (size.EndsWith("%"))
                    res = (orgSize * Convert.ToInt32(size.Substring(0, size.Length - 1))) / 100;
                else
                    return orgSize;

                if (res < 1)
                    return orgSize;

                return res;
            }
            catch
            {
                return orgSize;
            }
            finally
            {
            }
        }

        public static byte[] GenThumb(byte[] buffer, ImageFormat format, string width, string height)
        {
            try
            {
                MemoryStream msOriginal = new MemoryStream(buffer);
                Image imgOriginal = new Bitmap(msOriginal);

                int w = GetImageSize(width, imgOriginal.Width);
                int h = GetImageSize(height, imgOriginal.Height);

                Image imgConverted = imgOriginal.GetThumbnailImage(w, h, null, new IntPtr());
                MemoryStream msConverted = new MemoryStream();

                imgConverted.Save(msConverted, format);

                buffer = msConverted.ToArray();

                msOriginal.Dispose();
                msConverted.Dispose();
                imgOriginal.Dispose();
                imgConverted.Dispose();

                msOriginal = null;
                msConverted = null;
                imgOriginal = null;
                imgConverted = null;

                return buffer;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
            finally
            {
            }
        }

        public static byte[] GenWatermark(byte[] buffer, ImageFormat format, string copyright)
        {
            /// <summary>
            /// Based on code at:
            /// http://www.codeproject.com/KB/GDI-plus/watermark.aspx
            /// </summary>

            try
            {
                MemoryStream pMS = new MemoryStream(buffer);
                System.Drawing.Image imgPhoto = new System.Drawing.Bitmap(pMS);

                int phWidth = imgPhoto.Width;
                int phHeight = imgPhoto.Height;

                Bitmap bmPhoto = new Bitmap(phWidth, phHeight, imgPhoto.PixelFormat);

                bmPhoto.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);

                Graphics grPhoto = Graphics.FromImage(bmPhoto);

                grPhoto.SmoothingMode = SmoothingMode.AntiAlias;

                grPhoto.DrawImage(imgPhoto, new Rectangle(0, 0, phWidth, phHeight), 0, 0, phWidth, phHeight, GraphicsUnit.Pixel);

                int[] sizes = new int[] { 16, 14, 12, 10, 8, 6, 4 };

                Font crFont = null;
                SizeF crSize = new SizeF();

                for (int i = 0; i < 7; i++)
                {
                    crFont = new Font("Verdana", sizes[i], FontStyle.Bold);
                    crSize = grPhoto.MeasureString(copyright, crFont);

                    if ((ushort)crSize.Width < (ushort)phWidth)
                        break;
                }

                int yPixlesFromBottom = (int)(phHeight * .05);

                float yPosFromBottom = ((phHeight - yPixlesFromBottom) - (crSize.Height / 2));

                float xCenterOfImg = (phWidth / 2);

                StringFormat StrFormat = new StringFormat();
                StrFormat.Alignment = StringAlignment.Center;

                SolidBrush semiTransBrush2 = new SolidBrush(Color.FromArgb(153, 0, 0, 0));

                grPhoto.DrawString(copyright, crFont, semiTransBrush2, new PointF(xCenterOfImg + 1, yPosFromBottom + 1), StrFormat);

                SolidBrush semiTransBrush = new SolidBrush(Color.FromArgb(153, 255, 255, 255));

                grPhoto.DrawString(copyright, crFont, semiTransBrush, new PointF(xCenterOfImg, yPosFromBottom), StrFormat);

                Bitmap bmWatermark = new Bitmap(bmPhoto);
                bmWatermark.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);

                imgPhoto = bmWatermark;

                grPhoto.Dispose();

                MemoryStream wMS = new MemoryStream();
                imgPhoto.Save(wMS, format);

                imgPhoto.Dispose();

                Array.Resize(ref buffer, 0);

                buffer = wMS.ToArray();

                return buffer;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
            finally
            {
            }
        }

        public static string GenThumb(string fileIn, string fileOut, string width, string height)
        {
            if (BasicIO.WriteStreamToFile(fileOut, GenThumb(BasicIO.ReadStreamFromFile(fileIn), GetImageFormat(BasicIO.ExtractExt(fileOut)), width, height)))
                return "Manipulated Successfully";
            else
                return "Operation Interruppted!";
        }

        public static string GenWatermark(string fileIn, string fileOut, string copyright)
        {
            if (BasicIO.WriteStreamToFile(fileOut, GenWatermark(BasicIO.ReadStreamFromFile(fileIn), GetImageFormat(BasicIO.ExtractExt(fileOut)), copyright)))
                return "Watermarked Successfully!";
            else
                return "Operation Interruppted!";
        }
    }    
    
    #endregion


    #region DB Tools

    class DBTools
    {
        private static string GetTempPath()
        {
            string path = System.IO.Path.GetTempPath();
            path += path.EndsWith(Path.DirectorySeparatorChar.ToString()) ? string.Empty : Path.DirectorySeparatorChar.ToString();
            return path;
        }

        public static string CompactRepairJetDB(string mdwFilePath, string dbPw)
        {
            /// <summary>
            /// Based on code at:
            /// http://www.codeproject.com/KB/database/mdbcompact_latebind.aspx
            /// http://www.codeproject.com/KB/database/CompactAndRepair.aspx
            /// </summary>
            
            try
            {
                string tmpFile = GetTempPath() + @"tempdb.mdb";
                string cnnStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Jet OLEDB:Database Password={1};Jet OLEDB:Engine Type=5";

                string connectionString = string.Format(cnnStr, mdwFilePath, dbPw);
                string connectionStringTemp = string.Format(cnnStr, tmpFile, dbPw);

                object[] oParams;
                object objJRO = Activator.CreateInstance(Type.GetTypeFromProgID("JRO.JetEngine"));
                oParams = new object[] { connectionString, connectionStringTemp };
                objJRO.GetType().InvokeMember("CompactDatabase", System.Reflection.BindingFlags.InvokeMethod, null, objJRO, oParams);

                System.IO.File.Delete(mdwFilePath);
                System.IO.File.Move(tmpFile, mdwFilePath);

                System.Runtime.InteropServices.Marshal.ReleaseComObject(objJRO);
                objJRO = null;

                return "done!";
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
            finally
            {
            }
        }
    }

    #endregion


    #region Basic I/O

    class BasicIO
    {
        public static string ExtractExt(string file)
        {
            return file.Contains(".") ? file.Substring(file.LastIndexOf(".")) : string.Empty;
        }

        public static int GetFileSize(string file)
        {
            try
            {
                return (int)new FileInfo(file).Length;
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(ex.Message);
                return -1;
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
                return -1;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return -1;
            }
            finally
            {
            }
        }

        public static byte[] ReadStreamFromFile(string file)
        {
            try
            {
                int len = GetFileSize(file);
                byte[] data = new byte[len];

                using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                {
                    fs.Read(data, 0, len);
                    fs.Close();
                }

                return data;
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
            finally
            {
            }
        }

        public static bool WriteStreamToFile(string file, byte[] data)
        {
            try
            {
                using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write))
                {
                    fs.Write(data, 0, data.Length);
                    fs.Close();
                }

                return true;
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
            finally
            {
            }
        }
    }
    
    #endregion
}

منابع و ماخذ

Fraser, S. R. G. (2006) Pro Visual C++/CLI and the .NET 2.0 Platform Apress
http://msdn2.microsoft.com/
http://www.mono-project.com/Main_Page
http://www.gnu.org/software/dotgnu/
http://en.wikipedia.org/wiki/Mono_(software)#Software_developed_with_Mono
http://msdn.microsoft.com/en-us/library/k8d11d4s(VS.80).aspx
http://msdn.microsoft.com/en-us/library/ms173252(VS.80).aspx
http://msdn.microsoft.com/en-us/library/85344whh(VS.80).aspx
http://msdn.microsoft.com/en-us/library/x0w2664k(VS.80).aspx
http://blog.kowalczyk.info/article/clexe-cmd-line-args.html
http://www.netdecompiler.com/
http://www.red-gate.com/products/reflector/
http://www.red-gate.com/products/ants_profiler/
http://www.csharp411.com/net-obfuscators/
http://www.preemptive.com/
http://www.xenocode.com/Products/Postbuild-for-NET/
http://www.xenocode.com/Products/Virtual-Application-Studio/
http://www.xenocode.com/Products/Fox-Code-Analyzer/
http://support.microsoft.com/kb/931906

  1. Common Language Runtime [return]
  2. Dynamic Language Runtime [return]
  3. Streaming SIMD Extensions [return]
  4. http://www.mono-project.com/Main_Page [return]
  5. http://www.gnu.org/software/dotgnu/ [return]
  6. http://en.wikipedia.org/wiki/Mono_(software)#Software_developed_with_Mono [return]
  7. Unified Class Library [return]
  8. Base Class Library [return]
  9. Framework Class Library [return]
  10. در حالت عادی فایل های اجرائی در ویندوز Portable Executable یا به اختصار PE نامیده می شوند. مانند: exe, .dll, .sys, .obj. [return]
  11. Just-in-time [return]
  12. Common Intermediate Language [return]
  13. Microsoft Intermediate Language [return]
  14. دلیل استفاده از هر دوی این نام ها به چگونگی استفاده Microsoft در مستندات ارائه شده توسط این شرکت برمی گردد. در زمان ارائه مستندات اولیه .NET این شرکت از نام MSIL جهت ارجاع به کد میانی استفاده می نموده است که بلافاصله توسط کتب٬ مجلات و مقالات متعددی بصورت فراگیر استفاده شد. با وجود تغییر نام این بخش به CIL از سوی Microsoft هنوز هم بسیاری از برنامه نویسان قدیمی .NET از واژه MSIL برای رجوع به کد میانی استفاده می نمایند. در ادامه مقاله از واژه CIL جهت ارجاع به زبان میانی استفاده می نمائیم. [return]
  15. Global Assembly Cache [return]
  16. Common Language Specification [return]
  17. Common Type System [return]
  18. جهت کسب اطلاعات بیشتر:

    [return]
  19. http://www.netdecompiler.com/ [return]
  20. http://www.red-gate.com/products/reflector/ [return]
  21. http://www.red-gate.com/products/ants_profiler/ [return]
  22. جهت مشاهده و مقایسه اجمالی لیستی از این ابزارها به آدرس ذیل مراجعه نمائید:

    [return]
  23. http://www.preemptive.com/ [return]
  24. http://www.xenocode.com/Products/Postbuild-for-NET/ [return]
  25. http://www.xenocode.com/Products/Fox-Code-Analyzer/ [return]
  26. http://support.microsoft.com/kb/931906 [return]