• Welcome to Valhalla Legends Archive.
 

Automatic Memory Patching Class

Started by iago, August 22, 2003, 04:23 AM

Previous topic - Next topic

iago

I wrote a little class that'll patch a program's memory and, optionally, generate a little wrapper for it, and it will restore the patched locations in the destructor.

Here are things to watch out for:
- It has to patch over an exact number of assembly commands, otherwise it'll leave half an instruction which will probably blow stuff up
- It patches with a call, so if you patch over anything that uses the stack pointer (pushes, pops, function calls, local variables, etc.), it'll blow up (solution: don't auto-generate wrapper and fix it yourself)
- Also, patching over jump's is a bad idea, unless the source/destination are both passed over (solution: see above)
- The function that it calls should be declared as "void __declspec(naked) __fastcall MyFunc()", and should end with a "ret".

With that stuff aside, here is the code:
// MemoryEdit.h
// by iago
// Created 7/28/2003
//
// This is a class that will allow a user to easily edit a process's memory and restore it
// when the program ends

#ifndef MEMORYEDIT_H
#define MEMORYEDIT_H

#include <windows.h>
#include <assert.h>
#include <windows.h>
#include <iostream>
#include <string>
#include "Buffer.h"

using namespace std;

class MemoryEdit
{
public:
   BYTE Wrappers[10000]; // The buffer that will hold the wrappers
   DWORD ptrToPatches[2000]; // An array of pointers to the patches
   DWORD PatchLengths[2000]; // This array goes beside ptrToPatches, and holds the lengths
   DWORD NumberOfPatches; // The number of places that have been patched
   BYTE *NextWrapper; // The next free wrapper
   HANDLE Process; // Handle to the process (assigned using =, so it won't know if it's closed)
public:
   static const BYTE JMP = 0xE9;
   static const BYTE CALL = 0xE8;
   static const BYTE NOP = 0x90;

   MemoryEdit();
   MemoryEdit(HANDLE Process);
   ~MemoryEdit();
   
   // BytesToOverwrite must be at least 5 and has to be an exact number of machine code commands,
   // overwriting part of a command will cause problems.  Also, don't overwrite a jump or call that
   // goes outside of the overwritten text (or any offset-operator that goes outside of the patch)
   // because it will cause a problem :)
   // Also, patching over a push will cause problems.. you'll get over it :P
   bool PatchMem(void *AddressToEdit, void *FunctionToCall, int BytesToOverwrite, bool GenerateWrapper = true);

};

#endif


#include "MemoryEdit.h"


MemoryEdit::MemoryEdit()
{
   this->NextWrapper = this->Wrappers;
   this->Process = NULL;
   this->NumberOfPatches = 0;
   memset((void*)this->Wrappers, 0, sizeof(this->Wrappers));
}

MemoryEdit::MemoryEdit(HANDLE Process)
{
   this->NextWrapper = this->Wrappers;
   this->Process = Process;
   this->NumberOfPatches = 0;
   memset((void*)this->Wrappers, 0, sizeof(this->Wrappers));
}

MemoryEdit::~MemoryEdit()
{
   // Get the first original data
   BYTE *OriginalData = this->Wrappers;

   // Loop through each patch
   for(DWORD i = 0; i < this->NumberOfPatches; i++)
   {
      // Get the location and the length
      BYTE *PatchLocation = (BYTE*)this->ptrToPatches[i];
      DWORD PatchLength = this->PatchLengths[i];

      WriteProcessMemory(Process, (void*)PatchLocation, (void*)OriginalData, PatchLength, NULL);

      // Move up the original data to past this one, and 5 more for the call
      OriginalData += (PatchLength + 5);
   }
}

bool MemoryEdit::PatchMem(void *AddressToEdit, void *FunctionToCall, int BytesToOverwrite, bool GenerateWrapper)
{
   // This will patch the AddressToEdit with a call to somewhere in Wrappers, which will
   // do the stuff from the memory that was overwritten, then jump to our function which
   // should only have to take care of backing up registers

   Buffer Patch;
   Buffer Wrapper;

   assert(BytesToOverwrite >= 5);

   // Allocate space for the bytes that we'll be overwriting
   char *BytesOverwritten = (char*)malloc(BytesToOverwrite);
   if(!ReadProcessMemory(Process, AddressToEdit, BytesOverwritten, BytesToOverwrite, NULL))
      return false;

   // Adds the wrapper bytes to the patch buffer
   // (must be done whether or not we actually have to use it for restoring)
   for(int i = 0; i < BytesToOverwrite; i++)
   {
      Wrapper << (BYTE) BytesOverwritten[i];
   }


   // Add the "jmp" to the buffer
   Wrapper << JMP;
   // Add the distance from (NextWrapper + BytesToOverwrite) to (FunctionToCall)
   Wrapper << ((DWORD)FunctionToCall) - ((DWORD)NextWrapper + (DWORD)BytesToOverwrite + 5);

   // Add the "call" byte to the buffer
   Patch << CALL;
   // The call is going from AddressToEdit to NextWrapper, if there is a wrapper
   // The call is going from AddressToEdit to FunctionToCall if there is no wrapper
   if(GenerateWrapper)
      Patch << ((DWORD)NextWrapper - (DWORD)AddressToEdit - 5);
   else
      Patch << ((DWORD)FunctionToCall - (DWORD)AddressToEdit - 5);

   // Fill out the rest of the patch with NOPs
   for(int i = 0; i < (BytesToOverwrite - 5); i++)
   {
      Patch << NOP;
   }

   // First, write the wrapper
   if(WriteProcessMemory(Process, NextWrapper, Wrapper.c_str(), Wrapper.GetSize(), NULL) == 0)
      return false;

   // Then write the patch
   if(WriteProcessMemory(Process, AddressToEdit, Patch.c_str(), Patch.GetSize(), NULL) == 0)
      return false;

   // Store a pointer to the spot patched so we can fix it later
   ptrToPatches[NumberOfPatches] = (DWORD)AddressToEdit;
   // Store the number of bytes that were patches
   PatchLengths[NumberOfPatches] = (DWORD)BytesToOverwrite;

   // Move up the NextWrapper past the overwritten bytes and past the 5 "call" bytes
   NextWrapper += BytesToOverwrite + 5;

   // Increment the number of patches
   NumberOfPatches++;

   free(BytesOverwritten);

   return true;
}


Any questions, comments, or suggestions will be greatly appreciated!  
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


iago

This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*