Jak zajrzeć do plików binarnych z wiersza poleceń systemu Linux?
Opublikowany: 2022-01-29 Masz tajemniczy plik? Polecenie file
Linux szybko powie ci, jaki to jest typ pliku. Jeśli jednak jest to plik binarny, możesz dowiedzieć się o nim jeszcze więcej. file
ma całą masę stabilnych kolegów, które pomogą ci go przeanalizować. Pokażemy Ci, jak korzystać z niektórych z tych narzędzi.
Identyfikowanie typów plików
Pliki zwykle mają cechy, które pozwalają pakietom oprogramowania określić, jaki jest to typ pliku, a także co reprezentują zawarte w nim dane. Nie ma sensu otwierać pliku PNG w odtwarzaczu muzycznym MP3, więc przydatne i pragmatyczne jest, aby plik zawierał jakiś identyfikator.
Może to być kilka bajtów podpisu na samym początku pliku. Pozwala to na jednoznaczne określenie formatu i zawartości pliku. Czasami typ pliku jest wywnioskowany z charakterystycznego aspektu wewnętrznej organizacji samych danych, zwanego architekturą pliku.
Niektóre systemy operacyjne, takie jak Windows, są całkowicie kierowane przez rozszerzenie pliku. Można to nazwać naiwnym lub ufnym, ale system Windows zakłada, że każdy plik z rozszerzeniem DOCX naprawdę jest plikiem edytora tekstu DOCX. Linux taki nie jest, jak wkrótce się przekonasz. Chce dowodu i zagląda do pliku, aby go znaleźć.
Opisane tutaj narzędzia zostały już zainstalowane w dystrybucjach Manjaro 20, Fedora 21 i Ubuntu 20.04, których użyliśmy do zbadania tego artykułu. Zacznijmy nasze dochodzenie od polecenia file
.
Korzystanie z pliku Polecenie
W naszym bieżącym katalogu mamy kolekcję różnych typów plików. Są mieszanką dokumentów, kodu źródłowego, plików wykonywalnych i tekstowych.
Polecenie ls
pokaże nam, co jest w katalogu, a -hl
(rozmiary czytelne dla człowieka, długa lista) pokaże nam rozmiar każdego pliku:
ls-hl
Wypróbujmy file
na kilku z nich i zobaczmy, co otrzymamy:
plik instrukcje_budowy.odt
plik instrukcje_budowy.pdf
plik COBOL_Report_Apr60.djvu
Trzy formaty plików są poprawnie zidentyfikowane. Tam, gdzie to możliwe, file
dostarcza nam nieco więcej informacji. Zgłoszono, że plik PDF jest w formacie wersji 1.5.
Nawet jeśli zmienimy nazwę pliku ODT tak, aby miał rozszerzenie o dowolnej wartości XYZ, plik nadal jest poprawnie identyfikowany, zarówno w przeglądarce plików Files
, jak i w wierszu poleceń za pomocą file
.
W przeglądarce plików Files
jest wyświetlana poprawna ikona. W wierszu poleceń file
ignoruje rozszerzenie i zagląda do wnętrza pliku, aby określić jego typ:
plik instrukcje_budowy.xyz
Używanie file
na nośniku, takim jak pliki graficzne i muzyczne, zwykle dostarcza informacji dotyczących ich formatu, kodowania, rozdzielczości itd.:
plik zrzut ekranu.png
plik zrzut ekranu.jpg
plik Pachelbel_Canon_In_D.mp3
Co ciekawe, nawet w przypadku plików tekstowych, file
nie ocenia pliku po rozszerzeniu. Na przykład, jeśli masz plik z rozszerzeniem „.c”, zawierający standardowy zwykły tekst, ale bez kodu źródłowego, file
nie pomyli go z oryginalnym plikiem z kodem źródłowym C:
funkcja pliku+nagłówki.h
plik makefile
plik hello.c
file
poprawnie identyfikuje plik nagłówkowy („.h”) jako część kolekcji plików kodu źródłowego C i wie, że makefile jest skryptem.
Używanie pliku z plikami binarnymi
Pliki binarne są bardziej „czarną skrzynką” niż inne. Pliki obrazów można przeglądać, odtwarzać pliki dźwiękowe, a pliki dokumentów można otwierać za pomocą odpowiedniego pakietu oprogramowania. Jednak pliki binarne są większym wyzwaniem.
Na przykład pliki „hello” i „wd” to binarne pliki wykonywalne. Są programami. Plik o nazwie „wd.o” jest plikiem obiektowym. Gdy kod źródłowy jest kompilowany przez kompilator, tworzony jest jeden lub więcej plików obiektowych. Zawierają one kod maszynowy, który komputer ostatecznie wykona po uruchomieniu gotowego programu, wraz z informacjami dla konsolidatora. Konsolidator sprawdza każdy plik obiektowy pod kątem wywołań funkcji do bibliotek. Łączy je z dowolnymi bibliotekami, z których korzysta program. Wynikiem tego procesu jest plik wykonywalny.
Plik „watch.exe” to binarny plik wykonywalny, który został skompilowany w celu uruchomienia w systemie Windows:
plik wd
plik wd.o
plik cześć
plik watch.exe
Biorąc ten ostatni jako pierwszy, file
mówi nam, że plik „watch.exe” to wykonywalny program konsoli PE32+ dla rodziny procesorów x86 w systemie Microsoft Windows. PE oznacza przenośny format wykonywalny, który ma wersje 32- i 64-bitowe. PE32 to wersja 32-bitowa, a PE32+ to wersja 64-bitowa.
Wszystkie pozostałe trzy pliki są identyfikowane jako pliki w formacie wykonywalnym i z możliwością łączenia (ELF). Jest to standard dla plików wykonywalnych i udostępnianych plików obiektów, takich jak biblioteki. Przyjrzymy się wkrótce formatowi nagłówka ELF.
To, co może przykuć twoją uwagę, to to, że dwa pliki wykonywalne („wd” i „hello”) są identyfikowane jako obiekty współdzielone Linux Standard Base (LSB), a plik obiektowy „wd.o” jest identyfikowany jako relokowalny LSB. Słowo wykonywalny jest oczywiste pod jego nieobecność.
Pliki obiektowe są relokowalne, co oznacza, że zawarty w nich kod można załadować do pamięci w dowolnym miejscu. Pliki wykonywalne są wymienione jako obiekty współdzielone, ponieważ zostały utworzone przez konsolidator z plików obiektowych w taki sposób, że dziedziczą tę właściwość.
Pozwala to systemowi ASMR (Address Space Layout Randomization) na załadowanie plików wykonywalnych do pamięci pod wybranymi przez siebie adresami. Standardowe pliki wykonywalne mają adres ładowania zakodowany w ich nagłówkach, które określają, gdzie są ładowane do pamięci.
ASMR to technika bezpieczeństwa. Ładowanie plików wykonywalnych do pamięci pod przewidywalnymi adresami czyni je podatnymi na atak. Dzieje się tak, ponieważ ich punkty wejścia i lokalizacje ich funkcji będą zawsze znane atakującym. Niezależne od pozycji pliki wykonywalne (PIE) umieszczone pod losowym adresem przezwyciężają tę podatność.
Jeśli skompilujemy nasz program za pomocą kompilatora gcc
i udostępnimy opcję -no-pie
, wygenerujemy konwencjonalny plik wykonywalny.
Opcja -o
(plik wyjściowy) pozwala nam podać nazwę dla naszego pliku wykonywalnego:
gcc -o cześć -no-pie hello.c
Użyjemy file
na nowym pliku wykonywalnym i zobaczymy, co się zmieniło:
plik cześć
Rozmiar pliku wykonywalnego jest taki sam jak poprzednio (17 KB):
ls -hl cześć
Plik binarny jest teraz identyfikowany jako standardowy plik wykonywalny. Robimy to tylko w celach demonstracyjnych. Jeśli skompilujesz aplikacje w ten sposób, stracisz wszystkie zalety ASMR.
Dlaczego plik wykonywalny jest tak duży?
Nasz przykładowy program hello
ma 17 KB, więc trudno go nazwać dużym, ale wszystko jest względne. Kod źródłowy ma 120 bajtów:
kot cześć.c
Co powoduje masowanie pliku binarnego, jeśli wszystko, co robi, to wyświetlanie jednego ciągu w oknie terminala? Wiemy, że istnieje nagłówek ELF, ale dla 64-bitowego pliku binarnego ma on tylko 64 bajty. Po prostu musi to być coś innego:
ls -hl cześć
Przeskanujmy plik binarny poleceniem strings
jako pierwszym prostym krokiem, aby odkryć, co jest w środku. Przełożymy to na less
:
smyczki witaj | mniej
W pliku binarnym znajduje się wiele ciągów znaków, oprócz „Hello, Geek world!” z naszego kodu źródłowego. Większość z nich to etykiety regionów w pliku binarnym oraz nazwy i informacje o linkach udostępnianych obiektów. Należą do nich biblioteki i funkcje w tych bibliotekach, od których zależy plik binarny.
Polecenie ldd
pokazuje nam współdzielone zależności obiektów binarnych:
stary cześć
Dane wyjściowe zawierają trzy wpisy, a dwa z nich zawierają ścieżkę do katalogu (pierwszy nie zawiera):
- linux-vdso.so: Virtual Dynamic Shared Object (VDSO) to mechanizm jądra, który umożliwia dostęp do zestawu procedur w przestrzeni jądra przez plik binarny w przestrzeni użytkownika. Pozwala to uniknąć obciążenia związanego z przełączaniem kontekstu z trybu jądra użytkownika. Obiekty udostępnione VDSO są zgodne z formatem Executable and Linkable Format (ELF), dzięki czemu można je dynamicznie łączyć z plikami binarnymi w czasie wykonywania. VDSO jest przydzielane dynamicznie i korzysta z ASMR. Funkcjonalność VDSO zapewnia standardowa biblioteka GNU C, jeśli jądro obsługuje schemat ASMR.
- libc.so.6: obiekt współdzielony biblioteki GNU C.
- /lib64/ld-linux-x86-64.so.2: To jest dynamiczny linker, którego plik binarny chce użyć. Dynamiczny linker odpytuje plik binarny, aby odkryć, jakie ma zależności. Uruchamia te udostępnione obiekty w pamięci. Przygotowuje plik binarny do uruchomienia i jest w stanie znaleźć i uzyskać dostęp do zależności w pamięci. Następnie uruchamia program.
Nagłówek ELF
Możemy zbadać i zdekodować nagłówek ELF za pomocą narzędzia readelf
i opcji -h
(nagłówek pliku):
przeczytaj -h cześć
Nagłówek jest dla nas interpretowany.
Pierwszy bajt wszystkich plików binarnych ELF jest ustawiony na wartość szesnastkową 0x7F. Kolejne trzy bajty są ustawione na 0x45, 0x4C i 0x46. Pierwszy bajt to flaga, która identyfikuje plik jako plik binarny ELF. Aby wszystko było jasne, następne trzy bajty to „ELF” w ASCII:
- Klasa: wskazuje, czy plik binarny jest 32- lub 64-bitowym plikiem wykonywalnym (1=32, 2=64).
- Dane: Wskazuje używaną endianowość. Kodowanie Endian definiuje sposób przechowywania liczb wielobajtowych. W kodowaniu big-endian liczba jest przechowywana z najważniejszymi bitami w pierwszej kolejności. W kodowaniu little-endian liczba jest przechowywana z najmniej znaczącymi bitami w pierwszej kolejności.
- Wersja: Wersja ELF (obecnie to 1).
- OS/ABI: reprezentuje typ używanego interfejsu binarnego aplikacji. Definiuje to interfejs między dwoma modułami binarnymi, takimi jak program i biblioteka współdzielona.
- Wersja ABI: Wersja ABI.
- Typ: typ pliku binarnego ELF. Typowe wartości to
ET_REL
dla relokowalnego zasobu (takiego jak plik obiektowy),ET_EXEC
dla pliku wykonywalnego skompilowanego z flagą-no-pie
orazET_DYN
dla pliku wykonywalnego zgodnego z ASMR. - Maszyna: Architektura zestawu instrukcji. Wskazuje platformę docelową, dla której utworzono plik binarny.
- Wersja: Zawsze ustawiona na 1, dla tej wersji ELF.
- Adres punktu wejścia: adres pamięci w pliku binarnym, od którego rozpoczyna się wykonanie.
Pozostałe wpisy to rozmiary i liczby regionów i sekcji w pliku binarnym, dzięki czemu można obliczyć ich lokalizację.
Szybki rzut oka na pierwsze osiem bajtów pliku binarnego za pomocą hexdump
pokaże bajt podpisu i ciąg „ELF” w pierwszych czterech bajtach pliku. Opcja -C
(kanoniczna) daje nam reprezentację ASCII bajtów obok ich wartości szesnastkowych, a opcja -n
(liczba) pozwala nam określić, ile bajtów chcemy zobaczyć:
hexdump -C -n 8 cześć
objdump i widok granularny
Jeśli chcesz zobaczyć najdrobniejsze szczegóły, możesz użyć polecenia objdump
z opcją -d
(deasemblacja):
objdump -d cześć | mniej
To rozkłada wykonywalny kod maszynowy i wyświetla go w bajtach szesnastkowych obok odpowiednika w języku asemblera. Lokalizacja adresu pierwszego bye w każdym wierszu jest pokazana po lewej stronie.
Jest to przydatne tylko wtedy, gdy potrafisz czytać asembler lub jesteś ciekawy, co dzieje się za zasłoną. Jest dużo danych wyjściowych, więc wrzuciliśmy je do less
.
Kompilowanie i łączenie
Istnieje wiele sposobów kompilacji pliku binarnego. Na przykład deweloper decyduje, czy dołączyć informacje debugowania. Sposób, w jaki plik binarny jest połączony, ma również wpływ na jego zawartość i rozmiar. Jeśli referencje binarne współdzielą obiekty jako zależności zewnętrzne, będzie on mniejszy niż ten, z którym zależności łączą się statycznie.
Większość programistów zna już polecenia, które tutaj omówiliśmy. Jednak innym oferują proste sposoby na grzebanie i sprawdzanie, co znajduje się w binarnej czarnej skrzynce.