Level 04

About

Level04 introduces timing attacks, position independent executables (PIE), and stack smashing protection (SSP). Partial overwrites ahoy!

OptionSetting
Vulnerability TypeStack
Position Independent ExecutableYes
Read only relocationsNo
Non-Executable stackYes
Non-Executable heapYes
Address Space Layout RandomisationYes
Source FortificationYes

Source code

#include "../common/common.c"

// original code from micro_httpd_12dec2005.tar.gz -- acme.com. added vulnerabilities etc ;)

/* micro_httpd - really small HTTP server
**
** Copyright (c) 1999,2005 by Jef Poskanzer <[email protected]>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/


#define SERVER_NAME "level04.c"
#define SERVER_URL "https://gist.github.com/b69116098bcc6ef7dfb4"
#define PROTOCOL "HTTP/1.0"
#define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT"


/* Forwards. */
static void file_details( char* dir, char* name );
static void send_error( int status, char* title, char* extra_header, char* text );
static void send_headers( int status, char* title, char* extra_header,
  char* mime_type, off_t length, time_t mod );
static char* get_mime_type( char* name );
static void strdecode( char* to, char* from );
static int hexit( char c );
static void strencode( char* to, size_t tosize, const char* from );

int webserver(int argc, char **argv);

// random decoder 
void build_decoding_table();

char *password;
int password_size = 16;

int main(int argc, char **argv)
{
  int fd, i;
  char *args[6];

  /* Securely generate a password for this session */

  secure_srand();
  password = calloc(password_size, 1);
  for(i = 0; i < password_size; i++) {
      switch(rand() % 3) {
          case 0: password[i] = (rand() % 25) + 'a'; break;
          case 1: password[i] = (rand() % 25) + 'A'; break;
          case 2: password[i] = (rand() % 9) + '0'; break;
      }
  }

  // printf("password is %s\n", password);

  background_process(NAME, UID, GID); 
  fd = serve_forever(PORT);
  set_io(fd);
  alarm(15);

  args[0] = "/opt/fusion/bin/stack06";
  args[1] = "/opt/fusion/run";
  args[2] = NULL;

  build_decoding_table();

  webserver(2, args);
}

// random decoder from stackoverflow
// modified to make more vulnerable

#include <math.h>
#include <stdint.h>
#include <stdlib.h>

static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
                                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                                'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
                                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                                'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                                'w', 'x', 'y', 'z', '0', '1', '2', '3',
                                '4', '5', '6', '7', '8', '9', '+', '/'};
static char *decoding_table = NULL;
static int mod_table[] = {0, 2, 1};

void base64_decode(const char *data,
                    size_t input_length,
                    unsigned char *output,
                    size_t *output_length) {

    if (decoding_table == NULL) build_decoding_table();

    // printf("data: %p, input_length: %d, output: %p, output_length: %p\n",
    // data, input_length, output, output_length);

    if ((input_length % 4) != 0) {
  // printf("len % 4 = fail\n");
  return;
    }

    *output_length = input_length / 4 * 3;
    if (data[input_length - 1] == '=') (*output_length)--;
    if (data[input_length - 2] == '=') (*output_length)--;

    for (int i = 0, j = 0; i < input_length;) {

        uint32_t sextet_a = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
        uint32_t sextet_b = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
        uint32_t sextet_c = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
        uint32_t sextet_d = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];

        uint32_t triple = (sextet_a << 3 * 6)
                        + (sextet_b << 2 * 6)
                        + (sextet_c << 1 * 6)
                        + (sextet_d << 0 * 6);

        if (j < *output_length) output[j++] = (triple >> 2 * 8) & 0xFF;
        if (j < *output_length) output[j++] = (triple >> 1 * 8) & 0xFF;
        if (j < *output_length) output[j++] = (triple >> 0 * 8) & 0xFF;
    }
}


void build_decoding_table() {

    decoding_table = malloc(256);

    for (int i = 0; i < 0x40; i++)
        decoding_table[encoding_table[i]] = i;
}


void base64_cleanup() {
    free(decoding_table);
}


// end random decoder

int validate_credentials(char *line)
{
  char *p, *pw;
  unsigned char details[2048];
  int bytes_wrong;
  int l;
  struct timeval tv;
  int output_len;


  memset(details, 0, sizeof(details));

  output_len = sizeof(details);

  p = strchr(line, '\n');
  if(p) *p = 0;
  p = strchr(line, '\r');
  if(p) *p = 0;

  // printf("%d\n", strlen(line));
  base64_decode(line, strlen(line), details, &output_len);
  // printf("%s -> %s\n", line, details);
  // fflush(stdout);
  
  p = strchr(details, ':');
  pw = (p == NULL) ? (char *)details : p + 1;

  for(bytes_wrong = 0, l = 0; pw[l] && l < password_size; l++) {
      if(pw[l] != password[l]) {
          
#if 0
          char *buf;
          asprintf(&buf, "[%d] wrong byte (%02x vs %02x)\n", l,
                          password[l], pw[l]);
          write(58, buf, strlen(buf));
#endif
          
          bytes_wrong++;
      }
  }

  // anti bruteforce mechanism. good luck ;>
  
  tv.tv_sec = 0;
  tv.tv_usec = 2500 * bytes_wrong;

  select(0, NULL, NULL, NULL, &tv);

  // printf("%d bytes wrong!\n", bytes_wrong);

  if(l < password_size || bytes_wrong)
      send_error(401, "Unauthorized",
      "WWW-Authenticate: Basic realm=\"stack06\"",
      "Unauthorized");

  return 1;
}

