Es interesante recalcar que un proceso compilado para 32bits no puede leer ni escribir en la memoria de otro de 64 bits, pero el camino contrario si es correcto. Esto se debe a incompatibilidad del tamaño de los punteros.
Por motivo de dotar de más velocidad al código, he implementado una función que compara cadenas más rápido que su equivalente API, porque no hace verificaciones. Con esa función se gana un segundo sobre su equivalente API, aunque esto depende del S.O. y procesador.
Este es el código:
function _StrCmpNIW(Str1, Str2: PWCHAR; N: integer): DWORD; var T: integer; begin T:= 0; // Pasando a minusculas y comparando caracteres while ((Ord(Str1^) or 32) = (Ord(Str2^) or 32)) and (Str1^ <> #0) and (Str2^ <> #0) and (T<N) do begin inc(Str1); inc(Str2); inc(T); end; Result:= N-T; end; function EnablePrivilege(name: String; Enable: boolean = true): boolean; var hToken: THANDLE; priv: TOKEN_PRIVILEGES; begin priv.PrivilegeCount:= 1; priv.Privileges[0].Attributes:= 0; if Enable then priv.Privileges[0].Attributes:= SE_PRIVILEGE_ENABLED; LookupPrivilegeValue(nil, PCHAR(name), priv.Privileges[0].Luid); OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES, hToken); AdjustTokenPrivileges (hToken, FALSE, priv, sizeof(priv), nil, PDWORD(nil)^); Result:= (GetLastError = ERROR_SUCCESS); CloseHandle (hToken); end; //--------------------------------------------------------------------------- function RemoteReplaceString(PID: DWORD; Str, NewStr: PWCHAR): BOOL; var hProc: THANDLE; i, T, Size, Len, nBytesRead: DWORD; Buffer: PCHAR; mbi: MEMORY_BASIC_INFORMATION; Start: PCHAR; begin Result:= false; hProc:= OpenProcess(PROCESS_ALL_ACCESS, false, PID); Len:= lstrlenW(Str); Size:= Len * sizeof(WCHAR); EnablePrivilege('SeDebugPrivilege'); Start:= nil; // Escaneamos la memoria del proceso en busca de la cadena while VirtualQueryEx(hProc, Start, mbi, sizeof(mbi))<>0 do begin if mbi.Protect <> 0 and not(mbi.Protect and (PAGE_NOACCESS or PAGE_GUARD)) then begin Buffer:= VirtualAlloc(nil, mbi.RegionSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); if Buffer <> nil then begin if ReadProcessMemory(hProc, mbi.BaseAddress, Buffer, mbi.RegionSize, nBytesRead) and (nBytesRead = mbi.RegionSize) then begin //Le resto el modulo de la división para no perderme cadenas partidas T:= nBytesRead - Size - nBytesRead mod Size; i:= 0; while i <= T do begin // Busco la cadena en el buffer "no case sensitive" if _StrCmpNIW(PWCHAR(Buffer+i), Str, Len) = 0 then begin // Si la encuentro la escribo en el proceso remoto if WriteProcessMemory(hProc, pointer(int64(mbi.BaseAddress) + i), NewStr, Size, PDWORD(0)^) then begin // Incremento el índice para seguir buscando i:= i + Size - 1; Result:= Result or true; end; end; inc(i); end; end; VirtualFree(Buffer, 0, MEM_RELEASE); end; end; inc(Start, mbi.RegionSize); end; CloseHandle(hProc); end; procedure TForm1.Button1Click(Sender: TObject); var proc: TProcessEntry32; hSysSnapshot: THandle; begin proc.dwSize := SizeOf(TProcessEntry32); hSysSnapshot:= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSysSnapshot <> INVALID_HANDLE_VALUE) and Process32First(hSysSnapshot, proc) then begin repeat if SameText(String(proc.szExeFile), Edit1.Text) then RemoteReplaceString(proc.th32ProcessID, PWCHAR(WideString(Edit2.Text)), PWCHAR(WideString(Edit3.Text))); until not (Process32Next(hSysSnapshot, proc)); end; CloseHandle(hSysSnapshot); end;
Aclaraciones:
1.- Las cadenas de un proceso en memoria deben tratarse como UNICODE.
2.- Un código para 32bits no puede leer la memoria de un proceso 64 bits sin más, se precisa un driver o trucos especiales, esto es debido a la incompatibilidad de los punteros que definen el rango de direcciones que son capaces de direccionar.
3.- No se puede sustituir una cadena por otra de mayor longitud, el código evita esto para impedir la corrupción del proceso.
4.- El código es funcional en delphi 7 y Lazarus, en este último y compilado para 64 bits, produce un ejecutable capaz de modificar un proceso de 32 ó 64bits.
5.- Para los que quieran "cacharear", decir que al escribir código a bajo nivel para 64bits hay que tener mucho cuidado con la aritmética de punteros y conversiones, ya que su tamaño es de 64bits y no de 32, como estamos acostumbrados. En este caso el mismo código es funcional para generar un ejecutable de 32 ó 64 bits
6.- Está probado en WinXP y Win10.
Subo el proyecto, espero que sea de utilidad, o al menos una curiosidad para realizar alguna "broma" y nunca para "cosas malvadas".
Saludos.