Windows Service Recovery Action Blob Manglement!
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:
- 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.
- 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.
- Restart the Services
- Run a Program (cmd.exe)
- 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
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,
RESTART = 1,
RUN_CMD = 3
};
struct SC_ACTION {
SC_ACTION_TYPE Type;
u32 Delay;
};
struct SERVICE_FAILURE_ACTIONS {
u32 dwResetPeriod;
u32 lpRebootMsg;
u32 lpCommand;
u32 cActions;
SC_ACTION *lpsaActions[cActions] : s32;
};
SERVICE_FAILURE_ACTIONS actions @ 0x00;
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.](/assets/images/windows-service-recovery-imhex.png)
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 |