int
webserver( int argc, char** argv )
    {
    char line[10000], method[10000], path[10000], protocol[10000], idx[20000];
    char location[20000], command[20000];
    char* file;
    size_t len;
    int ich;
    struct stat sb;
    FILE* fp;
    struct dirent **dl;
    int i, n;
    int authed = 0;

    if ( argc != 2 )
  send_error( 500, "Internal Error", (char*) 0,
  "Config error - no dir specified." );
    if ( chdir( argv[1] ) < 0 )
  send_error( 500, "Internal Error", (char*) 0,
  "Config error - couldn't chdir()." );
    if ( fgets( line, sizeof(line), stdin ) == (char*) 0 )
  send_error( 400, "Bad Request", (char*) 0,
  "No request found." );
    if ( sscanf( line, "%[^ ] %[^ ] %[^ ]", method, path, protocol ) != 3 )
  send_error( 400, "Bad Request", (char*) 0, "Can't parse request." );
    while ( fgets( line, sizeof(line), stdin ) != (char*) 0 )
  {
        if ( strncmp ( line, "Authorization: Basic ", 21) == 0)
      authed = validate_credentials(line + 21);
      
  if ( strcmp( line, "\n" ) == 0 || strcmp( line, "\r\n" ) == 0 )
      break;
  }
    if ( ! authed) send_error(401, "Unauthorized",
      "WWW-Authenticate: Basic realm=\"stack06\"",
      "Unauthorized");

    if ( strcasecmp( method, "get" ) != 0 )
  send_error( 501, "Not Implemented", (char*) 0,
  "That method is not implemented." );
    if ( path[0] != '/' )
  send_error( 400, "Bad Request", (char*) 0, "Bad filename." );
    file = &(path[1]);
    strdecode( file, file );
    if ( file[0] == '\0' )
  file = "./";
    len = strlen( file );
    if ( file[0] == '/' || strcmp( file, ".." ) == 0 ||
      strncmp( file, "../", 3 ) == 0 || strstr( file, "/../" ) != (char*) 0 ||
      strcmp( &(file[len-3]), "/.." ) == 0 )
  send_error( 400, "Bad Request", (char*) 0, "Illegal filename." );
    if ( stat( file, &sb ) < 0 )
  send_error( 404, "Not Found", (char*) 0, "File not found." );
    if ( S_ISDIR( sb.st_mode ) )
  {
  if ( file[len-1] != '/' )
      {
      (void) snprintf(
      location, sizeof(location), "Location: %s/", path );
      send_error( 302, "Found", location, "Directories must end with a slash." );
      }
  (void) snprintf( idx, sizeof(idx), "%sindex.html", file );
  if ( stat( idx, &sb ) >= 0 )
      {
      file = idx;
      goto do_file;
      }
  send_headers( 200, "Ok", (char*) 0, "text/html", -1, sb.st_mtime );
  (void) printf( "<html><head><title>Index of %s</title></head>\n"
      "<body bgcolor=\"#99cc99\"><h4>Index of %s</h4>\n<pre>\n",
      file, file );
  n = scandir( file, &dl, NULL, alphasort );
  if ( n < 0 )
      perror( "scandir" );
  else
      for ( i = 0; i < n; ++i )
      file_details( file, dl[i]->d_name );
  (void) printf( "</pre>\n<hr>\n<address><a href=\"%s\">%s</a></address>"
      "\n</body></html>\n", SERVER_URL, SERVER_NAME );
  }
    else
  {
  do_file:
  fp = fopen( file, "r" );
  if ( fp == (FILE*) 0 )
      send_error( 403, "Forbidden", (char*) 0, "File is protected." );
  send_headers( 200, "Ok", (char*) 0, get_mime_type( file ), sb.st_size,
      sb.st_mtime );
  while ( ( ich = getc( fp ) ) != EOF )
      putchar( ich );
  }

    (void) fflush( stdout );
    exit( 0 );
    }


static void
file_details( char* dir, char* name )
    {
    static char encoded_name[1000];
    static char path[2000];
    struct stat sb;
    char timestr[16];

    strencode( encoded_name, sizeof(encoded_name), name );
    (void) snprintf( path, sizeof(path), "%s/%s", dir, name );
    if ( lstat( path, &sb ) < 0 )
  (void) printf( "<a href=\"%s\">%-32.32s</a>    ???\n",
      encoded_name, name );
    else
  {
  (void) strftime( timestr, sizeof(timestr), "%d%b%Y %H:%M",
      localtime( &sb.st_mtime ) );
  (void) printf( "<a href=\"%s\">%-32.32s</a>    %15s %14lld\n",
      encoded_name, name, timestr, (int64_t) sb.st_size );
  }
    }


static void
send_error( int status, char* title, char* extra_header, char* text )
    {
    send_headers( status, title, extra_header, "text/html", -1, -1 );
    (void) printf( "<html><head><title>%d %s</title></head>\n<body "
      "bgcolor=\"#cc9999\"><h4>%d %s</h4>\n", status,
      title, status, title );
    (void) printf( "%s\n", text );
    (void) printf( "<hr>\n<address><a href=\"%s\">%s</a></address>"
      "\n</body></html>\n", SERVER_URL, SERVER_NAME );
    (void) fflush( stdout );
    exit( 1 );
    }


static void
send_headers( int status, char* title, char* extra_header,
  char* mime_type, off_t length, time_t mod )
    {
    time_t now;
    char timebuf[100];

    (void) printf( "%s %d %s\015\012", PROTOCOL, status, title );
    (void) printf( "Server: %s\015\012", SERVER_NAME );
    now = time( (time_t*) 0 );
    (void) strftime( timebuf, sizeof(timebuf), RFC1123FMT, gmtime( &now ) );
    (void) printf( "Date: %s\015\012", timebuf );
    if ( extra_header != (char*) 0 )
  (void) printf( "%s\015\012", extra_header );
    if ( mime_type != (char*) 0 )
  (void) printf( "Content-Type: %s\015\012", mime_type );
    if ( length >= 0 )
  (void) printf( "Content-Length: %lld\015\012", (int64_t) length );
    if ( mod != (time_t) -1 )
  {
  (void) strftime( timebuf, sizeof(timebuf), RFC1123FMT, gmtime( &mod ) );
  (void) printf( "Last-Modified: %s\015\012", timebuf );
  }
    (void) printf( "Connection: close\015\012" );
    (void) printf( "\015\012" );
    }


static char*
get_mime_type( char* name )
    {
    char* dot;

    dot = strrchr( name, '.' );
    if ( dot == (char*) 0 )
  return "text/plain; charset=iso-8859-1";
    if ( strcmp( dot, ".html" ) == 0 || strcmp( dot, ".htm" ) == 0 )
  return "text/html; charset=iso-8859-1";
    if ( strcmp( dot, ".jpg" ) == 0 || strcmp( dot, ".jpeg" ) == 0 )
  return "image/jpeg";
    if ( strcmp( dot, ".gif" ) == 0 )
  return "image/gif";
    if ( strcmp( dot, ".png" ) == 0 )
  return "image/png";
    if ( strcmp( dot, ".css" ) == 0 )
  return "text/css";
    if ( strcmp( dot, ".au" ) == 0 )
  return "audio/basic";
    if ( strcmp( dot, ".wav" ) == 0 )
  return "audio/wav";
    if ( strcmp( dot, ".avi" ) == 0 )
  return "video/x-msvideo";
    if ( strcmp( dot, ".mov" ) == 0 || strcmp( dot, ".qt" ) == 0 )
  return "video/quicktime";
    if ( strcmp( dot, ".mpeg" ) == 0 || strcmp( dot, ".mpe" ) == 0 )
  return "video/mpeg";
    if ( strcmp( dot, ".vrml" ) == 0 || strcmp( dot, ".wrl" ) == 0 )
  return "model/vrml";
    if ( strcmp( dot, ".midi" ) == 0 || strcmp( dot, ".mid" ) == 0 )
  return "audio/midi";
    if ( strcmp( dot, ".mp3" ) == 0 )
  return "audio/mpeg";
    if ( strcmp( dot, ".ogg" ) == 0 )
  return "application/ogg";
    if ( strcmp( dot, ".pac" ) == 0 )
  return "application/x-ns-proxy-autoconfig";
    return "text/plain; charset=iso-8859-1";
    }


static void
strdecode( char* to, char* from )
    {
    for ( ; *from != '\0'; ++to, ++from )
  {
  if ( from[0] == '%' && isxdigit( from[1] ) && isxdigit( from[2] ) )
      {
      *to = hexit( from[1] ) * 16 + hexit( from[2] );
      from += 2;
      }
  else
      *to = *from;
  }
    *to = '\0';
    }


static int
hexit( char c )
    {
    if ( c >= '0' && c <= '9' )
  return c - '0';
    if ( c >= 'a' && c <= 'f' )
  return c - 'a' + 10;
    if ( c >= 'A' && c <= 'F' )
  return c - 'A' + 10;
    return 0;       /* shouldn't happen, we're guarded by isxdigit() */
    }


static void
strencode( char* to, size_t tosize, const char* from )
    {
    int tolen;

    for ( tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from )
  {
  if ( isalnum(*from) || strchr( "/_.-~", *from ) != (char*) 0 )
      {
      *to = *from;
      ++to;
      ++tolen;
      }
  else
      {
      (void) sprintf( to, "%%%02x", (int) *from & 0xff );
      to += 3;
      tolen += 3;
      }
  }
    *to = '\0';
    }