
در این فصل، به لایههای عمیقتری از درک تکنیکهای دور زدن آنتیویروس میپردازیم. ابتدا شما را با کد اسمبلی x86 آشنا میکنیم تا بتوانید سازوکارهای درونی سیستمعاملها، باینریهای کامپایلشده و نرمافزارها را بهتر درک کنید. سپس مفهوم، کاربرد و تمرین مهندسی معکوس را معرفی خواهیم کرد. پس از آن، پیادهسازی دور زدن آنتیویروس با استفاده از پچکردن باینری را بررسی میکنیم و سپس به استفاده از کدهای زائد (Junk Code) برای دور زدن و سختتر کردن تحلیلهایی که توسط پژوهشگران امنیتی و خود نرمافزارهای آنتیویروس انجام میشود میپردازیم. همچنین یاد میگیریم چگونه با استفاده از کد PowerShell آنتیویروس را دور بزنیم و با مفهوم استفاده از یک قابلیت مخرب واحد آشنا میشویم. در این فصل، موضوعات زیر بررسی خواهند شد:
• دور زدن آنتیویروس با استفاده از پچکردن باینری
• دور زدن آنتیویروس با استفاده از کد زائد (Junk Code)
• دور زدن آنتیویروس با استفاده از PowerShell
• دور زدن آنتیویروس با استفاده از یک قابلیت مخرب واحد
• قدرت ترکیب چندین تکنیک دور زدن آنتیویروس
• موتورهای آنتیویروسی که در پژوهش ما دور زده شدهاند
راه های دیگری برای دور زدن نرم افزار آنتی ویروس به جز استفاده از کدهای جدید نوشته شده وجود دارد. همچنین می توانیم از یک فایل باینری کامپایل شده استفاده کنیم.
چند تکنیک دور زدن نرم افزار آنتی ویروس وجود دارد که می تواند با کد از قبل کامپایل شده و آماده اجرا انجام شود، حتی اگر توسط موتورهای آنتی ویروس به عنوان بدافزار شناسایی شود.
ما در حین انجام تحقیق برای نگارش این کتاب از دو تکنیک فرعی استفاده کرده ایم:
اشکال زدایی/مهندسی معکوس
زمان سنجی
بیایید با جزئیات به این تکنیک ها نگاه کنیم.
برای انجام مهندسی معکوس بر روی یک فایل کامپایل شده در محیط x86 اینتل، ابتدا باید معماری اسمبلی x86 را درک کنیم.
زبان اسمبلی برای جایگزینی کد ماشین توسعه داده شد و به توسعه دهندگان اجازه می داد برنامه ها را راحت تر بسازند.
اسمبلی یک زبان سطح پایین در نظر گرفته می شود و به همین دلیل دسترسی مستقیم به سخت افزار کامپیوتر مانند CPU دارد. با استفاده از اسمبلی، توسعه دهنده نیازی به درک و نوشتن کد ماشین ندارد. در طول سال ها، زبان های برنامه نویسی زیادی برای ساده تر کردن برنامه نویسی برای توسعه دهندگان توسعه یافته اند.
گاهی اوقات، اگر ما — بهعنوان پژوهشگران امنیتی — نتوانیم یک برنامه را دیکامپایل کنیم تا به کد منبع آن دست یابیم، لازم است از ابزاری به نام دیساسمبلر (Disassembler) استفاده کنیم تا کد ماشین را به کد اسمبلی تبدیل کند.
نمودار زیر روند تبدیل از کد منبع به کد اسمبلی را نشان میدهد:

تکنیک دیباگینگ بر اساس تغییر مقادیر تکی درون پردازهٔ بارگذاریشده و سپس انجام پچکردن روی فایل نهاییشده است.
پیش از آنکه وارد دیباگکردن نرمافزارهای مخرب برای دور زدن آنتیویروس شویم، مفید است که ابتدا درک کنیم مهندسی معکوس شامل چه مراحلی میشود.
مهندسی معکوس فرآیند تحقیق و درک اهداف واقعی پشت یک برنامه یا هر سیستم دیگری از جمله کشف اصول مهندسی و جنبه های تکنولوژیکی آن است. در زمینه امنیت اطلاعات، از این تکنیک بیشتر برای یافتن آسیبپذیریها در کد استفاده میشود. مهندسی معکوس همچنین به طور گسترده ای برای درک فعالیت های مخرب انواع مختلف بدافزار استفاده می شود.
به منظور درک چگونگی مهندسی معکوس یک فایل، توضیح مختصری از چند اصل مهم ارائه خواهیم داد.
استک نوعی حافظه است که توسط پردازههای سیستم برای ذخیرهٔ مقادیری مانند متغیرها و پارامترهای توابع استفاده میشود. چیدمان حافظهٔ استک بر اساس اصل «آخرین ورودی، اولین خروجی» (LIFO) است؛ به این معنا که اولین مقداری که در استک ذخیره میشود، اولین مقداری است که از استک «خارج» (Pop) میشود.
نمودار زیر اصل LIFO را نشان میدهد: عنصر دادهٔ ۵ آخرین مقداری است که روی استک قرار داده شده و بنابراین اولین عنصری است که از استک خارج میشود.

