The Assembler guide

Update: Oct 4th, 2009

Around 2000 I got familiar enough with the x86 assembly language in order to write a tutorial on assembly language. The tutorial proven to be quite popular, mainly because it included a description of the inner workings of a simple virus.  The articles were published in an online newsletter.

The article is written in Romanian and it is called “Assembler by example”


Assambler prin exemple

Scris de Ligiu Uiorean – septembrie 2000


          Se spune ca ori ce programator ‘adevarat’ trebuie sa fi programat cel putin o data in asamblor. Pentru cine n-a facut-o, are acum ocazia sa afle cum se face.

Ce este Assambler (asamblor)?
Assamblerul este un Low Level Language, pentru a fi mai exacti, de nivelul cel mai de jos. Practic se da acces direct la resursele sistemului. In assambler nu se pot efectua decat operatii aritmetice simple (pentru care exista circuite electronice dedicate in procesor) citire si scriere pe porturi, accesarea directa a memoriei. A programa in assambler (fata de C de exemplu) este ca si cum ti-ai da toti angajatii din firma afara si te-ai apuca sa faci totul singur. Dezavantajele sunt multe, de exemplu trebuie sa-ti tii singur evienta adreselor, tipurilor de date si pointerilor. Codul este de obici absolut neportabil fiind foarte dependent de arhitectura interna a procesorului si a sistemului de operare (tot din acest motiv programarea in assambler nu se poate face numai cu ‘biblia’ – cartea tehnica – la capatai). Avantajele sunt putine dar majore: codul generat este foarte mic si ruleaza rapid (cu exceptia cazului cand a fost prost gandit), stiti in ori ce moment ce se intampla!. Alt avantaj ar fi setul redus si clar de instructiuni, care face limbajul usor de invatat. Turbo Assambler, (TASM si probabil ca si MASM – creatia Microsoft – chiar daca n-am folosit-o niciodata, din motive religioase), care este un fel de compilator, rezolva si unele probleme legate de calcularea adreselor si generarea codului, facand programarea mai umana. Assamblerul se foloseste la crearea de programe mici unde viteza de executie este critica. Majoritatea virusilor sunt scrisi in assambler.

Primul Program
Acest prim program – HelloWorld.com – va fi scris cu ajutorul programului ‘debug’ care intra in componenta dosului in mod standard si se gaseste si in Windows ( eu am W98SE si tot l-am gasit). In viitor vom folosi doar TASM, dar momentan, pentru simplitate (si pentru a lua cunostinta cu trivialitatea acestui mod de programare), folosim debug.
La linia de comanda scrieti ‘debug’. Va intampina un prompter prietenos: ‘-’(pentru help tastati ‘?’ dar momentan e suficient sa transcrieti ce-i mai jos:

	a 

Programul va arata o adresa de genul XYZK:0100 si va invita sa tastati (cu enter dupa fiecare instructiune):

	mov dx, 010c
	mov ah, 09
	int 21
	mov ax, 4c00
	int 21
	db 'Hello World!$'

Acum este momentul sa dati un enter fara nici o instructiune (linie goala). programul revine la prompterul initial. Tastati:

	r CX

vi se raspunde, politicos ‘CX 0000′ si ‘:’ tastati

	0019

(acest numar nu corespunde in cazul in care ati decis sa folositi un mesaj mai lung… acest numar este numarul care corespunde linie-i goale pe care ati dat enter minus 100: XYZK:0119 – atentie, totul este HEXA !!!) acum este momentul sa dati un nume programului dvs. si, ati ghicit, se face intr-un mod simplu si logic:

	n HelloW.com

si acum comanda de scris pe HDD:

	w

(vi se raspunde prompt: ‘Writing 00119 bytes’) iar acum sa iesim:

	q

Buuuuun ! daca totul a mers bine, veti gasi in directorul curent un fisier Hellow.com. Acest fisier, daca este executat, va prezenta mesajul Hello World!. In caz ca n-ati pus semnul $ la sfarsitul mesajului, programul va scuipa pe ecran continutul memoriei, pana intalneste un simbol $.
In numarul viitor urmeaza explicarea pas cu pas a programului!

 

Assambler prin exemple

Scris de Ligiu Uiorean – octombrie 2000


In numarul trecut am ramas la programul:

0:	mov dx, 010c
1:	mov ah, 09
2:	int 21
3:	mov ax, 4c00
4:	int 21
5:	db 'Hello World!$'

Explicarea liniilor:

linia 0: mov – mov vine de la move – a muta – (instructiunile in assambler sunt de 2-4 litere) Instructiunea mov este de forma:

<br> mov <destinatie>, <sursa><br>           Efectul instructiunii mov este de a transfera valoarea desemnata de la . Instructiunea mov poate transfera valoarea dintr-un registru intr-altul, poate scrie valori intr-un registru (ca in cazul acesta) sau poate face treburi mai exotice, de genul:

	mov ax, [bx]

pune in registrul ax valoarea aflata la adresa absoluta de memorie DS:BX
Din explicatia precedenta probabil ca aveti 2 nelamuriri:

  • ce este un registru
  • si de ce o adresa este de genul DS:BX …

Incep cu adresa:
Memoria calculatorului de arhitectura 80×86 este impartita in SEGMENTI de memorie de la 0000 la FFFF, fiecare SEGMENT fiind impartit in ADRESE RELATIVE de la 0000 la FFFF. Deci cand avem o adresa de genul 12AB:010C, prima parte (12AB) reprezinta segmentul de memorie, iar a doua (010C) adresa relativa la segment. (Sper ca na-ti chiulit in clasa a VII-a cand ati invatat ca cifrele in HEX – baza 16 – sunt de la 0 la f). Treaba cu segmentul de memorie, e mult mai complicata decat pare la prima vedere (si se complica si mai tare in programarea pe 32 de biti) dar mai lamurim pe parcurs.

Si acum ce sunt REGISTRII ?!
Registrii sunt niste ‘variabile’ ale procesorului. Procesorul lucreaza rapid cu ele. Sunt primul lucru care o sa vi se para cu adevarat limitant la calculatorul dvs. pentru ca intotdeauna o sa va doriti sa mai aveti unul. In arhitectura 80×86, registri se clasifica in felul urmator:

Registrii de uz general: AX, BX, CX, DX
Acesti registrii sunt fiecare de cate 16 biti (respectiv fiecare poate contine un numar de forma 1FAB). Acesti registrii ii puteti umple cu ce doriti dvs. dar pentru unele instructiuni ei au semnificatii speciale.
Fiecare dintre acesti registrii este impartit in doi registrii de 8 biti si se pot apela individual folosind denumirile AH, AL pentru AX; BH, BL pentru BX … etc. AH reprezentand primii 8 biti (dintr-un numar de forma 1fab reprezinta 1f), H de la high si AL ultimi doi (deci AB din 1fab), l de la low…etc.
Procesoarele de la 80386 in sus poseda niste registrii de 32 de biti notati cu EAX, EBX … acestia nu-i puteti folosi decat intr-un mod special, numit mod protejat, iar momentan n-o sa va loviti de ei…

Registrii indicatori de adresa SP si BP
Registrii de 16 biti fiecare, indica de obicei adresa stivei SP:BP. Stiva este un fel de lada de gunoi a procesorului, unde acesta isi salveaza tot felul de date curente, gunoaie. Cand ne vom lovi de ea voi explica mai amanuntit acesti doi registrii si utilitatea stivei.

Registrii de index SI si DI
Acesti registrii, de 16 bit fiecare, se folosesc pentru a explica procesorului cum sa acceseze date din alti segmenti de memorie decat cel curent. Se folosesc rar la vedere, dar sunt vitali. Registrii de segment CS, DS, ES, SS Registrii de 16 biti fiecare

  • CS- contine adresa segmentului de memorie in care se afla codul (programul)
  • DS- adresa segmentului unde se afla datele folosite de program
  • ES- adresa unde se afla date auxiliare folosite de program
  • SS- adresa segmentului curent de stiva

          Mai exista un registru: IP (instruction pointer) care indica procesorului adresa relativa unde se afla urmatoarea instructiune care va fi executata.
Cam acestia sunt registrii. Momentan trebuie sa tineti minte doar AX, BX, CX si DX. Pe parcurs ii vom folosi si pe ceilalti, dar acestia sunt cei mai utilizati.
Buuun ! Deci e clar, In linia 0 incarcam valoare 010c in registrul dx…
linia 1:
In linia a doua facem cam acelasi lucru, respectiv incarcam valoarea 09 in ah. Dece imi place mie sa incarc valori aiurea in registrii, ramane de vazut mai tarziu.
linia 2:
Aici apare o instructiune noua: int 21. Aceasta instructiune executa o intrerupere de nivel 21.

Dar ce este o intrerupere ?!
O intrerupere este un semnal care este declansat de un eveniment intern sau extern (hard sau soft). Trebuie sa priviti intreruperile ca niste functii. Aceste functii sunt apelate la diferite evenimente, de exemplu la apasarea unei taste (eveniment hard) este executata o intrerupere de nivel 9, care va lua din bufferul unde este stocat, codul tastei apasate si va actiona in consecinta. In cazul curent declansam un eveniment soft, apelant intreruperea 21. (in episodul in care voi descrie programele care raman rezidente in memorie – TSR – voi prezenta cum functioneaza sistemul de intreruperi si ce treburi dragute se pot face cu el)…

Acum, de ce apelez int 21 ?!
Intreruperea 21 (numita si DOS services) este caracteristica sistemului de operare DOS si derivatilor acestuia, recursiv compatibili. Este o “multiplex interrupt”, asta insemnand ca este o functie cu mai multe subfunctii independente, apelabile fiecare individual. In int21, sistemul DOS ne pune la dispozitie o colectie de functii care executa cele mai uzuale functii si faciliteaza interactiunea programului cu mediul exterior si cu sistemul de operare. Ori ce actiune ce se poate executa cu int 21 (ca de exemplu afisarea unui text), poate fi executata si fara int 21, apeland doar la intreruperi programate in hard (dar scrierea unui string ar fi mult mai complicata, hardul stiind sa scrie doar un caracter o data).

Structura de apelare a int 21
Pentru ca int 21 este o colectie de functii, undeva trebuie sa indicam subfunctia dorita.
La apelarea int 21, intotdeauna registrul ah contine numarul subfunctiei dorite. In ceilalti registrii trebuie stocate diferite valori, in functie de subfunctia apelata. Subfunctiile lui int 21 sunt multe, deci nu aveti cum sa le invatati la toate modul de apelare. (Eu le stiu doar pe cele mai uzuale) In mod sigur aveti nevoie de un manual unde sa le aveti descrise. Eu am unul electronic, cine si-l doreste, i-l pun la dispozitie cu cea mai mare placere, via email.
In cazul nostru dorim sa afisam un text pe ecran. In acest scop folosim int 21, subfunctia 09. Aceasta valoare (09) o incarcam in AH. Manualul ne spune ca aceasta subfunctie se asteapta sa gaseasca in DS:DX pointer la stringul care dorim sa-l afisam. Asta inseamna ca in DS:DX trebuie pusa adresa de memorie unde incepe textul. La intrarea intr-un program de tip COM, registrul DS este setat de sistem la valoare segmentului in care se afla programul. Avand in vedere ca si textul se afla in acelasi segment, nu alteram acest registru. Ramane doar DX. In acesta am decis sa pun valoarea 010C. Aceasta este adresa la care incepe textul. Cum am calculat-o ? Teoretic trebue sa calculez lungimea codului si sa adaug 100h. Nu are rost sa explic cum se face acest lucru. In numarul urmator transcriu acest cod in TASM, care ne scapa de pacostea calcularii adreselor. (Cine e totusi curios cum se face, poate sa-mi dea un email).
Ni se mai spune in documentatie ce textul afisat trebuie sa se termine in simbolul ‘$’ (care nu este afisat). Daca omiteti acest semn, va fi afisat continutul memoriei, pina cand se intalneste primul ‘$’.
Dupa toate pregatirile apelam int 21, care isi face treaba constiincios. Nu putem verifica in nici un fel ca totul a mers bine, pentru ca functia nu returneaza nici o valoare, dar e bine de stiut ca sunt si functii care returneaza coduride eroare.
Acum ne-am facut treaba si trebuie sa iesim cumva din program. Nu este indeajuns sa terminam codul. Cineva trebuie sa faca curat dupa noi in memorie si sa retransmita controlul sistemului de operare (plus sa inchida fisierele si sa faca multe altele). Treaba aceasta se poate face teoretic si manual dar ar lua multe pagini de cod degeaba. Mult mai simpla si mai facila este folosirea subfunctiei 4c din int 21. Deci, in linia 3 punem in ah valoarea 4c si in al 00. (mov ax, 4c00 este echivalent cu mov ah, 4c mov al, 00). Valoarea din al reprezinta valoarea pe care o returneaza programul sistemului de operare (in DOS teoretic ori ce program returneaza o valoare, aceasta valoare este utila in cazul scripturilor bat, cand dorim sa aflam daca un program a fost executat cu succes). In cazul nostru returnam valoarea 0 care in general inseamna OK. Apoi apelam int 21 pentru a pune capat executiei. In caz ca n-am fi executat aceasta secventa, procesorul ar fi interpretat toate datele din memorie ca si cod si probabil ca s-ar fi blocat (sau poate cine stie ?! ati fi gasit in mod miraculos a patra dimensiune). In linia 5 noi nu mai avem instructiuni ci avem textul de afisat. Procesorul ar interpreta si acest text ca si cod, dar nu ajunge niciodata la el, pentru ca exitul are loc in linia 4.
Cam asta a fost cu ‘Hello World !’
Pentru nelamuriri, lucruri care nu le-am explicat indeajuns, sau sugestii, astept mailuri. In numarul viitor transcriem acest program in TASM (pentru a ne familiariza cu acest mediu de programare) + un program nou, mai complex si mai util.

 

 

Assembler by example III

Scris de Ligiu Uiorean – noiembrie 2000


Motto:
‘Assembly is the language that before you can shoot yourself in the foot you have to invent the gun, hand, leg, and foot.’

      Daca va mai amintiti, in numarul trecut, terminasem descrierea la Hello World! Acum vom transcrie programul in sursa pentru TASM. TASM vine de la TURBO assembler si este un compilator de assembler care ne ofera diferite facilitati, de la evidenta adreselor, pana la optimizarea codului (?!). Principalele avantaje sunt posibilitatea scrierii codului intr-un editor civilizat (nu ca la arhaicul debug) si calculare automata a adreselor de memorie. Momentan vom transcrie programul si vom da o explicatie scurta la fiecare linie. TASM este mult mai complex decat il voi prezenta aici, dar il voi prezenta mai pe larg pe parcurs. Iata codul:

<br>.model tiny<br>.code <br> org 100h<br>start:<br> mov dx, offset text<br> mov ah, 09h<br> int 21h<br> mov ax, 4c00h<br> int 21h<br>text db ‘Hello World!$’<br>end start<br>

      Parca nu exista modificari majore, nu ? Totusi iata ce a aparut:

<br>.model tiny<br>

      Linia aceasta se pune la inceputul codului si explica compilatorului ca programul care intentionam noi sa-l facem este un program care isi pastreaza toate datele (codul, stack, buffer, date suplimentare) intr-un singur segment de memorie. Toate programele com sunt model tiny (mai exista modelele small, medium, large, huge, dar acestea sunt pentru fisiere exe). Eu personal sunt de parere ca ceea ce nu incape intr-un tiny n-ar trebui programat in assembler, deci toate programele care le vom folosi ca exemple vor fi model tiny.

<br>.code<br>

      Aceasta linie explica compilatorului ca ceea ce va gasi el mai jos este codul programului si va trebui sa-l trateze ca atare.

<br>org 100h<br>

      Aceasta linie explica compilatorului ca noi vom intentiona sa compilam respectivul program ca un fisier com. Fisierele com sunt incarcate de sistemul de operare direct in memorie (asta inseamna ca un fisier com contine exact imaginea programului din memorie in momentul executarii acestuia) intr-un segment liber de memorie, incepand cu adresa 0100h. Deci noi va trebui sa spunem compilatorului ca prima instructiune are adresa 0100h (si nu 0). Compilatorul necesita aceasta informatie pentru a calcula adresele la care se afla datele si adresele de salt absolut.

<br>start… end start<br>

      Indica inceputul si sfarsitul programului. Intre start si end start (sau ori ce alt label) trebuie sa se afle toate functiile, subfunctiile, codul, date…etc a programului.
Apoi urmeaza codul. In cod, sigurul lucru ciudat exte acel mov dx, offset text. Daca ne uitam mai jos, vedem ca textul nostru a primit un label (text). Deci practic ceea ce am zis noi se traduce in: pune in DX adresa (offset se traduce mai degraba ca ‘la ce distanta de mine’) textului text. Deci, in loc sa calculam noi unde se afla respectivul text (cum am facut pana acum) compilatorul face asta pentru noi.
Acum cred ca am sa abandonez Hello World, pentru ca deja am invatat destule pe spinarea lui. Urmatorul program care il propun va face urmatoarele: va primi in linia de comanda 1 argument (o parola). Apoi va citi datele de la tastatura (fisier) si le va encripta folosind parola si va scrie pe ecran (fisier) rezultatul (vom privi tastatura ca un fisier si ecranul tot ca un fisier pentru ca intrarea si iesirea pot fi redirectionate).
La acest program apar mai multe probleme. De exemplu, cum aflam ce a primit programul in linia de comanda:
Poate va ganditi ca exista o functie speciala pentruasa cea… s-ar putea, dar eu nu o cunosc. Dar ceea ce cunosc este ca sistemul de operare creaza inainte sa execute un program un Program Segment Prefix lung de 100h bytes (h de la HEXA). N-am sa va spun ce contine acest PSP pentru ca gasesti aceste lucruri in carti de scoala. Ce am sa va spun este ca in byteul 80h se afla lungimea argumentului primit de program (normal, in hexa) si incepand cu byteul 81h (pana la 100h)se afla chiar argumentul. Deci ceea ce trebuie sa faca programul nostru pentru a afla argumentul cu care a fost lansat, este sa citeasca byteul 80h si sa citeasca atatea caractere din memorie, incepand cu 81h.
O alta problema ar fi, cum se citesc fisierele (tastatura):click aici
Ultima problema ar fi metoda de encryptare a fisierului. Sa nu va imaginat ca acum o sa aflati algoritmul blowfish sau ecryptarea PGP… Am decis sa folosesc o metoda ‘didactica’, care are si avantajul ca este reversibila folosind acelasi program (respecitv mai rulati programul o data pe fisierul criptat, cu aceeasi cheie si obtineti fisierul original). Aceasta metoda minune este metoda xor (‘SAU EXCLUSIV’) si se invata in clasa a 9-a (?!) capitolul logica matematica. Voi indica un tabel cu actiunea comenzii xor. (De mentionat ca xor acctioneaza la nivel de bit, deci tabelul se refera la biti):

<br>sursa1 | 1 | 1 | 0 | 0 | <br>sursa2 | 0 | 1 | 0 | 1 |<br>————————–<br>rezulta| 1 | 0 | 0 | 1 |<br>       Instructiunea XOR este definita asa: <br>XOR dest,src<br>exclusive OR (toggle dest bits which are 1s in src)<br>dest<-(dest ^ src)<br>

      Cam asta ar fi totul. Acum codul sursa (pentru cei care chiar vor sa invete, sa incerce prima data sa faca o schema logica a programului si sa incerce sa o transcrie in assembler si unde nu stiu sa se uite la programul meu):

Mentiune speciala:

      Am un stil destul de dezordonat de programare si acest program nu ar trebui considerat etalon de nici un fel – click aici pentru sursa.
Cam asta ar fi un cod functional. Codul se compileaza (asambleaza) cu comanda ‘tasm cod.asm’ care va genera un fisier obj iar apoi acest fisier se transforma in program com prin comanda ‘tlink cod.obj /t’.
Pentru a folosi programul pe fisiere, il vom apela asa:

<br>program.com parola <infile >outfile<br>

      Linia <infile >outfile redirectioneaza intrarea de la fisierul infile iar iesirea pe fisierul outfile. Asfel vom obtine un fisier numit outfile, criptat cu ‘parola’. Pentru a decripta fisierul rulam incodata criptarea cu aceeasi parola, dar inlocuind infile cu numele fisierului criptat.
Momentan programul este functional, dar nu icercati sa criptati cu el documente ultrasecrete pentru ca poate fi spart in 30 de minute. Oricum, e bun de ascuns pozele alea de ochii parintilor :)
Chiar daca majoritatea codului a fost explicata deja, apar unele elemente noi. Acestea vor fi dezbatute in numarul viitor, unde vom si extinde aria de functionalitate a programului.
In memoria celui care a fost Ioan Uiorean, bunicul meu.


Assembler by example IV

Scris de Ligiu Uiorean – decembrie 2000


 

   În numărul trecut prezentasem acel program de encryptare/decryptare. Dacă aţi înţeles cum funcţionează, totul este ok. Oricum, eu ţin neapărat să prezint elementele noi care apar în program. S-o luăm cu începutul…
*** In lina 20 apare instrucţiunea JLE (Jump if less or equal). Procesoarele x86 au 8 biţi în memorie (un caracter) care sunt folosiţi ca flag-uri. Respectiv, în momentul executării unei comparaţii (şi alte instrucţiuni), aceşti biţi sunt setaţi sau nu (TRUE sau FALSE) in funcţie de rezultatul comparaţiei. Apoi, dacă executăm o instrucţiune ca cea de mai sus (JLE), procesorul analizează biţii de flag-uri şi acţionează în consecinţă. O instrucţiune gen JLE nu trebuie să succeadă imediat o instrucţiune CMP. Între ele pot fi oricâte instrucţiuni, cu condiţia să nu afecteze starea flag-urilor.
Instrucţiunile de genul JC (Jump if Clear) pe care le întâlniţi după apelarea int 21h, verifică dacă a fost setat flag-ul de eroare. Ele au funcţia de a intercepta eventual erori în procesul de execuţie (un fel de trow/catch la C).
*** În linia 43 apare instrucţiunea PUSH. Această instrucţiune salvează în stiva (despre care v-am vorbit in nr.2) conţinutul registrului care-l primeşte ca argument. Acesta instrucţiune se foloseşte împreună cu instrucţiunea POP care aduce din memorie ** ultima valoare salvata ** Deci instrucţiunile de genul: push CX pop AX au aceeaşi valoare ca si mov AX, CX.
Stiva este un lucru foarte folositor dar se poate si umple. În general e de 255 de caractere dar depinde de sistem. Oricum, a umple stiva (sau a da pop când nu e nimic salvat în ea) e o metodă sigură de a bloca programul.
În numărul acesta voi face o excepţie şi nu voi prezenta un program complet (printre altele şi pentru a va stimula să vă puneţi mintea la contribuţie pentru a va da seama dacă aveţi undeva neclarităţi). Oricum voi prezenta problemele principale legate de acest program. Iată la ce program m-am gândit: poate aţi observat ca în primul program (HelloWorld), mesajul era vizibil oricărui utilizator care s-ar uita la conţinutul fişierului binar. Ce facem dacă vrem sa creăm un program care să prezinte un mesaj care nu este vizibil la vizualizarea codului ? Sau mai bine, avem deja un program, care dorim să-l encryptam şi se va autodecrypta în momentul execuţiei ? În acest mod am şi proteja programul la eventuale încercări de dezasamblare (hackuire/spargere)…(nota: metoda prezentata aici este doar un exemplu…pentru o protecţie reala, ar trebui îmbunătăţit algoritmul de encryptare. Oricum, e mai bun decât nimic). Deci, programul se va manifesta cam aşa: Va primi la intrare un fişier COM. Îl va encrypta, şi ii va adăuga un cod de decryptare. Ceea ce obţinem este un program COM care va porni ceva mai greu dar va fi encryptat.
Cum facem ca acest program să se autodecrypteze, cum adăugăm cod ?
Codul de decryptare îl adăugăm pur şi simplu la sfârşitul programului encryptat. Pentru ca acest cod să fie executat înaintea oricărui altuia, trebuie să plasăm o instrucţiune jmp chiar la începutul codului, care va indica spre codul de decodare. ştiind că o instrucţiune jmp (jmp la adresa relativă şi nu absolută) ocupa trei bytes (e problema de experienţă), vom copia primi trei bytes originali ai programului la sfârşit şi îi vom înlocui cu jmp-ul de care avem nevoie. Treaba de a muta iarăşi bytesii originali la locul lor, revine codului de decodare. După ce acest lucru a fost făcut şi programul a fost decodat, nu trebuie decât sa executam un jmp la 100h (respectiv, unde începe programul com). Structura programului este simplă şi foloseşte elemente deja cunoscute. Funcţiile speciale de care aveţi nevoie sunt:
Clic aici pentru a vedea sursa.
Nu voi prezenta codul în numărul acesta, în schimb, în numărul viitor va apărea acest cod, dar extins. Anume: voi prezenta un virus. Singurul lucru care îl face virusul în plus fata de programul prezentat aici este faptul ca se propaga singur, fără ajutorul omului. Mai exact, îşi găseşte singur fişierele la care sa se ataşeze, fără să trebuiască să-i fie furnizate în linia de comandă.

