
در این فصل، با مبانی دور زدن موتور داینامیک یک نرمافزار آنتیویروس آشنا میشوید.
یاد میگیریم چگونه از VirusTotal و سایر پلتفرمهای تشخیص موتورهای آنتیویروس استفاده کنیم تا مشخص شود کدام آنتیویروسها را موفق شدهایم دور بزنیم.
همچنین به درک و پیادهسازی تکنیکهای مختلف دور زدن آنتیویروس میپردازیم؛ تکنیکهایی که میتوانند بهطور بالقوه موتورهای آنتیویروس را دور بزنند، مانند تزریق پردازش (Process Injection)، استفاده از کتابخانه پیوند پویا (DLL) و تکنیکهای مبتنی بر زمانبندی (Timing-based) برای دور زدن بیشتر نرمافزارهای آنتیویروس موجود.
در این فصل، به درکی عملی از تکنیکهای دور زدن آنتیویروس دست پیدا میکنید و موضوعات زیر را بررسی خواهیم کرد:
. The preparation
. VirusTotal
. Antivirus bypass using process injection
. Antivirus bypass using a DLL
. Antivirus bypass using timing-based techniques
آماده سازی
برخلاف هنگام جستجوی آسیبپذیریها و بهرهبرداری از آنها، تکنیکهای بایپس عمدتاً با تحقیقات آسیبپذیری موتور آنتیویروس سروکار ندارند. در عوض، آنها بیشتر با نوشتن بدافزارهایی سروکار دارند که حاوی تعدادی تکنیک دور زدن هستند و سپس بدافزار حاوی این تکنیک ها را در برابر موتورهای آنتی ویروسی که ما به دنبال دور زدن آن هستیم، آزمایش می کنند.
به عنوان مثال، اگر بخواهیم آسیب پذیری خاصی را در یک موتور آنتی ویروس پیدا کنیم، باید موارد زیر را رعایت کنیم:
1. ما باید سرنخ های تحقیقاتی را جمع آوری کنیم. سپس، برای هر لید، باید مشخص کنیم که لید چه کاری انجام میدهد، چه زمانی شروع به اجرا میکند، آیا یک سرویس است، آیا زمانی که یک فایل را اسکن میکنیم شروع به اجرا میکند یا خیر، و آیا یک DLL است که به همه فرآیندها تزریق میشود، به همراه بسیاری از سؤالات بیشتر برای کمک به تحقیق ما.
2. پس از آن، باید بفهمیم که به دنبال کدام آسیبپذیری هستیم، و تنها در این صورت است که میتوانیم واقعاً شروع به تحقیق در مورد نرمافزار آنتیویروس برای یافتن آسیبپذیری کنیم.
3. برای استفاده از تکنیک بای پس، ابتدا باید سرنخ های تحقیقاتی را جمع آوری کنیم و پس از آن، شروع به نوشتن کد بدافزاری می کنیم که حاوی چندین تکنیک بای پس مرتبط است.
4. سپس مرحله آزمون و خطا را با بدافزاری که نوشته ایم آغاز می کنیم.
آزمایش اینکه آیا میتواند نرمافزار آنتیویروس را دور بزند یا خیر و بر این اساس نتیجهگیری میکند.
هنگامی که یک تکنیک خاص موفق به دور زدن نرم افزار آنتی ویروس خاص می شود، همیشه ایده خوبی است که بدانیم چرا موفق شد و کدام موتور در نرم افزار آنتی ویروس دور زده شده است (استاتیک، پویا یا اکتشافی). ما میتوانیم این درک را در مورد سرنخهایی که برای انجام مهندسی معکوس جمعآوری کردهایم اعمال کنیم تا مطمئن باشیم که این تکنیک واقعاً در دور زدن موتور موفق است. البته در پایان این فرآیند، گزارش بای پس به فروشنده نرم افزار و پیشنهاد راهکارهایی برای بهبود نرم افزار آنتی ویروس آنها ضروری است.
قبل از شروع تحقیق دور زدن آنتی ویروس، در اینجا چند نکته مهم وجود دارد که باید در نظر داشته باشید:
. از آخرین نسخه نرم افزار آنتی ویروس استفاده کنید.
. پایگاه داده امضا را به جدیدترین نسخه به روز کنید تا مطمئن شوید که جدیدترین امضاهای ثابت را دارید.
. در حین انجام تحقیق، اتصال اینترنت را خاموش کنید، زیرا ما نمی خواهیم نرم افزار آنتی ویروس با یک سرور خارجی ارتباط برقرار کند و تکنیک بای پسی را که ما کشف کرده ایم امضا کند.
. از جدیدترین نسخه سیستم عامل با آخرین پایگاه دانش (KB) استفاده کنید تا بای پس موثر باشد.
اکنون که با موضوع تحقیق دور زدن آنتی ویروس آشنا شدیم، بیایید با اهمیت استفاده از VirusTotal و سایر پلتفرم ها به عنوان بخشی از تحقیقات خود آشنا شویم.
در این کتاب و به طور کلی در تحقیق در مورد تکنیک های بای پس آنتی ویروس، از پلتفرم هایی مانند ویروس توتال بسیار استفاده خواهیم کرد.
یک پلت فرم اسکن بدافزار بسیار شناخته شده و محبوب است.
ویروس توتال شامل موتورهای تشخیص سازندگان مختلف امنیتی است که میتوان آنها را هنگام آپلود فایلها بررسی کرد تا بررسی شود که آیا این موتورهای شناسایی فایلی را به عنوان بدافزار یا حتی مشکوک تشخیص میدهند، مقادیر جستجویی مانند Uniform Resource Locator (URL)، آدرسهای پروتکل اینترنت (IP) و هش فایلهای قبلاً آپلود شده. VirusTotal بسیاری از ویژگیهای بیشتر را فراهم میکند، مانند نمودار VirusTotal، که توانایی بررسی روابط فایلها، URLها و آدرسهای IP و ارجاع متقابل بین آنها را فراهم میکند. پلتفرمهایی مانند VirusTotal برای درک اینکه آیا بدافزار ما که مبتنی بر برخی از تکنیکهای بایپس ما است، واقعاً بخشی یا حتی تمام موتورهای آنتیویروس موجود در پلتفرم مربوطه را دور میزند بسیار مفید هستند. علاوه بر این، اگر بدافزار ما در یک یا چند موتور آنتی ویروس شناسایی شود، نام امضایی که بدافزار ما را شناسایی کرده است به ما ارائه می شود تا بتوانیم از آن درس بگیریم و مطابق با آن سازگار شویم.
صفحه اصلی VirusTotal در تصویر زیر نشان داده شده است:

وقتی فایلی را در VirusTotal آپلود می کنیم، سایت فایل را برای بسیاری از موتورهای آنتی ویروس ارسال می کند تا بررسی کند که آیا فایل مخرب است یا خیر. اگر هر موتوری فایل را به عنوان یک فایل مخرب شناسایی کرده باشد، Virus Total نام نرم افزار آنتی ویروسی را که بدافزار را شناسایی کرده است، به ما نشان می دهد و نام امضا را با رنگ قرمز مشخص می کند.
هنگامی که یک فایل را در VirusTotal آپلود کردیم، Virus Total بررسی می کند که آیا هش از قبل در پایگاه داده آن وجود دارد یا خیر. در این صورت، آخرین نتایج اسکن را نشان می دهد، و در غیر این صورت، ویروس توتال فایل را ارسال می کند تا بررسی کند که آیا فایل مخرب است یا خیر.
به عنوان مثال، در اینجا فایلی وجود دارد که توسط VirusTotal به عنوان بدافزار در چندین موتور آنتی ویروس شناسایی شده است:

به منظور شناسایی بهتر بدافزارها، VirusTotal شامل یک سندباکس داخلی به نام Virus Total Jujubox است.
VirusTotal Jujubox یک سندباکس تحلیل رفتار مبتنی بر ویندوز است که نتایج خود را به عنوان یک گزارش، به عنوان بخشی از نتایج بسیاری از فایلهای اسکن شده نشان میدهد.
جعبه شنی Jujubox اطلاعات رفتاری مهمی را در رابطه با اجرای فایلهای مخرب استخراج میکند، از جمله عملیات ورودی/خروجی فایل (I/O)، تعاملات رجیستری، فایلهای حذف شده، عملیات mutex، ماژولهای بارگذاریشده مانند DLL و فایلهای اجرایی، هش کردن JA3 و استفاده از واسط برنامهنویسی برنامه کاربردی Windows (Application Programming Interface). علاوه بر این، از رهگیری ترافیک شبکه از جمله تماسهای پروتکل انتقال ابرمتن (HTTP)، رزولوشنهای سیستم نام دامنه (DNS)، اتصالات پروتکل کنترل انتقال (TCP)، استفاده از الگوریتمهای تولید دامنه (DGAs)، ارائه یک تخلیه از فایلهای ضبط بسته (PCAP) و موارد دیگر پشتیبانی میکند.
برای نمایش نتایج کامل سندباکس Jujubox، باید به تب BEHAVIOR بروید، روی VirusTotal Jujubox کلیک کنید و سپس بر روی گزارش کامل، همانطور که در تصویر زیر نشان داده شده است، کلیک کنید:

پس از آن، یک پنجره جدید باز میشود که شامل جزئیات استخراجشده از VirusTotal Jujubox خواهد بود؛ برای مثال فراخوانیهای API ویندوز (Windows API Calls)، درخت پردازشها (Process Tree)، اسکرینشاتها و موارد دیگر، همانطور که در اسکرینشات زیر نشان داده شده است:

یکی از چالش های اصلی نویسندگان بدافزار مخفی کردن بدافزار از نرم افزار آنتی ویروس و کاربران است. این یک چالش آسان نیست.
در اصل، نویسندگان بدافزار بر تکنیک ساده تغییر نام بدافزار به نام فایل قانونی که باعث ایجاد سوء ظن در سیستم می شود، مانند svchost.exe یا lsass.exe تکیه می کردند. این تکنیک بر روی کاربران عادی که فاقد درک اولیه و پیشینه کامپیوتر و فناوری بودند، کار می کرد، اما البته، روی کاربران آگاه با درک نحوه عملکرد سیستم عامل ها و نرم افزارهای آنتی ویروس کار نمی کرد.
اینجاست که تکنیک فرآیند تزریق وارد تصویر می شود.
تزریق فرآیند یکی از رایج ترین تکنیک هایی است که برای دور زدن موتورهای آنتی ویروس به صورت پویا استفاده می شود. بسیاری از فروشندگان آنتی ویروس و توسعه دهندگان نرم افزار برای بررسی فرآیندهای در حال اجرا بر روی سیستم به اصطلاح به تزریق فرآیند یا تزریق کد متکی هستند. با استفاده از تزریق فرآیند، میتوانیم کدهای مخرب را به فضای آدرس یک فرآیند قانونی در سیستم عامل تزریق کنیم، در نتیجه از شناسایی موتورهای آنتی ویروس پویا جلوگیری میکنیم.
در اکثر مواقع، دستیابی به این هدف به ترکیب خاصی از فراخوانی های API ویندوز نیاز دارد. در حین نوشتن این کتاب ما از حدود پنج روش برای انجام این کار استفاده کردیم، اما سه روش اساسی از این تکنیک ها را برای تزریق کد به یک فرآیند هدف توضیح خواهیم داد. شایان ذکر است که اکثر موتورهای آنتی ویروس این عمل را به منظور بررسی کدهای مخرب در فرآیندهای در حال اجرا در سیستم عامل اجرا می کنند.
اما این تنها فروشندگان آنتی ویروس نیستند که از این توانایی سوء استفاده می کنند، بلکه عوامل تهدید نیز از آن برای تزریق کد مخرب خود برای اهدافی مانند ورود به سیستم ضربه زدن به کلید، پنهان کردن حضور بدافزار تحت سایر فرآیندهای قانونی، قلاب کردن و دستکاری توابع و حتی به منظور دسترسی به سطوح امتیاز افزایش یافته سوء استفاده می کنند.
قبل از اینکه بفهمیم تزریق فرآیند چیست، باید در مورد مفهوم فضای آدرس فرآیند بدانیم.
فضای آدرس فرآیند فضایی است که بر اساس میزان حافظه رایانه به هر فرآیند در سیستم عامل اختصاص داده می شود. به هر فرآیندی که فضای حافظه اختصاص داده می شود مجموعه ای از فضاهای آدرس حافظه داده می شود. هر فضای آدرس حافظه بسته به کد برنامه نویس، فرمت اجرایی مورد استفاده (مانند فرمت PE) و سیستم عاملی که در واقع بارگذاری فرآیند و ویژگی های آن، نگاشت آدرس های مجازی اختصاص داده شده به آدرس های فیزیکی و غیره را بر عهده دارد، هدف متفاوتی دارد. نمودار زیر یک طرح نمونه از یک فضای آدرس فرآیند معمولی را نشان می دهد:
اکنون که فهمیدیم تزریق فرآیند چیست، میتوانیم به درک بیشتر ادامه دهیم.
هدف از تزریق فرآیند، همانطور که قبلا ذکر شد، تزریق یک قطعه کد به فضای آدرس حافظه پردازش یک فرآیند دیگر، دادن مجوز اجرای فضای آدرس حافظه و سپس اجرای کد تزریق شده است. این نه تنها برای تزریق یک قطعه کد پوسته بلکه برای تزریق یک DLL یا حتی یک فایل کامل اجرایی (EXE) نیز صدق می کند.
برای رسیدن به این هدف، مراحل کلی زیر لازم است:
1. یک فرآیند هدف را شناسایی کنید که در آن کد را تزریق کنید.
2. یک دستگیره برای فرآیند مورد نظر برای دسترسی به فضای آدرس فرآیند آن دریافت کنید.
3. یک فضای آدرس حافظه مجازی که در آن کد تزریق و اجرا می شود، اختصاص دهید و در صورت نیاز یک پرچم اجرایی اختصاص دهید.
4. تزریق کد را به فضای آدرس حافظه اختصاص داده شده فرآیند مورد نظر انجام دهید.
5. در نهایت کد تزریق شده را اجرا کنید.
نمودار زیر کل این فرآیند را به شکل ساده شده نشان می دهد:

اکنون که این دیدگاه سطح بالا را در مورد نحوه انجام تزریق فرآیند یا تزریق کد داریم، اجازه دهید به توضیح عملکردهای Windows API بپردازیم.
ویندوز API
قبل از بررسی عملکردهای Windows API، ابتدا باید درک درستی از چیستی API به معنای کلی داشته باشیم. API پلی است بین دو برنامه کاربردی، سیستمها و معماریهای مختلف. از نظر عملی، هدف اصلی یک تابع API، انتزاعی کردن پیادهسازیهای اساسی است تا به توسعهدهندگان در ایجاد برنامهها کمک کند.
Windows API مجموعه اصلی APIهای مایکروسافت است که به توسعهدهندگان این امکان را میدهد تا کدی ایجاد کنند که با عملکردهای زیرین و از پیش نوشته شده ارائه شده توسط سیستم عامل ویندوز در تعامل باشد.
برای درک واضحتر این مفهوم، در زیر یک برنامه ساده "Hello World" با کد C ارائه شده است:

توجه داشته باشید که در قطعه کد قبلی، یک import از stdio.h وجود دارد که به عنوان فایل هدر شناخته می شود. واردات با استفاده از دستورالعمل #include انجام می شود. این فایل هدر تابعی به نام print f را ارائه می دهد که یک پارامتر را می گیرد: رشته ای که قرار است چاپ شود. تابع print f در واقع حاوی مقدار نسبتاً زیادی کد است که صرفاً یک رشته اصلی را چاپ می کند. این یک مثال عالی است زیرا اهمیت عملکردهای Windows API را برجسته می کند. اینها کارکردهای اساسی زیادی را در اختیار ما قرار می دهند که در غیر این صورت به توسعه خود نیاز داریم. با دسترسی به توابع مبتنی بر API، میتوانیم کد را آسانتر و کارآمدتر و به روشی واضحتر و زیباتر ایجاد کنیم.
برای درک عمیقتر آنچه در زیر سیستم عامل ویندوز میگذرد، باید تفاوتهای بین APIهای ویندوز و APIهای بومی را نیز بررسی کنیم. توابع Windows API توابع حالت کاربر هستند که به طور کامل در سایت مایکروسافت مستند شده اند. با این حال، اکثر توابع API ویندوز در واقع از API های بومی برای انجام کار فراخوانی می کنند.
یک مثال عالی از این تابع Windows API CreateFile() است که یک فایل ایجاد میکند یا یک دسته برای فایل موجود برای خواندن دادههای آن دریافت میکند. تابع ()CreateFile، مانند هر تابع دیگر API ویندوز، در دو نوع وجود دارد: یک نوع 'A' و یک نوع 'W'. هنگامی که نوع 'A' در یک تابع API ویندوز استفاده می شود، انتظار دارد آرگومان رشته ای موسسه استانداردهای ملی آمریکا (ANSI) را دریافت کند. هنگامی که نوع 'W' در یک تابع API ویندوز استفاده می شود، انتظار یک آرگومان رشته ای با کاراکتر گسترده را دارد. در واقع، بیشتر توابع API ویندوز از نوع W استفاده میکنند، اما این بستگی به نحوه ایجاد کد توسط نویسنده کد و انتخاب کامپایلر دارد.
هنگامی که یک تابع API ویندوز مانند CreateFile() فراخوانی می شود، بسته به پارامتر ارائه شده توسط توسعه دهنده، ویندوز اجرا را به یکی از دو روتین Native API منتقل می کند: ZwCreateFile یا NtCreateFile.
در اینجا یک مثال عملی از جریان اجرای CreateFile است که به آن اشاره شد. ما از گزینه File -> Open... در notepad.exe استفاده می کنیم و یک فایل آزمایشی را که قبلا برای این دمو ایجاد کرده ایم باز می کنیم. قبل از انجام این کار، باید از Process Monitor (ProcMon) استفاده کنیم.
همانطور که در تصویر زیر نشان داده شده است، در Procmon.exe، فیلترها را تنظیم می کنیم:

