📜 ⬆️ ⬇️

Expansion of the functionality of finished programs

In the software world there are a huge number of programs forgotten by their developers. Well, when there is already a good alternative. And if it is not? The program may catastrophically lack some trifles, some annoying errors may cause a lot of inconvenience to users over the years, and the program may refuse to work on new versions of the OS. Not always there are source codes to put the program in order. If the program is simple, it is not difficult to create an alternative in a short time. But if the program is large and complex, what to do in this case? It is not always rational to spend time and money on the development of a complete analogue, because it is possible to expand the functionality within reasonable limits and correct most of the errors already in the finished executable file.
In this article, techniques for modifying executable files will be demonstrated using the example of expanding the functionality of the legendary game Age of Empires II (real-time strategy).

So, what do we have:


Perhaps you can try to change something without looking at the ban in the license agreement. Don't you think so? :)

Denial of responsibility


The work was carried out solely for educational purposes, and in no case is it a call for violation of the current legislation. The author does not bear any responsibility for the illegal use of the submitted materials.
')

Information about the "patient"


The explored game is written on the Genie engine, the development of which from 1997 to 2000 was carried out by Ensemble Studios. During this time, 4 games from the Age of Empires series were released on this engine. Next Genie was licensed to LucasArts, which in 2001-2002 released 2 more games on this engine, but from the Star Wars series. By this time, the engine is already very outdated, because it worked in 256 color mode, which is very modest compared to games even in 2000. That is why the engine development was stopped, no new games based on it were released.
The game engine Genie downloads all game graphics, music, and most importantly - lists of units and their characteristics - from external files. That is, all games based on this engine for the most part have very similar executable files, and almost all modifications for one game are ported to others without any problems.

What do we want to achieve?


1. Disable CD availability check
In modern laptops (netbooks, etc.) is no longer a rarity when there is no built-in CD drive. Moreover, since the game is very old, the original CD may stop being read, and someone could lose it altogether. As a result, the owners of a completely legal copy of the game lose their ability to launch the game. So, perhaps, let the game run without a disk.

2. Add support for window mode
Age of Empires supports 3 fixed resolutions: 800 × 600, 1024 × 768 and 1280 × 1024. With these resolutions, the picture on widescreen monitors looks stretched, which causes discomfort. You can add support for new permissions to the game, but since the game uses its own interface graphics for each resolution, we’ll leave it for later, and for the time being we will limit ourselves to adding a windowed mode of operation to the game.

3. Add support for configuration files
To enable some system game options, they have to be transmitted via the command line every time the program is started. You can put all the parameters into a shortcut, but after moving the game to another directory, the shortcut will stop working. The way out is to add support for configuration files.

4. Make the game portable
The game is not portable. To run on another computer is not enough to copy the game files, you must go through the entire process of installing the game. In connection with the widespread use of external drives, I would like to endow the game with this property.

5. Correct known errors
In the game interface there are unpleasant errors. For example, in the list of recent games in the IP game connection dialog, previously entered addresses are truncated, which reduces the convenience of the entire fast connection function to nil; you have to manually enter the server address each time.

What do we need for this?


IDA Pro is an interactive disassembler and debugger widely used for reverse engineering. Able to build clear even to beginners flowcharts. All his power is manifested in interactive interaction with the user. After an automatic analysis, the user can give meaningful names to functions and variables, comment on the code, etc. Enough small knowledge in the field of the assembler in order to start doing research programs right now!

Flat Assembler is a freeware multipass assembler. FASM has a small size and a very high compilation speed, has a rich and capacious macro syntax that allows you to automate many routine tasks. One useful feature is the generation of clean machine code without headers, etc. All this is useful to us for the generation of assembly inserts.

Hexplorer is a free HEX editor. It has a simple x86 disassembler, which makes it easier to navigate inside the executable file. It is used directly for manual modification of a binary file without resizing it.

So, let's begin.

Disable CD availability check


IDA will help us find the code that is responsible for checking for the presence of a CD. It turned out to be very easy to do. It is enough to trace the calls of the GetDriveTypeA and GetVolumeInformationA functions that are used to obtain information about the drive, as we find the CD presence check function at 4485A0h (the base address is 400000h, that is, physically in the file the machine code is located at 485A0h), which returns to eax unit.
  CheckCD proc near
     sub esp, 214h
     push ebx
     push esi
     ;  ...
     ;  many checks
     ;  ...
     test eax, eax
     jz cd_check_fail
 cd_check_ok:
     pop esi
     mov al, 1
     pop ebx
     add esp, 214h
     retn 4
 cd_check_fail:
     pop esi
     pop ebx
     add esp, 214h
     retn 4
 CheckCD endp
It is worth noting that this function requests the CDPath value from the registry where the drive letter from which the game was installed is stored. That is, the game is tied specifically to the letter of the disk from which it was installed, and if there is an original disk in another drive, the game will not start anyway. The simplest solution in this case is to replace the jz cd_check_fail command, which takes 2 bytes, with 2 nop operations, so that after checking the disk in eax, one is always returned. However, the function is designed in such a way that if there is no CDPath value in the registry, the function immediately returns 0, which prevents the program from being portable. Therefore, we replace the entire function with 3 simple commands:
  use32
     xor eax, eax
     inc eax
     retn 4 
For obtaining machine codes of these commands FASM is useful to us.
  fasm src.asm dst.bin 
The main advantage of this assembler for us now is that it allows us to generate pure machine code without headers. We get (line by command):
  31 C0
 40
 C2 04 00
That is, we need to place these 6 bytes at the beginning of the function of detecting the presence of a CD, and replace the remaining commands with nop (code 90h).
Source machine codeReceived machine code
  8B 44 24 04 81 EC 14 02
 00 00 85 C0 53 56 8B D9
 ... 
  33 C0 40 C2 04 00 90 90
 90 90 90 90 90 90 90 90
 ... 
In the usual HEX editor, we replace the machine codes mentioned above starting at offset 485A0h, after which the game will never require the CD to run, even if the registry does not have the necessary key. At the same time, we freed 288 bytes for additional machine code, which will be useful to us in the future.

Bug fix


When connected to an IP network game, the game displays a list of IP and game names to which the player has connected before. However, for some reason, the IP address was often not completely saved, several of his last characters were truncated. It is necessary to understand what is happening and correct the error.
Let's look for the code responsible for storing the IP addresses of the latest games in the registry. We know that the game saves a list of the latest IP games in keys with the names like RecentGameName% d and RecentGameAddr% d. Using IDA, we find 3 references to these lines. It is very easy to determine where the necessary code is - in the code with offset 0512116h these keys are written, otherwise - reading. We will not give the found section of machine code - there are about 50 instructions, the general meaning of which can be represented as a small fragment of C code.
  for (int i = 0; i <count; i ++)
 {
	 char * keyname;
	 sprintf (keyname, "RecentGameName% d", i);
	 RegSetVal (1, keyname, gamename [i], strlen (gamename [i]));
	 sprintf (keyname, "RecentGameAddr% d", i);
	 RegSetVal (1, keyname, gameaddr [i], strlen (gamename [i]));
 } 
To set the value in the registry, the game uses its own wrapper function RegSetVal (int, char * ValueName, char * Data, int DataSize) - the name of the function and parameters are of course invented, they are not in the machine code. As you can see, the game saves in the registry exactly as many characters of the IP address as there are in the name of the network game. Apparently, the programmer copied 2 lines of saving the name of the game to save the address, but forgot to correct the name of the array in the strlen call.
To correct the error, we have 2 options: substitute the correct string in strlen or place a constant 16 instead of strlen (maximum length of the address string, for example, "255.255.255.255").
What does the strlen (gamename [i]) call look like in machine code?
  call _sprintf;  + 0x1022CF
 mov edi, ebp
 or ecx, 0FFFFFFFFh
 xor eax, eax
 add esp, 0Ch
 repne scasb
 not ecx
 push ecx 
The compiler optimized the code and replaced the call to the function strlen () by calculating the length in place. In the previous call to strlen (), the same line was used, in the optimization again - the pointer is placed in ebp once during the first call. That is, we do not have a precious pair of bytes in order to place the correct pointer in ebp. You can, of course, make jmp outside the function, then back - but this is not the most beautiful method. Therefore, let us dwell on the simplest version of correcting the error - we will always save 16 bytes of the address. To do this, replace the above machine code with the following:
Assembly codeMachine code
  call _sprintf;  + 0x1022CF
 add esp, 0Ch
 mov ecx, 0Fh 
  E8 CF 22 10 00
 83 4 0
 B9 0F 00 00 00 

Configuration file support


The game supports many useful command line parameters that it would be convenient to save somewhere in the file, so as not to transfer them every time you start it. The best way to implement this is to force the game to load the external library at each start and perform an initialization function. With such an organization, you can add many useful things to configuration files, such as: loading additional libraries (modules) without having to modify the executable file, intercepting registry functions to organize the storage of all game data in the game configuration file, and not in the registry. It would not be amiss to support multiple profiles with different sets of settings with the ability to quickly switch them. So let's get started.
The most convenient connection point for the external library was the ebueulax.dll library download function, which is responsible for displaying the license agreement. It is called almost immediately after the start of WinMain, however, in addition to loading the ebueulax.dll library and executing the EBUEula function, it does a lot of unnecessary actions, so we completely rewrite it so that it loads the config.dll library and performs the LoadConfig library function with a location pointer command line in memory (to change it) as a parameter.
Assembly codeMachine code
  LoadConfigLib proc near
     sub esp, 208h
     push esi
     push edi
     push ebx
     push ecx
     push edx
     mov esi, ds: LoadLibraryA
     push 67B8C4h;  lpLibFileName
     call esi;  LoadLibraryA
     mov ebx, eax
     test ebx, ebx
     jz short loc_587443
     push offset aLoadconfig
     push ebx;  hModule
     call ds: GetProcAddress
     mov edi, eax
     test edi edi
     jz short loc_58743C
     mov eax, [esp + 220h]; ** args
     push eax
     call edi
     jmp short loc_587443
 loc_58743C:
     push ebx;  hLibModule
     call ds: FreeLibrary
 loc_587443:
     pop edx
     pop ecx
     pop ebx
     pop edi
     pop esi
     add esp, 208h
     retn
 LoadConfigLib endp 
 
 81 EC 08 02 00 00
 56
 57
 53
 51
 52
 8B 35 E0 51 63 00
 68 C4 B8 67 00
 FF D6
 8B D8
 85 DB
 74 25
 68 B8 B8 67 00
 53
 FF 15 C8 50 63 00
 8B F8
 85 FF
 74 0C
 8B 84 24 20 02 00 00
 50
 FF D7
 EB 07

 53
 FF 15 20 51 63 00

 5A
 59
 5B
 5F
 5E
 81 C4 08 02 00 00
 C3 
The code above makes several calls to library functions. Their addresses were manually inserted into the machine code. In the next article (if you don’t mind :)) I’ll try to tell you how to use FASM to generate code that is completely ready to be inserted into an executable file.
Replace the original function (starting at offset 187400h) with a new HEX editor. Since the new function is much shorter than the original, do not forget to replace the remaining bytes with the operation code nop - 90h. We received another 192 bytes of free bytes for the additional machine code. This place can be used in the future.
Source machine codeReceived machine code
  81 EC 08 02 00 00 53 56 
 8B 35 E0 51 63 00 57 68
 CC B8 67 00 FF D6 8B D8 
 85 DB 75 0A 5F 5E 5B 81
 C4 08 02 00 00 C3 68 C4 
 B8 67 00 53 FF 15 C8 50
 63 00 8B F8 85 FF 75 13 
 53 FF 15 20 51 63 00 5F
 5E 33 C0 5B 81 C4 08 02 
 00 00 C3 8B 84 24 18 02
 00 00 50 FF D6 8B F0 85
 ... 
  81 EC 08 02 00 00 56 57
 53 51 52 8B 35 E0 51 63
 00 68 C4 B8 67 00 FF D6 
 8B D8 85 DB 74 25 68 B8
 B8 67 00 53 FF 15 C8 50 
 63 00 8B F8 85 FF 74 0C
 8B 84 24 20 02 00 00 50 
 FF D7 EB 07 53 FF 15 20
 51 63 00 5A 59 5B 5F 5E 
 81 C4 08 02 00 00 C3 90
 90 90 90 90 90 90 90 90 
 ... 
The rest of the work is done by the config.dll module. It can be written in C / C ++ / Delphi. Its task is simple - to accept from the executable file a pointer to the application command line and, depending on the settings in the configuration file, modify it. As a bonus, we can use the ability to load additional modules by configuring this in the configuration file. To do this, we simply need to call the WinAPI function LoadLibrary by the name of the module file that needs to be loaded into the address space of the game.

Support windowed mode


Support for windowing is organized as a separate module wndmode.dll, which is loaded by config.dll. This module is engaged in intercepting all calls to DirectDraw, modifies the parameters so that the program works in the window, and only after that transfers control to the original DirectDraw functions.

This is implemented by changing the function being intercepted. The first few bytes of the intercepted function are replaced with the unconditional command to intercept function. This trick is quite simply implemented in WinNT (since WinNT has its own copy of system library images for each process), but it is practically unrealizable in Win9X (because in Win9X, if you can make changes to the system library image, then only in the address spaces of all processes ).

By itself, the wndmode.dll module is a strongly modified version of the d3dhook.dll library, where it is implemented:


Now this library is ready to infiltrate almost any DirectDraw game in order to make it window. So if someone modifies other games, it will come in handy.

What happened?


The modification work has been done a lot; here we have reviewed only a few changes, since the description of even small changes requires tremendous effort. If you wish, you can explore the rest of the modifications in IDA by downloading the finished modified file.

In general, the following results were achieved:


All changes are stable, no failures are detected. In the windowed mode, the problem of displaying the system cursor simultaneously with the game after it leaves the area of ​​the game window and returns back remains unresolved. However, this is caused only by the fact that the game engine is not designed to run in the window and does not provide an option when the cursor leaves the game window. In the next version of the modification this will be fixed.
The logical conclusion of the development of this modification could be the introduction of game room support into the game with the creation of a corresponding game server, since the official game rooms of Age of Empires at Zone were closed a few years ago. All this is quite possible to implement, without having at the same time on hand no source codes.

Conclusion


If you are interested in trying what I did in action, you can download it from my website ( Age of Empires II: The Conquerors (Lite Edition) ).
Forgive me for what is written here about everything at once, and not in great detail. I tried to describe the general scheme. In the following articles, you can consider each stage of modification separately, give some advice, etc.

Source: https://habr.com/ru/post/51857/


All Articles