Cum să aruncați o privire în interiorul fișierelor binare din linia de comandă Linux
Publicat: 2022-01-29 Ai un dosar misterios? Comanda file
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. file
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 folosind comanda file
.
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ă file
câteva dintre acestea și să vedem ce obținem:
fișierul build_instructions.odt
fișier build_instructions.pdf
fișier COBOL_Report_Apr60.djvu
Cele trei formate de fișiere sunt identificate corect. Acolo unde este posibil, file
ne oferă puțin mai multe informații. Fișierul PDF este raportat a fi în formatul 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 Files
, cât și pe linia de comandă folosind file
.
În browserul de fișiere Files
, i se oferă pictograma corectă. Pe linia de comandă, file
ignoră extensia și caută în interiorul fișierului pentru a-i determina tipul:
fișierul build_instructions.xyz
Utilizarea file
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:
fișier screenshot.png
fișier screenshot.jpg
fișier Pachelbel_Canon_In_D.mp3
Interesant, chiar și în cazul fișierelor cu text simplu, file
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ă, file
nu îl confundă cu un fișier cod sursă C autentic:
funcția fișier+anteturi.h
fișier makefile
dosar salut.c
file
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.
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:
fișier wd
dosar wd.o
dosar salut
fișier watch.exe
Luând primul pe ultimul, file
ne spune că fișierul „watch.exe” este un program de consolă executabil PE32+ pentru familia de procesoare x86 de pe Microsoft Windows. PE reprezintă formatul executabil portabil, 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 fișiere ELF (Executable and Linkable Format). 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 obiecte partajate Linux Standard Base (LSB), 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 sistemului Address Space Layout Randomization (ASMR) să încarce 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.
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. Executabilii independente de poziție (PIE) poziționați la o adresă aleatorie depășesc această susceptibilitate.
Dacă compilam programul nostru cu compilatorul gcc
și oferim 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 salut -no-pie hello.c
Vom folosi file
pe noul executabil și vom vedea ce s-a schimbat:
dosar salut
Dimensiunea executabilului este aceeași ca înainte (17 KB):
ls -hl salut
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:
pisica salut.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 salut
Să scanăm binarul cu comanda strings
ca un prim pas simplu pentru a descoperi ce se află în el. Îl vom concentra în less
:
șiruri salut | Mai puțin
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.
Comanda ldd
ne arată dependențele de obiect partajate ale unui binar:
ldd salut
Există trei intrări în ieșire și două dintre ele includ o cale de director (prima nu include):
- linux-vdso.so: Virtual Dynamic Shared Object (VDSO) este un mecanism de nucleu care permite accesul unui set de rutine din spațiul nucleului de către un binar al spațiului utilizator. Acest lucru 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 biblioteca standard GNU C dacă nucleul acceptă schema ASMR.
- libc.so.6: obiectul comun GNU C Library.
- /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.
Antetul ELF
Putem examina și decoda antetul ELF folosind utilitarul readelf
și -h
(antetul fișierului):
readelf -h salut
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” în ASCII:
- Clasa: indică dacă binarul este un executabil pe 32 sau 64 de biți (1=32, 2=64).
- Date: indică îndianitatea în uz. 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 în uz. 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
șiET_DYN
pentru un executabil compatibil ASMR. - Mașină: 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 salut
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 salut | Mai puțin
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 ești curios ce se întâmplă în spatele cortinei. Există o mulțime de rezultate, așa că am introdus-o în less
.
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.