تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
Handling Debug Registers by:GamingMasteR
#1
(05-01-2025, 12:51 AM)GamingMasteR كتب : تاريخ المشاركة 2008-08-26 - 07:39 PM 
 
السلام عليكم ورحمة الله وبركاته .
درس اليوم عن كيفية كتابة debugger للتحكم في سير البرنامج و كيفية وضع نقط توقف Hardware Breakpoints .
كما نعلم فإن الـHardware Breakpoints - او سنرمز لها اختصاراً بـHWBP - أقصى عدد يمكن وضعه هو 4 ويتم التعامل معها عبر مسجلات التنقيح Debug Registers وهي 8 مسجلات طول كل منهم 32bit وظيفتهم كالتالي :-
Dr0-Dr1-Dr2-Dr3 : يمثل كل مسجل منهم العنوان الذي سنضع عليه نقطة التوقف .
Dr4-Dr5 : محجوزان ولا يمكن استعمالهم .
Dr6 : يسمى ايضا Debug Status Register و يمكننا من معرفة أي HWBP حدث .. سنتطرق له بالتفصيل لاحقاً .
Dr7 : يسمى بـDebug Control Register وهو الذي يتحكم بالـHWBP (الكل في الكل) ,, تركيبته كالتالي :



[صورة مرفقة: BlpFZDo.png]


أول 8 بتات ( 0 -> 7 ) يمكن تقسيمهم الى اثنين اثنين بحيث كل اثنين يخصان احدى نقط التوقف ,, اذا تم وضع اول بت (L) فأن نقطة التوقف تكون local breakpoint أي يحدث الـDebug exception في الـProcess صاحب الـthread الذي غيرنا Dr7 له .
ثاني بت (G) لوصف نقطة التوقف على انها global breakpoint اي يحدث الـdebug exception اذا توافرت شروطه في جميع الـprocesses .
من R/W0 الى R/W3 :
لتحديد نوع نقطة التوقف ,, هل اذا تم القراءة من العنوان او الكتابة عليه او تنفيذه كتعليمة ,, يمكن وضع قيمته كالتالي (بالبت) :
00 : توقف عند تنفيذ التعليمة التي بالعنوان .
01 : توقف عند الكتابة على العنوان .
10 : لا تستعمل الا في ظروف خاصة (للهاردوير) .
11 : توقف عند القراءة او الكتابة من العنوان .
من LEN0 الى LEN3 :
لتحديد طول البيانات التي ستضع عليها نقطة التوقف ,, يمكن وضع قيمته كالتالي (بالبت) :
00 : لتحديد طول 1 بايت .
01 : لتحديد طول 2 بايت .
10 : تستخدم في بعض المعالجات لتحديد طول 8 بايت (اقرأ الـdocumentations الخاصة بإنتل او AMD لتفاصيل اكثر) .
11 : لتحديد طول 4 بايت .
-----------------------------------------
حسنا لقد تعرفنا بما فيه الكفاية على مسجلات التنقيح ...
الان ما الخطة التي سنعمل عليها ؟
كيف سنقوم بوضع HWBP عملياً على Process معين ؟
كيف سنعرف انه تم الوصول الى نقطة توقف معينة ؟
لتوضيح الفكرة بشكل عملي سنقوم بكتابة debugger صغير يقوم بعمل debug على برنامج crackme كمثال ووضع نقط توقف فيه وعمل اشياء اخرى .
لتنقيح برنامج معين يمكن فعل ذلك عن طريق انشاء العملية بالدالة CreateProcess ووضع بارامتر الاعلام flags == DEBUG_PROCESS ..
كفكرة عامة ما الذي يحدث عند وقوع خطأ في برنامج معين ليس تحت التنقيح ؟
يتم اظهار الـexception او كما يقال exception raise ويتم تسليم اول SEH handler التنفيذ (ليس بدقّة) .. لكن ما يحدث عند وجود البرنامج تحت التنقيح ان الـdebugger هو الذي يستلم الـexecution ونوع الـexception الذي حدث ويقرر اذا كان سيتعامل مع هذا الـexception ام يجعل البرنامج نفسه (debuggee) هو الذي يتعامل مع الـexception .. أرجو ان تكون هذه الفكرة واضحة !
دوال win32api توفر عدة دوال للتعامل مع التنقيح مثل WaitForDebugEvent - GetThreadContext - SetThreadContext - Read/WriteProcessMemory ..
طبعا نحن هنا لسنا بصدد التعرف على تلك الدوال [صورة مرفقة: wink.png] .. يمكنك التطلع عليهم اكثر في مكتبة MSDN .
عند حدوث HWBP يتم رفع exception من النوع EXCEPTION_SINGLE_STEP الى المنقح اذا كان موجوداً .. اذا لابد علينا من عمل handling لهذا الـexception code ..
طيب سؤال اخر .. كيف نعرف اي HWBP من الاربعة هو الذي حدث ؟ لا توجد دوال api معينة لمعرفة ذلك !
صحيح .. نحن لم نتكلم عن Dr6 بالتفصيل بعد كما وعدنا وها قد حان الوقت .. تركيبة هذا المسجل بسيطة جداً كالتالي :



[صورة مرفقة: oPaxfKi.png]


ما يهمنا معرفته هو أول 4 bits وهي تمثل بت لكل HWBP .. اذا تم وضع احدهم فهو يدل على ان الـHWBP المناظر له هو الذي حدث .
حسنا كيف نضع HWBP ؟؟
ستقول لي من مسجلات التنقيح Drx ؟! طيب ما انت عارف ان استخدامها privileged ولايمكن تغييرها من user-mode !!
سأقول لك لا .. أين ذهبت دالة SetThreadContext اذا [صورة مرفقة: wink.png] ؟؟

 
BOOL WINAPI SetThreadContext(
  __in      	HANDLE hThread,
  __in      	const CONTEXT* lpContext
);

hThread : مقبض handle للثريد الذي سنغير الـcontext له ..
lpContext : مؤشر الى structure به الـcontext الجديد ..
ما هو الـThread Context ؟؟
تعرفون ان نظام ويندوز يتمتع بخاصية الـMultithreading أي امكانية عمل اكثر من thread في نفس الـprocess .. لكل thread له مسجلاته الخاصة و المكدس و اشياء اخرى .
يوجد في قلب النظام ما يسمى بالـKernel Dispatcher وهو المسؤول عن تنظيم الـthreads او ThreadScheduling .
عند عمل swap-out لـthread لابد من حفظ كل ما يتعلق به (مسجلات - مكدس - storage areas .. الخ) في مكان آمن حتى اذا تم عمل swap-in يتم استرجاع البيانات من هذا المكان مرة اخرى والعكس .. هذا المكان او الذاكرة هي ما نقصده بالـThread Context او Context مباشرة .
تركيب الـCONTEXT strcuture هي كالتالي :

 
struct {
	DWORD ContextFlags;
	//
	// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
	// set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
	// included in CONTEXT_FULL.
	//
	DWORD   Dr0;
	DWORD   Dr1;
	DWORD   Dr2;
	DWORD   Dr3;
	DWORD   Dr6;
	DWORD   Dr7;
	//
	// This section is specified/returned if the
	// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
	//
	FLOATING_SAVE_AREA FloatSave;
	//
	// This section is specified/returned if the
	// ContextFlags word contians the flag CONTEXT_SEGMENTS.
	//
	DWORD   SegGs;
	DWORD   SegFs;
	DWORD   SegEs;
	DWORD   SegDs;
	//
	// This section is specified/returned if the
	// ContextFlags word contians the flag CONTEXT_INTEGER.
	//
	DWORD   Edi;
	DWORD   Esi;
	DWORD   Ebx;
	DWORD   Edx;
	DWORD   Ecx;
	DWORD   Eax;
	//
	// This section is specified/returned if the
	// ContextFlags word contians the flag CONTEXT_CONTROL.
	//
	DWORD   Ebp;
	DWORD   Eip;
	DWORD   SegCs;          	// MUST BE SANITIZED
	DWORD   EFlags;         	// MUST BE SANITIZED
	DWORD   Esp;
	DWORD   SegSs;
	//
	// This section is specified/returned if the ContextFlags word
	// contains the flag CONTEXT_EXTENDED_REGISTERS.
	// The format and contexts are processor specific
	//
	BYTE	ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;


كما ترون توجد جميع مسجلات التنقيح داخل الـstrcuture ...
اذا ما سنفعله هو كالتالي :
1- جلب الـcontext عن طريق الدالة GetThreadContext .
2- تعديل مسجل Dr7 ووضع البتات الخاصة بطول الـHWBP و نوعها .
3- وضع عنوان الـHWBP في احد المسجلات الاربعة Dr0 -> Dr3 .
4- ارجاع الـcontext المعدلة عن طريق الدالة SetThreadContext .
هذا كود مبسط لشرح الكلام النظري السابق :

 
bool SetHWBP(HANDLE hThread, unsigned int linearAddress, int type, int length, int count)
{
	CONTEXT context = {CONTEXT_ALL|CONTEXT_DEBUG_REGISTERS};
	DR7 dr7;
	if (GetThreadContext(hThread, &context))
	{
    	dr7 = *(DR7*)&context.Dr7;
    	switch (count)
    	{
    	case 0:
        	{
            	context.Dr0 = linearAddress;
            	dr7.HWBP0_MODE   = HWBP_LOCAL;
            	dr7.HWBP0_LENGTH = length;
            	dr7.HWBP0_ACCESS = type;
            	break;
        	};
    	case 1:
        	{
            	context.Dr1 = linearAddress;
            	dr7.HWBP1_MODE   = HWBP_LOCAL;
            	dr7.HWBP1_LENGTH = length;
            	dr7.HWBP1_ACCESS = type;
            	break;
        	};
    	case 2:
        	{
            	context.Dr2 = linearAddress;
            	dr7.HWBP2_MODE   = HWBP_LOCAL;
            	dr7.HWBP2_LENGTH = length;
            	dr7.HWBP2_ACCESS = type;
            	break;
        	};
    	case 3:
        	{
            	context.Dr3 = linearAddress;
            	dr7.HWBP3_MODE   = HWBP_LOCAL;
            	dr7.HWBP3_LENGTH = length;
            	dr7.HWBP3_ACCESS = type;
            	break;
        	};
    	default:
        	return false;
    	};
    	context.Dr7 = *(PDWORD)&dr7;
    	return SetThreadContext(hThread, &context);
	};
	return false;
};

ولحذف HWBP نفعل مثل ما سبق لكن نصفّر الـbit الخاص بالـlocal والمسجل الخاص بالعنوان له :
 
bool RemoveHWBP(HANDLE hThread, int count)
{
	CONTEXT context = {CONTEXT_ALL|CONTEXT_DEBUG_REGISTERS};
	DR7 dr7;
	if (GetThreadContext(hThread, &context))
	{
    	dr7 = *(DR7*)&context.Dr7;
    	switch (count)
    	{
    	case 0:
        	{
            	context.Dr0	= 0;
            	dr7.HWBP0_MODE = 0;
            	break;
        	};
    	case 1:
        	{
            	context.Dr1	= 0;
            	dr7.HWBP1_MODE = 0;
            	break;
        	};
    	case 2:
        	{
            	context.Dr2	= 0;
            	dr7.HWBP2_MODE = 0;
            	break;
        	};
    	case 3:
        	{
            	context.Dr3	= 0;
            	dr7.HWBP3_MODE = 0;
            	break;
        	};
    	default:
        	return false;
    	};
    	context.Dr7 = *(PDWORD)&dr7;
    	return SetThreadContext(hThread, &context);
	};
	return false;
};


لتعامل أسهل مع مسجل Dr7 قمنا بعمل structure لتسهيل الامر بدلا من انجاز كل شئ بالـshifting والـbit level :
 
struct {
	ULONG	HWBP0_MODE    	:2;
	ULONG	HWBP1_MODE    	:2;
	ULONG	HWBP2_MODE    	:2;
	ULONG	HWBP3_MODE    	:2;
	ULONG	LE            	:1;
	ULONG	GE            	:1;
	ULONG	__unused    	:6;
	ULONG	HWBP0_ACCESS	:2;
	ULONG	HWBP0_LENGTH	:2;
	ULONG	HWBP1_ACCESS	:2;
	ULONG	HWBP1_LENGTH	:2;
	ULONG	HWBP2_ACCESS	:2;
	ULONG	HWBP2_LENGTH	:2;
	ULONG	HWBP3_ACCESS	:2;
	ULONG	HWBP3_LENGTH	:2;
}DR7;

مثال عملي :
سنقوم بعمل مثال loader على crackme بسيط للأخ dj-siba نقتنص فيه السيريال الصحيح + نتخطى رسالة badboy المزعجة .. التحدي اسمه "حاول ان تكسرني - تمرين رقم واحد" .. موجود بالمرفقات .
بتحليل سريع للتحدي اليكم المفيد :

 
00403FE9	CALL 00403E60
00403FEE	MOV EDX,dword ptr [ebp - 208]	---------------------> good serial
00403FF4	POP EAX
00403FF5	CALL 00402F58
00403FFA	JE SHORT 00404011    	---------------------> magic jump
00403FFC	PUSH 0
00403FFE	PUSH 00404050
00404003	PUSH 004040DC
00404008	PUSH 0
0040400A	CALL <JMP.&user32.MessageBoxA>	---------------------> badboy msg
0040400F	JMP SHORT 00404024
00404011	PUSH 0
00404013	PUSH 00404050
00404018	PUSH 00404100
0040401D	PUSH 0
0040401F	CALL <JMP.&user32.MessageBoxA>	---------------------> goodboy msg
00404024	XOR EAX,EAX



ما سنفعله هو كالتالي .. وضع نقطة توقف للـexecution عند العنوان 0x00403FF4 وبذلك نضمن وجود السيريال الصحيح في العنوان الذي يشير اليه المسجل edx .
وضع نقطة توقف اخرى عند عنوان القفزة التي تؤدي الى اضهار رسالة عدم التسجيل 0x00403FFA .
في اول HWBP نقوم بجلب مسجل edx عن طريق GetThreadContext ثم نقوم بقراءة العنوان الذي يشير اليه المسجل من الـprocess عن طريق الدالة ReadProcessMemory ونقوم باظهار السيريال الصحيح .
في ثاني HWBP نقوم بجلب مسجل الاعلام EFLAGS و نقوم بوضع العلم ZF وذلك كي تتم القفزة المشروطة و نتخطى الرسالة المزعجة .
مسجل EFLAGS أيضا يمكن التعامل معه عن طريق الـbit level لكن قمت بعمل structure لتسهيل الأمر :

 
struct {
	ULONG	CF        	:1;
	ULONG	__unused0	:1;
	ULONG	PF        	:1;
	ULONG	__unused1	:1;
	ULONG	AF        	:1;
	ULONG	__unused2	:1;
	ULONG	ZF        	:1;
	ULONG	SF        	:1;
	ULONG	TF        	:1;
	ULONG	IF        	:1;
	ULONG	DF        	:1;
	ULONG	OF        	:1;
	ULONG	IOPL    	:2;
	ULONG	__unused3	:1;
	ULONG	RF        	:1;
	ULONG	VM        	:1;
	ULONG	AC        	:1;
	ULONG	VIF        	:1;
	ULONG	VIP        	:1;
	ULONG	ID        	:1;
	ULONG	__unused4	:10;
}EFLAGS;


[صورة مرفقة: gKybO14.png]

 
المراجع :
Intel® 64 and IA-32 Architectures , System Programming Guide, Part 2
Toggle hardware data/read/execute breakpoints programmatically
 
GamingMaster aka Sadistic-X
Arab Team 4 Reverse Engineering
 
" اللهم أحسن خاتمتنا وأخرجنا من الدنيا علي خير"
أعضاء أعجبوا بهذه المشاركة : DarkDeath , xdvb_dz , [email protected] , farfes , Bosalem , Str0x , SeGNMeNT , Morpheus
#2
موضوع شيق أخي .كنت رأيت مقال لشخص يقوم بعمل BP لبرنامج لتغيير البرامترات تم تنفيد الفانكشن لتخطي بعض الحمايات.
كان عندي سؤال خارج الموضوع قليلا ادا تكرمت اخي و لديك اجابة
أريد ان اعرف كيف يتم تمرير الarguments التي تستعملهم الentry point مثلا في برنامج سي main()
كيف يتم تمرير البرامترات مثلا عندما يكون argc ==0 كيف يتم عمل set للargument argc=0
كنت قد قرأت طريقة اتبتاع createprocess() في كتاب windows internals part 2
لكن يا اما انا لم افهم ام ان الكتاب لم يشرح جيدا
لابسط لك اكثر لدي برنامج x يقوم بانشاء بروسسy اخر سواء ب createprocess اوrtlcreateuserprocess ولنفرض البروسس y لديه نقطة ادخال entry()
ولديها برامتر char* مثلا كما الشكل الاتي entry(char*p) كيف يقوم يتمرير هدا البرامتر من البروسس x
أعضاء أعجبوا بهذه المشاركة :
#3
دالة CreateProcess تستخدم بنية PROCESS_INFORMATION، ولتمرير البرامترات إلى البروسيس الجديد، يتم استخدام المعامل lpCommandLine الذي يمثل سطر الأوامر الذي سيتم تمريره إلى العملية الجديدة.

عندما تستدعي CreateProcess  مع سطر الأوامر (command line) ، فإن النظام يقوم بتوزيع هذا السطر على الذاكرة،
 بحيث يتمكن البروسيس الجديد من الوصول إلى هذه البيانات عبر argc و argv
  -  argc يحتوي على عدد البرامترات.
   - argv هو مصفوفة تحتوي على البرامترات.
" اللهم أحسن خاتمتنا وأخرجنا من الدنيا علي خير"
أعضاء أعجبوا بهذه المشاركة : Morpheus
#4
نعم اخي و بعدها قبل البدء في الانتري بوينت للبرنامج الثاني كيف يتم يقوم البرنامج الدي عملنا له spawn باخد البرامترات قبل البدء في العملية الاولى main
أعضاء أعجبوا بهذه المشاركة :
#5
يقوم نظام التشغيل بتوزيع سطر الأوامر مثلا (app.exe param1 param2 ) الذي مررته عبر المعامل lpCommandLine
في الذاكرة ثم يقوم بتقسيمه إلى برامترات.
  هذه البرامترات تخزن في الذاكرة ويتم تمريرها إلى العملية الجديدة عند بداية التنفيذ، حيث يمكن للبرنامج الوصول إليها عبر المتغيرات argc و argv.
" اللهم أحسن خاتمتنا وأخرجنا من الدنيا علي خير"
أعضاء أعجبوا بهذه المشاركة : Morpheus
#6
نعم هدا ما كنت اتحدث عنه
يعني يوجد loader في النضام اريد ان اعرف كيف يقوم يتوزيع هاته البرامترات في الداكرة كيف يقوم بعمل دلك قبل ان تبدأ الدالة main و تجدهم
أعضاء أعجبوا بهذه المشاركة :


التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 2 ) ضيف كريم