Delphi Process viewer!

AK_RAGE
AK_RAGE used Ask the Experts™
on
Hello,
OK let me start with the main goal - I want to write a program that will let me view the Entire Virtual Memory of a selected Process (like the "RAM editor" oprion in WinHex). Now currently I am using ToolHelp API to enumerate processes and try to view memory. the enumeration works well but the memory buffer remains the same for each process (and it is nothing like what i see in WinHex) EVEN THOUGH Toolhelp32ReadProcessMemory returns True. Please have a look at the following code. Any suggestions are greatly appreciated!

ATTN: NOT ALL FUNCTIONS INCLUDED TO SAVE SPACE - REQUEST TO VIEW OTHER FUNCTIONS
------------START CODE----------------

function StrToHex(Str: String):String;
procedure GetProcessList(List: TStrings);
procedure GetModuleList(List: TStrings);
function GetProcessHandle(ProcessID: DWORD): THandle;
procedure GetParentProcessInfo(var ID: Integer; var Path: String);


procedure TForm1.Button1Click(Sender: TObject);
begin
   GetProcessList(ListBox1.Items);
end;

procedure GetProcessList(List: TStrings);
var
  I: Integer;
  hSnapshoot: THandle;
  pe32: TProcessEntry32;
  MyBuf: array [0..100] of char;
  ret: Cardinal;
  mbiBuf: MEMORY_BASIC_INFORMATION;
  hProcess: THandle;
begin
  List.Clear;
  hSnapshoot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hSnapshoot = -1) then
      Exit;
  pe32.dwSize := SizeOf(TProcessEntry32);
  if (Process32First(hSnapshoot, pe32)) then
  repeat
    I := List.Add(Format('%x, %x: %s',
      [pe32.th32ProcessID, pe32.th32ParentProcessID, pe32.szExeFile]));
    hProcess := GetProcessHandle(pe32.th32ProcessID);
    ret := VirtualQueryEx(hProcess,nil,mbiBuf,SizeOf(MEMORY_BASIC_INFORMATION));
    FillChar(MyBuf, SizeOf(MyBuf), 'a');
    if Toolhelp32ReadProcessMemory(pe32.th32ProcessID,mbiBuf.BaseAddress,MyBuf,100,ret) then //Right here is the problem
      List.Add('good');

    List.Add(StrToHex(MyBuf));

  until not Process32Next(hSnapshoot, pe32);
  CloseHandle (hSnapshoot);
end;

procedure GetParentProcessInfo(var ID: Integer; var Path: String);
var
  ProcessID: DWORD;
  hSnapshoot: THandle;
  pe32: TProcessEntry32;
begin
  ProcessID := GetCurrentProcessID;
  ID := -1;
  Path := '';
  hSnapshoot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hSnapshoot = -1) then
      Exit;
  pe32.dwSize := SizeOf(TProcessEntry32);
  if (Process32First(hSnapshoot, pe32)) then
  repeat
    if pe32.th32ProcessID = ProcessID then
    begin
      ID := pe32.th32ParentProcessID;
      Break;
    end;
  until not Process32Next(hSnapshoot, pe32);
  if ID <> -1 then
  begin
    if (Process32First(hSnapshoot, pe32)) then
    repeat
      if pe32.th32ProcessID = ID then
      begin
        Path := pe32.szExeFile;
        Break;
      end;
    until not Process32Next(hSnapshoot, pe32);
  end;
  CloseHandle (hSnapshoot);
end;

function GetProcessHandle(ProcessID: DWORD): THandle;
begin
  Result := OpenProcess(PROCESS_ALL_ACCESS, True, ProcessID);
end;

function StrToHex(Str: String):String;
var
  i: Integer;
begin
   Result := '';
  for i := 0 to 100 do
  begin
     Result := Result + IntToHex(Integer(Str[i]),2);
  end;
end;


----------END CODE---------------

All help appreciated!

Thanx,
AK_RAGE
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Commented:
Didn't you post this at the borland newsgroups, too? Anyway: Two things:

(1) The loop in StrToHex is wrong. The first index in a "string" variable is 1, so the loop should go from 1 to 101.

