ReadProcessMemory (200pts!)

Will increase points to 200 when correct answer is given.

I need a function I can put in a DLL, which I would call like this:

TheData := ReadProcMem(ProcessID);

where TheData is a pchar.

It mustn't use the 'string' type as it means I would need to include an extra file with the dll (you can use pchar).

I want it to read in the entire memory of an app, starting from the base address (normally $400000), and reading till the end of the program.

I think it would need to use openprocess, readprocessmemory and closehandle. I have tried lots but I can't get it to work. Also I don't know the API to find the base address/end address.
LVL 1
plasmatekAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

MadshiCommented:
Hi plasmatek...

function ReadProcMem(processID: dword) : pointer;
var ph  : dword;
    p1  : pointer;
    mbi : TMemoryBasicInformation;
    dw1 : dword;
begin
  result := nil;
  ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, processID);
  if ph <> 0 then
    try
      p1 := nil;
      while VirtualQueryEx(ph, p1, mbi, sizeOf(TMemoryBasicInformation)) = sizeOf(TMemoryBasicInformation) do begin
        if (mbi.BaseAddress <> nil) and (mbi.BaseAddress = mbi.AllocationBase) then begin
          result := AllocMem(mbi.RegionSize);
          if (not ReadProcessMemory(ph, mbi.BaseAddress, result, mbi.RegionSize, dw1)) or
             (dw1 <> mbi.RegionSize) then begin
            FreeMem(result);
            result := nil;
          end;
          break;
        end;
        p1 := pointer(cardinal(p1) + mbi.RegionSize);
      end;
    finally CloseHandle(ph) end;
end;

Am awaiting 200 points...   :-)

Regards, Madshi.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
LischkeCommented:
Madshi, this is the kind of question you are living for, don't you :-)))?

Ciao, Mike
0
MadshiCommented:
Hehe... Yes, exactly...   :-))

Regards, Madshi.
0
Introducing Cloud Class® training courses

Tech changes fast. You can learn faster. That’s why we’re bringing professional training courses to Experts Exchange. With a subscription, you can access all the Cloud Class® courses to expand your education, prep for certifications, and get top-notch instructions.

plasmatekAuthor Commented:
procedure TForm1.Button1Click(Sender: TObject);
var
mypoint: pointer;
mypchar: pchar;
begin
mypoint := readprocmem($ffe34c65);
mypchar := @mypoint;
memo1.text := mypchar;
//memo1.text := inttostr(length(mypchar));
end;

I called it with this, and I just get 3 characters appearing in memo1.text - I did length(mypchar) and it said it was only 3 characters as well, and the characters change each time. Perhaps I am doing something wrong?
$ffe34c65 was the processid of a running process.
0
MadshiCommented:
Yes, you're doing something wrong. What do you expect? You're copying CODE, no TEXT. Windows' string types (= pchar) stop working at the first #0 character. So the if there's a #0 character in the code somewhere, the string gets cut off. That means in your case it's evident, that the application header has a #0 character in the 4th position. And that's very good possible.
If you want to look at the complete binary data, you can't put it into a memo, you have to convert it somehow, so that it's readable, like a HexEditor.

Okay, anyway, you surely need the length of the copied code. So I think we should change the function parameters. I think the easiest way would be to return a Delphi string. There you can ask Length(DelphiString) and that doesn't get cut off at the first #0 character...

function ReadProcMem(processID: dword) : string;
var ph  : dword;
    p1  : pointer;
    mbi : TMemoryBasicInformation;
    dw1 : dword;
begin
  result := '';
  ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, processID);
  if ph <> 0 then
    try
      p1 := nil;
      while VirtualQueryEx(ph, p1, mbi, sizeOf(TMemoryBasicInformation)) = sizeOf(TMemoryBasicInformation) do begin
        if (mbi.BaseAddress <> nil) and (mbi.BaseAddress = mbi.AllocationBase) then begin
          SetLength(result, mbi.RegionSize);
          if (not ReadProcessMemory(ph, mbi.BaseAddress, pointer(result), mbi.RegionSize, dw1)) or
             (dw1 <> mbi.RegionSize) then
            result := '';
          break;
        end;
        p1 := pointer(cardinal(p1) + mbi.RegionSize);
      end;
    finally CloseHandle(ph) end;
end;

Regards, Madshi.
0
plasmatekAuthor Commented:
Adjusted points to 200
0
plasmatekAuthor Commented:
Thanks! Just what I wanted :) Heres the points and an A Grade. Shame its a string, as I wanted to put it into a DLL and this means I will have to include that DLL, but I think I can manage to convert it into an array or something.

Thanks very much,
James. (plasmatek)
0
MadshiCommented:
Well, no problem, if you don't like strings, you can do it like this, too:

function ReadProcMem(processID: dword; var size: cardinal) : pointer;
var ph  : dword;
    p1  : pointer;
    mbi : TMemoryBasicInformation;
    dw1 : dword;
begin
  result := nil;
  size := 0;
  ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, processID);
  if ph <> 0 then
    try
      p1 := nil;
      while VirtualQueryEx(ph, p1, mbi, sizeOf(TMemoryBasicInformation)) = sizeOf(TMemoryBasicInformation) do begin
        if (mbi.BaseAddress <> nil) and (mbi.BaseAddress = mbi.AllocationBase) then begin
          size := mbi.RegionSize;
          result := AllocMem(size);
          if (not ReadProcessMemory(ph, mbi.BaseAddress, result, size, dw1)) or
             (dw1 <> size) then begin
            FreeMem(result);
            size := 0;
            result := nil;
          end;
          break;
        end;
        p1 := pointer(cardinal(p1) + mbi.RegionSize);
      end;
    finally CloseHandle(ph) end;
end;
0
plasmatekAuthor Commented:
Thanks very much! I have now adapted this to remove the size parameter and changed it to stdcall so it can be called from VB without any problems (make sure you declare the parameters as byval). This code is flawless! Thanks very much again Madshi.

function ReadProcMem(processID: dword) : pointer;stdcall;
var ph  : dword;
    p1  : pointer;
    mbi : TMemoryBasicInformation;
    dw1 : dword;
    size: cardinal;
begin
  result := nil;
  ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, processID);
  if ph <> 0 then
    try
      p1 := nil;
      while VirtualQueryEx(ph, p1, mbi, sizeOf(TMemoryBasicInformation)) = sizeOf(TMemoryBasicInformation) do begin
        if (mbi.BaseAddress <> nil) and (mbi.BaseAddress = mbi.AllocationBase) then begin
          size := mbi.RegionSize;
          result := AllocMem(size);
          if (not ReadProcessMemory(ph, mbi.BaseAddress, result, size, dw1)) or
             (dw1 <> size) then begin
            FreeMem(result);
            result := nil;
          end;
          break;
        end;
        p1 := pointer(cardinal(p1) + mbi.RegionSize);
      end;
    finally CloseHandle(ph) end;
end;
0
plasmatekAuthor Commented:
I had a little problem. If I try to use this on a process which has been compressed (petite,shrinker,aspack,upx etc) then it doesn't display all the data. (Only displays about 4000 bytes).
0
MadshiCommented:
Please send such a 4000 bytes compressed exe to "madshi@gmx.net". I'll have a look at it...
0
MadshiCommented:
Bad, VERY bad. I don't know what's happening there. Well, I know how to solve it, but it's *VERY* (very, very) complicated. You have to enumerate the modules of the other application (and that's again different between win9x and winNT), then parse through the image module header and so on...    :-((
0
MadshiCommented:
Well, here comes the function that works perfectly for OUR process. But to make this work for other processes, too, is (as I said) VERY difficult.

function GetModuleNtHeaders(module: cardinal) : PImageNtHeaders;
const CENEWHDR = $003C;          // offset of new EXE header
      CEMAGIC  = $5A4D;          // old EXE magic id:  'MZ'
      CPEMAGIC = $4550;          // NT portable executable
begin
  result := nil;
  try
    if TPWord(module)^ = CEMAGIC then begin
      result := pointer(module + TPWord(module + CENEWHDR)^);
      if result^.signature <> CPEMAGIC then
        result := nil;
    end;
  except result := nil end;
end;

function GetMyProcMem : pointer;
begin
  with GetModuleNtHeaders(HInstance)^.OptionalHeader do begin
    result := AllocMem(SizeOfCode);
    Move(pointer(HInstance + BaseOfCode)^, result^, SizeOfCode);
  end;
end;
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.