
ExportHider: توليد جدول التصدير أثناء وقت التشغيل لإخفاء الدوال المُصدَّرة من ملف DLL.
يُنشئ ExportHider قالب DLL بلغة C++ يحتوي على كود stub يسمح لك بإخفاء الدوال المُصدّرة من دليل التصدير (Export Directory) لملف DLL على نظام الملفات. بعد وضع تعريفات الدوال وتجميع الملف، لن ترى دوال التصدير المخفية من خلال عارضي ملفات PE مثل CFF Explorer. ومع ذلك، نظرًا لأن كود stub في القالب يُعيد إنشاء دليل التصدير أثناء وقت التشغيل، فإن استدعاءات GetProcAddress الشرعية ستنفذ بنجاح. تعمل هذه الطريقة فقط مع حالات تحميل DLL الديناميكي أو حالات محمل DLL المخصص.
عادةً، عندما تريد تعريف دالة مُصدّرة في ملفات DLL (بلغة C أو C++)، تضع ببساطة الكلمة المفتاحية __declspec(dllexport) قبل اسم الدالة، أو تنشئ ملف .def. بعد التجميع، يُنشئ المترجم جدولًا محددًا يسمى دليل التصدير لحفظ المعلومات المتعلقة بالدوال المُصدّرة. يمكن رؤية هيكل دليل التصدير أدناه:
عندما تريد عملية استخدام دالة من ملف DLL، يقوم محمل Windows ببساطة بتحليل هذا الهيكل واستيراد الدوال المطلوبة باستخدام المصفوفات AddressOfFunctions و AddressOfNames و AddressOfNameOrdinals.
يشرح منشور مدونة ferreirasc الإجراء المحدد لاستيراد دالة بالتفصيل، ولكن باختصار، بالنسبة لدالة مستوردة بالاسم، يقوم المحمل بالتكرار على مصفوفة (قيم هذه المصفوفة هي مجرد قيم RVA) ويبحث عن الاسم المحدد. بمجرد أن يجد المحمل تطابقًا في الموضع “i”، سيشير إلى الفهرس ith من مصفوفة ويحصل على الترتيب (ordinal) المرتبط بهذه الدالة. بعد الحصول على الترتيب، سيشير المحمل إلى في موضع قيمة الترتيب للحصول أخيرًا على RVA المرتبط بالدالة المستوردة.
AddressOfNamesAddressOfNameOrdinalsAddressOfFunctions
النقطة الحاسمة هنا هي أن جميع عمليات البحث والوصول هذه التي يقوم بها محمل Windows، عند استدعاء LoadLibrary، تتم بعد تعيين ملف DLL إلى مساحة عنوان العملية. أثناء تعيين DLL، يُكتب ملف DLL بأكمله، بما في ذلك رؤوس PE الخاصة به، إلى الذاكرة، ويقوم المحمل بتحليل الرؤوس في الذاكرة للوصول إلى دليل التصدير. هذا يعني أنه إذا كان DLL نفسه قادرًا على الكتابة فوق رؤوس PE الخاصة به في الذاكرة لتغيير عنوان دليل التصدير (ببساطة الفهرس 0 من DataDirectory) بعد الارتباط بالعملية، يمكن للمحمل البحث عن دوال لاستيرادها في دليل تصدير عشوائي بدلاً من دليل التصدير الذي أضافه المترجم.
__ _ _ _
/__\_ ___ __ ___ _ __| |_ /\ /(_) __| | ___ _ __
/_\ \ \/ / '_ \ / _ \| '__| __|/ /_/ / |/ _` |/ _ \ '__|
//__ > <| |_) | (_) | | | |_/ __ /| | (_| | __/ |
\__/ /_/\_\ .__/ \___/|_| \__\/ /_/ |_|\__,_|\___|_|
|_|
by @R0h1rr1m
Usage of C:\Users\Public\DLLDemo\ExportHider.exe:
-h | --help Show the help message.
-i | --input <Input Path> Input path for the list of function names to be hidden. (Mandatory)
-o | --output <Output Path> Output path for the DLL template. (Mandatory)
-n | --name <DLL Name> Name of the DLL for the Export Directory. (Mandatory)
-c | --count <Number of Other Functions> Number of other exported functions that won't be hidden.
فيما يتعلق بمعامل -i | --input <Input Path>، تحتاج إلى تحديد مسار لملف الإدخال الذي يخزن أسماء الدوال المراد إخفاؤها سطرًا سطرًا. سيكون محتوى ملف إدخال مثال كالتالي:
TestFunction1
TestFunction2
TestFunction3
فيما يتعلق بمعامل -c | --count، إذا كنت لا ترغب في إخفاء جميع دوال التصدير الخاصة بك (أي أن هناك بعض الدوال المُصدّرة باستخدام __declspec(dllexport) أو ملف .def، وتظهر في دليل التصدير لملف DLL على نظام الملفات عبر عارضي ملفات PE)، فقط حدد عددها باستخدام هذا المعامل لأن الأداة تحتاج إلى هذه المعلومات عند إجراء حسابات الذاكرة.
إذا كنت ترغب في اللعب بهذه التقنية، هناك نقطتان مثيرتان للاهتمام واجهتهما أثناء تطوير المشروع. قد تحتاج إلى معرفتهما قبل تغيير المشروع:
GetProcAddress باسم. بسبب ذلك، يجب ترتيب أسماء جميع الدوال المُصدّرة، بما في ذلك المخفية، في مصفوفة AddressOfNames. وإلا، فإن الدالة GetProcAddress تُرجع NULL. لذلك، استخدمت خوارزمية Bubble Sort لترتيب أعضاء هذه المصفوفة.AddressOfNames ومصفوفة AddressOfFunctions ومصفوفة DataDirectory وبعض الحقول الأخرى قيمًا في عناوين افتراضية نسبية (RVAs). بالإضافة إلى ذلك، فهي تحتفظ بقيم RVA هذه في حقول بحجم DWORD. عندما تقوم بتخصيص منطقة ذاكرة لدليل تصدير عشوائي جديد باستخدام دوال تخصيص الذاكرة الديناميكية مثل VirtualAlloc أو HeapAlloc، ستكون العناوين المقدمة بعيدة عن منطقة تعيين DLL، وقيم RVA لا تناسب حقول بحجم DWORD، وتواجه تجاوزًا صحيحًا. ولهذا السبب استخدمت متغيرات عامة من نوع مصفوفة بايت في قالب DLL لتلبية متطلبات الذاكرة.كان هدفي الأول أثناء إنشاء هذا المشروع هو إنشاء DLL يفتقد إلى الصادرات (أو يفتقر تمامًا إلى جدول تصدير) ولكن لا يزال يتم تحميله بنجاح بواسطة العملية المنشأة حديثًا. اعتقدت أن هذا السلوك قد يوفر ساحة لعب جديدة لحمولات التحميل الجانبي لـ DLL. ومع ذلك، لم أتمكن من العثور على أي دالة أو طريقة تجعل كود إصلاح دليل التصدير يعمل قبل أن يتحقق محمل Windows من دوال DLL المُصدّرة.

بشكل أكثر تقنية، لاحظت التدفق واستدعاء الدالة التاليين لكل DLL في أقسام الكود المتعلقة بمحمل Windows في NTDLL (جُمعت من ملاحظاتي القديمة؛ قد يكون هناك بعض الأخطاء لأنني لست مهندس عكسي خبير):
1. LdrpMapDll - This is where the DLL is mapped to the Process Address Space. All DLLs that satisfy the DLL name condition
are directly put into the memory, no precheck control for the filesystem version.
2. LdrpSnapModule - This is where the Windows Loader starts to resolve imports. For each import descriptor, it parses the PE
structure, checks the export table, binary searching for the imported function, calculating RVA of that,
and writes its address to the corresponding caller's process' Import Address Table entry during this function.
3. LdrpDoPostSnapWork - If step 2 succeeds for each imported function, memory protections, TLS initialization, CFG enablement
are done in this function.
4. LdrpInitializeNode - If step 3 succeeds, there are module linking functions in this step.
5. LdrpCallTlsInitializers - This is where the TLS callbacks are called before the DllMain function.
6. LdrpCallInitRoutine - This is where the DLLMain itself is called for the first time for the imported DLL. In the original
solution, this function is too late to fix the export table.
عند تشغيل ملف تنفيذي يستورد DLL، إذا لم يتمكن محمل Windows من العثور على اسم الدالة المطلوبة في جدول التصدير لـ DLL، فإنه يوقف التنفيذ ولا ينفذ الدوال التي يتم تنفيذها بعد LdrpSnapModule.
بالنسبة لحالة التحميل الجانبي لـ DLL، لا يمكننا تعديل العملية المستدعية؛ وبالتالي، فإن الفرصة الوحيدة لإصلاح جدول التصدير ديناميكيًا هي إيجاد فرصة لتنفيذ الكود بين دالتي LdrpMapDll و LdrpSnapModule لأن المحمل يتوقف فورًا أثناء فحوصات دالة LdrpSnapModule. لقد جربت استدعاءات TLS، وتحميلات DLL الثانية، والصادرات المعاد توجيهها، وبعض الحلول البديلة الأخرى، لكن لم يساعدني أي منها في العثور على مثل هذا المكان، لذلك وللأسف، لا تعمل هذه الطريقة مباشرةً مع التحميل الجانبي لـ DLL أو DLLs المستوردة بشكل ثابت. إذا اكتشفت حلاً أو حلاً بديلاً قابلاً للتطبيق لهذه المشكلة، سأكون سعيدًا جدًا باستكشافه بشكل أكبر — سواء كان ذلك بمناقشة الفكرة، أو التفكير في النهج معًا، أو تنفيذها. أي مساهمة في هذا الاتجاه ستكون محل تقدير كبير.
إحدى الطرق الممكنة لاستخدام هذه الطريقة في حالات التحميل الجانبي لـ DLL هي تقسيم العمل بأكمله إلى اثنين من DLLs، وهما Proxy DLL و Payload DLL. يأخذ Proxy DLL الاسم الذي تتوقعه الـ EXE، وله صادرات مرئية تلبي فحص الاستيراد الثابت للمحمل، بينما يحتوي Payload DLL على الوظيفة المخفية الحقيقية، ويفتقر إلى الصادرات، ويعيد بناء جدول التصدير الخاص به في DllMain. لا أعتقد أن هذا حل بديل جيد لهذه المشكلة، لذلك لم أقم بتنفيذه.
للاستخدام في الاختبارات الأمنية المصرح بها فقط. إساءة استخدام هذه الأداة ضد أنظمة دون إذن صريح غير قانوني.