(2) You're only asking the first 100 bytes of the first section of each process. Each process has multiple of sections. I guess that the first section in each process is one specific dll, which is loaded in all processes, hence you get the same bytes. You have to iterate through all sections of all processes by using VirtualQueryEx in a loop. In the second parameter first give in "nil", the second time give in "nil + mbiBuf.RegionSize". The third time give in "nil + lastMbiBuf.RegionSize + thisMbiBuf.RegionSize" and so on. Best would be to use a simple pointer variable like this:

var p1 : pointer;
begin
  p1 := nil;
  while VirtualQueryEx(..., p1, ...) ... do begin
    ...
    dword(p1) := dword(p1) + mbiBuf.RegionSize;
  end;

Regards, Madshi.

Author

Commented:
Thank you Madshi!
Very helpfull...I didn't post this on newsgroups but if you could tell me where you saw this I would be very greatful!

Also I saw an example program that did not use VirtualQueryEx at all it just changed the base address from $00400000 to $7fffffff will this work?

Something like this
ReadProcessMemory(hProcess, Pointer($00400000), @MemBlock[0], BUFFMAX, ret);

sad:=($00400000);
ead:=($7fffffff);

ad:=sad;
found:=false;
repeat

  BytesRead:=ReadBufferFromMemory(ad, BUFFMAX, MemBlock);
  if BytesRead=0 then break;
  // make sure we don't miss the search term when it spans two blocks
  If BytesRead=BUFFMAX Then BytesRead:=BytesRead-stCount;
  // search this MemoryBlock
  // cycle through the MemoryBlock
  For x:=0 To BytesRead-1 do
      begin
      found:=true;
      // check for search term
      For y:=0 To stCount-1 do
          If MemBlock[x+y]<>st[y] then
             begin
             found:=false;
             break;
             end;
      If found Then
         begin
            //WE HAVE IT ;)
         break; // stop searching
         end;
      end;
  ad:=ad+BytesRead;
  until (ad>=ead) or found;

Will this do the same thing as calling VirtualQueryEx over and over again?

Thanks,
AK_RAGE

Commented:
I'm not sure where it was, but almost the same question was asked somewhere else just a few days before. Anyway, doesn't matter.

You probably know that each process has its own memory area from $00000000-$ffffffff. The exe module and all loaded dll modules of each process are mapped into each process' memory area. Also all allocated memory is mapped into that memory area somewhere. With VirtualQueryEx you browse through the whole memory area of the specified process and find out which areas are allocated and which size they have.

The exe module is most of the time - but not always - located at $400000. Now I can't tell you what you want to do. You should know that yourself...  :-)  If you want to read only the exe file mapped into the memory then it's most of the time enough to do that $400000 thing. But if you want to get all the memory areas, you have to browse through VirtualQueryEx like I explained in my first comment.
Fundamentals of JavaScript

Learn the fundamentals of the popular programming language JavaScript so that you can explore the realm of web development.

Author

Commented:
Thanks again!
I decided to do the VirtualQueryEx Method but i still dont seem to be getting all of it! For example i have my program analize AOL's memory - WinHex can find the string 'ISP/LAN Connection' My prog can't! Maybe you can have a look at the code below:

---------CODE--------------

procedure GetProcessList(List: TStrings);
function GetProcessHandle(ProcessID: DWORD): THandle;
procedure ProcessAOL(pe32: TProcessEntry32; List: TStrings);

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
   GetProcessList(Memo1.Lines);

end;


procedure GetProcessList(List: TStrings);
var
  hSnapshoot: THandle;
  pe32: TProcessEntry32;
begin
  List.Clear;
  hSnapshoot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hSnapshoot = -1) then
      Exit;
  pe32.dwSize := SizeOf(TProcessEntry32);
  if (Process32First(hSnapshoot, pe32)) then
  repeat
    if (Pos('AOL.EXE',pe32.szExeFile) > 0) then
    begin
      List.Add(pe32.szExeFile);
      ProcessAOL(pe32,List);
    end;
  until not Process32Next(hSnapshoot, pe32);
  CloseHandle (hSnapshoot);
end;

const BUFFMAX=65536;
type TMemblock=array[0..BUFFMAX-1] of byte;

