Debugging services startup in Svchost from a kernel mode debug session

Windows shared services allow us to run system services together in a single service by having multiple DLLs run in a single process called Svchost. This allows Windows to have many services to run with the overhead of a single process.   You can find more information about shared services here and here.

 

I like to do a lot of my user mode debugging from a kernel mode debugger. I can just attach to a Hyper-V VM though a virtual serial port and all my tools are available to me without having to set up a user mode debugger on the target, plus if my application calls into kernel mode I’m all set.

 

The Svchost process won’t load the DLL until it’s time to start the service, so it can be tricky to set the correct breakpoint to catch the service starting up. This is complicated by the fact that the dll entry point will usually be <module>!ServiceMain, however this is not necessarily the case.

 

We can leverage the fact that svchost.exe exists in the same shared memory across all the Svchost processes, and a change to this shared memory will affect all of the svchost process.  When you set a user mode breakpoint from a kernel mode debugger the debugger changes the user mode code by inserting an int 3 (the debugger changes the code back when it is executed).  We can take advantage of this functionality to set one breakpoint that will fire from any svchost process.

 

If we can get past the Svchost process loading the service dll, we can find the host process main function or any other function we want to set a breakpoint on.

 

First, find the process address of any Svchost. I’m just showing the first of a long list of processes:

 

kd> !process  0 0 svchost.exe

PROCESS fffffa80098c5970

    SessionId: 0  Cid: 0270    Peb: 7fffffd8000  ParentCid: 0204

    DirBase: 35986000  ObjectTable: fffff8a001115b20  HandleCount: 343.

    Image: svchost.exe

 

Now let’s get into the context of this Svchost (it doesn’t matter which one) and set a breakpoint.  .process –i sets an invasive breakpoint so we can get into the active context of the process.

kd> .process -i fffffa80098c5970

You need to continue execution (press 'g' <enter>) for the context

to be switched. When the debugger breaks in again, you will be in

the new process context.

 

Then go the debugger to enter the active process.

kd> g

Break instruction exception - code 80000003 (first chance)

nt!DbgBreakPointWithStatus:

 

GetServiceMainFunctions()  is where Svchost both loads the service DLL and gets the service main function so let’s set a breakpoint for it:

kd> .reload /user

Loading User Symbols

.......................................................

kd> bp svchost!GetServiceMainFunctions

 

Now I just go the debugger and start my service.  I’ll use the workstation service as an example (I had previously stopped the workstation service for this test).

 

kd> g

 

At a command prompt on the target I enter:

C:\> net start workstation

 

In the debugger I get:

 

Breakpoint 0 hit

svchost!GetServiceMainFunctions:

0033:00000000`ffc813cc fff3            push    rbx

kd> k

Child-SP          RetAddr           Call Site

00000000`0339f7f8 00000000`ffc8128d svchost!GetServiceMainFunctions

00000000`0339f800 000007fe`fe38a82d svchost!ServiceStarter+0xba

00000000`0339f890 00000000`77a5f56d sechost!ScSvcctrlThreadW+0x25

00000000`0339f8c0 00000000`77b93281 kernel32!BaseThreadInitThunk+0xd

00000000`0339f8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

 

svchost!GetServiceMainFunctions is called from Svchost!ServiceStarter.  I just leave GetServiceMainFunctions with gu (Go Up). Now the service dll will have been loaded and I can load symbols for wkssvc.dll and get the service main() function.

 

kd> gu

svchost!ServiceStarter+0xba:

0033:00000000`ffc8128d 488b7c2430      mov     rdi,qword ptr [rsp+30h] ss:002b:00000000`0339f830=000007fefb443a7c

kd> k

Child-SP          RetAddr           Call Site

00000000`0339f800 000007fe`fe38a82d svchost!ServiceStarter+0xba

00000000`0339f890 00000000`77a5f56d sechost!ScSvcctrlThreadW+0x25

00000000`0339f8c0 00000000`77b93281 kernel32!BaseThreadInitThunk+0xd

00000000`0339f8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

 

Now loading my user symbols I can get all the symbols for wkssvc.dll

kd> .reload /user

Loading User Symbols

................................................................

kd> dps 00000000`0339f800  l7

00000000`0339f800  00000000`00000000

00000000`0339f808  00000000`00000000

00000000`0339f810  00000000`00000000

00000000`0339f818  00000000`00000000

00000000`0339f820  00000000`00000000

00000000`0339f828  00000000`00000000

00000000`0339f830  000007fe`fb443a7c wkssvc!ServiceMain <- Here is the Service Main function.

 

I can now set a breakpoint on wkssvc!ServiceMain,  and as a bonus I now have access to all available breakpoints in wkssvc.dll

 

kd> bp wkssvc!ServiceMain

kd> g

Breakpoint 1 hit

wkssvc!ServiceMain:

0033:000007fe`fb443a7c 48895c2408      mov     qword ptr [rsp+8],rbx ss:002b:00000000`0339f800={wkssvc!SvchostPushServiceGlobals (000007fe`fb442ff4)}

kd> k

Child-SP          RetAddr           Call Site

00000000`0339f7f8 00000000`ffc81344 wkssvc!ServiceMain

00000000`0339f800 000007fe`fe38a82d svchost!ServiceStarter+0x1e8

00000000`0339f890 00000000`77a5f56d sechost!ScSvcctrlThreadW+0x25

00000000`0339f8c0 00000000`77b93281 kernel32!BaseThreadInitThunk+0xd

00000000`0339f8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

Now I can debug the service on startup, not caring what instance of svchost it’s in with full user and kernel mode access.