{"id":9665,"date":"2024-11-23T10:28:53","date_gmt":"2024-11-23T10:28:53","guid":{"rendered":"https:\/\/www.hexacorn.com\/blog\/?p=9665"},"modified":"2024-11-23T10:28:53","modified_gmt":"2024-11-23T10:28:53","slug":"how-to-debug-windows-service-processes-in-the-most-old-school-possible-way","status":"publish","type":"post","link":"https:\/\/www.hexacorn.com\/blog\/2024\/11\/23\/how-to-debug-windows-service-processes-in-the-most-old-school-possible-way\/","title":{"rendered":"How to debug Windows service processes in the most old-school possible way&#8230;"},"content":{"rendered":"\n<p>Debugging Service Processes on Windows is a bit tricky &#8211; the old IFO \/ Debugger trick doesn&#8217;t work anymore, because services <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/framework\/windows-services\/introduction-to-windows-service-applications\">run in their own session<\/a>.<\/p>\n\n\n\n<p>Also, when you attempt to debug a service process by attaching your debugger to it, you will often come across this error message:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">ERROR_SERVICE_REQUEST_TIMEOUT\n\n1053 (0x41D)\n\nThe service did not respond to the start or control request in a timely fashion.<\/pre>\n\n\n\n<p>or its GUI equivalent:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc_timeout.png\"><img decoding=\"async\" loading=\"lazy\" width=\"352\" height=\"164\" src=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc_timeout.png\" alt=\"\" class=\"wp-image-9666\" srcset=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc_timeout.png 352w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc_timeout-300x140.png 300w\" sizes=\"(max-width: 352px) 100vw, 352px\" \/><\/a><\/figure>\n\n\n\n<p>Luckily, we can adjust the value of this timeout by modifying the following Registry DWORD value:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\ServicesPipeTimeout<\/pre>\n\n\n\n<p>The <em><a href=\"https:\/\/learn.microsoft.com\/en-us\/troubleshoot\/windows-server\/system-management-components\/service-not-start-events-7000-7011-time-out-error\">ServicesPipeTimeout<\/a><\/em> value represents the time in milliseconds before a service process execution times out.<\/p>\n\n\n\n<p>We can modify this value and set it to say&#8230; 5 minutes = 300,000, and then we must restart our test system.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>The next problem is catching the moment the service process executable actually starts. <\/p>\n\n\n\n<p>Here, the good ol&#8217; &#8216;never-ending loop&#8217; 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 &#8216;jump to the beginning of the jump instruction&#8217; aka a never-ending loop.<\/p>\n\n\n\n<p>With that in place we are now ready to go.<\/p>\n\n\n\n<p>The last thing to do is launching an elevated instance of your favorite user-mode debugger &#8212; this is to make sure we can attach it to a privileged service process.<\/p>\n\n\n\n<p>Let&#8217;s go:<\/p>\n\n\n\n<ul>\n<li>Modify the <em>ServicesPipeTimeout<\/em> timeout value<\/li>\n\n\n\n<li>Restart the system<\/li>\n\n\n\n<li>Stop the target service process if it is running (helps to change it to &#8216;Demand Start&#8217; as well)<\/li>\n\n\n\n<li>Patch the target service process binary&#8217;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&#8217; 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\\&lt;target service>\\ImagePath)<\/li>\n\n\n\n<li>Launch the debugger, elevated<\/li>\n\n\n\n<li>Start the target service<\/li>\n\n\n\n<li>Go to the debugger and attach it to the service process<\/li>\n\n\n\n<li>You should now see the debugger breaking on the never-ending loop<\/li>\n\n\n\n<li>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)<\/li>\n\n\n\n<li>Patch the EB FE back to original bytes<\/li>\n\n\n\n<li>The program may now runaway, but your hardware breakpoint should stop the execution on the next instruction<\/li>\n\n\n\n<li>Start putting the breakpoints on APIs you want to break on:\n<ul>\n<li>StartServiceCtrlDispatcherA<\/li>\n\n\n\n<li>StartServiceCtrlDispatcherW<\/li>\n\n\n\n<li>OpenSCManagerA<\/li>\n\n\n\n<li>OpenSCManagerW<\/li>\n\n\n\n<li>CreateServiceA<\/li>\n\n\n\n<li>CreateServiceW<\/li>\n\n\n\n<li>RegisterServiceCtrlHandlerA<\/li>\n\n\n\n<li>RegisterServiceCtrlHandlerW<\/li>\n\n\n\n<li>RegisterServiceCtrlHandlerExA<\/li>\n\n\n\n<li>RegisterServiceCtrlHandlerExW<\/li>\n\n\n\n<li>SetServiceStatus<\/li>\n\n\n\n<li>etc.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Run!<\/li>\n\n\n\n<li>Analyze!<\/li>\n<\/ul>\n\n\n\n<p>We can test this process using the <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/services\/the-complete-service-sample\">SvcName service example<\/a> from Microsoft. The only modification to their source code we need to add is this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">StringCbPrintf(szPath, MAX_PATH, TEXT(\"\\\"%s_patched\\\"\"), szUnquotedPath);<\/pre>\n\n\n\n<p>inside the <em>Svc.cpp<\/em> file.<\/p>\n\n\n\n<p>This will ensure that our compiled <em>Svc.exe<\/em> can still work, but the installation of the service will point its binary path to <em>Svc.exe_patched<\/em> (that&#8217;s the one with the entry point we will manually patch to EB FE).<\/p>\n\n\n\n<p>The moment we attach the debugger:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc1.png\"><img decoding=\"async\" src=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc1-1024x210.png\" alt=\"\" class=\"wp-image-9667\" width=\"512\" srcset=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc1-1024x210.png 1024w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc1-300x62.png 300w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc1-768x158.png 768w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc1-500x103.png 500w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc1.png 1085w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>We now patch the entry point back and our hardware breakpoint stops the execution:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc2.png\"><img decoding=\"async\" src=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc2-1024x232.png\" alt=\"\" class=\"wp-image-9668\" width=\"512\" srcset=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc2-1024x232.png 1024w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc2-300x68.png 300w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc2-768x174.png 768w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc2-500x113.png 500w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc2.png 1035w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>We can let the code run until the breakpoint on <em>StartServiceCtrlDispatcherA<\/em>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><a href=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc3.png\"><img decoding=\"async\" src=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc3.png\" alt=\"\" class=\"wp-image-9669\" width=\"512\" srcset=\"https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc3.png 992w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc3-300x55.png 300w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc3-768x141.png 768w, https:\/\/www.hexacorn.com\/blog\/wp-content\/uploads\/2024\/11\/svc3-500x92.png 500w\" sizes=\"(max-width: 992px) 100vw, 992px\" \/><\/a><\/figure>\n\n\n\n<p>We are now in control.<\/p>\n\n\n\n<p>Bonus:<\/p>\n\n\n\n<ul>\n<li>It helps to run Procmon with the filter on your service process&#8217; events on as it may speed up analysis<\/li>\n<\/ul>\n\n\n\n<p>Things that are weird:<\/p>\n\n\n\n<ul>\n<li>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&#8217;t know the exact logic at play here<\/li>\n\n\n\n<li>The after-patch-code-execution-runaway is an anomaly; it could be a bug in xdbg, I don&#8217;t know<\/li>\n\n\n\n<li>Microsoft example service process code compiled at first go, w\/o any troubleshooting \ud83d\ude09<\/li>\n\n\n\n<li>The <em>ServicesPipeTimeout<\/em> timeout value affects all services, so if you happen to have some broken service you may see delayed system startup<\/li>\n<\/ul>\n\n\n\n<p>There are probably other, and probably better ways to analyze windows service processes out there, but&#8230; old school is cool.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Debugging Service Processes on Windows is a bit tricky &#8211; the old IFO \/ Debugger trick doesn&#8217;t work anymore, because services run in their own session. Also, when you attempt to debug a service process by attaching your debugger to &hellip; <a href=\"https:\/\/www.hexacorn.com\/blog\/2024\/11\/23\/how-to-debug-windows-service-processes-in-the-most-old-school-possible-way\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[53,9,44],"tags":[],"_links":{"self":[{"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/posts\/9665"}],"collection":[{"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/comments?post=9665"}],"version-history":[{"count":4,"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/posts\/9665\/revisions"}],"predecessor-version":[{"id":9673,"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/posts\/9665\/revisions\/9673"}],"wp:attachment":[{"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/media?parent=9665"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/categories?post=9665"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.hexacorn.com\/blog\/wp-json\/wp\/v2\/tags?post=9665"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}