آنالیز ساختار ویندوز و روت کیت های سطح هسته-بخش دوم

در ادامه بحث های مطرح شده در آنالیز ساختار ویندوز و روت کیت های سطح هسته-بخش اول  ادامه بحث را دنبال خواهیم کرد.

سطح هسته همانند سطح کاربر دارای هوک های خاص خودش است که معروفترین آن ایجاد تغییرات دلخواه در جداولی است که آدرس توابع مورد نیاز سیستم را در خود نگهداری می کنند:

  • System Service Dispatch Table (SSDT)
  • Interrupt Descriptor Table (IDT)

آدرس توابع سطح کرنل درون جدول SSDT نگهداری می شود(توابع nt*). هنگامیکه روند اجرا یک برنامه سطح کاربر میخواهد به سمت کرنل هدایت شود  ID مرتبط با تابع کرنل درون رجیستر EAX قرار می گیرد و رجیستر EDX به لیست پارامترهای که بایستی به تابع ارجاع داده می شود اشاره می کند، سپس با اجرای دستور int 2e و یا sysenter روند اجرا پروسه به سطح کرنل خواهد رفت.  هنگامیکه وقفه ای رخ می دهد سیستم عامل با جستجو در IDT ، روتین مرتبط با مدیریت آن وقفه را بدست میاورد. این جدول تمامی وقفه ها را درون خود نگهداری می کند. روتین هندل کننده وقفه 0x2e  تابع KiSystemService() از ntoskrnl است. برای بازگشت به سطح کاربر از دستور iret استفاده می شود. در صورتی که از دستور sysenter استفاده شود برای بازگشت به سطح کاربر از دستور sysexit استفاده خواهد شد. برای پشتیبانی از دستور sysenter ویندوز در زمان بوت آدرس روتین مرتبط را بجای IDT در رجیستر  MSR[1]  ذخیره می کند. . اسکریپت زیر را می توانید از این آدرس[2] دریافت نمایید. با دستور زیر در windbg میبینیم که handler این وقفه تابع  KiSystemService() است :

یا از دستور زیر استفاده نمایید:

اما در سیستم های x64 بحث متفاوت تر است و از دستور syscall استفاده می شود :

x64-syscall

اولین تکنیک از هوک های کرنل، هوک IDT است که می توانیم Handler مرتبط با وقفه 0x2e را تغییر دهیم. روت کیت می تواند با ایجاد تغییر در IDT بجای nt!KiSystemService آدرس روتین خود را قرار دهد و همچنین SSDT جدیدی را ایجاد نماید و کاملا روند اجرا را تحت کنترل داشته باشد. روت کیت ها می توانند IDTR[3] را نیز تغییر دهد. این رجیستر آدرس ابتدای IDT را در خود نگه میدارد، حال روت کیت می تواند IDT مورد نیاز خود را ایجاد نماید و آدرس درون IDTR را نیز تغییر دهد. برای مشاهده رجیستر MSR می توان از rdmsr و از دستور wrmsr  برای نوشتن در این رجیستر استفاده کرد ، ادرس روتین Dispatcher در 0x176 قرار دارد:

MSR_register

برای سیستم های x64 بجای 0x176 بایستی 0xC0000082 استفاده نماییم. براحتی روت کیت می تواند این رجیستر را بازنویسی نماید با کامند !addressمی توانیم آدرس را بعنوان پارامتر به آن دهیم تا بفهمیم در محدوده چه پروسه ای  است:

windbg-kernel-debug3

همچنین می توانیم از کامند !!display_current_msrsاستفاده کنیم تا رجیستر MSR را بطور کامل مشاهده نماییم.

یکی دیگر از هوک های سطح کرنل ایجاد تغییرات در جدول SSDT[4] است. همانطور که قبلا گفتیم شماره مربوط به تابع سطح کرنل درون رجیستر EAX که رجیستر 32بیتی است ذخیره می گردد. این فضای برای ذخیره شدن یک شماره تابع ساده بسیار بزرگ است.  بنابراین مقدار درون این رجیستر به 3 بخش تقسیم می شود.

  • بیت های 0-11 مربوط به SSN[5] است درواقع همان ایندکس داده موردنیاز درون جدول را مشخص می کند. بنابراین سایز جدول 4096 بایت است.
  • بیت های 12-13 نیز مربوط به انتخاب جدول است، پس می توانیم 4 جدول داشته باشیم.
  • بیت های 14-31 نیز استفاده نمی شوند.

تصویر زیر این ساختار را بدرستی نمایش میدهد:

SSDT1

برای بیت های 12-13 گفتیم که جدول مورد استفاده را انتخاب می کند. ما تنها دو جدول داریم مقدار 0x00 نشان دهنده جدول KeServiceDescriptorTable که جدول پیش فرض و اصلی است و مقدار 0x01 نشان دهنده جدول KeServiceDescriptorTableShadow است.  این دو جدول همانند هم هستند ولی با این تفاوت که در  KeServiceDescriptorTableShadow  دارای توابع GDI  سطح کرنل که در Win32k.sys پیاده سازی شده اند می باشد. هنگامی که NTOSKNRL.EXE  بارگذاری می شود اولین عنصر این دو جدول را مقدار دهی می کند، سپس Win32k .sys بارگذاری می شود و با استفاده از تابع KeAddSystemServiceTable از  NTOSKNRL ، دومین عنصر از آرایه KeServiceDescriptorTableShadow  را مقدار دهی می کند. ساختار این دو جدول بصورت زیر است :

