
डायनामिक विंडोज एपीआई रिज़ॉल्वर और अनहुकर जो हुक किए गए फ़ंक्शंस (IAT, EAT, इनलाइन पैच) का पता लगाता है और उन्हें पुनर्स्थापित करता है ताकि रेड टीम मैलवेयर से अनमॉनिटर्ड सिस्टम कॉल्स को आमंत्रित किया जा सके।
घुसपैठिए AVs और EDRs के युग में जो चल रहे प्रोसेसों में उन्नत ऑप्टिक्स आवश्यकताओं के लिए हॉट-पैच प्रस्तुत करते हैं, आधुनिक प्रतिद्वंद्वियों के पास इन पहरेदारों से फिसलने के लिए एक मजबूत उपकरण होना चाहिए। प्रस्तावित डायनामिक इम्पोर्ट्स रिज़ॉल्वर का कार्यान्वयन जो फ्लाई पर उपयोग किए गए फ़ंक्शंस को अनहुक करने में सक्षम होगा, प्रतिद्वंद्वी लचीलापन प्रयासों को मजबूत करने की दिशा में एक और कदम है।
मैं यहाँ जो समाधान प्रस्तावित कर रहा हूँ वह है लिंकर-रिज़ॉल्वड WinAPI इम्पोर्ट्स (विशेष रूप से Import Address Table) का उपयोग करने से हटकर पूरी तरह से डायनामिक दृष्टिकोण अपनाना, जो केवल डायनामिक तरीके से इम्पोर्ट्स को हल करने पर जोर देता है। ऐसा डायनामिक रिज़ॉल्वर ऑपरेटर की ओर से किसी भी मार्गदर्शन के बिना पृष्ठभूमि में होने वाले अनहुकिंग लॉजिक से सुसज्जित किया जा सकता है।
यह सुनिश्चित करने का तरीका है कि आप MessageBoxW को अनहुक और अनमॉनिटर्ड कॉल करें:
RESOLVE(user32, MessageBoxW);
_MessageBoxW(0, L"Look Ma! I'm unhooked!", L"Third - Unhooked", 0);
सारा जादू RESOLVE मैक्रो परिभाषा के भीतर होता है, जो _MessageBoxW नामक ImportResolver<T> ऑब्जेक्ट का निर्माण करता है।

यहाँ बताया गया है कि UnhookMe उदाहरण कैसे काम करता है:
MessageBoxW प्रस्तुत करता है जो हुकिंग के अधीन नहीं है।MessageBoxW के प्रस्तावना को हुक करते हैं ताकि यह हमेशा 0 लौटाए बिना अपना संदेश प्रदर्शित किए।UnhookingImportResolver रिज़ॉल्वर का उपयोग करके MessageBoxW को डायनामिक रूप से हल करते हैं, जो लागू प्रस्तावना पैच का पता लगाएगा और मूल बाइट्स को पुनर्स्थापित करेगा, प्रभावी रूप से MessageBoxW की कार्यक्षमता को अनहुक करेगा।मैसेज बॉक्स पॉप करने के बीच में, कंसोल के stdout पर ये लॉग लाइनें प्रिंट होती हैं:
[~] Resolved symbol kernel32.dll!CreateFileA
[~] Resolved symbol kernel32.dll!ReadProcessMemory
[~] Resolved symbol kernel32.dll!MapViewOfFile
[~] Resolved symbol kernel32.dll!VirtualProtectEx
[#] Found trampoline hook in symbol: MessageBoxW . Restored original bytes from file.
[~] Resolved symbol user32.dll!MessageBoxW
कुल 5 C++ स्रोत कोड/हेडर फ़ाइलें हैं जिन्हें आपके समाधान को शामिल करने की आवश्यकता है। हालाँकि, आपकी मुख्य प्रोग्राम फ़ाइल को केवल दो आवश्यक हेडर शामिल करने की आवश्यकता है, जैसा कि नीचे विस्तार से बताया गया है।
resolver.h - एक हेडर जिसमें UnhookingImportResolver कार्यान्वयन और उपयोगी मैक्रो परिभाषाएँ शामिल हैंresolver.cpp - वैश्विक विकल्पों के साथ स्रोत कोड परिभाषितusings.h - एक बड़ी और बदसूरत हेडर फ़ाइल जिसमें सामान्यतः उपयोग किए जाने वाले WinAPIs के लिए दर्जनों using प्रकार परिभाषाएँ हैंPE.cpp - कस्टम PE पार्सर स्रोत कोड फ़ाइलPE.h - कस्टम PE पार्सर हेडर फ़ाइलआपके प्रोग्राम को केवल दो हेडर शामिल करने की आवश्यकता होगी:
#include "usings.h"
#include "resolver.h"
कुछ वैश्विक विकल्प हैं जिन्हें बदला जा सकता है, जो रिज़ॉल्वर के काम करने या उसकी गतिविधि रिपोर्ट करने के तरीके को प्रभावित करते हैं। ये resolver.cpp फ़ाइल की शुरुआत में परिभाषित हैं:
Resolver के वैश्विक विकल्प:
globalQuietOption - यदि आप कोई आउटपुट नहीं चाहते हैं तो true पर सेट करेंglobalVerboseOption - यदि आप विस्तृत वर्बोज़ आउटपुट चाहते हैं तो true पर सेट करेंglobalAntiSplicingOption - यदि हुक किए गए हैं तो हल किए गए फ़ंक्शंस को अनहुक करेंglobalLogFilePath - आउटपुट लॉग लाइनों को कहाँ रीडायरेक्ट करना है। यदि खाली है, तो stdout चुनेंbool globalQuietOption = false;
bool globalVerboseOption = true;
bool globalAntiSplicingOption = true;
wchar_t globalLogFilePath[MAX_PATH] = L"";
Resolver का उपयोग करने के लिए, एक फ़ंक्शन पॉइंटर प्रकार को पहले सख्त रूप में using स्टेटमेंट के साथ घोषित किया जाना चाहिए:
using fn_FunctionName = ReturnType WINAPI (
ParamType1 paramName1,
...,
ParamTypeN paramNameN,
);
यह रिपॉजिटरी usings.h हेडर फ़ाइल के साथ आती है जिसमें दर्जनों लोकप्रिय Windows APIs के लिए पूर्वनिर्धारित using प्रकार हैं।
FunctionName उस WinAPI के अनुरूप होगा जिसे हम ImportResolver द्वारा हल करना चाहते हैं और उस फ़ंक्शन पॉइंटर को WINAPI कॉल कन्वेंशन (x86 पर __stdcall और x64 पर __fastcall) के रूप में चिह्नित किया जाना चाहिए। ReturnType को WINAPI प्रकार संशोधक से पहले होना चाहिए।
फ़ंक्शन पॉइंटर प्रकार ऊपर निर्दिष्ट के अनुसार परिभाषित होने के बाद, हम इसे निम्नलिखित तरीके से उपयोग करने में सक्षम होंगे:
RESOLVE(libraryName, FunctionName);
ReturnType output = _FunctionName(param1, ..., paramN);
मैक्रो RESOLVE ImportResolver टेम्पलेटेड ऑब्जेक्ट को इंस्टैंशिएट करने और निर्दिष्ट लाइब्रेरी के नाम को समायोजित करने का ध्यान रखता है।
Resolver विभिन्न परिस्थितियों में उपयोग में आसान कंस्ट्रक्टर आह्वान प्रदान करने वाले कई और मैक्रो परिभाषाएँ प्रस्तुत करता है:
#define RESOLVE(mod, func) RESOLVE_PARAMETERIZED(mod, func, ::globalVerboseOption, ::globalAntiSplicingOption)
#define RESOLVE_NO_UNHOOK(mod, func) RESOLVE_PARAMETERIZED(mod, func, ::globalVerboseOption, false)
#define RESOLVE_VERBOSE_UNHOOK(mod, func) RESOLVE_PARAMETERIZED(mod, func, true, true)
#define RESOLVE_VERBOSE_NOUNHOOK(mod, func) RESOLVE_PARAMETERIZED(mod, func, true, false)
#define RESOLVE_NOVERBOSE_UNHOOK(mod, func) RESOLVE_PARAMETERIZED(mod, func, false, true)
#define RESOLVE_NOVERBOSE_NOUNHOOK(mod, func) RESOLVE_PARAMETERIZED(mod, func, false, false)
Resolver का कंस्ट्रक्टर:
template<typename Ret, typename ...Args>
ImportResolver<Ret WINAPI(Args...)>(
std::string dllName,
std::string funcName,
bool _verbose = false,
bool _unhook = false,
bool *_wasItHooked = nullptr
)
अंतर्निहित रिज़ॉल्वर कस्टम PE हेडर पार्सर का लाभ उठाता है, जो प्रत्येक संदर्भित DLL मॉड्यूल को उनके निर्यात को मैप करने और उस मॉड्यूल के PE हेडर की अखंडता के साथ-साथ संदर्भित फ़ंक्शन के स्टब बाइट्स की अखंडता को सत्यापित करने के लिए संसाधित करता है।
विचार इस प्रकार है:
पहले हम LoadLibrary जारी करते हैं ताकि उपयोगकर्ता द्वारा संदर्भित लाइब्रेरी (जो RESOLVE मैक्रो के पहले पैरामीटर के रूप में निर्दिष्ट है) को लोड किया जा सके, यदि इसे GetModuleHandle के माध्यम से एक्सेस नहीं किया जा सकता है।
फिर हम लोड/संदर्भित लाइब्रेरी के PE हेडर को संसाधित करते हैं, इसके निर्यात को मैप करते हैं, निर्यात पतों की सरणी प्राप्त करते हैं और साथ ही क्रॉस-सत्यापन के लिए इन पतों की गणना स्वयं करते हैं।
यदि DLL के Export Address Table में परिभाषित रूटीन का पता हमारी अपेक्षा के अनुरूप नहीं है, तो निर्यात को EAT हुक माना जाता है। यही बात तब भी लागू होती है जब उस फ़ंक्शन के लिए हमारी एक्जीक्यूटेबल Import Address Table (IAT) प्रविष्टि बदल दी गई है और अब DLL के कोड अनुभाग में सही स्थान पर इंगित नहीं करती है - तब फ़ंक्शन को IAT हुक माना जाता है।
यह मानते हुए कि अब तक कोई हुक नहीं पाया गया है, हम फ़ंक्शन के प्रस्तावना के पहले N बाइट्स प्राप्त करते हैं और उनकी तुलना डिस्क पर फ़ाइल में संग्रहीत DLL से करते हैं। यदि मेमोरी से प्राप्त बाइट्स और फ़ाइल से प्राप्त बाइट्स के बीच विसंगति है - तो हम मानते हैं कि फ़ंक्शन को इनलाइन पैच (हॉट-पैच) किया गया था।
यदि फ़ंक्शन को हुक माना गया था - तो हम मूल निर्यात का पता (जिसकी हमने स्वयं गणना की है) लौटाते हैं और/या प्रविष्टि को अनहुक करते हैं। यदि पैच बाइट्स मौजूद थे, तो हम उन्हें पुनर्स्थापित करेंगे।
अंत में, रिज़ॉल्वर के प्रदर्शन प्रभाव को अनुकूलित करने के लिए - हम सभी लोड किए गए मॉड्यूल के इमेजबेस और हल किए गए फ़ंक्शन पतों को कैश करते हैं और बाद के हिट के दौरान उन्हें कैश (जो std::map है) से लौटाते हैं।
ऐसे डायनामिकली-अनहुकिंग रिज़ॉल्वर के सामने आने वाली समस्याओं में से एक फ़ॉरवर्डेड APIs (एक DLL में Export thunk हो सकता है जो कहता है कि यह फ़ंक्शन इस मॉड्यूल में लागू नहीं है, लेकिन दूसरे में है) को ट्रैवर्स करने की समस्या है - हालाँकि इस कार्यान्वयन में इसके लिए समर्थन है, कभी-कभी यह इसके ट्रैवर्सल लॉजिक को तोड़ देता है।
यह और अन्य प्रोजेक्ट रातों की नींद हराम करने और बहुत सारी मेहनत का परिणाम हैं। यदि आपको मेरा काम पसंद है और आप सराहना करते हैं कि मैं हमेशा समुदाय को वापस देता हूँ, तो मेरे लिए एक कॉफ़ी खरीदने पर विचार करें (या बेहतर होगा एक बीयर) बस धन्यवाद कहने के लिए! 💪
Mariusz Banach / mgeeky, 21
<mb [at] binary-offensive.com>
(https://github.com/mgeeky)