تقييم الموضوع :
  • 6 أصوات - بمعدل 3.17
  • 1
  • 2
  • 3
  • 4
  • 5
Handling Debug Registers
#1
السلام عليكم ورحمة الله وبركاته .
درس اليوم عن كيفية كتابة 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 (الكل في الكل) ,, تركيبته كالتالي :
أول 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 ..
طبعا نحن هنا لسنا بصدد التعرف على تلك الدوال  .. يمكنك التطلع عليهم اكثر في مكتبة MSDN .
عند حدوث HWBP يتم رفع exception من النوع EXCEPTION_SINGLE_STEP الى المنقح اذا كان موجوداً .. اذا لابد علينا من عمل handling لهذا الـexception code .. 
طيب سؤال اخر .. كيف نعرف اي HWBP من الاربعة هو الذي حدث ؟ لا توجد دوال api معينة لمعرفة ذلك !
صحيح .. نحن لم نتكلم عن Dr6 بالتفصيل بعد كما وعدنا وها قد حان الوقت .. تركيبة هذا المسجل بسيطة جداً كالتالي :
ما يهمنا معرفته هو أول 4 bits وهي تمثل بت لكل HWBP .. اذا تم وضع احدهم فهو يدل على ان الـHWBP المناظر له هو الذي حدث .
حسنا كيف نضع HWBP ؟؟
ستقول لي من مسجلات التنقيح Drx ؟! طيب ما انت عارف ان استخدامها privileged ولايمكن تغييرها من user-mode !!
سأقول لك لا .. أين ذهبت دالة SetThreadContext اذا  ؟؟
 
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; 

حسنا اترككم مع الكود في المرفقات .. أي شئ مبهم لا تتردد بالسؤال عنه .. أيضا اذا كان هناك خطأ في النظري او الكود ارجو ابلاغي .
المراجع :
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


صاحب الموضوع الأصلي: GamingMasteR
أعضاء أعجبوا بهذه المشاركة :
#2
صاحب التعليق الاصلي : AnasD في 30-08-2008, 09:30 AM
شكراً اخ GamingMasteR على الدرس القيم
"< رغم ندرة مشاركاتي بس اتمنى تعتبروني صديق >"
انا في عندي سؤالين
الاول بشأن الكود اخي

if ((context.Dr6 & (1 << 0))) //hwbp #1

واضح انه لمعرفة فيما إذا كان hwbp #1 هو الذي حدث ام لا وذلك من خلال Dr6 ... جميل جداً, بس مشكلتي انني لست مبرمج بلغة ++C لذلك لم افهم تركيب جملة الشرط هذه .
اتمنى لو انه في توضيح حتى اتمكن من كتابة جملة الشرط للـ hwbp #3 و #4
السؤال الثاني إذا ما في غلبة
لدي برنامج قمت بصطياد السيريال لمساره على جهازي - يعتمد السيريال على القرص الموجود عليه البرنامج -
حاولت اقوم بتنفيذ هذا الدرس عليه بس في مشكلة وهي كيف يمكنني الحصول على محتوى ST0 عند النقطة المحدده في الصوره - بالمرفقات-.
#### على فكره انا لم افهم ما تقوم به هذه التعليمات (FADD , FCOMP , FSTSW ) لكن لاحظة ان ( FADD , FSTP , FLD) تؤثر على ST0 والتعليمة FCOMP تقوم بالمقارنة.
إذا اتمكنت من انشاء اللودر سأضعة مثال في هذا الموضوع
وشكراً


صاحب التعليق الاصلي : GamingMasteR في 30-08-2008, 11:12 AM
السلام عليكم.
إقتباس :اتمنى لو انه في توضيح حتى اتمكن من كتابة جملة الشرط للـ hwbp #3 و #4

كما ذكرت سابقاً فإن اول 4bit في مسجل Dr6 هو الذي يحدد أي HWBP حدث , يعني اذا كان اول بت = 1 فان اول HWBP حدث واذا ثاني بت = 1 فان ثاني HWBP حدث وهكذا ..
 
if ((context.Dr6 & (1 << 0)))

هذا الكود يقوم باختبار قيمة البت الأول (رقم صفر) لمعرفة اذا كان اول HWBP حدث , يمكن تغيير 0 الى رقم البت المراد اختباره , مثلا لاختبار البت الثالث (رقم 2) :
 
if ((context.Dr6 & (1 << 2)))

----------------------------
إقتباس :على فكره انا لم افهم ما تقوم به هذه التعليمات (FADD , FCOMP , FSTSW ) لكن لاحظة ان ( FADD , FSTP , FLD) تؤثر على ST0 والتعليمة FCOMP تقوم بالمقارنة.

هذه التعليمات خاصة بمعالج الـFPU وهو مخصص للتعامل مع الاعداد العشرية والكبيرة , يحتوي على عدة مسجلات 8 منهم لحفظ الارقام طول كل واحد منهم 10 بايت وهي تمثل من ST0 الى ST7 .
يمكن اصطياد قيم هذه المسجلات بنفس الطريقة السابقة , هناك عنصر في الـCONTEXT structure اسمه FloatSave وهو عبارة عن structure من النوع FLOATING_SAVE_AREA وتركيبته كالتالي :

 
struct {
    DWORD   ControlWord;
    DWORD   StatusWord;
    DWORD   TagWord;
    DWORD   ErrorOffset;
    DWORD   ErrorSelector;
    DWORD   DataOffset;
    DWORD   DataSelector;
    BYTE    RegisterArea[80];
    DWORD   Cr0NpxState;
} FLOATING_SAVE_AREA;

يحتوي هذا التركيب على المسجلات الخاصة بالـFPU ومنهم مسجلات الـST كلها تحت اسم RegisterArea وهي مصفوفة تتكون من 80 بايت (اي عدد الـST * طول كل واحد منهم) .
بالمناسبة هذه الأرقام تركيبها ليس كتركيب الارقام الصحيحة مثل DWORD-int-integer بل لها تركيبات خاصة ,, يمكنك تحويلها الى نص عن طريق احد دوال الـtext formatting مثل printf مع وضع الـformat للرقم "f%" , السلام عليكم .

---------------------------
صاحب التعليق الاصلي : MouradPr في 03-10-2008, 09:44 PM
ياعم وين حق الأسمبلي من الموضوع... البارحة كانت لي أول محاولة بالأسمبلي لأضع الدرس Serie API 3 أحببت أن أضيف هذه الطريقة لكني لم أفلح في وضع نقطة التوقف وذلك بسبب البنيات التي أضفت
DR7 و RegFlag
هل يمكن ان تتكرم وتقدم مساعدة بالأسمبلي..

صاحب التعليق الاصلي : GamingMasteR في 03-10-2008, 10:49 PM
للأسف هذه البنيات(التي بها bits) لابد من التعامل معها في الاسمبلي على انها DWORD ويتم التحقق من الـbit المراد عن طريق and/test .

صاحب التعليق الاصلي : GamingMasteR في 04-10-2008, 12:54 PM
أجدع كود اسمبلي لأجدع مراد في الدنيا Big Grin
shift و and و or وكل اللي قلبك يحبه


الملفات المرفقة
.rar   DbgLoader_asm.rar (الحجم : 41.7 KB / التحميلات : 45)
سبحان الله وبحمده، سبحان الله العظيم
أعضاء أعجبوا بهذه المشاركة : M!X0R


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


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