Structura şi funcţionarea unui virus

Scris de Ligiu Uiorean – ianuarie 2001


 

      În acest număr voi prezenta structura şi funcţionarea unui virus (in acest număr şi in numărul viitor acesta şi următorul). În primul rând voi prezenta codul virusului (asm). Aici am o mare dorinţă către voi: nu vă apucaţi să compilaţi virusul, doar ca să-i arătaţi vecinului de sus că sunteţi mai deştept ca el. Oricum virusul datează de prin 1993 şi sper că este detectat de toţi antiviruşii posibili. Deci, codul, ca să avem despre ce vorbi! Pentru a vedea codul clic aici.
Codul este in totalitate comentat, deci n-ar trebui să fie o problemă să descifraţi (cu biblia aproape) ceea ce se întâmplă. oricum, iată câteva explicaţii:
Dacă ne uităm la începutul codului, vom observa că programul îşi face o intrare destul de ciudată, cu un CALL (apel la subrutină) la linia următoare. Acest lucru este necesar din următorul motiv: mare problemă a viruşilor, este faptul că infectează fişiere de mărime variabilă. Din acest motiv, nu ştim niciodată o instrucţiune sau un buffer la ce adresă se află. Fiindcă avem nevoie de adresa la care începe virusul, executăm acest call. Apoi, trebuie să ştim că instrucţiunea call salvează în stack, adresa de la care sa executat call-ul. Următoarele instrucţiuni au unicul scop de a pune valoarea adresei in bp. Mai departe acest bp va fi folosit ca “delta” în ori ce apel la adresă absolută, respectiv orice adresă va fi indexată cu valoarea lui bp, practic lungimea programului infectat. În acest sens nu vom mai folosi instrucţiunea mov pentru a pune adresa absolută într-un registru şi apoi să-i adăugăm bp, ci vom folosi instrucţiunea LEA, care face cam acelaşi lucru.
Apoi urmează o serie de instrucţiuni care vor decripta programul. Virusul se ataşează la fişier în formă ecryptată, dar pentru a fi executat, trebuie întâi decryptat. Problema care se pune aici, este că la prima rulare (în momentul când virusul este executat prima dată, fără să fie ataşat la un program) el se află în stare necryptată. De aceea el îşi compara primul byte al programului cu ceea ce ar trebui să fie în stare necryptată. În caz că află că este necryptat, sare peste procedura de decryptare. Procedura de cryptare este simplă, prin negarea (înlocuieşte 1 cu 0 şi invers) biţilor.
Apoi urmează câteva secvenţe de cod care nu sunt executate niciodată (sau n-au nici un efect), unicul lor scop fiind de a induce în eroare antiviruşii care folosesc metoda heuristică de căutare a viruşilor.
Următorul pas este să se verifice dacă virusul este activ în memorie. În acest scop, virusul apelează o subfuncţie a int 21, inexistentă în mod normal, dar care virusul si-o creează în momentul în care se încarcă în memorie (vom vedea mai jos). În caz că primeşte răspunsul aşteptat, decide că este deja activ, şi nu se mai încarcă o dată în memorie. Apoi el restaurează primii 3 bytes ai programului infectat şi îi transferă acestuia controlul.
În caz că acesta constată că nu este deja activ în memorie, începe procesul lung şi complicat al transferului în memorie. O dată activ în memorie, virusul va infecta ori ce program rulat, care nu a fost infectat deja. Despre acest aspect vom discuta în numărul viitor.
În acest director voi pune şi altele, când va fi nevoie.

Structura şi funcţionarea
unui virus II

Scris de Ligiu Uiorean – februarie 2001


 

      A rămas în numărul trecut la problema încărcării virusului în memorie. Folosind subfuncţia 35h a int21h, virusul află vectorul întreruperii 21h. Clic aici.
Trebuie să ştiţi că memoria unui PC începe cu o zonă numită tabelă de întreruperi, practic o zonă în care există o listă de adrese. Aceste adrese reprezintă adresa codului unde este tratată acea întrerupere. Asta înseamnă că, în momentul când sistemul primeşte o întrerupere de nivel 0 (sau 1, 2, etc) acesta execută practic un jump la prima (sau a doua, etc.) adresa din tabela de întreruperi şi de acolo execută un jump la adresa indicată, numită vector de întrerupere. Deci programul nostru doreşte să afle unde se află codul care tratează întreruperea 21h. După ce află, salvează această adresă într-o zonă special rezervată la sfârşitul codului.
Codul care urmează este greu de explicat şi oricum ar fi fără sens să explic cum face. Ceea ce face este să afle dimensiunile programului folosind MCB, un prefix al programului, creat de sistemul de operare, în momentul lansării acestuia în execuţie. Apoi, folosind subfuncţia 48h a int 21h, virusul îşi alocă memoria necesară pentru a rămâne rezident. Clic aici.
După ce a primit spaţiu în memorie, virusul se copiază în acel spaţiu, folosind instrucţiunile REP şi MOVSB. Cele două instrucţiuni fac parte din categoria String Operation Instructions şi au următoarele definiţii: Clic aici.
Acum, nu avem decât să modificăm tabela de întreruperi, astfel încât vectorul de la int21h să indice către codul nostru. În acest scop folosim subf 25h a int 21h: Clic aici.
Din acest moment, ori ce int21h care este executată pe sistem, va trece prima dată prin codul virusului nostru (nu tot codul ci doar secţiunea care este în memorie, şi anume de la “Handler…”, pentru că aşa am setat vectorul). Acum virusul restaurează primii 3 bytes ai programului original, şi îi transferă acestuia controlul.
Secţiunea de cod care urmează acum ar putea fi considerată un alt program. Acesta este codul care rămâne rezident în memorie şi care se ocupă de infectare şi altele. Nu trebuie să scăpăm din vedere faptul că acest cod este executat de fiecare dată când un program apelează int21h. Deci să începem.
În momentul în care este executat in21h, pe noi (virusul) ne interesează ce subfuncţie este apelată. În acest scop executăm o serie de comparaţii. Subfuncţiile care ne interesează sunt:

&amp;amp;lt;br&amp;amp;gt; 4bh &#8211; execut&amp;amp;amp;amp;amp;#259 program&amp;amp;lt;br&amp;amp;gt; CAh &#8211; resident check&amp;amp;lt;br&amp;amp;gt;

      Celelalte două subfuncţii se referă la schimbarea directoarelor şi discurilor. Codul nu face nimic în momentul în care le întâlneşte, ele există doar pentru un upgrade viitor al virusului.
Subf. CAh: Dacă este apelată aceasta subfuncţie, virusul ştie că o altă copie a sa de pe sistem, încearcă tocmai să afle dacă virusul este rezident sau nu. De aceea, virusul rezident răspunde punând în bh, Ca. Apoi este executat mai departe vechiul cod al int21 (ca şi în cazul în care nici una dintre subfuncţii nu este interceptată). Subf. 4bh.
Dacă se apelează această funcţie, înseamnă că sistemul de operare sau un alt program, doreşte să execute un program. Noi dorim să interceptăm acest program şi să-l infectăm. În primul rând salvăm toţi regiştrii cu care a fost apelată int21h, ca să-i putem restaura în momentul execuţiei vechiului int21h.
În acest moment avem la adresa DS:DX un string care indică un program care trebuie să fie executat. Noi vom încerca să infectăm acest program înainte să fie executat. Întâi, cu subf 43h, citim atributele fişierului (readonly, etc) şi le modificăm pentru a putea accesa normal fişierul. Vechile atribute sunt salvate pentru a fi restaurate după infectare.
Dar prima dată trebuie să aflăm dacă fişierul nu a fost infectat deja. Virusul foloseşte o metodă empirică: Află atributele fişierului (time-dat) cu subf. 57h. Dacă secundele de la atributele fişierului (respectiv data şi ora la care a fost modificat) sunt egale cu 58, atunci virusul trage concluzia ca fişierul este deja infectat şi îl lasă în pace. Dacă nu, urmează procesul de infectare. Fişierul este deschis cu subfn 3dh, pentru citire-scriere.
Acum începe infectarea. Mutăm pointerul de citire la sfârşit, folosind 42h. Astfel aflăm dimensiunile programului de infectat. Această dimensiune ne este necesară pentru a calcula jmp-ul care trebuie scris la începutul fişierului. Apoi mutăm pointerul înapoi la început şi citim primii 3 bytes, cei care trebuie înlocuiţi cu jmp-ul. Un lucru am uitat să facem: nu ştim dacă fişierul pe care lucrăm e com sau exe… Pentru că noi suntem un virus care infectează doar com-uri. În acest scop, comparăm octeţii citiţi cu MZ. Dacă găsim MZ, atunci fişierul este un exe, nu avem ce-i face, restaurăm atributele şi ieşim. (Notă istorică: toate fişierele EXE încep cu MZ. MZ vine de la Mark Zbigovski, unul dintre designerii DOS). Dacă totuşi fişierul nostru este un com, calculăm distanţa la care trebuie făcut jmp-ul şi îl scriem la începutul fişierului. Apoi luăm codul virusului, aflat în memorie, şi jonglând cu el, îl copiem într-un alt spaţiu şi îl encryptăm, tot în afară de primii bytes de decryptare. Observăm că procedura de encyptare este complementară cu cea de decryptare de la începutul virusului. Acum avem de două ori virusul în memorie: o copie care rulează şi o copie care este ecryptată. Nu ne mai rămâne decât să ataşăm această a doua copie la programul original. Acum fişierul este infectat cu succes. Nu ne mai rămâne decât să restaurăm atributele originale ale fişierului, regiştrii cu care a fost apelată int21h şi să transferăm controlul procedurii vechi de tratare a întreruperii.
Cam acesta este un virus. După cum vedeţi, este un program complex, care cere o cunoaştere detaliată a sistemului de operare şi a assembler-ului. Majoritatea viruşilor sunt exemple de programare.
În numărul viitor părăsim tărâmul codului ascuns şi ne vom ocupa un pic de grafica în Assembler.

 

Fonturi in MSDOS

Scris de Ligiu Uiorean – martie 2001


 

      Multa vreme am crezut ca fonturile (si toate necazurile care deriva din ele) ar fi apanajul sistemelor grafice. Dar, surpriza, si in DOS se pot schimba fonturile. Mai jos voi prezenta metoda:
Folosind întreruperea 10h (video services) putem face multe efecte speciale. Una dintre subfunctiile acestei întreruperi este 1110H (=AX). Iata o explicatie mai amanuntita a metodei de apelare:
BH înaltimea fiecarui caracter (biti/ caracter ddefinit)
BL Blocul de fonturi care trebuie incarcat (EGA: 0-3; VGA: 0-7)
CX numarul de caractere care vor fi redefinite
DX Codul ASCII al primului caracter redefinit în tabelul de la ES:BP
ES:BP adresa definitiei fonturilor
Explicatii suplimentare:
BH înaltimea caracterului. Latimea caracterului este întotdeauna de 8 biti. BL pot fi definite pâna la 8 seturi de caractere. Având în vedere ca dorim sa modificam fontul activ, BL va fi 0. La adresa ES:BP vom pune un tabel cu descrierea fonturilor. Tabelul ar trebui sa aiba lungimea de BH*CX baiti.
Definirea fonturilor se face in felul urmator:

&amp;amp;lt;br&amp;amp;gt;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-&amp;amp;lt;br&amp;amp;gt;+76543210+&amp;amp;lt;br&amp;amp;gt;¦ ¦=00000000=00&amp;amp;lt;br&amp;amp;gt;¦ ¦=00000000=00&amp;amp;lt;br&amp;amp;gt;¦ ___ ¦=00111000=38&amp;amp;lt;br&amp;amp;gt;¦ __ __ ¦=01101100=6C&amp;amp;lt;br&amp;amp;gt;¦__ __ ¦=11000110=C6&amp;amp;lt;br&amp;amp;gt;¦__ __ ¦=11000110=C6&amp;amp;lt;br&amp;amp;gt;¦__ __ ¦=11000110=C6&amp;amp;lt;br&amp;amp;gt;¦_______ ¦=11111110=FE&amp;amp;lt;br&amp;amp;gt;¦__ __ ¦=11000110=C6&amp;amp;lt;br&amp;amp;gt;¦__ __ ¦=11000110=C6&amp;amp;lt;br&amp;amp;gt;¦__ __ ¦=11000110=C6&amp;amp;lt;br&amp;amp;gt;+__ __ ¦=11000110=C6&amp;amp;lt;br&amp;amp;gt;¦ ¦=00000000=00&amp;amp;lt;br&amp;amp;gt;¦ ¦=00000000=00&amp;amp;lt;br&amp;amp;gt;¦ ¦=00000000=00&amp;amp;lt;br&amp;amp;gt;¦ ¦=00000000=00&amp;amp;lt;br&amp;amp;gt;+&#8212;&#8212;&#8211;+ &amp;amp;lt;br&amp;amp;gt;       (caracterul A) devine urmatorul sir de baiti: 00,00,38,6C,C6,C6,C6,FE,C6,C6,C6,C6,00,00,00,00
Ideea în spatele acestei prezentari ar putea parea stupida. Dar are si unele utilitati: de exemplu Norton Utilities (4 DOS) foloseste cu succes o metoda de afisare semi grafica, si anume, redefinind unele caractere speciale creaza itemuri ca checkboxuri, dropdownlist etc. În numarul de astazi intentionez sa prezint demonstrativ un program care sa modifice fontul unui caracter cu un font care sa reprezinte o sageata de mouse. Astfel, când este initializat mouseul cu acel caracter, veti avea un pointer adevarat…
Iata cam cum va arata pointerul nostru: &amp;amp;lt;br&amp;amp;gt;00000000 == 00&amp;amp;lt;br&amp;amp;gt;00000000 == 00&amp;amp;lt;br&amp;amp;gt;01000000 == 40&amp;amp;lt;br&amp;amp;gt;01100000 == 60&amp;amp;lt;br&amp;amp;gt;01110000 == 70&amp;amp;lt;br&amp;amp;gt;01111000 == 78&amp;amp;lt;br&amp;amp;gt;01111100 == 7C&amp;amp;lt;br&amp;amp;gt;01111110 == 7E&amp;amp;lt;br&amp;amp;gt;00011100 == 1C&amp;amp;lt;br&amp;amp;gt;00011100 == 1C&amp;amp;lt;br&amp;amp;gt;00001110 == 0E&amp;amp;lt;br&amp;amp;gt;00001110 == 0E&amp;amp;lt;br&amp;amp;gt;00000000 == 00&amp;amp;lt;br&amp;amp;gt;00000000 == 00&amp;amp;lt;br&amp;amp;gt;00000000 == 00&amp;amp;lt;br&amp;amp;gt;00000000 == 00&amp;amp;lt;br&amp;amp;gt;       Deci: 00, 00, 40, 60, 70, 78, 7C, 7E, 1C, 1C, 0E, 0E, 00, 00, 00, 00
Acum nu mai trebuie decât sa stim codul ASCII al caracterului care dorim sa-l modificam, care este 21 (!), si ne putem apuca de program.
Programul este extrem de simplu, dar pentru a modifica întreg seul de caractere, trebuie depusa un pic de munca. Ca demonstratie aveti atasat un program care face asta. Codul.

 

 
Assembler By Example 8

Scris de Ligiu Uiorean – martie 2001


 

      Acesta fiind ultimul numãr din Assembler by Example, m-am gândit sã public ceva special. Astfel, fiind la capitolul graficã, am fãcut un program care afiseazã un fisier PCX pe ecran (în DOS mode). Programul este oarecum limitat, fiind capabil sã afiseze numai fisiere de 320×200 pixeli, ca *acesta*. Codul este în totalitate comentat, deci ar trebui sã îl întelegeti fãrã alte explicatii. Totusi aveti *AICI* o descriere a formatului PCX (în englezã, copyrighted) si o descriere pe scurt a firului programului.
In primul rând verificam dacã fisierul PCX existã.
Apoi îl deschidem. Presupunând cã totul a decurs normal, procedãm la decodarea lui. Pentru a intelege procesul de decodare al fisierului, trebuie sã studiati cu atentie descrierea formatului PCX. Este mult mai simplu decât vã imaginati si este un exercitiu bun de programare (desi formatul este cam “nefericit”). Pentru a comunica cu dispozitivul de afisare folosim doua metode: copiem biti in memoria video (bit blitting, o metoda asa de veche incât la HC nu se mai folosea, fiind consideratã lentã si învechitã, dar s-a revenit la ea, e metoda preferatã la animatie 3d) si a doua metodã este de a face out pe portul vga (3c8h) pentru a seta paleta de culori. (In rutina de setare a paletei de culori, putem introduce tot felul de functii de modificare a bitiilor, astfel vom obtine efecte ciudate, unele interesante. Dacã facem aceste transformãri dinamic, putem obtine o imagine cu culori schimbãtoare…) Apoi schimbãm din nou modul video in text 80×25 (standardul dos) si iesim.
Cam acesta este tot programul. Acest program demonstreazã puterea assembler-ului si tot odatã poate constitui baza pentru alte aplicatii grafice sub dos (?!). Eu personal m-am distrat copios scriindu-l si apoi modificându-l. Printre altele am reusit sã fac un mic player video sub dos, folosind repetat rutina de afisare. Rutina este însã asa de rapidã (lucru bun!) încât a trebuit sã introduc temporizãri între cadre (pe un Celeron la 300).
Cam atât a fost ce am avut eu sã vã spun despre assembler (sub DOS). Nu este nici pe departe totul, n-am discutat programarea pe 32 de biti/sub windows. Personal cred cã assemblerul n-ar trebui folosit la “programare” sub windows ci doar la debuging. Sper sa pot realiza si o descriere a programarii sub windows, dare asta cel mai devreme peste 4 luni, dupa bac…
Assemblerul rãmâne in continuare metoda preferatã în caz cã se cere executare rapidã si consum mic de resurse. Îmi cer scuze deca am lasat lucruri “in aer”, mailu meu ramâne deschis pentru ori ce intrebare la care sper sa va pot raspunde…

 

No comments yet.