ساختار KeServiceDescriptorTable  در  ntoskrnl  است. هر کدام از این جداول یک آرایه 4 عنصری است که هرکدام از عناصر آرایه دارای ساختاری از نوع SDE [6] است. خوب حال ساختار SDE را می بینیم که به چه صورت است:

روتین KiSystemService آرگومان ها را از پشته سطح کاربر به درون پشته سطح کرنل کپی می کند و سپس تابع سطح کرنل اجرا می گردد. کرنل با استفاده از جدول Argument Table می داند که چند بایت از پشته بایستی کپی گردد. این جدول آرایه ای از نوع بایت است و هربایت اندازه آرگومان های درون پشته را مشخص می کند. در سیستم های x64 اطلاعات Service Table توسط System Call Table Compaction بصورت کد شده ذخیره می گردد. ابتدا جداول مرتبط با دستور x جستجو می کنیم:

windbg-x-command

آدرس این دو جدول را بدست آوردیم، حال با توجه به ساختار SDE که گفتیم بایستی تعداد 16 عدد DWORD برای هرکدام از جداول داشته باشیم. اختلاف بین دو جدول 64بایت است ، هر DWORD سایز 4بایت دارد، بنابراین در فاصله بین دو جدول  میتوانیم 16 عدد مقدار Dword داشته باشیم. پس ابتدا جدول دوم را با طول 16 مشاهده می کنیم:

descriptor-table

تا به اینجا با ساختار این دو جدول آشنا شدیم حال میخواهیم محتوای SSDT را مشاهده کنیم.  جدولی که روت کیت ها مقادیر درون آن را تغییر می دهند تا توابع حساس را هوک نمایند و بتوانند خود را پنهان نمایند. با استفاده از کامند زیر می توانیم محتوای این جدول را مشاهده نماییم.

SSDT2

با استفاده از کامند بالا جدول SSDT را بطور کامل مشاهده می کنیم، L نشان دهنده طول می باشد که ما طول جدول را نیز مشخص کرده ایم. دستور dps برای نمایش حافظه است که بطور خودکار تصمیم به اندازه آن گرفته می شود (16-64 bit) و همچنین اگر دارای سیمبول است نشان داده شود. اگر توابعی در این جدول هوک شود آدرس آن خارج از محدوده nt می باشد و براحتی می توان آن را تشخیص داد. برروی سیستم های X64 با استفاده از مکانیزم Kernel Patch Protection جداول درحال مانیتور شدن می باشند اگر تغییری در این جداول بوجود آید سیستم کرش می کند. روش دیگر هوک کردن توابع بصورت in-line است تا تغییری در ساختار جداول ایجاد نشود. جدول SSDT در حافظه read-only قرار دارد که این امر مشکل اساسی در بازنویسی نمودن این جدول است. فلگ WP در رجیستر CR0 می تواند مقدار 0,1 داشته باشد. اساسا این فلگ، از حافظه read-only در برابر بازنویسی محافظت می کند. این فلگ تاثیرش در سطح کرنل است :

  • 0 : کرنل اجازه دارد تا در حافظه های read-only بنویسد صرفنظر از فلگ های R/W , U/S در PDE و  PTE
  • 1: کرنل اجازه نوشتن ندارد. فلگ های R/W , U/S در PDE و  PTE استفاده می شود تا بتوان نحوه دسترسی کرنل به حافظه را مشخص نماید.

برای مشاهده رجیستر CR0 در قالب های مختلف از کامند .formats استفاده می کنیم. با استفاده از آن بیت 16 رجیستر که همان WP[7] را میبینیم که چه مقداری دارد:

cr0

در زیر به راه حل ها میپردازیم که به اما اجازه نوشتن در SSDT را میدهد:

  • تغییر مقدار WP : هنگامی که مقدار این فلگ را در رجیستر CR0 به مقدار 0 ست نماییم دیگر مقادیر PDE,PTE نادیده گرفته می شود.
  • تغییر در کلید رجیستری “HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory\Management\EnforceWriteProtection” که به ما اجازه نوشتن میدهد.
  • با استفاده از MDL می توانیم SSDT را قابل بازنویسی نماییم. MDL خود را در جایی که SSDT وجود دارد ایجاد می نماییم.

در ساختار nt!KTHREAD فیلد ServiceTable در واقع آدرس همان دو جدول است:

با دستور زیر می توانیم این فیلد را در تمامی ترید های فعال مشاهده کنیم که مقدار آن بایستی به یکی از دو جدولی که قبلا ذکر شده اشاره کند در غیر اینصورت جدولی جعلی درون سیستم وجود دارد و هوک انجام شده است:

 

در بخش بعدی به سایر تکنیک ها خواهیم پرداخت.

 

 


 

[1] Machine-Specific Register

[2] http://www.laboskopia.com/download/SysecLabs-Windbg-Script.zip

[3] Interrupt Descriptor Table Register

[4] System Service Dispatch Table

[5] System Service Number

[6] ServiceDescriptorTable

[7] Write Protect

HamiD

قطره ای از دریای بیکران IT

More Posts

4 thoughts on “آنالیز ساختار ویندوز و روت کیت های سطح هسته-بخش دوم”

  1. مگه آدم میتونه رو این یادداشت یادداشت جدیدی ارسال کنه؟ به این خفنی!!!
    ایولا داری کاش همه قطره های دریای بیکران اینشکلی باشن.

  2. سلام
    با تشکر از توضیحات شما
    چگونه می توان با یک دیباگر وقتی به دستورsysenter می رسی دستوراتی که در ان اجرا می شود را مشخص نمود

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *