Shellcode چیست و چگونه اجرا میشود ؟

Shellcode چیست ؟

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

Shellcode هارا معمولا به شکل نوشتار زبان C نمایش میدهند تا راحت بتوان آن ها را در برنامه های زبان C استفاده کرد . از آنجایی که Shellcode کد ماشین قابل اجرا توسط CPU است ، پس میتوان آن را به صورت آرایه ای از بایت (char) فرض کرد . با توجه به این موضوع میتوان Shellcode را به شکل زیر در زبان C تعریف کرد :

unsigned char shellcode_bytes[] = "\xFC\x33\xD2\xB2\x30\x64\xFF\x32\x5A\x8B\x52\x0C\x8B\x52\x14\x8B\x72\x28\x33\xC9\xB1\x18\x33\xFF\x33\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0\x81\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B\x12\x75\xDA\x8B\x53\x3C\x03\xD3\xFF\x72\x34\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\x65\x75\xE2\x49\x8B\x72\x24\x03\xF3\x66\x8B\x0C\x4E\x8B\x72\x1C\x03\xF3\x8B\x14\x8E\x03\xD3\x52\x33\xFF\x57\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x68\x33\x32\x01\x01\x66\x89\x7C\x24\x02\x68\x75\x73\x65\x72\x54\xFF\xD0\x68\x6F\x78\x41\x01\x8B\xDF\x88\x5C\x24\x03\x68\x61\x67\x65\x42\x68\x4D\x65\x73\x73\x54\x50\xFF\x54\x24\x2C\x57\x68\x4F\x5F\x6F\x21\x8B\xDC\x57\x53\x53\x57\xFF\xD0\x68\x65\x73\x73\x01\x8B\xDF\x88\x5C\x24\x03\x68\x50\x72\x6F\x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24\x40\xFF\x54\x24\x40\x57\xFF\xD0";

در کد بالا ، یک رشته به نام shellcode_bytes که خود ، آرایه ای از char است تعریف کرده ایم و یک Shellcode که کار آن فقط نمایش یک پیغام ساده گرافیکی توسط تابع MessageBoxA است را در قالب یک رشته در آن ذخیره کرده ایم . اگر دقت کنید این Shellcode دنباله ای از بایت ها است که به صورت کد hex (مبنای ۱۶) نمایش داده شده اند .

Shellcode بالا را میتوان به صورت زیر نیز تعریف کرد :

unsigned char shellcode_bytes[] = {0xFC,0x33,0xD2,0xB2,0x30,0x64,0xFF,0x32,0x5A,0x8B,0x52,0x0C,0x8B,0x52,0x14,0x8B,0x72,0x28,0x33,0xC9,0xB1,0x18,0x33,0xFF,0x33,0xC0,0xAC,0x3C,0x61,0x7C,0x02,0x2C,0x20,0xC1,0xCF,0x0D,0x03,0xF8,0xE2,0xF0,0x81,0xFF,0x5B,0xBC,0x4A,0x6A,0x8B,0x5A,0x10,0x8B,0x12,0x75,0xDA,0x8B,0x53,0x3C,0x03,0xD3,0xFF,0x72,0x34,0x8B,0x52,0x78,0x03,0xD3,0x8B,0x72,0x20,0x03,0xF3,0x33,0xC9,0x41,0xAD,0x03,0xC3,0x81,0x38,0x47,0x65,0x74,0x50,0x75,0xF4,0x81,0x78,0x04,0x72,0x6F,0x63,0x41,0x75,0xEB,0x81,0x78,0x08,0x64,0x64,0x72,0x65,0x75,0xE2,0x49,0x8B,0x72,0x24,0x03,0xF3,0x66,0x8B,0x0C,0x4E,0x8B,0x72,0x1C,0x03,0xF3,0x8B,0x14,0x8E,0x03,0xD3,0x52,0x33,0xFF,0x57,0x68,0x61,0x72,0x79,0x41,0x68,0x4C,0x69,0x62,0x72,0x68,0x4C,0x6F,0x61,0x64,0x54,0x53,0xFF,0xD2,0x68,0x33,0x32,0x01,0x01,0x66,0x89,0x7C,0x24,0x02,0x68,0x75,0x73,0x65,0x72,0x54,0xFF,0xD0,0x68,0x6F,0x78,0x41,0x01,0x8B,0xDF,0x88,0x5C,0x24,0x03,0x68,0x61,0x67,0x65,0x42,0x68,0x4D,0x65,0x73,0x73,0x54,0x50,0xFF,0x54,0x24,0x2C,0x57,0x68,0x4F,0x5F,0x6F,0x21,0x8B,0xDC,0x57,0x53,0x53,0x57,0xFF,0xD0,0x68,0x65,0x73,0x73,0x01,0x8B,0xDF,0x88,0x5C,0x24,0x03,0x68,0x50,0x72,0x6F,0x63,0x68,0x45,0x78,0x69,0x74,0x54,0xFF,0x74,0x24,0x40,0xFF,0x54,0x24,0x40,0x57,0xFF,0xD0};

به هر حال تفاوتی نمیکند . نهایتا ما آرایه ای از بایت های Shellcode مورد نظرمان را خواهیم داشت . معمولا Shellcode هارا به صورت رشته ای که در مورد قبلی دیدیم نمایش میدهند .

روش سنتی اجرای Shellcode در زبان C

ما دیدیم چگونه Shellcode هارا میتوان تعریف کرد . حالا چگونه اجرا میشوند ؟‌ باید بگویم که Shellcode ها به خودی خود اجرا نمیشوند . درست است که قابل اجرا هستند ولی کسی یا چیزی باید آن ها را به اجرا در بیاورد . فرض کنید که ما همان Shellcode مربوط به MessageBoxA را که در بالا دیدیم را تعریف کرده ایم . با همان نام shellcode_bytes . ما میدانیم که آرایه shellcode_bytes که در بخش قبلی تعریف کردیم ، در واقع از نظر زبان C یک اشاره گر به نوع char است . روش سنتی اجرای Shellcode به این صورت است که آرایه ی shellcode_bytes ، که یک اشاره گر به char است را به اشاره گر تابع (Function Pointer) تبدیل کنیم و سپس آن را فراخوانی کنیم . این باعث میشود تا آن بایت هایی که در shellcode_bytes تعریف شده اند به عنوان کد های یک تابع فرض شوند و حین فراخوانی اجرا شوند و این یعنی اجرای Shellcode . طبق چیزی که گفته شد به این صورت عمل میکنیم :

unsigned char shellcode_bytes[]=
    "\xFC\x33\xD2\xB2\x30\x64\xFF\x32\x5A\x8B"
    "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x33\xC9"
    "\xB1\x18\x33\xFF\x33\xC0\xAC\x3C\x61\x7C"
    "\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0"
    "\x81\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B"
    "\x12\x75\xDA\x8B\x53\x3C\x03\xD3\xFF\x72"
    "\x34\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03"
    "\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47"
    "\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F"
    "\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72"
    "\x65\x75\xE2\x49\x8B\x72\x24\x03\xF3\x66"
    "\x8B\x0C\x4E\x8B\x72\x1C\x03\xF3\x8B\x14"
    "\x8E\x03\xD3\x52\x33\xFF\x57\x68\x61\x72"
    "\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F"
    "\x61\x64\x54\x53\xFF\xD2\x68\x33\x32\x01"
    "\x01\x66\x89\x7C\x24\x02\x68\x75\x73\x65"
    "\x72\x54\xFF\xD0\x68\x6F\x78\x41\x01\x8B"
    "\xDF\x88\x5C\x24\x03\x68\x61\x67\x65\x42"
    "\x68\x4D\x65\x73\x73\x54\x50\xFF\x54\x24"
    "\x2C\x57\x68\x4F\x5F\x6F\x21\x8B\xDC\x57"
    "\x53\x53\x57\xFF\xD0\x68\x65\x73\x73\x01"
    "\x8B\xDF\x88\x5C\x24\x03\x68\x50\x72\x6F"
    "\x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24"
    "\x40\xFF\x54\x24\x40\x57\xFF\xD0";


int main () {
	/* casting shellcode_bytes to a function pointer */
	void (*fp)() = (void (*)()) shellcode_bytes;

	/* calling the function pointer */
	(*fp)();
	return 0 ;
}

اگر کد بالا را Compile کنیم و در ویندوزی قدیمی مثل XP اجرا کنیم بدونه هیچ مشکلی جادو میشود :

نتیجه جادو که نمایش یک پیغام گرافیکی توسط MessageBoxA هست را میبینیم ولی آیا این روش در ویندوز های جدیدتر نیز جواب میدهد . امتحان میکنیم :

خیر ! احتمالا جواب نمیدهد . دلیل این موضوع بوجود آمدن مکانیزم دفاعی DEP در ویندوز های جدید است که در ادامه بررسی میکنیم .

مکانیزم دفاعی DEP در ویندوز

Data Execution Prevention (DEP) یک مکانیزم امنیتی در سیستم عامل ویندوز است که با فعال بودن آن ، ویندوز بین بخش هایی از حافظه که داده در آن ها ذخیره میشود و بخش هایی که کد اجرایی در آن ها ذخیره میشود فرق میگذارد . در این مکانیزم امنیتی بخش های حافظه هر برنامه در حال اجرا به دو نوع تقسیم میشوند :

۱ – بخش هایی که فقط داده (Data) داخل آن ها است

۲ – بخش هایی که کد اجرایی داخل آن ها است

DEP اطمینان حاصل میکند که اجرای کد فقط از حافظه های نوع ۲ (کد های اجرایی) باشد و در غیر این صورت اگر برنامه ای تلاش کرد تا یک کد اجرایی از نوع اول حافظه یعنی جاهایی که به عنوان ذخیره داده در نظر گرفته شده اند اجرا کند ، خطای دسترسی حافظه رخ میدهد و برنامه متوقف میشود .

این کار نکته امنیتی دارد و باعث میشود تا اجرای حملاتی مثل سرریز بافر (Buffer Overflow) سخت تر شود .

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

اجرای Shellcode در حضور DEP

با وجود مکانیزم های امنیتی مثل DEP دیگر اجرای Shellcode توسط روش سنتی امکان پذیر نخواهد بود و روش های جدیدی برای اینکار نیاز است . روش های مختلفی وجود دارد تا ما Shellcode خود را در حضور DEP اجرا کنیم . تعدادی از این روش ها را در بخش های آینده بررسی خواهیم کرد اما در اینجا به معرفی یک روش کلی برای اجرای Shellcode در حضور DEP میپردازیم که توسط توابع api ویندوز انجام میشود . برای حل مشکل DEP باید shellcode خود را در قسمتی از حافظه ذخیره کنیم که قابل اجرا (Executable) باشد و سپس آن را توسط تبدیل به اشاره گر تابع اجرا کنیم . برای اینکار میتوانیم ابتدا یک بخش از حافظه با دسترسی های خواندن (R) , نوشتن (W) و اجرایی (X) رزرو کنیم (توسط تابع VirtualAlloc) . سپس Shellcode خود را در حافظه رزرو شده بنویسیم (توسط تابع WriteProcessMemory) و نهایتا با تبدیل اشاره گر حافظه به اشاره گر تابع ، آن را اجرا کنیم . به کد زیر دقت کنید :

#include <windows.h>

