Początkowo skupie się na instrukcjach warunkowych, pętlach, mechanizmami wejścia i wyjścia itp. i nawet udowodnię moc wskaźników.
Początkowo zaczynam od prostego przykładu:
#include <stdio.h>
int main() {
int i;
for(i = 0; i < 10; i++) {
puts("Hello, world!\n");
}
return 0; // Informuje OS o zakonczeniu programu
}
Pewnie wszyscy od takiego przykładu zaczynają i można spotkać w wielu kursach do nauki języka C, developerzy - zwłaszcza po ukończonych studiach, myślą, że jak zna płynnie składnie języka, to zna język od deski do deski i takie rozumowanie jest błędne. Kiedyś też tak myślałem, że jak napisze program to znam dobrze C, lecz gdy dowiedziałem się, o tym, że do skompilowanego programu mogę zajrzeć i go troszkę po penetrować, zrozumiałem jak mało wiem o tym języku. Teraz wracając do przykładu chciałbym pokazać jak można zajrzeć do skompilowanego programu i przeczytać jego zapis w języku maszynowym. Komputer, który użytkuje jest w architekturze x86 i pod tą architekturę będę pokazywał kod asemblera, a do tego posłuży mi narzędzie objdump. I ten prosty program wygląda u mnie tak.
Polecenie:
objdump -D zad1.o | grep -A20 main.:
Wynik: 0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
f: eb 0e jmp 1f <main+0x1f>
11: bf 00 00 00 00 mov $0x0,%edi
16: e8 00 00 00 00 callq 1b <main+0x1b>
1b: 83 45 fc 01 addl $0x1,-0x4(%rbp)
1f: 83 7d fc 09 cmpl $0x9,-0x4(%rbp)
23: 7e ec jle 11 <main+0x11>
25: b8 00 00 00 00 mov $0x0,%eax
2a: c9 leaveq
2b: c3 retq
Disassembly of section .rodata:
0000000000000000 <.rodata>:
0: 48 rex.W
1: 65 gs
2: 6c insb (%dx),%es:(%rdi)
Standardowo wartości są podawane w trybie szesnastkowym, lecz zawsze można to odpowiednio zmienić, dodatkowo ta adnotacja kodu maszynowego jest pokazana do architektury AT&T i jeżeli chcemy zmienić adnotacje to wystarczy podać to w parametrze M, co przedstawiam poniżej: objdump -M intel -D zad1.o | grep -A20 main.:
Wynik: 0000000000000000 <main>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 10 sub rsp,0x10
8: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
f: eb 0e jmp 1f <main+0x1f>
11: bf 00 00 00 00 mov edi,0x0
16: e8 00 00 00 00 call 1b <main+0x1b>
1b: 83 45 fc 01 add DWORD PTR [rbp-0x4],0x1
1f: 83 7d fc 09 cmp DWORD PTR [rbp-0x4],0x9
23: 7e ec jle 11 <main+0x11>
25: b8 00 00 00 00 mov eax,0x0
2a: c9 leave
2b: c3 ret
Początkowo kod jest bardzo trudny do odczytu, lecz od razu mozna zobacyzć, zę na początku jest deklarowane rejestry od akumulatorów, liczników, danych i bazowych. Później polecenia:- mov - deklaruje zmienią do pamięci rdp-0x4 == 4,
- jmp - służy do przeskoków pamięci, call wykonuje odpowiedni rozkaz w tym przypadku wywołuje polecenie putsa,
- add - dodaje nam do zmiennej plus jeden,
- cmp - jest instrukcją porównania czy zmienna z pamięci rdp-0x4 jest równa 9 i później wykonanie tego warunku, a jak nie to wraca do punktu 0
Objdump jest przydatnym narzędziem, jeśli chcę zobaczyć aplikacje w kodzie maszynowym, ale nie pomoże jeśli aplikacja zawiera błędy, w poszczególnych instrukcjach, czy też jeżeli sprawdzam odpowiedni fragment kodu aplikacji.
Na szczęście jest narzędzie idealne do większej analizy kody, przeznaczonej dla programistów, czy tez testerów penetracyjnych i zwie się gdb i tak to jest debuger, które omówię już w następnym poście.
Brak komentarzy:
Prześlij komentarz