Podziel się trikami hakerskimi, przesyłając PR-y doHackTricks i HackTricks Cloud repozytoriów na GitHubie.
Podstawowe informacje
Binarki systemu Mac OS zazwyczaj są kompilowane jako uniwersalne pliki binarne. Uniwersalny plik binarny może obsługiwać wiele architektur w tym samym pliku.
Te binarki stosują strukturę Mach-O, która składa się z:
Jak możesz sobie wyobrazić, zazwyczaj uniwersalny plik binarny skompilowany dla 2 architektur podwaja rozmiar tego skompilowanego tylko dla 1 architektury.
Nagłówek Mach-O
Nagłówek zawiera podstawowe informacje o pliku, takie jak magiczne bajty identyfikujące go jako plik Mach-O oraz informacje o architekturze docelowej. Możesz go znaleźć w: mdfind loader.h | grep -i mach-o | grep -E "loader.h$"
#defineMH_MAGIC0xfeedface /* the mach magic number */#defineMH_CIGAM0xcefaedfe /* NXSwapInt(MH_MAGIC) */struct mach_header {uint32_t magic; /* mach magic number identifier */cpu_type_t cputype; /* cpu specifier (e.g. I386) */cpu_subtype_t cpusubtype; /* machine specifier */uint32_t filetype; /* type of file (usage and alignment for the file) */uint32_t ncmds; /* number of load commands */uint32_t sizeofcmds; /* the size of all the load commands */uint32_t flags; /* flags */};#defineMH_MAGIC_640xfeedfacf /* the 64-bit mach magic number */#defineMH_CIGAM_640xcffaedfe /* NXSwapInt(MH_MAGIC_64) */struct mach_header_64 {uint32_t magic; /* mach magic number identifier */int32_t cputype; /* cpu specifier */int32_t cpusubtype; /* machine specifier */uint32_t filetype; /* type of file */uint32_t ncmds; /* number of load commands */uint32_t sizeofcmds; /* the size of all the load commands */uint32_t flags; /* flags */uint32_t reserved; /* reserved */};
MH_OBJECT: Plik obiektowy do przenoszenia (produkty pośrednie kompilacji, jeszcze nie wykonywalne).
MH_EXECUTE: Pliki wykonywalne.
MH_FVMLIB: Plik biblioteki VM o stałym rozmiarze.
MH_CORE: Zrzuty kodu.
MH_PRELOAD: Plik wykonywalny wczytany z góry (już nieobsługiwany w XNU).
MH_DYLIB: Biblioteki dynamiczne.
MH_DYLINKER: Łącznik dynamiczny.
MH_BUNDLE: "Pliki wtyczek". Generowane za pomocą -bundle w gcc i ładowane jawnie przez NSBundle lub dlopen.
MH_DYSM: Plik towarzyszący .dSym (plik ze symbolami do debugowania).
MH_KEXT_BUNDLE: Rozszerzenia jądra.
# Checking the mac header of a binaryotool-archarm64e-hv/bin/lsMachheadermagiccputypecpusubtypecapsfiletypencmdssizeofcmdsflagsMH_MAGIC_64ARM64EUSR00EXECUTE191728NOUNDEFSDYLDLINKTWOLEVELPIE
Kod źródłowy definiuje również kilka przydatnych flag do ładowania bibliotek:
MH_NOUNDEFS: Brak niesprecyzowanych odwołań (w pełni połączony)
MH_DYLDLINK: Łączenie Dyld
MH_PREBOUND: Dynamiczne odwołania są wcześniej związane.
MH_SPLIT_SEGS: Plik dzieli segmenty tylko do odczytu i do zapisu.
MH_WEAK_DEFINES: Binarne symbole zdefiniowane jako słabe
MH_BINDS_TO_WEAK: Binarne używa słabych symboli
MH_ALLOW_STACK_EXECUTION: Umożliwia wykonanie stosu
MH_NO_REEXPORTED_DYLIBS: Biblioteka nie zawiera poleceń LC_REEXPORT
MH_PIE: Wykonywalny o niezależnej pozycji
MH_HAS_TLV_DESCRIPTORS: Istnieje sekcja z zmiennymi lokalnymi wątku
MH_NO_HEAP_EXECUTION: Brak wykonania dla stron sterty/danych
MH_HAS_OBJC: Binarne sekcje oBject-C
MH_SIM_SUPPORT: Wsparcie dla symulatora
MH_DYLIB_IN_CACHE: Używane w dylibs/frameworks w udostępnionej pamięci podręcznej bibliotek.
Polecenia ładowania Mach-O
Układ pliku w pamięci jest tutaj określony, szczegółowo opisując lokalizację tabeli symboli, kontekst głównego wątku na początku wykonania oraz wymagane biblioteki współdzielone. Instrukcje są dostarczane do dynamicznego ładowacza (dyld) dotyczące procesu ładowania binarnego do pamięci.
Używa struktury load_command, zdefiniowanej w wspomnianym loader.h:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Istnieje około 50 różnych rodzajów poleceń ładowania, które system obsługuje w inny sposób. Najczęstsze z nich to: LC_SEGMENT_64, LC_LOAD_DYLINKER, LC_MAIN, LC_LOAD_DYLIB i LC_CODE_SIGNATURE.
LC_SEGMENT/LC_SEGMENT_64
W zasadzie ten rodzaj polecenia ładowania definiuje sposób wczytywania segmentów __TEXT (kod wykonywalny) i __DATA (dane procesu) zgodnie z przesunięciami wskazanymi w sekcji danych podczas wykonywania binariatu.
Te polecenia definiują segmenty, które są mapowane do przestrzeni pamięci wirtualnej procesu podczas jego wykonywania.
Istnieją różne rodzaje segmentów, takie jak segment __TEXT, który przechowuje kod wykonywalny programu, oraz segment __DATA, który zawiera dane używane przez proces. Te segmenty znajdują się w sekcji danych pliku Mach-O.
Każdy segment może być dodatkowo podzielony na wiele sekcji. Struktura polecenia ładowania zawiera informacje o tych sekcjach w odpowiednim segmencie.
W nagłówku znajduje się najpierw nagłówek segmentu:
struct segment_command_64 { /* dla architektur 64-bitowych */uint32_t cmd; /* LC_SEGMENT_64 */uint32_t cmdsize; /* zawiera rozmiar struktur section_64 */char segname[16]; /* nazwa segmentu */uint64_t vmaddr; /* adres pamięci tego segmentu */uint64_t vmsize; /* rozmiar pamięci tego segmentu */uint64_t fileoff; /* przesunięcie pliku tego segmentu */uint64_t filesize; /* ilość do zmapowania z pliku */int32_t maxprot; /* maksymalna ochrona VM */int32_t initprot; /* początkowa ochrona VM */uint32_t nsects; /* liczba sekcji w segmencie */uint32_t flags; /* flagi */};
Przykład nagłówka segmentu:
Ten nagłówek definiuje liczbę sekcji, których nagłówki po nim występują:
struct section_64 { /* for 64-bit architectures */char sectname[16]; /* name of this section */char segname[16]; /* segment this section goes in */uint64_t addr; /* memory address of this section */uint64_t size; /* size in bytes of this section */uint32_t offset; /* file offset of this section */uint32_t align; /* section alignment (power of 2) */uint32_t reloff; /* file offset of relocation entries */uint32_t nreloc; /* number of relocation entries */uint32_t flags; /* flags (section type and attributes)*/uint32_t reserved1; /* reserved (for offset or index) */uint32_t reserved2; /* reserved (for count or sizeof) */uint32_t reserved3; /* reserved */};
Przykład nagłówka sekcji:
Jeśli dodaszprzesunięcie sekcji (0x37DC) + przesunięcie, gdzie arch zaczyna się, w tym przypadku 0x18000 --> 0x37DC + 0x18000 = 0x1B7DC
Możliwe jest również uzyskanie informacji o nagłówkach z wiersza poleceń za pomocą:
otool-lv/bin/ls
Wspólne segmenty ładowane przez tę komendę:* **`__PAGEZERO`:** Instruuje jądro, aby **mapować** **adres zero**, dzięki czemu **nie można go odczytać, zapisać ani wykonać**. Zmienne maxprot i minprot w strukturze są ustawione na zero, aby wskazać, że na tej stronie **nie ma praw do odczytu-zapisu-wykonania**.
* Ta alokacja jest ważna dla **zmniejszenia podatności na odwołania do wskaźników NULL**. Wynika to z faktu, że XNU narzuca twardą stronę zero, która zapewnia, że pierwsza strona (tylko pierwsza) pamięci jest nieosiągalna (oprócz w i386). Binarny może spełnić te wymagania, tworząc małe \_\_PAGEZERO (używając `-pagezero_size`) obejmujące pierwsze 4 KB i pozwalając na dostęp do reszty pamięci 32-bitowej zarówno w trybie użytkownika, jak i jądra.
* **`__TEXT`**: Zawiera **wykonywalny** **kod** z uprawnieniami **do odczytu** i **wykonania** (bez możliwości zapisu)**.** Wspólne sekcje tego segmentu:
* `__text`: Skompilowany kod binarny* `__const`: Dane stałe (tylko do odczytu)* `__[c/u/os_log]string`: Stałe łańcuchy znaków C, Unicode lub os logs* `__stubs` i `__stubs_helper`: Zaangażowane podczas procesu dynamicznego ładowania bibliotek* `__unwind_info`: Dane rozluźniania stosu.* Zauważ, że cała ta zawartość jest podpisana, ale również oznaczona jako wykonywalna (tworząc więcej opcji do eksploatacji sekcji, które niekoniecznie potrzebują tego uprawnienia, jak sekcje dedykowane łańcuchom znaków).
* **`__DATA`**: Zawiera dane, które są **do odczytu** i **zapisu** (bez możliwości wykonania)**.*** `__got:` Globalna tabela przesunięć* `__nl_symbol_ptr`: Wskaźnik symbolu niepóźnego (łączenie podczas ładowania)* `__la_symbol_ptr`: Wskaźnik symbolu leniwego (łączenie przy użyciu)* `__const`: Powinny być to dane tylko do odczytu (w rzeczywistości nie)* `__cfstring`: Ciągi CoreFoundation* `__data`: Zmienne globalne (które zostały zainicjowane)* `__bss`: Zmienne statyczne (które nie zostały zainicjowane)* `__objc_*` (\_\_objc\_classlist, \_\_objc\_protolist, itp.): Informacje używane przez środowisko uruchomieniowe Objective-C
* **`__DATA_CONST`**: \_\_DATA.\_\_const nie jest gwarantowane jako stałe (uprawnienia do zapisu), podobnie jak inne wskaźniki i tabela GOT. Ta sekcja sprawia, że `__const`, niektóre inicjalizatory i tabela GOT (po rozwiązaniu) są **tylko do odczytu** za pomocą `mprotect`.
* **`__LINKEDIT`**: Zawiera informacje dla linkera (dyld), takie jak wpisy do tabel symboli, łańcuchów i relokacji. Jest to ogólny kontener na treści, które nie znajdują się w `__TEXT` ani `__DATA`, a jego zawartość jest opisana w innych poleceniach ładowania.
* Informacje dyld: Rebase, operacje wiązania niepóźnego/leniwego/słabego i informacje o eksporcie* Początki funkcji: Tabela adresów początkowych funkcji* Dane w kodzie: Wyspy danych w \_\_text* Tabela symboli: Symbole w binarnym pliku* Tabela symboli pośrednich: Symbole wskaźników/stubów* Tabela łańcuchów znaków* Sygnatura kodu* **`__OBJC`**: Zawiera informacje używane przez środowisko uruchomieniowe Objective-C. Chociaż te informacje mogą być również znalezione w segmencie \_\_DATA, w różnych sekcjach \_\_objc\_\*.
* **`__RESTRICT`**: Segment bez zawartości z pojedynczą sekcją o nazwie **`__restrict`** (również pustą), która zapewnia, że podczas uruchamiania binarnego zostaną zignorowane zmienne środowiskowe DYLD.
Jak można było zauważyć w kodzie, **segmenty również obsługują flagi** (choć nie są one zbyt często używane):* `SG_HIGHVM`: Tylko rdzeń (nieużywane)* `SG_FVMLIB`: Nie używane* `SG_NORELOC`: Segment nie ma relokacji* `SG_PROTECTED_VERSION_1`: Szyfrowanie. Używane na przykład przez Finder do szyfrowania tekstu w segmencie **`__TEXT`**.
### **`LC_UNIXTHREAD/LC_MAIN`****`LC_MAIN`** zawiera punkt wejścia w atrybucie **entryoff**. Podczas ładowania, **dyld** po prostu **dodaje** tę wartość do (w pamięci) **bazowego adresu binarnego**, a następnie **przechodzi** do tej instrukcji, aby rozpocząć wykonywanie kodu binarnego.
**`LC_UNIXTHREAD`** zawiera wartości rejestrów, które muszą być ustawione przy uruchamianiu głównego wątku. Jest to już przestarzałe, ale **`dyld`** wciąż tego używa. Można zobaczyć wartości rejestrów ustawione przez to polecenie za pomocą:
Zawiera informacje na temat podpisu kodu pliku Mach-O. Zawiera tylko przesunięcie, które wskazuje na blok podpisu. Zazwyczaj znajduje się na samym końcu pliku.
Można jednak znaleźć pewne informacje na temat tej sekcji w tym poście na blogu oraz w tym gists.
LC_ENCRYPTION_INFO[_64]
Obsługuje szyfrowanie binarne. Jednak oczywiście, jeśli atakujący zdoła skompromitować proces, będzie mógł zrzucić pamięć bez szyfrowania.
LC_LOAD_DYLINKER
Zawiera ścieżkę do wykonywalnego dynamicznego łącznika, który mapuje biblioteki współdzielone do przestrzeni adresowej procesu. Wartość zawsze jest ustawiona na /usr/lib/dyld. Ważne jest zauważenie, że w macOS mapowanie dylibów zachodzi w trybie użytkownika, a nie w trybie jądra.
LC_IDENT
Przestarzałe, ale gdy skonfigurowane do generowania zrzutów w przypadku paniki, tworzony jest zrzut rdzenia Mach-O, a wersja jądra jest ustawiana w poleceniu LC_IDENT.
LC_UUID
Losowy UUID. Jest przydatny do niczego bezpośrednio, ale XNU przechowuje go wraz z resztą informacji o procesie. Może być używany w raportach o awariach.
LC_DYLD_ENVIRONMENT
Pozwala wskazać zmienne środowiskowe dla dyld przed wykonaniem procesu. Może to być bardzo niebezpieczne, ponieważ pozwala na wykonanie dowolnego kodu wewnątrz procesu, dlatego to polecenie ładowania jest używane tylko w dyld zbudowanym z #define SUPPORT_LC_DYLD_ENVIRONMENT i dodatkowo ogranicza przetwarzanie tylko do zmiennych o formie DYLD_..._PATH określających ścieżki ładowania.
LC_LOAD_DYLIB
To polecenie ładowania opisuje zależność dynamicznej biblioteki, która nakazujeładowaczowi (dyld) załadować i połączyć tę bibliotekę. Istnieje polecenie ładowania LC_LOAD_DYLIBdla każdej biblioteki, którą wymaga plik Mach-O.
To polecenie ładowania jest strukturą typu dylib_command (która zawiera strukturę dylib, opisującą rzeczywistą zależną bibliotekę dynamiczną):
struct dylib_command {
uint32_t cmd; /* LC_LOAD_{,WEAK_}DYLIB */
uint32_t cmdsize; /* includes pathname string */
struct dylib dylib; /* the library identification */
};
struct dylib {
union lc_str name; /* library's path name */
uint32_t timestamp; /* library's build time stamp */
uint32_t current_version; /* library's current version number */
uint32_t compatibility_version; /* library's compatibility vers number*/
};
Możesz również uzyskać te informacje za pomocą wiersza poleceń:
Potencjalne biblioteki związane z złośliwym oprogramowaniem to:
DiskArbitration: Monitorowanie dysków USB
AVFoundation: Przechwytywanie dźwięku i obrazu
CoreWLAN: Skanowanie sieci Wifi.
Plik Mach-O może zawierać jeden lub więcej konstruktorów, które zostaną wykonane przed adresem określonym w LC_MAIN.
Przesunięcia dowolnych konstruktorów są przechowywane w sekcji __mod_init_func segmentu __DATA_CONST.
Dane Mach-O
W centrum pliku znajduje się region danych, który składa się z kilku segmentów zdefiniowanych w regionie poleceń ładowania. W każdym segmencie może być umieszczona różnorodność sekcji danych, z każdą sekcją zawierającą kod lub dane specyficzne dla danego typu.
Dane to w zasadzie część zawierająca wszystkie informacje, które są ładowane przez polecenia ładowania LC_SEGMENTS_64