Podstawy teoretyczne
- Procesor wykonując program działa sekwencyjnie wg następującego schematu:
- Pobierz rozkaz wskazywany przez IP (Instruction Pointer)
- Zdekoduj rozkaz
- Wykonaj rozkaz i zaktualizuj flagi stanu
- Zwiększ IP, wróć do punktu 1
- Przykład, kod w asemblerze:
Co stworzył kompilator: (od lewej - adres, bajty rozkazu, instrukcje)start:
mov eax, 10
mov ecx, 2
sub edx, edx
idiv ecxstart:
0: b8 0a 00 00 00 mov eax,10
5: b9 02 00 00 00 mov ecx,2
a: 29 d2 sub edx,edx
c: f7 f9 idiv ecx - Prześledźmy to po kolei:
IP = 0, wskazuje na instrukcjęmov eax, 10- Procesor odczytuje bajt
b8 - Procesor dekoduje, że jest to instrukcja
mov eax, LICZBA, pobiera jeszcze 4 bajty stanowiące liczbę - W tym momencie
IP = 5, wskazuje na bajtb9czyli instrukcjęmov ecx, LICZBA, itd.
- Pisząc w asemblerze mamy możliwość odwzorowania 1-do-1 tego co zaprogramujemy i tego co zostanie wykonane. Natomiast kompilator C (i innych języków) zamienia program na określone sekwencje instrukcji asemblera - czasem lepiej, czasem gorzej niż człowiek.
- Wyżej wspomniane zostały
eaxiecx, co to jest? Procesor ma wbudowany w siebie szereg rejestrów. Każdy rejestr to jednostka pamiętająca pewną liczbę bitów. Można powiedzieć w uproszczeniu, żeeaxiecxprzypominają zmienne z języka C. - Dostępne rejestry:
| 32-bit | EAX | ECX | EDX | EBX | EDI | ESI | ||||||||||||||||||
| 16-bit | AX | CX | DX | BX | DI | SI | ||||||||||||||||||
| 8-bit | AH | AL | CH | CL | DH | DL | BH | BL | ||||||||||||||||
- Przykład: (AX = 1234, AH = 4, AL = 210)
| AX | b15 | b14 | b13 | b12 | b11 | b10 | b09 | b08 | b07 | b06 | b05 | b04 | b03 | b02 | b01 | b00 |
| AH | b15 | b14 | b13 | b12 | b11 | b10 | b09 | b08 | ||||||||
| AL | b07 | b06 | b05 | b04 | b03 | b02 | b01 | b00 | ||||||||
| 123410 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 |
- Podstawowe instrukcje:
mov CEL, ŹRÓDŁO- przekopiuj wartość ze ŹRÓDŁA do CEL u np.mov eax, ecxskopiuje zawartośćecxdoeaxadd CEL, WARTOŚĆ- dodaj do CELu WARTOŚĆ np.add eax, 1doda 1 doeaxsub CEL, WARTOŚĆ- jw. ale odejmowaniediv REJESTR- dzieli zawartośćeaxprzez REJESTR. Wynik zapisany będzie weax, reszta z dzielenia wedxale UWAGA!, rejestredxmusi być wcześniej wyzerowany!mul REJESTR- mnożyeaxprzez REJESTR
- Procesor posiada jeszcze specjalny rejestr stanu, do którego zazwyczaj nie sięga się bezpośrednio. Jak napisano powyżej, rejestr stanu zmienia się w ogólności po każdym wykonaniu instrukcji i odzwierciedla on status systemu.
- Aby wykonać kod warunkowo (tzn. odpowiednik if() czy pętli z języka C), należy ze sobą połączyć dwie instrukcje:
cmp CEL, WARTOŚĆ- porównanie CELu z WARTOŚCIĄ np.cmp eax, ecxporówna rejestreaxzecx; po takim porównaniu ustawione zostaną flagij?? ETYKIETA- jest to skok warunkowy do ETYKIETY; znaki zapytania to odpowiednie litery, których należy użyć do określonej funkcjonalności:je- jump-if-equal, skocz jeśli porównane wartości są równejne- jump-if-not-equal, skocz jeśli nie są równejlorazjle- jump-if-less oraz jump-if-less-or-equal, skocz jeśli CEL jest mniejszy od WARTOŚCI (jle= jeśli mniejszy lub równy)jgorazjge- jump-if-greater oraz jump-if-greater-or-equal, skocz jeśli CEL jest większy od WARTOŚCI (jle= jeśli większy lub równy)
Instrukcja warunkowa if()
C:
...
}
Asembler:
cmp eax, 4
jle koniec_if
...
koniec_if:
Warto zauważyć, że w C programista pisze if() myśląc przy jakim warunku wykonać kod, natomiast w asemblerze programista myśli przy jakim warunku przeskoczyć kod :)
Pętla while()
C:
...
n = n + 1;
}
Asembler:
cmp eax, 5
jge koniec_while
...
add eax, 1
jmp poczatek_while
koniec_while:
Instrukcja jmp to bezwarunkowy skok - zawsze skacze do podanej etykiety, niezależnie od rejestru stanów.
Szablon do własnej pracy
Używany kompilator na zajęciach: http://flatassembler.net/download.php
Przykładowy kod: http://www.cs.put.poznan.pl/tzok/example.asm
entry start
include 'win32ax.inc'
section '.import' import data readable
library msvcrt, 'msvcrt.dll'
import msvcrt,\
printf, 'printf',\
scanf, 'scanf',\
system, 'system'
section '.data' data readable writeable
format_in db '%d', 0
format_out db '%d', 13, 10, 0
n dd ?
section '.code' code readable writeable executable
start:
cinvoke scanf, format_in, n
mov eax, [n]
add eax, 10
sub edx, edx
mov ecx, 5
idiv ecx
cinvoke printf, format_out, eax
cinvoke system, 'pause'
ret
Komentarz:
- Do wywołania funkcji z języka C używamy składni
cinvoke NAZWA, ARGUMENT1, ARGUMENT2, ... - Napisy to zmienne typu
db, koniecznie zakończone zerem. Znak nowej linii to13, 10(zobacz przykład powyżej, zmiennaformat_out) - Liczby całkowite to zmienne typu
dd - Zapis
mov eax, [n]oznacza "zawartość zmiennej n wpisz do rejestru eax"
Debugowanie
Używany debuger na zajęciach: http://www.ollydbg.de/odbg110.zip
Debugowanie to proces kontrolowanego uruchomienia aplikacji. Oznacza to tyle, że można wykonywać program instrukcja po instrukcji obserwując dokładnie jak zmieniają się wartości w rejestrach i flagi stanu.
Po włączeniu OllyDbg wczytujemy nasz skompilowany program przez opcję File->Open

Legenda:
- zielony - adresy załadowanego do pamięci programu
- fioletowy - instrukcje w czytelnej dla maszyny postaci
- żółty - instrukcje w czytelnej dla człowieka postaci :)
- czerwony - wartości rejestrów (pionowa kolumna poniżej to wszystkie flagi tworzące razem rejestr stanu)
Aby przejść jedną instrukcję do przodu, naciskamy F8. Po każdym kroku zmienione rejestry oraz flagi zapalają się na czerwono.
Ćwiczenia na laboratorium
- Proszę wykorzystać poniższy, minimalny kod do tworzenia rozwiązań zadań na ćwiczeniach:
format pe console
entry start
include 'win32ax.inc'
section '.code' code readable writeable executable
start:
mov bl, 1
mov cl, 2
mov dl, 3
; kontynuuj tutaj!
ret
Tematy zaawansowane
- Jak widać na powyższych przykładach, kod to po prostu bajty, pewne liczby... Można je zmieniać w samym programie, jest to idea SMC (self-modifying code) używana np. przez wirusy i z tego też powodu często zabroniona przez systemy operacyjne. Przykład:
Animacja:start:
mov eax, 10
sub eax, 1
mov dword [start + 1], eax
jnz start
- Dlaczego to działa?
- Do
eaxtrafia wartość 10 - Następnie w
eaxznajduje się wartość o 1 mniejsza - Zapisujemy to pod adres
[start + 1], czyli co to oznacza? Spójrz na sam początek tej strony gdzie znajduje się przykład jak wygląda instrukcjamov eax, LICZBA; są to bajtyb8 ?? ?? ?? ??gdzie znaki zapytania oznaczają liczbę. A zatem zapis pod adres[start + 1]nadpisuje te cztery bajty! - Instrukcja
subustawia flagęZFjeśli po jej wykonaniu wynikiem będzie zero. Natomiast instrukcjamovnie zmienia żadnych flag. Dlatego sekwencja:sub,mov,jnz startto prosta pętla, która będzie wracać do punktu startu dopóki weaxnie znajdzie się zero.
- Do
- Procesory Intel/AMD mają bardzo bogaty zbiór instrukcji. Niektóre są bardzo specyficzne, niektóre są nawet nieudokumentowane ;). Tę samą rzecz można często wykonać na kilka sposobów. Przykład, jak dodać 1 od rejestru
eax:Zupełnie różne bajty, a wynik ten sam.; dodaj 1
83 c0 01 add eax,0x1
; inkrementuj
40 inc eax
; odejmij -1
83 e8 ff sub eax,0xffffffff
; wpisz 1 do rejestru, odejmij od niego
29 d2 sub edx,edx
42 inc edx
29 d0 sub eax,edx - Idąc dalej tym tropem, okazuje się, że nawet taka sama instrukcja może mieć kilka wersji maszynowych (różne bajty kodują to samo), przykład:
W tym przypadku bajt6a 00 push 0x0
68 00 00 00 00 push 0x06aoznacza umieść na stosie wartość o wielkości 1-bajta, natomiast68to umieść na stosie wartość o wielkości 4-bajtów - Z innych ciekawych instrukcji:
jecxz ETYKIETA- skocz do etykiety jeśliecxjest równe 0 (nie potrzeba wykonywać insturkcjicmp)cmov?? CEL, WARTOŚĆ- działa jak instrukcjamov, ale działa warunkowo; pod znaki zapytania należy wstawić końcówki jak w insturkcjach skoku np.cmovzalbocmovle
- Jak w języku C zamienić dwie wartości miejscami? Trzeba się posłużyć zmienną pomocniczą:
A w asemblerze wystarczy jedna instrukcja (i oszczędza się pamięć, bo nie potrzeba dodatkowej zmiennej):int m = 2, n = 5;
int tmp = m;
m = n;
n = tmp;xchg eax, ecx