برای طرح سوال و دسترسی به آموزش ها سورس کدهای بیشتر به مجموعه xstack و یا به کانال ما در تلگرام کانال ما در تلگرام بپویندید.

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

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

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

  • 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[۱]  ذخیره می کند. . اسکریپت زیر را می توانید از این آدرس[۲] دریافت نمایید. با دستور زیر در windbg میبینیم که handler این وقفه تابع  KiSystemService() است :

kd> !@display_idt

####################################
# Interrupt Descriptor Table (IDT) #
####################################

Processor 00
Base : 8003F400    Limit : 07FF

Int   Type      Sel : Offset   Attributes  Symbol/Owner
----  --------  -------------  ----------  ------------
۰۰۲A  IntG32    0008:8053DA7E  DPL=3  P    nt!KiGetTickCount (8053da7e)
۰۰۲B  IntG32    0008:8053DB80  DPL=3  P    nt!KiCallbackReturn (8053db80)
۰۰۲C  IntG32    0008:8053DD20  DPL=3  P    nt!KiSetLowWaitHighThread (8053dd20)
۰۰۲D  IntG32    0008:8053E660  DPL=3  P    nt!KiDebugService (8053e660)
۰۰۲E  IntG32    0008:8053D521  DPL=3  P    nt!KiSystemService (8053d521)
۰۰۲F  IntG32    0008:80540838  DPL=0  P    nt!KiTrap0F (80540838)
۰۰۳۰  IntG32    0008:806D7D50  DPL=0  P    hal!HalMakeBeep+0x130 (806d7d50)

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

kd> !idt 2e
Dumping IDT:
۲e:	۸۰۵۳d521 nt!KiSystemService

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

x64-syscall

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

MSR_register

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

windbg-kernel-debug3

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

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

  • بیت های ۰-۱۱ مربوط به SSN[۵] است درواقع همان ایندکس داده موردنیاز درون جدول را مشخص می کند. بنابراین سایز جدول ۴۰۹۶ بایت است.
  • بیت های ۱۲-۱۳ نیز مربوط به انتخاب جدول است، پس می توانیم ۴ جدول داشته باشیم.
  • بیت های ۱۴-۳۱ نیز استفاده نمی شوند.

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

SSDT1

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

typedef struct ServiceDescriptorTable
{
	SDE ServiceDescriptor[4];
}

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

typedef struct ServiceDescriptorEntry 
{ 
	PDWORD KiServiceTable; 	//address  of the SSOT
	PDWORD CounterBaseTable; 	//not used
	DWORD nSystemCalls; 		//number of system call
	PDWORD KiArgumentTable;	//byte array (each byte = size of arg stack) 
} SOE,  *PSDE;

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

windbg-x-command

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

descriptor-table

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

SSDT2

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

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

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

cr0

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

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

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

   +0x0e0 ServiceTable     : Ptr32 Void

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

!for_each_thread ".echo Thread: @#Thread; dt nt!_kthread ServiceTable  @#Thread"

 

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

 

 


 

[۱] Machine-Specific Register

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

[۳] Interrupt Descriptor Table Register

[۴] System Service Dispatch Table

[۵] System Service Number

[۶] ServiceDescriptorTable

[۷] Write Protect

قطره ای از دریای بیکران IT
نوشته ایجاد شد 18

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

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

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

دیدگاهتان را بنویسید

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

نوشته های مرتبط

متنی که میخواهید برای جستجو وارد کرده و دکمه جستجو را فشار دهید. برای لغو دکمه ESC را فشار دهید.

بازگشت به بالا