Hoe u in binaire bestanden kunt kijken vanaf de Linux-opdrachtregel

Een gestileerde Linux-terminal met regels groene tekst op een laptop.
fatmawati achmad zaenuri / Shutterstock

Heeft u een mysteriebestand? Het Linux file commando zal u snel vertellen welk type bestand het is. Als het echter een binair bestand is, kunt u er nog meer over te weten komen. file heeft een hele reeks stalgenoten die u zullen helpen het te analyseren. We laten u zien hoe u enkele van deze tools kunt gebruiken.

Identificatie van bestandstypen

Bestanden hebben meestal kenmerken waarmee softwarepakketten kunnen identificeren welk type bestand het is, en wat de gegevens erin vertegenwoordigen. Het zou niet logisch zijn om te proberen een PNG-bestand te openen in een mp3-muziekspeler, dus het is zowel handig als pragmatisch dat een bestand een of andere vorm van ID met zich meedraagt.

Dit kunnen enkele handtekeningbytes zijn aan het begin van het bestand. Hierdoor kan een bestand expliciet zijn over zijn formaat en inhoud. Soms wordt het bestandstype afgeleid uit een onderscheidend aspect van de interne organisatie van de gegevens zelf, bekend als de bestandsarchitectuur.

Sommige besturingssystemen, zoals Windows, worden volledig gestuurd door de bestandsextensie. Je kunt het goedgelovig of vertrouwend noemen, maar Windows gaat ervan uit dat elk bestand met de DOCX-extensie echt een DOCX-tekstverwerkingsbestand is. Linux is niet zo, zoals je snel zult zien. Het wil bewijs en kijkt in het bestand om het te vinden.

De tools die hier worden beschreven, waren al geïnstalleerd op de Manjaro 20-, Fedora 21- en Ubuntu 20.04-distributies die we hebben gebruikt om dit artikel te onderzoeken. Laten we ons onderzoek beginnen door de file opdracht.

Gebruik het bestand Command

We hebben een verzameling verschillende bestandstypen in onze huidige directory. Ze zijn een combinatie van document-, broncode-, uitvoerbare bestanden en tekstbestanden.

De ls commando laat ons zien wat er in de directory staat, en het -hl (door mensen leesbare formaten, lange lijst) optie toont ons de grootte van elk bestand:

ls -hl

ls -hl in een terminalvenster.

Laten we proberen file op een paar hiervan en kijk wat we krijgen:

file build_instructions.odt
file build_instructions.pdf
file COBOL_Report_Apr60.djvu

file build_instructions.odt in een terminalvenster.

De drie bestandsindelingen zijn correct geïdentificeerd. Waar mogelijk, file geeft ons wat meer informatie. Het PDF-bestand zou in de indeling van versie 1.5 zijn.

Zelfs als we het ODT-bestand hernoemen zodat het een extensie heeft met de willekeurige waarde van XYZ, wordt het bestand nog steeds correct geïdentificeerd, beide binnen de Files bestandsbrowser en op de opdrachtregel met file.

OpenDocument-bestand correct geïdentificeerd in de bestandsbrowser, ook al is de extensie XYZ.

Binnen de Files bestandsbrowser, krijgt deze het juiste pictogram. Op de opdrachtregel, file negeert de extensie en kijkt in het bestand om het type te bepalen:

file build_instructions.xyz

file build_instructions.xyz in een terminalvenster.

Gebruik makend van file op media, zoals beeld- en muziekbestanden, levert meestal informatie op over hun formaat, codering, resolutie, enzovoort:

file screenshot.png
file screenshot.jpg
file Pachelbel_Canon_In_D.mp3

bestand screenshot.png in een terminalvenster.

Interessant is dat zelfs met platte tekstbestanden, file beoordeelt het bestand niet op basis van de extensie. Als u bijvoorbeeld een bestand heeft met de extensie “.c”, dat standaard platte tekst bevat maar geen broncode, file verwart het niet met een echt C-broncodebestand:

file function+headers.h
file makefile
file hello.c

bestandsfunctie + headers.h in een terminalvenster.

file identificeert het header-bestand (“.h”) correct als onderdeel van een C-broncodeverzameling van bestanden, en het weet dat het makefile een script is.

Gebruik een bestand met binaire bestanden

Binaire bestanden zijn meer een “zwarte doos” dan andere. Afbeeldingsbestanden kunnen worden bekeken, geluidsbestanden kunnen worden afgespeeld en documentbestanden kunnen worden geopend met het juiste softwarepakket. Binaire bestanden zijn echter een grotere uitdaging.

De bestanden “hallo” en “wd” zijn bijvoorbeeld binaire uitvoerbare bestanden. Het zijn programma’s. Het bestand met de naam “wd.o” is een objectbestand. Wanneer de broncode wordt gecompileerd door een compiler, worden een of meer objectbestanden gemaakt. Deze bevatten de machinecode die de computer uiteindelijk zal uitvoeren wanneer het voltooide programma wordt uitgevoerd, samen met informatie voor de linker. De linker controleert elk objectbestand op functieaanroepen naar bibliotheken. Het koppelt ze aan alle bibliotheken die het programma gebruikt. Het resultaat van dit proces is een uitvoerbaar bestand.

Het bestand “watch.exe” is een binair uitvoerbaar bestand dat is gecompileerd om op Windows te draaien:

file wd
file wd.o
file hello
file watch.exe

bestand wd in een terminalvenster.

Eerst de laatste nemen, file vertelt ons dat het “watch.exe” -bestand een PE32 + uitvoerbaar consoleprogramma is voor de x86-processorfamilie op Microsoft Windows. PE staat voor portable executable format, dat 32- en 64-bit versies heeft. De PE32 is de 32-bits versie en de PE32 + is de 64-bits versie.

De andere drie bestanden worden allemaal geïdentificeerd als Executable and Linkable Format (ELF) -bestanden. Dit is een standaard voor uitvoerbare bestanden en gedeelde objectbestanden, zoals bibliotheken. We zullen binnenkort het ELF-headerformaat bekijken.

Wat misschien opvalt, is dat de twee uitvoerbare bestanden (“wd” en “hallo”) worden geïdentificeerd als gedeelde Linux Standard Base (LSB) -objecten, en het objectbestand “wd.o” wordt geïdentificeerd als een LSB-verplaatsbaar. Het woord uitvoerbaar is duidelijk in zijn afwezigheid.

Objectbestanden zijn verplaatsbaar, wat betekent dat de code erin op elke locatie in het geheugen kan worden geladen. De uitvoerbare bestanden worden weergegeven als gedeelde objecten omdat ze zo door de linker van de objectbestanden zijn gemaakt dat ze deze mogelijkheid erven.

Hierdoor kan het Address Space Layout Randomization (ASMR) -systeem de uitvoerbare bestanden in het geheugen laden op adressen naar keuze. Standaard uitvoerbare bestanden hebben een laadadres gecodeerd in hun headers, die dicteren waar ze in het geheugen worden geladen.

ASMR is een beveiligingstechniek. Door uitvoerbare bestanden op voorspelbare adressen in het geheugen te laden, worden ze vatbaarder voor aanvallen. Dit komt doordat hun toegangspunten en de locaties van hun functies altijd bekend zijn bij aanvallers. Positie Independent Executables (PIE) die op een willekeurig adres zijn gepositioneerd, overwinnen deze gevoeligheid.

Als we ons programma compileren met de gcc compiler en geef het -no-pie optie, zullen we een conventioneel uitvoerbaar bestand genereren.

De -o (uitvoerbestand) optie laat ons een naam opgeven voor ons uitvoerbare bestand:

gcc -o hello -no-pie hello.c

We zullen gebruiken file op het nieuwe uitvoerbare bestand en kijk wat er is veranderd:

file hello

De grootte van het uitvoerbare bestand is hetzelfde als voorheen (17 KB):

ls -hl hello

gcc -o hallo -no-pie hello.c in een terminalvenster.

Het binaire bestand wordt nu geïdentificeerd als een standaard uitvoerbaar bestand. We doen dit alleen voor demonstratiedoeleinden. Als je op deze manier applicaties compileert, verlies je alle voordelen van de ASMR.

Waarom is een uitvoerbaar bestand zo groot?

Ons voorbeeld hello programma is 17 KB, dus het kan nauwelijks groot worden genoemd, maar alles is relatief. De broncode is 120 bytes:

cat hello.c

Wat bulkt het binaire bestand als het alleen maar één string naar het terminalvenster afdrukt? We weten dat er een ELF-header is, maar dat is slechts 64 bytes lang voor een 64-bits binair bestand. Het moet duidelijk iets anders zijn:

ls -hl hello

cat hello.c in een terminalvenster.

Laten we het binaire bestand scannen met de strings commando als een eenvoudige eerste stap om te ontdekken wat erin zit. We spuiten het erin less:

strings hello | less

strings hallo |  minder in een terminalvenster.

Er zijn veel strings in het binaire bestand, naast de “Hallo, Geek-wereld!” uit onze broncode. De meeste zijn labels voor regio’s binnen het binaire bestand en de namen en koppelingsinformatie van gedeelde objecten. Deze omvatten de bibliotheken en functies binnen die bibliotheken, waarvan het binaire bestand afhankelijk is.

De ldd commando toont ons de gedeelde objectafhankelijkheden van een binair bestand:

ldd hello

ldd hallo in een terminalvenster.

