11 września 2016

CreateToolhelp32Snapshot

Podczas pracy nad narzędziem do wizualizacji pamięci obcego procesu, natrafiłem na pewne nieudokumentowane zachowanie funkcji CreateToolhelp32Snapshot w WinApi. Zgodnie z dokumentacją (opens new window). Funkcja ta wykonuje “zapis” stanu analizowanego procesu. Jednak to, co zostanie zapamiętane, zależy od wartości pierwszego argumentu.

Opis działania CreateToolhelp32Snapshot

Możemy zapisać stan procesu w następujących obszarach:

  • Sterty, jakie proces posiada (Heaps).
  • Moduły, jakie zostały załadowane do pamięci procesu z rozróżnieniem na te 32-bitowe oraz 64-bitowe.
  • Wątki, jakie proces wykonuje.
  • Procesy – sytuacja, gdy chcemy enumerować wszystkie procesy w systemie.

Dokumentacja rozróżnia procesy 32 oraz 64-bitowe. Ponadto określono wyraźnie, że w przypadku analizowania 64-bitowego procesu procesem 32-bitowym, nie można otrzymać informacji o modułach 64-bitowych.

Ważne odnotowania jest również to, że nie ma czegoś takiego jak sterta 64-bitowa. Dokumentacja przewiduje jedynie jeden parametr, aby zamrozić stan wszystkich stert w procesie - TH32CS_SNAPHEAPLIST.

Głębsze przyjrzenie się temu zagadnieniu nie doprowadziło mnie również do znalezienia informacji o jakichkolwiek ograniczeniach, gdy analizowany proces jest 32-bitowy, a analizujący 64-bitowy.

Zachowanie CreateToolhelp32Snapshot zależnie od sposobu kompilacji

Program badany

Poniżej przedstawiam prostą aplikację, która posłuży nam za obiekt badawczy.

#include <iostream>

int main() {
    std::cout << "Hello_1" << std::endl;
 std::cin.ignore();
    return 0;
}

Aplikacje kompilujemy Visual Studio 2015 przy pomocy poniższej komendy.

cl /O2 /GL  /MD /EHsc /Fo"{temp}" /Fe"{output}"  {input}

Otrzymamy 32 bitową binarkę, która potrzebuje WOW64 do pracy na maszynach 64 bitowych – Nic specjalnego.

Aplikacja analizująca

Załóżmy, że chcemy się dowiedzieć, jak nasz program badany wykorzystuje stertę. Chcemy poznać lokalizacje stert oraz ich ilość, a następnie przejść wszystkie alokacje. Do tego celu możemy wykorzystać funkcje WinApi: Heap32ListFirst, Heap32ListNext, Heap32First, Heap32Next. Poniżej prosa aplikacja wyświetlająca ID (adres) wszystkich stert w obcej aplikacji.

#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <cstdio>
#include <conio.h>

DWORD find_PID()
{
 PROCESSENTRY32 entry;
 entry.dwSize = sizeof(PROCESSENTRY32);
 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
 if (Process32First(snapshot, &entry) == TRUE)
 while (Process32Next(snapshot, &entry) == TRUE)
 if (stricmp(entry.szExeFile, "Hello1_VS_DYNAMIC.exe") == 0)
 return entry.th32ProcessID;
 CloseHandle(snapshot);
 return 0;
}

void ShowHeapsInfo(DWORD pid)
{
 HEAPLIST32 entry;
 entry.dwSize = sizeof(HEAPLIST32);
 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, pid);
 if (Heap32ListFirst(snapshot, &entry) == FALSE)
 return;
 do
 {
    printf("HEAP ID: 0x%x \n", entry.th32HeapID);
 } while (Heap32ListNext(snapshot, &entry) == TRUE);

 CloseHandle(snapshot);
}

int main(void)
{
 DWORD pid = find_PID();
 printf("PID is [ %d ]\n", pid);
 if (pid == 0) return 1;
 ShowHeapsInfo(pid);

 _getche();
 return 0;
}

Okazuje się, że wyniki, jakie uzyskamy, zależą od tego, jak skompilujemy naszą aplikacje diagnostyczną. Badana aplikacja pozostaje bez zmian, nie zostanie nawet zrestartowana, podczas gdy (zależnie od trybu kompilacji) otrzymujemy zupełnie różne informacje o stertach.

Kompilacja Wartości
x86 PID is [ 7024 ]
HEAP ID: 0x1450000
x64 PID is [ 7024 ]
HEAP ID: 0x1380000
HEAP ID: 0xdc0000

Otrzymane wyniki są różne. Prawdziwa sterta to ta zwracana w trybie 32-bitowym, natomiast 64-bitowe są prawdopodobnie wykorzystywane przez WOW64.

Dokumentacja wykorzystywanych funkcji nie dostarczyła żadnych informacji o takim zachowaniu.