Delphi reading from the memory of another process. Process memory search

This is an article based on questions on the forums: “How can I return a string from a DLL?”, “How to pass and return an array of records?”, “How to pass a form to a DLL?”.

So that you don’t spend half your life figuring it out, in this article I will bring everything on a platter.

The topics of this article have been touched upon more than once in this blog to varying degrees, but in this article they are collected together and justified. In short, you can throw a link to this article at those who develop DLLs.

Important Note: article must be read sequentially. Code examples are provided only as examples, at each step (point) of the article, the example code is added with new details. For example, at the very beginning of the article there is no error handling; “classical” methods are indicated (such as using GetLastError , the sdtcall convention, etc.), which are replaced with more adequate ones as the article progresses. This was done so that “new” (“unusual”) designs would not raise questions. Otherwise, it would be necessary to insert a note for each example like: “this is discussed in that paragraph below, and that is discussed in this paragraph.” In any case, at the end of the article there is a link to ready-made code, written taking into account everything said in the article. You can just take it and use it. And the article explains why and why. If you are not interested in “why and why”, scroll to the end to the conclusion and the link to download the example.

Map a file into memory for sharing among multiple processes.

The first process creates the Temp.txt file and then maps it into memory

//first process
unit mainServ;

Uses

Dialogs, StdCtrls;


type
TForm1 = class(TForm)
btnCreate: TButton;
Memo1: TMemo;
procedure btnCreateClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);

Private
(Private declarations)
public
(Public declarations)
end;
const

bufSize=100;//buffer size

Var
Form1: TForm1;
hFile,hMapedFile:HWND;
pMapFile:Pointer;

//Create named, shared memory
procedure TForm1.btnCreateClick(Sender: TObject);
var
outBuff:array of Char;
byteWrt:Cardinal;
begin

UnmapViewOfFile(pMapFile);
if hMapedFile<>INVALID_HANDLE_VALUE then
CloseHandle(hMapedFile);
if (hFile<>INVALID_HANDLE_VALUE) then
CloseHandle(hFile);
hFile:=CreateFile(PWideChar("Temp.txt"),
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil,CREATE_ALWAYS or OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);

If hFile=INVALID_HANDLE_VALUE then
begin
ShowMessage("Error creating file "+SysErrorMessage(GetLastError));
Exit;
end;
StrPCopy(outBuff,Memo1.Lines.Text);
WriteFile(hFile,outBuff,bufSize*SizeOf(Char),byteWrt,nil);

HMapedFile:=CreateFileMapping(hFile, //INVALID_HANDLE_VALUE - use of the swap file
nil, // default protection
PAGE_READWRITE, //read/write access
0, // max. object size
bufSize*SizeOf(Char), // buffer size
MMFName); // name of the object reflected in memory


begin