Er zijn drie items in de uitvoer en twee ervan bevatten een directorypad (de eerste niet):

  • linux-vdso.so: Virtual Dynamic Shared Object (VDSO) is een kernelmechanisme waarmee een set kernelruimte-routines toegankelijk is voor een binaire gebruikersruimte. Dit vermijdt de overhead van een contextwisseling vanuit de gebruikerskernelmodus. VDSO-gedeelde objecten voldoen aan het Executable and Linkable Format (ELF) -formaat, waardoor ze tijdens runtime dynamisch kunnen worden gekoppeld aan het binaire bestand. De VDSO wordt dynamisch toegewezen en maakt gebruik van ASMR. De VDSO-mogelijkheid wordt geleverd door de standaard GNU C-bibliotheek als de kernel het ASMR-schema ondersteunt.
  • libc.so.6: Het gedeelde object van de GNU C-bibliotheek.
  • /lib64/ld-linux-x86-64.so.2: Dit is de dynamische linker die het binaire bestand wil gebruiken. De dynamische linker ondervraagt ​​het binaire bestand om te ontdekken welke afhankelijkheden het heeft. Het lanceert die gedeelde objecten in het geheugen. Het bereidt het binaire bestand voor om te worden uitgevoerd en om de afhankelijkheden in het geheugen te kunnen vinden en openen. Vervolgens wordt het programma gestart.

De ELF-koptekst

We kunnen de ELF-header onderzoeken en decoderen met behulp van de readelf hulpprogramma en het -h (file header) optie:

readelf -h hello

readelf -h hallo in een terminalvenster.

De koptekst wordt voor ons geïnterpreteerd.

Uitvoer van readelf -h hallo in een terminalvenster.

De eerste byte van alle ELF-binaries is ingesteld op hexadecimale waarde 0x7F. De volgende drie bytes zijn ingesteld op 0x45, 0x4C en 0x46. De eerste byte is een vlag die het bestand identificeert als een ELF-binair bestand. Om dit glashelder te maken, spellen de volgende drie bytes “ELF” in ASCII:

  • Klasse: Geeft aan of het binaire bestand een 32- of 64-bits uitvoerbaar bestand is (1 = 32, 2 = 64).
  • Gegevens: Geeft de endianness in gebruik aan. Endian-codering definieert de manier waarop multibyte-nummers worden opgeslagen. Bij big-endian-codering wordt een nummer opgeslagen met de meest significante bits eerst. Bij little-endian-codering wordt het nummer opgeslagen met de minst significante bits eerst.
  • Versie: De versie van ELF (momenteel is het 1).
  • OS / ABI: Geeft het type gebruikte binaire interface van de toepassing weer. Dit definieert de interface tussen twee binaire modules, zoals een programma en een gedeelde bibliotheek.
  • ABI-versie: De versie van de ABI.
  • Type: Het type ELF-binair bestand. De gemeenschappelijke waarden zijn ET_REL voor een verplaatsbare bron (zoals een objectbestand), ET_EXEC voor een uitvoerbaar bestand dat is gecompileerd met de -no-pie vlag, en ET_DYN voor een ASMR-bewust uitvoerbaar bestand.
  • Machine: De instructieset-architectuur. Dit geeft het doelplatform aan waarvoor het binaire bestand is gemaakt.
  • Versie: Altijd op 1 zetten, voor deze versie van ELF.
  • Toegangspunt Adres: Het geheugenadres binnen het binaire bestand waarop de uitvoering begint.

De andere items zijn maten en aantallen regio’s en secties binnen het binaire bestand, zodat hun locaties kunnen worden berekend.

Een snelle blik op de eerste acht bytes van het binaire bestand met hexdump zal de handtekeningbyte en “ELF” -reeks in de eerste vier bytes van het bestand tonen. De -C (canonieke) optie geeft ons de ASCII-weergave van de bytes naast hun hexadecimale waarden, en de -n (nummer) optie laat ons specificeren hoeveel bytes we willen zien:

hexdump -C -n 8 hello

hexdump -C -n 8 hallo in een terminalvenster.

objdump en de Granular View

Als je de precieze details wilt zien, kun je de objdumpcommando met de -d (demonteer) optie:

objdump -d hello | less

objdump -d hallo |  minder in een terminalvenster.

Hiermee wordt de uitvoerbare machinecode gedemonteerd en weergegeven in hexadecimale bytes naast het equivalent van de assembleertaal. De adreslocatie van de eerste bye in elke regel wordt uiterst links weergegeven.

Dit is alleen handig als je assembleertaal kunt lezen, of als je nieuwsgierig bent naar wat er achter het gordijn gebeurt. Er is veel output, dus we hebben het doorgesluisd less.

Putput van objdump -d hallo |  minder in een terminalvenster.

Compileren en koppelen

Er zijn veel manieren om een ​​binair bestand te compileren. De ontwikkelaar kiest bijvoorbeeld of hij foutopsporingsinformatie wil opnemen. De manier waarop het binaire bestand is gekoppeld, speelt ook een rol bij de inhoud en grootte. Als de binaire verwijzingen objecten delen als externe afhankelijkheden, zal deze kleiner zijn dan een waaraan de afhankelijkheden statisch zijn gekoppeld.

De meeste ontwikkelaars kennen de opdrachten die we hier hebben behandeld al. Voor anderen bieden ze echter enkele eenvoudige manieren om rond te snuffelen en te zien wat er in de binaire zwarte doos zit.

Nieuwste artikelen

spot_img

Related Stories

Leave A Reply

Vul alstublieft uw commentaar in!
Vul hier uw naam in