Cum să aruncați o privire în interiorul fișierelor binare din linia de comandă Linux

Ai un dosar misterios? Comanda de fișier Linux vă va spune rapid ce tip de fișier este. Dacă este un fișier binar, totuși, puteți afla și mai multe despre el. fișierul are o mulțime de colegi de grajd care vă vor ajuta să îl analizați. Vă vom arăta cum să utilizați unele dintre aceste instrumente.

Identificarea tipurilor de fișiere

Fișierele au, de obicei, caracteristici care permit pachetelor software să identifice ce tip de fișier este, precum și ce reprezintă datele din acesta. Nu ar avea sens să încercați să deschideți un fișier PNG într-un player de muzică MP3, așa că este atât util, cât și pragmatic ca un fișier să poarte cu el o formă de ID.

Acesta ar putea fi câțiva octeți de semnătură chiar la începutul fișierului. Acest lucru permite unui fișier să fie explicit cu privire la formatul și conținutul său. Uneori, tipul de fișier este dedus dintr-un aspect distinct al organizării interne a datelor în sine, cunoscut sub numele de arhitectură de fișier.

Unele sisteme de operare, cum ar fi Windows, sunt complet ghidate de extensia unui fișier. Îl puteți numi credul sau de încredere, dar Windows presupune că orice fișier cu extensia DOCX este într-adevăr un fișier de procesare de text DOCX. Linux nu este așa, așa cum veți vedea în curând. Vrea dovezi și caută în interiorul fișierului pentru a o găsi.

Instrumentele descrise aici au fost deja instalate pe distribuțiile Manjaro 20, Fedora 21 și Ubuntu 20.04 pe care le-am folosit pentru a cerceta acest articol. Să începem investigația utilizând comanda fișierului.

Folosind fișierul Command

Avem o colecție de diferite tipuri de fișiere în directorul nostru actual. Sunt un amestec de documente, cod sursă, fișiere executabile și text.

Comanda ls ne va arăta ce se află în director, iar opțiunea -hl (dimensiuni care pot fi citite de om, listare lungă) ne va arăta dimensiunea fiecărui fișier:

ls -hl

Să încercăm să înregistrăm câteva dintre acestea și să vedem ce obținem:

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

Cele trei formate de fișiere sunt identificate corect. Acolo unde este posibil, fișierul ne oferă puțin mai multe informații. Fișierul PDF este raportat a fi în format versiunea 1.5.

Chiar dacă redenumim fișierul ODT pentru a avea o extensie cu valoarea arbitrară a XYZ, fișierul este încă identificat corect, atât în ​​browserul de fișiere Fișiere, cât și pe linia de comandă folosind fișier.

În browserul de fișiere Fișiere, i se oferă pictograma corectă. Pe linia de comandă, fișierul ignoră extensia și caută în interiorul fișierului pentru a-i determina tipul:

file build_instructions.xyz

Utilizarea fișierelor pe medii, cum ar fi fișierele de imagine și muzică, furnizează de obicei informații despre formatul, codificarea, rezoluția și așa mai departe:

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

Interesant, chiar și în cazul fișierelor cu text simplu, fișierul nu judecă fișierul după extensia sa. De exemplu, dacă aveți un fișier cu extensia „.c”, care conține text simplu standard, dar nu cod sursă, fișierul nu îl confundă cu un C autentic. fișier cod sursă:

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

fișierul identifică corect fișierul antet („.h”) ca parte a unei colecții de fișiere de cod sursă C și știe că makefile este un script.

  Cum să utilizați confortabil un computer Linux pe timp de noapte cu dimmer pentru desktop

Utilizarea fișierului cu fișiere binare

Fișierele binare sunt mai mult o „cutie neagră” decât altele. Fișierele de imagine pot fi vizualizate, fișierele de sunet pot fi redate și fișierele de documente pot fi deschise cu pachetul software corespunzător. Fișierele binare, totuși, sunt mai mult o provocare.

De exemplu, fișierele „hello” și „wd” sunt executabile binare. Sunt programe. Fișierul numit „wd.o” este un fișier obiect. Când codul sursă este compilat de un compilator, sunt create unul sau mai multe fișiere obiect. Acestea conțin codul mașinii pe care computerul îl va executa în cele din urmă atunci când se rulează programul terminat, împreună cu informații pentru linker. Linker-ul verifică fiecare fișier obiect pentru apeluri de funcție către biblioteci. Le leagă la orice biblioteci pe care le folosește programul. Rezultatul acestui proces este un fișier executabil.

Fișierul „watch.exe” este un executabil binar care a fost compilat încrucișat pentru a rula pe Windows:

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

Luând primul pe ultimul, fișierul ne spune că fișierul „watch.exe” este un program PE32+ executabil, de consolă, pentru familia de procesoare x86 de pe Microsoft Windows. PE înseamnă format portabil executabil, care are versiuni pe 32 și 64 de biți. PE32 este versiunea pe 32 de biți, iar PE32+ este versiunea pe 64 de biți.

Celelalte trei fișiere sunt toate identificate ca Format executabil și conectabil (ELF). Acesta este un standard pentru fișierele executabile și fișierele obiect partajate, cum ar fi bibliotecile. Vom arunca o privire la formatul antetului ELF în curând.

Ceea ce vă poate atrage atenția este că cele două executabile („wd” și „hello”) sunt identificate ca Linux Standard Base (LSB) obiecte partajate, iar fișierul obiect „wd.o” este identificat ca un LSB relocabil. Cuvântul executabil este evident în absența lui.

Fișierele obiect sunt relocabile, ceea ce înseamnă că codul din interiorul lor poate fi încărcat în memorie în orice locație. Executabilele sunt listate ca obiecte partajate deoarece au fost create de către linker din fișierele obiect, astfel încât să moștenească această capacitate.

Acest lucru permite Randomizare aspect spațiu de adrese (ASMR) pentru a încărca executabilele în memorie la adresele pe care le alege. Executabilele standard au o adresă de încărcare codificată în anteturile lor, care dictează locul în care sunt încărcate în memorie.

  Cum se instalează Slack pe Linux

ASMR este o tehnică de securitate. Încărcarea executabilelor în memorie la adrese previzibile le face susceptibile la atac. Acest lucru se datorează faptului că punctele lor de intrare și locațiile funcțiilor lor vor fi întotdeauna cunoscute atacatorilor. Poziția executanților independenți (PIE) poziționat la o adresă aleatorie depășește această susceptibilitate.

Dacă noi compilați programul nostru cu compilatorul gcc și oferind opțiunea -no-pie, vom genera un executabil convențional.

Opțiunea -o (fișier de ieșire) ne permite să furnizăm un nume pentru executabilul nostru:

gcc -o hello -no-pie hello.c

Vom folosi fișierul pe noul executabil și vom vedea ce s-a schimbat:

file hello

Dimensiunea executabilului este aceeași ca înainte (17 KB):

ls -hl hello

Binarul este acum identificat ca un executabil standard. Facem asta doar în scop demonstrativ. Dacă compilați aplicațiile în acest fel, veți pierde toate avantajele ASMR.

De ce este un executabil atât de mare?

Exemplul nostru de program hello are 17 KB, deci cu greu ar putea fi numit mare, dar apoi, totul este relativ. Codul sursă este de 120 de octeți:

cat hello.c

Ce creează volumul binarului dacă tot ceea ce face este să imprime un șir în fereastra terminalului? Știm că există un antet ELF, dar acesta are doar 64 de octeți pentru un binar pe 64 de biți. Evident, trebuie să fie altceva:

ls -hl hello

hai sa scanează binarul cu strings comanda ca un prim pas simplu pentru a descoperi ce se află în interiorul ei. Îl vom concentra în mai puțin:

strings hello | less

Există multe șiruri în interiorul binarului, pe lângă „Hello, Geek world!” din codul nostru sursă. Cele mai multe dintre ele sunt etichete pentru regiuni din cadrul binarului, precum și numele și informațiile de legătură ale obiectelor partajate. Acestea includ bibliotecile și funcțiile din acele biblioteci, de care depinde binarul.

The comanda ldd ne arată dependențele de obiect partajate ale unui binar:

ldd hello

Există trei intrări în ieșire și două dintre ele includ o cale de director (prima nu include):

linux-vdso.so: Obiect virtual dinamic partajat (VDSO) este un mecanism de nucleu care permite accesul unui set de rutine de spațiu nucleu de către un binar de spațiu utilizator. Acest evită supraîncărcarea unei comutări de context din modul nucleu utilizator. Obiectele partajate VDSO aderă la formatul ELF (Executable and Linkable Format), permițându-le să fie conectate dinamic la binar în timpul execuției. VDSO este alocat dinamic și profită de ASMR. Capacitatea VDSO este furnizată de standard Biblioteca GNU C dacă nucleul acceptă schema ASMR.
libc.so.6: The Biblioteca GNU C obiect comun.
/lib64/ld-linux-x86-64.so.2: Acesta este linkerul dinamic pe care binarul vrea să îl folosească. Linkerul dinamic interoghează binarul pentru a descoperi ce dependențe are. Lansează acele obiecte partajate în memorie. Pregătește binarul să ruleze și să poată găsi și accesa dependențele din memorie. Apoi, lansează programul.

  Cum să utilizați managerul de parole Buttercup pe Linux

Antetul ELF

Putem examinați și decodați antetul ELF folosind utilitarul readelf și opțiunea -h (antetul fișierului):

readelf -h hello

Antetul este interpretat pentru noi.

Primul octet al tuturor binarelor ELF este setat la valoarea hexazecimală 0x7F. Următorii trei octeți sunt setați la 0x45, 0x4C și 0x46. Primul octet este un flag care identifică fișierul ca un binar ELF. Pentru a face acest lucru clar, următorii trei octeți scriu „ELF”. ASCII:

Clasa: indică dacă binarul este un executabil pe 32 sau 64 de biți (1=32, 2=64).
Date: indică endianness in folosinta. Codificarea Endian definește modul în care sunt stocate numerele multiocteți. În codarea big-endian, un număr este stocat mai întâi cu biții cei mai semnificativi. În codificarea little-endian, numărul este stocat mai întâi cu biții cei mai puțin semnificativi.
Versiune: versiunea ELF (în prezent, este 1).
OS/ABI: Reprezintă tipul de interfață binară a aplicației in folosinta. Aceasta definește interfața dintre două module binare, cum ar fi un program și o bibliotecă partajată.
Versiunea ABI: versiunea ABI.
Tip: tipul de binar ELF. Valorile comune sunt ET_REL pentru o resursă relocabilă (cum ar fi un fișier obiect), ET_EXEC pentru un executabil compilat cu indicatorul -no-pie și ET_DYN pentru un executabil compatibil ASMR.
Masina: The arhitectura setului de instrucțiuni. Aceasta indică platforma țintă pentru care a fost creat binarul.
Versiune: Setați întotdeauna la 1, pentru această versiune de ELF.
Adresa punctului de intrare: adresa de memorie din binarul la care începe execuția.

Celelalte intrări sunt dimensiuni și numere de regiuni și secțiuni din binar, astfel încât locațiile lor pot fi calculate.

O privire rapidă la primii opt octeți ai binarului cu hexdump va afișa octetul de semnătură și șirul „ELF” în primii patru octeți ai fișierului. Opțiunea -C (canonică) ne oferă reprezentarea ASCII a octeților alături de valorile lor hexazecimale, iar opțiunea -n (număr) ne permite să specificăm câți octeți vrem să vedem:

hexdump -C -n 8 hello

objdump și vizualizarea granulară

Dacă doriți să vedeți detaliile esențiale, puteți utiliza comanda objdump cu opțiunea -d (dezasamblare):

objdump -d hello | less

Acest lucru dezasambla codul mașină executabil și îl afișează în octeți hexazecimali alături de echivalentul limbajului de asamblare. Locația adresei primului revedere din fiecare linie este afișată în extrema stângă.

Acest lucru este util doar dacă poți citi limbajul de asamblare sau dacă ești curios ce se întâmplă în spatele cortinei. Există o mulțime de rezultate, așa că l-am concentrat în mai puțin.

Compilarea și legarea

Există multe moduri de a compila un binar. De exemplu, dezvoltatorul alege dacă să includă informații de depanare. Modul în care binarul este legat, de asemenea, joacă un rol în conținutul și dimensiunea acestuia. Dacă referințele binare partajează obiecte ca dependențe externe, acesta va fi mai mic decât unul la care dependențele se leagă static.

Majoritatea dezvoltatorilor cunosc deja comenzile pe care le-am acoperit aici. Pentru alții, totuși, oferă câteva modalități ușoare de a scotoce și de a vedea ce se află în interiorul cutiei negre binare.