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->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+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->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->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"<