Computer Emergency Response Center

Birbaşa sistem çağırışları ilə "hook" mühərriklərindən yayınma

Bundan öncəki məqalələrimiz birində (https://mrl.cert.gov.az/az/articles/view/79) sizlərə Win32 api hooking haqqında məlumat vermişdik. Bu məqalənin əsas mövzusu isə bu tipli hook mühərriklərindən yayınmaq üçün istifadə edilən DSC (Direct-System-Call) olacaqdır. Olduqca uzun hətta anlamaq üçün bir neçə dəfə oxumaq məcburiyyətində qalacağınız bir məqalə sizləri gözləyir. Məqalədə görəcəyiniz testlər Windows 7 x86 N (SP1) əməliyyat sistemində icra edilmişdir.

Giriş

Hook mühərrikləri və mexanizmləri uzun zamandır kodların izlənməsi (code tracing), proqram təminatlarının testlərin, antivirusların şübhəliləri davranış əsaslı aşkar etməsi (behaviour engine), sandboxing və s. üçün istifadə edilir. Mühərriklərin əsas hədəfləri əksər hallarda sistem api (Application Programming Interface) olur. Hook mexanizmi, izlənilməsi nəzərdə tutulan hər hansı bir funksiyanın (öncədən bəlli) başqa bir adresdə var olan funksiya ilə əvəzlənməsi kimi dəyərləndirilə bilər. Bu əvəzlənmə sözü gedən funksiya kodlarını tamami ilə dəyişdirilməsini nəzrədə tutmur bunun əvəzinə yönləndirmə (jumping) metodu istifadə edilir.

Application Programming Interface hook, prosesin istifadə etdiyi dinamik kitabxana fayllarını hədəf alır. Bunun səbəbi isə System-Call mexanizmidir.

Sistem çağırışları

Hər hansı bir proses cari axının (thread) müəyyən intervalda dondurulmasını istəyirsə bunun üçün Sleep funksiyası çağrılır. Bu funksiya kernel32.dll kitabxanası tərəfindən ixrac (export) edilir.

Funksiya prototipi:

void Sleep(
  [in] DWORD dwMilliseconds
);

Disassembler ilə kernel32.dll kitabxanasına baxsaq sözü gedən funksiyanın 77E2C466 adresində yerləşdiyini görürük.

Daha rahat analiz üçün assembly dilində sözü gedən funksiyanı test edəcək kiçik bir kod yazaq (kodları qısa tutmaq üçün bir hissəsi bilərəkdən kəsilmişdir):

Sleep.exe

section '.text' code readable executable
  start:
	push	0xc350
	call	[Sleep]
	push	0
	call	[ExitProcess]

Burada ilk olaraq cari axın 50000 millisaniyə dondurulur. Daha sonra ExitProcess ilə proqram işini sonlandırır. Burada bizə lazım olan funksiya Sleep-dir.

Kodu kompilasiya edərək debugger main funksiyasına nəzər salaq.

  • 0xC350 dəyəri stack bölgəsinə ötürülür və Sleep funksiyası çağrılır.
  • Sleep funksiyası lazımı sazlamaları etdikdən sonra kernelbase.dll içərisində yerləşən _Sleep@4 stub çağrılır
  • _Sleep@4 isə kernelbase.dll içərisində yerləşən SleepEx funksiyasını çağırır.

  • SleepEx funksiyası içərisində bəzi sazlamalar etdikdən sonra isə ntdll.dll içərisində yerləşən NtDelayExecution funksiyası çağrılır.

NtDelayExecution

Diqqər yetirsəniz NtDelayExecution funksiya içərisində digərlərindən nisbətən fərqli əməliyyatların icra olunduğunu görə bilərsiniz.

İlk olaraq EAX registerinə 0x62 dəyəri mənimsədilir. Daha sonra EDX registerinə KiFastSystemCall adresi mənimsədilir və EDX registeri içərisində yerləşən prosedur (KiFastSystemCall) çağrılır. KiFastSystemCall simvolu 7FFE0300 adresinə qarşılıq gəlir.

Bu adres isə 772E6C00 adresinə pointerdir.

Burada ilk olaraq ESP-nin dəyəri EDX registerinə köçürülür və SYSENTER əmri çağrılır.

SYSENTER

P.S burada bütovlükdə Sistem çağırış mexanizmi haqqında məlumat verilməmişdir. Ətraflı məlumatı internet üzərindən əldə edə bilərsiniz. Windows əməliyyat sistemində kritik funksiyalar ring 0 (kernel) tərəfində yerləşir. Intel tərəfindən təqdim edilən SYSENTER təlimatı (32 bit ƏS – istisnalar var) ilə cari axın (user  - ring 3) kernel tərəfinə (ring -0) keçir və lazımi funksiya çağrılır.  Çağrılacaq funksiyalar SSDT (System Service Descriptor table və ya KiServiceTable) adı verilən bir tablo içərisində saxlanılır. Bu tablo içərisində isə əməliyyatı icra edəcək funksiya-ya keçid üçün pointer saxlanılır.

Daha yaxşı anlamanız üçün gəlin ilk olaraq bu tabloya göz gəzdirək. Tablo uzun olduğu üçün bir qismi göstərilmişdir.

Yuxarıda NtDelayExecution funksiyasına diqqət etsəniz ilk olaraq EAX registerinə 0x62 dəyərinin mənimsədildiyini görə bilərsiniz.

Burada 0x62 (98) dəyəri SSDT içərisində index-ə işarə edir. Gəlin bu index-in hansı rutinə qarşılıq gəldiyinə baxaq.

Yəni istifadəçi səviyyəsində çağrılan Sleep funksiyası son olaraq SYSENTER təlimatı ilə kernel (user3) səviyyəsinə keçir. Daha sonra EAX registerində olan dəyəri (index) üzərindən SSDT tablosunda saxlanılan kernel rutini çağrılır. EDX registerində isə sözü gedən funksiya -ya göndərilən parametrlər saxlanılır. Çarı axın ring 3 -dən ring -0 -a keçirilir və system servisi çağrılır.

SSDT haqqında ətraflı məlumat almaq üçün İstinadlar bölməsinə baxın.

Artıq Sleep funksiyası çağrıldığı zama icra edilən əməliyyat (lar) zənciri ilə tanış olduq. Qayıdaq yenidən hook mexanizminə.

Hook əməliyyatı necə icra olunur

Hər hansı bir funksiyanı hook etmək üçün funksiya kodlarına müdaxilə edilir. Bu haqda ətraflı məlumat üçün başlıq qismində sizə təqdim edilən məqaləni oxumağınız məsləhər görülür. Qısa olaraq desək hər hansı bir hook edilmək istənən funksiya yaddaşda olan başqa bir funksiya (adresə) yönləndirilir. Test üçün gəlin ESET Nod32 antivirusunun “ESET Deep Behavioral Inspection” mühərrikinə göz gəzdirək.

Eset sözü gedən modulu proseslər tərəfinən icra edilən şübhəli aktivlikləri izləmək üçün istifadə edir. P.S Hook əməliyyatı istifadəçi səviyyəsində (ring 3) icra edilir.

PatchGuard

Əvvəllər məşhur antivius proqram təminatları şübhəli davranışları aşkar etmək üçün windows kernel funksiyalarını (SSDT) hook edirdilər. Microsoft ilk dəfə 2005-ci ildə 64 bit Windows ƏS üçün PatchGuard-ı tanıtdı. Bu təhlükəsizlik mexanizmi ilə artıq Microsoft, Windows ƏS-nin kernel funksiyalarına müdaxilənin qarşısını alacağını bildirdi. Hər hansı bir şəkildə kernel funksiyalarına müdaxilə edilərsə, ƏS dərhal BSOD (Blue Screen of Death) verir və sistemin fəaliyyətini dayandırır. Bu səbəbdən artıq antiviruslar hook əməliyyatlarını istifadəçi səviyyəsində icra edirlər. https://en.wikipedia.org/wiki/Kernel_Patch_Protection

Modul monitorinq əməliyyatını ntdll.dll içərisində yerləşən bəzi kritik funksiyaları hook etmək ilə həyata keçirir. İlk olaraq izlənmək istənən proses yaddaşına sözü gedən modul inyeksiya edilir.  Daha sonra izlənmək istənən funksiya JMP təlimatı ilə ESET modulu içərisində yerləşən hook (proxy) funksiyasına yönləndirirlir. Belə olan halda ESET çağrılan orijinal funksiya-ya göndərilən bütün parametrlərə müdaxilə və müşahidə etmək imkanı əldə edir.

Misal olaraq yuxarıda bizim tərəfimizdən yazılan Sleep proqamına göz gəzdirək. Sleep.exe -ni işə salaraq, proses yaddaşına hansı modulların yükləndiyinə baxaq.

Daha sonra hansı funksiyaların "ESET Deep Behavioral Inspection" mühərriki tərəfindən hook edildiyinə baxırıq.

Tabloya baxdığımız zaman ESET modulunun ntdll.dll içərisində bir çox funksiyanı hook etdiyini görə bilərik.

Funksiyaların hook edildiyi necə aşkar edilir?

Yuxarıdakı şəkildə ESET-in hansi funksiyaları hook etdiyini aşkar etmək üçün alətin dəstəyini aldıq. Bəs alət hansi funksiyaların hook edildiyini necə aşkar edir?

Bunun üçün dinamik kitabxana içərisində olan bütün funksiya adresləri götürülür.

Daha sonra həmin adreslərdə olan kodlar ilə (hamısına ehtiyyac yoxdur, misal ilk 5 bayt) orjinal kitabxana faylında olan kodlar qarşılaşdırılır. Nəticə eyni deyilsə funksiyanın hook edildiyinə qərar verilir.

Daha yaxşı anlamaq üçün kiçik bir test keçirək. ESET inspeksiya modulunu debug edilən prosesin yaddaşına yükləmir. Kiçik bir kod yazaraq proqramı normal şəkildə və debug rejimində işə salaraq yuxarıdakı tabloda olan funksiayaların hər hansı birinin adresində yerləşən kodlara baxaq.

MemRead.exe

int main(void)
{
	BYTE buff[5] = {0};
	PVOID NtCloseAddr = NULL;
	int i = 0;

	NtCloseAddr = (PVOID)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtClose");

	memcpy(buff, NtCloseAddr, 5);

	for(i=0;i < 5;i++)
	{
		printf("%02X ", buff[i]);
	}

}

Yuxarıdakı kod ilk olaraq GetProcAddress funksiyasının dəstəyi ilə ESET-in normalda hook etdiyi NtClose funksiyasının adresini götürür. Daha sonra həmin funksiyanın ilk 5 baytını lokal dəyişənə köçürür və köçürülən 5 baytlıq məlumatı ekrana yazdırır.

Debug rejim (ESET proses yaddaşına öz modulunu inyeksiya etmir)

Sözü gedən funksiya ntdll kitabxanasını  disassemble edərək baxdığımız zamanda eyni məlumatı görə bilərik.

İndi isə gəlin proqramı debug rejimdə deyil, normal şəkildə icra edək və nəticəyə baxaq.

Gördüyünüz kimi ESET tərəfindən monitorinqi aparılan NtClose funksiyasının ilk 5 baytı fərqli oldu. Bu isə funksiyanın hook edildiyini göstərir.

Funksiyanı kimin (hansı modulun) hook etdiyi necə aşkar edilir?

Cavab: Hook edilən funksiyanın hansı adresə yönləndirildiyini (JMP təlimatı ilə) aşkar edərək.

Yuxarıdakı kodlara diqqət etsəniz E9 (JMP təlimatının opkodudur). Ondan sonra gələn adres isə ESET inspeksiya modulunun içərisində yönləndirilən rutinin adresidir. Proses modulları içərisində bu adresin hansı modulun yaddaş aralığında olduğunu aşkar edərək funksiyanı kimin hook etdiyini anlamaq mümkündür.

Hook mühərriklərindən yayınmaq

Keçək mövzumuzun əsas qisminə. Testlərimizi Sleep.exe ilə Sleep funksiyası üzərindən aparacağıq.

İlk olaraq Sleep əmri çağrıldığı zaman hansı kitabxanalar və funksiyalara müraciət edildiyini xatırladaq.

Kernel32.dll->Sleep->KernelBase.dll->SleepEx->NTDLL.DLL->NtDelayExecution

Sleep.exe proqramını işə salaraq NtDelayExecution funksiyasına hook tətbiq edək və nəticəyə baxaq.

Note. NtDelayExecution funksiyası “undocumented” funksiyadır. Microsoft bu haqda ətraflı məlumat vermir. Lakin bəzi mənbələrdə bu haqda məlumat əldə etmək mümkündür.

NtDelayExecution funksiya prototipi:

NTSYSAPI 
NTSTATUS
NTAPI NtDelayExecution(IN BOOLEAN Alertable, IN PLARGE_INTEGER DelayInterval);

Funksiya 2 ədad parametr qəbul edir.

  1. State – Alertable (Ətraflı məlumat üçün SleepEx)
  2. Vaxt aralığı (LARGE_INTEGER strukturuna pointer)

Hook edilən funksiya (NtDelayExecution) nəticəsi:

Monitorinq proqramı ilə NtDelayExecution funksiyasına hansı parametrlərin göndərildiyini gördük. Antiviruslar, Sandbox və s. sistemlərin artıq proqram təminatları necə izlədiyi haqqında yetəri qədər məlumat əldə etdikdən sonra keçik yayınma prosesinə.

Sleep funksiyasının ntdll.NtDelayExecution çağırdığını, NtDelayExecution funksiyasının isə son mərhələdə SYSENTER əmri ilə özünə gələn parametrləri kernel tərəfinə keçirərək cari axını dondurduğunu öyrəndik. Bəs oxşar əməliyyatı NTDLL.NtDelayExecution-a toxunmadan (yan keçərək) necə icra edə bilərik?

Hook mühərriklərindən yayınmaq üçün NTDLL.NtDelayExecution funksiyası ilə eyni əməliyyatı icra edəcək bizə aid olan (MRL_NtDelayExecution) funksiyasını yazmaq lazımdır. Belə olan halda hook mühərriki NTDLL içərisində olan NtDelayExecution funksiyasını hook etsə belə bizim funksiyadan xəbərdar olmayacaq və biz birbaşa sistem çağırışları ilə cari axını dondura biləcəyik. Aşağıda hazırladığımız diaqram üzərindən nədən bəhs etdiyimizi daha yaxşı anlayacaqsınız.

Yuxarıda normal axın (hook mühərriki məhz bunu hissədə dayanır), aşağıda isə klonlaşdırılmış NtDelayExecution (hook mühərrikinin görə bilmədiyi hissə) ilə birbaşa sistem çağırışı edilir.

Sözü gedən əməliyyatı icra edəcək tərkib kodu:

__declspec(naked)  DWORD MRL_NtDelayExecution(DWORD bAlert, PLARGE_INTEGER pLarge)
{
	__asm
	{
		mov eax, 0x62;
		mov edx, 7FFE0300h;
		call dword ptr [edx];
		retn 8;
	}
}

DWORD MRL_Sleep(DWORD dwMilliseconds)
{
	
	LARGE_INTEGER li = {0};
	li.QuadPart = -(LONGLONG)(dwMilliseconds * 10000);
	
	MRL_NtDelayExecution(0, &li);
}

Nəticə:

 

 

İstinadlar

[1] http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FThread%2FNtDelayExecution.html
[2] https://www.malwaretech.com/2015/04/intercepting-all-system-calls-by.html
[3] https://www.evilsocket.net/2014/02/11/On-Windows-syscall-mechanism-and-syscall-numbers-extraction-methods/
[4] https://en.wikipedia.org/wiki/Kernel_Patch_Protection
[5] http://web.archive.org/web/20141028130908/https://quequero.org/2014/10/kaspersky-hooking-engine-analysis/
[6] https://web-assets.esetstatic.com/wls/en/papers/resources/ESET_Deep_Behavioral_Inspection.pdf
[7] https://www.cynet.com/attack-techniques-hands-on/api-hooking/

Press ESC to close