Notatnik techniczny

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ą. funkcja ta wykonuje “zapis” stanu analizowanego procesu. Co dokładnie 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 moduły 32- i 64-bitowe, ponadto jest określone wyraźnie, że jeśli proces analizowany jest 64 bitowy, a proces analizujący 32 wówczas nie otrzymamy informacji o modułach 64 bitowych.

Ważne jest, że nie ma czegoś takiego jak 64-bitowa sterta – dokumentacja przewiduje tylko jeden parametr, aby “zamrozić” stan wszystkich stert w procesie - TH32CS_SNAPHEAPLIST.

Nie znalazłem również ani słowa o jakichkolwiek ograniczeniach, gdy to analizowany proces jest x86, a analizujący x64.

Zachowanie CreateToolhelp32Snapshot zależnie od sposobu kompilacji

Program badany

Poniżej przedstawiam prostą aplikację, jaka 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 ewentualnie 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ę ze wyniki, jakie uzyskamy, zależą od tego, jak skompilujemy naszą aplikacje diagnostyczną. Badana aplikacja pozostaje bez zmian, nawet jej nie restartuje, 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

Wyniki są różne, “prawdziwa” sterta to ta zwracana w trybie 32-bitowym, te w trybie 64-bitowym prawdopodobnie są wykorzystywane przez WOW64. W dokumentacji do żadnej z wykorzystywanych funkcji nie znalazłem ani słowa o takim zachowaniu.