How to debug Windows service processes in the most old-school possible way…

Debugging Service Processes on Windows is a bit tricky – the old IFO / Debugger trick doesn’t work anymore, because services run in their own session.

Also, when you attempt to debug a service process by attaching your debugger to it, you will often come across this error message:

ERROR_SERVICE_REQUEST_TIMEOUT

1053 (0x41D)

The service did not respond to the start or control request in a timely fashion.

or its GUI equivalent:

Luckily, we can adjust the value of this timeout by modifying the following Registry DWORD value:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ServicesPipeTimeout

The ServicesPipeTimeout value represents the time in milliseconds before a service process execution times out.

We can modify this value and set it to say… 5 minutes = 300,000, and then we must restart our test system.

With that change, we buy a lot of precious time that we can now utilize to attach the debugger to the service process before it times out.

The next problem is catching the moment the service process executable actually starts.

Here, the good ol’ ‘never-ending loop’ trick comes to the rescue. We take the executable that the service points to, and modify its entry point to 2 bytes: EB FE. This is an opcode for ‘jump to the beginning of the jump instruction’ aka a never-ending loop.

With that in place we are now ready to go.

The last thing to do is launching an elevated instance of your favorite user-mode debugger — this is to make sure we can attach it to a privileged service process.

Let’s go:

  • Modify the ServicesPipeTimeout timeout value
  • Restart the system
  • Stop the target service process if it is running (helps to change it to ‘Demand Start’ as well)
  • Patch the target service process binary’s entry point (or any other place where you want to break into when you attach the debugger); note: you can copy the service process’ binary to a different location and patch it, and then modify the service configuration in the Registry to point to it (HKLM\SYSTEM\CurrentControlSet\Services\<target service>\ImagePath)
  • Launch the debugger, elevated
  • Start the target service
  • Go to the debugger and attach it to the service process
  • You should now see the debugger breaking on the never-ending loop
  • Make a hardware execution breakpoint on the next logical instruction after the patched instruction at the entry point; this is your backup plan if the patching you do in the next point causes the program to runaway (not sure why, but it happens under xdbg)
  • Patch the EB FE back to original bytes
  • The program may now runaway, but your hardware breakpoint should stop the execution on the next instruction
  • Start putting the breakpoints on APIs you want to break on:
    • StartServiceCtrlDispatcherA
    • StartServiceCtrlDispatcherW
    • OpenSCManagerA
    • OpenSCManagerW
    • CreateServiceA
    • CreateServiceW
    • RegisterServiceCtrlHandlerA
    • RegisterServiceCtrlHandlerW
    • RegisterServiceCtrlHandlerExA
    • RegisterServiceCtrlHandlerExW
    • SetServiceStatus
    • etc.
  • Run!
  • Analyze!

We can test this process using the SvcName service example from Microsoft. The only modification to their source code we need to add is this:

StringCbPrintf(szPath, MAX_PATH, TEXT("\"%s_patched\""), szUnquotedPath);

inside the Svc.cpp file.

This will ensure that our compiled Svc.exe can still work, but the installation of the service will point its binary path to Svc.exe_patched (that’s the one with the entry point we will manually patch to EB FE).

The moment we attach the debugger:

We now patch the entry point back and our hardware breakpoint stops the execution:

We can let the code run until the breakpoint on StartServiceCtrlDispatcherA:

We are now in control.

Bonus:

  • It helps to run Procmon with the filter on your service process’ events on as it may speed up analysis

Things that are weird:

  • Despite changing the timeout to just 5 minutes, I noticed that I could often analyze the service process for much longer than that; I don’t know the exact logic at play here
  • The after-patch-code-execution-runaway is an anomaly; it could be a bug in xdbg, I don’t know
  • Microsoft example service process code compiled at first go, w/o any troubleshooting 😉
  • The ServicesPipeTimeout timeout value affects all services, so if you happen to have some broken service you may see delayed system startup

There are probably other, and probably better ways to analyze windows service processes out there, but… old school is cool.

Beating the dead horse, only to inject it some more…

The windows shatter attack is so old that it’s time for someone to reinvent it.

This someone could be me.

While looking at wscadminui.exe I noticed that it expects 2 arguments: the first one is a /DefaultProductRequest string, and the second is also a string (a name of an app).

When these are provided, the program calls wscapi.dll::wscLaunchAdminMakeDefaultUI API and passes the app name to it. The wscLaunchAdminMakeDefaultUI in turn, passes the app name to another function called wscShowAMSCNEx. The latter creates a window of a class AMNotificationDialog.

So, running:

wscadminui.exe /DefaultProductRequest foobar

will start the wscadminui.exe process and it will create the AMNotificationDialog window for us:

With that in place, we can look at the window procedure handling the messages for the AMNotificationDialog window:

You can see that it is using WM_NCCREATE message to set a Window Long Pointer at offset 0 to a value provided in that windows message (lParam). What attracts our attention more though is that the very same value is later used as a function pointer — in other words, whatever the offset the Window Long Ptr @0 points to, the code at this offset will be executed!

So, one could inject code into wscadminui.exe process and then execute it using a simple call to SetWindowLongPtr API:

  WinExec ("wscadminui.exe /DefaultProductRequest foobar",0);
  Sleep(1000);
  HWND x = FindWindow("AMNotificationDialog", "");
  if (x != NULL)
  	{
  		SetWindowLongPtr (x, 0, 0x123456789ABCDEF);
  		ShowWindow (x, SW_SHOW);
  	}

Now, the very same program invocation:

wscadminui.exe /DefaultProductRequest foobar

leads to a creation of another window — this time it is of an ANIMATION_TIMER_HWND class (you can see it on the screenshot above). This window’s lifecycle is handled by the UIAnimation.dll, and this is where we can find the implementation of the window’s procedure handling messages for it:

Again, we can easily manipulate this GWLP_USERDATA pointer – a simple snippet like the one below can redirect code execution of the scapegoat wscadminui.exe to the pointer of our liking:

  WinExec ("wscadminui.exe /DefaultProductRequest foobar",0);
  Sleep(1000);
  HWND x = FindWindow("ANIMATION_TIMER_HWND", "");
  if (x != NULL)
  	{
  		SetWindowLongPtr (x, GWLP_USERDATA, 0x123456789ABCDEF);
  		ShowWindow (x, SW_SHOW);
  	}

As usual, there are more examples like this out there, but the point I want to make is that over 20 years after the window shatter attack was described for the first time it is still available to attackers in many forms and places.