Wyszukiwanie wartości w pamięci procesu - DUMP

0

Witam,

od paru dni męczę się próbując wykombinować sposób, aby przeszukać pamięć procesu w celu odnalezienia pewnych wartości - głównie string.

Przeszukałem masę stron dotyczących

VirtualQueryEx

oraz ReadProcessMemory

, lecz na żadnej z tych stron nie znalazłem oczekiwanego rezultatu.

Widziałem, że kiedyś Olesio się interesował DUMP-em pamięci procesu do pliku i nawet podał sposób wyszukiwania który jest w miarę ok, lecz tyczy się on tylko niewielkiej ilości zakresu przeszukiwania pamięci. Mnie interesuje obszar od 
```delphi
$400000

do $7FFFFFFF

. 

Obecnie kod wygląda tak:
```delphi
function ReadBufferFromMemory(Ad, Size: integer; var MB: TMemBlock): Cardinal;
var
  Cnt : Cardinal;
begin
  ReadProcessMemory(hPRocess, Pointer(Ad), @MB[0], Size, Cnt);
  ReadBufferFromMemory := Cnt;
end;

var
  St : TBytes6;
  StCount : word;
  BytesRead : Cardinal;
  Sad, Ead, Ad : Cardinal;
  X, Y, Z : Cardinal;
  Found :  Boolean;
  MemBlock : TMemBlock;
  MemStr : TMemoryStream;



  Temp : array[1..1024] of Byte;
  i, j: integer;
  adress: integer;
const
  WindowClass = 'Nazwa klasy okna';
begin
{  St[0] := $74;
  St[1] := $6F;
  St[2] := $6D;
  St[3] := $61;
  St[4] := $73;
  St[5] := $7A;
  St[6] := $2E;
  St[7] := $6F;
  St[8] := $70;
  St[9] := $79;
  St[10] := $64;   }
  st[0]:=$90;       //in asm this means: nop
  st[1]:=$90;
  st[2]:=$90;
  st[3]:=$90;
  st[4]:=$90;
  st[5]:=$90;
  StCount := 6;
  Sad := ($00400000);
  Ead := ($7FFFFFFF);
  Ad := Sad;
  Found := False;
  MemStr := TMemoryStream.Create;
  WindowH := FindWindow(WindowClass, nil);
  GetWindowThreadProcessId(WindowH, @ProcessId);
  if WindowH = 0 then
  begin
    ShowMessage('Nie uruchomiono procesu o klasie okna: ' + WindowClass);
    Exit;
  end;
  hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, ProcessId);

  I := 0;
  for adress := Sad to Ead -1 do
  begin
    repeat
      ReadProcessMemory(hProcess, Ptr(adress + i + 1000), @Temp, SizeOf(Temp), BytesRead);
     // RichLog.Lines.Append(IntToHex(Sad + i, 8));
      MemStr.Write(Temp, SizeOf(Temp));
      I := I + BytesRead;
    until BytesRead = 0;
    MemStr.SaveToFile('E:\Viking\Debug\Win32\dump\memstr.txt');
    RichLog.Lines.Append(IntToHex(adress + i, 8));
  end;
 { repeat
    BytesRead := ReadBufferFromMemory(Ad, BUFFMAX, MemBlock);
    if BytesRead = 0 then
      Break;
    RichLog.Lines.Append(IntToHex(Integer(ad), 8));
    if BytesRead = BUFFMAX then
      BytesRead := BytesRead - StCount;
    for X := 0 to BytesRead - 1 do
    begin
      Found := True;
      for Y := 0 to StCount - 1 do
        if MemBlock[X + Y] <> St[Y] then
        begin
          Found := False;
          Break;
        end;
        if Found then
        begin
          Z := Ad + X + Y - StCount;
          RichLog.Lines.Append(IntToHex(Z, 8));
          //ShowMessage('Ciąg bajtów znaleziono pod adresem: ' + IntToHex(Z, 6));
          Break;
        end;
      end;
      Ad := Ad + BytesRead;
      MemStr.write(MemBlock, SizeOf(MemBlock));

    until (Ad >= Ead);                            }
    MemStr.SaveToFile('E:\Viking\Debug\Win32\dump\memstr.txt');
    MemStr.Free;
    ShowMessage('Koniec szukania.');
end;

Jest tu straszny bałagan ale to dlatego, że ciągle próbuję uzyskać jakiś lepszy efekt - niestety jak dotychczas bez powodzenia.
Od razu napiszę, że przeszukiwanie (w powyższym kodzie) pętlą

for

trwa wieki jeśli mam przeszukiwać cały obszar pamięci. Czy istnieje jakiś szybszy sposób? Przykładowo Cheat Engine robi to idealnie w krótkim czasie.

Jaki należy przyjąć schemat działania - zdumpować pamięć do pliku i w pliku szukać danej wartości, czy na bieżąco w kodzie analizować przychodzące dane... już nie mam sił do tego, jakieś konkretne kodowe podpowiedzi byłyby mile widziane.

Podaję również inne przykłady, może przybliżą Wam o co chodzi.

procedure ScanMem(start, ende: dword);
var
  dwRead    : DWORD;
  iBuffer   : integer;
  adress    : Integer;

begin
  frmmain.lbl3.caption := 'start ' + Inttostr(start);
  frmmain.lbl5.caption := 'ende  ' + Inttostr(ende);
  Application.ProcessMessages;
  dwRead  := 0;
  iBuffer := 44112;

  for adress := start to ende - 1 do
  begin
    ReadProcessMemory(memory.IDProcess, ptr(adress), @iBuffer, SizeOf(Integer), dwRead);
    if iBuffer = 44112 then
      frmMain.RichLog.Lines.Append(inttostr(start));
  end;
end;

procedure GetMemMinMax;
var
  mbi     : TMemoryBasicInformation;
  adress  ,
  start   ,
  ende    : Cardinal;
begin
  adress := $400000;
  while adress < $7FFFFFFF do
  begin
    VirtualQueryEx(memory.IDProcess, ptr(adress), mbi, SizeOf(TMemoryBasicInformation));

    if (mbi.State = MEM_COMMIT)
    and (not (mbi.Protect = PAGE_GUARD)
    or (mbi.Protect = PAGE_NOACCESS)) then
    begin
      start := DWORD(mbi.BaseAddress);
      ende  := DWORD(mbi.BaseAddress) + mbi.RegionSize;
      ScanMem(start,ende);   // als hex?
    end;
    adress := adress + mbi.RegionSize;
  end;
//  showmessage(ergebnis);
end;

Powyższy przykład wygląda fajnie, lecz nie robi tego co powinien.

var
  APtr: Pointer;
  Buffer: TMemoryBasicInformation;
  Index: Integer;
  MemoryStream: TMemoryStream;
  FileStream: TFileStream;
  hProcess: THandle;
  BytesRead: DWord;

  adress: dword;
  start, ende: cardinal;
begin
  APtr := nil;
  Index := 0;
  hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, memory.PID);

  adress := $400000;

  while adress < $7FFFFFFFF do
  begin

    VirtualQueryEx(hProcess, Ptr(adress), Buffer, SizeOf(TMemoryBasicInformation));
    Inc(Index);

    if (Buffer.State = MEM_COMMIT) and (not (Buffer.Protect = PAGE_GUARD)
        or (Buffer.Protect = PAGE_NOACCESS)) and (Buffer.Protect = PAGE_READWRITE) then
    begin

      start := dword(Buffer.BaseAddress);
      ende := dword(Buffer.BaseAddress) + Buffer.RegionSize;

      MemoryStream := TMemoryStream.Create;
      MemoryStream.Size := Buffer.RegionSize;
      for adress := start to ende -1 do
      begin

      //for adress := start to ende -1 do
      //begin
        ReadProcessMemory(hProcess, Buffer.BaseAddress, @Buffer, 1000, BytesRead);
        //if Buffer = 44112 then
          frmMain.RichLog.Lines.Append(IntToHex(Integer(Buffer.BaseAddress), 8));
      //end;


        //frmMain.RichLog.Lines.Append(IntToHex(Integer(start), 8));
      end;


      FileStream := TFileStream.Create('E:\Viking\Debug\Win32\dump\memory_' + IntToHex(Integer(Buffer.BaseAddress), 8) + '_' +  IntToHex(DWord(Buffer.BaseAddress) + Buffer.RegionSize, 8) + '.txt', fmCreate);
      FileStream.CopyFrom(MemoryStream, 0);
      FileStream.Free;
      MemoryStream.Free;
    end;
    adress := adress + Buffer.RegionSize;
    DWord(APtr) := DWord(APtr) + Buffer.RegionSize;
  end;
  ShowMessage(IntToStr(Index));
end;
0

Generalnie to podstawową optymalizacją jest właśnie pytanie o aktywne strony. Najlepiej jest chyba czytać pamięć stronami a potem w każdej stronie szukać tego co cie interesuje.

  for adress := Sad to Ead -1 do
  begin
    repeat
      ReadProcessMemory(hProcess, Ptr(adress + i + 1000), @Temp, SizeOf(Temp), BytesRead);
     // RichLog.Lines.Append(IntToHex(Sad + i, 8));
      MemStr.Write(Temp, SizeOf(Temp));
      I := I + BytesRead;
    until BytesRead = 0;

Wytłumacz mi sens tego kodu. O ile for jest dziwny, bo wykona się tysiące razy, to ten repeat w środku jest jeszcze dziwniejszy. Ten kod jest bez sensu i dlatego jest wolny.

Moja rada: napisz ten kod samemu, nie wklejaj na ślepo gotowce. Pytaj system o dostępne strony, następnie czytaj te strony i tam szukaj odpowiedniej wartości.
Jest jeszcze inny sposób: Pobieranie nazwy pliku EXE i próbowanie znalezienia tego stringa bezpośrednio w nim, wtedy można łatwo znaleźć miejsce gdzie to zostało załadowane w pamięci. Wadą tego jest że nie zda się to na nic jeżeli jest użyty jakiś packer.

0

Szczerze Ci powiem, że już się tak zakręciłem w tym kodzie, że już nic nie wiem. Próbowałem pisać od nowa ale wszystko zawsze sprowadzało się do tego "dziadostwa" na końcu. Heh pętla repeat w pętli for :) Dlatego właśnie zwróciłem się o pomoc na to forum bo już sam nie daję rady. Liczę na jakieś kodowe wskazówki żeby przybliżyć sobie rozwiązanie.

Co do drugiego sposobu z otwieraniem execa - odpada. Chodzi tutaj o dynamiczne przypisywanie pamięci którego nie jestem w stanie przewidzieć i nie jest one przypisywane w samym execu, tylko podczas działania aplikacji.

Nie wiem też czemu pętla

MemStart := $1E6A1F94;
while MemStart < $7FFFFFFF do
begin

nie leci po tych alokacjach tylko przeleci kilka tyś. bajtów i zakańcza działanie. Coś tu jest pokręcone i nie za bardzo już wiem co.

Aktualnie wygląda to tak - po rozbiciu na procedurę i funkcję:

procedure GetMemMinMax(ValueType: string);
var
  MemInfo   : TMemoryBasicInformation;
  MemStart  ,
  start     ,
  ende      : Cardinal;

begin
  MemStr := TMemoryStream.Create;

  MemStart := $1E6A1F94;
  while MemStart < $7FFFFFFF do
  begin
    while (VirtualQueryEx(memory.IDProcess, Pointer(MemStart), MemInfo, SizeOf(MemInfo)) <> 0) do
    begin

      if (MemInfo.State = MEM_COMMIT) and (not (MemInfo.Protect = PAGE_GUARD)
          or (MemInfo.Protect = PAGE_NOACCESS)) then
      begin
        start := DWORD(MemInfo.BaseAddress);
        ende  := DWORD(MemInfo.BaseAddress) + MemInfo.RegionSize;

        if ValueType = 'string' then
          ScanMem_String(start,ende, MemInfo);

        //if ValueType = 'integer' then
        //  ScanMem_Integer(start,ende);

      end;
    end;

    MemStart := MemStart + MemInfo.RegionSize;

  end;
end;
function ScanMem_String(start, ende: dword; MemInfo: TMemoryBasicInformation): string;
var
  ReceivedBytes : cardinal;
  iBuffer       : integer;
  address       : Integer;

  i: integer;
  Temp : array[1..4096] of Byte;
begin
  frmmain.lbl3.caption := 'start ' + Inttostr(start);
  frmmain.lbl5.caption := 'ende  ' + Inttostr(ende);

  //Application.ProcessMessages;
//  ReceivedBytes  := 0;
//  iBuffer := 0;

  //for address := start to ende - 1 do
  //begin
    frmmain.lbl6.caption := 'current  ' + Inttostr(address);

    I := 0;
    repeat
      ReadProcessMemory(memory.IDProcess, ptr(start + i), @Temp, SizeOf(Temp), ReceivedBytes);



      frmMain.RichLog.Lines.Append('scan: ' + IntToHex(start + i, 8) + ' buff: ');
      MemStr.Write(Temp, SizeOf(Temp));
      I := I + ReceivedBytes;

      MemStr.SaveToFile('E:\Viking\Debug\Win32\dump\memory.txt');
    until ReceivedBytes = 0;
  //end;

  FreeMem(Buffer);
end;

do pliku zapisuje się zrzut pamięci z procesu ale nie z pełnego zakresu który nadaje pętla while w powyższej procedurze.

1

@user322: Możesz kombinować jak radzi @-123oho. A co procedury, którą kiedyś podawałem to w zasadzie dla wyszukiwania do "oporu" powinna ona wyglądać tak dla wyszukiwania ciągu bajtów w procesie o podanym PIDzie.

function FindBytesInMemory(APID : WORD; RangeFrom, RangeTo : Cardinal;
  ArrayOfBytes : array of Byte) : Cardinal;
const
  BufferSize = 65535;
var
  FOund : boolean;
  SizeOfArr : Byte;
  X, Y, Addr : Cardinal;
  HProcess, BytesRead, BytesToRead : DWORD;
  Buffer : array[0..BufferSize - 1] of Byte;
begin
  Result := 0;
  SizeOfArr := Length(ArrayOfBytes);
  HProcess := OpenProcess(PROCESS_ALL_ACCESS, False, APID);
  if (HProcess > 0) and (RangeFrom < RangeTo) then
  begin
    Addr := RangeFrom;
    while Addr < RangeTo do
    begin
      BytesToRead := BufferSize;
      if (RangeTo - Addr) < BytesToRead then
      begin
        BytesToRead := RangeTo - Addr;
      end;
      ReadProcessMemory(HProcess, Pointer(Addr), @Buffer[0], BytesToRead, BytesRead);
      if BytesRead > 0 then
      begin
        for X := 0 to BytesRead - 1 do
        begin
          Found := True;
          for Y := Low(ArrayOfBytes) to High(ArrayOfBytes) do
            if Buffer[X + Y] <> ArrayOfBytes[Y] then
            begin
              Found := False;
              Break;
            end;
          if Found then
          begin
            Result := Addr + X + Y - SizeOfArr;
            Break;
          end;
        end;
      end;
      Addr := Addr + BytesToRead;
    end;
  end;
  CloseHandle(HProcess);
end;

Najlepiej przeanalizuj sobie źródła Cheat Engine. Powinny być na sieci dostępne jeszcze do znalezienia źródła do skompilowania pod Delphi 7 Personal, bo o ile wiem to nowsze niż 5.6 wersje autor kompiluje pod Lazarusem i dla takowego IDE udostępnie kody źródłowe. Jeżeli dysponujesz Lazarusem (ja wolę Delphi 7 na przykład, Lazarusa jakoś nie używałem, bo ostatnio piszę raczej w WinAPI i mam tylko dla Delphi w wersji 7 zoptymalizowane moduły bez kodu źródłowego, które pozwalają tworzyć mi jeszcze mniejsze exeki wynikowe, ale Lazarus jest darmowy więc nie ma problemu z użyciem jeśli chcesz pisać pod VCL). A i ja powyższy kod kiedyś znalazłem oczywiście w google tylko go ładnie dopasowałem pod kątem zmiennych. Mając go już łatwo napisać wyszukiwanie stringu, ale wyszukiwanie od $0 do $7FFFFFFF nie ma sensu, lepiej odczytać informację o stronach i wyszukiwać w ich zakresach. Po więcej odsyłam do google. Wiadomo, że @-123oho nie lubi wrzucania gotowców, ale trudno dawno nic nie wrzucałem ;P A jako "zadanie domowe" masz sobie SAMODZIELNIE zoptymalizować ten kod poniżej pod wyszukiwanie w odpowiednich stronach pamięci oraz ewentualne pokazywanie postępu wyszukiwania (najlepiej rónież zrobić do tego osobną procedurę tak jak dla wyników wyszukiwania - tyle podpowiem na teraz :)).

