Ever since the "great blackout" of 2003, I've been using Energizer-brand UPSs to protect some of my computers. For Linux, I had to write a driver from scratch, as no UPS application supported these devices. For Windows, the situation was different: the UPS came with a CD-ROM containing a somewhat tacky, but functional control program, the latest version of which can also be downloaded from the Energizer Web site. (NB: Please do not ask me for copies of this software. I have no right to distribute copyrighted code that is owned by Energizer. Please nag Energizer to provide better support for the products they sell.)
This version (4.2.3)* works quite well, except for one thing: a nasty handle leak, that causes its handle count to go up by one every second.
I requested help from Energizer, but needless to say, my query remains unanswered to this day. So, I decided to take matters into my own hands, so to speak, and attack the problem with a debugger.
It didn't take long to zero in on the cause. The application makes an overlapped I/O call to read data from the UPS; for this purpose, it creates a new event handle. This handle, however, is not destroyed when the read operation is completed. Here is the relevant bit of code:
0040C8E4 push 4BC544h "" 0040C8E9 push 1 0040C8EB push 1 0040C8ED push 0 0040C8EF call 004B73C6 ;; CreateEvent(NULL, 1, 1, ""); 0040C8F4 mov dword ptr [ebp-48h],eax 0040C8F7 mov ecx,dword ptr [ebp-48h] 0040C8FA mov dword ptr [ebp-74h],ecx 0040C8FD xor eax,eax 0040C8FF mov dword ptr [ebp-7Ch],eax 0040C902 xor edx,edx 0040C904 mov dword ptr [ebp-78h],edx 0040C907 push dword ptr [ebp-48h] 0040C90A call 004B75B8 ;; ResetEvent() 0040C90F lea ecx,[ebp-84h] 0040C915 push ecx 0040C916 lea eax,[ebp-50h] 0040C919 push eax 0040C91A push 40h 0040C91C lea edx,[ebp-0C4h] 0040C922 push edx 0040C923 mov ecx,dword ptr [ebp+8] 0040C926 push dword ptr [ecx+320h] 0040C92C call 004B75B2 ;; ReadFile() 0040C931 mov dword ptr [ebp-4Ch],eax 0040C934 push 1F4h 0040C939 push dword ptr [ebp-48h] 0040C93C call 004B766C ;; WaitForSingleObject() 0040C941 mov dword ptr [ebp-4Ch],eax 0040C944
What is missing is a call to CloseHandle() with the same parameter that is passed to WaitForSingleObject(), i.e., the event handle. Inserting code into a binary file is nasty business, but fortunately, the programmer did something inefficient here that allows us to free up some space. He creates an event that is initially set, followed by an explicit call to ResetEvent(); instead, we can create an event that is initially not set, eliminate the ResetEvent() call, and insert the CloseHandle() call, having to move only a few bytes of code as a result. This is what the patched file should look like (patches shown in red):
0040C8E4 push 4BC544h "" 0040C8E9 push 0 0040C8EB push 1 0040C8ED push 0 0040C8EF call 004B73C6 ;; CreateEvent(NULL, 1, 1, ""); 0040C8F4 mov dword ptr [ebp-48h],eax 0040C8F7 mov ecx,dword ptr [ebp-48h] 0040C8FA mov dword ptr [ebp-74h],ecx 0040C8FD xor eax,eax 0040C8FF mov dword ptr [ebp-7Ch],eax 0040C902 xor edx,edx 0040C904 mov dword ptr [ebp-78h],edx 0040C907 push dword ptr [ebp-48h] 0040C90A call 004B75B8 ;; ResetEvent() 0040C907 lea ecx,[ebp-84h] 0040C90D push ecx 0040C90E lea eax,[ebp-50h] 0040C911 push eax 0040C912 push 40h 0040C914 lea edx,[ebp-0C4h] 0040C91A push edx 0040C91B mov ecx,dword ptr [ebp+8] 0040C91E push dword ptr [ecx+320h] 0040C924 call 004B75B2 ;; ReadFile() 0040C929 mov dword ptr [ebp-4Ch],eax 0040C92C push 1F4h 0040C931 push dword ptr [ebp-48h] 0040C934 call 004B766C ;; WaitForSingleObject() 0040C939 mov dword ptr [ebp-4Ch],eax 0040C93C push dword ptr [ebp-48h] 0040C93F call 004B73B4 ;; CloseHandle() 0040C944
Because only a few lines have been relocated, only one relative address (that of the WaitForSingleObject() call) had to be modified.
For many system calls, this program uses a lookup table with the actual call addresses; i.e., the subroutine calls in the code above are referencing this lookup table. Fortunately, the table already contained an entry for the CloseHandle() function, so the call could be added easily; I just had to find the right entry in the lookup table first.
I made these edits to the binary file using a Linux system and a version of the vi editor that can handle binary files. The patched application runs well, its handle count now a constant 60 instead of an ever increasing number.
Of course not everyone has access to a binary-capable editor. For this reason, I put together a small program that can patch the Energizer FileSaver.exe executable in place. USE IT AT YOUR OWN RISK: This program should be considered untested. It either works, or it destroys your system and your UPS goes up in flames. I accept no responsibility if that happens!
To use this program, copy it (epatch.exe) to the directory that contains Energizer FileSaver.exe, then run it from the command line, as in the following example (yes, you need to use the Command Prompt I'm afraid):
C:\>cd "\Program Files\Energizer FileSaver" C:\Program Files\Energizer FileSaver>copy "Energizer FileSaver.exe" "Energizer
FileSaver (backup).exe" 1 file(s) copied. C:\Program Files\Energizer FileSaver>epatch
Patch completed. C:\Program Files\Energizer FileSaver>
As this example demonstrates, it is a VERY GOOD IDEA to make a backup copy of the program first before patching it.
The patch program will fail if
- It cannot find the file Energizer FileSaver.exe; for instance, you run it in the wrong directory
- The file Energizer FileSaver.exe cannot be opened for writing (e.g., it is currently running)
- The file Energizer FileSaver.exe is of the wrong version (not 4.2.3) or has already been patched.
Last but not least, to those who're interested in the dirty details, here's the C-language source for epatch.exe:
#include <stdio.h> const unsigned char patch[] = { /* 0000BF00: */ 0x45, 0x84, 0x33, 0xD2, 0x89, 0x55, 0x88, 0x8D, 0x8D, 0x7C, 0xFF, 0xFF, 0xFF, 0x51, 0x8D, 0x45, /* 0000BF10: */ 0xB0, 0x50, 0x6A, 0x40, 0x8D, 0x95, 0x3C, 0xFF, 0xFF, 0xFF, 0x52, 0x8B, 0x4D, 0x08, 0xFF, 0xB1, /* 0000BF20: */ 0x20, 0x03, 0x00, 0x00, 0xE8, 0x89, 0xAC, 0x0A, 0x00, 0x89, 0x45, 0xB4, 0x68, 0xF4, 0x01, 0x00, /* 0000BF30: */ 0x00, 0xFF, 0x75, 0xB8, 0xE8, 0x33, 0xAD, 0x0A, 0x00, 0x89, 0x45, 0xB4, 0xFF, 0x75, 0xB8, 0xE8, /* 0000BF40: */ 0x70, 0xAA, 0x0A, 0x00, 0x8B, 0x45, 0xB4, 0x83, 0xE8, 0x01, 0x72, 0x10, 0x2D, 0x01, 0x01, 0x00, }; void main(void) { FILE *f; unsigned char buf[256]; unsigned char sum = 0; int i; memset(buf, 0, sizeof(buf)); f = fopen("Energizer FileSaver.exe", "rb"); if (f == NULL) { fprintf(stderr, "Could not open Energizer FileSaver.exe\n"); exit(1); } fseek(f, 0xBF00, SEEK_SET); fread(buf, sizeof(buf), 1, f); for (i = 0; i < sizeof(buf); i++) { sum = sum ^ buf[i]; } fclose(f); if (sum != 62) { fprintf(stderr, "Checksum failure (wrong version?)\n"); exit(1); } f = fopen("Energizer FileSaver.exe", "r+b"); if (f == NULL) { fprintf(stderr, "Could not open Energizer FileSaver.exe for writing.\n"); fprintf(stderr, "Is the file in use (is FileSaver running)?\n"); exit(1); } fseek(f, 0xBF00, SEEK_SET); fwrite(patch, sizeof(patch), 1, f); fclose(f); fprintf(stderr, "Patch completed.\n"); }
* Since this writing, Energizer released at least one new version (5.0), but that is for a completely different version of their product line. Externally, the UPSs look the same and have the same product numbers, but internally they're altogether different. In practice, this means that the 5.0 (or later) version of the software is not compatible with older UPSs, and the 4.x version of the software is not compatible with newer units.