آليّة حلّ أسماء DLL في Windows - ترتيب البحث وSxS
· آخر تحديث: · 小村 豪 · Windows, DLL, المحمّل, الأمن, تطوير Windows
سجل التعديلات (3 تحديثات، آخر تحديث 3 Sep، 2026)
سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.
- أُضيفت روابط الاستشارة الموجودة في الأصل الياباني (consultation_services). ولم يتغيّر نصّ المقالة نفسه. قراءة النسخة السابقة لهذا التحديث (DOI: 10.5281/zenodo.22240891)
- أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
- أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621454)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
小村 豪 (2026). آليّة حلّ أسماء DLL في Windows - ترتيب البحث وSxS. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621454 https://comcomponent.com/ar/blog/2026/03/24/002-windows-dll-name-resolution/
- DOI (أحدث نسخة)
- 10.5281/zenodo.21621454
- DOI (هذه النسخة)
- 10.5281/zenodo.22279890
حين يصير الحديث عن التعامل مع DLL أصلي في Windows، يتكرّر كثيراً لبس من هذا النوع.
- إذا كتبت
LoadLibrary("foo.dll")، أين ينظر فعلاً - وضعتها في المجلّد نفسه مع الملفّ التنفيذي، فلماذا تُحمَّل DLL أخرى
- أيّهما أولى،
System32أم مجلّد التطبيق - في أيّ مرحلة تفعل manifest وAPI set وKnown DLLs
- ماذا يتغيّر باستخدام
SetDllDirectoryأوAddDllDirectory - بأيّ فعل يسهل هجوم زرع DLL أو DLL hijacking
هذا الحديث لا ينفع في العمل بمجرّد «حفظ ترتيب البحث في سطر واحد». في الواقع، محمّل Windows يقيّم أوّلاً عدّة قواعد خاصّة قبل أن يمسح نظام الملفّات بالترتيب.
flowchart TB
accTitle: لماذا لا يكفي حفظ ترتيب البحث
accDescr: مخطّط يبيّن أنّ حلّ أسماء DLL لا ينفع في العمل بحفظ ترتيب البحث في سطر واحد، وأنّ محمّل Windows يقيّم أوّلاً عدّة قواعد خاصّة قبل البحث المتتابع في نظام الملفّات.
a0["حفظ ترتيب البحث في سطر واحد"] -.-> a1["لا ينفع في العمل"]
a2["واقع محمّل Windows"] --> a3["تقييم القواعد الخاصّة أوّلاً"]
a3 --> a4["ثم البحث المتتابع في نظام الملفّات"]
الشكل 1: حلّ الأسماء يبدأ بقواعد خاصّة سابقة، قبل «بحث المجلّدات».
ترتّب هذه المقالة حلّ أسماء DLL في Windows عمليّاً، بما يشمل الفرق بين unpackaged app وpackaged app، وKnown DLLs، وloaded-module list، وAPI set، ومانيفست side-by-side، وأثر واجهات LoadLibraryEx.
المحتوى يفترض معلومات Microsoft Learn العلنيّة في مارس 2026.123456789
مصطلحات هذه المقالة
نرتّب أوّلاً في سطر واحد كلمات ستظهر لاحقاً بلا شرح. التفصيل في فصول المتن.
| المصطلح | بجملة واحدة |
|---|---|
| packaged app / unpackaged app | تطبيق يُوزَّع ويُثبَّت حزمة مثل MSIX، أم تطبيق يضع الملفّ التنفيذي في مجلّد كالسابق. تعريف ترتيب البحث نفسه مختلف (الفصلان 3 و4) |
| safe DLL search mode | إعداد مفعّل افتراضيّاً ينقل current folder إلى خلف ترتيب البحث. يُعطَّل بجعل قيمة السجلّ SafeDllSearchMode صفراً1 |
| DLL redirection | آليّة تضع علامة اسم-التطبيق.exe.local بجانب الملفّ التنفيذي فيجعل المحمّل ينظر إلى مجلّد الملفّ التنفيذي أوّلاً. تُدعى أيضاً .local وDotLocal (الفصل 7)7 |
| SxS (side-by-side) | آليّة تُبقي إصدارات متعدّدة من DLL نفسها متعايشة، وتحدّد في manifest أيّ إصدار يُربَط. اختصار side-by-side، وقد يُشرح بالعربية بـ «التجاور» (الفصل 7)9 |
| loaded-module list | آليّة يتأكّد بها النظام مما إذا كانت DLL بالاسم نفسه محمّلة أصلاً في ذاكرة تلك العمليّة. بغضّ النظر عن المجلّد الذي قُرئت منه، إن كانت محمّلة استُخدمت (5.1)1 |
| Known DLLs | قائمة DLL يعدّها Windows معروفة في ذلك الإصدار. DLL المطابقة تُستخدم منها نسخة جهة النظام (5.2)1 |
| API set | اسم عقد مثل api-ms-win-.... اسم مستعار افتراضي يخفي DLL المادّيّة التي تنفّذ العقد (الفصل 6)3 |
| package dependency graph | مجموعة حزمة التطبيق نفسها وحزم التبعيّة المكتوبة PackageDependency في قسم Dependencies من مانيفست الحزمة. تُبحَث بترتيب كتابتها في المانيفست1 |
1. الخلاصة أوّلاً
نرتّب أوّلاً الخلاصة العمليّة وحدها.
- حلّ أسماء DLL في Windows ليس «بحث نظام الملفّات أوّلاً». عناصر مثل DLL redirection وAPI set ومانيفست SxS وloaded-module list وKnown DLLs تدخل سابقة لترتيب البحث.1
- في الشكل القياسي لـ unpackaged app مع تفعيل safe DLL search mode، مجلّد التطبيق في مرتبة عليا، لكن القواعد الخاصّة أعلاه تُقيَّم قبله.1
- حتى إن حمّلت DLL بمسار كامل، لا تُثبَّت DLLات التبعيّة تلقائيّاً بالمسار الكامل نفسه. تُبحَث DLLات التبعيّة باسم الوحدة فقط، فقد تُحَلّ من موضع آخر.1
Known DLLsآليّة تربط DLL معيّنة يعدّها نظام التشغيل معروفة بنسخة جهة النظام، وليست حديثاً عن الكتابة فوقها بتوزيع جهة التطبيق العادي.1- API set ليس «اسم DLL المادّي نفسه»، بل اسماً مستعاراً افتراضيّاً يخفي DLL التنفيذ. النظر إلى اسم مثل
api-ms-win-...بإحساس بحث DLL العادي يسهّل سوء الفهم.3 SetDllDirectoryلا يغيّر ترتيب البحث فحسب، بل يملك سلوكاً يعطّل safe DLL search mode فعليّاً، فاستخدامه بخفّة قد ينعكس أثره أمنيّاً.1- في العمل، الأأمن تضييق نطاق البحث صراحة بالجمع بين المسار الكامل، و
SetDefaultDllDirectories، وAddDllDirectory، وأعلامLOAD_LIBRARY_SEARCH_*فيLoadLibraryEx.4562
باختصار، التفكير العملي أنّ حلّ أسماء DLL في Windows لا يُحسَم بـ «أيّ مجلّد في المرتبة كم»، بل بـ «أيّ قواعد سابقة تحلّ الاسم إلى ماذا» و«كيف غيّر الـ API فضاء البحث».
flowchart TB
accTitle: ثلاثة عناصر تحسم حلّ أسماء DLL
accDescr: مخطّط يبيّن أنّ حلّ أسماء DLL في Windows لا يُحسَم بترتيب المجلّدات أيّها في المرتبة كم فحسب، بل بتداخل ثلاثة عناصر: القواعد السابقة التي تحلّ الاسم إلى ماذا، وترتيب بحث المجلّدات، وكيف غيّر الـ API فضاء البحث.
b1["القواعد السابقة للاسم"] --> b4["DLL التي تُحمَّل فعلاً"]
b2["ترتيب بحث المجلّدات"] --> b4
b3["فضاء البحث الذي غيّره الـ API"] --> b4
b1 -.-> b1d["redirection وKnown DLLs"]
b3 -.-> b3d["أعلام LoadLibraryEx"]
الشكل 2: نتيجة الحلّ تُحسَم بتداخل القواعد السابقة وترتيب المجلّدات والـ API.
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 23، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. لحلّ أسماء DLL قواعد سابقة قبل «بحث المجلّدات»
في شرح DLL search order في Microsoft Learn، تُعامَل عند تحميل DLL أوّلاً عناصر كهذه جزءاً من ترتيب البحث.1
- DLL redirection
- API sets
- SxS manifest redirection
- loaded-module list
- Known DLLs
بعد ذلك يدخل البحث في نظام الملفّات مثل مجلّد التطبيق وSystem32 ومجلّد Windows وPATH.1
إن أُغفل هذا، بدا مثلاً «أن يُحسَم شيء قبل مجلّد التطبيق غريباً»، لكن في شرح محمّل Windows ذلك هو المسار الأصليّ بالأحرى.
flowchart TD
A["نريد حلّ اسم DLL"] --> B["DLL redirection"]
B --> C["API set"]
C --> D["SxS manifest redirection"]
D --> E["loaded-module list"]
E --> F["Known DLLs"]
F --> G["ترتيب البحث في نظام الملفّات"]
G --> H["تُحسَم DLL التي تُحمَّل فعلاً"]
الشكل 3: تُقيَّم القواعد السابقة بالترتيب، ولا يدخل بحث نظام الملفّات إلّا إن لم يطابق أيّ منها.
3. ترتيب البحث القياسي لـ unpackaged app
في تطبيق سطح مكتب عادي، عند تحميل DLL بلا مسار كامل، يشرح Microsoft Learn ترتيب البحث القياسي لـ unpackaged app. في الحالة الافتراضيّة مع تفعيل safe DLL search mode يكون الترتيب التالي.1
| # | موضع البحث | النوع | ملاحظة |
|---|---|---|---|
| 1 | DLL redirection | قاعدة سابقة | هل يوجد اسم-التطبيق.exe.local (الفصل 7) |
| 2 | API sets | قاعدة سابقة | من اسم العقد إلى DLL التنفيذ (الفصل 6) |
| 3 | SxS manifest redirection | قاعدة سابقة | الربط عبر manifest (الفصل 7) |
| 4 | loaded-module list | قاعدة سابقة | هل وحدة بالاسم نفسه محمّلة (5.1) |
| 5 | Known DLLs | قاعدة سابقة | إن طابقت فسخة جهة النظام (5.2) |
| 6 | package dependency graph | قاعدة سابقة | Windows 11 الإصدار 21H2 فصاعداً. تُبحَث بترتيب كتابتها في المانيفست |
| 7 | المجلّد الذي حُمِّل منه التطبيق | نظام ملفّات | من هنا يبدأ بحث المجلّدات الفعليّ |
| 8 | مجلّد النظام | نظام ملفّات | عادة %SystemRoot%\System32. الموضع الذي يمكن الحصول عليه بـ GetSystemDirectory |
| 9 | 16-bit system folder | نظام ملفّات | مجلّد System من عصر 16 بتّاً. لا توجد دالّة للحصول على المسار، لكن البحث يتمّ. في التطبيقات الحديثة نادراً ما يُلتفَت إليه، ويكفي معرفة أنّه «باقٍ بنداً في الترتيب» |
| 10 | مجلّد Windows | نظام ملفّات | الموضع الذي يمكن الحصول عليه بـ GetWindowsDirectory |
| 11 | current folder | نظام ملفّات | إن عُطِّل safe DLL search mode صعد هذا إلى موضع 8 |
| 12 | المجلّدات المصطفّة في PATH |
نظام ملفّات | لا يشمل المسار الخاصّ بالتطبيق في مفتاح سجلّ App Paths |
هذا الجدول ليس للحفظ. إنّه جدول لتحديد في أيّ درجة حُسمت حالتك. إن حاولت إصلاح مشكلة حُسمت في 1 إلى 6 بتوزيع المجلّدات من 7 فصاعداً، فلن يتغيّر شيء مهما فعلت.
flowchart TB
accTitle: الاستخدام الصحيح لجدول ترتيب البحث
accDescr: مخطّط يبيّن أنّ جدول ترتيب البحث ليس للحفظ بل لتحديد في أيّ درجة حُسمت حالتك، وأنّ محاولة إصلاح مشكلة حُسمت بالقواعد السابقة بتوزيع المجلّدات لا تغيّر شيئاً.
c1["العَرَض: تُقرأ DLL غير مقصودة"] --> c2{"في أيّ درجة حُسم الأمر"}
c2 -->|"قواعد سابقة (1 إلى 6)"| c3["تغيير توزيع المجلّدات لا يغيّر شيئاً"]
c2 -->|"نظام الملفّات (7 إلى 12)"| c4["مجال ينفع فيه ترتيب التوزيع أو المسار"]
الشكل 4: قبل الإصلاح، حدّد في أيّ درجة حُسمت تلك المشكلة.
ما ينفع خصوصاً في العمل هذه الثلاث.
- current folder في الخلف كثيراً افتراضيّاً. safe DLL search mode يصعّب تقديم current folder.1
- لكن الوجود في الخلف لا يعني الأمان. ما دام مجلّد يستطيع المهاجم السيطرة عليه باقياً في أهداف البحث، بقيت فسحة DLL preloading.2
- من Windows 11 21H2 فصاعداً دخل package dependency graph أيضاً في شرح بحث unpackaged app. فرق يسهل إغفاله إن حُفظ الشرح القديم وحده.1
flowchart TB
accTitle: موضع current folder والأمان
accDescr: مخطّط يبيّن أنّ current folder يُوضَع في خلف ترتيب البحث كثيراً في الافتراضي مع تفعيل safe DLL search mode، لكن الوجود في الخلف لا يعني الأمان، وما دام موضع يستطيع المهاجم السيطرة عليه باقياً في أهداف البحث بقيت فسحة DLL preloading.
d1["safe DLL search mode مفعّل (افتراضي)"] --> d2["يُوضَع current folder في الخلف كثيراً"]
d2 -.-> d3["الوجود في الخلف لا يعني الأمان"]
d3 --> d4["إن بقي موضع يستطيع المهاجم السيطرة عليه في أهداف البحث بقيت الفسحة"]
الشكل 5: موضع current folder خُفِّض، لكن ذلك وحده لا يصير دفاعاً.
4. packaged app وunpackaged app ليسا سواء
في Microsoft Learn يُعرَّف لـ packaged app ترتيب بحث آخر. في packaged app يفعل package dependency graph في مرحلة أسبق، ويختلف تفكير البحث نفسه قليلاً.1
إن أُغفل هذا الفرق وقع لبس كهذا بعد التحويل إلى MSIX أو إدخال Windows App SDK.
- DLL تُوجَد في تشغيل unpackaged أثناء التطوير ولا تُوجَد في حزمة الإنتاج
- تختلط تبعية مانيفست الحزمة بالاعتماد القديم على
PATHفتتغيّر شروط إعادة الإنتاج - يُشرح «ترتيب بحث DLL في Windows هكذا» بجدول واحد فيُسقط فرق سلوك packaged app
في المقالات ومراجعة التصميم، الأأمن الفصل أوّلاً: «أحديث packaged app أم unpackaged app؟».1
flowchart TB
accTitle: افصل packaged عن unpackaged أوّلاً
accDescr: مخطّط يبيّن أنّ لـ packaged app ترتيب بحث آخر ويفعل package dependency graph في مرحلة أسبق، فيقع لبس مثل DLL تُوجَد في تشغيل unpackaged أثناء التطوير ولا تُوجَد في حزمة الإنتاج، لذلك الأأمن في مراجعة التصميم الفصل أوّلاً عن أيّ الحديثين.
e1{"حديث أيّ تطبيق"}
e1 -->|"unpackaged app"| e2["ترتيب البحث القياسي في الفصل 3"]
e1 -->|"packaged app"| e3["ترتيب بحث آخر (dependency graph سابق)"]
e3 -.-> e4["منبع لبس تغيّر السلوك بعد MSIX عن وقت التطوير"]
الشكل 6: ترتيب البحث ليس جدولاً واحداً؛ ينقسم أوّلاً حسب packaged أو unpackaged.
5. ماذا يفعل Known DLLs وloaded-module list
ما يخالف الحدس في حلّ DLL هو loaded-module list وKnown DLLs.
5.1 loaded-module list
يشرح Microsoft Learn أنّ النظام يستطيع التأكّد مما إذا كانت DLL بالاسم نفسه محمّلة أصلاً في الذاكرة.1
أي إنّه قبل بحث نظام الملفّات يدخل حكم:
- أذلك الاسم محمّل أصلاً
- وبالتالي، هل ثمّة حاجة أصلاً للبحث الآن
لذلك إن أُغفلت أثناء التحقيق حقيقة أنّ «DLL بالاسم نفسه من مجلّد آخر كانت محمّلة أوّلاً في هذه العمليّة»، أُسيء قراءة شروط إعادة الإنتاج.
flowchart TB
accTitle: حكم loaded-module list
accDescr: مخطّط يبيّن أنّه قبل بحث نظام الملفّات يُتأكَّد مما إذا كانت DLL بالاسم نفسه محمّلة أصلاً في ذاكرة تلك العمليّة، فإن كانت محمّلة استُخدمت بغضّ النظر عن المجلّد الذي قُرئت منه، فتسقط الحاجة نفسها إلى البحث.
f1["طلب حلّ اسم DLL"] --> f2{"هل وحدة بالاسم نفسه محمّلة"}
f2 -->|"محمّلة"| f3["استخدام تلك الوحدة (المجلّد غير ذي صلة)"]
f2 -->|"غير محمّلة"| f4["التقدّم إلى البحث التالي"]
f3 -.-> f5["إن قُرئت أوّلاً DLL بالاسم نفسه من مجلّد آخر تِهت"]
الشكل 7: إن وُجدت وحدة محمّلة بالاسم نفسه، لا يُبحث من جديد.
5.2 Known DLLs
Known DLLs قائمة DLL يعدّها Windows معروفة في ذلك الإصدار، ويمكن تأكيدها في HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\KnownDLLs. إن طابقت DLL، استخدم النظام نسخة تلك DLL المعروفة.1
المهمّ هنا أنّ Known DLLs ليست حديثاً من نوع «يكفي أن يضع التطبيق العامّ DLL بالاسم نفسه في مجلّد التطبيق ليفوز».
فهمها سباق أسبقيّة بسيطاً مع System32 يسيء فهم السلوك.
flowchart TB
accTitle: عمل Known DLLs
accDescr: مخطّط يبيّن أنّ Known DLLs قائمة DLL يعدّها Windows معروفة في ذلك الإصدار، فإن طابقت استُخدمت نسخة جهة النظام، لذلك ليست حديثاً من نوع أن يضع التطبيق العامّ DLL بالاسم نفسه في مجلّد التطبيق ليفوز، وفهمها سباق أسبقيّة مع System32 يسيء الفهم.
g1["اسم DLL المراد حلّه"] --> g2{"هل يطابق Known DLLs"}
g2 -->|"يطابق"| g3["تُستخدم نسخة جهة النظام"]
g2 -->|"لا يطابق"| g4["التقدّم إلى البحث التالي"]
g3 -.-> g5["ليس حديث كتابة فوق بتوزيع مجلّد التطبيق"]
الشكل 8: DLL المعروفة تُربَط بنسخة جهة النظام ولا تصير سباق أسبقيّة.
6. API set اسم عقد لا «اسم DLL مادّي»
حين ترى اسماً مثل api-ms-win-core-... يسهل التفكير «من أين نبحث عن ملفّ DLL ذلك». لكن Microsoft Learn يشرح أنّ API set اسم مستعار افتراضي لـ DLL مادّي، وآليّة تفصل التنفيذ عن العقد.3
أي إنّ التفكير:
- اسم API set = اسم ملفّ DLL المادّي كما هو
- حلّ API set = بحث ملفّات كـ DLL العادي
غير دقيق.
إذا أُدخل تفكير API set صار أسهل شرح أنّ:
- الأمر يستقيم حتى إن اختلف اسم DLL التنفيذ حسب إصدار Windows أو نوع الجهاز
- جهة الاستدعاء لا تحتاج معرفة ثابتة بـ «أيّ DLL مضيف ينفّذ»
flowchart TB
accTitle: API set اسم عقد
accDescr: مخطّط يبيّن أنّ اسماً مثل api-ms-win- ليس اسم ملفّ DLL مادّي بل اسماً مستعاراً افتراضيّاً يخفي DLL التنفيذ، وبفصل العقد عن التنفيذ يستقيم الأمر حتى إن اختلف التنفيذ حسب الإصدار أو الجهاز، ولا تحتاج جهة الاستدعاء معرفة ثابتة بأيّ DLL مضيف ينفّذ.
h1["اسم عقد api-ms-win-…"] --> h2["يُحَلّ اسماً مستعاراً افتراضيّاً"]
h2 --> h3["DLL المادّي الذي ينفّذ مخفيّ"]
h3 --> h4["يستقيم حتى إن اختلف التنفيذ حسب الإصدار أو الجهاز"]
h1 -.-> h5["إحساس بحث الملفّات العادي غير دقيق"]
الشكل 9: API set ليس «اسم ملفّ يُبحث عنه»، بل اسم عقد يخفي التنفيذ.
7. manifest وside-by-side (SxS) حلّ آخر لمشكلة إصدارات DLL
DLL redirection ومانيفست SxS ليسا حيلة صغيرة في ترتيب البحث، بل يُشرحان بوصفهما آليّة لتفادي تصادم إصدارات DLL.789
في Microsoft Learn الترتيب هو:
- manifest ملفّ XML يصف side-by-side assembly أو isolated application
- side-by-side assembly وحدة التسمية والربط والإصدار والتوزيع
- المحمّل يحكم أيّ إصدار يُربَط بناء على التبعيّة المكتوبة في manifest
flowchart TB
accTitle: علاقة manifest وside-by-side
accDescr: مخطّط يبيّن أنّ manifest ملفّ XML يصف side-by-side assembly أو isolated application، وأنّ side-by-side assembly وحدة التسمية والربط والإصدار والتوزيع، وأنّ المحمّل يحكم أيّ إصدار يُربَط بناء على التبعيّة المكتوبة في manifest، بوصف ذلك آليّة لتفادي تصادم إصدارات DLL.
i1["manifest (XML)"] --> i2["وصف side-by-side assembly والإصدار المعتمد"]
i2 --> i3["المحمّل يحكم أيّ إصدار يُربَط"]
i3 --> i4["يمكن تعايش إصدارات متعدّدة من DLL نفسها"]
الشكل 10: SxS ليس حيلة ترتيب بحث، بل حلّاً آخر بوصفه آليّة لتصادم الإصدارات.
لذلك يلزم في العمل فصل هذه الثلاثة.
- حديث وضع private DLL في مجلّد التطبيق فحسب
- حديث استخدام DLL redirection مثل
.local - حديث استخدام ربط side-by-side عبر manifest
الثلاثة قريبة من جهة «تؤثّر في حلّ DLL»، لكن قصد التصميم ليس واحداً. يرشد Microsoft Learn إلى التمييز: إن أردت الحلّ دون لمس تطبيق قائم فـ DLL redirection، وإن كان تطبيقاً جديداً فمكوّن side-by-side.7
flowchart TB
accTitle: تمييز الوسائل الثلاث
accDescr: مخطّط يبيّن أنّ حديث وضع private DLL في مجلّد التطبيق، وحديث استخدام DLL redirection مثل .local، وحديث ربط side-by-side عبر manifest، تؤثّر كلّها في حلّ DLL لكن قصد التصميم ليس واحداً، ويُرشَد إلى DLL redirection حين تريد الإصلاح دون لمس تطبيق قائم، وside-by-side لتطبيق جديد.
j0{"حديث أيّ وسيلة"}
j0 --> j1["وضع private DLL في مجلّد التطبيق"]
j0 --> j2["DLL redirection (.local)"]
j0 --> j3["ربط مانيفست SxS"]
j2 -.-> j4["حين تريد الإصلاح دون لمس تطبيق قائم"]
j3 -.-> j5["إدارة تبعيّة تطبيق جديد"]
الشكل 11: وسائل ثلاث تبدو متشابهة تُميَّز بقصد التصميم.
7.1 ماذا يفعل .local تحديداً
كثيراً ما يُمرَّر في سطر، فنكتب سلوك unpackaged app مفصولاً.7
- ما يُوضَع: اسم ملفّ إعادة التوجيه
اسم-الملفّ-التنفيذي.local. إن كانEditor.exeفضعEditor.exe.localفي المجلّد نفسه مع الملفّ التنفيذي. ضع DLL المراد تحميلها في المجلّد نفسه أيضاً - المحتوى: محتوى الملفّ يُتجاهَل. الوجود نفسه إشارة تجعل المحمّل ينظر إلى مجلّد الملفّ التنفيذي أوّلاً عند تحميل DLL
- نطاق الفعل: يفعل في التحميل بمسار كامل وفي التحميل باسم الوحدة فقط. بغضّ النظر عن المسار الممرَّر إلى
LoadLibraryأوLoadLibraryEx، إن وُجدت DLL بالاسم نفسه في مجلّد الملفّ التنفيذي قُرئت تلك. المواصفة لإنقاذ مواضع ليس فيها سوى موضع تسجيل واحد مثل COM - إن لم تُوجَد: إن لم تكن في مجلّد الملفّ التنفيذي عاد الأمر إلى ترتيب البحث العادي
- شكل المجلّد: يعمل أيضاً بصنع مجلّد اسمه
Editor.exe.localووضع DLL داخله - التفعيل على الجهاز كلّه: اصنع قيمة DWORD اسمها
DevOverrideEnableفيHKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Optionsواجعلها 1 ثم أعد التشغيل. بهذا الإعداد تفعل إعادة التوجيه بـ.localحتى إن كان للتطبيق application manifest - في packaged app: يتغيّر موضع الوضع، ويُنظَر إلى
<موضع تثبيت الحزمة>\microsoft.system.package.metadata\application.local\
هنا أثر جانبي إن أُغفل تاه التحقيق.
إذا استخدمت DLL redirection وكان التطبيق لا يستطيع الوصول إلى كلّ الأقراص والمجلّدات في ترتيب البحث، أوقف LoadLibrary البحث لحظة رفض الوصول. إن لم تستخدم DLL redirection، قُفز عن المجلّدات غير القابلة للوصول واستمرّ البحث.7
إن قيل في بيئة وُضع فيها .local فقط «DLL التي يفترض أن تكون أبعد غير موجودة»، فاشتبه في هذا الفرق.
flowchart TB
accTitle: سلوك .local
accDescr: مخطّط يبيّن أنّ علامة اسم-الملفّ-التنفيذي.local في المجلّد نفسه مع الملفّ التنفيذي تجعل الوجود نفسه إشارة بغضّ النظر عن المحتوى فينظر إلى جانب الملفّ التنفيذي أوّلاً ويفعل حتى في التحميل بمسار كامل، وأنّه إن لم توجد عاد إلى ترتيب البحث العادي، مع أثر جانبي هو وقف البحث عند الرفض.
k1["وضع اسم-الملفّ-التنفيذي.local"] --> k2["النظر إلى جانب الملفّ التنفيذي أوّلاً"]
k2 --> k3{"هل توجد DLL بالاسم نفسه"}
k3 -->|"توجد"| k4["قراءتها حتى مع المسار الكامل"]
k3 -->|"لا توجد"| k5["العودة إلى ترتيب البحث العادي"]
k1 -.-> k6["أثر جانبي: وقف البحث عند الرفض"]
الشكل 12: .local إشارة بالوجود نفسه، ويفعل حتى في التحميل بمسار كامل.
8. ماذا يتغيّر بـ LoadLibraryEx وSetDllDirectory وAddDllDirectory
8.1 SetDllDirectory
يغيّر SetDllDirectory ترتيب البحث، لكن Microsoft Learn ينصّ صراحة على أنّه يعطّل safe DLL search mode فعليّاً.1
أي حتى إن استخدمته بنيّة «إضافة مجلّد واحد خاصّ بالتطبيق»، تغيّر فضاء البحث بما يشمل التعامل مع current folder.
ثمّ إن استُدعي SetDllDirectory في العمليّة الأب، فقد يصل الأثر إلى ترتيب البحث القياسي في جهة العمليّة الابن.1
لذلك في العمل، الأأمن من استعمال SetDllDirectory بخفّة الميل إلى:
SetDefaultDllDirectoriesAddDllDirectoryLOAD_LIBRARY_SEARCH_*فيLoadLibraryEx
flowchart TB
accTitle: فخّ SetDllDirectory
accDescr: مخطّط يبيّن أنّ SetDllDirectory حتى بنيّة إضافة مجلّد واحد خاصّ بالتطبيق يعطّل safe DLL search mode فعليّاً فيغيّر فضاء البحث كلّه، وأنّ الاستدعاء في العمليّة الأب قد يصل أثره إلى ترتيب بحث العمليّة الابن، لذلك الأأمن الميل إلى واجهات مثل SetDefaultDllDirectories وAddDllDirectory وأعلام بحث LoadLibraryEx.
l1["إضافة مجلّد واحد بـ SetDllDirectory"] --> l2["تعطيل safe DLL search mode فعليّاً"]
l2 --> l3["تغيّر فضاء البحث كلّه بما يشمل current folder"]
l2 -.-> l4["قد يصل الأثر إلى ترتيب بحث العمليّة الابن"]
l3 --> l5["الميل بدلاً من ذلك إلى واجهات مجموعة SetDefaultDllDirectories"]
الشكل 13: نيّة «إضافة مجلّد واحد فقط» تُضعِف فضاء البحث كلّه.
8.2 AddDllDirectory
المسارات المضافة بـ AddDllDirectory تُستخدم مع LOAD_LIBRARY_SEARCH_USER_DIRS.
في Microsoft Learn، ترتيب البحث عند إضافة أكثر من واحد غير محدّد.15
لذلك فالأفضل تجنّب تصميم:
- أضفت مجلّدات متعدّدة
- وتوقّعت ترتيب استكشافها بدقّة أيضاً
flowchart TB
accTitle: استخدام AddDllDirectory والتنبيه
accDescr: مخطّط يبيّن أنّ المسارات المضافة بـ AddDllDirectory تفعل مع LOAD_LIBRARY_SEARCH_USER_DIRS، وأنّ ترتيب البحث عند إضافة أكثر من واحد غير محدّد، لذلك ينبغي تجنّب تصميم يضيف مجلّدات متعدّدة ويتوقّع ترتيب استكشافها بدقّة.
m1["إضافة مسار بـ AddDllDirectory"] --> m2["يفعل مع LOAD_LIBRARY_SEARCH_USER_DIRS"]
m2 -.-> m3["الترتيب عند الإضافة المتعدّدة غير محدّد"]
m3 --> m4["تجنّب تصميماً يعتمد على ترتيب الاستكشاف"]
الشكل 14: الترتيب بين مجلّدات المستخدم المضافة لا يُعوَّل عليه في المواصفة.
8.3 SetDefaultDllDirectories
يُشرح SetDefaultDllDirectories بوصفه واجهة تخرج من مسار بحث DLL القياسي المجلّدات الأسهل ضعفاً وتقيّد أهداف البحث.4
خصائص يُستحسن ضبطها خصوصاً هذه الثلاث.
- يفعل على مستوى العمليّة
- بعد الاستدعاء يستمرّ طوال عمر العمليّة
- لا يمكن إعادة مسار البحث القياسي الذي ضُبط مرّة إلى شكله القياسي الأصلي كما هو
من جهة الأمن، واجهة يسهل بها تصميم «الميل فور الإقلاع إلى فضاء بحث أميل إلى الأمان».4
flowchart TB
accTitle: خصائص SetDefaultDllDirectories
accDescr: مخطّط يبيّن أنّ SetDefaultDllDirectories واجهة تخرج من مسار بحث DLL القياسي المجلّدات الأسهل ضعفاً وتقيّد أهداف البحث، وتفعل على مستوى العمليّة وتستمرّ طوال عمر العمليّة بعد الاستدعاء ولا يمكن إعادة مسار البحث الذي ضُبط مرّة إلى شكله القياسي الأصلي، لذلك يسهل بها تصميم الميل فور الإقلاع إلى فضاء بحث أميل إلى الأمان.
n1["SetDefaultDllDirectories فور الإقلاع"] --> n2["إخراج المواضع الأسهل ضعفاً من أهداف البحث"]
n2 --> n3["يفعل على مستوى العمليّة طوال العمر"]
n3 -.-> n4["لا يمكن الإعادة إلى الشكل القياسي الأصلي"]
الشكل 15: واجهة تُستدعى مرّة فور الإقلاع لإمالة العمليّة كلّها إلى جهة الأمان.
8.4 LoadLibraryEx
يستطيع LoadLibraryEx تغيير سلوك البحث بأعلام مثل LOAD_WITH_ALTERED_SEARCH_PATH وLOAD_LIBRARY_SEARCH_*.61
عمليّاً، واجهة تسهل تلبية مطالب مثل:
- إدراج مجلّد DLL مصدر التحميل أيضاً في أهداف الاستكشاف، بما يشمل DLLات التبعيّة
- التضييق على مجلّد التطبيق و
System32ومجلّدات المستخدم المضافة صراحة فقط
قبل الكتابة ثمّة أربعة قيود يُستحسن ضبطها.6
| القيد | المحتوى |
|---|---|
| الوسيط الثاني | hFile محجوز للمستقبل، ويجب تمرير NULL حتماً |
| عدم الجمع | LOAD_WITH_ALTERED_SEARCH_PATH لا يُجمَع مع أيّ علم LOAD_LIBRARY_SEARCH_* |
| المسار الكامل لازم | إن استخدمت LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR وجب أن يكون الوسيط الأوّل مساراً كاملاً |
| الترتيب عند التحديد المتعدّد | يُبحَث بترتيب LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR ثم LOAD_LIBRARY_SEARCH_APPLICATION_DIR ثم LOAD_LIBRARY_SEARCH_USER_DIRS ثم LOAD_LIBRARY_SEARCH_SYSTEM32. غير أنّ الترتيب داخل USER_DIRS غير محدّد |
إن حدّدت علماً واحداً حتى من LOAD_LIBRARY_SEARCH_*، لا يُستخدم مسار البحث القياسي أصلاً.
أي إن مرّرت LOAD_LIBRARY_SEARCH_SYSTEM32 وحده، لم يُبحث مجلّد التطبيق. «التضييق» يعني ذلك.
flowchart TB
accTitle: تحديد أعلام البحث يلغي المسار القياسي
accDescr: مخطّط يبيّن معنى «التضييق»: إن حدّدت علماً واحداً حتى من مجموعة LOAD_LIBRARY_SEARCH_ لم يُستخدم مسار البحث القياسي أصلاً، فإن مرّرت LOAD_LIBRARY_SEARCH_SYSTEM32 وحده لم يُبحث مجلّد التطبيق.
o1{"هل حدّدت علماً من مجموعة LOAD_LIBRARY_SEARCH_"}
o1 -->|"لم تحدّد"| o2["يُستخدم مسار البحث القياسي"]
o1 -->|"حتّى علم واحد"| o3["لا يُستخدم مسار البحث القياسي أصلاً"]
o3 --> o4["يُستكشَف نطاق الأعلام المحدّدة فقط"]
o4 -.-> o5["SYSTEM32 وحده لا يبحث مجلّد التطبيق"]
الشكل 16: العلم ليس «إضافة»، بل تحديد «الاستبدال بذلك النطاق فقط».
8.5 أصغر مثال شيفرة (C/C++)
إذا جُمعت الواجهات حتى هنا في واحدة صارت على الشكل التالي.
SetDefaultDllDirectories وLOAD_LIBRARY_SEARCH_* واجهتان من Windows 8 فصاعداً، لذلك يلزم التصريح بإصدار الهدف في الترويسة.46
/* cl /W4 loader.c (Visual Studio 2022 + Windows SDK 10)
* SetDefaultDllDirectories / AddDllDirectory / LOAD_LIBRARY_SEARCH_* は
* Windows 8 以降。Windows 7 も対象にするなら KB2533623 が前提になり、
* GetProcAddress で Kernel32.dll から実行時に取得する形が必要です。 */
#define _WIN32_WINNT 0x0602 /* Windows 8 */
#include <windows.h>
#include <stdio.h>
int wmain(void)
{
/* 1. プロセス既定の検索空間を安全側へ寄せる。
* current folder と PATH を検索対象から外すのが目的です。
* LOAD_LIBRARY_SEARCH_DEFAULT_DIRS は
* APPLICATION_DIR + SYSTEM32 + USER_DIRS の組み合わせです。
* USER_DIRS を含めておかないと、手順 2 の AddDllDirectory は
* プロセス既定には反映されません。 */
if (!SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)) {
wprintf(L"SetDefaultDllDirectories failed: %lu\n", GetLastError());
return 1;
}
/* 2. 自前のプラグインフォルダだけを明示的に足す。
* AddDllDirectory に渡すのは絶対パスでなければなりません。 */
const wchar_t *pluginDir = L"C:\\MyApp\\plugins";
DLL_DIRECTORY_COOKIE cookie = AddDllDirectory(pluginDir);
if (cookie == NULL) {
wprintf(L"AddDllDirectory failed: %lu\n", GetLastError());
return 1;
}
/* 3. ロードする。
* 第 2 引数 hFile は予約済みなので必ず NULL。
* ここでフラグを渡すと、手順 1 のプロセス既定ではなく
* このフラグの組み合わせだけが使われます。
* APPLICATION_DIR を外しているので、アプリフォルダは探されません。 */
HMODULE h = LoadLibraryExW(
L"foo.dll",
NULL,
LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32);
if (h == NULL) {
wprintf(L"LoadLibraryExW failed: %lu\n", GetLastError());
RemoveDllDirectory(cookie);
return 1;
}
/* 4. どこから読まれたかを必ず確認する。
* 調査では「読めたかどうか」より「どこから読めたか」が重要です。 */
{
wchar_t loadedPath[MAX_PATH];
DWORD cap = (DWORD)(sizeof(loadedPath) / sizeof(loadedPath[0]));
DWORD len = GetModuleFileNameW(h, loadedPath, cap);
if (len > 0 && len < cap) {
wprintf(L"loaded from: %s\n", loadedPath);
} else {
wprintf(L"GetModuleFileNameW failed: %lu\n", GetLastError());
}
}
FreeLibrary(h);
RemoveDllDirectory(cookie);
return 0;
}
ما تضبطه هذه الشيفرة أربع نقاط.
- استدعِ
SetDefaultDllDirectoriesأوّلاً. بعد الاستدعاء مرّة يفعل طوال عمر العمليّة، ولا يمكن الإعادة إلى مسار البحث القياسي4 AddDllDirectoryلا معنى له إلّا معLOAD_LIBRARY_SEARCH_USER_DIRS. إن لم يتضمّنSetDefaultDllDirectoriesقيمةUSER_DIRS، لم تُستخدم المجلّدات المضافة إلّا في استدعاءLoadLibraryExالذي حدّدLOAD_LIBRARY_SEARCH_USER_DIRS5- قم بالتنظيف. الـ cookie الذي أعاده
AddDllDirectoryيمكن إزالته بتمريره إلىRemoveDllDirectory5 - أخرج موضع القراءة. وجود سطر
GetModuleFileNameWأو غيابه يغيّر زمن التحقيق عند العطل
لاحظ أنّ DLL التي يعتمد عليها foo.dll أيضاً تُبحَث في نطاق LOAD_LIBRARY_SEARCH_* هذا.
إن أردت مجلّد foo.dll نفسه هدفاً لاستكشاف DLLات التبعيّة، فاجعل الوسيط الأوّل مساراً كاملاً وأضف LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR.
flowchart TB
accTitle: تدفّق أصغر مثال شيفرة
accDescr: مخطّط يبيّن التدفّق: إمالة فضاء البحث الافتراضي للعمليّة إلى جهة الأمان بـ SetDefaultDllDirectories، وإضافة مجلّد الإضافة صراحة بـ AddDllDirectory، والتحميل بتحديد أعلام البحث في LoadLibraryEx، وتأكيد موضع القراءة بـ GetModuleFileNameW، ثم التنظيف بـ RemoveDllDirectory.
p1["1. إمالة الافتراضي إلى الأمان بـ SetDefaultDllDirectories"] --> p2["2. إضافة المجلّد المسموح بـ AddDllDirectory"]
p2 --> p3["3. تحميل بتحديد الأعلام في LoadLibraryEx"]
p3 --> p4["4. تأكيد مصدر التحميل بـ GetModuleFileNameW"]
p4 --> p5["5. التنظيف بـ RemoveDllDirectory"]
الشكل 17: هيكل مثال الشيفرة خمس حركات: ضيّق ثم أضف ثم حمّل ثم أكّد ثم نظّف.
8.6 عند الاستخدام من C#
الفكرة نفسها تُستخدم في .NET أيضاً. مسار بحث P/Invoke يُتحكَّم فيه بسمة DefaultDllImportSearchPaths، والتحميل الصريح بـ NativeLibrary.Load.
// .NET 8 / C# 12
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
// アセンブリ内のすべての P/Invoke について、既定の検索先を System32 に限定する。
// 制約: 絶対パスを指定した P/Invoke には、この属性は適用されません。
// また Windows 以外のプラットフォームでは効果がありません。
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetTickCount();
private static void Main()
{
// السمة لا تفعل شيئاً بمجرد الإعلان. لا تسري إلا عند الاستدعاء.
Console.WriteLine($"GetTickCount = {GetTickCount()}");
// الملحق موضوع في مجلد مخصّص. لا تكتب هنا
// NativeLibrary.Load("foo.dll", asm, ApplicationDirectory | System32)
// ApplicationDirectory يشير إلى مجلد الـ exe، وSystem32 إلى مجلد نظام التشغيل،
// ولا ينظر أيّ منهما إلى مجلد الملحقات.
// يفشل التحميل أو يمسك foo.dll آخر وُجد صدفة بجانب الـ exe.
// إن عُرف موضع الملف، فالتحديد بمسار كامل هو الأضمن.
string pluginDir = Path.Combine(AppContext.BaseDirectory, "plugins");
string pluginPath = Path.GetFullPath(Path.Combine(pluginDir, "foo.dll"));
if (!File.Exists(pluginPath))
{
throw new FileNotFoundException("الملحق غير موجود.", pluginPath);
}
// التحميل الزائد الذي يأخذ مساراً يقرأ ذلك الملف مباشرة (لا يبحث).
IntPtr handle = NativeLibrary.Load(pluginPath);
try
{
// تحقّق من أين حُمِّل.
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
if (string.Equals(module.ModuleName, "foo.dll", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"loaded from: {module.FileName}");
}
}
}
finally
{
NativeLibrary.Free(handle);
}
}
}
قيم DllImportSearchPath تقابل أعلام LOAD_LIBRARY_SEARCH_*.
لذلك إن كتبت في جهة C# «System32 فقط» انطبق قيد 8.4 كما هو. لا يُبحث مجلّد التطبيق.
ما يُلتفَت إليه هنا أنّ DllImportSearchPath ليس فيه قيمة تشير إلى «مجلّد اختياري». ApplicationDirectory يشير إلى مجلّد الـ exe، وSystem32 إلى مجلّد نظام التشغيل، ولا توجد قيمة تمثّل مجلّداً قرّرته أنت للإضافة. لذلك إن حاولت التقاط ما وُضع في مجلّد مخصّص بأعلام البحث لم يصل، وصار التحديد بمسار كامل كما أعلاه.
لاحظ أيضاً أنّ القراءة بمسار كامل، كما في الفصل 9، لا تثبّت DLLات التي يعتمد عليها foo.dll. إن جاءت الإضافة بـ DLL تبعيّة خاصّة بها، لزم أسلوب جهة C كما هو: إضافة ذلك المجلّد بـ AddDllDirectory وتفعيل LOAD_LIBRARY_SEARCH_USER_DIRS، أو جمع التبعيّات في مجلّد واحد واستخدام LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR.
flowchart TB
accTitle: تفكير قراءة DLL في مجلّد مخصّص من C#
accDescr: مخطّط يبيّن أنّ DllImportSearchPath ليس فيه قيمة تشير إلى مجلّد اختياري، وأنّ ApplicationDirectory يشير إلى مجلّد الـ exe وSystem32 إلى مجلّد نظام التشغيل، لذلك لا تصل إضافة وُضعت في مجلّد مخصّص بأعلام البحث، فيُحدَّد مسار كامل ويُقرأ مباشرة، والتحكّم في DLLات التبعيّة يحتاج أسلوب جهة C كما هو.
q1["وضع الإضافة في مجلّد مخصّص"] --> q2{"هل تُلتقَط بأعلام البحث"}
q2 -.->|"لا توجد قيمة تشير إلى مجلّد اختياري"| q3["الأعلام لا تصل"]
q2 -->|"الطريقة الأكيدة"| q4["تجميع مسار كامل والقراءة المباشرة بـ NativeLibrary.Load"]
q4 -.-> q5["التحكّم في DLLات التبعيّة يحتاج أسلوب جهة C"]
الشكل 18: في .NET أيضاً، المجلّد الذي تقرّره بنفسك يُقرأ بمسار كامل على وجه أكيد.
9. المسار الكامل لا يثبّت DLLات التبعيّة
هذا موضع مهمّ جدّاً عمليّاً في هذا الموضوع ويسهل إغفاله. يشرح Microsoft Learn أنّه حتى إن حمّلت أوّل DLL بمسار كامل، تُبحَث DLLات التبعيّة لذلك الـ DLL باسم الوحدة فقط.14
أي إنّه ليس بالضرورة أنّ:
- حمّلت
C:\\MyApp\\plugins\\foo.dllصراحة - لذلك تُؤخَذ
bar.dllالتي يعتمد عليهاfoo.dllحتماً من المجلّد نفسه
إن وُجد هذا الوهم صار عطلاً مزعجاً قليلاً من نوع:
- يعمل في بيئة التطوير
- تُحَلّ
bar.dllأخرى في جهة التوزيع - يصير تصادم DLL التبعيّة معتمداً على بيئة إعادة الإنتاج
flowchart TB
accTitle: المسار الكامل لا يثبّت DLLات التبعيّة
accDescr: مخطّط يبيّن أنّ تحميل أوّل DLL بمسار كامل يجعل ذلك الـ DLL نفسه يُقرأ كما حُدِّد، لكن DLL التي يعتمد عليها تُبحَث باسم الوحدة فقط فقد تُحَلّ من موضع آخر حسب البيئة، فيصير عطلاً معتمداً على البيئة يعمل في التطوير ويُكسر في جهة التوزيع.
r1["تحميل foo.dll بمسار كامل"] --> r2["foo.dll نفسه يُقرأ كما حُدِّد"]
r1 --> r3["bar.dll المعتمدة تُبحَث باسم الوحدة فقط"]
r3 --> r4["قد تُحَلّ من موضع آخر حسب البيئة"]
r4 -.-> r5["عطل معتمد على البيئة يعمل في التطوير ويُكسر في جهة التوزيع"]
الشكل 19: ما يمكن تثبيته بمسار كامل هو الأوّل وحده، والتبعيّة معاملة أخرى.
10. كيف نتجنّب DLL preloading / hijacking
في أمن DLL في Microsoft Learn يُشرح أنّ الجمع بين التحميل الديناميكي بلا مسار كامل ومجلّد بحث يستطيع المهاجم السيطرة عليه يؤدّي إلى هجوم DLL preloading أو هجوم binary planting.2
في العمل يسهل التفكير بالميل إلى هذا الشكل الأساس.
- تقليل التحميل باسم عارٍ مثل
LoadLibrary("foo.dll") - استخدام المسار الكامل إن لزم
- تضييق مسار البحث الافتراضي للعمليّة بـ
SetDefaultDllDirectories - إضافة المجلّدات المسموح بها صراحة فقط بـ
AddDllDirectory - التصريح بنطاق الاستكشاف بـ
LOAD_LIBRARY_SEARCH_SYSTEM32وLOAD_LIBRARY_SEARCH_APPLICATION_DIRوLOAD_LIBRARY_SEARCH_USER_DIRSوغيرها - تجنّب current folder والاعتماد غير الحذر على
PATH
خصوصاً خطر أن تحمل عمليّة تعمل بامتياز المدير مسار بحث غامضاً. ويشرح Microsoft Learn أيضاً أنّ DLL خبيثة إذا حُمِّلت نُفِّذت بامتياز تلك العمليّة.2
flowchart TB
accTitle: الجمع الذي يقوم عليه هجوم DLL preloading
accDescr: مخطّط يبيّن أنّ الجمع بين التحميل الديناميكي بلا مسار كامل ومجلّد بحث يستطيع المهاجم السيطرة عليه يكتمل شرط قيام هجوم DLL preloading، وأنّ DLL خبيثة إذا حُمِّلت نُفِّذت بامتياز تلك العمليّة، لذلك فالتدبير تقليل الأسماء العارية وتضييق فضاء البحث.
s1["تحميل ديناميكي بلا مسار كامل"] --> s3["اكتمال شرط قيام DLL preloading"]
s2["مجلّد بحث يستطيع المهاجم السيطرة عليه"] --> s3
s3 --> s4["DLL خبيثة تُنفَّذ بامتياز العمليّة"]
s3 -.-> s5["التدبير: تقليل الأسماء العارية وتضييق فضاء البحث"]
الشكل 20: الهجوم يقوم بضرب «اسم غامض» في «موضع يمكن السيطرة عليه».
11. تأكيد أيّ DLL قُرئت من أين
حتى هنا كان حديث المواصفة. في التحقيق الفعليّ، التأكيد دون تخمين أسرع. نورد أربعة حسب الغرض.
| ماذا تريد معرفته | ما يُستخدم | أين يُنظَر |
|---|---|---|
| أين بُحث وأين وُجد | Process Monitor10 | سجلّ الوصول إلى الملفّات |
| ما المحمّل الآن | Process Explorer، tasklist /m11 |
قائمة وحدات العمليّة |
| أيّ عمليّة تمسك DLL معيّنة | ListDLLs12 | قائمة عبر العمليّات |
| المسار الذي قُرئ فعلاً أثناء التنقيح | نافذة الوحدات في Visual Studio | «تصحيح» > «نوافذ» > «وحدات»7 |
11.1 تتبع أثر الاستكشاف بـ Process Monitor
هذا الأغزر معلومات. الإجراء كالتالي.10
- شغّل Process Monitor مديراً وابدأ التسجيل
- افتح «Filter» > «Filter…» وأضف
Process Nameisمع اسم الـ exe المستهدف - في الشاشة نفسها أضف
Pathends with.dll - شغّل التطبيق المستهدف وأوقف التسجيل عند وقوع المشكلة
ما يُنظَر إليه هنا عمود Result.
يحاول المحمّل الفتح من الأعلى وفق ترتيب البحث، فتُصفّ NAME NOT FOUND في المواضع التي لم توجد، وSUCCESS في الموضع الذي فُتح فعلاً.
أي إنّ السطر التالي لآخر صفّ من NAME NOT FOUND هو المسار المعتمد فعلاً.
بالمقابلة مع جدول الفصل 3 يتّضح في أيّ درجة حُسمت تلك الحالة. إن حُسمت بقواعد سابقة (1 إلى 6 في الجدول) لم يظهر أصلاً سجلّ الذهاب للبحث عن الملفّ. وهذا أيضاً دليل مهمّ.
flowchart TB
accTitle: كيف يُقرأ سجلّ Process Monitor
accDescr: مخطّط يبيّن أنّ المحمّل يحاول الفتح من الأعلى وفق ترتيب البحث فتصفّ NAME NOT FOUND في المواضع التي لم توجد وSUCCESS في الموضع الذي فُتح فعلاً، وأنّ السطر التالي لآخر صفّ من NAME NOT FOUND هو المسار المعتمد فعلاً، وأنّ غياب السجلّ نفسه يعني أنّ الأمر حُسم بقواعد سابقة.
t1["اقرأ عمود Result من الأعلى"] --> t2["صفّ NAME NOT FOUND = مواضع بُحثت ولم توجد"]
t2 --> t3["SUCCESS التالي هو المسار المعتمد فعلاً"]
t1 -.-> t4["إن لم يظهر سجلّ فالأمر حُسم بقواعد سابقة"]
الشكل 21: الأثر يبقى في عمود Result، وغياب السجلّ نفسه دليل أيضاً.
11.2 إدراج الوحدات المحمّلة
إن أردت فقط رؤية ما قُرئ من أين في عمليّة شغّالة أصلاً، يمكن التأكيد بلا أداة إضافيّة.
tasklist /m foo.dll
هذا الأمر يدرج أسماء العمليّات وPID التي تحمّل الوحدة المحدّدة.11 بعد تضييق أيّ عمليّة هي المستهدفة، حوّل الجزء السفلي في Process Explorer إلى عرض DLL لتأكيد المسار الكامل لـ DLL التي تقرأها تلك العمليّة.
باستخدام ListDLLs من Sysinternals يمكن تأكيد الشيء نفسه من سطر الأوامر، وعبر العمليّات أيضاً.12
هل يفعل loaded-module list في 5.1 لا يُعرَف إلّا بهذه الطريقة. لأنّه إن حُمِّلت DLL بالاسم نفسه أوّلاً من مجلّد آخر، لم تبحث تلك العمليّة من جديد.
flowchart TB
accTitle: إجراء تأكيد الوحدات المحمّلة
accDescr: مخطّط يبيّن إجراء تضييق العمليّات التي تمسك الوحدة بـ tasklist /m ثم رؤية المسار الكامل بعرض DLL في Process Explorer أو ListDLLs، وأنّ أثر loaded-module list لا يُرى إلّا بهذه الطريقة.
u1["ضيّق العمليّات الممسكة بـ tasklist /m foo.dll"] --> u2["انظر المسار الكامل في Process Explorer أو ListDLLs"]
u2 --> u3["يُحسَم من أين قُرئت بوصفها واقعاً"]
u3 -.-> u4["أثر loaded-module list لا يُرى إلّا هنا"]
الشكل 22: ما يُقرأ الآن يُحسَم بقائمة الوحدات، لا بالتخمين.
12. قائمة تحقّق للحكم في العمل
عند مراجعة تصميم تحميل DLL في Windows، يقلّ الحادث إن أكّدت على الأقلّ ما يلي.
- أذلك التطبيق packaged app أم unpackaged app
- أيّ DLL من الربط الساكن وأيّها تحميل ديناميكي
- أمسار كامل أم اسم وحدة فقط
- هل يُستخدم
SetDllDirectory - هل التكوين يتيح
SetDefaultDllDirectoriesوLOAD_LIBRARY_SEARCH_* - هل تُستخدم
AddDllDirectoryعدّة مرّات مع توقّع ضمني للترتيب - بأيّ من manifest / SxS / private DLL / redirection تُدار التبعيّة
- هل ثمّة فرضيّة ضعيفة أمنيّاً على current folder أو
PATH - هل قد تُحَلّ DLL التبعيّة من موضع آخر في بيئة أخرى
- هل أكّدت في البيئة التي يظهر فيها العَرَض من أين قُرئت فعلاً (الفصل 11)
إن أكّدت هذه العشر مفصولة، أمكن إغلاق مشاكل مثل «DLL غير موجودة» و«قُرئت DLL مختلفة» و«لا يقلع في الإنتاج فقط» و«يتوقّف في مراجعة الثغرات» في مرحلة أسبق كثيراً.
flowchart TB
accTitle: تدفّق التأكيد في المراجعة
accDescr: مخطّط يبيّن أنّ حوادث مراجعة تصميم تحميل DLL تقلّ بالنظر بهذا التدفّق: شكل التطبيق packaged أو unpackaged، وطريقة التحميل مسار كامل أو اسم فقط، واستخدام الـ API مثل وجود SetDllDirectory والأعلام، وإدارة التبعيّة في manifest أو DLL التبعيّة، ثم تأكيد مصدر التحميل فعلاً في البيئة التي يظهر فيها العَرَض.
v1["تأكيد شكل التطبيق (packaged / unpackaged)"] --> v2["تأكيد طريقة التحميل (مسار كامل أم اسم فقط)"]
v2 --> v3["تأكيد استخدام الـ API (SetDllDirectory والأعلام)"]
v3 --> v4["تأكيد إدارة التبعيّة (manifest / SxS / PATH)"]
v4 --> v5["تأكيد مصدر التحميل في الميدان (الفصل 11)"]
الشكل 23: قائمة التحقّق تُهضَم بترتيب الشكل ثم التحميل ثم الـ API ثم التبعيّة ثم التأكيد الميداني.
13. خلاصة
حلّ أسماء DLL في Windows ليس مجرّد «ترتيب استكشاف المجلّدات». في الواقع يُحسَم بتداخل DLL redirection وAPI set ومانيفست SxS وloaded-module list وKnown DLLs وفضاء البحث الذي تغيّره استدعاءات الـ API.134
الأهمّ في العمل ينحصر في هذه الستّ.
- لا تحفظ ترتيب البحث جدولاً واحداً. الجدول يُستخدم لتحديد في أيّ درجة حُسمت حالتك
- افصل packaged / unpackaged
- افهم أنّ المسار الكامل قد يجعل DLL التبعيّة معاملة أخرى
- لا تستخدم
SetDllDirectoryبخفّة - إن ملت إلى جهة الأمان فاستخدم
SetDefaultDllDirectoriesوأعلام بحثLoadLibraryEx - لا تنتهِ عند التخمين؛ أكّد المسار الذي قُرئ فعلاً بـ Process Monitor وغيره
حلّ اسم DLL موضع يسهل أن تظهر فيه مجتمعة أعطال الإقلاع وفروق البيئة ومشاكل الأمن. لذلك يستحقّ الفهم لا لـ «بأيّ ترتيب يبحث Windows» فحسب، بل لـ «ماذا يعامل Windows أصلاً فرضيّة لحلّ الأسماء» أيضاً.
flowchart TB
accTitle: خلاصة هذه المقالة
accDescr: مخطّط يبيّن أنّ حلّ اسم DLL موضع يسهل أن تظهر فيه مجتمعة أعطال الإقلاع وفروق البيئة ومشاكل الأمن، وأنّه يستحقّ الفهم لا لترتيب البحث فحسب بل لما يعامله Windows أصلاً فرضيّة لحلّ الأسماء.
w1["بأيّ ترتيب يبحث (ترتيب المجلّدات)"] --> w3["لا يستقيم الشرح إلّا بفهم الاثنين"]
w2["ماذا يعامل فرضيّة (القواعد السابقة وفضاء البحث)"] --> w3
w3 --> w4["يمكن إغلاق أعطال الإقلاع وفروق البيئة والأمن معاً"]
الشكل 24: معرفة العمل في حلّ أسماء DLL ليست حفظ الترتيب، بل فهم الفرضيّة حتى نهايتها.
مقالات ذات صلة
- قائمة تحقّق للحدّ الأدنى من الأمان في تطوير تطبيقات Windows
- إلى أيّ مدى يمكن لتطبيق Windows أن يكون فعلاً single binary - ما الذي يندرج في EXE واحد وما الذي يبقى معتمداً على Windows
- متى يصبح Windows admin privilege ضرورياً - UAC والمناطق المحميّة وكيفيّة التمييز على مستوى التصميم
- ما هو Reg-Free COM - كيف يعمل Registration-Free COM وأين يلائم وأين لا يلائم
روابط مرجعية
- Microsoft Learn: Dynamic-link library search order
- Microsoft Learn: Dynamic-Link Library Security
- Microsoft Learn: Windows API sets
- Microsoft Learn: SetDefaultDllDirectories function
- Microsoft Learn: AddDllDirectory function
- Microsoft Learn: LoadLibraryEx function
- Microsoft Learn: Dynamic-link library redirection
- Microsoft Learn: Manifests
- Microsoft Learn: About Side-by-Side Assemblies
- Microsoft Learn: Process Monitor
- Microsoft Learn: ListDLLs
- Microsoft Learn: tasklist
-
Microsoft Learn, Dynamic-link library search order, accessed March 24, 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23 ↩24 ↩25
-
Microsoft Learn, Dynamic-Link Library Security, accessed March 24, 2026 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Windows API sets, accessed March 24, 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, SetDefaultDllDirectories function, accessed March 24, 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, AddDllDirectory function, accessed March 24, 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, LoadLibraryEx function, accessed March 24, 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Dynamic-link library redirection, accessed March 24, 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, About Side-by-Side Assemblies, accessed March 24, 2026 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Process Monitor ↩ ↩2
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
DllMain وقفل المحمِّل ── السبب الحقيقي لعبارة «لا تفعل شيئاً في تهيئة الـDLL»
لماذا يُحظَر استدعاء LoadLibrary أو مزامنة مؤشّرات الترابط من DllMain. نشرح من المصادر الأوّليّة كيف يسلسل قفل المحمِّل كلّ إشعار DLL، وس...
متى يلزم امتياز المسؤول على Windows - UAC والمناطق المحميّة وكيفيّة التمييز في التصميم
نرتّب المواضع التي يلزم فيها امتياز المسؤول على Windows من زاوية UAC والمناطق المحميّة والخدمات وبرامج التشغيل وتصميم per-user/per-machine.
ما الذي يفعله بدء التشغيل السريع فعليّاً ── لماذا «إيقاف التشغيل» في Windows ليس إعادة التشغيل
إيقاف التشغيل في Windows إغلاق هجين افتراضيّاً، فيُحفَظ النواة وبرامج التشغيل في hiberfil.sys. لماذا لا تُعاد تهيئتها إلا بإعادة التشغيل،...
Time Travel Debugging ── تسجيل الأخطاء التي لا تتكرّر في التطبيقات طويلة التشغيل وإرجاعها
خطأ يظهر مرّة في الشهر لا يترك في تفريغ الانهيار سوى نتيجته. سجّل التنفيذ وأرجعه بـ WinDbg Time Travel Debugging (TTD): TTD.exe والمخزن ا...
لماذا تتعطّل الوسائط ── قواعد وسائط سطر أوامر Windows
في Windows لا توجد مصفوفة وسائط؛ يصل إلى CreateProcess سلسلة واحدة ويتولّى الطرف المستقبل تقسيمها. نشرح قواعد CommandLineToArgvW وCRT و.N...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
لأن موضع ملفات DLL وطريقة تحميلها يتّصلان مباشرة بإمكان إقلاع تطبيق Windows وبشكل التوزيع وبقابلية إعادة الإنتاج عند حدوث مشكلة، فمن المفيد تناولهما في سياق تطوير تطبيقات Windows.
الاستشارات التقنية ومراجعة التصميم
حلّ أسماء ملفات DLL نقطة تتقاطع فيها التحقيق في الأخطاء والنقل وتفادي الثغرات وتصميم التوزيع، وهو موضوع يسهل ترتيبه ضمن مراجعة التصميم أو الاستشارة التقنية.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- إذا حدّدت اسم DLL في LoadLibrary، بأيّ ترتيب يبحث Windows؟
- قبل البحث المتتابع في نظام الملفّات تُقيَّم قواعد سابقة. تحديداً تفعل أوّلاً DLL redirection وAPI set وSxS manifest redirection وloaded-module list وKnown DLLs، ثم يدخل البحث في نظام الملفّات مثل مجلّد التطبيق وSystem32 ومجلّد Windows وcurrent folder وPATH. في الحالة الافتراضيّة مع تفعيل safe DLL search mode يُوضَع current folder في الخلف كثيراً. كما يختلف تفكير ترتيب البحث نفسه بين packaged app وunpackaged app.
- إذا حمّلت DLL بمسار كامل، هل تُقرأ DLLات التبعيّة أيضاً من المجلّد نفسه؟
- ليس بالضرورة. حتى إن حمّلت أوّل DLL بمسار كامل، تُبحَث DLLات التي يعتمد عليها ذلك الـ DLL باسم الوحدة فقط، فقد تُحَلّ من موضع آخر. إن وُجد هذا الوهم عمل الأمر في بيئة التطوير بينما حُلّت DLL تبعيّة أخرى في جهة التوزيع، فصار العطل معتمداً على البيئة ومزعجاً. إذا أردت التحكّم بما يشمل DLLات التبعيّة فصرّح بفضاء البحث بأعلام LOAD_LIBRARY_SEARCH_* في LoadLibraryEx مثلاً.
- لماذا لا ينبغي استخدام SetDllDirectory؟
- لأنّه لا يغيّر ترتيب البحث فحسب، بل يملك سلوكاً يعطّل safe DLL search mode فعليّاً. حتى إن أردت إضافة مجلّد واحد خاصّ بالتطبيق، تغيّر فضاء البحث كلّه بما في ذلك التعامل مع current folder، وقد ينعكس أثره أمنيّاً. ثمّ إن استُدعي في العمليّة الأب فقد يصل الأثر إلى ترتيب بحث العمليّة الابن. الأأمن الميل إلى SetDefaultDllDirectories وAddDllDirectory وأعلام بحث LoadLibraryEx.
- كيف نمنع DLL hijacking (هجوم DLL preloading)؟
- يجمع الهجوم بين التحميل الديناميكي بلا مسار كامل ومجلّد بحث يستطيع المهاجم السيطرة عليه، لذلك نضيّق الاثنين. تحديداً نقلّل LoadLibrary باسم عارٍ، ونستخدم المسار الكامل إن لزم، ونضيّق مسار البحث الافتراضي للعمليّة بـ SetDefaultDllDirectories، ونضيف بـ AddDllDirectory المجلّدات المسموح بها فقط، ونتجنّب current folder والاعتماد غير الحذر على PATH. خصوصاً خطر أن تحمل عمليّة تعمل بامتياز المدير مسار بحث غامضاً، فتُنفَّذ DLL خبيثة بامتياز تلك العمليّة.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.