اکنون که فهمیدیم استک چیست، بیایید به سراغ هیپ و ثباتهای CPU برویم.
برخلاف حافظهٔ استک که خطی است، حافظهٔ هیپ حافظهای «آزاد» و بهصورت پویا تخصیصیافته است. حافظهٔ هیپ میتواند در هر زمانی تخصیص داده شود و در هر زمانی آزاد شود. این نوع حافظه عمدتاً برای اجرای برنامهها در زمان اجرا (Runtime) درون سیستمعاملها استفاده میشود.
معماری x86 چندین ثبات همهمنظوره (GPRs) را بههمراه تعدادی ثبات برای عملیاتهای خاص تعریف میکند. این مکانهای ویژهٔ حافظه بخش جداییناپذیر CPU هستند و مستقیماً توسط پردازنده استفاده میشوند. در رایانههای امروزی، بیشتر ثباتها برای کارهایی غیر از هدف اولیهٔ طراحیشان به کار میروند. برای مثال، ثبات ۳۲ بیتی ECX (یا RCX در معماری ۶۴ بیتی) معمولاً بهعنوان شمارنده برای عملیاتهایی مانند حلقهها و مقایسهها استفاده میشود، اما میتواند برای عملیاتهای دیگر نیز به کار رود. فهرست زیر کاربرد کلی هر یک از ثباتها را توضیح میدهد:
• EAX — معمولاً برای عملیاتهای حسابی استفاده میشود؛ در عمل بهعنوان ناحیهای از حافظه برای ذخیرهٔ مقادیر بازگشتی و اهداف دیگر نیز به کار میرود.
• EBX — عموماً برای ذخیرهٔ آدرسهای حافظه استفاده میشود.
• ECX — بیشتر بهعنوان شمارنده برای عملیاتهای حلقه و مقایسهها استفاده میشود.
• EDX — عمدتاً برای عملیاتهای تقسیم و ضرب که به حافظهٔ بیشتری برای نگهداری مقادیر نیاز دارند استفاده میشود. همچنین EDX آدرسهای مورد استفاده برای عملیاتهای ورودی/خروجی (I/O) را ذخیره میکند.
برخی ثباتها بهعنوان اشارهگر به مکانهای مشخصی استفاده میشوند:
• ESI — اندیس مبدأ (Source Index) که عمدتاً برای انتقال داده از یک ناحیهٔ حافظه به ناحیهٔ مقصد (EDI) استفاده میشود.
• EDI — اندیس مقصد (Destination Index) که معمولاً بهعنوان مقصد دادههایی که از ناحیهٔ حافظهٔ مبدأ (ESI) منتقل میشوند به کار میرود.
• ESP — بههمراه ثبات EBP برای تعریف فریم استک استفاده میشود و به بالای استک اشاره میکند.
• EBP — بههمراه ثبات ESP برای تعریف فریم استک به کار میرود و به پایهٔ استک اشاره میکند.
• EIP — به دستور بعدی که باید توسط CPU اجرا شود اشاره میکند.
در مثال زیر، ما از netcat.exe استفاده کردیم که قبلاً توسط اکثر موتورهای آنتی ویروس به عنوان یک فایل مخرب امضا شده و شناسایی شده است. وقتی فایل کامپایل شده را با x32dbg باز کردیم و به نقطه ورودی فایل رسیدیم، اولین چیزی که دیدیم این بود که اولین تابع از دستور sub esp، 18 استفاده میکند - از رجیستر ESP کم میکند (همانطور که قبلا توضیح داده شد).
برای اطمینان از اینکه فایل را "break" یا "corrupt" نمی کنیم، به این معنی که فایل حتی پس از ایجاد تغییرات همچنان می تواند در سیستم عامل اجرا شود، یک تغییر جزئی در کد برنامه ایجاد کردیم. ما عدد 18 را به 17 تغییر دادیم، سپس فایل را اصلاح کردیم تا به عنوان بخشی از فایل اجرایی اصلی در هارد دیسک کامپیوتر ذخیره شود.
هنگامی که فایل را در VirusTotal آپلود کردیم، متوجه شدیم که با این تغییر بسیار جزئی، در واقع موفق شده ایم حدود 10 برنامه آنتی ویروس را دریافت کنیم. شناسایی آنتی ویروس از 34 به 24 کاهش یافت.
از نظر تئوری، هر تغییری در محتویات یک فایل میتواند یک موتور آنتی ویروس ثابت دیگر را دور بزند، زیرا نمیدانیم هر موتور استاتیک از کدام امضا استفاده میکند.
تصویر زیر netcat.exe اصلی را با زیر دستورالعمل نشان می دهد
esp، 18:

تکنیک دیگری که می توانیم روی یک فایل کامپایل شده اجرا کنیم Timestomping نام دارد. این بار، ما خود فایل را ویرایش نمی کنیم، بلکه در عوض، زمان ایجاد آن را ویرایش می کنیم.
یکی از راه هایی که بسیاری از موتورهای آنتی ویروس برای امضای بدافزار استفاده می کنند، تاریخ ایجاد فایل است. آنها این کار را برای انجام امضای ثابت انجام می دهند. به عنوان مثال، اگر رشته های X، Y و Z
وجود داشته باشد و فایل در 15 ژانویه 2017 ایجاد شده است، سپس فایل به عنوان بدافزار از نوع خاصی شناسایی می شود.
موتورهای آنتی ویروس گاهی اوقات در منطق کد جستجو می کنند تا آن را شناسایی کنند تا بعداً آن را به عنوان نوع خاصی از بدافزار طبقه بندی کنند.
برای اینکه نرم افزار آنتی ویروس نتواند در منطق کد جستجو کند، می توانیم از کد ناخواسته استفاده کنیم که به ما کمک می کند منطق کد را پیچیده تر کنیم.
راه های زیادی برای استفاده از این تکنیک وجود دارد، اما رایج ترین روش ها شامل استفاده از پرش های شرطی، نام متغیرهای نامربوط و توابع خالی است.
به عنوان مثال، به جای نوشتن بدافزاری که حاوی یک تابع اصلی با دو متغیر معمولی (مثلاً یک آدرس IP و یک شماره پورت) با نام متغیرهای عمومی و بدون شرط است، ترجیح داده می شود، اگر بخواهیم کد را پیچیده کنیم، سه تابع ایجاد کنیم که دو تابع خالی (استفاده نشده) هستند. در داخل تابع مخرب، ما همچنین میتوانیم تعداد معینی از شرایط را اضافه کنیم که هرگز رخ نمیدهند و نام متغیرهای بیمعنی را اضافه کنیم.
نمودار مثال ساده زیر کدی را نشان می دهد که برای باز کردن یک سوکت به آدرس یک مهاجم، 192.168.10.5 طراحی شده است.
در سمت راست، ما کدهای ناخواسته را اضافه کرده ایم تا برنامه اصلی را پیچیده کنیم و در عین حال همان عملکرد را تولید کنیم:

علاوه بر استفاده از توابع خالی، شرایطی که هرگز اتفاق نمیافتند و نامهای بیگناه متغیرها، میتوانیم نرمافزار آنتیویروس را با انجام عملیاتهای گستردهتر که بر روی هارد دیسک تأثیر میگذارد، گیج کنیم. راههای مختلفی برای رسیدن به این هدف وجود دارد، از جمله بارگذاری یک DLL که وجود ندارد و ایجاد مقادیر قانونی رجیستری.
در اینجا یک مثال است:

در این نمودار، میتوانید شبهکدی ساده را ببینید که با استفاده از یک سوکت، اتصالی به سرور فرماندهی و کنترل (C2) مهاجم برقرار میکند. در سمت چپ، کد قبل از اعمال تکنیک کد زائد (Junk Code) قرار دارد و در سمت راست، همان قابلیت پس از استفاده از تکنیک کد زائد نمایش داده شده است.
نکتهٔ مهم
کد زائد همچنین میتواند همراه با تکنیکهایی مانند مبهمسازی جریان کنترل (Control Flow Obfuscation) استفاده شود تا تحلیل را برای پژوهشگران امنیتی دشوارتر کرده و دور زدن آنتیویروس را بهطور بالقوه مؤثرتر کند.
یکی از مشکلات اصلی که شرکتهای تولیدکنندهٔ نرمافزارهای آنتیویروس باید با آن مقابله کنند، تشخیصهای اشتباه (False Positives) است. نرمافزار آنتیویروس نباید هر رویداد کوچک و کماهمیتی را که روی سیستم رخ میدهد به کاربر گزارش کند. اگر چنین کاری انجام دهد، ممکن است کاربر مجبور شود آنتیویروس را کنار بگذارد و به سراغ نرمافزار دیگری برود که هنگام استفادهٔ عادی وقفههای کمتری ایجاد میکند.
برای مقابله با تشخیصهای اشتباه، شرکتهای آنتیویروس نرخ تشخیص خود را افزایش میدهند. برای مثال، اگر یک فایل در موتورهای استاتیک و داینامیک امضا نشده باشد، موتور هیوریستیک وارد عمل میشود و بهصورت مستقل شروع به محاسبه میکند که آیا فایل مخرب است یا خیر، با استفاده از پارامترهای گوناگون. برای نمونه، آنتیویروس بررسی میکند که آیا فایل در حال باز کردن یک سوکت است، آیا اقدام به ایجاد ماندگاری (Persistence) در پوشههای مربوطه میکند، و آیا از یک سرور راهدور دستور دریافت میکند یا نه.
برای مثال، ممکن است با احتمال ۷۰٪ فایل بهعنوان مخرب شناسایی شود و نرمافزار آنتیویروس از اجرای آن جلوگیری کند.
برای استفاده از این وضعیت جهت دور زدن آنتیویروس، باید یک سؤال مهم بپرسیم:
آیا نرمافزار آنتیویروس زمانی که یک فایل مخرب فقط یک قابلیت مخرب را اجرا میکند، هشدار صادر میکند؟
بنابراین، پاسخ به نوع قابلیت بستگی دارد. اگر دربارهٔ قابلیتی صحبت کنیم که لزوماً مخرب محسوب نمیشود، آنتیویروس فایل را بهعنوان فایلی که دارای یک قابلیت مخرب است شناسایی میکند، اما امتیاز آن به اندازهای بالا نخواهد بود که به کاربر هشدار دهد یا از اجرای فایل مخرب جلوگیری کند؛ در نتیجه، نرمافزار آنتیویروس اجازهٔ اجرای فایل را میدهد.
این نوع رفتار موتور هیوریستیک دقیقاً همان چیزی است که میتوان از آن برای دور زدن نرمافزارهای آنتیویروس استفاده کرد.
نکتهٔ مهم این است که از نظر عملی، برای دور زدن یک موتور آنتیویروس در دنیای واقعی، باید از ترکیبی از چندین تکنیک دور زدن استفاده کنید، نه فقط یک تکنیک واحد. حتی اگر یک تکنیک خاص بتواند از موتور استاتیک عبور کند، منطقی است که فرض کنیم موتور داینامیک و/یا هیوریستیک قادر به شناسایی فایل خواهد بود. برای مثال، میتوان از ترکیبی از تکنیکهای زیر برای دستیابی به یک دور زدن کامل آنتیویروس استفاده کرد:

در این فصل از کتاب، با سایر تکنیکهای دور زدن آنتیویروس آشنا شدیم که میتوانند بهطور بالقوه برای دور زدن هر دو موتور استاتیک و داینامیک استفاده شوند.
تکنیکهایی که در این فصل ارائه شدند شامل پچکردن باینری، کد زائد (Junk Code)، PowerShell و استفاده از یک قابلیت مخرب واحد بودند.
در تکنیک پچکردن باینری، با مبانی مهندسی معکوس برنامههای مبتنی بر ویندوز x86 و تکنیک تایماستمپینگ (Timestomping) آشنا شدیم که برای دستکاری زمانمُهر (Timestamp) فایلهای اجرایی استفاده میشود.
در تکنیک کد زائد (Junk Code)، استفاده از دستورات شرطی if را توضیح دادیم که باعث انحراف مکانیزم تشخیص آنتیویروس میشوند.
در تکنیک PowerShell، از ابزار PowerShell برای دور زدن آنتیویروس استفاده کردیم.
و در تکنیک استفاده از یک قابلیت مخرب واحد، یک سؤال مهم را برای درک بهتر دیدگاه موتور تشخیص آنتیویروس مطرح کردیم و سپس با یک مثال عملی به آن پاسخ دادیم.
در فصل بعدی، یاد خواهیم گرفت که با تکنیکهای دور زدن آنتیویروسی که تاکنون در این کتاب آموختهایم، چه کارهایی میتوان انجام داد.
Telegram: @CaKeegan
Gmail : amidgm2020@gmail.com