Search the Community
Showing results for tags 'assembler'.
-
In acest tutorial voi descrie caracteristicile de baza a limbajului de programare assembly.Vom avea un simplu exemplu de baza si anume stamparea unei variabile in stdout, structura instructiilor , definirea sectiunilor de baza , memoria, registrii de baza care pot fi intalniti in arhitectura procesoarelor de 32 biti Intel , nu in ultimul rand voi converti un exemplu de cod assembly scris pentru arhitectura intel 32 biti in 64 biti.Voi folosi cei mai simpli termeni pentru a descrie structura instructiilor si a unui registru.Pentru a nu duce in erroare un user ma voi folosi doar de registrii indispensabili.Assembly este un limbaj de programare Low Level.Spre deosebirea celorlalte limbaje de programare assembly necesita o traducere simpla in Machine Code deoarece fiecare cuvant sau mai bine spus instructie in assembly vine transformata intro instructie macchine code.Intructiile in machine code sunt intructii binare care pot fi interpretate foarte simplu de catre un computer dar putin mai greu de catre o persoana.De aceea pentru fiecare instructie binara a fost creata o instructie in assembly care poate fi denumita human readable deoarece creierului uman este mai simplu sa memoreze un anumit alias decat o instructie in 0 si 1.Fiecare procesor are un set de instructii assembly care difera de la o arhitectura la alta.Pentru a traduce instructiile assembly in operation code (macchine instruction code) vine folosit un traducator chemat assembler.Acesta este un software care interpreteaza instructiile scrise in assembly si le traduce in macchine code.Ei bine exista mai multe tipuri de assembler si fiecare are o anumita sintaxa.Instructiile scrise de catre un anumit assembler nu pot fi traduse in opcode de catre alt tip de assembler deoarece instructiile sunt diverse.Cele mai cunoscute tipuri de assembler sunt urmatoarele: NASM Windows, Linux, Mac OS X, DOS, OS/2 MASM Windows, DOS, OS/2 TASM Windows, DOS Yasm Windows, DOS, Linux, Unix-like HLA Windows, Linux, FreeBSD, Mac OS X GAS Unix-like, Windows, DOS, OS/2 Evident lista poate fi mult mai lunga , dar ma limitez aici.Ceea ce este important sa intelegeti este faptul ca fiecare are o sintaxa particulara pentru a scrie intructiile si ca exista diverse moduri de a programa in assembly.Pentru acest tutorial si urmatoarele exemple eu voi folosi GAS ca assembler.Ce este important sa cunoasteti este ca acest tip de assembler permite scrierea instructiilor assembly in 2 moduri.De default este folosita sintaxa AT&T dar poate fi folosita si sintaxa Intel.Ambele sintaxe pot traduce instructiile in opcode compatibil cu procesoarele Intel doar ca difera modul in care sunt scrise instructiile. Intex Syntax instructie dest,source AT&T Syntax instructie source,dest In urmatoarele exemple voi folosi doar AT&T Syntax.Assembly nu este un limbaj de programare cross platform deci anumite instructii vor putea fi executatea doar pe anumite platforme si nu pe altele.Exemplele sunt create pentru sistemele Linux 32 Biti Intel.In final voi traduce exemplul din tutorial si pentru arhitectura Intel 64 biti Linux.De obicei limbajele de programare se invata intrun anumit mod .Vin invatate structurele de date, instructiile , structurele de control , functiile si cu putina practica o persoana poate incepe sa programeze.Ei bine in assembly este putin diverssi pentru a obtine o anumita functie vor trebui scrise mai multe linii de cod.Pentru a cunoaste assembly necesita cunostinta sistemului operativ, a structurii procesorului si cum vine alocata memoria in ambientul respectiv.Acest tutorial este basic deoarece am vrut sa merg pe principul keep it simple .Vom incepe cu descrierea arhitecturii a unui computer.Avem un procesor , 3 tipuri de memorie (cache memory, main memory , secondary memory) ,Input/Output Devices (tastiera spre exemplu).Procesorul acceseaza date in memorie in urmatorul fel:Va cauta in mod direct existenta datelor in cache memory , main memory(ram) si in mod indirect date in secondary memory (hard disk).In cazul in care datele nu se gasesc in casche memory sau in main memory vor fi incarcate din secondary memory in main memory dupa care procesorul va avea access direct la ele.Conceptul programarii in assembly este urmatorul: Procesorul preia date din memorie executa operatii aritmetice asupra datelor si stocheaza rezultatul inapoi in memorie sau il stampeaza in stdout (monitorul user/ului).La randul lor datele pot fi preluate din stdin (tastiera) , pot fi procesate si stocate in memorie sau pe ecranul user-ului.In momentul in care datele vin preluate de catre cpu din main memory(ram) pentru a executa anumite operatii asupra lor vin stocate intrun alt spatiu de memorie, acest spatiu vine chemat registu.Un cpu detine mai multi registrii pentru a stocain mod temporar datele atunci cand se fac operatii asupra lor dupa care datele vin puse la locul de unde au fost luate .Ceea ce probabil il pune pe user intro stare de umra este faptul ca procesorul poate detine multi registrii.Nu e nevoie sa cunosti fiecare registru daca nu il folosesti atunci cand programezi in assembly .In acest articol nu voi descrie toti registrii deoarece voi crea o confuzie.Ceea ce este important sa cunoasteti este faptul ca atunci cand o valoare este preluata din memorie trebuie stocata intrun anumit loc temporar unde pentru a face operatii asupra ei dupa care rezultatul va fi stocat inapoi in memorie iar registrul temporar va prelua urmatoarea valoare. Ca in fiecare limbaj de programare si in assembly programul vine impartit in anumite sectii.Exista o sectie unde pot fi declarate anumite tipuri de date.Spre exemplu exista o sectie chemata .data , aici pot fi declarate tipurile de date cu care se va lucra, Aceasta sectie poate fi paragonata cu declararea variabilelor in alte limbaje de programare. O alta sectie este .bss , in aceasta sectie se pot declara alte tipuri de date a caror valoare spre exemplu nu este cunoscuna initial.O alta sectie este .text , aici pot fi scrise instructiile care vor procesa datele declarate si procesate in sectiile .data .bss.O alta sectie importanta este .globl , aici pot fi chemate functii/librari externe care au fost deja create in precedenta.Vom avea si o sectie _start de unde instructiile vor incepe sa fie executate.Aceasta sectie poate fi paragonata cu functia main() in c. Pentru a scrie un simplu program in assembly se vor crea sectiile respective, se vor umple cu date dupa care se vor chema instructii asupra datelor declarate.In urmatoarele exemple vom vedea cum se pot declara date , cum vin declarate instructiile? , ce se intampla atunci cand instructiile vin traduse in opcode?, cum vin incarcate datele din memorie intrun registru?, cum pot fi chemate functii externe asupra datelor? Pentru a putea face o operatie asupra unei valori , va trebui declarata valoarea respoectiva , acelasi lucru si pentru oricare alt tip de date.Initial vom construi o sectie .data unde vom stoca un nickname. Declar o sectie data in memorie .data Declar o eticheta pentru tipul de date pe care il voi crea NickName: Declar tipul de date pe care vreau sa le stochez in memorie .ascii "pyth0n3\n: Asadar am creat o sectie, o eticheta , un tip de date Eticheta va avea o adresa in memoria RAM , la adresa respectiva se vor gasi datele pe care le-am declarat Etichetei ii vine atribuita o adresa de memorie NickName data variable address 0x8049098 La adresa 0x8049098 vom avea datele stocate si anume pyth0n3\n NickName data variable content in excaped hex 0x70 0x79 0x74 0x68 0x30 0x6e 0x33 0x0a p y t h 0 n 3 \n Evident o data ce instructia .ascii "pyth0n3\n" va fi tradusa in opcode de catre assembler vom avea urmatorul cod Nick Name data variable content in binary giant, 8 bytes 0000101000110011011011100011000001101000011101000111100101110000 Important este sa intelegeti faptul ca acest cod se afla la o anumita adresa si anume la 0x8049098 ,am tradus adresa in hex dar pentru procesor va fi tot o valoare in 0 si 1.Dupa ce am declarat datele ramane sa decidem ce vrem sa facem cu ele .Vom stampa pe ecran ceea ce am stocat la adresa 0x8049098.Evident pentru a face acest lucru exista mai multe alternative.In Linux ne putem folosi de syscall.Trebuie individuate functiile de care avem nevoie pentru a stampa datele + functia pentru a inchide programul.Fiecare syscall are un anumit id numeric .In fiecare sistem linux lista se poate gasi in /usr/include/asm .In fisierul unistd_32.h se vor gasi functiile pentru procesoarele 32 biti iar in unistd_64.h pentru procesoarele 64 biti.Vom folosi 2 syscall diverse 1 pentru a stampa datele declarate 2 pentru a iesi din program. Pentru a stampa datele vom folosi #define __NR_write care are ca id numarul 4 Pentru a iesi din program vom folosi #define __NR_exit care are ca id numarul 1 Datele care vin procesate trebuie incarcate in registrii asadar vom folosi 4 registrii a procesorului pentru a stoca temporar date care vor fi executate.Asadar vom folosi EAX,EBX,ECX,EDX care pot fi paragonate cu 4 variabile temporare unde vom stoca date care trebuie executate. In momentul in care nu am specificat nici o instructie registrii momentan nu au nici o valoare eax 0x0 0 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 Probabil va intrebati in care ordine pot fi introduse datele in registrii spre exemplu care este primul registru? Ei bine ordinea este urmatoarea EAX EBX ECX EDX.Vom muta valoarea functiei write in primul registru Pentru a scrie instructiile trebuie creata o sectie .text , avand in vedere oricum faptul ca vom chema si functii externe syscall va trebui specificata si o sectie pentru external routines si anume .globl _start dupa care vom crea sectia _start unde vom incepe sa executam instructiile pe care le vom scrie Asadar vom incepe cu prima instructie .text .globl _start _start movl $4, %eax Dupa executarea acestei instructii registrul EAX va avea valoarea 4 eax 0x4 4 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 Functia write are nevoie de cateva argumente pentru a stampa datele si anume primul argument pe care va trebui sa il specificam functiei dupa ce a fost incarcata in registrul EAX este unde anume vrem sa stampam datele? In linux exista 3 tipuri de fisiere chemate file descriptor Standard input identificat de catre valoarea 0 acet fisier preia input-ul de la tastiera spre exemplu Standard output identificat de catre valoarea 1 , acest file este folosit pentru a stampa informatia Standard error indenbtificat de catre valoarea 2, acest file este folosit pemtru a stampa mesajele de erroare. Pentru a stampa datele vom folosi standard output, asadar primul argument va fi specificat in al doilea registru si anume EBX , standard output are ca valoare 1 asadar urmatoarea instructie pe care o vom scrie este movl $1, %ebx Acum registrii au urmatoarele valori eax 0x4 4 ecx 0x0 0 edx 0x0 0 ebx 0x1 1 Al doilea parametru a functiei care trebuie specificat este adresa in memorie unde se afla datele pe care vrem sa la stampam.In urmatorul registru vom incarca adresa etichetei cu urmatoarea instructie leal NickName, %ecx Asadar registrii vor avea urmatoarele valori eax 0x4 4 ecx 0x8049098 134516888 edx 0x0 0 ebx 0x1 1 Dupa cum observati instructia leal NickName, %ecx a incarcat in ECX adresa unde se afla datele si anume 0x8049098 care este echivalent cu 134516888 in decimal.Al treilea argument al functiei care trebuie incarcat in urmatorul registru si anume EDX este lungimea pe care vrem sa o stampam din datele pe care le/am declarat (lungimea nickname-ului este pyth0n3 7+1 newline). movl $8, %edx In acest moment registrii vor avea urmatoarele date eax 0x4 4 ecx 0x8049098 134516888 edx 0x8 8 ebx 0x1 1 Deci pana aici i-am spus procesorului sa faca urmatorul lucru.Mergi la adresa 0x8049098 si stampeaza pe ecran urmatoarele 8 caractere.Daca am fi specificat mai multe caractere ar fi stampat ceea ce se afla dupa 8 caractere pe care le-am declarat in memorie(incercati, o sa va stampeze garbage).In momentul in care instructiile au fost declarate ,nu ramane decat sa chemam procesorul ca sa execute ceea ce am facut pana acum.Urmatoarea instructie va face acest lucru , este un interupt (kernelul va trimite un semnal procesurului ca sa execute ceea ce a fost incarcat in registrii acum. int 0x80 O data ce aceasta instructie vine executata , vor fi stampate pe ecran 8 caractere din datele declarate.(ultimul caracter este doar un newline).Dupa ce datele vor fi stampate registrii vor avea urmatoarele valori eax 0x8 8 ecx 0x8049098 134516888 edx 0x8 8 ebx 0x1 1 Dupa cum observati valoarea din registru EBX este in EAX acum.Dupa ce codul vine executat se va face un return in EAX.Acum nu ramane decat sa declaram instructiile necesare pentru a iesi din program , asadar vom folosi ca syscall exit care are valoarea 1.Dupa cum am specificat , ordinea pentru a incarca instructiile in registrii este urmatoarea .Prima valoare in EAX , iar urmatorii parametrii in EBX,ECX,EDX.Atentie acest lucru este valabil doar pentru procesoarele intel 32 biti.Deci vom chema valoare 1 in registrul EAX. movl $1, %eax Asadar vom avea urmatoarele valori eax 0x1 1 ecx 0x8049098 134516888 edx 0x8 8 ebx 0x1 1 Dupa cum observati registrii nu au fost modificati si unele valori pe care le-am incarcat inainte au ramas in registrii.Atunci cand veti scrie cod putin mai complicat in assembly va trebui sa aduceti registrul la statul initial sau uneori sa daceti un decrement dealtfel registrul va detine valoarea care a fost incarcata initial. Urmatorul parametru pentru functia exit este un return code.Aici putem specifica modul in care vrem sa iesim din program.Un return cod 0 va iesi curat , se poate specifica un return code 1 cand vrem sa iesim cu o erroare. In cazul nostru nu exista o erroare , asadar vom iesi cu 0.Primul si singurul parametru al functiei exit va fi 0. movl, $0 %ebx Dupa cum observati instructiile pe care le-am declarat au fost executate si valorile au fost urcate in registrii EAX va avea valoarea 1 , EBX va avea valoarea 0 iar restul registrilor vor avea valorile precedente eax 0x1 1 ecx 0x8049098 134516888 edx 0x8 8 ebx 0x0 0 Pentru a executa ceea ce este acum in EAX va trebui sachemam un inerrupt , acest semnal va spune procesorului sa execute ce gaseste acum in EAX.Nu vor fi executate datele din registrul ECX si EDX deoarece valoarea pe care am puso in registrul EAX si este echivalenta cu functia exit chiama doar un singur parametru si anume 0 pe care l-am pus in registrul EBX .Asadar vor fi executate datele din EAX SI EBX int $0x80 In acest caz am iesit din program , nu mai vine nimic stampat pe ecran , dar totusi putem observa cu care exist status am iesit.In Linux se poate face acest lucru folosind urmatorul comand imediat dupa ce am executat un program. echo $? In acest tutorial am facut un simplu exemplu in assembly si am observat ce se intampla atunci cand fiecare instructie vine executata. Acest lucru poate fi facut cu un debugger,asadar nu uitati sa asablati codul cu extensii pentru debugging daca vreti sa vedetice face pentru a asambla codul se vafolosi as. as -ggstabs nickname.s -o nickname.o Note: Am specificat optiunea ggstabs doar pentru debugging ,asadar il puteti urca intrun debugger preferat ca sa observati cum vin alocate datele.In momentul de fata vom avea un objec code nickname.o care trebuie ytrecut prin linker.Vom folosi ld pentru linking , asadar vom crea un executabil. ld nickname.o -o nickname Evident in momentul in care il veti trece in debugger veti observa mai multi registrii.Eu m-am limitat ca sa nu duc in erroare.Fiecare registru are rolul lui si fiecare registru este necesar.Un simplu exemplu pe care il veti pbserva atunci cand si doar daca veti face debugging este urmatorul.Presupunem ca am creat un breakpoint inainte ca prima instructie sa fie executata si vrem sa observam valorile care sunt stocate in momentul de fata in registrii.Atentie nu in fiecare registru trebuie neaparat sa existe valori, unii vor avea un flag sau nici o valoare. eax 0x0 0 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 esp 0xbffff840 0xbffff840 ebp 0x0 0x0 esi 0x0 0 edi 0x0 0 eip 0x8048074 0x8048074 <_start> eflags 0x200212 [ AF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x0 0 Dupa cum observati unii registrii au anumite valori precum registrul ESP care va avea intotdeuna o adresa de memorie si anume adresa dintyrun anumit segment de memorie chemat stack (probabil este interesant atunci cand se va scrie un exploit , stack overflow spre exemplu sau buffer overflow).EIP va detine intotdeuna adresa din top a segmentului de memorie chemat stack.Un alt registru inportant este EIP care este un instruction pointer , acest registru va detine intoteauna urmatoarea adresa urmatoarei sintructii care trebuie executate.Sa nu uitam faptul ca si instructiile pe care le folosim trebuie sa fie stocate undeva in memorie.Pentru acest tutorial ma voi opri aici deoarece in momentul de fata nu am folosit alti registrii pentru a putea explica ceea ce fac.Va las exemplele codului din acest tutorial si nu in ultimul rand pentru cai care sunt curiosi voi traduce codul assembly 32 bit in 64 bit doar ca sa observati diferentele. #nickname.s 32 bit intel gas AT&T syntax assembly .data NickName: .ascii "pyth0n3\n" .text .globl _start _start: movl $4, %eax movl $1, %ebx leal NickName, %ecx movl $24, %edx int $0x80 movl $1, %eax movl $0, %ebx int $0x80 #nickname.s 64 bit intel gas AT&T syntax assembly .data NickName: .ascii "pyth0n3\n" .text .globl _start _start: movq $1, %rax movq $1, %rdi movq $NickName, %rsi movq $8, %rdx syscall movq $60, %rax movq $0, %rdi syscall Cum am mai spus exista mai multe variante pentru a scrie acest cod. Exemplu : Vom stampa "OK" pe ecran intrun mod divers sintaxa pentr 32 bit .text .globl _start _start: pushl $0x0a6b6f mov %esp, %ecx mov $0x4, %edx mov $0x4, %eax movl $1, %eax movl $0, %ebx int $0x80 Probabil exista greseli de exprimare, nu ezitati sa scrieti , le voi corecta. ###END