Jak Działa API Windowsa (WinAPI, NtAPI)


Spis treści:



Wstęp

Kiedyś zastanawiało mnie jak działają funkcje systemowe Windowsa. Jak to się dzieje, że np. funkcja exit z biblioteki standardowej C, przechodzi do wywołania systemowego (syscall).

Ale co to są syscalle?

Jeśli nigdy nie miałeś okazji pisać w assemblerze (albo nawet nie wiesz co to jest ;)), to mam dobrą wiadomość. Z okazji sprzedsprzedaży trwa promocja -43% na moją książkę Assembler x86 - Programowanie i Podstawy Systemów Operacyjnych, która przekaże ci wszystko w bardzo prosty sposób.



Może spójrzmy najpierw jak wygląda przepływ danych API Windowsa.

API Windowsa ma pare warstw między programem, a kernelem.

  1. Language Runtime
  2. Win32 API (potocznie WinAPI)
  3. Windows Native API (NtAPI)
  4. OS Kernel

Zatem zacznijmy od początku.


Language Runtime

Language Runtime - to funkcje z biblioteki standardowej C, takie jak np. exit. int main(void) { exit(1337); }


Win32 API

Win32 API (potocznie WinAPI) - to udokumentowane API Windowsa, które korzysta z Windows Native API (NtAPI).

Obsługuje ono wszystkie funkcjonalności systemu, takie jak: IO, obsługa połączeń TCP/UDP, systemu plików, window manager...

Wszystkie języki programowania, które mają w bibliotece standardowej obsługę funkcji systemowych korzystają z Win32 API (WinAPI) do ich obsługi - to powinno ci wiele wyjaśnić.

Przykład tego samego kodu zaimplementowanego przy użyciu Win32 API (WinAPI). ```

include<windows.h>

int main(void) { ExitProcess(1337); } ```


Windows Native API (NtAPI)

NtAPI - to nieudokumentowane API Windowsa, które wykonuje wywołania systemowe (nie wszystkie funckje z NtAPI wykonują syscalle! - tylko funckje w przedrostkiem Nt i Zw).

Ale dlaczego funkcje NtAPI nie są udokumentowane?

Bo mając na względzie to, iż istnieje o wiele prostsze API (poziom wyżej - Win32 API), to nie ma potrzeby tworzenia kolejnego użytkowego API. NtAPI jest po prostu niższą warstwą przepływu danych, stworzoną na potrzeby architektury Windowsa.

Jak wygląda teraz wyżej przedstawiony kod przy użyciu NtAPI?

To już nie takie proste przez wzgląd na większą złożoność funkcji NtAPI, ale przejdźmy może do wstecznej inżynierii.


Funkcja main

int main(void) {
    ExitProcess(1337);
}

deasemblacja przy użyciu IDA:

Liczba 1337 w hexie jest równa 0x539.

Funckaj ExitProcess

Przechodząc do funkcji ExitProcess, jesteśmy na poziome Win32 API (potocznie WinAPI).


Funkcja ExitProcess znajduje się w kernel32.dll, jak widać jej logika jest prosta, odwołuje się do innej biblioteki dll załadowanej do pamięci.

call    cs:off_7FF8613A39D0

Pod offsetem off_7FF8613A39D0 jest funkcja RtlExitUserProcess w bibliotece ntdll.dll.

Co oznacza że dotarliśmy do Windows Native API! (NtAPI)

Co oznacza przedrostek Rtl?

Funkcje NtAPI z przedrostkiem Rtl nie obsługują bezpośrednio jądra systemu. W nich znajduje się logika, która to dopiero odwołuje się do funkcji z przedrostkiem Nt i Zw (do tych co już wykonują syscalle).

Co z resztą widać na przykładzie funkcji którą reversujemy.

W tym artykule nie będę analizował całego kodu.

Póki co skoczymy do pierwszego wywołania NtTerminateProcess.

Dokumentacja konwencji wywołań daje przykład tego, w jakich rejestrach znajdują się argumenty funkcji. func1(int a, int b, int c, int d, int e, int f); // a in RCX, b in RDX, c in R8, d in R9, f then e pushed on stack Przy pierwszym wywołaniu widać że rejestr RDX przechowuje exit code 1337 (0x539), ale według konwencji wywołań w RDX przechowywany jest drugi parametr. Co oznacza że NtTerminateProcess przyjmuje jeszcze jeden paramter, który w tym wypadku jest równy 0.

Teraz przejdziemy do następnego wywołania funkcji NtTerminateProcess.

Po wartości pierwszego argumenty, można wywnioskować że jest to typ HANDLE.

Ale skąd te wnioski?

Decymalnie 0xFFFFFFFFFFFFFFFF będzie oznaczać -1.

Można to prosto sprawdzić za pomocą funkcji GetCurrentProcess().

int main(void) {
    HANDLE handle = GetCurrentProcess();
    printf("%d\n", handle);
    printf("%#010x\n", handle);
}

output konsoli: -1 0xffffffff

Zatem -1 w typie HANDLE oznacza nasz aktualnie wykonywany proces. Czyli po tym syscallu proces popełnia samobójstwo :), działanie programu dobiega końca.


Podobne artykuły