Pending a native IDA solution, I could solve this for myself with a C program that used the following procedure:
- Read the base executable into memory;
- Read the overlay into memory. Note that this is a regular executable, in its own right.
- In the base executable, adjust the following EXE header items:
b. add the
blocks_in_file from the overlay to the base's,
c. add the
num_relocs from the overlay to the base's.
- Calculate the new executable size; this is the total number of
blocks_in_file * 512 -
original_header_paragraphs*16. You need this number for the new stack segment
- The new
header_paragraphs size - that is, the original one plus the relocations from the overlay - is going to be
reloc_table_offset + 4*num_relocs. Make sure to round up to paragraph size,
At this point the new header should be complete. Write it to a new file. The main executable's relocation table should be unchanged, and you can write it as well.
The overlay's relocation table then needs to be adjusted.
The relocation table itself contains a
segment:offset pair for each relocation that points into the binary executable image to the position where a zero-based segment value is stored (i.e., the value for "the first segment" would be
0000h, and when loaded, the actual segment is added to this value).
Since you added data to the executable part, you need to adjust the linear offset into the file; add the size of the original executable code (which should be rounded upwards to a 512-byte long page). Read the segment at this position, add the size of the original executable code in paragraphs, and write it back. Then add the size of the original executable code to the
segment part of the original relocation. Do this for each relocation in the overlay's table.
After adjusting the overlay's relocation table, write this to your output file, immediately followed by the original executable's data (which is totally unchanged). Then add the changed overlay data to the output file.
One thing to note is that the executable I tested with (only one!) stored a very small stack in the executable as well. I cannot tell if that's usual or not; I had to remove the saved stack from the 'main' code, and add it again after the combined main code + overlay code.
This procedure worked for me because the program I tested it on uses dynamically-loaded overlays. The main program loads the overlay and copies a number of data and code addresses into the main
dseg segment. After that, it calls the overlaid code through these pointers. The overlaid code used
ds "as usual", it simply used the main's
Of course, now the "initialization" routine did not get called, these pointers to the overlaid routines are uninitialized and you have to find out manually what gets copied to where. But at least all code can be loaded into IDA in one go.
Will it still run?
Of course it will. The main code calls
LOAD/EXEC, the overlay gets loaded, pointers get copied, and All is Well. The only thing is that the main executable is suddenly a whole lot larger .. but that extra part is never seen nor used by the original code. (Naturally, the main executable may now be so large that there is no more memory left to load overlays.)
The purpose of this exercise was not to remove the need to load the overlays, only to be able to disassemble the entire thing as one binary.
You can download the C source here: http://www.jongware.com/binaries/combine_exe_ovl.zip
- be warned, it's a pretty dirty hackish piece of code, so it comes with no warranties.