This shows you the differences between two versions of the page.
so:curs:exec-process [2020/03/29 18:10] razvan.deaconescu created |
so:curs:exec-process [2021/04/14 06:24] (current) razvan.deaconescu [Apelarea unor funcționalități ascunse] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Capitol 07 - Analiza executabilelor și proceselor ====== | ====== Capitol 07 - Analiza executabilelor și proceselor ====== | ||
- | * [[TODO|Notițe de curs]] | + | * [[https://docs.google.com/document/d/154mUxvHLsqigBTzR0R4SfJiiKNCw_0AQaB1F_lgNWYY/edit?usp=sharing|Notițe de curs]] |
+ | |||
+ | * Suport curs | ||
+ | * Reversing: Secrets of Reverse Engineering | ||
+ | * Chapter 2: Low-Level Software | ||
+ | |||
+ | * Resurse extra | ||
+ | * [[https://nora.codes/tutorial/an-intro-to-x86_64-reverse-engineering/|]] | ||
+ | * [[https://nora.codes/tutorial/additional-exercises-in-reverse-engineering/|Additional Exercises in Reverse Engineering]] | ||
+ | * [[https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html|A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux]] | ||
+ | * [[https://beginners.re/|Reverse Engineering for Beginners]] | ||
+ | * [[https://github.com/onethawt/reverseengineering-reading-list|Reverse Engineering Reading List]] | ||
+ | |||
+ | * Filmări | ||
+ | * 3CA: https://web.microsoftstream.com/video/f0316c59-353b-4675-a5f4-d268b9d512ac | ||
+ | * 3CC curs 10, partea 1: https://web.microsoftstream.com/video/5ffc701c-343d-4aad-8528-beb1bb5d11f0 | ||
+ | * 3CC curs 10, partea a 2-a: https://web.microsoftstream.com/video/9152c7c0-0d89-4a5a-a4c6-cab016038242 | ||
+ | |||
+ | * Curs CA [[https://www.slideshare.net/alexandruradovici/sisteme-de-operare-analiza-executabilelor-i-proceselor|slideshare]] | ||
===== Demo-uri ===== | ===== Demo-uri ===== | ||
Line 15: | Line 33: | ||
Acum putem parcurge secțiunile cu demo-uri de mai jos. | Acum putem parcurge secțiunile cu demo-uri de mai jos. | ||
- | ==== Executabile fără libc ==== | + | ==== Secțiuni și adrese în cadrul unui fișier executabil ==== |
- | + | ||
- | În mod obișt | + | |
- | Dorim să investigăm modul în care se alocă variabilele într-un executabil. Pentru aceasta accesăm subdirectorul ''exec-vars/''; urmărim conținutul fișierului ''exec-vars.c''. În acest fișier definim/alocăm variabile în diverse forme: dinamic, static, global. Vrem să vedem cum sunt acestea alocate în executabil și în proces. | + | Dorim să urmărim adresele secțiunilor și simbolurilor în cadrul unui fișier executabil de tip ELF (//Executable and Linking Format//). Pentru aceasta accesăm subdirectorul ''exec-addr/''; urmărim conținutul fișierului ''exec-addr.c''. În acest fișier definim variabile globale și afișăm adresele acestor variabile și a funcțiilor din modul. Vom observa că adresele variabilelor globale și a funcțiior sunt cunoscute de la link-time, în momentul link-editării și a obținerii executabilului. |
Compilăm programul folosind ''make''. | Compilăm programul folosind ''make''. | ||
- | Investigăm zonele de memorie din executabil în care sunt salvate variabilele, folosind comanda<code bash> | + | Pentru început investigăm simbolurile din executabil. Ne interesează variabilele globale și funcțiile așa că vom rula comanda de afișare a simbolurilor din care vom extrage liniile de interes:<code bash> |
- | objdump -t addr-space | grep var | + | user@host:$ objdump --syms exec-addr | grep '\(exec_\| main\|simple_func\)' |
+ | 0000000000600da0 l O .data 0000000000000004 exec_static_int_global | ||
+ | 000000000040076b l F .text 000000000000001a simple_func | ||
+ | 0000000000600dc4 g O .bss 0000000000000004 exec_int_global_noinit | ||
+ | 0000000000600da4 g O .data 0000000000000004 exec_int_global | ||
+ | 00000000004008b8 g O .rodata 0000000000000006 exec_array_ro | ||
+ | 0000000000400785 g F .text 000000000000009c main | ||
</code> | </code> | ||
- | De ce apar doar unele variabile? | + | Prin rularea comenzii objdump de mai sus afișăm informații despre simboluri, în format pe coloane, astfel: |
+ | * În prima coloană sunt adresele simbolurilor. Aceste adrese se vor regăsi întocmai în proces (vom vedea în continuare). Adresele pot să difere în cazul obținerii executabilului pe alt sistem. | ||
+ | * A doua coloană este tipul simbolului. Simbolurile statice sunt marcate cu ''l'' (//local//) pentru că vor fi locale modulului. Celelalte sunt marcate cu ''g'' (//global//) și vor putea fi exportate în alte module. | ||
+ | * A patra coloană este secțiunea din executabil unde este alocat simbolul. Simbolul este alocat în modul încă de la compile-time. Secțiunile sunt după cum urmează: | ||
+ | * ''.data'': variabile globale inițializate | ||
+ | * ''.bss'': variabile globale neinițializate | ||
+ | * ''.rodata'': variabile globale de tip read-only | ||
+ | * ''.text'': zonă de cod/instrucțiuni (pentru funcții) | ||
+ | * A cincea coloană este spațiul ocupat de simbol. Variabilele întregi ocupă ''sizeof(int) = 4'' octeți, șirul de caractere ocupă 6 octeți (incluzând NUL-terminatorul) iar funcțiile ocupă spațiul dat de codul acestora. | ||
- | <spoiler Răspuns> | + | Observăm că zona de date read-only (''.rodata'') este apropiată de zona de cod (''.text'') ambele fiind zone care nu pot fi scrise. |
- | Pentru variabilele alocate dinamic (pe stivă, heap) se alocă la runtime, adică în momentul în care procesul rulează. Executabilul nu are legătură cu datele alocate la runtime. | + | |
- | </spoiler> | + | |
- | Ce semnfică ''l'' și ''g'' în output-ul obținut? | + | Ca să verificăm faptul că adresele precizate în executabil se vor regăsi și în momentul rulării procesului, la run-time, rulăm executabilul:<code bash> |
+ | $ ./exec-addr | ||
+ | Inside simple_func | ||
- | <spoiler Răspuns> | + | Run-time addresses are: |
- | Arată că variabilele respective sunt simboluri globale (''g'' - la nivelul întregului modul) sau locale (''l'' - locale unei funcții). | + | &exec_static_int_global: 0x600da0 |
- | </spoiler> | + | &exec_int_global: 0x600da4 |
+ | &exec_int_global_noinit: 0x600dc4 | ||
+ | &exec_array_ro: 0x4008b8 | ||
+ | &simple_func: 0x40076b | ||
+ | &main: 0x400785 | ||
- | Pentru a afișa conținutul zonei ''.rodata'' folosind comanda<code bash> | + | Run `pmap -p $(pidof exec-addr)' to show process map. |
- | readelf -x .rodata addr-space | + | |
+ | Press ENTER to continue ... | ||
</code> | </code> | ||
- | Secțiunea ''.rodata'' conține variabile read-only, în cazul de față literalul ''rulz''. Literalii și constantele se stocează în secțiunea ''.rodata'' a unui executabil. | ||
- | ==== Investigarea mapării folosind pmap ==== | + | Observăm din rezultatul rulării că adresele de la run-time sunt aceleași cu cele din executabil. |
- | Vrem să vizualizăm spațiul virtual de adrese al unui proces. Pentru aceasta, accesăm subdirectorul ''pmap/''; urmărim conținutul fișierului ''pmap.c''; facem mapări (folosind apelul ''mmap'') folosind diverse flag-uri. | + | După cum ni se indică la rulare, vom rula ''pmap'' pentru a consulta spațiul virtual de adresă al procesului:<code bash> |
+ | $ pmap -p $(pidof exec-addr) | ||
+ | 13545: ./exec-addr | ||
+ | 0000000000400000 4K r-x-- /home/razvan/school/2011-2012/so/git-repos/cursuri.git/curs-07-demo/exec-addr/exec-addr | ||
+ | 0000000000600000 4K rw--- /home/razvan/school/2011-2012/so/git-repos/cursuri.git/curs-07-demo/exec-addr/exec-addr | ||
+ | 00007fd884842000 1664K r-x-- /lib/x86_64-linux-gnu/libc-2.18.so | ||
+ | 00007fd8849e2000 2044K ----- /lib/x86_64-linux-gnu/libc-2.18.so | ||
+ | 00007fd884be1000 16K r---- /lib/x86_64-linux-gnu/libc-2.18.so | ||
+ | 00007fd884be5000 8K rw--- /lib/x86_64-linux-gnu/libc-2.18.so | ||
+ | 00007fd884be7000 16K rw--- [ anon ] | ||
+ | 00007fd884beb000 128K r-x-- /lib/x86_64-linux-gnu/ld-2.18.so | ||
+ | 00007fd884dcf000 12K rw--- [ anon ] | ||
+ | 00007fd884e06000 16K rw--- [ anon ] | ||
+ | 00007fd884e0a000 4K r---- /lib/x86_64-linux-gnu/ld-2.18.so | ||
+ | 00007fd884e0b000 4K rw--- /lib/x86_64-linux-gnu/ld-2.18.so | ||
+ | 00007fd884e0c000 4K rw--- [ anon ] | ||
+ | 00007fffb51d1000 132K rw--- [ stack ] | ||
+ | 00007fffb51fe000 8K r-x-- [ anon ] | ||
+ | ffffffffff600000 4K r-x-- [ anon ] | ||
+ | total 4068K | ||
+ | </code> | ||
- | Compilăm programul folosind ''make'' și apoi îl rulăm:<code bash> | + | Legat de partea de executabil, observăm că avem două pagini (''4K'') mapate din executabilul ''exec-addr''. Una este readable/executable (''r-x'') și începe de la adresa ''0x400000'', iar alta este readable/writable (''rw-'') și începe de la adresa ''0x600000''. În prima pagină sunt mapate secțiunile ''.text'' și ''.rodata'', iar în cealaltă sunt mapate secțiunile ''.data'' și ''.bss''; observăm acest lucru pe baza adreselor. |
- | ./pmap | + | |
+ | ==== Executabile statice și 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 bilioteci: 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: o dată știute adresele simobolurilor în cadrul executabilului, se pot completa instrucțiunile de procesor (cod mașină) care foloseau aceste simboluri | ||
+ | |||
+ | Î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 seț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 foloesc. 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 și read-execute (''.text'', ''.rodata'') din bibilioteci cu alte procese obținute din executabile dinamice care folosesc aceleași biblioteci. | ||
+ | * Executabilele statice sunt portabile, pot fi transferate pe un alt sistem (cu aceeași arhitectură a sistemului pentru care a fost compilat binarul) 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. | ||
+ | |||
+ | În subdirectorul ''static-dynamic/'' se găsește fișierul sursă ''hello.c'' și un fișier ''Makefile'' pe care îl folosim pentru a compila un executabil static (''hello-static'') și unul dinamic (''hello-dynamic''): | ||
+ | <code> | ||
+ | $ ls | ||
+ | hello.c Makefile | ||
+ | |||
+ | $ make | ||
+ | cc -Wall -fno-PIC -c -o hello.o hello.c | ||
+ | cc -no-pie -static -o hello-static hello.o | ||
+ | cc -no-pie -o hello-dynamic hello.o | ||
+ | |||
+ | $ ls -F | ||
+ | hello.c hello-dynamic* hello.o hello-static* Makefile | ||
</code> | </code> | ||
- | Într-o altă consolă urmărim schimbările din spațiul virtual de adrese al procesului creat folosind comanda<code bash> | + | Putem observa că executabilul static are dimensiunea mai mare decât cel dinamic (de circa 100 de ori mai mare) și că are mai multe simboluri: |
- | watch -d pmap $(pidof pmap) | + | <code> |
- | </code> Comanda de mai sus urmărește spațiul virtual de adrese. Ca să generăm schimbări în spațiul virtual de adrese, apăsăm ''ENTER'' în consola în care rulează programul pentru a continua pașii din cod. | + | $ ls -lh hello-* |
+ | -rwxr-xr-x 1 student student 8.4K Mar 29 21:57 hello-dynamic | ||
+ | -rwxr-xr-x 1 student student 826K Mar 29 21:57 hello-static | ||
- | Urmărim modificările care apar în urma diferitelor tipuri de mapare din cod. Observăm că se mapează câte o singură pagină; la fiecare operație de mapare spațiul total crește cu 4K, iar la fiecare operație de demapare spațiul total scade cu 4K. Observăm că în cazul mapării partajate permisiunile sunt ''rw-s''; ''s'' înseamnă shared. Tot în cazul memoriei partajate apare mapat dispozitivul ''/dev/zero'', unul dintre modurile uzuale de a face mapare partajată. | + | $ nm hello-static | wc -l |
+ | 1664 | ||
- | De ce unele biblioteci sunt mapate cu permisiuni de scriere? | + | $ nm hello-dynamic | wc -l |
+ | 38 | ||
+ | </code> | ||
- | <spoiler Răspuns> | + | Așa cum am discutat, un executabil dinamic are referințe nerezolvate către biblioteci (în cazul de față biblioteca standard C), referințe ce vor fi rezolvate la //load time//. Executabilul static nu are referințe nerezolvate: |
- | Bibliotecile au mai multe secțiuni, similar unui executabil, mapate cu permisiunile corespunzătoare. Secțiunea de cod/text este mapată cu permisiuni de citire și execuție (''r-x''), cea de date este mapată cu permisiuni de citire și scriere (''rw-'') iar cea de date read-only este mapată cu permisiuni doar de citire (''r--''). | + | <code> |
- | </spoiler> | + | $ nm hello-dynamic | grep ' U ' |
+ | U fflush@@GLIBC_2.2.5 | ||
+ | U fgets@@GLIBC_2.2.5 | ||
+ | U __libc_start_main@@GLIBC_2.2.5 | ||
+ | U printf@@GLIBC_2.2.5 | ||
+ | U puts@@GLIBC_2.2.5 | ||
+ | U __stack_chk_fail@@GLIBC_2.4 | ||
- | ==== Alocarea de memorie virtuală ==== | + | $ nm hello-static | grep ' U ' |
+ | $ | ||
+ | </code> | ||
- | Vrem să urmărim modul în care se alocă memorie virtuală în spațiul virtual de adrese al unui proces. Pentru aceasta, accesăm subdirectorul ''allocation/''; urmărim conținutul fișierului ''allocation.c''; în cadrul fișierului se fac alocări de memorie virtuală folosind pe rând apelul ''malloc'' și apelul ''mmap''. | + | Utilitarul ''ldd'' afișează bibliotecile dinamice necesare pentru executabilele dinamice, iar pentru un executabil static nu afișează nimic: |
+ | <code> | ||
+ | $ ldd hello-dynamic | ||
+ | linux-vdso.so.1 (0x00007ffcda18d000) | ||
+ | libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6dbc088000) | ||
+ | /lib64/ld-linux-x86-64.so.2 (0x00007f6dbc479000) | ||
- | Compilăm fișierul folosind comanda ''make''. | + | $ ldd hello-static |
+ | not a dynamic executable | ||
+ | </code> | ||
- | Deschidem o consolă nouă și rulăm în cele două console astfel: | + | La rularea oricărui executabil (//loading//), acesta așteaptă să apăsăm ''Enter'' ca să continue. Acest lucru ne permite să investigăm spațiul de adresă pentru procesul proaspăt pornit folosind utilitarul ''pmap'' ca mai jos: |
- | * Într-o consolă rulăm executabilul aferent:<code> | + | <code> |
- | ./allocation | + | $ pmap $(pidof hello-dynamic) |
+ | 24227: ./hello-dynamic | ||
+ | 0000000000400000 4K r-x-- hello-dynamic | ||
+ | 0000000000600000 4K r---- hello-dynamic | ||
+ | 0000000000601000 4K rw--- hello-dynamic | ||
+ | 0000000001986000 132K rw--- [ anon ] | ||
+ | 00007f556ba27000 1948K r-x-- libc-2.27.so | ||
+ | 00007f556bc0e000 2048K ----- libc-2.27.so | ||
+ | 00007f556be0e000 16K r---- libc-2.27.so | ||
+ | 00007f556be12000 8K rw--- libc-2.27.so | ||
+ | 00007f556be14000 16K rw--- [ anon ] | ||
+ | 00007f556be18000 156K r-x-- ld-2.27.so | ||
+ | 00007f556c01d000 8K rw--- [ anon ] | ||
+ | 00007f556c03f000 4K r---- ld-2.27.so | ||
+ | 00007f556c040000 4K rw--- ld-2.27.so | ||
+ | 00007f556c041000 4K rw--- [ anon ] | ||
+ | 00007ffeceb5d000 132K rw--- [ stack ] | ||
+ | 00007ffeceba4000 12K r---- [ anon ] | ||
+ | 00007ffeceba7000 4K r-x-- [ anon ] | ||
+ | ffffffffff600000 4K --x-- [ anon ] | ||
+ | total 4508K | ||
+ | |||
+ | $ pmap $(pidof hello-static) | ||
+ | 24245: ./hello-static | ||
+ | 0000000000400000 728K r-x-- hello-static | ||
+ | 00000000006b6000 24K rw--- hello-static | ||
+ | 00000000006bc000 4K rw--- [ anon ] | ||
+ | 000000000091c000 140K rw--- [ anon ] | ||
+ | 00007ffdb1a33000 132K rw--- [ stack ] | ||
+ | 00007ffdb1b80000 12K r---- [ anon ] | ||
+ | 00007ffdb1b83000 4K r-x-- [ anon ] | ||
+ | ffffffffff600000 4K --x-- [ anon ] | ||
+ | total 1048K | ||
</code> | </code> | ||
- | * Într-o altă consolă vizualizăm dimensiunea spațiului fizic ocupat și a a spatiului virtual ocupat, folosind comanda<code bash> | + | |
- | watch -n 1 ps -o pid,rss,vsz,cmd -p $(pidof allocation) | + | Observăm că în cazul executabilului dinamic, spațiul virtual de adrese cuprinde bibliotecile dinamice partajate (biblioteca standard C ''libc-2.27.so'' și dynamic linker/loader-ul ''ld-2.27.so''). Spațiul virtual de adrese al executabilului static este mai redus, cuprinzând doar zonele specifice executabilului și zonele dinamice ale procesului (stivă, heap, mapări anonime), fără biblioteci. |
+ | |||
+ | ==== Executabile fără libc ==== | ||
+ | |||
+ | <note important> | ||
+ | Pentru acest demo trebuie să aveți instalat pe sistem asamblorul NASM. Pe un sistem Debian/Ubuntu îl puteți instala folosind comanda: | ||
+ | <code> | ||
+ | sudo apt install nasm | ||
</code> | </code> | ||
+ | </note> | ||
+ | |||
+ | În mod obișnuit un executabil este obținut prin legarea unui sau a mai multor module obiect la biblioteca standard C (''libc'') și, la nevoie, alte biblioteci. Așa cum am prezentat mai sus, în cazul legării statice (//static linking//) linker-ul adaugă în executabil toate componentele necesare (cod, date) din cadrul bibliotecii statice; în cazul legării dinamice (//dynamic linking//) linker-ul adaugă referințe la simbolurile din bibliotecile dinamice, iar rezolvarea acestor simboluri și adăugarea componentelor se întămplă la încărcare (//load time//). | ||
+ | |||
+ | Putem crea programe și folosi linker-ul să nu folosească deloc biblioteca standard C. Acest lucru este util pentru medii de lucru specializate (numite //free standing environments//) în care nu avem acces la o bibliotecă standard C sau nu o putem folosi. Astfel de programe vor implementa singure funcționalități similare celor oferite de biblioteca standard C sau vor realiza direct apeluri de sistem. | ||
+ | |||
+ | În directorul ''no-libc/'' avem un exemplu de astfel de program, care folosește direct apeluri de sistem pentru a afișa un mesaj la ieșirea standard. Programul este scris în limbaj de asamblare pentru arhictectura x86_64 în sintaxa AT&T folosind asamblorul GNU (''gas'' parte din GCC) în fișierul ''hello.s'' și în sintaxa Intel folosind asamblorul NASM în fișierul ''hello.asm''. | ||
+ | |||
+ | În ambele fișiere (''hello.s'' și ''hello.asm'') invocăm apelul de sistem ''write(1, "Hello, world!\n", 14);''. Pentru invocarea acestui apel de sistem, urmărim [[https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/|convenția de apel de sistem specifică arhitecturii x86_64 pe Linux]]: | ||
+ | * registrul ''rax'' reține numărul apelului de sistem (''1'' pentru apelul de sistem ''sys_write'') | ||
+ | * registrul ''rdi'' reține primul argument (''1'' pentru descriptorul de fișier corespunzător ieșirii standard - //standard output//) | ||
+ | * registrul ''rsi'' reține al doilea argument (adresa mesajului de afișat, adică adresa șirului ''Hello, world!\n'') | ||
+ | * registrul ''rdx'' reține al treilea argument (''14'' pentru lungimea mesajului de afișat) | ||
+ | |||
+ | Pentru încheierea execuției, invocăm apelul de sistem ''exit(0)''. | ||
+ | |||
+ | Pentru a obține executabilele este nevoie de un pas de asamblare, care obține fișierul obiect corespunzător și un pas de legare care obține fișierul executabil. Realizăm acești pași prin intermediul comenzii ''make'': | ||
+ | |||
+ | <code> | ||
+ | $ ls | ||
+ | hello.asm hello.s Makefile | ||
- | Observați cum crește dimensiunea spațiului fizic și a spatiului virtual în cazul folosirii ''malloc'' și **doar** a spațiului virtual folosind ''mmap''. | + | $ make |
+ | nasm -f elf64 -o hello-nasm.o hello.asm | ||
+ | cc -nostdlib -no-pie -Wl,--entry=main -Wl,--build-id=none hello-nasm.o -o hello-nasm | ||
+ | gcc -c -o hello-gas.o hello.s | ||
+ | cc -nostdlib -no-pie -Wl,--entry=main -Wl,--build-id=none hello-gas.o -o hello-gas | ||
- | Pentru a explica acest comportament, rulăm executabilul prin ''strace'':<code> | + | $ ls |
- | strace ./allocation | + | hello.asm hello-gas hello-gas.o hello-nasm hello-nasm.o hello.s Makefile |
</code> | </code> | ||
- | Observăm că în cazul funcției de bibliotecă ''malloc'' se realizează apelul de sistem ''brk'', în timp ce în cazul funcției de bibliotecă ''mmap'' se realizează apelul de sistem ''mmap''. Apelul de sistem ''mmap'' alocă **doar** memorie virtuală. | + | În urma rulării comenzii am obținut fișierele obiect ''hello-nasm.o'' și ''hello-gas.o'' și fișierele executabile ''hello-nasm'' și ''hello-gas''. |
- | Observăm de asemenea, că se realizează un număr redus de apeluri de sistem ''brk'' raportat la cele ''1024'' de apeluri de bibliotecă ''malloc''. Un apel ''brk'' alocă un pool mai mare de memorie care va fi apoi folosit la apeluri viitoare ''malloc''; realizează o prealocare. | + | Rulăm cele două executabile care vor afișa mesajul ''Hello, world!'' la ieșirea standard: |
- | Pentru a vedea comportamentului funcției de bibliotecă ''malloc'', actualizăm codul astfel încât ''malloc'' să aloce, la fel ca ''mmap'' calupuri de ''1MB'' de memorie. Adică bucla ''for'' aferentă să arate așa:<code c> | + | <code> |
- | for (cnt = 0; cnt < NUM_ROUNDS; cnt++) { | + | $ ./hello-nasm |
- | puts("Using malloc to allocate 1024 sets of 1024 bytes."); | + | Hello, world! |
- | p = malloc(1024*1024); | + | $ ./hello-gas |
- | DIE(p == NULL, "malloc"); | + | Hello, world! |
- | sleep(2); | + | |
- | } | + | |
</code> | </code> | ||
- | Compilăm noua sursă folosind comanda ''make''. La fel ca mai devreme rulăm executabilul într-o consolă și comanda de vizualizare în altă consolă. Observăm că acum atât funcția de bibliotecă ''malloc'', cât și funcția de bibliotecă ''mmap'' alocă **doar** memorie virtuală. | + | Putem observa diferențe între aceste executabile (obținute fără suport de bibliotecă standard C) și cele de la demo-ul anterior, obținute cu suport de bibliotecă standard C. |
- | Folosim în continuare ''strace'' pentru a investiga:<code bash> | + | Fișierele sunt mai mici, nu conțin elemente de setup și cleanup din biblioteca standard C, au doar miminul necesar pentru a funcționa: au minimul de simboluri, minimul de secțiuni, minimul de cod, așa cum putem observa rulând comenzile de mai jos: |
- | strace ./allocation | + | |
+ | <code> | ||
+ | $ ls -l hello-gas hello-nasm | ||
+ | $ nm hello-gas | ||
+ | $ nm hello-nasm | ||
+ | $ objdump -d hello-gas | ||
+ | $ objdump -d -M intel hello-nasm | ||
+ | $ readelf -S hello-gas | ||
+ | $ readelf -S hello-nasm | ||
</code> | </code> | ||
- | Observăm că acum și funcția de bibliotecă ''malloc'' folosește în spate tot apelul de sistem ''mmap''. Acesta alocă doar memorie virtuală de unde și comportamentul. De la o valoare dată, alocarea cu ''malloc'' folosește apelul de sistem ''mmap''. | + | Putem obține aceste executabile fără să apelăm comanda ''gcc'' cu un număr complicat de parametri. Putem invoca direct linker-ul folosind comenzile: |
+ | <code> | ||
+ | ld -o hello-gas --entry=main hello-gas.o | ||
+ | ld -o hello-nasm --entry=main hello-nasm.o | ||
+ | </code> | ||
- | <spoiler Despre apelul malloc> | + | Nu în ultimul rând, putem reduce dimensiunea executabilelor eliminând tabela de simboluri cu ajutorul comenzii ''strip'': |
- | Valoarea de la care funcția de bibliotecă ''malloc'' folosește ''mmap'' este definită de ''MMAP_THRESHOLD'', în mod implicit configurat la ''128KB''. Detalii se găsesc în [[http://man7.org/linux/man-pages/man3/malloc.3.html#NOTES|pagina de manual a malloc]]:<code> | + | <code> |
- | Normally, malloc() allocates memory from the heap, and adjusts the | + | $ strip hello-nasm |
- | size of the heap as required, using sbrk(2). When allocating blocks | + | $ strip hello-gas |
- | of memory larger than MMAP_THRESHOLD bytes, the glibc malloc() | + | |
- | implementation allocates the memory as a private anonymous mapping | + | |
- | using mmap(2). MMAP_THRESHOLD is 128 kB by default, but is | + | |
- | adjustable using mallopt(3). Allocations performed using mmap(2) are | + | |
- | unaffected by the RLIMIT_DATA resource limit (see getrlimit(2)). | + | |
</code> | </code> | ||
- | </spoiler> | ||
- | ==== Paginare la cerere (demand paging) ==== | + | <note tip> |
+ | Vedeți [[https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html|aici]] un articol despre cum puteți obține un executabil cât mai mic pe un sistem Linux. | ||
+ | </note> | ||
- | Vrem să urmărim modul în care se alocă pagini de memorie fizică la cerere, proces care se numește ''(on) demand paging''. Pentru aceasta, accesăm subdirectorul ''demand-paging/''; urmărim conținutul fișierului ''demand-paging.c''; în cadrul fișierului alocăm memorie virtuală folosind ''mmap'' și apoi accesăm primul octet al fiecărei pagini alocate. | + | ==== Apelarea unor funcționalități ascunse ==== |
- | Compilăm codul sursă folosind comanda ''make''. | + | În mod obișnuit, un program execută fluxul de apel dat de codul său și de intrarea venită de la utilizator sau alte procese sau alte forme de I/O. În anumite situații putem dori să declanșăm mai repede un apel al unei funcționalități prezente în executabil, dar care nu se găsește pe fluxul de apel curent. |
- | Avem nevoie de două console: | + | La nivel de provocare (//challenge//) în lumea competițiilor de tip Capture the Flag (CTF), sunt situații în care un program conține funcționalități ascunse; adică sunt funcții și variabile prezente în program dar care nu sunt apelate sau folosite explicit. Un astfel de exemplu este în subdirectorul ''hidden/''. |
- | * Într-o consolă rulăm executabilul aferent:<code> | + | |
- | ./demand-paging | + | În subdirectorul ''hidden/'' se găsește executabilul ''hidden'' obținut din compilarea și legarea fișierelor ''hidden.c'' și ''reader.c''. Obiectivul nostru este să afișăm mesajul ''success'' cu ajutorul acestui program, în orice mod posibil. |
+ | |||
+ | Pentru început, observăm că acest program se apelează funcția ''hidden_function()''. Numai că apelul se realizează astfel încât depinde de valori aleatoare. În plus valorile variabilelor ''test'' și ''success_message'' nu ajută la obiectivul nostru: afișarea mesajului ''success''. Identificăm următoarele limitări ale programului, limitări peste care trebuie să trecem: | ||
+ | * variabila ''test'' are o valoare predefinită care nu este modificată în program | ||
+ | * variabila ''success_message'' are o valoare predefinită care nu este modificată în program | ||
+ | * al doilea apel al funcției ''hidden_function()'' depinde de două valori aleatoare, generate la rulare (//run time//) | ||
+ | |||
+ | === Primul apel de funcție: analiză statică === | ||
+ | |||
+ | Pentru început ne propunem să realizăm primul apel al funcției ''hidden_function()'', adică apelul ''hidden_function(100)'' apel ce nu depinde de valori aleatoare generate la rulare. Putem să realizăm acest apel folosind doar analiză statică (pe executabil) și modificând executabilul. Adică urmărim: | ||
+ | * Să suprascriem valoarea variabilei ''test'' cu valoarea ''100'', argumentul apelului ''hidden_function()''. În felul acesta vom trece de condiția ''if'' din cadrul funcției ''hidden_function()'' (linia ''11'' din fișierul ''hidden.c''). | ||
+ | * Să suprascriem valoarea variabilei ''success_message'' cu valoarea ''success\0''. în felul acesta apelul funcției ''puts()'' din cadrul funcției ''hidden_function()'' (linia ''12'' din fișierul ''hidden.c''). | ||
+ | |||
+ | Pentru a realiza aceste suprascrieri trebuie să știm la ce offset în cadrul fișierului executabil se găsesc cele două valori. Pentru a determina acest offset, vom face pașii: | ||
+ | - Aflăm ce adresă le este asociată (//address binding//) celor două variabile (''test'' și ''success_message''). Fie aceste adrese ''test_address'' și ''success_message_address''. | ||
+ | - Aflăm ce adresă are asociată zona ''.data'' în care se găsesc cele două variabile. Fie această adresă ''data_address''. | ||
+ | - Aflăm la ce offset în cadrul fișierului executabil se găsește zona ''.data''. Fie acest offset ''data_offset''. | ||
+ | - Calculăm offset-ul celor două variabile în cadrul fișierului: ''test_offset = test_address - data_address + data_offset'', ''success_message_offset = success_message_address - data_address + data_offset''. | ||
+ | |||
+ | Pasul ''1'' îl putem realiza folosind utilitarul ''nm'' sau utiltarul ''readelf'': | ||
+ | <code> | ||
+ | $ nm hidden | grep ' test$' | awk -F '[ \t]+' '{print $1;}' | ||
+ | 0000000000601080 | ||
+ | |||
+ | $ readelf -s hidden | grep ' test$' | awk -F '[ \t]+' '{print $3;}' | ||
+ | 0000000000601080 | ||
</code> | </code> | ||
- | * Într-o altă consolă vizualizăm dimensiunea spațiului fizic ocupat și a a spatiului virtual ocupat, folosind comanda:<code bash> | + | |
- | watch -n 1 ps -o pid,rss,vsz,cmd -p $(pidof demand-paging) | + | Pasul ''2'' și pasul ''3'' îi realizăm folosind utilitarul ''readelf'': |
+ | <code> | ||
+ | $ readelf -S hidden | grep ' .data ' | awk -F '[ \t]+' '{print $5;}' | ||
+ | 0000000000601060 | ||
+ | |||
+ | $ readelf -S hidden | grep ' .data ' | awk -F '[ \t]+' '{print $6;}' | ||
+ | 00001060 | ||
</code> | </code> | ||
- | Observăm cum crește dimensiunea spațiului spatiului virtual (coloana ''VSZ'') fără a crește dimensiunea spațiului fizic în prima parte. Se face alocare de memorie virtuală, fără paginare - adică fără alocare de spațiu fizic aferent. În partea a doua, observați cum crește dimensiunea spațiului fizic (coloana ''RSS'') în a doua parte (fără a crește dimensiunea spațiului virtual). Aceasta este //(on) demand paging//, cu alocarea paginilor fizice necesare la nevoie. | + | Pasul ''4'' îl realizăm prin operații aritmetice simple. |
- | Ca să detaliem, urmărim page fault-urile realizate pe parcursul rulării programului. Folosim, la fel, două console: | + | O dată aflat offset-ul putem folosi utilitarul ''dd'' pentru a suprascrie părți din fișier, cu template-ul: |
- | * Într-o consolă rulăm executabilul aferent:<code> | + | <code> |
- | ./demand-paging | + | $ echo -en "<value>" | dd seek=<offset> of=hidden bs=1 conv=notrunc |
</code> | </code> | ||
- | * Într-o altă consolă vizualizăm numărul de page fault-uri generate de program (''min_flt'' este coloana de interes, ''maj_flt'' este pentru interacțiuni cu discul -- swapping):<code bash> | + | |
- | watch -n 1 ps -o pid,min_flt,maj_flt,cmd -p $(pidof demand-paging) | + | Acești pași sunt realizați în scriptul ''rewrite_exec.sh''. În urma rulării acestui script obținem fișierul ''hidden_copy'' cu modificările realizate pentru a afișa mesajul ''success'': |
+ | <code> | ||
+ | $ ./rewrite_exec.sh | ||
+ | test_offset: 0x1080 | ||
+ | success_message_offset: 0x10a0 | ||
+ | Generated updated executable in hidden_copy. | ||
+ | |||
+ | $ ./hidden_copy | ||
+ | success | ||
+ | Give number: ^C | ||
</code> | </code> | ||
- | Observați cum nu există page fault-uri în prima pare a rulării programului, în momentul în care facem mapări de memorie. Dar apar page fault-uri în a doua parte a rulării programului. | ||
- | Câte page fault-uri sunt generate la o "trecere prin chunk"? De ce? | + | În acest moment avem realizat cu succes primul apel al funcției ''hidden_function()'' în urmă căruia este apelat mesajul ''success''. |
- | <spoiler Răspuns> | + | <note tip> |
- | Se generează 256 page fault-uri. Asta se întâmplă pentru ca la fiecare "trecere prin chunk" se accesează 256 pagini, fiecare pagină având câte 4KB, pentru un total de 1MB. Un page fault înseamnă alocarea (on demand) a unei pagini fizice. | + | Pentru mod programatic și avansat de analiză și modificare de executabile putem folosi [[https://lief.quarkslab.com/|LIEF]] sau [[http://docs.pwntools.com/en/stable/elf/elf.html|Modulul ''pwnlib.elf.elf'' din pwntools]]. |
- | </spoiler> | + | </note> |
- | ==== Page fault-uri la fork (copy-on-write) ==== | + | === Al doilea apel de funcție: analiză dinamică === |
- | Vrem să urmărim realizarea page fault-urilor în urma unui apel ''fork''; page fault-urilor vor fi cauzate de mecanismul de copy-on-write în momentul în care unul dintre cele două procese (copil sau părinte) scrie în zona respectivă. Pentru aceasta, accesăm subdirectorul ''fork-faults''; urmărim conținutul fișierului ''fork-faults.c''. În cadrul fișierului ''fork-faults.c'' se execută următorii pași: | + | Folosind doar analiză statică (lucru pe executabil) am realizat primul apel reușit al funcției ''hidden_function()''. Urmărim să realizăm și al doilea apel. Pentru al doilea apel depindem de informații aleatoare, a căror valoarea este știută doar la rulare (//run time//). De aceea avem nevoie de analiză dinamică, adică investigația folosind un debugger, în cazul nostru GDB. |
- | - se alocă memorie virtuală folosind ''mmap'' | + | |
- | - se alocă memorie fizică pentru paginile de mai sus, folosind //(on) demand paging// prin accesarea primului octet al fiecărei pagini | + | |
- | - se creează un proces nou | + | |
- | - procesul copil citește valoarea din prima jumătate a numărului de pagini (**doar** citește) | + | |
- | - procesul copil scrie o valoare în cadrul fiecărei pagini din a două jumătate | + | |
- | - procesul părinte scrie o valoare în toate paginile | + | |
- | Ca să urmărim ce se întâmplă, compilăm fișierul folosind ''make''. Apoi rulăm programul obținut:<code bash> | + | Pentru a apela corespunzător a doua oară funcția ''hidden_function()'', trebuie ca cele două condiții ''if'' (liniile ''11'' și ''26'' din fișierul ''hello.c'') să fie satisfăcute. Pentru aceasta trebuie: |
- | ./fork-faults | + | * să aflăm valorile ''r1'' și ''r2'' generate aleator |
- | </code> Folosim ''ENTER'' pentru a continua programul, dar după rularea ''pidstat'' (mai jos). | + | * să transmitem la intrarea standard în variabila ''in'' valoarea variabilei ''r1'' ca să satisfacem condiția ''if'' de la linia ''26'' |
+ | * să suprascriem valoarea variabilei ''test'' cu valorea variabilei ''r2'' ca să satisfacem condiția ''if'' de la linia ''11'' | ||
- | Într-o altă consolă folosim utilitarul ''pidstat'' din pachetul ''sysstat'' care permite monitorizarea page fault-urilor unui proces (prin intermediul argumentului ''-r''). Dacă nu există comanda ''pidstat'' trebuie să instalăm pachetul ''sysstat'' folosind comanda:<code bash> | + | Ne vom folosi de facilitățile GDB pentru acest lucru: |
- | apt-get install sysstat | + | * folosirea de breakpoint-uri și suspendarea execuției programului/procesului într-un punct convenabil |
+ | * aflarea adreselor simbolurilor din cadrul programului | ||
+ | * investigarea conținutului memoriei programului/procesului | ||
+ | * suprascrierea conțiinutului memoriei programului/procesului | ||
+ | |||
+ | Adică vom urma pașii: | ||
+ | - Vom suspenda execuția programului după generarea celor două numere aleatoare ''r1'' și ''r2''. | ||
+ | - Vom afla valorile celor două numere. | ||
+ | - Vom afla adresa variabilei ''test'' în memorie. | ||
+ | - Vom suprascrie valoarea variabilei ''test'' cu valoarea variabilei ''r2'' ca să satisfacem condiția ''if'' de la linia ''11''. | ||
+ | - Vom continua execuția programului și la citirea valorii de la intrarea standard (prin intermediul funcției ''read_uint()'') vom transmite valoarea variabilei ''r1'' ca să satisfacem condiția ''if'' de la linia ''26''. | ||
+ | - Programul își va continua execuția și va apela corespunzător funcția ''hidden_function()'' care va apela a doua oară mesajul ''success''. | ||
+ | |||
+ | Pașii sunt urmați în GDB în secvența de mai jos: | ||
+ | |||
+ | <code> | ||
+ | $ gdb -q ./hidden_copy | ||
+ | Reading symbols from ./hidden_copy...done. | ||
+ | (gdb) start | ||
+ | Temporary breakpoint 1 at 0x400702: file hidden.c, line 20. | ||
+ | Starting program: /home/student/so/demo/exec-process/hidden/hidden_copy | ||
+ | |||
+ | Temporary breakpoint 1, main () at hidden.c:20 | ||
+ | 20 hidden_function(100); | ||
+ | (gdb) set disassembly-flavor intel | ||
+ | (gdb) disass | ||
+ | Dump of assembler code for function main: | ||
+ | 0x00000000004006fa <+0>: push rbp | ||
+ | 0x00000000004006fb <+1>: mov rbp,rsp | ||
+ | 0x00000000004006fe <+4>: sub rsp,0x10 | ||
+ | => 0x0000000000400702 <+8>: mov edi,0x64 | ||
+ | 0x0000000000400707 <+13>: call 0x4006d7 <hidden_function> | ||
+ | 0x000000000040070c <+18>: mov edi,0x0 | ||
+ | 0x0000000000400711 <+23>: call 0x4005d0 <time@plt> | ||
+ | 0x0000000000400716 <+28>: mov edi,eax | ||
+ | 0x0000000000400718 <+30>: call 0x4005a0 <srand@plt> | ||
+ | 0x000000000040071d <+35>: call 0x4005e0 <rand@plt> | ||
+ | 0x0000000000400722 <+40>: mov DWORD PTR [rbp-0x4],eax | ||
+ | 0x0000000000400725 <+43>: call 0x4005e0 <rand@plt> | ||
+ | 0x000000000040072a <+48>: mov DWORD PTR [rbp-0x8],eax | ||
+ | 0x000000000040072d <+51>: mov edi,0x400834 | ||
+ | 0x0000000000400732 <+56>: call 0x400753 <read_uint> | ||
+ | 0x0000000000400737 <+61>: mov DWORD PTR [rbp-0xc],eax | ||
+ | 0x000000000040073a <+64>: mov eax,DWORD PTR [rbp-0xc] | ||
+ | 0x000000000040073d <+67>: cmp eax,DWORD PTR [rbp-0x4] | ||
+ | 0x0000000000400740 <+70>: jne 0x40074c <main+82> | ||
+ | 0x0000000000400742 <+72>: mov eax,DWORD PTR [rbp-0x8] | ||
+ | 0x0000000000400745 <+75>: mov edi,eax | ||
+ | 0x0000000000400747 <+77>: call 0x4006d7 <hidden_function> | ||
+ | 0x000000000040074c <+82>: mov eax,0x0 | ||
+ | 0x0000000000400751 <+87>: leave | ||
+ | 0x0000000000400752 <+88>: ret | ||
+ | End of assembler dump. | ||
+ | (gdb) b *0x0000000000400732 | ||
+ | Breakpoint 2 at 0x400732: file hidden.c, line 25. | ||
+ | (gdb) c | ||
+ | Continuing. | ||
+ | success | ||
+ | |||
+ | Breakpoint 2, 0x0000000000400732 in main () at hidden.c:25 | ||
+ | 25 in = read_uint("Give number: "); | ||
+ | (gdb) x/2wd $rbp-8 | ||
+ | 0x7fffffffdc08: 1586380876 1175232931 | ||
+ | (gdb) p test | ||
+ | $1 = 100 | ||
+ | (gdb) p &test | ||
+ | $2 = (unsigned int *) 0x601080 <test> | ||
+ | (gdb) set *0x601080=1586380876 | ||
+ | (gdb) p test | ||
+ | $1 = 1586380876 | ||
+ | (gdb) c | ||
+ | Continuing. | ||
+ | Give number: 1175232931 | ||
+ | success | ||
+ | [Inferior 1 (process 28011) exited normally] | ||
+ | (gdb) q | ||
</code> | </code> | ||
- | Pentru a rula ''pidstat'' și a urmări page fault-urile, folosim, pe a doua consolă, comanda<code bash> | + | Comenzile GDB folosite în secvența de mai sus sunt: |
- | pidstat -r -T ALL -p $(pidof fork-faults) 5 100 | + | * ''start'': pentru a porni procesul și a crea un breakpoint la începutul funcției ''main'' |
- | </code> Comanda de mai sus afișează câte un mesaj la fiecare 5 secunde. În prima consolă apăsăm ''ENTER'' pentru a continua rularea și urmărim informațiile afișate de ''pidstat'', apoi continuăm apăsarea ''ENTER'' etc. | + | * se apelează prima instanță a funcției ''hidden_function'', se afișează ''success'' |
+ | * ''set disassembly-flavor intel'': pentru a dezasambla în sintaxă Intel | ||
+ | * ''disass'': pentru a dezasambla codul, să urmărim unde putem plasa un breakpoint | ||
+ | * ''b *0x0000000000400732'': pentru a crea un breakpoint înainte de apelul funcției ''read_uint''; adică *după* ce au fost generate în variabilele ''r1'' și ''r2'' | ||
+ | * variabilele ''r1'' și ''r2'' sunt reținute pe stivă respectiv la adresele ''rbp-4'' și ''rbp-8''; observăm în secvența dezasamblată între adresele ''0x40071d'' și ''0x40072a'' | ||
+ | * ''c'': pentru continuarea execuției până la breakpoint-ul de mai sus | ||
+ | * acum suspendat programul la breakpoint, putem citi valorile variabilelor ''r1'' și ''r2'' | ||
+ | * ''x/2wd $rbp-8'': pentru citirea a două (''2'') numere pe 32 de biți (''w'') și afișarea lor în format zecimal (''d'') de la adresa ''rbp-8'', adică valorile de la ''rbp-8'' (''r2'') și ''rbp-4'' (''r1''). Rezultă de aici că ''r1'' este ''1175232931'' și ''r2'' este ''1586380876''. | ||
+ | * ''p test'': pentru afișarea valorii variabilei ''test'', inițial ''100'' | ||
+ | * ''p &test'': pentru afișarea adresei variabilei ''test'', adică ''0x601080'' | ||
+ | * ''set *0x601080=1586380876'': pentru modificarea valorii de la adresa ''0x601080'' (adică a variabilei ''test'') în valoarea variabilei ''r2'' (''1586380876'') | ||
+ | * ''p test'': pentru verificarea noii valori a variabilei ''test''; se confirmă că am modificat valoarea la 1586380876 | ||
+ | * ''c'': pentru continuarea execuției programului | ||
+ | * rulează funcția ''read_uint'' care solicită furnizarea unui număr ce va fi comparat cu ''r1''; furnizăm valoarea lui ''r1'' adică ''1175232931'' | ||
+ | * programul continuă rularea, apelează funcția ''hidden_function'', afișează a doua oară ''success'' | ||
+ | * ''q'': pentru încheierea sesiunii curente și închiderea GDB | ||
+ | |||
+ | === Alte trucuri === | ||
+ | |||
+ | Pașii de mai sus pot fi simplificați prin folosirea în GDB a comenzii ''call'' care permite apelul direct al unei funcții. Astfel, la începutul sesiunii GDB a programului, putem declanșa apelul direct al funcției ''hidden_function'' ca mai jos: | ||
+ | |||
+ | <code> | ||
+ | (gdb) start | ||
+ | Temporary breakpoint 1 at 0x400702: file hidden.c, line 20. | ||
+ | Starting program: /home/student/so/demo/exec-process/hidden/hidden_copy | ||
+ | |||
+ | Temporary breakpoint 1, main () at hidden.c:20 | ||
+ | 20 hidden_function(100); | ||
+ | (gdb) call hidden_function(100) | ||
+ | success | ||
+ | </code> | ||
+ | |||
+ | În felul acesta putem apela orice funcție din executabil fără a fi nevoie să facem operații mai complicate de investigare și modificare a memoriei. | ||
+ | |||
+ | Ba mai mult, putem apela direct funcția ''puts'': | ||
+ | <code> | ||
+ | (gdb) call puts("success") | ||
+ | success | ||
+ | $1 = 8 | ||
+ | </code> | ||
+ | |||
+ | <note tip> | ||
+ | GDB nu are o interfață mai puțin prietenoasă și lipsuri pe partea de exploiting și reverse engineering. Pentru acestă, vă recomandăm să augmentați GDB cu proiecte dedicate pentru acest lucru: | ||
+ | * [[http://pwndbg.re/|pwndbg]] | ||
+ | * [[https://gef.readthedocs.io/|GEF]] | ||
+ | * [[https://github.com/longld/peda|PEDA]] | ||
+ | |||
+ | PEDA este o versiune mai puțin menținută curentă, dar cu o istorie mai lungă în comunitatea de securitatea. pwndbg și GEF sunt proiecte mai recente, cu funcționalități mai multe și comunități active de dezvoltare. | ||
+ | |||
+ | Cele trei proiecte pot fi configurate să fie prezente simultan (dar folosite alternativ) în GDB, așa cum este descris [[https://infosecwriteups.com/pwndbg-gef-peda-one-for-all-and-all-for-one-714d71bf36b8|aici]] (și configurat automat cu [[https://github.com/apogiatzis/gdb-peda-pwndbg-gef|acest repository]]). | ||
+ | </note> | ||
- | Urmărim, în outputul comenzii ''pidstat'', evoluția numărului de page fault-uri pentru cele două procese: părinte și copil. Page fault-urile care apar în cazul unui copy-on-write în procesul copil vor fi vizibile ulterior și în procesul părinte. Observăm că procesul copil generează page fault-uri doar pe jumătate din pagini (cele în care scrie), iar procesul părinte generează page fault-uri pe toate paginile. Asta pentru că un proces creează o copie a paginilor inițiale, dar lasă acele pagini ''read-only'', iar alt proces primește page fault dar doar schimbă permisiunile din read-only în read-write. |