Exploit Exercises - Protostar Net 3

The last in the Net series of Protostar is Net 3. It was of course the most difficult of all of them. However, it still wasn’t too bad.

First, we’re given the following code:

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

#define NAME "net3"
#define UID 996
#define GID 996
#define PORT 2996

/*
 * Extract a null terminated string from the buffer 
 */

int get_string(char **result, unsigned char *buffer, u_int16_t len)
{
 unsigned char byte;

 byte = *buffer;

 if(byte > len) errx(1, "badly formed packet");
 *result = malloc(byte);
 strcpy(*result, buffer + 1);

 return byte + 1;
}

/*
 * Check to see if we can log into the host
 */

int login(unsigned char *buffer, u_int16_t len)
{
 char *resource, *username, *password;
 int deduct;
 int success;

 if(len < 3) errx(1, "invalid login packet length");

 resource = username = password = NULL;

 deduct = get_string(&resource, buffer, len);
 deduct += get_string(&username, buffer+deduct, len-deduct);
 deduct += get_string(&password, buffer+deduct, len-deduct);

 success = 0;
 success |= strcmp(resource, "net3");
 success |= strcmp(username, "awesomesauce");
 success |= strcmp(password, "password");

 free(resource);
 free(username);
 free(password);

 return ! success;
}

void send_string(int fd, unsigned char byte, char *string)
{
 struct iovec v[3];
 u_int16_t len;
 int expected;

 len = ntohs(1 + strlen(string));

 v[0].iov_base = &len;
 v[0].iov_len = sizeof(len);

 v[1].iov_base = &byte;
 v[1].iov_len = 1;

 v[2].iov_base = string;
 v[2].iov_len = strlen(string);

 expected = sizeof(len) + 1 + strlen(string);

 if(writev(fd, v, 3) != expected) errx(1, "failed to write correct amount of bytes");

}

void run(int fd)
{
 u_int16_t len;
 unsigned char *buffer;
 int loggedin;

 while(1) {
  nread(fd, &len, sizeof(len));
  len = ntohs(len);
  buffer = malloc(len);

  if(! buffer) errx(1, "malloc failure for %d bytes", len);

  nread(fd, buffer, len);

  switch(buffer[0]) {
   case 23: 
    loggedin = login(buffer + 1, len - 1);
    send_string(fd, 33, loggedin ? "successful" : "failed");
    break;

   default:
    send_string(fd, 58, "what you talkin about willis?");
    break;
  }
 }
}

int main(int argc, char **argv, char **envp)
{
 int fd;
 char *username;

 /* Run the process as a daemon */
 background_process(NAME, UID, GID); 

 /* Wait for socket activity and return */
 fd = serve_forever(PORT);

 /* Set the client socket to STDIN, STDOUT, and STDERR */
 set_io(fd);

 /* Don't do this :> */
 srandom(time(NULL));

 run(fd);
}

Now because my C is a bit rusty, and I didn’t always understand what was going on well, I re-wrote some of it to look like this:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

int get_string(char **result, unsigned char *buffer, u_int16_t len)
{
        unsigned char byte;

        byte = *buffer;

        printf("[*] Byte as hex: %x\n", byte);

        if(byte > len)
        {
                printf("[*] Culprit: %s. %x is greater than %x\n", &buffer, byte, len);
                errx(1, "badly formed packet");
        }
        *result = malloc(byte);
        strcpy(*result, buffer + 1);

        printf("[*] Returning: %i\n", byte+1);
        return byte + 1;
}

int login(unsigned char *buffer, u_int16_t len)
{
        char *resource, *username, *password;
        int deduct;
        int success;

        if(len < 3) errx(1, "invalid login packet length");

        resource = username = password = NULL;

        deduct = get_string(&resource, buffer, len);
        deduct += get_string(&username, buffer+deduct, len-deduct);
        deduct += get_string(&password, buffer+deduct, len-deduct);

        printf("Resource: %s\n", resource);
        printf("Username: %s\n", username);
        printf("Password: %s\n", password);

        success = 0;
        success |= strcmp(resource, "net3");
        printf("[*] Success (iteration 1): %x\n", success);

        success |= strcmp(username, "awesomesauce");
        printf("[*] Success (iteration 2): %x\n", success);

        success |= strcmp(password, "password");
        printf("[*] Success (iteration 3): %x\n", success);


        free(resource);
        free(username);
        free(password);

        return ! success;
}

void main(int argc, char **argv)
{
        unsigned char *buffer;
        u_int16_t len;
        int loggedin;

        buffer = "string values go here yo";

        len = strlen(buffer);

        loggedin = login(buffer, len);
        printf("[*] Logged in: %x\n", loggedin);

}

This allowed me to get a LOT more debug information, and not worry about my python program failing somewhere.

In my analysis, I found this program to be the most complicated (of course). It has a daemon running on port 2996. It needs a login string to be sent to it just perfectly. That login string needs to have the first byte be in little-endian format, the length of the login string. Then each of the 3 strings, the resource, username, and password, must be sent with their length in little-endian prepended to them, and a null string terminator appended. However, before any of that login string is sent, it needs a control character of “\x17” (23) to go into the login logic.

Eventually, I ended up with the following code:

#!/usr/bin/env python

# Protostar Net 3
# http://exploit-exercises.com/protostar/net3
# Matt Andreko
# twitter: @mandreko
# contact: matt [at] mattandreko.com

from socket import *
from struct import *
from optparse import OptionParser
import select

def main(host, port):

 s = socket(AF_INET, SOCK_STREAM)
 s.connect((host, port))
 
 login_string = ("\x17" # \x17 = 23, which lets us by the switch statement.
                        # It appears to be a control character
     "\x05net3\x00" # Send the resource name, prepended with 
                    #it's length in hex, and appended with a null
                    # byte
     "\x0dawesomesauce\x00" # Send the user name, prepended with
                            # it's length in hex, and appended
                            # with a null byte
     "\x0apassword\x00") # Send the password, prepended with
                         # it's length in hex, and appended 
                         # with a null byte

 login_length = len(login_string) # The initial byte needs to be the length
                                  # of the entire login string, so that it
                                  # knows how much memory to malloc()

 s.send(pack(">H", login_length))
 
 s.send(login_string)
 
 print s.recv(1024)
 
 s.close()

if __name__ == "__main__":
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-H", "--host", dest="hostname", default="127.0.0.1", 
     type="string", help="Target to run against")
    parser.add_option("-p", "--port", dest="portnum", default=2996, 
     type="int", help="Target port")

    (options, args) = parser.parse_args()
    
    main(options.hostname, options.portnum)

When I run that code, I get:

C:\Protostar>net3.py -H 192.168.1.132
 ♂!successful

That is the last of all the Net challenges that I see documented. But I do wonder, since in the virtual machine, there is a net4 binary. :)

comments powered by Disqus