``Ușa din spate''

Mihai BUDIU - budiu@cs.cornell.edu
http://www.cs.cornell.edu/Info/People/budiu/budiu.html

18 martie 1997


Contents

Subiect:
Putem avea încredere în programe?
Cuvinte cheie:
back-door
Cunoștințe necesare:
elemente despre funcționarea calculatoarelor, compilare, elemente de C

Personajele

Ken Thomson este unul dintre numele cele mai mari din istoria calculatoarelor. Sau, dacă asta poate fi contestat, este cu siguranță unul dintre numele cele mai faimoase. Ken Thomson lucrează la laboratoarele Bell, care au aparținut firmei AT&T. (Laboratoarele Bell au fost sediul cercetării gigantului telecomunicațiilor; multe dintre descoperirile revoluționare ale secolului au fost făcute aici. O lista ne-exhaustivă trebuie să cuprindă: sateliții de telecomunicații, bateriile solare, tranzistorul, laser-ul, sistemul de operare Unix și limbajul C++. La ora actuală AT&T a fost spartă în bucăți de legea americană anti-trust, așa că o parte însemnată a laboratoarelor Bell a devenit o firmă independentă numită Lucent Technologies).

Ken Thomson este practic inventatorul Unix-ului. (Am scris un articol ceva mai detaliat in BYTE din August 1996 despre istoria acestui faimos sistem de operare). Acest sistem a revoluționat în multe privințe sistemele de calcul, iar influențele sale se dovedesc fructuoase și în ziua de azi. Pentru această realizare lui Thomson i-a fost decernat în 1984 premiul Turing al ACM (Association for Computing Machinery), care pentru lumea calculatoarelor este echivalent cu un Nobel.

În cuvîntarea ținută cu prilejul decernării premiului, Thomson a dezvăluit cea mai mare fraudă informatică, al cărei autor este el însuși. Textul comunicării este ``Reflections on Trusting Trust'', adică ``Reflecții asupra încrederii în încredere''. Problema centrală care pe care Thomson o pune este dacă putem avea încredere în programele pe care le folosim, și cum putem dobîndi încredere în ele. Expunerea lui arată că posibilitatea de a citi programele nu este o garanție suficientă pentru a ne asigura de corectitudinea lor!

O mulțime de bani se investesc în ziua de azi în cercetări legate de această întrebare. Fericiți sunt cei care nu au avut niciodată de suferit de pe urma virușilor. În ziua de azi programele devin din ce în ce mai mobile (de exemplu applet-urile sunt programe transferate prin rețea în momentul cînd clientul de web accesează un document) și Internetul este calul de bătaie al marilor firme, care au înțeles că o mulțime de bani se pot obține prin servicii oferite prin rețea. Întrebările lui Thomson sunt cu atît mai acute din această cauză, iar răspunsuri pe deplin satisfăcătoare sunt departe de a fi date.

Thomson sesiza însă gravitatea acestor probleme acum 20 de ani, cînd calculatoarele erau departe de a fi atît de răspîndite. Iată povestea lui:

Programul login

Thomson este, după cum am spus, autorul sistemului de operare Unix. Sistemul Unix este conceput pentru calculatoare care sunt folosite de mai mulți utilizatori simultan, și din cauza aceasta adresează de la bun început destul de serios problema securității. Unix-ul încearcă să izoleze procesele care se execută pe o aceeași mașină între ele, ca să nu se poată incomoda. (Un proces este un program care a fost pornit in execuție, deci un obiect activ). În Unix programele care sunt executate de un același utilizator au oarecari posibilități lărgite de a se influența reciproc; de exemplu un proces al meu poate omorîpe un altul care-mi aparține, dar nu poate omorînici unul din procesele care nu a fost create de mine. (Mai precis, poate trimite semnale, care uneori pot cauza moartea; moartea unui proces este încetarea execuției sale).

Pentru a putea decide ce drepturi au procesele, ele trebuie să fie asociate cumva utilizatorilor. Asocierea aceasta este realizată în Unix de programul /bin/login.

(În cele ce urmează vom simplifica discuția pentru a pune în valoare doar detaliile care ne interesează; în realitate lucrurile sunt puțin mai complicate. Poate vom consacra cîndva un articol special securității în sistemele Unix.)

Programul login face legătura dintre un utilizator și procesele sale; acest program autentifică un utilizator testînd cunoașterea unui secret. Această verificare se face cînd utilizatorul se așează prima oară în fața calculatorului (face ``login''), o singură dată, și efectele ei sunt vizibile pînă cînd utilizatorul părăsește terminalul în mod explicit (face ``logout''). Toate comenzile lansate de la acest terminal între cele două evenimente sunt implicit ale utilizatorului care a fost autentificat. (Dacă utilizatorul părăsește terminalul pentru o pauză de cafea și altcineva îl folosește în răstimp, calculatorul nu are nici o metodă de a verifica acest lucru, și ia comenzile ca fiind executate de persoana care a fost ultima autentificată. Calculatorul nu are la ora actuală nici o metodă de a ``simți'' lumea exterioară, pentru a observa că utilizatorul este altul.)

Lucrurile funcționează cam așa:

  1. Dacă un utilizator (eu) vrea să folosească o mașina Unix, în primul rînd trebuie să vorbească cu administratorul de sistem. Administratorul de sistem îi acordă un nume de utilizator (budiu) și un număr asociat întotdeauna (uid = ``user identifier''); pentru mine din întîmplare uid=831. Pentru că în general corespondența nume-număr este unu-la-unu, vom folosi numele de ``uid'' pentru ambele. Comanda id în Unix afișează numele și numărul, precum și alte informații legate de identificare:

    uid=813(budiu) gid=5(users)
    

  2. Noul utilizator îi comunică administratorului un secret: o parolă. Parola este criptată și pusă pe disc ca fiind asociată utilizatorului respectiv.

  3. Toate parolele (criptate) și numele de utilizatori sunt ținute în Unix-urile tradiționale într-un fișier numit /etc/passwd. Iată o posibila linie:

    budiu:PGffJFh3BfCAw:831:5:Mihai Budiu:/home/budiu:/bin/bash
    

    Cîmpurile sunt separate cu semnul ``:''. Primul este numele meu de utilizator asociat, al doilea este parola mea criptată, al treilea este numărul (uid) asociat mie; celelalte nu ne interesează prea tare în acest articol, dar pentru curioși sunt: numărul grupului din care fac parte, numele meu real, directorul meu casă și programul cu care conversez eu în mod normal (``shell''-ul meu).

  4. În acest moment fiecare utilizator are un nume și o parolă, pe care nu trebuie să le comunice nimănui (ori altcineva se va putea substitui lui în folosirea calculatorului). Utilizatorul poate să-și schimbe mai tîrziu parola dacă vrea, dar uid-ul este în principiu fixat.

  5. Calculatorul la pornire execută pentru fiecare terminal legat la calculator cîte o copie a programului /bin/login.

  6. Programul login scrie pe fiecare terminal textul login: și așteaptă să vină un utilizator.

  7. Utilizatorul se așează la terminal și își tastează numele asignat (eu de pildă scriu login: budiu).

  8. Programul login cere parola scriind: Password:.

  9. Utilizatorul tastează parola (nu spun). /bin/login criptează parola, și compară rezultatul cu șirul din fișierul /etc/passwd. Dacă rezultatele coincid (în cazul meu se obține PGffJFh3BfCAw), atunci utilizatorul este considerat autentificat, și este primit de sistem.

  10. Dacă utilizatorul nu este primit, /bin/login se execută din nou de la pasul 6.

  11. Dacă utilizatorul a fost primit, pe acel terminal se execută un program care acceptă comenzi de la utilizator și le execută (shell). Acest program are pusă în frunte o ștampilă care spune: ``acest program aparține lui cutare uid'' (831 pentru mine).

  12. În continuare, shell-ul ascultă comenzile mele, le execută una cîte una, și reia conversația cu mine, pîna tastez logout.

  13. Toate comenzile care sunt ``pui'' ai shell-ului moștenesc genetic de la el uid-ul meu, 831.

Discuția se poate dovedi pe alocuri confuză pentru că același cuvînt se folosește simultan pentru a desemna mai multe lucruri; de pildă ``login'' înseamnă:

Din schema asta complexă trebuie reținut un lucru destul de simplu: toate procesele mele moștenesc de la /bin/login uid-ul meu; /bin/login este singurul program care mă identifică.

Ticăloșia

De acest lucru s-a folosit Thomson pentru a crea o intrare universală (o ``ușa din spate neștiută de nimeni'': a ``back door''): Thomson a scris programul /bin/login în așa fel încît dacă cineva tastează numele lui și o parolă fixată să fie acceptat în sistem chiar dacă nu are dreptul (adică chiar dacă în /etc/passwd nu există nici o linie pentru Thomson!). În felul acesta Thomson poate avea acces neîngrădit la orice sistem Unix din lume.

Lumea și-a exprimat dintotdeauna temerile cu privire la programe cumpărate pe de-a gata: ele sunt întotdeauna susceptibile să conțină asemenea portițe care îi dau fabricantului tot felul de privilegii din momentul în care programele ajung să se execute. Nu ne vom lansa în considerații etice aici, mai bine să urmărim mai departe subterfugiile lui Thomson, care nu se opresc aici!

Thomson este gata să pună la dispoziția celor sperioși sursele C ale programului /bin/login, pe care aceștia să le poată inspecta. Programul în principiu trebuie să fie destul de simplu ceva de genul (scris în pseudo-C):

while (1) {
  scrie("login: ");
  nume = citeste();
  scrie("Password: ");
  parola = citeste();

  criptata = cripteaza(parola);
  parola_corecta = cauta_parola("/etc/passwd", nume);
  if (egale(criptata, parola_corecta)) {
     uid = cauta_uid("/etc/passwd", nume);
     shell = cauta_shell("/etc/passwd", nume);
     executa(shell);
  }
}

Dar dacă sursele sunt vizibile, oricine poate depista porțiunea în care în funcție de o comparație cu numele lui Thomson se ia o decizie specială.

Probabil testul arată cam așa:

usa_din_spate()
{
  if (egale(nume, "Thomson") &&
      egale(parola, "secret")) 
    executa("/bin/sh");
}

Testul poate fi invocat inserînd o linie care cheamă funcția usa_din_spate() în locul unde am lăsat o linie albă în sursele lui login.

Compilatorul

Thomson a scos acest test din sursele programului /bin/login, dar a modificat compilatorul de C! (În definitiv tot el a scris și compilatorul, nu?). Compilatorul de C se uită atunci cînd compilează un program dacă nu cumva programul este chiar sursa de la /bin/login; dacă este chiar sursa, atunci înainte de a compila programul, compilatorul de C inserează el însuși funcția usa_din_spate() și apelul ei în sursă!

În felul acesta sursele programului sunt absolut inofensive pentru orice cititor, pentru că ele sunt transformate abia la compilare, iar rezultatul compilării nu mai este atît de simplu de citit.

De data asta cel care arată ciudat este compilatorul însuși: codul lui este probabil cam așa (șirurile de caractere sunt foarte lungi, așa că înlocuim cu ``...'' conținutul lor în exemplul de mai jos):

char * cod_login = "while (1) {\n\tscrie(\"login: \");\n....";
char * cod_usa_din_spate = "usa_din_spate(){if(egale(nume, ....";
char * apel_usa_din_spate = "usa_din_spate();";

modifica_login(program)
{
   /* text care modifica programul login */
   insereaza(program, cod_usa_din_spate);
   insereaza(program, apel_usa_din_spate);
}

analizeaza(program)
{
   if (seamana(program, cod_login)) {
       modifica_login(program);
   }
   compilare_obisnuita();
}

Dacă cineva se uită la sursele compilatorului de C va observa probabil acest tratament neobișnuit al programului care tocmai se compilează.

Din cauza asta Thomson a mers și mai departe, acționînd în același fel: a modificat compilatorul de C în așa fel încît să depisteze și cînd codul compilat este chiar compilatorul de C. Sursele compilatorului de C distribuite utilizatorilor sunt ``curate'' (nu conțin codul de mai sus), dar compilatorul deja instalat pe fiecare mașină este deja ``infectat'', pentru că procedează astfel:

Cînd observă că tocmai compilez din nou compilatorul din surse (de exemplu cînd compilez funcția analizeaza(), atunci inserează la începutul ei codul care modifică programul login.

Cu alte cuvinte compilatorul instalat arată cam așa:

modifica_login(program)
{
    ....
    insereaza(program, cod_usa_din_spate);
}    

modifica_compilator(program)
{
    ....
    insereaza(program, cod_modifica_login);
    insereaza(program, cod_modifica_compilator);
}

analizeaza(program)
{
   ...
   if (seamana(program, cod_login)) {
      modifica_login(program);
   }
   if (seamana(program, cod_analizeaza)) {
      modifica_compilator(program)
   }
   compilare_obisnuita();
}

Nimeni nu poate descoperi această stratagemă cu ușurință. Un individ mai suspicios va analiza cu atenție codul compilatorului, va observa că totul este în regulă, îl va compila folosind compilatorul existent. Dar deja în acest moment compilatorul sigur a devenit infectat. Pe de altă parte este foarte greu să compilezi ceva fără a folosi compilatorul de C (în Unix C este limbajul favorit). Din această cauză este extrem de dificil de îndepărtat ușa din spate.

Concluzii

Voi încheia acest articol traducînd literal încheierea lui Thomson însuși; cei la curent cu atacurile informatice petrecute în ultima vreme în România vor vedea aceste cuvinte ca fiind de o remarcabilă actualitate (vă reamintesc că textul este din 1984):

Morala este evidentă: nu poți avea încredere în cod pe care nu l-ai creat în totalitate tu însuți (mai ales cod de la companii care angajează oameni ca mine). Nici o cantitate de verificare a codului la nivelul surselor sau analiză nu te va proteja în cazul în care folosești cod nesigur. Demonstrînd posibilitatea acestui fel de atac am ales compilatorul de C. Aș fi putut alege orice alt program care mînuiește programe, cum ar fi un asamblor, un încărcător (loader) sau chiar microcod hardware. Pe măsură ce nivelul programelor scade, aceste ``bug''-uri devin din ce in ce mai greu de detectat. Un bug bine-instalat in microcod va fi aproape imposibil de detectat.

După ce am încercat sa vă conving că nu pot fi crezut, vreau să moralizez un pic. Aș vrea să critic presa în felul în care tratează ``hackerii'' [...] Actele făcute de acești copii sunt vandalism în cel mai bun caz, și probabil încălcarea proprietății private și furt în cel mai rău. Numai inadecvarea legilor penale îi salvează pe acești hackeri de la urmăriri penale serioase. Companiile care sunt vulnerabile activității lor (și majoritatea companiilor mari sunt vulnerabile) fac mari presiuni pentru a adapta legile. [În Statele Unite în ziua de azi lucrurile stau deja mult mai bine din acest punct de vedere.]

Este o situație explozivă: pe de o parte presa, televiziunea și filmele fac eroi din vandali numindu-i ``copii minune''. Pe de altă parte actele acestor copii vor fi în curînd pedepsibile cu ani grei de închisoare.

Am privit acești copii depunînd mărturie în fața Congresului. Este evident că nu-și dau de loc seama de seriozitatea actelor lor. Este evident o prăpastie culturală. Actul de a pătrunde într-un sistem de calcul ar trebui să fie însoțit de același stigmat social cu acela de a intra în casa vecinului. Nu trebuie să conteze că ușa vecinului nu este încuiată. Presa trebuie să învețe că folosirea ``strîmbă'' a unui calculator nu este cu nimic mai uimitoare decît condusul în stare de ebrietate.