#define _POSIX_C_SOURCE 200809L #include /* exit */ #include /* memcmp, strlen */ #include /* waitpid */ #include /* alarm, close, fork, read, write */ /* Jedním ze standardních cílů útoku na počítač je tzv. remote code * execution, totiž schopnost libovolně spouštět útočníkův kód. * Podobně standardním prostředkem je (po úspěšném provedení útoku) * instalace tzv. backdoor – programu, který útočníkovi * zprostředkuje ono spouštění příkazů. * * Vaším úkolem je naprogramovat proceduru ‹backdoor_serve›, která bude * komunikovat s jedním připojeným klientem – konkrétně každý * přečtený řádek spustí jako příkaz shellu. Standardní výstup přesměruje * do zadaného popisovače. * * Parametry pro ‹backdoor_serve›: * * • ‹fd› popisovač připojeného proudového socketu. * • ‹fd_stdout› popisovač souboru, do kterého má být přesměrován * standardní výstup. * * Na každý přečtený řádek odpoví server nejprve jednobajtovým * potvrzením, že byl příkaz přijat, a dále po jeho skončení * jednobajtovým kódem (výsledkem spuštění příkazu). * * Výsledkem ‹backdoor_serve› je počet úspěšně spuštěných příkazů * ukončí-li klient spojení, nebo -1 v případě chyby (špatný příkaz * od klienta není chybou v tomto smyslu). */ int backdoor_serve( int fd, int fd_stdout ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* assert */ #include /* err, errx, warn */ #include /* uint8_t */ #include /* memcmp, strlen */ #include /* socketpair */ static void socketpair_or_die( int type, int *sv ) { if ( socketpair( AF_UNIX, type, 0, sv ) == -1 ) err( 2, "creating socketpair" ); } static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static int check_output( const int read_fd, const char* expected, const size_t size ) { char buffer[ 256 ]; ssize_t bytes; size_t already_read = 0; while ( ( bytes = read( read_fd, buffer, sizeof( buffer ) ) ) > 0 ) { if ( memcmp( expected + already_read, buffer, bytes ) != 0 ) return 0; already_read += bytes; } if ( bytes == -1 ) err( 2, "reading from fd %d", read_fd ); if ( already_read > size ) return 0; // if read more bytes = read( read_fd, buffer, 1 ); if ( bytes < 0 ) err( 2, "reading from fd %d", read_fd ); already_read += bytes; return already_read == size; } static void write_or_die( int fd, const char *buffer, int nbytes ) { int bytes_written = write( fd, buffer, nbytes ); if ( bytes_written == -1 ) err( 1, "writing %d bytes", nbytes ); if ( bytes_written != nbytes ) errx( 1, "unexpected short write: %d/%d written", bytes_written, nbytes ); } static pid_t fork_client( int fds[ 2 ], int stdio[ 2 ], const char* data, const int len ) { alarm( 5 ); int *client = fds, *server = fds + 1; int *fd_stdin = stdio, *fd_stdout = stdio + 1; write_or_die( *client, data, len ); pid_t pid = fork(); if ( pid == -1 ) err( 2, "fork" ); if ( pid == 0 ) { close_or_warn( *client, "client socket" ); close_or_warn( *fd_stdin, "check stdin" ); int rv = backdoor_serve( *server, *fd_stdout ); close_or_warn( *fd_stdout, "check stdout" ); close_or_warn( *server, "server socket" ); exit( rv ); } close_or_warn( *server, "server socket" ); close_or_warn( *fd_stdout, "check stdout" ); return pid; } static int reap( pid_t pid ) { int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 2, "wait" ); if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); else return -1; } static int check_resps( int fd, uint8_t *resps, size_t n ) { int bytes = 0, state = 0; char buf[ 256 ]; n *= 2; while ( ( bytes = read( fd, &buf + state, ( n > sizeof( buf ) ) ? sizeof( buf ) : n ) ) > 0 ) { for ( int i = 0; i < bytes / 2; ++ i ) if ( *( resps ++ ) != buf[ 2 * i + 1 ] ) return 0; state = bytes % 2; n -= bytes; } if ( bytes < 0 ) err( 2, "read" ); return n == 0; } int main( void ) { alarm( 7 ); pid_t pid; int fds[2], *client = fds; int stdio[2], *fd_stdin = stdio; char *data_1 = "echo 'Words are very unnecessary. They can only do harm.'\n"; uint8_t resp_1[] = { 0 }; char *data_2 = "echo 'abc'\necho -n 'zjb'\necho -n 'toto uz ne'"; uint8_t resp_2[] = { 0, 0 }; char *data_3 = "hellofromtheotherside\n"; uint8_t resp_3[] = { 127 }; /* Test case 1. */ socketpair_or_die( SOCK_STREAM, fds ); socketpair_or_die( SOCK_STREAM, stdio ); pid = fork_client( fds, stdio, data_1, strlen( data_1 ) ); assert( check_resps( *client, resp_1, 1 ) ); close_or_warn( *client, "client socket" ); assert( reap( pid ) == 1 ); assert( check_output( *fd_stdin, "Words are very unnecessary. They can only do harm.\n", 51 ) ); close_or_warn( *fd_stdin, "check stdin" ); /* Test case 2. */ socketpair_or_die( SOCK_STREAM, fds ); socketpair_or_die( SOCK_STREAM, stdio ); pid = fork_client( fds, stdio, data_2, strlen( data_2 ) ); assert( check_resps( *client, resp_2, 2 ) ); close_or_warn( *client, "client socket" ); assert( reap( pid ) == 2 ); assert( check_output( *fd_stdin, "abc\nzjb", 7 ) ); close_or_warn( *fd_stdin, "check stdin" ); /* Test case 3. */ socketpair_or_die( SOCK_STREAM, fds ); socketpair_or_die( SOCK_STREAM, stdio ); pid = fork_client( fds, stdio, data_3, strlen( data_3 ) ); assert( check_resps( *client, resp_3, 1 ) ); close_or_warn( *client, "client socket" ); assert( reap( pid ) == 1 ); close_or_warn( *fd_stdin, "check stdin" ); return 0; }