همانطور که در اینجا دیده میشود، میتوانیم فیلتر Process Name را طوری تنظیم کنیم که فقط و دقیقاً نتایج مربوط به پردازش notepad.exe نمایش داده شود. سپس از فیلتر Operation استفاده میکنیم و مقدار آن را فقط روی CreateFile قرار میدهیم؛ عملیاتی که همانطور که قبلاً توضیح داده شد، یک فایل جدید ایجاد میکند یا یک هندل (Handle) به یک فایل موجود دریافت میکند.
در نهایت، از فیلتر Path بههمراه مقدار Demo استفاده میکنیم تا فقط نتایجی نمایش داده شوند که نام فایل آنها شامل رشته Demo باشد.
در ادامه، یک اسکرینشات نمایش داده شده است که نتایج را پس از باز شدن فایل توسط notepad.exe نشان میدهد.

همانطور که در اینجا مشاهده می شود، عملیات CreateFile همانطور که باید با دسترسی دلخواه Generic Read انجام می شود. بیایید اکنون عمیق تر برویم و درک کنیم که چگونه این عملیات از دیدگاه سطح پایین اجرا می شود. در مثال زیر و در مورد برنامه notepad.exe ویندوز، تابع API ویندوز استفاده شده CreateFilew است. برای درک جریان اجرا باید روی این تابع یک نقطه شکست قرار دهیم. برای این کار از دیباگر حالت کاربری x64dbg استفاده می کنیم.
تصویر زیر نشان می دهد که چگونه یک نقطه شکست در تابع CreateFileW تنظیم می شود و نشان می دهد که فرآیند به نقطه شکست رسیده است:

در قسمت فرمان x64dbg می توانید دستور bp CreateFilew را ببینید و پس از زدن Enter و کلید F9 برای ادامه اجرا، فرآیند به نقطه شکست می رسد. در آنجا، اکنون میتوانیم دستورالعمل اسمبلی jmp CreateFilew را ببینیم که بخشی از کتابخانه kernel32.dll است.
اسکرینشات زیر نشان میدهد که پس از اجرای دستور jump چه اتفاقی میافتد؛ جریان اجرا از کتابخانه kernel32.dll به کتابخانه kernelbase.dll منتقل میشود. این کتابخانه شامل تابع واقعی Windows Native API با نام ZwCreateFile است.

در نهایت، در اسکرینشات زیر میتوانید مشاهده کنید که پیش از اجرای دستور syscall، جریان اجرا از کتابخانه kernelbase.dll به کتابخانه ntdll.dll منتقل میشود و سپس از طریق این دستور به لایههای پایینتر سیستمعامل ویندوز، مانند هسته (Kernel)، انتقال مییابد.

با داشتن این درک عمیقتر از مفاهیم و شیوههای اساسی زیربنای نحوه مدیریت ویندوز با اجرای فرآیند، اکنون میتوانیم به سه تکنیک تزریق فرآیند بپردازیم.
ما به این تکنیک نخست، تزریق DLL کلاسیک میگوییم. این روش با استفاده از شش تابع پایه از Windows API، یک DLL مخرب را مجبور میکند داخل یک پردازش دیگر (پردازش راهدور) بارگذاری شود.
توابع مورد استفاده در این روش به شرح زیر هستند:
OpenProcess
با استفاده از این تابع و ارائهی شناسهی پردازش هدف (Process ID) بهعنوان یکی از پارامترها، پردازش تزریقکننده یک هندل معتبر به پردازش راهدور دریافت میکند.
VirtualAllocEx
این تابع برای اختصاص یک بافر حافظه در فضای آدرس پردازش هدف استفاده میشود. این بافر در نهایت شامل مسیر فایل DLL بارگذاریشده خواهد بود.
WriteProcessMemory
این تابع عملیات تزریق واقعی را انجام میدهد و محتوای مخرب (Payload) را مستقیماً داخل حافظهی پردازش هدف مینویسد.
CreateRemoteThread
این تابع یک ترد جدید در داخل پردازش راهدور ایجاد میکند و در نهایت تابع LoadLibrary() را اجرا میکند تا DLL موردنظر بارگذاری شود.
LoadLibrary / GetProcAddress
این توابع آدرس DLL بارگذاریشده در پردازش را برمیگردانند. با توجه به اینکه فایل kernel32.dll در تمامی پردازشهای ویندوز در یک آدرس یکسان نگاشت (Map) میشود، میتوان از این توابع برای بهدست آوردن آدرس API موردنظر جهت بارگذاری در پردازش راهدور استفاده کرد.
پردازشهای x86 و x64 چیدمان حافظه متفاوتی دارند و DLLهای بارگذاریشده در فضاهای آدرس متفاوتی نگاشت (Map) میشوند.
پس از انجام این شش عملکرد، فایل DLL مخرب در داخل عامل اجرا می شود.
سیستم در فضای آدرس فرآیند قربانی هدف.
در تصویر اسکرینشات زیر، میتوانید یک بدافزار را مشاهده کنید که از تزریق DLL کلاسیک استفاده میکند؛ این تصویر در نمای IDA Pro نمایش داده شده است:

حالا که این تکنیک پایهی تزریق پردازش را درک کردیم، بیایید به سراغ روشهای بعدی برویم.
دومین تکنیکی که در اینجا بررسی میکنیم، Process Hollowing نام دارد. این روش یکی دیگر از شیوههای رایج برای اجرای کد مخرب در فضای آدرس حافظهی یک پردازش دیگر است، اما به شکلی کمی متفاوت از تزریق DLL کلاسیک عمل میکند.
در این تکنیک، ابتدا یک پردازش قانونی در سیستمعامل (برای مثال یک برنامهی عادی) در حالت SUSPENDED ایجاد میشود. سپس محتوای حافظهی این پردازش قانونی تخلیه (توخالی) شده و با محتوای مخرب جایگزین میگردد. این کار همراه با تنظیم آدرس پایهی مناسب برای بخش توخالیشده انجام میشود.
به این ترتیب، حتی کاربران حرفهای ویندوز نیز متوجه اجرای یک پردازش مخرب در سیستمعامل نخواهند شد.
در ادامه، توابع API مورد استفاده برای اجرای تکنیک تزریق Process Hollowing آورده شدهاند:
CreateProcess
این تابع یک پردازش قانونی سیستمعامل (مانند notepad.exe) را با استفاده از پارامتر dwCreationFlags در حالت تعلیق (Suspended) ایجاد میکند.
ZwUnmapViewOfSection / NtUnmapViewOfSection
این توابع از Native API برای Unmap کردن کل فضای حافظهی یک بخش مشخص از پردازش استفاده میشوند. در این مرحله، پردازش قانونی دارای یک بخش توخالیشده میشود که امکان نوشتن محتوای مخرب در آن فراهم میگردد.
VirtualAllocEx
پیش از نوشتن محتوای مخرب، این تابع برای اختصاص فضای حافظهی جدید در پردازش هدف مورد استفاده قرار میگیرد.
WriteProcessMemory
همانطور که در تزریق DLL کلاسیک مشاهده شد، این تابع محتوای مخرب را مستقیماً در حافظهی پردازش مینویسد.
SetThreadContext / ResumeThread
این توابع کانتکست ترد را تنظیم کرده و پردازش را از حالت تعلیق خارج میکنند؛ در نتیجه، پردازش شروع به اجرا میکند.
در اسکرینشات زیر، میتوان نمونهای از یک بدافزار را مشاهده کرد که از تکنیک Process Hollowing استفاده میکند. این تصویر در نمای IDA Pro نمایش داده شده است.

اسکرینشات قبلی سه فراخوانی اول از توابع API ویندوز را نشان میدهد. اسکرینشات بعدی چهار مورد آخر از این توابع را نمایش میدهد:

خالی کردن فرآیندها روشی موثر برای دور زدن نرمافزار آنتیویروس بود، اما موتورهای آنتیویروس امروزی آن را نسبتاً آسان تشخیص میدهند. بیایید به آخرین روند ادامه دهیم - نمونه تزریق.
سومین و آخرین تکنیکی که در این کتاب توضیح میدهیم Process Doppelgänging نام دارد. این تکنیک جذابِ تزریق فرایند (Process Injection) عمدتاً برای دور زدن موتورهای آنتیویروس استفاده میشود و میتواند از برخی ابزارها و تکنیکهای جرمیابی حافظه (Memory Forensics) نیز فرار کند.
Process Doppelgänging از مجموعهای از توابع Windows API و Native API زیر استفاده میکند:
CreateFileTransacted:
این تابع بر اساس قابلیت NTFS-TXF مایکروسافت، یک فایل، استریم فایل یا دایرکتوری را ایجاد یا باز میکند. از این تابع برای باز کردن یک فرایند قانونی مانند notepad.exe استفاده میشود.
WriteFile:
این تابع دادهها را در فایلی که قرار است کد در آن تزریق شود، مینویسد.
NtCreateSection:
این تابع یک Section جدید ایجاد کرده و فایل مخرب را در فرایند هدفی که تازه ایجاد شده بارگذاری میکند.
RollbackTransaction:
این تابع در نهایت مانع از ذخیره شدن فایل اجرایی تغییریافته (مثلاً notepad.exe) روی دیسک میشود.
، NtCreateProcessEx، RtlCreateProcessParametersEx،
VirtualAllocEx، WriteProcessMemory،
NtCreateThreadEx، NtResumeThread:
تمام این توابع برای ایجاد، راهاندازی و اجرای فرایند تغییریافته استفاده میشوند تا فرایند بتواند فعالیت مخرب مورد نظر خود را انجام دهد.
در اسکرینشات زیر میتوانید یک فایل PE را مشاهده کنید که از تکنیک Process Doppelgänging استفاده میکند و در نمای IDA Pro نمایش داده شده است.

اسکرینشات قبلی اولین دو فراخوانی Windows API را نشان میدهد. اسکرینشات بعدی دو فراخوانی آخر از این توابع را نمایش میدهد.

یک DLL فایل کتابخانهای است که شامل تعداد زیادی تابع (گاهی صدها تابع یا بیشتر) میباشد که همانطور که از نامش پیداست، بهصورت پویا (Dynamic) بارگذاری شده و توسط فایلهای Windows PE مورد استفاده قرار میگیرد.
فایلهای DLL یا شامل توابع Windows API و Native API هستند، یا این توابع را اِکسپورت (Export) میکنند؛ توابعی که توسط فایلهای اجرایی PE استفاده یا ایمپورت میشوند. این DLLها توسط برنامههای مختلفی مانند نرمافزارهای آنتیویروس مورد استفاده قرار میگیرند و با فراهم کردن امکان فراخوانی طیف گستردهای از توابع از پیش نوشتهشده، فرایند توسعه نرمافزار را برای برنامهنویسان سادهتر میکنند.
برای درک بهتر اینکه یک فایل DLL چیست ــ و بهطور کلی سایر انواع فایلهای مبتنی بر PE ــ لازم است با فرمت فایل PE آشنایی داشته باشیم.
فایلهای PE
فایلهای PE نقش بسیار مهمی در سیستمعامل ویندوز ایفا میکنند. این فرمت فایل توسط فایلهای اجرایی باینری با پسوند .exe و همچنین کتابخانههای DLL با پسوند .dll استفاده میشود؛ اما اینها تنها انواع فایلهایی نیستند که از این فرمت انعطافپذیر بهره میبرند. در ادامه به چند مورد دیگر اشاره شده است:
CPL:
فایل پایه برای پیکربندیهای Control Panel که نقش اساسی و مهمی در سیستمعامل دارد. نمونهای از آن ncpa.cpl است که فایل پیکربندی رابطهای شبکه موجود در ویندوز میباشد.
SYS:
فایلهای سیستمی مربوط به درایورهای دستگاه یا پیکربندی سختافزار در سیستمعامل ویندوز که به ویندوز اجازه میدهند با سختافزار و دستگاهها ارتباط برقرار کند.
DRV:
فایلهایی که برای امکان تعامل رایانه با دستگاههای خاص استفاده میشوند.
SCR:
فایلهایی که بهعنوان اسکرینسیور (Screen Saver) در سیستمعامل ویندوز استفاده میشوند.
OCX:
فایلهایی که توسط ویندوز برای کنترلهای ActiveX و برای اهدافی مانند ایجاد فرمها و ویجتهای صفحات وب استفاده میشوند.
DLL:
برخلاف فایلهای EXE، فایلهای DLL را نمیتوان با دوبار کلیک از روی هارددیسک اجرا کرد. اجرای یک فایل DLL نیازمند یک فرایند میزبان (Host Process) است که توابع آن را ایمپورت کرده و اجرا کند. برای انجام این کار روشهای مختلفی وجود دارد.
مانند بسیاری از فرمتهای فایل دیگر (از جمله Executable Linkable Format – ELF و Mach Object – Mach-O)، ساختار فرمت فایل PE از دو بخش اصلی تشکیل شده است:
هدرهای PE (PE Headers):
که شامل اطلاعات فنی مهم و مرتبط درباره فایلهای مبتنی بر PE هستند.
بخشهای PE (PE Sections):
که محتوای اصلی فایل PE را در خود جای میدهند.
هر یک از این بخشها در فایلهای PE وظیفه و هدف مشخص و متفاوتی را دنبال میکنند.
نمودار زیر ساختار یک فایل mmmArsen.exe را نمایش میدهد:

بیایید هدرهای PE را بررسی کنیم.
در ادامه، توضیح هر یک از هدرهای فایل PE آورده شده است:
Disk Operating System (DOS) Header:
یک شناسه یا مقدار جادویی (Magic Value) برای شناسایی فایلهای PE است.
DOS Stub:
یک پیام قدیمی که هنوز در بیشتر فایلهای PE باقی مانده است. این پیام معمولاً عبارت
This program cannot be run in DOS mode:
را نمایش میدهد و گاهی اوقات برای دور زدن نرمافزارهای آنتیویروس دستکاری میشود.
PE Header:
این هدر اساساً اعلام میکند که فایل موردنظر از فرمت PE پیروی میکند.
Optional Header:
شامل اطلاعات متغیری مانند اندازه کد (Code Size)، نقطه ورود (Entry Point) فایل اجرایی یا کتابخانه، Image Base، Section Alignment و موارد دیگر است.
Sections Table:
یک جدول مرجع برای هر یک از بخشهای (Sections) فایل PE محسوب میشود.
در ادامه، توضیح هر یک از بخشهای فایل PE آورده شده است:
این بخش شامل کد ماشین (Machine Code) برنامه است که در نهایت توسط واحد پردازش مرکزی (CPU) اجرا میشود.
این بخش شامل توابع مورد نیاز است که از DLLها مانند Kernel32.dll و Ntdll.dll ایمپورت میشوند.
این بخش شامل متغیرها و پارامترهای توابع است که توسط برنامه استفاده میشوند.
اولین گزینه، استفاده از rundll32.exe است، که امکان اجرای تابعی موجود در یک فایل DLL را از طریق خط فرمان فراهم میکند.
برای مثال، برای اجرای نقطه ورود (Entry Point) با یک آرگومان، میتوان از دستور زیر استفاده کرد:
RUNDLL32.EXE <dllname>, <entrypoint> <argument>
به عنوان مثال، اسکرینشات زیر نشان میدهد یک فایل DLL تحت rundll32.exe اجرا شده است، حتی با نام تابعی که وجود ندارد.

روش دوم برای اجرای فایلهای DLL، بارگذاری آنها در یک فایل EXE با استفاده از توابع LoadLibrary() یا LoadLibraryEx() است.
وقتی یک فایل EXE از تابع LoadLibrary() استفاده میکند، نام ماژول را بهعنوان پارامتر به این صورت ارسال میکند:

فایل DLL تنها پس از انجام این مرحله میتواند درون فایل EXE که آن را فراخوانی کرده است، اجرا شود.
بسیاری از هکرها از این مکانیزم به دلایل زیر استفاده میکنند:
. فایلهای DLL معمولاً از دید کاربر عادی مخفی هستند.
. هنگامی که یک DLL درون یک فرایند دیگر بارگذاری میشود، به فضای حافظه آن فرایند دسترسی پیدا میکند.
. انجام تحلیل داینامیک خودکار (Automatic Dynamic Analysis) روی یک DLL بسیار دشوارتر از یک فایل EXE است.
. وقتی یک DLL در یک فرایند بارگذاری میشود، پیدا کردن آن در میان فرایندهای سیستم دشوارتر میشود؛ این موضوع کار تشخیص توسط آنتیویروس و پاسخ به رخدادها (Incident Response) را سختتر میکند.
حال که با روش دور زدن آنتیویروس با استفاده از DLL آشنا شدیم، به سراغ یادگیری سومین تکنیک دور زدن آنتیویروس خواهیم رفت: دور زدن آنتیویروس با استفاده از تکنیکهای مبتنی بر زمان (Timing-Based Techniques).
برای فروش محصولات امنیتی، شرکتهای تولیدکننده آنتیویروس باید روی دو ویژگی اصلی تأکید کنند:
. سطح بالای شناسایی (High Level of Detection): محافظت از کاربر در برابر تهدیدها
. کاربرپسند بودن (User‑Friendly): رابط کاربری راحت (UI)، تصاویر واضح، اسکن سریع و موارد دیگر
برای مثال، میتوان یک سیستم نهایی (Endpoint) را در نظر گرفت که حدود ۱۰۰٬۰۰۰ فایل دارد. اگر از یک آنتیویروس انتظار حداکثر شناسایی ممکن را داشته باشیم، اسکن تمام این ۱۰۰٬۰۰۰ فایل ممکن است چند روز طول بکشد — و در برخی موارد حتی بیشتر. این یک انتظار افراطی است که شرکتهای آنتیویروس عملاً نه میتوانند و نه قرار است آن را برآورده کنند.
برای جلوگیری از چنین وضعیتی، تولیدکنندگان آنتیویروس تمام تلاش خود را میکنند تا زمان انتظار در طول اسکن را بهینه کنند؛ حتی اگر این موضوع به این معنا باشد که در بهترین حالت، دقت شناسایی کمتر شود، یا در بدترین حالت، بدافزار اصلاً شناسایی نشود.
شرکتهای آنتیویروس ترجیح میدهند ۱۰۰٬۰۰۰ فایل را در ۲۴ دقیقه با نرخ شناسایی حدود ۷۰٪ اسکن کنند، تا اینکه همان تعداد فایل را در ۲۴ ساعت با نرخ شناسایی حدود ۹۵٪ بررسی نمایند. دقیقاً همین ترجیح است که مهاجمان و پژوهشگران میتوانند از آن برای فرار از شناسایی و در واقع دور زدن آنتیویروس استفاده کنند.
چندین تکنیک وجود دارد که میتوان آنها را در قالب دور زدن مبتنی بر زمان به کار برد. در این کتاب، دو تکنیک اصلی توضیح داده میشود:
تکنیک اول از فراخوانیهای Windows API استفاده میکند که باعث میشود بدافزار در مدتزمان کوتاه به عملکرد مخرب خود نرسد.
تکنیک دوم باعث میشود بدافزار زمان زیادی برای بارگذاری صرف کند؛ در نتیجه نرمافزار آنتیویروس از ادامه اسکن صرفنظر کرده و نتیجه بگیرد که فایل بیخطر است.
فراخوانیهای Windows API برای دور زدن آنتیویروس
دو فراخوانی Windows API که در این فصل به آنها میپردازیم عبارتاند از:
Sleep()
این تابع اجرای برنامه را برای مدتزمان مشخصی متوقف میکند.
GetTickCount()
این تابع تعداد میلیثانیههایی را که از زمان راهاندازی سیستم گذشته است، برمیگرداند.
این دو تابع معمولاً در تکنیکهای دور زدن آنتیویروس مبتنی بر زمان استفاده میشوند تا رفتار برنامه بهگونهای تنظیم شود که از شناسایی توسط آنتیویروس جلوگیری کند.
در گذشته، نویسندگان بدافزار از تابع Sleep() استفاده میکردند تا اجرای عملکرد مخرب بدافزار را برای چند ثانیه، چند دقیقه، چند ساعت یا حتی چند روز به تأخیر بیندازند. به این ترتیب، بدافزار میتوانست با انجام ضدتحلیل (Anti‑Analysis) از شناسایی فرار کند و کار را برای نرمافزارهای آنتیویروس و تحلیلگران بدافزار دشوارتر سازد.
اما امروزه، برای مثال زمانی که موتور استاتیک یک آنتیویروس وجود تابع Sleep() را در یک فایل تشخیص میدهد، موتور آنتیویروس باعث میشود شبیهساز (Emulator) آن وارد این تابع شده و فایل را به مدت زمانی که تابع مشخص کرده است اجرا کند.
به عنوان نمونه، اگر موتور استاتیک تابع Sleep() را با تأخیر ۴۸ ساعته شناسایی کند، شبیهساز آنتیویروس عملیات شبیهسازی را بهگونهای انجام میدهد که گویی ۴۸ ساعت گذشته است؛ در نتیجه، مکانیزم «دفاعی» بدافزار عملاً بیاثر میشود.
این موضوع دلیل اصلی آن است که تابع Sleep() امروزه دیگر گزینه مناسبی برای دور زدن آنتیویروس محسوب نمیشود. بنابراین، برای استفاده از تکنیکهای دور زدن مبتنی بر زمان، باید از توابع دیگری استفاده کنیم؛ توابعی مانند GetTickCount().
تابع GetTickCount() هیچ پارامتری دریافت نمیکند، اما مدت زمانی را که سیستمعامل در حال اجرا بوده است، بر حسب میلیثانیه (ms) برمیگرداند. حداکثر مقداری که این تابع میتواند بازگرداند ۴۹٫۷ روز است.
با استفاده از این تابع، بدافزار تشخیص میدهد که سیستمعامل چه مدت در حال اجرا بوده و بر اساس آن تصمیم میگیرد بهترین زمان برای اجرای عملکردهای مخرب چه زمانی است و — البته — آیا اصلاً اجرای آنها توصیه میشود یا خیر.
اسکرینشات زیر، تابع Sleep() را درون یک فایل PE نشان میدهد.

یکی دیگر از راهها برای سوءاستفاده از زمان محدودی که نرمافزار آنتیویروس برای بررسی هر فایل در اختیار دارد، انجام تخصیص حافظه بسیار بزرگ در داخل کد بدافزار است.
این کار باعث میشود آنتیویروس برای بررسی اینکه فایل مخرب است یا سالم، منابع بسیار زیادی مصرف کند. زمانی که آنتیویروس برای انجام یک اسکن ساده روی حجم نسبتاً بزرگی از حافظه، منابع بیش از حد مصرف میکند، ناچار میشود از ادامه تشخیص فایل مخرب صرفنظر کند. به این تکنیک Memory Bombing گفته میشود.
قبل از آنکه به یک مثال عملی از نحوه دور زدن آنتیویروس با استفاده از این تکنیک بپردازیم، ابتدا باید مکانیزم تخصیص حافظه را درک کنیم؛ از جمله اینکه هنگام استفاده از تابع malloc() دقیقاً چه اتفاقی در حافظه رخ میدهد و تفاوت بین malloc() و calloc() چیست. همچنین یک Proof‑of‑Concept عملی را بررسی خواهیم کرد که اثربخشی این تکنیک را نشان میدهد.
malloc() چیست؟
دستور malloc() تابعی از زبان C است که تا حدی در اکثر سیستمعاملهای رایج مانند Linux، macOS و البته Windows مورد استفاده قرار میگیرد.
هنگام نوشتن یک برنامه مبتنی بر C/C++، میتوان تابع malloc() را بهصورت یک اشارهگر (Pointer) تعریف کرد؛ به این شکل:
void *malloc(size);
پس از اجرای این تابع، مقدار بازگشتی یک اشارهگر به حافظه تخصیصیافته در Heap فرایند است (و در صورت شکست اجرای تابع، مقدار NULL بازگردانده میشود).
نکته مهم این است که آزادسازی حافظه تخصیصیافته بر عهده برنامهنویس است و این کار با استفاده از تابع free() انجام میشود؛ به این صورت:
free(*ptr);
پارامتر *ptr در تابع free() همان اشارهگر به حافظهای است که قبلاً با استفاده از malloc() تخصیص داده شده بود.
اهمیت آزادسازی حافظه از دید مهاجم
از دید یک مهاجم، آزادسازی فضای حافظه تخصیصیافته بسیار حیاتی است؛ زیرا این کار بهویژه برای پاک کردن دادههایی اهمیت دارد که ممکن است بهعنوان مدرک توسط تیمهای آبی (Blue Teams)، کارشناسان جرمیابی دیجیتال و تحلیلگران بدافزار مورد استفاده قرار گیرند.
نمودار زیر نشان میدهد که تابع malloc() چگونه یک بلوک از حافظه را درون حافظه Heap یک فرایند تخصیص میدهد:

مقایسه calloc() با malloc()
تابع calloc() یکی دیگر از توابعی است که میتوان از آن برای تخصیص حافظه در Heap یک فرایند استفاده کرد. برخلاف malloc() که فقط درخواست تخصیص حافظه میدهد اما آن حافظه را با هیچ دادهای پر نمیکند و بهصورت مقداردهینشده (Uninitialized) باقی میگذارد، تابع calloc() تمام حافظه تخصیصیافته را مقداردهی اولیه کرده و با بیتهای صفر (Zero Bits) پر میکند.
با این درک پایهای از نحوه تخصیص حافظه، اکنون به سراغ مثال عملی زیر میرویم.
در ادامه، یک نمونه Proof‑of‑Concept از تکنیک Memory Bombing آورده شده است که به زبان C نوشته شده است:
int main() { char *memory_bombing NULL; memory_bombing = (char *) calloc(200000000, sizeof(char)); if (memory_bombing != NULL) { free(memory_bombing); payload(); } return 0; }
این مثال نشان میدهد که چگونه با استفاده از calloc() میتوان مقدار بسیار بزرگی از حافظه را تخصیص داد تا از این طریق، آنتیویروس را وادار به مصرف منابع زیاد کرده و فرایند تشخیص را مختل کرد.
این کد یک تابع main() تعریف میکند که در نهایت تابع calloc() را با دو پارامتر اجرا میکند (تعداد عناصر و اندازه کلی هر عنصر). سپس دستور if بررسی میکند که مقدار بازگشتی یک اشارهگر معتبر باشد. در این نقطه، پس از اجرای تابع calloc()، آنتیویروس از ادامه اسکن صرفنظر میکند و بدین ترتیب کد ما آنتیویروس را دور میزند.
در ادامه، حافظه تخصیصیافته با فراخوانی تابع free() و دادن اشارهگر به حافظه تخصیصیافته آزاد میشود و در نهایت Shellcode مخرب ما اجرا میشود.
خلاصه جریان عملیاتی که در این کد رخ میدهد به شرح زیر است:
تعریف تابع main().
اعلام یک متغیر اشارهگر به نام memory_bombing از نوع char با مقدار NULL.
مقداردهی متغیر memory_bombing با اشارهگر حاصل از حافظه تخصیصیافته توسط calloc(). در این نقطه، آنتیویروس برای اسکن فایل دچار مشکل شده و از ادامه اسکن صرفنظر میکند.
برای حفظ کدنویسی تمیز و درست، بررسی میکنیم که مقدار بازگشتی memory_bombing یک اشارهگر معتبر به حافظه تخصیصیافته باشد.
در نهایت، حافظه تخصیصیافته با استفاده از تابع free() آزاد شده و Shellcode مخرب مورد نظر با فراخوانی تابع payload() اجرا میشود.
اکنون بیایید منطق پشت این تکنیک دور زدن آنتیویروس را درک کنیم.
منطق پشت این تکنیک
منطق این نوع از تکنیکهای دور زدن آنتیویروس بر این اساس است که موتور داینامیک آنتیویروس برای شناسایی کدهای مخرب در فرایندهای تازه ایجادشده، اقدام به تخصیص حافظه مجازی میکند تا فرایند اجراشده را در یک محیط ایزوله (Sandbox) از نظر وجود کد مخرب اسکن کند.
مقدار حافظهای که به این منظور تخصیص داده میشود محدود است؛ زیرا موتورهای آنتیویروس نمیخواهند تجربه کاربری (UX) را تحت تأثیر منفی قرار دهند. به همین دلیل، اگر ما مقدار بسیار بزرگی از حافظه را تخصیص دهیم، موتورهای آنتیویروس ترجیح میدهند از ادامه اسکن عقبنشینی کنند و همین موضوع راه را برای اجرای Payload مخرب ما هموار میسازد.
در این فصل از کتاب، ابتدا به آمادهسازی برای پژوهش در زمینه دور زدن آنتیویروس پرداختیم و دیدگاه اصلی خود را درباره این موضوع بیان کردیم؛ از جمله استفاده از پلتفرمهایی مانند VirusTotal و جایگزینهای دیگر آن. علاوه بر این، با توابع Windows API و نحوه استفاده آنها در سیستمعامل ویندوز آشنا شدید، همچنین مفاهیمی مانند فضای آدرس فرایندها و سه تکنیک مختلف تزریق فرایند (Process Injection) را بررسی کردیم.
سپس شما را با دانشی تکمیلی آشنا کردیم؛ از جمله انواع رایج فایلهای PE، ساختار فایل PE، نحوه اجرای فایلهای DLL و اینکه چرا مهاجمان از فایلهای DLL بهعنوان بخش جداییناپذیر حملات خود استفاده میکنند.
در ادامه، به بررسی حملات مبتنی بر زمان (Timing‑Based Attacks) پرداختیم و دیدیم که چگونه از توابع Sleep() و GetTickCount() برای فرار از شناسایی آنتیویروس استفاده میشود. همچنین بررسی کردیم که چرا تابع Sleep() در تکنیکهای مدرن دور زدن آنتیویروس دیگر کاربرد چندانی ندارد.
Telegram: @CaKeegan
Gmail : amidgm2020@gmail.com