Standardowym językiem programowania w systemie Linux jest C. Z tego względu najlepsze opisy wywołań systemowych pokażą je jako wywoływane funkcje C. Biorąc pod uwagę ich opis jako funkcję C i wiedzę o tym, jak zmapować je do rzeczywistego wywołania systemowego w złożeniu, będzie można łatwo użyć dowolnego wywołania systemowego, które chcesz.
Po pierwsze, potrzebujesz referencji dla wszystkich wywołań systemowych, jakie będą wyglądały dla programisty C. Najlepszą, jaką znam, jest sekcja Linux man-pages project, w szczególności sekcja system calls.
Jako przykład przyjrzyjmy się wywołaniu systemowemu write
, ponieważ jest to odpowiedź na pytanie. Jak widać, pierwszym parametrem jest liczba całkowita ze znakiem, która zwykle jest deskryptorem pliku zwróconym przez systemową konfigurację open
. Te deskryptory plików mogły również zostać odziedziczone po procesie nadrzędnym, jak to zwykle bywa w przypadku pierwszych trzech deskryptorów plików (0 = stdin, 1 = standardowe wyjście, 2 = stderr). Drugi parametr to wskaźnik do bufora, a trzeci parametr to rozmiar bufora (jako liczba całkowita bez znaku). Wreszcie funkcja zwraca liczbę całkowitą ze znakiem, która jest liczbą zapisanych bajtów lub liczbą ujemną z powodu błędu.
Teraz, jak odwzorować to na faktyczne wywołanie systemowe? Istnieje wiele sposobów wykonania wywołania systemowego na 32-bitowym systemie x86 (z którego prawdopodobnie korzystasz, na podstawie nazw rejestrów); uważaj, że jest zupełnie inny na 64-bitowym x86 (upewnij się, że montujesz w trybie 32-bitowym i łączymy 32-bitowy plik wykonywalny, zobacz przykład, jak coś może pójść źle w przeciwnym razie). Najstarszym, najprostszym i najwolniejszym z nich w 32-bitowym x86 jest metoda int $0x80
.
Dla metody int $0x80
, można umieścić numer wywołania systemowego w %eax
i parametry w %ebx
, %ecx
, %edx
, %esi
, %edi
i %ebp
, w tej kolejności. Następnie wywołujemy int $0x80
, a zwracana wartość z wywołania systemowego jest na %eax
. Zwróć uwagę, że ta wartość zwracana to inna z tego, co mówi odnośnik; odniesienie pokazuje, jak biblioteka C go zwróci, ale wywołanie systemowe zwraca -errno
po błędzie (na przykład -EINVAL
). Biblioteka C przeniesie to do errno
i zwróci -1
w takim przypadku. Aby uzyskać więcej szczegółów, zobacz syscalls(2) i intro(2).
więc, w przykładzie write
, należy umieścić numer systemowy write
zaproszenia w %eax
pierwszy parametr (numer deskryptora pliku) w %ebx
, drugi parametr (wskaźnik do łańcucha) w %ecx
, a trzeci parametr (długość struny) w %edx
. Wywołanie systemowe zwróci w postaci %eax
albo liczbę zapisanych bajtów albo zanegowany numer błędu (jeśli wartość zwracana wynosi od -1 do -4095, jest to zanegowany numer błędu).
Na koniec, jak znaleźć numery połączeń systemowych? Można je znaleźć pod adresem /usr/include/linux/unistd.h
. W moim systemie obejmuje to tylko /usr/include/asm/unistd.h
, który ostatecznie obejmuje /usr/include/asm/unistd_32.h
, więc numery są tam (dla write
, można zobaczyć __NR_write
jest 4
). To samo dotyczy numerów błędów, które pochodzą z /usr/include/linux/errno.h
(w moim systemie, po ściganiu łańcucha włączenia znajduję pierwsze z /usr/include/asm-generic/errno-base.h
, a resztę pod /usr/include/asm-generic/errno.h
). W przypadku wywołań systemowych, które używają innych stałych lub struktur, ich dokumentacja mówi, które nagłówki należy szukać, aby znaleźć odpowiednie definicje.
Teraz, jak już powiedziałem, int $0x80
jest najstarszą i najwolniejszą metodą. Nowsze procesory mają specjalne instrukcje systemowe, które są szybsze. Aby z nich korzystać, jądro udostępnia wirtualny dynamiczny obiekt współdzielony (vDSO
, jest podobny do biblioteki współdzielonej, ale tylko w pamięci) z funkcją, którą można wywołać w celu wykonania wywołania systemowego przy użyciu najlepszej dostępnej metody dla danego sprzętu. Udostępnia także funkcje specjalne, aby uzyskać bieżący czas, bez konieczności wykonywania połączenia systemowego i kilku innych rzeczy. Oczywiście korzystanie z dynamicznego linkera jest nieco trudniejsze.
Istnieje również inna starsza metoda, vsyscall
, która jest podobna do vDSO
, ale korzysta z pojedynczej strony o stałym adresie. Ta metoda jest przestarzała, spowoduje ostrzeżenia w dzienniku systemowym, jeśli używasz najnowszych jąder, może być wyłączona podczas uruchamiania na nowszych jądrach i może zostać usunięta w przyszłości. Nie używaj tego.
Oczywiście, nawet jeśli wyszukujesz każdego z nich w google lub wyszukiwaniu kodu źródłowego w celu jego implementacji, możesz znaleźć bezpośrednie przykłady, jak są one używane - co zrobiłem. Celem mojego wpisu jest pominięcie wyszukiwania i analizy za pomocą miłego arkusza oszukańczego. Dzięki. –