Sunteți pe pagina 1din 7

Capitol 07: Analiza executabilelor și

proceselor
Sumar curs anterior
spațiul virtual de adrese al procesului e compus din zone statice și dinamice, zone read-only
și read-write
în general, paginile fizice nu se alocă la comanda de alocare a utilizatorului ci la primul
access: demand paging
tabela de pagini are marcată intrarea nevalidă, urmând ca informațiile sistemului de operare
să știe că e vorba de acces nevalid sau demand paging
se fac în ordine: rezervarea unei pagini virtuale, alocarea unei pagini fizice, maparea paginii
fizice la pagina virtuală
un acces la o pagină marcată nevalidă face ca MMU să transmită o excepție de acces la
pagină numită page fault, care apelează un page fault handler înregistrat de sistemul de
operare
zonele read-only sunt partajate între procese
inițial la fork() procesul părinte și procesul copil partajează toate paginile fizice
imediat după fork(), paginile fizice sunt marcate read-only; la primul acces are loc
copy-on-write: duplicarea paginii și marcarea acesteia read-write în procesul ce a generat
acțiunea de scriere
pentru a preveni epuizarea spațiului fizic de memorie, se extinde prin folosire spațiului de
swap
swap out: evacuarea unei pagini fizice pe swap
swap in: readucerea unei pagini din swap în memoria fizică
dacă au loc operații de swap in și swap out pe aceeași pagină, sistemul devine ineficient,
apare fenomenul de thrashing
fișierele pot fi mapate în spațiul virtual de adrese al procesului pentru eficiență temporală și
spațială: este cazul fișierelor executabile și al bibliotecilor partajate

Analiza statică și dinamică


Analiza statică este analiza pe program fără ca acestea să ruleze. Poate fi analiză statică pe
cod sursă sau analiză pe binar / executabil.

Analiza dinamică are loc în momentul rulării programului în proces și are ca țintă principală
procesul și resursele folosite de acesta: memorie, registre, fișiere, apeluri de sistem, fluxul
de execuție al programului.

Analiza pe executabil și proces este esențială în cazul în care nu avem acces la codul sursă.
Chiar în prezența codului sursă, analiza pe executabil și proces este importantă.
În general avem trei obiective în folosirea analizei statice / dinamice:
● testarea / validarea / depanarea unei aplicații: confirmarea că funcționează conform
specificațiilor și dacă nu, descoperirea problemelor și rezolvarea lor
● îmbunătățirea unei aplicații: analiza consumului de resurse, zonelor critice, profilarea
aplicației pentru a realiza optimizări / îmbunătățiri (viteză, latență, dimensiune)
● înțelegerea funcționării unei aplicații, care este un mijloc pentru diferite deziderate:
○ didactic: vrem să înțelegem internele funcționării unei aplicații, funcții,
biblioteci, componente software
○ extinderea funcționalităților unei aplicații, înțelegând acum cum funcționează
○ exploatarea unei aplicații, dezvoltarea unei soluții ofensive, malware
○ protejarea unei aplicații (hardening): identificarea unor puncte slabe și
validarea mecanismelor de protecție

Pentru aceste deziderate folosim și analiză statică și analiză dinamică. Analiza statică are
avantajul privirii complete a programului dar dezavantajul lipsei de focus, nu se poate
construi un flux de execuție al programului. Analiza dinamică este precisă, urmărește un flux
de execuție cert al programului dar nu are o privire în ansamblu și nu poate acoperi toate
cazurile.

Reminder: Procesul de compilare


Așa cum ținem minte de la USO și IOCLA, calea de la un program cod sursă la un proces
este:
● compilarea programului: cod sursă -> limbaj de asamblare
● asamblarea programului: limbaj de asamblare -> modul / cod obiect
● legarea (linking): module obiect + biblioteci -> executabil
● încărcarea programului: executabil -> proces
În cazul interpretării, interpretorul este un executabil existent care este încărcat. În cadrul
acestui proces se interpretează codul sursă.

