Debug Windows Service

Posted on Mar 13, 2024

Recently at $dayjob I had to deal with a malware that was run as a Windows Service. After some searching I found an old article that exemplifies how to do it. This post will not bring anything new, but serves as a reference for myself, with a script that facilitates the debugging.

The idea behind this setup is to use Global Flags to spawn Microsoft’s Console Debugger (CBD) which will expose a debugging server, and then use WinDBG to attach to CBD.

Requirements

To be able to debug the service we need the following:

  • Global Flags (gflags) - which allows us to spawn a debugger when a given program is executed, you can get it here;
  • Console Debugger (cdb) - which will be the debugger that launches when the program is executed, available from the previous link as well;
  • WinDBG - which we’ll use as the interface to attach to cdb. I personally use the new WinDBG, which is available here. The older version comes with the debugger tools.

Prerequisites

As we are launching a debugger for a service, the debugger will stop the service execution until we tell it to continue. This is an issue as the service is supposed to set its status, and if it doesn’t do it in a given time, Windows assumes that the service did not start correctly and terminates the process. To overcome this we need to change the ServicesPipeTimeout value to a bigger value:

reg add "HKLM\SYSTEM\CurrentControlSet\Control" /v ServicesPipeTimeout /t REG_DWORD /d 86400000 /f

After setting this we need to restart the machine for the changes to take effect.

Debugging

Creating the Service

I took the batch script from here and made some modifications, including adding support for both 32 and 64 bit services. The script is the following:

The script takes 2 arguments, the first argument is the architecture (32 or 64), and the second argument is the fullpath to the service DLL. The script should be run in a CMD with Administrator privileges. An example of creating a 32 bit service would be:

create_service.bat 32 C:\sample.dll
💡 The script always create a service with the name `EVILSVC`

Configuring gflags

We’ll now configure gflags to spawn cdb and expose a debugging server so that we can then attach to it.

Configuration is very straightforward: we just need to tell it that when malsvchost.exe (a copy of svchost.exe) spawns, we want to run it inside a debugger. The following image shows how gflags should look.

gflags Configuration

The Debugger field contains the path to cdb.exe as well as the options to expose a debugging server:

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe -server tcp:port=5505

Start and Debug the Service

We can now start the service and attach WinDBG to CDB. Starting is pretty straightforward:

sc start EVILSVC

And if we check the processes, we can see a new cdb.exe process, whose parent is services.exe and has the malsvchost.exe as its child:

Process Explorer

As we’ve modified the ServicesPipeTimeout value, we don’t need to rush to attach to cdb. We can now open WinDBG and connect to a remote debugger with the following string:

tcp:Port=5505,Server=localhost

We ideally want to avoid all the svchost.exe code and just break when we load our service. We can do that with the following command:

sxe ld sample.dll

Which will cause a module load exception when the module sample.dll is loaded. In practice this will break on ntdll!NtMapViewOfSection, we just need to g until we reach the module load exception:

0:000> sxe ld sample.dll
0:000> g
ModLoad: 00000000`77120000 00000000`7712a000   C:\Windows\System32\wow64cpu.dll
ModLoad: 00000000`75190000 00000000`75280000   C:\Windows\SysWOW64\KERNEL32.DLL
ModLoad: 00000000`75770000 00000000`759aa000   C:\Windows\SysWOW64\KERNELBASE.dll
ModLoad: 00000000`75090000 00000000`75106000   C:\Windows\SysWOW64\sechost.dll
ModLoad: 00000000`762f0000 00000000`763af000   C:\Windows\SysWOW64\RPCRT4.dll
ModLoad: 00000000`769c0000 00000000`76ae0000   C:\Windows\SysWOW64\ucrtbase.dll
(199c.10ec): WOW64 breakpoint - code 4000001f (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
ntdll_77130000!LdrpDoDebuggerBreak+0x2b:
771e1c12 cc              int     3
0:000> g
*** WARNING: Unable to verify checksum for c:\sample.dll
ModLoad: 00000000`741b0000 00000000`741bf000   c:\sample.dll
ntdll!NtMapViewOfSection+0x14:
00007ff9`67d6d564 c3              ret

From here we can set breakpoints wherever we’re interest in the service, like entry, ServiceMain or Update.

Happy Debugging!