B.M.AbdelAziZ
25-02-2009, 08:17 AM
بسم الله الرحمن الرحيم
بإختصار API hook هو التعديل بحيث تستدعى دالتنا بدلا من دالة API
مثال الدالة TerminateProcessمن Help نجد:
BOOL TerminateProcess(
HANDLE hProcess, // handle to the process
UINT uExitCode // exit code for the process
);
لتعريفها في Delphi مثلا بإسم جديد HookeD تكون من الشكل:
Var
HookeD: function(hProcess: THandle; uExitCode: UINT): BOOL; stdcall;
الدالة موجدة في kernel32.dll
نحصل على مكانها بإستخدام الدالة GetProcAddress
لإستخدام GetProcAddress نحتاج handle مقبض المكتبة Kernel32.dll
الذي نحصل عليه بدوره بإستخدام الدالة GetModuleHandle
طبعا إن كانت المكتبة غير موجود بالذاكرة يجب تحميلها بإستخدام الدالة LoadLibrary
h:= GetModuleHandle('kernel32.dll');
if h = 0 then
h:= LoadLibrary('kernel32.dll');
@HookED:= GetProcAddress(h, 'TerminateProcess');
حيث نحصل على عنوان الدالة في HookeD
و h مقبض handle في دلفي ب THandle
نعرف دالتا الجديدة NewOne مثل الدالة القديمة تماما:
function NewOne(hProcess: THandle; uExitCode: UINT): BOOL; stdcall;
begin
//
end;
حان وقت العمل، نغير في برنامجنا في الذاكرة بإستخدام الدالة WriteProcessMemory
طبعا قبل التغيير نحفظ ما نريد تغييره في متغير OldBytes بقرائتها بإستخدام الدالة ReadProcessMemory
لكن ماذا نغير ؟
نغير ما نريد، لكن ماذا نريد ؟
نريد أن تنفذ دالتنا NewOne بدلا من الدالة HookeD التي تمثل الدالة TerminateProcess في ذاكرة برنامجنا
في الأخير كل من الدوال ما هي إلا أوامر في موضع ما في الذاكرة
ولكي تنفذ دالتنا يكفي تغيير الأوامر الأولى بأمر قفز لدالتنا أي JMP بلغة التجميع
و يمكننا أيضا أن نستعمل أمر إستدعاء Call
لكن في حالتنا نستخدم Retرجوع و لنقل لسبب ما لا يهما الأن...
يصبح ما نريد كتابته بالشكل:
Push XXXX
Ret
لمن لا يعرف لغة التجميع، تعني ضع XXXX في stack
ثم عد من حيث أتيت Ret
ْXXXX هو عنوان ما في الذاكرة، أين توجد دالتنا الجديدة
وهنا أكتشث ما يسمى Pointer مؤشر... أمزح
عند تجميع الأمرين فوق يصبحان
68
ْXXXX
C3
مكتوبة بنظام 16 أو Hex
في المجموع 6 حروف أو bytes
68 أمر Push
XXXX مؤشر دالتنا الجديدة
C3 أمر Ret
هممم...
نغير 6 حروف
وقبل تغييرها نحفظ 6 حروف القديمة لإرجاعها فيما بعد، حتى يكون العمل نظيف
لتسهيل الأمور قليلا ننشئ Typeنمط جديد للحروف نسميه مثلا T6Bytes
type
T6Bytes = packed record
P: Byte;
addr: Pointer;
R: Byte;
end;
Var
OldBytes, NewBytes: T6Bytes;
في المتغير NewBytes
P تساوي 69
R تساوي C3
addr مؤشر لا نعرفه إلا وقت التنفيذ
نستخدم الدوال كما هو مكتوب فوق:
if ReadProcessMemory(DWORD(-1),
@HookeD, @OldBytes, SizeOf(OldBytes), Dw) then
begin
NewBytes.P:= $68;
NewBytes.R:= $C3;
NewBytes.addr:= @NewOne;
WriteProcessMemory(DWORD(-1),
@HookeD, @NewBytes, SizeOf(NewBytes), Dw);
end;
نجرب إستدعاء الدالة TerminateProcess في زر مثلا Button نكتب :
TerminateProcess(DWORD(-1), 0);
الأن نكتب الكود السابق الذى يعمل Hook في زر ثاني
ننفذ البرنامج و نجرب أو لا بالضغط على الزر الأول عندها يتوقف عمل البرنامج
أي تم تنفيذ الدالة الأصلية TerminateProcess
الأن نعيد تنفيذ البرنامج لكن نظغط على الزر الثاني لعمل Hook ثم نضغط الزر الأول
لا يحدث شيئ لأنه تم تنفيذ دالتنا الجديدة NewOne و هذا هو المطلوب !
طبعا الدالة NewOne لا تعمل شيئ لأننا لم نكتب بها أي شيئ
يمكننا مثلا عرض رسالة تطلب من المستخدم إن أراد الخروج و في حالة موافته نستدعى الدالة الأصلية TerminateProcess و قبل ذلك نعيد الأمور كما كانت :
if MessageBox(Form1.Handle, 'Close ?', 'API HooK',
MB_YESNO or MB_ICONQUESTION or MB_DEFBUTTON2) = IDYES then
begin
WriteProcessMemory(DWORD(-1), @HookeD, @OldBytes, SizeOf(OldBytes), Dw);
HookeD(hProcess, uExitCode);
end;
وهنا إنتهى هذا المثال البسيط،
بالملف المرفق هذا المثال بدلفي و ملف تنفيذي لمن يريد تجربته
ملاحظة: هذا شرح بسيط من صفر لأصفار..مثله! كتبته من قبل في ArabTeam2000-forum (http://www.arabteam2000-forum.com/index.php?showtopic=95621&st=0&start=0)
بإختصار API hook هو التعديل بحيث تستدعى دالتنا بدلا من دالة API
مثال الدالة TerminateProcessمن Help نجد:
BOOL TerminateProcess(
HANDLE hProcess, // handle to the process
UINT uExitCode // exit code for the process
);
لتعريفها في Delphi مثلا بإسم جديد HookeD تكون من الشكل:
Var
HookeD: function(hProcess: THandle; uExitCode: UINT): BOOL; stdcall;
الدالة موجدة في kernel32.dll
نحصل على مكانها بإستخدام الدالة GetProcAddress
لإستخدام GetProcAddress نحتاج handle مقبض المكتبة Kernel32.dll
الذي نحصل عليه بدوره بإستخدام الدالة GetModuleHandle
طبعا إن كانت المكتبة غير موجود بالذاكرة يجب تحميلها بإستخدام الدالة LoadLibrary
h:= GetModuleHandle('kernel32.dll');
if h = 0 then
h:= LoadLibrary('kernel32.dll');
@HookED:= GetProcAddress(h, 'TerminateProcess');
حيث نحصل على عنوان الدالة في HookeD
و h مقبض handle في دلفي ب THandle
نعرف دالتا الجديدة NewOne مثل الدالة القديمة تماما:
function NewOne(hProcess: THandle; uExitCode: UINT): BOOL; stdcall;
begin
//
end;
حان وقت العمل، نغير في برنامجنا في الذاكرة بإستخدام الدالة WriteProcessMemory
طبعا قبل التغيير نحفظ ما نريد تغييره في متغير OldBytes بقرائتها بإستخدام الدالة ReadProcessMemory
لكن ماذا نغير ؟
نغير ما نريد، لكن ماذا نريد ؟
نريد أن تنفذ دالتنا NewOne بدلا من الدالة HookeD التي تمثل الدالة TerminateProcess في ذاكرة برنامجنا
في الأخير كل من الدوال ما هي إلا أوامر في موضع ما في الذاكرة
ولكي تنفذ دالتنا يكفي تغيير الأوامر الأولى بأمر قفز لدالتنا أي JMP بلغة التجميع
و يمكننا أيضا أن نستعمل أمر إستدعاء Call
لكن في حالتنا نستخدم Retرجوع و لنقل لسبب ما لا يهما الأن...
يصبح ما نريد كتابته بالشكل:
Push XXXX
Ret
لمن لا يعرف لغة التجميع، تعني ضع XXXX في stack
ثم عد من حيث أتيت Ret
ْXXXX هو عنوان ما في الذاكرة، أين توجد دالتنا الجديدة
وهنا أكتشث ما يسمى Pointer مؤشر... أمزح
عند تجميع الأمرين فوق يصبحان
68
ْXXXX
C3
مكتوبة بنظام 16 أو Hex
في المجموع 6 حروف أو bytes
68 أمر Push
XXXX مؤشر دالتنا الجديدة
C3 أمر Ret
هممم...
نغير 6 حروف
وقبل تغييرها نحفظ 6 حروف القديمة لإرجاعها فيما بعد، حتى يكون العمل نظيف
لتسهيل الأمور قليلا ننشئ Typeنمط جديد للحروف نسميه مثلا T6Bytes
type
T6Bytes = packed record
P: Byte;
addr: Pointer;
R: Byte;
end;
Var
OldBytes, NewBytes: T6Bytes;
في المتغير NewBytes
P تساوي 69
R تساوي C3
addr مؤشر لا نعرفه إلا وقت التنفيذ
نستخدم الدوال كما هو مكتوب فوق:
if ReadProcessMemory(DWORD(-1),
@HookeD, @OldBytes, SizeOf(OldBytes), Dw) then
begin
NewBytes.P:= $68;
NewBytes.R:= $C3;
NewBytes.addr:= @NewOne;
WriteProcessMemory(DWORD(-1),
@HookeD, @NewBytes, SizeOf(NewBytes), Dw);
end;
نجرب إستدعاء الدالة TerminateProcess في زر مثلا Button نكتب :
TerminateProcess(DWORD(-1), 0);
الأن نكتب الكود السابق الذى يعمل Hook في زر ثاني
ننفذ البرنامج و نجرب أو لا بالضغط على الزر الأول عندها يتوقف عمل البرنامج
أي تم تنفيذ الدالة الأصلية TerminateProcess
الأن نعيد تنفيذ البرنامج لكن نظغط على الزر الثاني لعمل Hook ثم نضغط الزر الأول
لا يحدث شيئ لأنه تم تنفيذ دالتنا الجديدة NewOne و هذا هو المطلوب !
طبعا الدالة NewOne لا تعمل شيئ لأننا لم نكتب بها أي شيئ
يمكننا مثلا عرض رسالة تطلب من المستخدم إن أراد الخروج و في حالة موافته نستدعى الدالة الأصلية TerminateProcess و قبل ذلك نعيد الأمور كما كانت :
if MessageBox(Form1.Handle, 'Close ?', 'API HooK',
MB_YESNO or MB_ICONQUESTION or MB_DEFBUTTON2) = IDYES then
begin
WriteProcessMemory(DWORD(-1), @HookeD, @OldBytes, SizeOf(OldBytes), Dw);
HookeD(hProcess, uExitCode);
end;
وهنا إنتهى هذا المثال البسيط،
بالملف المرفق هذا المثال بدلفي و ملف تنفيذي لمن يريد تجربته
ملاحظة: هذا شرح بسيط من صفر لأصفار..مثله! كتبته من قبل في ArabTeam2000-forum (http://www.arabteam2000-forum.com/index.php?showtopic=95621&st=0&start=0)