czwartek, 20 listopada 2014

Penetrujemy aplikacje napisane w języku C

Dziś wracam do podstaw programowania w języku C, lecz nie będzie to typowa nauka języka programowania. Aplikacje w języku C są kompilowane i składnia którą piszemy, musi być najpierw przetłumaczona na język maszynowy, gdzie zwykle programiści nie zaglądają. Ja będę tym złym i będę sprawdzał jak moje programy wyglądają od środka.
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
Teraz można zobaczyć, że porównanie w kodzie maszynowym wygląda inaczej niż ten co zapisałem, w prostym przykładzie w C. 
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