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
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:
Now we pop that blob and the pattern into ImHex and we get a nice, intuitive breakdown of the binary:
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 |