unsigned char shellcode_bytes[]=
    "\xFC\x33\xD2\xB2\x30\x64\xFF\x32\x5A\x8B"
    "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x33\xC9"
    "\xB1\x18\x33\xFF\x33\xC0\xAC\x3C\x61\x7C"
    "\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0"
    "\x81\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B"
    "\x12\x75\xDA\x8B\x53\x3C\x03\xD3\xFF\x72"
    "\x34\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03"
    "\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47"
    "\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F"
    "\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72"
    "\x65\x75\xE2\x49\x8B\x72\x24\x03\xF3\x66"
    "\x8B\x0C\x4E\x8B\x72\x1C\x03\xF3\x8B\x14"
    "\x8E\x03\xD3\x52\x33\xFF\x57\x68\x61\x72"
    "\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F"
    "\x61\x64\x54\x53\xFF\xD2\x68\x33\x32\x01"
    "\x01\x66\x89\x7C\x24\x02\x68\x75\x73\x65"
    "\x72\x54\xFF\xD0\x68\x6F\x78\x41\x01\x8B"
    "\xDF\x88\x5C\x24\x03\x68\x61\x67\x65\x42"
    "\x68\x4D\x65\x73\x73\x54\x50\xFF\x54\x24"
    "\x2C\x57\x68\x4F\x5F\x6F\x21\x8B\xDC\x57"
    "\x53\x53\x57\xFF\xD0\x68\x65\x73\x73\x01"
    "\x8B\xDF\x88\x5C\x24\x03\x68\x50\x72\x6F"
    "\x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24"
    "\x40\xFF\x54\x24\x40\x57\xFF\xD0";


int main () {
	/* allocating memory for shellcode bytes */
	void * memory = VirtualAlloc(NULL , sizeof(shellcode_bytes) , MEM_COMMIT , PAGE_EXECUTE_READWRITE);
	
	/* writing shellcode bytes to allocated memory */
	WriteProcessMemory(GetCurrentProcess() , memory , shellcode_bytes , sizeof(shellcode_bytes) ,NULL);
	
	/* cast allocated memory to function pointer and call it */
	((void (*)())memory)();
	
	return 0 ;
}

همانند قبل shellcode_bytes را تعریف کرده ایم اما روند اجرای آن در تابع main تغییر پیدا کرده است . در تابع main ابتدا یک حافظه به اندازه بایت های shellcode_bytes رزرو کرده ایم (خط ۳۲) . سپس آن بایت های shellcode_bytes را در آن حافظه نوشته ایم (خط ۳۵) . دقت کنید تابع GetCurrentProcess که در خط ۳۵ استفاده شده یک handle به پروسه فعلی برمیگرداند .

نهایتا در خط ۳۸ حافظه رزرو شده که حاوی بایت های Shellcode است را به اشاره گر تابع تبدیل کرده ایم و درجا فراخوانی کرده ایم .

حال اگر کد بالا را در سیستمی که DEP فعال دارد اجرا کنیم :‌

 

میبینیم که بدونه هیچ مشکلی Shellcode ما اجرا خواهد شد .

منابع

سایت shell-storm.org یک پایگاه از مجموعه Shellcode های نوشته شده توسط افراد مختلف برای انجام کارهای مختلف و برای سیستم عامل ها و پردازنده های مختلف است . Shellcode مربوط به MessageBoxA که در این مطلب استفاده کردیم نیز از همین سایت بود .

VirtualAlloc

WriteProcessMemory

DEP

 

تصویر پست

مارا دنبال کنید

پست های مرتبط

مطالعه این پست ها رو از دست ندین!
KeyLogger کیلاگر

ساخت KeyLogger با استفاده از GetAsyncKeyState

آنچه در این پست میخوانید مارا دنبال کنید یکی از روش های مرسوم و اولیه برای پیاده سازی KeyLogger ها…

بیشتر بخوانید

جاسازی Shellcode در فایل اجرایی exe

در اینجا میخواهیم ببینیم چطور میتوان یک Shellcode را در یک فایل اجرایی ویندوزی جاسازی کرد به طوری که با اجرای فایل اجرایی آلوده ، ابتدا Shellcode به صورت مخفیانه اجرا شده و سپس محتوای عادی فایل اجرایی شروع به اجرا شدن میکنند طوری که همه چیز عادی به نظر برسد .

بیشتر بخوانید

پیاده سازی APC Injection در C

APC Injection یکی دیگر از روش های تزریق و اجرای کد در پروسه های دیگر است . در این پست به بررسی اینکه APC در ویندوز چیست و چگونه بدافزار ها از آن برای تزریق کد استفاده میکنند میپردازیم . 

بیشتر بخوانید

نظرات

سوالات و نظراتتون رو با ما به اشتراک بذارید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *