Step 2: Writing the Codecave
At this point in the article, a slight problem arises. It will not be a problem to implement our codecave, but it will be a problem to actually write it to the process. The concept of dynamically changing a loaded program's code is another article in itself. Because of this limitation, I will provide the basic functions we will use to accomplish our goals for this article. It will be up to you to research more into them to fully understand what they do and why things are the way they are (something that takes a bit of time and practice). The code is well commented though so only a few concepts need to be explained.
We will generally have at least two functions for each codecave we make. The first function will be the codecave itself. The remaining functions are support functions that are called from the first function to handle additional logic that cannot go in the first function. The codecave function itself has to be a special type of function, a naked function. Here is an excerpt taken from MSDN:
Functions declared with the naked attribute are emitted without prolog or epilog code, enabling you to write your own custom prolog/epilog sequences using the inline assembler. Naked functions are provided as an advanced feature. They enable you to declare a function that is being called from a context other than C/C++, and thus make different assumptions about where parameters are, or which registers are preserved. Examples include routines such as interrupt handlers. This feature is particularly useful for writers of virtual device drivers (VxDs).
When we use a naked function, we have a few guidelines that we must follow. For a list of guidelines that you must follow, please take a look at this article: Rules and Limitations for Naked Functions. An important thing to remember is that you cannot declare variables inside a naked function. Instead, they must be declared outside of the function. If you place a variable declaration in a naked function, you will be referencing an address on the stack. Aside from that, it is advisable to only place the least amount of non-assembly code in the main codecave function as possible. You should place everything else in the support functions.
Since we are using a codecave in a DLL and using the CALL method, we will need a total of two variables and two functions. The first variable will hold the current score obtained from the game. The second variable will hold the return address when we enter and exit our codecave. For this simple example, we will just display the current score to a console window to show that everything works.
What follows now is the relevant code for the codecave implementation. The DllMain and extra utility functions are not shown.
// This variable holds our current score
DWORD currentScore = 0;
// This variable holds the return address, it must be global!
DWORD ExtractScoreRetAddr = 0;
// This is our higher level C++ function that is called to display
// the current score
void DisplayCurrentScore()
{
// Simply display the current score to the console
printf("Current score: %in", currentScore);
}
// This is our codecave function, we must remember to
// make it a "__declspec(naked)" function
__declspec(naked) void CC_ExtractScore(void)
{
__asm
{
// The first thing we must do in our codecave is save
// the return address from the top of the stack
pop ExtractScoreRetAddr
// Since we know the current score is in EDX, copy it over into
// our variable
MOV currentScore, EDX
// Remember that we need to preserve registers and the stack!
PUSHAD
PUSHFD
}
// Invoke our C++ function now
DisplayCurrentScore();
__asm
{
// Restore everything to how it was before
POPFD
POPAD
// This is an important part here, we must execute whatever
// code we took out for the codecave.
// Also note that we have to use 0x3B9ACA00 for a HEX #
// and not 3B9ACA00, which would be misinterpreted by the compiler.
CMP EDX, 0x3B9ACA00
// The last thing we must do in our codecave is push
// the return address back onto the stack and then RET back
push ExtractScoreRetAddr
ret
}
}
// Initialize function called by the loader's inject function
extern "C" __declspec(dllexport) void Initialize()
{
// We will place a codecave at the address 0x01017580.
// The function will call CC_ExtractScore
// and one extra byte will be NOP'ed
Codecave(0x01017580, CC_ExtractScore, 1);
// Create a console since we are in a DLL
CreateConsole();
}
That is not so bad now, is it? Remember that only the codecave related code is shown, the rest of the code is part of the project. If we recall all of the theory we learned in the second section of this article, everything seems to be here. We first have our codecave save the return address to a variable. That is seen in the line pop ExtractScoreRetAddr. Next, we save our data to another variable as seen in the line MOV currentScore, EDX. Before we call our support function, we save the registers and flags to the stack and then restore them later. We finally execute the code that was taken out for the codecave, which was the line CMP EDX, 0x3B9ACA00, and return to where we are supposed to be using the stored return address.
The Initialize function is an exported function that our Loader will use to inject the DLL into the Pinball game so the EXE will call the codecave. The function Codecave will do most of the work for us in writing out the codecave itself. Since we are using the CALL method to get into the codecave, the Codecave function creates a CALL instruction using the address of the function that we pass in. The last parameter is the NOP count that we specify to erase extra bytes that need to be taken out. This number is simply the total bytes you want to codecave minus 5. In this case, there are 6 bytes in the CMP EDX, 3B9ACA00 instruction as we can see from the assembly listing. Five of these bytes are for the codecave, so that leaves on extra byte we must NOP so the program does not crash on resume due to an invalid byte sequence. If there were exactly 5 bytes, we would not need any NOPs. If there were 7 bytes we would need 2 NOPs.
To create future codecave DLLs yourself, you can use the provided project as a template. You will have to update the Initialize function as well as implement new codecaves for yourself. Now that we have the Codecave DLL completed, it is time to move to the last step in this process, putting it all together!
Step 3: Putting it all Together
In order to see the final product of what we have just made in action, we must find a way to get our DLL into the Pinball process. Once we do that, the Initialize function has to be called to make the DLL patch the program so the codecave is called. After that, whenever the score is updated, our codecave will be triggered and we will see our score being displayed to the console.
// Program entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// Structures for creating the process
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
BOOL result = FALSE;
// Strings for creating the program
char exeString[MAX_PATH + 1] = {0};
char workingDir[MAX_PATH + 1] = {0};
// Holds where the DLL should be
char dllPath[MAX_PATH + 1] = {0};
// Get the current directory
GetCurrentDirectory(MAX_PATH, workingDir);
// Build the full path to the EXE
_snprintf(exeString, MAX_PATH, ""%s\PINBALL.EXE" -quick", workingDir);
// Set the static path of where the Inject DLL is, hardcoded for a demo
_snprintf(dllPath, MAX_PATH, "PinballCodecave.dll");
// Need to set this for the structure
si.cb = sizeof(STARTUPINFO);
// Try to load our process
result = CreateProcess(NULL, exeString, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, workingDir, &si, p);
if(!result)
{
MessageBox(0, "Process could not be loaded!", "Error", MB_ICONERROR);
return -1;
}
// Inject the DLL, the export function is named 'Initialize'
Inject(pi.hProcess, dllPath, "Initialize");
// Resume process execution
ResumeThread(pi.hThread);
// Standard return
return 0;
}
Remember that only the WinMain is shown above, the rest of the code is in the project file itself. At this point we now have all of the main pieces. It is time to test! Copy over the "PinballCodecave.dll" and "PinballLoader.exe" files into your Pinball folder, which is "C:Program FilesWindows NTPinball" by default. Run the "PinballLoader.exe" file to start up the Pinball game, you should see a DOS console popup as well. If everything works out well, you should be able to play the game. When you score points, your current score should be outputted to the console window. Neat, huh? If anything goes wrong, take a look over the address you are placing the codecave on as well as all of the minor details. The good thing about this low level stuff is if you mess up, 90% of the time you will be aware of it since the program crashes or does something unexpected.
Conclusion
It has been a long and challenging journey, but you have finally reached the end. At this point, you should have gained a basic, but complete, understanding of what a codecave is. We have seen through the theory section of what a codecave does and how it can be used. You now know three important attributes of codecave that you have to keep in mind when designing your own. They are the location of the codecave, the entry and exit points, and the stack and register preservation techniques. You have a practical example to reference as well as a template code at your disposal for your future projects. You might have even gained a little knowledge of a few new tools that you can use in your future endeavors.
The big question left that always comes at any end is "what now?" From here, you can continue to explore using codecave to accomplish various tasks that you might not have been able to do before. Sometimes it is challenging to figure out "what to do", so if you do not have anything to work on immediately, do not worry about it! Just remember what you have learnt and perhaps you can apply it someplace else in the future. I hope you have enjoyed this article.
License: The Code Project Open License (CPOL)
Author: Drew_Benton