Dezvoltarea codului shell pentru linux și bsd, linux exp group

Codul Shell este o secvență de instrucțiuni pentru mașină care poate fi folosită pentru a forța un program care rulează deja pentru a efectua ceva alternativ. Cu această metodă, puteți exploata anumite vulnerabilități software (de exemplu, overflow de stivă, overflow de heap, vulnerabilități de șir de formate).







Un exemplu de modul în care ar putea arăta un cod shell:

Aceasta este, în general, aceasta este o secvență de octeți în limbajul mașinii. Scopul acestui document este de a lua în considerare cele mai comune tehnici pentru dezvoltarea codului shell pentru sistemele Linux și BSD care rulează pe arhitectura x86.

Rummaged în rețea, puteți găsi cu ușurință exemple gata de cod shell, care pot fi copiate și plasate numai în locul potrivit. De ce studiază evoluția sa? Pentru gustul meu, există cel puțin două motive bune:

- În al doilea rând, trebuie amintit că shell-cod poate rula în complet diferite medii, cum ar fi input-output manipulare porțiuni filtru șir de caractere, IDS, și este util să ne imaginăm cum trebuie modificată în conformitate cu condițiile;

- În plus, noțiunea de exploatare a vulnerabilităților vă va ajuta să scrieți programe mai sigure.

Listarea 1. Apelurile de sistem definite în /usr/src/linux/include/asm-i386/unistd.h; fiecare are propriul număr

Apeluri sistem Linux
Deși codul shell poate executa în esență ceva, scopul principal al lansării sale este să obțină acces la shell-ul shell-ului pe mașina țintă, de preferință în modul privilegiat, unde, de fapt, numele a trecut de la codul shell.
Modul cel mai simplu și direct de a efectua o sarcină complexă în asamblare este de a folosi apelurile de sistem. Apelurile de sistem oferă o interfață între spațiul utilizator și spațiul kernel-ului; cu alte cuvinte, aceasta este modalitatea de a obține programul de întreținere al utilizatorului de la serviciile de bază. De exemplu, sistemul de fișiere este gestionat, se lansează procese noi, se oferă acces la dispozitive și așa mai departe.
Așa cum se arată în Lista 1, apelurile de sistem sunt definite în fișierul /usr/src/linux/include/asmi386/unistd.h, fiecare dintre ele are un număr.
Există două modalități standard de a utiliza apelurile de sistem:

- utilizarea unei întreruperi software 0x80;
- apelați funcția de împachetare din libc.

Prima metodă este mai portabilă, deoarece o vom folosi pentru orice distribuție Linux (definită de codul kernelului). A doua metodă este mai puțin portabilă, deoarece este definită de codul standard al bibliotecii.

int 0x80
Ne uităm mai îndeaproape la prima metodă. Când procesorul primește o întrerupere de 0x80, acesta intră în modul kernel și execută funcția solicitată, obținând mânerul dorit din tabela descriptorului de întrerupere (tabele descriptor de întrerupere). Numărul apelului de sistem trebuie definit în EAX, care va conține eventual valoarea returnată. La rândul său, în EBX, ECX, EDX, ESI, EDI și EBP trebuie să conțină argumente de funcții de până la șase, în această ordine și numai numărul necesar de registre, nu toate. Dacă funcția necesită mai mult de șase argumente, trebuie să le plasați în structură și să salvați pointerul la primul element din EBX.

Amintiți-vă că kernel-urile Linux de sub 2.4 nu utilizează registrul EBP pentru a transmite argumente și, prin urmare, pot trece doar cinci argumente prin intermediul registrelor.

După salvarea numărului de apel și a parametrilor de sistem în registrele corespunzătoare, este apelată întreruperea 0x80: procesorul intră în modul kernel, efectuează un apel sistem și transferă controlul procesului utilizatorului. Pentru a reda acest script, aveți nevoie de:

- Salvați numărul apelului de sistem în EAX;
- salvați argumentele de apel sistem în registrele necesare;

- Creați o structură în memorie care conține parametrii apelului sistem;
- Salvați pointerul la primul argument în EBX;
- executați o întrerupere de software 0x80.

Cel mai simplu exemplu va conține un clasic - un apel de sistem pentru ieșire (2). știm numărul de fișier /usr/src/linux/include/asm-i386/unistd.h: 1. Ghid pagina ne spune că doar un singur argument obligatoriu (statusul), așa cum se arată în Listarea 2.

Listing 2. Pagina man indică faptul că este necesar un parametru







Îl vom păstra în registrul EBX. Astfel, sunt necesare următoarele instrucțiuni:

libc
Așa cum am menționat, o altă metodă standard este de a folosi funcția C. Să vedem cum se face acest lucru cu un exemplu de program simplu C:

Trebuie doar să o compilați:

Dezasamblați-l cu gdb pentru a vă asigura că utilizează același apel de sistem (listare 3).

Listare 3. Dezasamblați programul de ieșire cu ajutorul programului de depanare gdb

Ultima funcție în principal () este ieșirea (3). Apoi, vedem că ieșirea (3) la rândul său cheamă _exit (2), care apelează apelul de sistem, inclusiv întreruperea 0x80, Listing 4.

Listarea 4. Executarea unui apel sistem

Astfel, codul shell folosind libc invocă indirect apelul de sistem _exit (2):

Lista 5. Începutul fișierului /usr/src/sys/kern/syscalls.master în OpenBSD

Prima linie conține numărul apelului sistem, al doilea - tipul acestuia, al treilea - funcția prototip. Spre deosebire de Linux, * apelurile de sistem BSD nu utilizează o convenție de apel rapid, cu argumente plasate în registre, în schimb, este folosit un stil C, cu argumente plasate pe stivă. Argumentele sunt plasate în ordine inversă, începând cu cea dreaptă, astfel încât acestea vor fi extrase în ordinea corectă. Imediat după întoarcerea de la apelul de sistem, teancul trebuie să fie eliminat prin plasarea numărului de octeți în indicatorul offset al stivei, care este lungimea tuturor argumentelor (sau, mai simplu, prin adăugarea octeților în numărul de argumente înmulțit cu 4). Rolul registrului EAX este același ca și în Linux, conține numărul apelului sistem și conține în cele din urmă valoarea returnată.

Astfel, pentru a executa un apel de sistem, aveți nevoie de patru pași:

- salvarea numărului de apel în EAX;
- punerea argumentelor în stivă în ordinea inversă;
- executarea întreruperii software-ului 0x80;
- curățarea stivei.

Un exemplu pentru Linux, convertit la * BSD, ar arăta astfel:

Scrieți un cod shell
Următoarele exemple, concepute pentru Linux, pot fi ușor adaptate lumii * BSD. Pentru a obține codul de coadă gata, trebuie să obținem opcodele care corespund instrucțiunilor de asamblare. Trei metode sunt utilizate frecvent pentru a obține opcode:

- le scrie manual (cu documentația Intel în mână!);
- scrierea unui cod de asamblare cu extragerea ulterioară a unui cod opcode;
- scrierea codului în C și apoi dezasamblarea acestuia.

Să analizăm acum celelalte două metode.

montator
Primul pas este să utilizați codul de asamblare din exemplul exit.asm utilizând apelul de sistem _exit (2). Pentru a obține opcode, utilizați nasm și apoi dezasamblați binarele asamblate cu objdump, așa cum se arată în listare 6.

Listing 6. Construiți codul din textul de asamblare și dezasamblați-l

Listarea 7. Testarea unui cod opcod

Sper că gura ta sa deschis destul de larg. Pentru a vă asigura că codul shell este executat, rulați aplicația sub strace, Listing 8.

Listarea 8. Urmărirea aplicației de verificare

Ultimul rând este un apel la ieșire (2). Cu toate acestea, privind codul shell, vedem o mică problemă: conține mulți octeți zero. Deoarece codul shell este adesea scris în buffer-ul de șir, acești octeți vor fi îngropați în separatorul de linii și atacul va eșua. Există două modalități de a rezolva problema:

- Scrieți instrucțiuni care nu conțin octeți nula (ceea ce nu este întotdeauna posibil);
- scrieți codul shell pentru a-l modifica manual, eliminând octeți zero, astfel încât la execuție codul însuși adaugă octeți zero, aliniind linia la delimitator.

Ne uităm la prima metodă.
Prima instrucțiune (mov ebx, 0) poate fi modificată mai ușor de utilizat (din motive de performanță):

A doua declarație conține toate aceste zerouri, deoarece utilizează un registru de 32 de biți (EAX), generează 0x01, care devin 0x01000000 (fursecuri sunt inversate ca Intel ® - procesor little endian). Deci, putem rezolva această problemă pur și simplu folosind un registru de 8 biți (AL):

Acum, codul nostru de asamblare arată astfel:

și nici un bytes nul (Listing 9).

Listing 9. Verificarea codului shell

Pe C
Înainte: extragerea opcodelor prin dezasamblarea programului compilat în C. Să luăm binarul obținut de la exit.c și să îl deschidem cu gdb, Listing 10.

Listing 10. Exit.c binar, deschis cu gdb

După cum puteți vedea, funcția _exit (2) utilizează de fapt două apeluri de sistem: 0xfc (252), _exit_group (2) și apoi _exit (2). _exit_group (2) este similar cu _exit (2), dar scopul său este de a termina toate firele din grup. Pentru codul nostru, este necesar doar cel de-al doilea apel sistem.

De asemenea, ca în exemplul anterior, trebuie să depășiți octeții nulați.

Obținerea consolei
Este timpul să scrieți un cod shell care vă va permite să faceți ceva mai util. De exemplu, putem crea coduri pentru a avea acces la consola și că, după generarea consolei, este complet terminat. Cea mai simplă abordare este utilizarea apelului de sistem execve (2). Nu uita să te uiți la pagina man, Listing 11.

Listing 11. man 2 execve

Trebuie să trecem trei argumente:

- indică numele programului pentru a executa, în cazul nostru, un pointer la șir / bin / sh;
- pointer la o serie de siruri de caractere trecut ca argumente de program, primul argument trebuie să fie argv [0], adică numele programului în sine, ultimul argument trebuie să fie un pointer nul;
- Un pointer la o serie de șiruri de caractere pentru a le transmite ca mediu al programului; aceste șiruri sunt de obicei specificate în formatul key = value, iar ultimul element al matricei trebuie să fie un pointer nul. Pe C, arata cam asa:

Vom colecta și vom vedea cum funcționează:

Așa că am primit cochilie. Acum, să vedem cum arată acest apel în sistem în asamblare (deoarece am folosit trei argumente, putem folosi registrele în loc de structuri). Imediat se dezvăluie două probleme:

Să vedem ce face el:

De acum înainte, puteți utiliza o structură de cod shell compusă din ceva util. Să analizăm pas cu pas acțiunile noastre planificate:

Codul de asamblare rezultat este prezentat în Lista 13.

Listing 13. Reproiectat codul de asamblare

Să scoatem opcodele, Listing 14:







Articole similare

Trimiteți-le prietenilor: