Przy okazji badań opisanych w ostatnim poście, odkryłem w sterowniku gmer’a pewien błąd logiczny mogący powodować nieprawidłowe działanie losowych aplikacji.
Żeby przybliżyć sobie kwestię, o której będę pisał polecam zajrzeć do punktu drugiego poprzedniego postu, a dokładnie do implementacji rozwiązanie w ring0.
Do rzeczy, naszym obiektem zainteresowań jest najnowszy sterownik gmer’a na dzień 22.07.2010:
FileVersion : 1, 0, 15, 4809 built by: WinDDK
[+]Lokalizacja problemu
Jeśli jakiś plik nie może zostać usunięty w standardowy sposób, gmer stara się zamknąć wszystkie otwarte handlery odwołujące się do tego pliku, a następnie go usunąć. Moim zdaniem, niestety implementacja tej procedury przez twórcę gmer’a nie została do końca dobrze przemyślana.
Rzućmy okiem na tą prockę:
.text:0001B488 ; int __stdcall sub_1B488(wchar_t *filePath, int a2) .text:0001B488 sub_1B488 proc near ; CODE XREF: sub_1CB32+299p .text:0001B488 .text:0001B488 PFILE_OBJECT = dword ptr -30h .text:0001B488 var_2C = dword ptr -2Ch .text:0001B488 var_28 = dword ptr -28h .text:0001B488 PSYSTEM_HANDLE_INFORMATION= dword ptr -24h .text:0001B488 pEPROCESS = dword ptr -20h .text:0001B488 index = dword ptr -1Ch .text:0001B488 ms_exc = CPPEH_RECORD ptr -18h .text:0001B488 filePath = dword ptr 8 .text:0001B488 a2 = dword ptr 0Ch .text:0001B488 .text:0001B488 push 20h .text:0001B48A push offset stru_1EED0 .text:0001B48F call __SEH_prolog .text:0001B494 xor esi, esi .text:0001B496 mov [ebp+pEPROCESS], esi .text:0001B499 lea eax, [ebp+var_28] .text:0001B49C push eax ; int .text:0001B49D push 10h ; SystemHandleInformation .text:0001B49F call wrap_NtQuerySystemInformation .text:0001B4A4 mov [ebp+PSYSTEM_HANDLE_INFORMATION], eax .text:0001B4A7 cmp eax, esi .text:0001B4A9 jz error .text:0001B4AF mov [ebp+ms_exc.disabled], esi .text:0001B4B2 mov [ebp+index], esi .text:0001B4B5 mov ebx, ds:NtClose .text:0001B4BB .text:0001B4BB loc_1B4BB: ; CODE XREF: sub_1B488+119j .text:0001B4BB mov ecx, [ebp+index] .text:0001B4BE cmp ecx, [eax] ; eax = NumberOfHandles .text:0001B4C0 jnb end_of_SYSTEM_HANDLE_INFORMATION .text:0001B4C6 shl ecx, 4 .text:0001B4C9 lea edi, [ecx+eax+4] .text:0001B4CD mov [ebp+var_2C], edi .text:0001B4D0 mov esi, [edi+8] .text:0001B4D3 mov [ebp+PFILE_OBJECT], esi .text:0001B4D6 test esi, esi .text:0001B4D8 jz next_struct .text:0001B4DE push esi ; VirtualAddress .text:0001B4DF call ds:MmIsAddressValid .text:0001B4E5 test al, al .text:0001B4E7 jz next_struct .text:0001B4ED cmp word ptr [esi], 5 .text:0001B4F1 jnz next_struct .text:0001B4F7 mov eax, [esi+34h] .text:0001B4FA test eax, eax .text:0001B4FC jz next_struct .text:0001B502 push eax ; VirtualAddress .text:0001B503 call ds:MmIsAddressValid .text:0001B509 test al, al .text:0001B50B jz next_struct .text:0001B511 push [ebp+filePath] ; VirtualAddress .text:0001B514 call ds:MmIsAddressValid .text:0001B51A test al, al .text:0001B51C jz short next_struct .text:0001B51E push [ebp+filePath] ; wchar_t * .text:0001B521 call ds:wcslen .text:0001B527 pop ecx .text:0001B528 sub eax, 6 .text:0001B52B mov [ebp+a2], eax .text:0001B52E movzx ecx, word ptr [esi+30h] ; fileObject->FileName.Length .text:0001B532 shr ecx, 1 .text:0001B534 cmp ecx, eax .text:0001B536 jnz short next_struct .text:0001B538 push eax ; size_t .text:0001B539 mov eax, [ebp+filePath] .text:0001B53C add eax, 0Ch .text:0001B53F push eax ; wchar_t * .text:0001B540 push dword ptr [esi+34h] ; fileObject->FileName.Buffer .text:0001B543 call ds:_wcsnicmp .text:0001B549 add esp, 0Ch .text:0001B54C test eax, eax .text:0001B54E jnz short next_struct .text:0001B550 lea eax, [ebp+pEPROCESS] .text:0001B553 push eax .text:0001B554 push dword ptr [edi] .text:0001B556 call ds:PsLookupProcessByProcessId .text:0001B55C mov status, eax .text:0001B561 test eax, eax .text:0001B563 jnz short next_struct .text:0001B565 mov eax, [ebp+pEPROCESS] .text:0001B568 cmp eax, current_process_EPROCESS .text:0001B56E jz short handleBelongsToCurrentProcess .text:0001B570 push eax .text:0001B571 call ds:KeAttachProcess .text:0001B577 movzx eax, word ptr [edi+6] .text:0001B57B push eax ; Handle .text:0001B57C call ebx ; NtClose .text:0001B57E mov status, eax .text:0001B583 call ds:KeDetachProcess .text:0001B589 jmp short loc_1B592 .text:0001B58B ; --------------------------------------------------------------------------- .text:0001B58B .text:0001B58B handleBelongsToCurrentProcess: ; CODE XREF: sub_1B488+E6j .text:0001B58B movzx eax, word ptr [edi+6] .text:0001B58F push eax ; Handle .text:0001B590 call ebx ; NtClose .text:0001B592 .text:0001B592 loc_1B592: ; CODE XREF: sub_1B488+101j .text:0001B592 mov ecx, [ebp+pEPROCESS] ; Object .text:0001B595 call ds:ObfDereferenceObject .text:0001B59B .text:0001B59B next_struct: ; CODE XREF: sub_1B488+50j .text:0001B59B ; sub_1B488+5Fj ... .text:0001B59B inc [ebp+index] .text:0001B59E mov eax, [ebp+PSYSTEM_HANDLE_INFORMATION] .text:0001B5A1 jmp loc_1B4BB .text:0001B5A6 ; --------------------------------------------------------------------------- .text:0001B5A6 .text:0001B5A6 _end: ; DATA XREF: .rdata:stru_1EED0o .text:0001B5A6 xor eax, eax .text:0001B5A8 inc eax .text:0001B5A9 retn
Gdzie tu jest problem?
Przyjrzyjmy się samej kwesti odnalezienia przez gmer’a uchwytu, który jest powiązany z plikiem do którego handlery chcemy pozamykać. Autor gmer’a postanowił tutaj skorzystać z dostarczonego w
strukturze SYSTEM_HANDLE_INFORMATION FILE_OBJECT’u i slusznie,no ale…
Sprawdzenie rozpoczyna się od zbadania długości scieżki podanej jako argument i tej zawartej w file object’e.
.text:0001B51E push [ebp+filePath] ; wchar_t * .text:0001B521 call ds:wcslen .text:0001B527 pop ecx .text:0001B528 sub eax, 6 .text:0001B52B mov [ebp+a2], eax .text:0001B52E movzx ecx, word ptr [esi+30h] ; fileObject->FileName.Length .text:0001B532 shr ecx, 1 .text:0001B534 cmp ecx, eax .text:0001B536 jnz short next_struct
Jeśli długości są identyczne przechodzimy do następnego testu. Zanim jednak przejde dalej odczuwam wielką potrzebe czepienia się jednej kwestii związaną z dobrymi praktykami jak i optymalizacją kodu.
Chodzi dokładnie o ten fragment:
.text:0001B51E push [ebp+filePath] ; wchar_t * .text:0001B521 call ds:wcslen
Ten fragment znajduję się wewnątrz pętli i przy każdej iteracji, kiedy obiektem okazuje się plikiem, wywoływana jest zupełnie bez sensownie funkcja wcslen na zmiennej filePath, która została podana jako parametr do sub_1B488!!! Takie wielokrotne wyliczanie tej samej wartości wewnątrz pętli prowadzi do niczego innego niż zmniejszenia wydajności kodu a w tym przypadku wydajność będzie mala wręcz wprost proporcjonalnie do długości stringu. Także, nie polecam takich konstrukcji ;)(btw: jak zobaczyłem ten twór to przypomniał mi się post Malcom’a „Programowanie wymaga myślenia!” http://blog.malcom.pl/2009/09/09/programowanie-wymaga-myslenia/, polecam).
Kontynuujmy:
.text:0001B538 push eax ; length .text:0001B539 mov eax, [ebp+filePath] .text:0001B53C add eax, 0Ch .text:0001B53F push eax ; wchar_t * .text:0001B540 push dword ptr [esi+34h] ; fileObject->FileName.Buffer .text:0001B543 call ds:_wcsnicmp
Tutaj dokonywane jest już porównanie stringów reprezentujących ścieżki do plików.
Ale, ale !!! Nie jest to porównanie zawierające litery partycji, na której dany plik się znajduje!!!
Ścieżki mają tu postać:
„\folder\file.ext”
Tylko i wyłącznie takie porównanie doprowadza do sytuacji, w której handler powiązany z plikiem o takiej samej scieżce jak scieżka podana jako parametr funkcji, lecz znajdujący się na innej partycji zostanie zamknięty!!!
[+]Przykład
Na potrzeby testów stworzyłem niewielką aplikację, która otwiera handler do pliku bez praw do usunięcia( bez FILE_SHARE_DELETE, w celu zmuszenia gmer’a do wywołania powyżej omawianej procki). Kod tej aplikacji jest następujący:
int main(int argc, char* argv[]) { HANDLE h = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if(h == INVALID_HANDLE_VALUE) { cout<<"[+]INVALID_HANDLE_VALUE"<<endl; cout<<getLastError(); return 0; } cout<<"[+]File opened"<<endl; getchar(); } [/cpp] oraz narzędzia Handle v3.42, do wylistowania wszystkich otwartych handlerów przez dany proces. Na dwóch konsolach odpaliłem swój kod skompilowany do pliku zlo.exe z następującymi parametrami: [code] Console_1: zlo.exe C:\install.exe [/code] oraz [code] Console_2: zlo.exe E:\install.exe [/code] Po odpaleniu aplikacji wylistowałem otwarte przez nie handler: [code] // Przed proba usuniecia C:\install.exe [+] c:\Documents and Settings\virtual&amp;gt;handle.exe -p zlo.exe Handle v3.42 Copyright (C) 1997-2008 Mark Russinovich Sysinternals - www.sysinternals.com ------------------------------------------------------------------------------ zlo.exe pid: 1928 SLAVE\virtual C: File (RW-) C:\Documents and Settings\virtual 7E8: File (RW-) C:\WINDOWS\WinSxS\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_x-ww_d08d0375 7F4: File (RW-) C:\install.exe ------------------------------------------------------------------------------ zlo.exe pid: 932 SLAVE\virtual C: File (RW-) C:\Documents and Settings\virtual 7E8: File (RW-) C:\WINDOWS\WinSxS\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_x-ww_d08d0375 7F4: File (RW-) E:\install.exe [/code] Następnie przy pomocy gmer’a usunąłem plik C:\install.exe i ponownie wylistowałem handlery: [code] //Po usunieciu przez gmer'a C:\install.exe c:\Documents and Settings\virtual&amp;gt;handle.exe -p zlo.exe Handle v3.42 Copyright (C) 1997-2008 Mark Russinovich Sysinternals - www.sysinternals.com ------------------------------------------------------------------------------ zlo.exe pid: 1928 SLAVE\virtual C: File (RW-) C:\Documents and Settings\virtual 7E8: File (RW-) C:\WINDOWS\WinSxS\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_x-ww_d08d0375 ------------------------------------------------------------------------------ zlo.exe pid: 932 SLAVE\virtual C: File (RW-) C:\Documents and Settings\virtual 7E8: File (RW-) C:\WINDOWS\WinSxS\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_x-ww_d08d0375 [/code] Efekt potwierdził moją teorie, tak jak widać uchwyt do E:\install.exe również został zamknięty. [+]Rada Dodanie pełnego badania ścieżek do plików m.in z wykorzystaniem np. IoQueryFileDosDeviceName. No i chyba tyle;)<!--:--><!--:en-->Messing a little bit recently with a gmer's code I discovered logical bug which can cause abnormal behavior of an random applications. Our object of interest will be the newest gmer's driver on day 22.07.2010. FileVersion : 1, 0, 15, 4809 built by: WinDDK [+]Localization of a problem If some file can't be deleted in the usual way, gmer will try to close all opened handlers related with this file and after it delete file. In my opinion implementation of this procedure has not been thought out correctly. Let's take a look at this routine: .text:0001B488 ; int __stdcall sub_1B488(wchar_t *filePath, int a2) .text:0001B488 sub_1B488 proc near ; CODE XREF: sub_1CB32+299 p .text:0001B488 .text:0001B488 PFILE_OBJECT = dword ptr -30h .text:0001B488 var_2C = dword ptr -2Ch .text:0001B488 var_28 = dword ptr -28h .text:0001B488 PSYSTEM_HANDLE_INFORMATION= dword ptr -24h .text:0001B488 pEPROCESS = dword ptr -20h .text:0001B488 index = dword ptr -1Ch .text:0001B488 ms_exc = CPPEH_RECORD ptr -18h .text:0001B488 filePath = dword ptr 8 .text:0001B488 a2 = dword ptr 0Ch .text:0001B488 .text:0001B488 push 20h .text:0001B48A push offset stru_1EED0 .text:0001B48F call __SEH_prolog .text:0001B494 xor esi, esi .text:0001B496 mov [ebp+pEPROCESS], esi .text:0001B499 lea eax, [ebp+var_28] .text:0001B49C push eax ; int .text:0001B49D push 10h ; SystemHandleInformation .text:0001B49F call wrap_NtQuerySystemInformation .text:0001B4A4 mov [ebp+PSYSTEM_HANDLE_INFORMATION], eax .text:0001B4A7 cmp eax, esi .text:0001B4A9 jz error .text:0001B4AF mov [ebp+ms_exc.disabled], esi .text:0001B4B2 mov [ebp+index], esi .text:0001B4B5 mov ebx, ds:NtClose .text:0001B4BB .text:0001B4BB loc_1B4BB: ; CODE XREF: sub_1B488+119 j .text:0001B4BB mov ecx, [ebp+index] .text:0001B4BE cmp ecx, [eax] ; eax = NumberOfHandles .text:0001B4C0 jnb end_of_SYSTEM_HANDLE_INFORMATION .text:0001B4C6 shl ecx, 4 .text:0001B4C9 lea edi, [ecx+eax+4] .text:0001B4CD mov [ebp+var_2C], edi .text:0001B4D0 mov esi, [edi+8] .text:0001B4D3 mov [ebp+PFILE_OBJECT], esi .text:0001B4D6 test esi, esi .text:0001B4D8 jz next_struct .text:0001B4DE push esi ; VirtualAddress .text:0001B4DF call ds:MmIsAddressValid .text:0001B4E5 test al, al .text:0001B4E7 jz next_struct .text:0001B4ED cmp word ptr [esi], 5 .text:0001B4F1 jnz next_struct .text:0001B4F7 mov eax, [esi+34h] .text:0001B4FA test eax, eax .text:0001B4FC jz next_struct .text:0001B502 push eax ; VirtualAddress .text:0001B503 call ds:MmIsAddressValid .text:0001B509 test al, al .text:0001B50B jz next_struct .text:0001B511 push [ebp+filePath] ; VirtualAddress .text:0001B514 call ds:MmIsAddressValid .text:0001B51A test al, al .text:0001B51C jz short next_struct .text:0001B51E push [ebp+filePath] ; wchar_t * .text:0001B521 call ds:wcslen .text:0001B527 pop ecx .text:0001B528 sub eax, 6 .text:0001B52B mov [ebp+a2], eax .text:0001B52E movzx ecx, word ptr [esi+30h] ; fileObject-&amp;amp;amp;amp;amp;gt;FileName.Length .text:0001B532 shr ecx, 1 .text:0001B534 cmp ecx, eax .text:0001B536 jnz short next_struct .text:0001B538 push eax ; size_t .text:0001B539 mov eax, [ebp+filePath] .text:0001B53C add eax, 0Ch .text:0001B53F push eax ; wchar_t * .text:0001B540 push dword ptr [esi+34h] ; fileObject-&amp;amp;amp;amp;amp;amp;amp;gt;FileName.Buffer .text:0001B543 call ds:_wcsnicmp .text:0001B549 add esp, 0Ch .text:0001B54C test eax, eax .text:0001B54E jnz short next_struct .text:0001B550 lea eax, [ebp+pEPROCESS] .text:0001B553 push eax .text:0001B554 push dword ptr [edi] .text:0001B556 call ds:PsLookupProcessByProcessId .text:0001B55C mov status, eax .text:0001B561 test eax, eax .text:0001B563 jnz short next_struct .text:0001B565 mov eax, [ebp+pEPROCESS] .text:0001B568 cmp eax, current_process_EPROCESS .text:0001B56E jz short handleBelongsToCurrentProcess .text:0001B570 push eax .text:0001B571 call ds:KeAttachProcess .text:0001B577 movzx eax, word ptr [edi+6] .text:0001B57B push eax ; Handle .text:0001B57C call ebx ; NtClose .text:0001B57E mov status, eax .text:0001B583 call ds:KeDetachProcess .text:0001B589 jmp short loc_1B592 .text:0001B58B ; --------------------------------------------------------------------------- .text:0001B58B .text:0001B58B handleBelongsToCurrentProcess: ; CODE XREF: sub_1B488+E6 j .text:0001B58B movzx eax, word ptr [edi+6] .text:0001B58F push eax ; Handle .text:0001B590 call ebx ; NtClose .text:0001B592 .text:0001B592 loc_1B592: ; CODE XREF: sub_1B488+101 j .text:0001B592 mov ecx, [ebp+pEPROCESS] ; Object .text:0001B595 call ds:ObfDereferenceObject .text:0001B59B .text:0001B59B next_struct: ; CODE XREF: sub_1B488+50 j .text:0001B59B ; sub_1B488+5F j ... .text:0001B59B inc [ebp+index] .text:0001B59E mov eax, [ebp+PSYSTEM_HANDLE_INFORMATION] .text:0001B5A1 jmp loc_1B4BB .text:0001B5A6 ; --------------------------------------------------------------------------- .text:0001B5A6 .text:0001B5A6 _end: ; DATA XREF: .rdata:stru_1EED0 o .text:0001B5A6 xor eax, eax .text:0001B5A8 inc eax .text:0001B5A9 retn
Where the problem is?
Let’s take a look on issue where the gmer searches for a opened handles associated with interesting for us file to close them.
Gmer’s author decided here to use delivered FILE_OBJECT with SYSTEM_HANDLE_INFORMATION structure (NtQuerySystemInformation) and rightly, but …
Verification starts with comparison file path length of file passed as argument and this one included in a FILE_OBJECT.
.text:0001B51E push [ebp+filePath] ; wchar_t * .text:0001B521 call ds:wcslen .text:0001B527 pop ecx .text:0001B528 sub eax, 6 .text:0001B52B mov [ebp+a2], eax .text:0001B52E movzx ecx, word ptr [esi+30h] ; fileObject-&amp;amp;amp;amp;amp;gt;FileName.Length .text:0001B532 shr ecx, 1 .text:0001B534 cmp ecx, eax .text:0001B536 jnz short next_struct
If a lengths are identical we go to a another test. Before I will go further I feel big need to niggle one piece of code and to mention here about good programming practices and code optimization. Exactly here is the piece of code:
.text:0001B51E push [ebp+filePath] ; wchar_t * .text:0001B521 call ds:wcslen
Above fragment of code is inside the loop so during each iteration when object is a file, is called totally without any sense function wcslen which calculates length of filePath passed to sub_1B488 as argument!!! That multiple calculation of the same value leads to nothing else like loss of code productivity and in this case productivity will be decreased proportion to filePath length. Honestly, I highly don’t recommend such a constructions ;).
Go ahead:
.text:0001B538 push eax ; length .text:0001B539 mov eax, [ebp+filePath] .text:0001B53C add eax, 0Ch .text:0001B53F push eax ; wchar_t * .text:0001B540 push dword ptr [esi+34h] ; fileObject-&amp;amp;amp;amp;amp;gt;FileName.Buffer .text:0001B543 call ds:_wcsnicmp
Above we can see code resposibles of comparison two strings representing paths to files.
But, but,but !!! It’s not comparison of file paths which contain volume letter!!!.
Paths look here in the following way:
„folderfile.ext”
Only that kind of comparison leads to situation where handle related with file which path is the same as this one passed by us as argument, but located on totally different volume will be also closed!!!.
[+]Example
For tests purpose I created an small application which opens a handle to a file without delete privilege ( without FILE_SHARE_DELETE ) to force gmer to use above explained procedure. Code of this application you can find below:
cpp]
int main(int argc, char* argv[])
{
HANDLE h = CreateFileA(argv[1],
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if(h == INVALID_HANDLE_VALUE)
{
cout<<"[+]INVALID_HANDLE_VALUE"<