|
BreakPoint چيست ؟
به عنوان يك تعريف ساده ،
BP نقطه اي از برنامه است كه كاربر مايل است با رسيدن روند اجراي برنامه به آن نقطه
، كنترل فرآيند پردازش برنامه توسط پردازنده را مجددا" در اختيار بگيرد ؛
چرا BreakPoint مهم است ؟
وقتي در حال ديباگ – به معناي بررسي مجدد و رفع خطاها و نواقص – يك برنامه هستيد ،
شايد مايل باشيد در نقطه اي مشخص ، يعني مثلا" در ادرس خاصي رو حافظه ، يا قبل از
اجراي دستور زبان ماشين خاصي ، مقدار برخي از متغيرها را – قبل از ادامه يافتن
برنامه – چك كنيد ، يا قبل از فراخواني يك تابع ، از صحت اجرا و بازگشت تابع ديگري
مطمئن شويد ، يا وقتي در حال مهندسي معكوس يك باينري هستيد شايد مايل باشيد درست
هنگام فراخواني API به خصوصي ، يا هنگام ارجاع دسترسي به ماژول به خصوصي ، روند
اجراي برنامه بصورت عادي متوقف ، و اعمالي توسط شما انجام شود ، شايد مقاديري
بررسي شوند يا وضعيت متغير يا تابعي در فضاي آدرسي برنامه – و در زمان اجرا – تغيير
كند و ...مواردي مانند اين . در حقيقت هر وقت براي كاربر يك باينري مهم باشد كه در
مسير و روند اجراي باينري ، در مقاطع خاص و مشخصي ، با توقف كليه روندهاي در حال
اجراي برنامه ، وضعيت عناصر خاصي از برنامه بررسي ، يا محتواي برخي از آنها در همين
حين تغيير كند ، يك BreakPoint مفيدترين گزينه است .
يك BreakPoint اصولا" چه چيزي است ؟
قبل از پاسخ بايد بدانيم BP ها را ميتوان به دو دسته كلي تقسيم كرد :
* Hardware BP
* Software BP
BP هاي اصطلاحا" سخت افزاري ، قابليتي هستند كه توسط خود پردازنده در اختيار سيستم
عامل و نرم افزارهاي آن قرار ميگيرند . اغلب پردازنده هاي امروزي رجيسترهاي انحصاري
براي فرآيندهاي مرتبط با ديباگ در نظر گرفته اند ، بدين معني كه آدرس و مشخصات BP
مورد نظر كاربر در اين رجيسترها ثبت و در زمان مناسب توسط پردازنده استفاده خواهند
شد ، و اين يعني توقف روند اجرا توسط خود پردازنده و با قابليتهاي داخلي اش انجام
خواهد شد ، و شايد صحيح تر و فني تر باشد كه بگوئيم ، فرا رسيدن يك BP توسط
پردازنده به ديباگر گزارش خواهد شد و اين ديباگر است كه با اعلان پردازنده ، روند
اجرا را "كنترل" ( و نه لزوما" متوقف ) ميكند .
BP هاي اصطلاحا" نرم افزاري ، نقاط توقفي هستند كه توسط ديباگر ايجاد و مديريت
ميشوند . ديباگر با دريافت موقعيتي كه كاربر باينري مايل است يك نقطه توقف باشد ،
محل دقيق نقطه مذكور را با دستور ديگري كه اصطلاحا" تله يا Trap نام دارد پر ميكند
، كه اين Trap شايد يك Instruction مخصوص يا يك دستور نامناسب كه توليد خطا يا
استثناء ميكند باشد ، كه بدين ترتيب ، با رسيدن به موقعيت مذكور و بروز "حادثهء"
مورد نظر ، ديباگر از محقق شدن "فراخواني BP" مطمئن ميشود ، و برنامه را كنترل
ميكند . Instruction يا دستوراتي كه به عنوان Trap در نظر گرفته ميشوند بايد تا حد
امكان از لحاظ طول و اندازه كوتاه باشند ، بطوريكه نتوان به وسط يك Trap يا جائي
بين آن ، Jump كرد اغلب پردازنده ها يك دستور غير صحيح يا illegal دارند كه
ديباگرها ميتوانند از اين Instruction به عنوان Trap استفاده كنند .
مثال يك : پردازنده هاي خانواده x86 اينتل كه مبتني بر معماري IA32 هستند ، روي
مدلهاي i386 به بعد ، حداقل هشت رجيستر به نامهاي DR0 تا DR7 به بعد به عنوان Debug
Register در نظر گرفته است يا پردازنده هاي شركت فليپس كه مبتني بر مدل ARM4 و
متعلق به خانوادهء LPC21xx هستند تا 12 رجيستر 32 بيتي براي ديباگ در نظر گرفته اند
؛ رجيسترهاي ديباگ بسته به معماري مورد نظر ، كاربرد و منظور مشخصي دارند و – اغلب
– از هر كدام آنها وظيفه اي خاص انتظار ميرود كه حمايت از BP هاي سخت افزاري يكي از
اين وظايف است .
مثال دو : GDB يا GNU Debugger به عنوان معروفترين ديباگر منبع آزاد ، كه دامنه
وسيعي از معماريهاي سخت افزاري و نرم افزاري را حمايت ميكند ، از گونه هاي مختلف و
متنوع BP نرم افزاري پشتيباني ميكند ؛ Software BP ها ميتوانند مبتني بر يك Regular
Expression باشند ، مثلا" در اين صورت كه EAX محتوي عددي خاص و EDX تهي بود ، آدرس
مشخصي حاوي يك BP خواهد بود ، و الا خير ، كه به اين نوع BP اصطلاحا" WatchPoint
گفته ميشود ؛ نوع ديگري از BP نرم افزاري بنام CatchPoint وقتي بوجود مي آيد كه
"واقعهء" مشخصي اتفاق افتاده باشد ، مثلا" در صورت وقوع فلان استثناء در فلان روتين
CPP يا فراخواني فلان ماژول توسط برنامه ، فلان آدرس حاوي BP خواهد بود . هنگام
استفاده از GDB اگر بخواهيد روي تابع خاصي BP بگذاريد مينويسيد : Break
function.name يا اگر مايليد روي آفست خاصي BPبگذاريد مينويسيد : Break
offset.number و ...ديباگر هاي مختلف روشهاي متفاوتي براي تزريق BP به روند اجراي
برنامه دارند ، و اغلب ديباگرهاي امروزي كه نما و رابطي بصري دارند به كمك چند كليك
، انواع متفاوتي از BP را روي كد ايجاد ميكنند ؛
چگونه از وجود
BreakPoint مطلع شويم ؟
BP ها يا روي پردازنده
هستند يا كدي هستند در متن برنامهء در حال اجرا ، پس بايد روش يا روشهاي مشخصي براي
اطلاع از وجود يك BP هنگام اجرا كد وجود داشته باشد . فرض كنيد برنامه اي نوشته ايد
كه به دلائلي مايليد مورد بررسي قرار نگيرد ، در واقع دوست نداريد كسي آن را ديباگ
كند ، و ديباگ بدون BP امكان پذير نيست ؛ شايد ساده ترين روش بررسي رجيسترهاي CPU
براي وجود يا عدم وجود BP باشد ، و بعد از آن بررسي محلهاي خاصي از برنامه كه امكان
دارد نقاط خوبي براي يك توقف باشند ، وقتي به عنوان مثال روي معماري اينتل Trap يا
همان illegal instruction ما int 3 هست ، قاعدتا" اگر يك BP نرم افزار معمولي در
محل A وجود داشته باشد ، قبل از رسيدن به محل A بايد بررسي آن محل به خروجي 0xCC كه
معادل int 3 است منتج شود . معمولا" سيستمهاي عامل دسترسي مستقيم به رجيسترهاي حساس
و كنترلي را لغو ميكنند و اين خدمت از طريق API هاي سطح بالا ارائه ميشود ، و
ميتوان از اين توابع براي كسب اطلاع از وجود Hardware BP استفاده كرد ، و اگر
ديباگر با Hook و re-direct اين توابع ، خروجي آنها را تغيير دهد ، كسب اطلاع از
وضعيت واقعي پردازنده دشوار است ، و مرحله بعدي نبرد ، مقابله با Hook است و ...و
همينطور براي BP هاي نرم افزاري ...؛ در واقع روشهاي عمومي كسب اطلاع در مورد BP
ساده و روشن اند اما وقتي اين چالش به يك مقابله مبدل شود ، صورت مسئله كسب اطلاع
دربارهء BP نخواهد بود بلكه عبور از سد ها يا مقابله با روشهائي است كه واقعيت را
تحريف ميكنند . يكي از مسائل جاري و عمومي مهندسي معكوس ، كشف نقاط ضعف نرم افزاري
، كرك نرم افزارهاي تجاري و قفلها و غيره ، مبارزه با ديباگ ، يا همان روتينهاي
آنتي ديباگ هستند ، كه مسئلهء BreakPoint يكي از سر فصلهاي جدي آن است .
Memory BreakPoint چيست
؟
بر خلاف BP هاي سخت افزاري
كه تعريف محدود و معيني دارند ، BP هاي نرم افزاري ميتوانند منعطف و خلاق باشند .
در واقع براي اينكه نقطه اي ، نقطهء توقف باشد ، ميتوان شروط و قواعد و ملاحظات
متعددي را در نظر گرفت ، كه هر كدام ، و يا با تركيب بعضي از آنها ، يك Software
BreakPoint جديد داريم . Memory BreakPoint ها پيش از همه توسط ديباگر محبوب و
معروف OllyDbg معرفي شدند .
يك Memory BreakPoint ، دسترسي به مكان يا "محدوده" خاصي از فضاي آدرسي برنامه را
به ديباگر اطلاع ميدهد . تفاوت چشمگير Memory BP ها در ماهيت انهاست . Hardware BP
ها از رجيسترهاي CPU كمك ميگيرند پس كشف انها چندان دشوار نيست و Software BP ها
هميشه چيزي شبيه به 0xCC به كد برنامه روي حافظه اضافه ميكنند و كشف آنها روز از
ذهن نيست ، اما Memory BP ها – خصوصا" روي ويندوز – از تكنيك متفاوتي استفاده
ميكنند كه پيدا كردن آنها به هيچ عنوان ساده نيست .
فضاي آدرسي يك پروسه وقتي پردازنده و سيستم عامل در وضعيت Protected-Mode عمل
ميكنند به بخشهاي كوچكي بنام Page تقسيم شده است . اين Page ها كه كوچكترين عناصر
Virtual Memory هستند ، براي خوانده شدن و نوشته شدن و اجرا ، ميتوانند مانند
فايلها ، داراي حقوق دسترسي باشند . يعني سيستم عامل ميتواند با ارائه توابعي ،
حقوق دسترسي براي هر كدام از Page هاي Virtual Memory فراهم كند . ويندوز با توابعي
نظير VirtualProtectEX براي Page ها حافظه ، حق دسترسي تعريف ميكند ، و اين حقوق
دسترسي هنگام دسترسي به Pageهاي مورد نظر با توابعي نظير ReadProcessMemory يا
WriteProcessMemory و ساير توابع خودشان را نشان خواهند داد . مثلا" براي اعطاي
مجوز Read و Write به Page اي حاوي حاوي آدرس 0X401000 است ( يعني Page اي كه اين
ادرس در آن قرار دارد ) ميتوان از چنين متدي استفاده كرد :
VirtualProtectEx(pi.hprocess,(LPVOID)dwaddress,1,dwnewprotect,&oldprotect )
كه در آن pi.hprocess
هندلي به پروسه مورد نظر و dwaddress آدرس مورد نظر و dwnewprotect معادل حق دسترسي
Read و Write يعني PAGE_READWRITE هست ؛ و پس از اين ، Page كه حاوي اين آدرس است
، براي خواندن ، و نوشتن ، در دسترس خواهد بود ، يعني هر درخواستي براي خواندن از
آن ، يا براي نوشتن روي آن ، با موفقيت مواجه خواهد شد ، مگر خطائي در سيستم عامل
يا فرآيند مورد نظر ايجاد شود . براي مطالعه مجوزهاي موجود براي Page ها MSDN را
ببينيد . اگر مجوز يك Page را معادل PAGE_GUARD تعريف كنيد ، به مفهوم Memory BP
نزديك شده ايد ؛ يعني هر گاه Page اي با اين مجوز فراخواني شود ، براي هر عملي ،
مانند خواندن يا نوشتن ، خطائي محتوي STATUS_GUARD_PAGE_VIOLATION رخ خواهد داد .
مديريت اين حقوق دسترسي و فراخواني اين استثناء به عهدهء مدير حافظه مجازي ويندوز –
VMM – هست . به كمك اين ويژگي و امكاني بنام Trap flag در پردازنده كه رجيستر 8
بيتي است و براي اجراي برنامه بصورت single-step در نظر گرفته شده ( يعني اجراي
مرحله به مرحله هر دستور اسمبلي و توقف قبل از اجراي دستور بعدي و صدور يك استثناء
محتوي EXCEPTION_SINGLE_STEP ) ميتوان Memory BP ها را مديريت كرد . مفيدترين
كاربرد Memory BP ها در Unpacking است ، يعني برگرداندن برنامه هاي Pack شده به
حالت اوليه ، يا وضعيتي كه بتوان روند اجراي "كدهاي واقعي" را ديباگ كرد .
آشنائي با مفهوم BreakPoint براي كسب تسلط بر Debugging ضروري است . BP هاي مختلف
براي نيل به مقاصد متفاوتي مناسب هستند كه كسب تجربه و تبحر بيش از هر چيز در ايجاد
اين شناخت موثر است . اغلب راه حلهاي نرم افزاري حفاظت از نرم افزار ، روتينهائي
براي كشف BP هاي مختلف دارند ، و متقابلا" ديباگرها و كاربران علاقه مند هر كدام ،
امكاناتي براي عبور و فريب دادن روتينهاي BP-detection ارائه ميكنند
نویسنده:
Inprise
برگرفته از سایت:
www.barnamenevis.org
|