CloseHandle(hFile);
Exit;
end;
pMapFile:=MapViewOfFile(hMapedFile, //descriptor of the “projected” object
FILE_MAP_ALL_ACCESS, // read/write permission
0,0,

if pMapFile=nil then
begin

CloseHandle(hMapedFile);
CloseHandle(hFile);
Exit;
end;
end;

Procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if not Assigned(pMapFile) then
UnmapViewOfFile(pMapFile);
if hMapedFile<>INVALID_HANDLE_VALUE then
CloseHandle(hMapedFile);
if (hFile<>INVALID_HANDLE_VALUE) then
CloseHandle(hFile);end;

The second process can access the same data by calling the OpenFileMapping function with the same name as the first process. It can then use the MapViewOfFile function to obtain a pointer to the file's data view.

//second process

Unit clientMain;

Uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

Type
TForm1 = class(TForm)
btnRead: TButton;
Memo1: TMemo;
procedure btnReadClick(Sender: TObject);
private
(Private declarations)
public
(Public declarations)
end;
const
MMFName: PWideChar = "DelphiFileMappedExample"; // name of the file mapping object
bufSize=100;//buffer size

Var
Form1: TForm1;

Procedure TForm1.btnReadClick(Sender: TObject);
var
hMapedFile:HWND;
pMapFile:Pointer;

Begin
hMapedFile:=OpenFileMapping(FILE_MAP_ALL_ACCESS, // read/write access
False, //name is not inherited
MMFName);//name of the "projected" object

If hMapedFile=INVALID_HANDLE_VALUE then
begin
ShowMessage("Error while mapping the file into memory "+SysErrorMessage(GetLastError));
Exit;
end;

PMapFile:=MapViewOfFile(hMapedFile,//descriptor of the “mapped” object
FILE_MAP_ALL_ACCESS, // read/write permission
0,0,
bufSize*SizeOf(Char));//buffer size

If pMapFile=nil then
begin
ShowMessage("Error displaying file "+SysErrorMessage(GetLastError));
CloseHandle(hMapedFile);
Exit;
end;

Memo1.Text:=PChar(pMapFile);
UnmapViewOfFile(pMapFile);
CloseHandle(hMapedFile);
end;

When a process no longer needs access to the file-memory-mapping object, it must call the CloseHandle function. When all handles are closed, the system can free the section of the page file used by the object.

You can download projects

Process memory search

// Search for a DWORD value in the specified process
// [email protected]
program search;
($APPTYPE CONSOLE)
uses Windows, SysUtils;
var
ProcessID: DWord ;
ProcessHandle: THandle ;
Mbi: TMemoryBasicInformation;
Addr: DWord;
Value: DWord ;
I: Cardinal ;
Buf: PChar ;
BytesRead: DWord ;
begin
if ParamCount< 2 then
begin
WriteLn( "Usage: search.exe processid value") ;
Exit ;
end ;
ProcessID:= StrToInt(ParamStr(1));
WriteLn ("Process id: " + IntToStr (ProcessID) ) ;
Value:= StrToInt(ParamStr(2));
WriteLn ("Value to search: " + IntToStr (Value) ) ;
// Open the process
ProcessHandle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ or
PROCESS_VM_OPERATION, false , ProcessID);
if ProcessHandle<>0 then
try
Addr := 0 ;
// List all regions of the process's virtual memory
while VirtualQueryEx(ProcessHandle, Pointer (Addr) , Mbi, SizeOf (Mbi) )<>0 do
begin
// Uncomment to see a list of regions found in the address space
// WriteLn("region: " + IntToHex(Integer(Mbi.BaseAddress), 8) +
// " size: " + IntToStr(Mbi.RegionSize));
// If the region has memory allocated and the region is not a "watchdog" (like the top of the stack),
// then read this region
if (Mbi.State = MEM_COMMIT) and not ((Mbi.Protect and PAGE_GUARD) = PAGE_GUARD) then
begin
// This is a demo program, so it allocates a buffer for the entire region.
// The region can be quite large, so it's better to read it in blocks to save money
// memory. But here, for the sake of simplicity of the algorithm, all optimization is screwed up.
GetMem(Buf, Mbi.RegionSize);
try
// Read the entire region into the allocated buffer
if ReadProcessMemory(ProcessHandle, Mbi.BaseAddress , Buf,
Mbi.RegionSize , BytesRead) then
begin
// Look for a DWORD value in the buffer
for I:= 0 to BytesRead - SizeOf (Value) do
begin
if PDWord (@Buf[ I] ) ^ = Value then
// Found, print
WriteLn("Value" + IntToStr(Value) +" found at " +!}
IntToHex(Integer(Cardinal(Mbi.BaseAddress) + I), 8));
end ;
end
else
WriteLn( "Failed to read process memory"+ IntToStr(GetLastError));
finally
FreeMem(Buf);
end ;
end ;
// Calculate the address of the next region
Addr := Addr + Mbi.RegionSize ;
end ;
finally
CloseHandle(ProcessHandle) ;
end
else
WriteLn("Failed to open process");
end.

And here is the program in which we search for an example:

program someprog;
($APPTYPE CONSOLE)
uses SysUtils;
var
SomeValue: Integer ;
begin
SomeValue:= 12345 ;
WriteLn( "One variable of this program has a value "+ IntToStr (SomeValue) ) ;
WriteLn("Press any key to exit" ) ;!}
ReadLn;
end.