//...
type
  TOnFindProc = procedure(Address : Cardinal);

procedure AnOnFindProc(Address : Cardinal);
begin
  with Form1 do
  begin
    Memo1.Lines.Add(IntToHex(Address, 2));
  end;
end;

procedure FindStringInMemory(APID : WORD; RangeFrom, RangeTo : Cardinal;
  StringToFind : string; IgnoreCase : boolean; OnFindProc : TOnFindProc);
const
  BufferSize = 65535;
var
  FOund : boolean;
  S1, S2 : string;
  SizeOfArr : Word;
  X, Y, Addr : Cardinal;
  ArrayOfBytes : array of Byte;
  HProcess, BytesRead, BytesToRead : DWORD;
  Buffer : array[0..BufferSize - 1] of Byte;
begin
  if StringToFind <> '' then
  begin
    SetLength(ArrayOfBytes, Length(StringToFind));
    for X := Low(ArrayOfBytes) to High(ArrayOfBytes) do
    begin
      ArrayOfBytes[X] := Ord(StringToFind[X + 1]);
    end;
    SizeOfArr := Length(ArrayOfBytes);
    HProcess := OpenProcess(PROCESS_ALL_ACCESS, False, APID);
    if (HProcess > 0) and (RangeFrom < RangeTo) then
    begin
      Addr := RangeFrom;
      while Addr < RangeTo do
      begin
        BytesToRead := BufferSize;
        if (RangeTo - Addr) < BytesToRead then
        begin
          BytesToRead := RangeTo - Addr;
        end;
        ReadProcessMemory(HProcess, Pointer(Addr), @Buffer[0], BytesToRead, BytesRead);
        if BytesRead > 0 then
        begin
          for X := 0 to BytesRead - 1 do
          begin
            Found := True;
            for Y := Low(ArrayOfBytes) to High(ArrayOfBytes) do
            begin
              if IgnoreCase then
              begin
                S1 := AnsiLowerCase(Chr(Buffer[X + Y]));
                S2 := AnsiLowerCase(Chr(ArrayOfBytes[Y]));
                if S1 <> S2 then
                begin
                  Found := False;
                  Break;
                end;
              end
              else
              begin
                if Buffer[X + Y] <> ArrayOfBytes[Y] then
                begin
                  Found := False;
                  Break;
                end;
              end;
            end;
            if Found then
            begin
              if @OnFindProc <> nil then
              begin
                OnFindProc(Addr + X + Y - SizeOfArr);
              end;
            end;
          end;
        end;
        Addr := Addr + BytesToRead;
      end;
    end;
    CloseHandle(HProcess);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear;
  FindStringInMemory(GetCurrentProcessId, $400000, $500000, 'delphi', False, AnOnFindProc);
end;
//...

P.S.: oczywiście nie polecam wyszukiwania jakiś krótkich stringów jak na przykład pojedyncza litera, ponieważ wiadomo wyników będzie zbyt wiele i w sumie nie ma to większego sensu. A i w temacie wspominasz słowo "DUMP", więc podejrzewam, że chcesz też robić za pewne zrzut pamięci procesu do pliku. Ja na bazie prostego kodu który można wygooglowac pod VCL stowrzyłem na własne potrzeby prosty tool w WinAPI. W sumie można go już przy okazji tego rematu opublikowac wraz ze źródłami WinAPI. W archiwum dołaczony jest exek oraz plik build.bat pozwalający na szybkie skompilowanie oraz spakowanie całości UPX'em. Projekt wykorzystuje: http://kolmck.net/sys/SysDcu7.zip które trzymam u siebie w $(Delphi)\LIB\FOR_WINAPI.

0

Olesio!

Wielkie dzięki. Wiedziałem że że mogę na Ciebie liczyć, pomogłeś i to nawet nie wiesz jak bardzo.
Co do optymalizacji Twojego kodu, jak najbardziej ale najpierw muszę go przeanalizować i wtedy porobić ewentualne przeróbki w miarę swoich potrzeb.

Na teraz zabieram się do dalszej pracy i studiowania kodu.
Jeszcze raz wielkie dzięki za rady i pomoc.

1 użytkowników online, w tym zalogowanych: 0, gości: 1