procedure ProcessAOL(pe32: TProcessEntry32; List: TStrings);
var
  MemBlock:TMemBlock;
  ret: Cardinal;

  SearchSize: Cardinal;
  BufLeft,BufRight: Cardinal;
  SearchStr: string;

  hProcess: THandle;
  Temp: String;

  sad,ead,ad:Cardinal;
  X,Y :Integer;
  Found :Boolean;
  Total: Cardinal;
  p1 : pointer;
  mbiBuf: MEMORY_BASIC_INFORMATION;
begin
   Total := 0;

   SearchStr := 'ISP/LAN Connection'; //what we want to find
   BufLeft := 20;   //we want to see 40 bytes around the search string
   BufRight := 20;
   SearchSize := BufLeft + BufRight + Length(SearchStr);

   ad:=sad;

   hProcess := GetProcessHandle(pe32.th32ProcessID);

   p1 := nil;
   while VirtualQueryEx(hProcess,p1,mbiBuf,SizeOf(MEMORY_BASIC_INFORMATION)) > 0 do
   begin
      ad := dword(mbiBuf.BaseAddress);
      while ad <= ( dword(mbiBuf.BaseAddress) + mbiBuf.RegionSize )do
      begin

         FillChar(MemBlock,BUFFMAX,0);
         ReadProcessMemory(hProcess, Pointer(ad), @MemBlock[0], BUFFMAX, ret);
         Total := Total + ret;

         if ret = 0 then
            break
         else if ret = BUFFMAX then
            ret := ret - SearchSize;

         For X:=0 to ret - 1 do
         begin
            Found := true;
            For Y:=0 to Length(SearchStr) - 1 do
               if MemBlock[X+Y] <> Byte(SearchStr[Y]) then
               begin
                  Found := False;
                  break;
               end;
            //If found is true then SearchStr is at pos X
            if ( (X-BufLeft)<0 ) or ( (X+BufRight)>BUFFMAX ) then Found := False;
            If Found then
            begin
               Temp := '';
               For Y:=X-BufLeft to X+BufRight do
               begin
                  if MemBlock[Y] <> 0 then
                     Temp := Temp + Char(MemBlock[Y]);
               end;
               List.Add(Temp);
               ShowMessage(Temp);
               List.Add('----------------------------------');
            end;
         end;
         ad:=ad+ret;
      end;
      dword(p1) := dword(p1) + mbiBuf.RegionSize;
   end;

   List.Add(IntToHex(ad,8));
   List.Add(IntToStr(Total));
end;

function GetProcessHandle(ProcessID: DWORD): THandle;
begin
  Result := OpenProcess(PROCESS_ALL_ACCESS, True, ProcessID);
end;

end.


---------------END-----------------

If you help me with this I can transfer you more points!

Thanks Again,
AK_RAGE

Commented:
First of all you should check which attributes the memory area has, which is reported by VirtualQueryEx. Read it only if it is readable. But that's just for the sake of clean programming.

Basically your code looks more or less alright. But what if that search string is bad positioned? Let's say you do ReadProcessMemory and get "ISP/LAN " at the end of the buffer, then you do the next ReadProcessMemory and get "Connection" at the begin of the buffer. Possible.

Regards, Madshi.

Author

Commented:
I made the sections overlap so that that does not happen.
         else if ret = BUFFMAX then
            ret := ret - SearchSize;
this sais if you got a full block make it "Size of search string" shorter so that the next block will overlap.

Can you tell me your AIM so that we can talk in real time? or contact me at AKRAGE2 i am on right now.

Thank You,
AK_RAGE

Author

Commented:
I made the sections overlap so that that does not happen.
         else if ret = BUFFMAX then
            ret := ret - SearchSize;
this sais if you got a full block make it "Size of search string" shorter so that the next block will overlap.

Can you tell me your AIM so that we can talk in real time? or contact me at AKRAGE2 i am on right now.

Thank You,
AK_RAGE

Commented:
Sorry, I don't use/like AIM or similar tools.

You'll have no other choice then to put your code to a hard test. Log exactly what VirtualQueryEx tells you, then how often you called ReadProcessMemory with which addresses and how many bytes were returned. E.g. I could imagine that ReadProcessMemory fails, if you read BUFFMAX, although only half the size is readable anymore.

Regards, Madshi.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial