In a
previous blog I explored the methods and techniques that Zero Access uses to hook kernel functions. One of the functions I mentioned was IoIsOperationSynchronous, which caught the eye of an astute reader. Why does Zero Access hook IoIsOperationSynchronous?
Hooked IoIsOperationSynchronous:
804EE549 loc_804EE549:
804EE549 call 0xF78D40F5 // call Hook_Implementation
804EE54E Subroutine IoIsOperationSynchronous:
804EE54E jmp 0x804EE549 // Jump to Hook call
804EE550 loc_804EE550:
804EE550 push ebp
804EE551 mov ebp,esp
Initially, I thought it was because IoIsOperationSynchronous is called by many system threads and would provide a method to obtain consistent time slices from the system without creating a new system thread. However, there are many kernel functions that would serve the same purpose, so why hook IoIsOperationSynchronous?
IoIsOperationSynchronous does have the added benefit of providing access to many of the system's IRPs, especially file system IRPs. When I examined the code run by the hook, Zero Access uses the IRP passed into IoIsOperationSynchronous.
F78D40F5 Subroutine Hook_Implementation:
F78D40F5 pushad
F78D40F6 mov ecx,dword ptr [esp+0x28] // obtain pIrp passed into IoIsOperationSynchronous
F78D40FA call 0xF78CF940 // call IrpHandler
F78D40FF loc_F78D40FF:
F78D40FF popad
F78D4100 add dword ptr [esp],0x2 // Increment the return address by 2 so we skip over the hook jmp code
F78D4104 ret
What is located at [esp+0x28]? Let's examine the stack after the pushad instruction::
esp+00 [edi]
esp+04 [esi]
esp+08 [ebp]
esp+0c [esp]
esp+10 [ebx]
esp+14 [edx]
esp+18 [ecx]
esp+1c [eax]
esp+20 [804EE54E] // ret address to IoIsOperationSynchronous
esp+24 [xxxxxxxx] // ret address to caller of IoIsOperationSynchronous
esp+28 [pIRP] // arg0 = pIrp
There is the IRP. Just glancing at the IrpHandler() function, the use of "%u.exe" and string comparison stand out immediately. Is it checking the process name?
F78CF940 Subroutine IrpHandler
F78CF940 mov eax,dword ptr [ecx+0x60] // pIrp->Tail.Overlay.CurrentStackLocation
F78CF943 sub esp,0x50 // make space on the stack for some local variables
F78CF946 cmp byte ptr [eax],0x0
F78CF949 jne 0xF78CF9C2
ECX is the IRP pointer passed into IoIsOperationSynchronous. ECX+60 is the offset for pIrp->Tail.Overlay.CurrentStackLocation, so this initial block of code is behaving like IoGetCurrentIrpStackLocation().
F78CF94B loc_F78CF94B:
F78CF94B push esi
F78CF94C mov esi,dword ptr [eax+0x18] // obtain IoStackLocation->FileObject
F78CF94F test esi,esi
F78CF951 je 0xF78CF9C1
The FileObject member of the stack location is retrieved.
F78CF953 loc_F78CF953:
F78CF953 mov eax,dword ptr [0xF78D9984] // data_F78D9984
F78CF958 not eax
F78CF95A push eax
F78CF95B lea ecx,[esp+0x18]
F78CF95F push 0xF78D74A8 // %u.exe
F78CF964 push ecx
F78CF965 call dword ptr [0xF78D7084] // __ptr_ntoskrnl.exe!swprintf[80536BA5]
Zero Access creates a wide string buffer with "%u.exe" formatting. The parameter supplied to printf for the %u is a dword equivalent for the usermode component's name. For example:
NOT 0xA0B8A151
= 0x5F475EAE
= 1598512814 in decimal, so the zero Access usermode component's process (and file) name is 1598512814.exeA5]
This could be used as a file name or a process name comparison.
F78CF96B loc_F78CF96B:
F78CF96B movzx ecx,word ptr [esi+0x30] // length of target filename from FileObject->FileName.Length
F78CF96F add eax,eax
F78CF971 add esp,0xC
F78CF974 lea edx,[esp+0x14] // ptr to swprintf dest buffer
F78CF978 mov word ptr [esp+0xC],ax // set length
F78CF97D mov word ptr [esp+0xE],ax // set max length
F78CF982 mov dword ptr [esp+0x10],edx // set ptr
F78CF986 cmp cx,ax // Is caller target filename long enough?
F78CF989 jb 0xF78CF9C1
Obtains the FileObject->FileName.Length field and uses that to validate the string lengths before doing the actual string comparison. The block also sets up a UNICODE_STRING on the local stack.
F78CF98B loc_F78CF98B:
F78CF98B mov edx,dword ptr [esi+0x34] // ptr to target filename buffer
F78CF98E mov word ptr [esp+0x4],ax
F78CF993 mov word ptr [esp+0x6],ax
F78CF998 movzx eax,ax
F78CF99B sub edx,eax
F78CF99D movzx eax,cx
F78CF9A0 add edx,eax
F78CF9A2 push 0x1
F78CF9A4 lea ecx,[esp+0x8]
F78CF9A8 mov dword ptr [esp+0xC],edx
F78CF9AC push ecx
F78CF9AD lea edx,[esp+0x14]
F78CF9B1 push edx
F78CF9B2 call dword ptr [0xF78D7028] // __ptr_ntoskrnl.exe!RtlEqualUnicodeString[805D67A2]
Compare the FileObject Name from the IRP to the manually constructed string.
F78CF9B8 loc_F78CF9B8:
F78CF9B8 test al,al
F78CF9BA je 0xF78CF9C1 // jmp if RtlEqualUnicodeString returned 0 (strings are not equal)
F78CF9BC loc_F78CF9BC:
F78CF9BC call 0xF78CD650 // call Handle_ZeroAccess_UserModeComponent_IRP
F78CF9C1 loc_F78CF9C1:
F78CF9C1 pop esi
F78CF9C2 loc_F78CF9C2:
F78CF9C2 add esp,0x50
F78CF9C5 ret
If the strings match, Handle_ZeroAccess_UserModeComponent_IRP is called. Otherwise the function returns without doing anything.
So now the question is, what does Handle_ZeroAccess_UserModeComponent_IRP do? Is it trying to block file access to the user mode component's file? Is it using this as a usermode->kernelmode communication mechanism? Or is it returning false data for file reads to prevent AV detection?
To be continued in my next blog...