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.