Windows Service Recovery Action Hijinks!

3 minute read

TL;WR: You can make chaotic-neutral customizations to Windows service recovery actions!
(ᴘʟᴇᴀsᴇ ᴅᴏɴ’ᴛ ᴅᴏ ᴛʜɪs)

Shameless plug: I’m going to be using ImHex in this post and. It has an eye-watering featureset and a bunch of existing patterns WerWolv/ImHex-Patterns. Definitely recommend that you check it out for binary data analysis.

You can actually edit the recovery actions of a service to include more steps that the first, second, and subsequent options available in the UI. Before I explain I just want to get the following points out of the way:

  1. Don’t do this. It’s a bad idea. No support whatsoever and nobody else will ever think to check for it. This whole post is a concertina wire bundle of edge cases.
  2. If you actually need customizable recovery you should do anything else. Alternatives include: watchdog services, wrapper/manager services, scheduled tasks, recovery scripts, hand cranked flashlights, sound powered telephones, or spark-gap radio.

With that out of the way, service recovery actions as described in the services.msc settings are stored as a binary blob in the registry. You’ll find the blob at HKLM:\\SYSTEM\CurrentControlSet\Services\[SERVICENAME] under the FailureActions binary key. I’m going to generate an example blob that does the following so that we can review it in more detail.

  1. Restart the Services
  2. Run a Program (cmd.exe)
  3. Take no Action
Hex View  00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
00000000  00 00 00 00 00 00 00 00  01 00 00 00 03 00 00 00
00000010  14 00 00 00 01 00 00 00  60 EA 00 00 03 00 00 00
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
Fig 1. Example Blob

Fortunately for us this isn’t as opaque as it seems. It’s a pretty simple instance of SERVICE_FAILURE_ACTIONS with an array of SC_ACTION. We can use the following ImHex pattern code to parse this for us:

enum SC_ACTION_TYPE: u32 {
	NONE = 0,
	REBOOT = 2,
	RUN_CMD = 3

struct SC_ACTION {
	u32 Delay;

    u32     dwResetPeriod;
    u32    lpRebootMsg;
    u32    lpCommand;
    u32     cActions;
    SC_ACTION *lpsaActions[cActions] : s32;


Now we pop that blob and the pattern into ImHex and we get a nice, intuitive breakdown of the binary:

A screenshot above blob in ImHex.
Fig 3. ImHex Breakdown

With that you should have an idea where we’re going with this. You can change the lpsaActions array to as many as 1024 entries which I find hilarious. All you need to do is change the cActions value and add your new actions. We’re going to change it to 4 entries.

Var Offset Hex Value    
dwResetPeriod 0x00 : 0x03 0x00000000 0 Seconds    
lpRebootMsg 0x04 : 0x07 0x00000000 N/A (no pointer to a reboot message string)    
lpCommand 0x08 : 0x0B 0x00000001 cmd.exe (value of 1 means load command from other registry keys)    
cActions 0x0C : 0x0F 0x00000004 Array size of 4 Actions    
*lpsaActions 0x10 : 0x13 0x00000014 Pointer to the array (next DWORD)    
lpsaAction[0]:Type 0x14 : 0x17 0x00000001 SC_ACTION_TYPE RESTART (restart service)    
lpsaAction[0]:Delay 0x18 : 0x1B 0x0000EA60 Delay 60000 ms    
lpsaAction[1]:Type 0x1C : 0x1F 0x00000001 SC_ACTION_TYPE RESTART (restart service)    
lpsaAction[1]:Delay 0x20 : 0x23 0x0000EA60 Delay 60000 ms    
lpsaAction[2]:Type 0x24 : 0x27 0x00000001 SC_ACTION_TYPE RESTART (restart service)    
lpsaAction[2]:Delay 0x28 : 0x2B 0x0000EA60 Delay 60000 ms    
lpsaAction[3]:Type 0x2C : 0x2F 0x00000003 SC_ACTION_TYPE RUN_CMD (run a command)    
lpsaAction[3]:Delay 0x30 : 0x33 0x0000EA60 Delay N/A    
Fig 4. New blob breakdown