6- Trampoline Hook, 32-bit, 64-bit

بسم الله الرحمن الرحيم

السلام عليكم ورحمة الله وبركاته

اللهم علمنا ما ينفعنا، وانفعنا بما علمتنا، وزدنا علماً

==========> Trampoline Hook, 32-bit, 64-bit <==========

لقد قمنا سابقا بشرح الـDetours Hook ، وقد قلنا بانه وفي حالة استدعاء الـAPI نفسها من الـFilterFun فإنه سوف يحدث Infinite LOOP ، طبعا لانه عند استدعاء الـAPI يقوم بالقفز الى FilterFun واذا تم استدعاء الـAPI من FilterFun مرة اخرى سوف يؤدي الى Infinite LOOP 

ولكن نحن بحاجة احيانا الى فلترة الـParameters مع اعادة استدعاء الـAPI نفسها وبذلك نلجأ الى الـTrampoline Hook
اي ان الفرق بين الـTrampoline و detours هو امكانية إعادة الاستدعاء من FilterFun

والتعليمات المستخدمة هي JMP  كما تلكمنا سابقا وبدون اي تغيير ، اي ان الفرق بسيط 

يمكن ان يتدابر للبعض ان يعمل Hook - UnHook باستخدام الـDetours
اي انه اذا اراد استدعاء الـAPI نفسها من داخل FilterFun قام بارجاع الـبايتات الاصلية ثم قام باستدعاء الـAPI ثم قام بكتابة قفزة الـJMP مرة اخرى  
صحيح بانها تنجح ، لكن هذا خطا ، يوجد احتمالية الفشل
في حال استدعاء الـAPI اكثر من مرة ، خاصة اذا كانت الـAPI تاخذ وقت لتنفيذها ، فيمكن بعد ارجاع البايتات الاصلية ، استدعائها اكثر من مرة من قبل البرنامج المنصب عليه الـHook ، وذلك يمكن ان يتم قبل كتابة الـJMP  وهذا ما لا نريده !

==========> الآلية والفرق Trampoline Hook <==========

لقد قمنا في الـDetours بالكتابة على العنوان مباشرة 
اي OverWritten ، ولكن الذي يحدث بالـTrampoline وكمثال على MessageBoxA كالتالي ...





الصورة تتكلم ، يوجد MainFun قام باستدعاء MessageBoxA API لكن نحن وضعنا قفزة الى FilterFun الخاصة بنا ، ولكن قبل وضع القفزة قمنا بنسخ البايتات ( التعليمات ) الاصلية وحجز مكان في الذاكرة وهو FreeSpace ووضع البايتات ( التعليمات ) الاصلية فيه ثم من MessageBoxA تم القفز الى FilterFun ثم بعد الانتهاء من التنقية اردنا استدعاء الـMessageBoxA الاصلية فقمنا باستدعاء البايتات ( التعليمات ) القديمة ( التي كتب عليها قفزة الـJMP  ، وهي  ... OverWritten ... ) ، وعند تنفيذ التعليمات ( البايتات ) الاصلية تم الانتقال مرة اخرى الى MessageBoxA ولكن ما بعد القفزة ( اي XXAddress ) حتى تتكون تعليمات الاسمبل بشكل كامل 
وهذا هو الـTrampoline ، ونلاحظ من الصورة باننا ايضا نتحكم بالقيمة الراجعة حتى ولو قمنا باستدعاء الـAPI فجيب ارجاع قيمتها اذا اردنا ذلك ولن ترجع قيمتها تلقائيا

اذا ما علينا القيام به بشكل اساسي هو 
1- نسخ البايتات ( التعليمات ) الاصلية 
2- تنصيب الهوك على API المطلوبة
3- حجز مكان في الذاكرة ووضع البايتات الاصلية فيه اضافة الى كود القفزة الى الادرس ما بعد قفزة الى FilterFun  ( اي XXAddress )
4- تجهيز الـAPI لامكانية استدعائها مرة اخرى

ولكن ، نلقي النظر على النقطة الاولى ، وهي نسخ البايتات الاصلية ، المشكلة يمكن ان تكون التعليمة الواحدة ذات طول 7Byte وتعليمة الـJMP 5BYTE مثلا ، فاذا قمنا بنسخ 5 Bytes سوف يؤدي الى ضياع التعليمة ، اذن نحتاج الى نسخ التعليمة كاملة ، ولكن كيف نعرف ما طول التعليمة التي تساوي او  تكون اكبر من حجم JMP ، لننسخ التعليمة كاملة ! 
وبالواقع لهذه السبب سوف نستخدم مكتبة خارجية باسم LDE64 ، وهذه المكتبة تحتوي على Fun نمرر له الادرس المطلوب ليعطينا طول التعليمة في ذلك الـAddress وبذلك تحل المشكلة ان شاء الله

==========> برمجة Trampoline Hook <==========

بالبداية عليك تحميل مكتبات الـLDE64
LDE64.lib x86
LDE64.lib x64

الان نحتاج الى تعريف الـAPI بوساطة typedef لنقوم باستدعائها مرة اخرى وسوف تتضح الامور من خلال الـCode
سوف نقوم بالتطبيق على MessageBoxA

قما قلنا سابقا عند الذهاب الى الـDeclaration نجد التالي ...

الان نعرف الـFilterFun و الـAPI بوساطة typedef فيصبح الكود النهائي ل32 و 64 كالتالي ...

#include <Windows.h>

typedef int (WINAPI* RealMsgBox)(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);

RealMsgBox ReCall;
int WINAPI FilterMsgBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

BOOL WINAPI DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) { 
switch (Reason) { 
case DLL_PROCESS_ATTACH: 
}
return 1;
}

int WINAPI FilterMsgBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
// Filtering Code ...
}

ونلاحظ بانني قمت بتعريف متغير ReCall وهو سوف يمثل الـAPI الجديدة ، ذات العنوان الجديد الذي سوف يتم حجزه لكتابة البايتات القديمة

الان كود الـHOOK

C++ CODE 32-bit
وبعد اضافة المكتبة الى المشروع



#pragma comment(lib, "LDE64")
extern "C" DWORD __stdcall LDE(const DWORD lpData, unsigned int ProcType);

BYTE JMP[5] = { 0xE9, 0x0, 0x0, 0x0, 0x0 }; // __asm { JMP Address_4Bytes }
#define BuffSize sizeof(JMP)

DWORD BytesLen(DWORD Address, int ASMLen);
DWORD Hook_Trp_x86(char LibName[], char API_Name[], LPVOID NewFun) {
DWORD OrgFun = (DWORD)GetProcAddress(GetModuleHandleA(LibName), API_Name);
if (OrgFun == NULL) return 0;

DWORD JMP_GAP, OldProtect, BtLen, NewBuff;
BYTE* OverWritten;

JMP_GAP = (DWORD)NewFun - OrgFun - BuffSize;
memcpy(&JMP[1], &JMP_GAP, 4);
VirtualProtect((LPVOID)OrgFun, BuffSize, PAGE_EXECUTE_READWRITE, &OldProtect);
BtLen = BytesLen(OrgFun, BuffSize);
OverWritten = new BYTE[BtLen];
memcpy(OverWritten, (LPVOID)OrgFun, BtLen);
memcpy((LPVOID)OrgFun, JMP, BuffSize);

VirtualProtect((LPVOID)OrgFun, BuffSize, OldProtect, &OldProtect);
NewBuff = (DWORD)VirtualAlloc(NULL, BtLen + BuffSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
JMP_GAP = (OrgFun + BtLen) - (NewBuff + BtLen) - BuffSize;
memcpy(&JMP[1], &JMP_GAP, 4);
memcpy((LPVOID)NewBuff, OverWritten, BtLen);
memcpy((LPVOID)(NewBuff + BtLen), JMP, BuffSize);
return NewBuff;
}

DWORD BytesLen(DWORD Address, int ASMLen) {
DWORD num = 0;
while (1) {
if (num < ASMLen) num += LDE(Address + num, 0);
else return num;
}
}

نلاحظ ، قمنا بتعريف واستيراد الـLDE Function من مكتبة LDE64 ، وقمنا بتعريف BytesLen Function وهي الـFunction المسؤولة عن استخدام LDE Function ومن خلالها سوف نقوم بجلب البايتات التي سوف نحتاج نسخها قبل كتابة تعليمة الـJMP

نكمل مع Hook_Trp_x86  لديها 3 Parameters ، الاول اسم المكتبة ، والثاني اسم الـAPI والثالث يتم تمرير له الـFilterFun ، وتقوم بارجاع عنوان الـAPI الجديد

وبداية قمنا بجلب عنوان الـAPI عن طريق GetProcAddress API و GetModuleHandle API وقد يسال البعض لماذا استخدمت سابقا الـLoadLibrary API في Detours Hook ، الفرق بينهما بان LoadLibrary API تقوم بعمل Load للمكتبة اذا لم تكن محملة اصلا ضمن الذاكرة ، لكن GetModuleHandle API تقوم بجلب الـHandle لها فقط اذا كانت محملة من قبل الـProcess ، اي اذا كانت موجودة 

نكمل ، قمنا بحساب القفزة الخاصة بالـJMP وتخزينها في JMP_GAP
وبعد ذلك قمنا باستخادم memcpy ( وقمنا بشرحها سابقا ) لتخزين مساحة القفزة في JMP Array
قمنا بتغيير الحماية في عنوان الـAPI المستهدفة الى PAGE_EXECUTE_READWRITE عن طريق VirtualProtect API ( وقد سبق شرحها في الدرس السابق )

بعد ذلك ، استخدمنا BytesLen Function ، وقمنا بتمرير مساحة الـJMP و عنوان الـAPI لتخزين نتيجة عدد البايتات التي نريد نسخها داخل 
BtLen 

اما بالنسبة لـBytesLen 
هي Function تستخدم LDE ، كيفية عملها
بالبداية الـLDE Function 
اول Parameter هو الـAddress
ثاني Parameter هو نظام الـProcess 
32-bit يتم تمرير 0
64-bit يتم تمرير 64

والقيمة الراجعة مساحة اول تعليمة في الـAddress

فاذا كانت مساحة التعليمة اصغر من مساحة الـJMP ، فيتم اضافة مساحة اول تعليمة الى متغير ، ومن بعد ذلك يتم اضافة قيمة المتغير الى الـAddress فيقوم بجلب مساحة التعليمة التي تليها وهكذا ...
الى ان يصبح طول التعليمات اكبر او يساوي طول تعليمة الـJMP كاملة ، وبذلك يتم ارجاع الطول المناسب للنسخ دون ضياع التعليمات


بعد تخزين عدد البايتات التي نريد نسخها في BtLen ، نقوم بتعريف OverWritten على عدد الـBtLen
ثم نقوم باستخدام memcpy بنسخ البايتات من OrgFun اي API الى OverWritten
ثم بعد ذلك نقوم بنسخ تعليمة الـJMP على OrgFun عن طريق memcpy
ثم نقوم بارجاع الحماية كما كانت عن طريق VirtualProtect API
وبذلك ننتهي من تنصيب الهوك وننتقل الى اعداد الـAPI الجديدة

نقوم باستخدام VirtualAlloc API ( وقد سبق شرح VirtualAllocEx API في درس Dll File Injector ، و الفرق بين  VirtualAlloc API و  VirtualAllocEx API بان VirtualAllocEx API تحتوي على HANLDE Parameter والمعنى ان VirtualAlloc API تستخدم لنفس الـProcess وهذا هو الفرق )
وقمنا بحجز ذاكرة بمساحة البايتات التي قمنا بنسخها ومساحة القفزة التي سوف نقفز من خلالها الى العنوان الاصلي
BtLen + BuffSize
طبعا MEM_COMMIT لتخزين 
و صلاحيات PAGE_EXECUTE_READWRITE نريد القراءة وتنفيذ التعليمات والكتابة على العنوان وقمنا بتخزين العنوان الجديد داخل NewBuff
بعد ذلك قمنا مرة اخرى بحساب القفزة ، بين المساحة التي تم حجزها وبين القفزة القديمة ( لاستكمال التعليمات كما في الصورة XXAddress ) وقمنا بتخزينها داخل JMP_GAP
ثم قمنا بكتابة JMP_GAP داخل JMP Array عن طريق memcpy
وبعد ذلك قمنا بكتابة الـOverWritten ( البايتات القديمة ) على NewBuff ( المساحة المحجوزة )
ثم بعد ذلك قمنا بكتابة تعليمة الـJMP بعد الـOverWritten في NewBuff 

وبالنهاية قامت الـFun بارجاع عنوان الـAPI الجديد
الان الاستدعاء يكون كالتالي ...


RealMsgBox ReCall;
DWORD Hook_Trp_x86(char LibName[], char API_Name[], LPVOID NewFun);
int WINAPI FilterMsgBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
BOOL WINAPI DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) { 
switch (Reason) { 
case DLL_PROCESS_ATTACH: 
ReCall = (RealMsgBox)Hook_Trp_x86("User32", "MessageBoxA", FilterMsgBox);
}
return 1;
}

int WINAPI FilterMsgBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
return ReCall(hWnd, "Hooked Successfully", "Not Available", uType);
}





C++ CODE 64-bit
وبعد اضافة مكتبة x64 الى المشروع وعمل الخصائص ليتم عمل Compile على x64


#pragma comment(lib, "LDE64x64")
extern "C" DWORD __fastcall LDE(const DWORD64 lpData, unsigned int ProcType);

BYTE MOV[10] = { 0x48, 0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; // __asm { MOV rax, Address_8Bytes }
BYTE JMP_RAX[2] = { 0xFF, 0xE0 }; // __asm { JMP rax }
#define BuffSizeX64 (sizeof(MOV) + sizeof(JMP_RAX))

DWORD BytesLen_x64(DWORD64 Address, int ASMLen);
DWORD64 Hook_Trp_x64(char LibName[], char API_Name[], LPVOID NewFun) {
DWORD64 OrgFun = (DWORD64)GetProcAddress(GetModuleHandleA(LibName), API_Name);
if (OrgFun == NULL) return 0;

DWORD OldProtect, BtLen;
BYTE* OverWritten;
DWORD64 NewBuff;

memcpy(&MOV[2], &NewFun, 8);
VirtualProtect((LPVOID)OrgFun, BuffSizeX64, PAGE_EXECUTE_READWRITE, &OldProtect);
BtLen = BytesLen_x64(OrgFun, BuffSizeX64);
OverWritten = new BYTE[BtLen];
memcpy(OverWritten, (LPVOID)OrgFun, BtLen);
memcpy((LPVOID)OrgFun, MOV, sizeof(MOV));
memcpy((LPVOID)(OrgFun + sizeof(MOV)), JMP_RAX, sizeof(JMP_RAX));
VirtualProtect((LPVOID)OrgFun, BuffSizeX64, OldProtect, &OldProtect);

NewBuff = (DWORD64)VirtualAlloc(NULL, BtLen + BuffSizeX64, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
OrgFun += BtLen;  
memcpy(&MOV[2], &OrgFun, 8);
memcpy((LPVOID)NewBuff, OverWritten, BtLen);
memcpy((LPVOID)(NewBuff + BtLen), MOV, sizeof(MOV));
memcpy((LPVOID)(NewBuff + BtLen + sizeof(MOV)), JMP_RAX, sizeof(JMP_RAX));

return NewBuff;
}

DWORD BytesLen_x64(DWORD64 Address, int ASMLen) {
DWORD num = 0;
while (1) {
if (num < ASMLen) num += LDE(Address + num, 64);
else return num;
}
}

نلاحظ ، قمنا بتعريف واستيراد الـLDE Function من مكتبة LDE64 ، وقمنا بتعريف BytesLen_x64 Function وهي الـFunction المسؤولة عن استخدام LDE Function ومن خلالها سوف نقوم بجلب البايتات التي سوف نحتاج نسخها قبل كتابة تعليمة الـJMP

نكمل مع Hook_Trp_x64  لديها 3 Parameters ، الاول اسم المكتبة ، والثاني اسم الـAPI والثالث يتم تمرير له الـFilterFun ، وتقوم بارجاع عنوان الـAPI الجديد

وبداية قمنا بجلب عنوان الـAPI عن طريق GetProcAddress API و GetModuleHandle API وقد يسال البعض لماذا استخدمت سابقا الـLoadLibrary API في Detours Hook ، الفرق بينهما بان LoadLibrary API تقوم بعمل Load للمكتبة اذا لم تكن محملة اصلا ضمن الذاكرة ، لكن GetModuleHandle API تقوم بجلب الـHandle لها فقط اذا كانت محملة من قبل الـProcess ، اي اذا كانت موجودة 

وبعد ذلك قمنا باستخادم memcpy ( وقمنا بشرحها سابقا ) لتخزين عنوان الNewFun في MOV Array
قمنا بتغيير الحماية في عنوان الـAPI المستهدفة الى PAGE_EXECUTE_READWRITE عن طريق VirtualProtect API ( وقد سبق شرحها في الدرس السابق )

بعد ذلك ، استخدمنا BytesLen_x64 Function ، وقمنا بتمرير مساحة الـJMPوMOV وعنوان الـAPI لتخزين نتيجة عدد البايتات التي سوف ننسخها داخل 
BtLen 

اما بالنسبة لـBytesLen_x64
هي Function تستخدم LDE ، كيفية عملها
بالبداية الـLDE Function 
اول Parameter هو الـAddress
ثاني Parameter هو نظام الـProcess 
32-bit يتم تمرير 0
64-bit يتم تمرير 64

والقيمة الراجعة مساحة اول تعليمة في الـAddress

فاذا كانت مساحة التعليمة اصغر من مساحة الـJMPوMOV ، فيتم اضافة مساحة اول تعليمة الى متغير ، ومن بعد ذلك يتم اضافة قيمة المتغير الى الـAddress فيقوم بجلب مساحة التعليمة التي تليها وهكذا ...
الى ان يصبح طول التعليمات اكبر او يساوي طول تعليمة الـJMPوMOV كاملة ، وبذلك يتم ارجاع الطول المناسب للنسخ دون ضياع التعليمات


بعد تخزين عدد البايتات التي نريد نسخها في BtLen ، نقوم بتعريف OverWritten على عدد الـBtLen
ثم نقوم باستخدام memcpy بنسخ البايتات من OrgFun اي API الى OverWritten
ثم بعد ذلك نقوم بنسخ تعليمة الـMOV على OrgFun عن طريق memcpy
ثم بعد ذلك نقوم بنسخ تعليمة الـJMP على OrgFun + MOVعن طريق memcpy
ثم نقوم بارجاع الحماية كما كانت عن طريق VirtualProtect API
وبذلك ننتهي من تنصيب الهوك وننتقل الى اعداد الـAPI الجديدة

نقوم باستخدام VirtualAlloc API ( وقد سبق شرح VirtualAllocEx API في درس Dll File Injector ، و الفرق بين  VirtualAlloc API و  VirtualAllocEx API بان VirtualAllocEx API تحتوي على HANLDE Parameter والمعنى ان VirtualAlloc API تستخدم لنفس الـProcess وهذا هو الفرق )
وقمنا بحجز ذاكرة بمساحة البايتات التي قمنا بنسخها ومساحة القفزة التي سوف نقفز من خلالها الى العنوان الاصلي
BtLen + BuffSize
طبعا MEM_COMMIT لتخزين 
و صلاحيات PAGE_EXECUTE_READWRITE نريد القراءة وتنفيذ التعليمات والكتابة على العنوان وقمنا بتخزين العنوان الجديد داخل NewBuff
بعد ذلك نقوم بتحديد العنوان الذي سوف يتم انتقال اليه من الـAPI الجديدة والذي سوف يكون ( العنوان الاصلي + البايتات المنسوخة )
ثم قمنا بكتابة OrgFun داخل MOV Array عن طريق memcpy
وبعد ذلك قمنا بكتابة الـOverWritten ( البايتات القديمة ) على NewBuff ( المساحة المحجوزة )
ثم بعد ذلك قمنا بكتابة الـMOVوJMP على العنوان الجديد ليتم القفز الى الـAPI القديمة

وبالنهاية قامت الـFun بارجاع عنوان الـAPI الجديد
الان الاستدعاء يكون كالتالي ...



RealMsgBox ReCall;
DWORD64 Hook_Trp_x64(char LibName[], char API_Name[], LPVOID NewFun);
int WINAPI FilterMsgBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
BOOL WINAPI DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) { 
switch (Reason) { 
case DLL_PROCESS_ATTACH: 
ReCall = (RealMsgBox)Hook_Trp_x64("User32", "MessageBoxA", FilterMsgBox);
}
return 1;
}

int WINAPI FilterMsgBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
return ReCall(hWnd, "Hooked Successfully", "Not Available", uType);
}




إن أصبت فمن الله ، وإن اخطئت فمني ومن الشيطان 
اترككم في امان الله ورعايته 

والسلام عليكم ورحمة الله وبركاته



DoneByM

عمل هوك ، صنع هوك ، فلترة دالة ، فلترة API ، القضاء على Function ، انشاء حماية ، منع استخدام Function ، كيفية عمل هوك ، كيفية عمل Hook ، هوك Detours ، هوك trampoline ، هوكات IAT , EAT ، Kernel32.dll , System32 , User32.dll , هوك Ring3 ، User Mode Hook, Kernel Mode Hook , الكتابة على الذاكرة 






ليست هناك تعليقات:

إرسال تعليق

يمكنك النقل من المدونة كيف ما تشاء وبدون ذكر المصدر