Procesul obținut are un spațiu virtual de adrese, spațiu care este populat cu informațiile din
executabil (zone statice: cod / text, date) și informații dinamice, alocate la încărcare (load
time) și rulare (run time) (heap, stivă, mapări anonime, fișiere mapate).

În general fișierul executabil este mapat (file mapping) în spațiul virtual de adrese al
procesului proaspăt creat.

Fișiere obiect și fișiere executabile


fișier obiect: relocabil (adrese nestabilite - unbound), fără main, referințe nerezolvate către
simboluri externe
fișier executabil obținut după linking: conține main, are referințele rezolvate, are adresele
stabilite (address binding)
asemănări fișier obiect/executabil: același format (ELF, PE, Mach-O, COFF): header, date,
cod, simboluri, simboluri de debug

utilitarele de investigație statică pot fi folosite și pe fișiere obiect și pe fișiere executabile;


evident, dau mai multe informații pe fișiere executabile

+ demo: rulare utilitare pe modul obiect și pe fișier executabil, văzut diferențe, se poate folosi
demo-ul static-dynamic

Legare (linking). Executabile statice și executabile


dinamice
Pentru a obține un executabil, folosim linker-ul pentru a lega unul sau mai multe module
obiect cu biblioteci. Legarea (linking) presupune:

● comasarea secțiunilor similare din modulele obiect și din biblioteci: de exemplu


secțiunile de cod (.text) sunt comasate într-o singură secțiune
● atașarea de adrese de start fiecărei secțiuni și de adrese fiecărui simbol (address
binding): fiecare simbol (adică fiecare variabilă, funcție) are acum o adresă unică în
cadrul executabilului
● rezolvarea referințelor în cadrul secțiunilor comasate sau inter-secțiuni: odată știute
adresele simbolurilor în cadrul executabilului, se pot completa instrucțiunile de
procesor (cod mașină) care foloseau aceste simboluri
● stabilirea entry point-ului programului, adică adresa primei instrucțiuni ce va fi
executată

Entry point-ul executabilului (adresa primei instrucțiuni ce va fi executate) se găsește în


header-ul executabilului. În general referă un simbol cu numele _start / start care face niște
acțiuni pregătitoare și apoi apelează funcția main.

În modul simplu, legarea este statică (static linking) iar rezultatul este un executabil static.
Un executabil static realizează complet toți pașii de mai sus și are în cadrul său toate
secțiunile de care are nevoie și toate referințele rezolvate.

O alternativă la legarea statică este legarea dinamică (dynamic linking) care are ca rezultat
un executabil dinamic. Un executabil dinamic nu include toate secțiunile și menține referințe
nerezolvate; rezolvarea acestora este amânată la momentul lansării în execuție a
executabilului, numit și încărcare (loading, load time), când se creează procesul
corespunzător.

Un executabil dinamic nu include în secțiunile sale părțile corespunzătoare din biblioteci.


Aceste biblioteci sunt biblioteci dinamice și vor fi adăugate în spațiul virtual de adrese al
procesului după încărcare (loading), fără a încărca executabilul.

Astfel, un executabil dinamic va fi semnificativ mai mic decât un executabil static. Pe lângă
dimensiunea redusă a executabilului, un alt avantaj este partajarea zonelor de cod din
cadrul bibliotecilor dinamice cu alte procese. Întrucât aceste zone nu sunt modificate, sunt
mapate în spațiul virtual al tuturor proceselor care le folosesc. De exemplu, în cazul
bibliotecii standard C, folosită de toate procesele sistemului, zona sa de cod este prezentă o
singură dată în memoria RAM și mapată în spațiul virtual de adrese al tuturor proceselor
provenite din executabile dinamice. Un proces obținut dintr-un executabil static nu poate
partaja nici o parte din zona sa cu alte procese obținute din executabile static, însemnând un
consum mai mare de memorie. Bibliotecile dinamice, partajabile, poartă în Linux denumirea
de shared objects; de aici și extensia fișierelor de tip bibliotecă dinamică .so.

Executabilele dinamice au dezavantajul unui timp mai mare de încărcare. La orice lansare în
execuție a executabilului trebuie realizată procesul de rezolvare a referințelor, numit dynamic
binding. Suplimentar, dacă un executabil dinamic este transferat pe un sistem unde nu este
prezentă o bibliotecă dinamică de care are nevoie, sau este prezentă o versiune
incompatibilă, acesta nu va rula.

Sumarizând, executabilele statice și dinamice au următoarele avantaje:

● Executabilele dinamice ocupă mai puțin spațiu pe disc, iar procesele aferente ocupă
mai puțină memorie fizică, partajând zonele read-only (.text, .rodata) din biblioteci cu
alte procese obținute din executabile dinamice care folosesc aceleași biblioteci.
● Executabilele statice sunt portabile, pot fi transferate pe un alt sistem fără a fi nevoie
de prezența unor biblioteci sau biblioteci compatibile. Lansarea lor în execuție
(loading) este mai rapidă, nefiind nevoie de rezolvarea referințelor (dynamic binding);
toate referințele au fost rezolvate la link time.

+ demo: static-dynamic

Acțiuni de analiză statică


Acțiuni specifice analizei statice pe executabile sunt:
● dezasamblarea codului
● identificarea simbolurilor (variabile, funcții): adrese, valori, dimensiuni
● identificarea șirurilor
● construirea grafului de apel al programului (call graph)
● identificarea bibliotecilor externe necesare
● identificarea simbolurilor exportate și a simbolurilor importate din surse externe

Aceste acțiuni sunt realizate cu utilitare specifice de analiză statică. În lumea Linux astfel de
utilitare sunt: objdump, readelf, nm, strings, ldd, radare2, IDA, Ghidra. radare2, IDA și Ghidra
sunt utilitare complexe care permit o gamă largă de acțiuni de analiză statică și unele de
analiză dinamică. Celelalte utilitare sunt folosite pentru cazuri de utilizare mici,
demonstrative, didactice. Pentru analiză profesionistă se folosesc radare2, IDA și Ghidra.

+ demo: no-libc
Loading. Crearea unui proces
Un proces este obținut dintr-un executabil. Loader-ul încarcă secțiunile de cod și date din
executabil și pregătește stiva pentru proces (registrul de stivă) și referă registrul de
instrucțiuni la entry point-ul procesului, indicat în header-ul executabilului.

După ce pregătește stiva, loader-ul încarcă la începutul ei variabilele de mediu și


argumentele programului. Funcția main va avea pe stivă două argumente: argc și argv, cu
argv referind adresa de pe stivă unde sunt stocate argumentele programului.

În cazul executabilelor dinamice, la loading acționează dynamic linker-ul care rezolvă


referințele către biblioteci externe și încarcă acele biblioteci în spațiul de adrese al
procesului.

Executabilele și bibliotecile sunt mapate în spațiul virtual de adrese ale procesului. Zone
read-only (non-writable) sunt partajate între procese care le folosesc. Adică procese create
din același executabil vor partaja zona de cod și rodata. Procese care folosesc aceeași
bibliotecă (de exemplu biblioteca standard C) vor partaja zona de cod și rodata a acesteia.

Astfel un proces va avea un spațiu virtual de adrese compus din zone de dimensiune
predefinită, obținute din încărcarea / maparea executabilului la load time (.text, .rodata,
.data, .bss) și zone de dimensiune dinamică obținute la run time (heap, stivă, mapări
anonime, fișiere mapate). În cazul unui executabil dinamic anumite zone vor fi obținute din
încărcarea / maparea bibliotecilor dinamice la load time; bibliotecile dinamice pot fi încărcate
la run time (run time dynamic linking / loading) prin folosirea unor apeluri specifice
(dlopen()/dlclose() pe Linux, LoadLibrary() / FreeLibrary() pe Windows). Run time dynamic
linking / loading este folosit pentru plugin-uri care pot fi încărcate și descărcate dinamic (la
run time) în / din spațiul de adrese al unui proces.

Acțiuni de analiză dinamică


Acțiunile specifice analizei statice se pot realiza și în analiză dinamică. Pe lângă aceasta, se
poate urmări fluxul de execuție al unui program, însemnând că putem:
● realiza breakpoint-uri și stepping
● inspecta memoria și registrele programului
● inspecta spațiul de memorie folosit, resursele folosite
● urmări fluxul de execuție al programului: tracing
● realiza instrumentarea codului care să permită urmărire sau analiză: model aplicat de
Valgrind, AddressSanitizer, profilere
Utilitarul esențial de analiză dinamică este depanatorul / debuggerul. Orice platformă vine cu
un debugger care este folosit la analiză dinamică: GDB, LLDB, WinDbg.

Soluții care fac instrumentare și care au rol de analiză mai detaliată sunt: Intel Pin, Valgrind,
AddressSanitizer.
Profilerele precum perf, gcov, Intel VTune folosesc instrumentare pentru analiză dinamică a
consumului de resurse, timp.

Utilitare care realizeaza tracing precum strace, ltrace, ftrace (pe Linux) sau Dtrace (pe BSD,
MacOS) prezintă fluxul de apel al unui program / proces.

+ demo: hidden

Sumar
analiza statică se realizează pe fișiere cod sursă sau pe fișiere binare obiect / executabil
chiar în prezența codului sursă, analiza statică este utilă pentru verificare funcționalităților
analiza dinamică are rol pe un program executabil în execuție, pe un proces
folosim analiză statică și analiză dinamică având ca obiective: testare / verificare / depanare,
îmbunătățirea aplicației, înțelegerea funcționării (reversing) - un mijloc pentru obiective
didactice, de exploiting, de hardening, de extindere
fluxul de la cod sursă la proces este:
cod sursă -> compilator (compile time) -> limbaj de asamblare -> asamblor -> cod mașină
(modul obiect) -> linker (link time) -> executabil -> loader (load time) -> proces -> acțiuni
dinamice (run time)
fișierele obiect sunt legate împreună cu fișiere bibliotecă în fișiere executabile, acțiune făcută
de linker
fișierele executabile au entry point, adrese asociate simbolurilor, simboluri rezolvate
linker-ul realizează pașii:
● agregă secțiunile din module obiect
● atașează adrese simbolurilor (address binding)
● rezolvă referințele de simboluri (symbol resolution)
● stabilește entry point-ul
executabilul obținut poate fi static, conținând toate informațiile și neavând nici un simbol
nerezolvat sau poate fi dinamic, mai mic ca dimensiuni, cu rezolvarea simbolurilor din
bibliotecile amânată pentru load time
analiza statică cuprinde: dezasamblare, listare simboluri, identificare entry point, urmărire
call graph, extragere șiruri, extragere simboluri exportate și importate, biblioteci de care
depinde (pentru executabile dinamice)
crearea unui proces din executabil este făcută de loader (load time)
loaderul încarcă secțiunile de cod, date din executabil în memorie, pregătește stiva,
argumentele, variabilele de mediu și dă drumul primei instrucțiuni (de la entry point); în cazul
executabilelor dinamice, loader încarcă și bibliotecile dinamice corespunzătoare
încărcarea executabilelor și bibliotecilor este de fapt maparea acestora în spațiul virtual de
adrese al procesului
zonele nemodificabile din executabile și biblioteci partajate (.text, .rodata) sunt partajate cu
alte procese care le folosesc
analiza dinamică cuprinde acțiunile analizei statice și: suspendarea execuției programului și
urmărirea pas cu pas a execuției, investigarea și modificarea memoriei și registrelor cu
posibilitatea modificării fluxului de execuție, inspectarea resurselor folosite (fișiere, sockeți,
IPC), tracing (apeluri de bibliotecă, apeluri de sistem), instrumentare pentru analiză detaliată

S-ar putea să vă placă și