index search github twitter

Exploit-Exercises: Protostar (Net Levels)

Image: Exploit-Exercises: Protostar (v2)

user@protostar:~$ wget https://raw.githubusercontent.com/73696e65/gdbinit/master/gdb_init.txt --no-check-certificate -O ~/.gdbinit -q 

Protostar Net0

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

#define NAME "net0"
#define UID 999
#define GID 999
#define PORT 2999

void run()
{
  unsigned int i;
  unsigned int wanted;

  wanted = random();

  printf("Please send '%d' as a little endian 32bit int\n", wanted);

  if(fread(&i, sizeof(i), 1, stdin) == NULL) {
      errx(1, ":(\n");
  }

  if(i == wanted) {
      printf("Thank you sir/madam\n");
  } else {
      printf("I'm sorry, you sent %d instead\n", i);
  }
}

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();
}

Converting strings to little endian integers. Solution:

#!/usr/bin/env ruby

require 'socket'

host = "127.0.0.1"
port = 2999

s = TCPSocket.new host, port
while line = s.gets
        puts line
        n = [Integer(line.scan(/\d+/).first)].pack("V")
        s.send n, 0
        s.flush
end
s.close
user@protostar:/tmp$ ./net0.rb 
Please send '1690508608' as a little endian 32bit int
Thank you sir/madam

Protostar Net1

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

#define NAME "net1"
#define UID 998
#define GID 998
#define PORT 2998

void run()
{
  char buf[12];
  char fub[12];
  char *q;

  unsigned int wanted;

  wanted = random();

  sprintf(fub, "%d", wanted);

  if(write(0, &wanted, sizeof(wanted)) != sizeof(wanted)) {
      errx(1, ":(\n");
  }

  if(fgets(buf, sizeof(buf)-1, stdin) == NULL) {
      errx(1, ":(\n");
  }

  q = strchr(buf, '\r'); if(q) *q = 0;
  q = strchr(buf, '\n'); if(q) *q = 0;

  if(strcmp(fub, buf) == 0) {
      printf("you correctly sent the data\n");
  } else {
      printf("you didn't send the data properly\n");
  }
}

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();
}

Another easy challenge, we need to convert binary integers to string. Solution:

#!/usr/bin/env ruby

require 'socket'

host = "127.0.0.1"
port = 2998

s = TCPSocket.new host, port

x = s.recv(4).unpack('I').first.to_s
s.send x, 0

puts s.gets
s.close
user@protostar:/tmp$ ./net1.rb 
you correctly sent the data

Protostar Net2

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

#define NAME "net2"
#define UID 997
#define GID 997
#define PORT 2997

void run()
{
  unsigned int quad[4];
  int i;
  unsigned int result, wanted;

  result = 0;
  for(i = 0; i < 4; i++) {
      quad[i] = random();
      result += quad[i];

      if(write(0, &(quad[i]), sizeof(result)) != sizeof(result)) {
          errx(1, ":(\n");
      }
  }

  if(read(0, &wanted, sizeof(result)) != sizeof(result)) {
      errx(1, ":<\n");
  }


  if(result == wanted) {
      printf("you added them correctly\n");
  } else {
      printf("sorry, try again. invalid\n");
  }
}

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();
}
user@protostar:/tmp$ nc 0 2997 | hexdump -C
00000000  85 9e e6 45 93 a9 db 61  6f 22 cd 4b 1d a0 cb 57  |...E...ao".K...W|

We need to add up 4 unsigned 32-bit integers. Solution:

#!/usr/bin/env ruby

require 'socket'

host = "127.0.0.1"
port = 2997

s = TCPSocket.new host, port
n = 0

4.times do 
        n += s.recv(4).unpack('I').first
end
n = [n & 0xffffffff].pack("V")
s.send n, 0

puts s.gets
s.close
user@protostar:/tmp$ ./net2.rb 
you added them correctly

Protostar Net3

#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);
}

It is easy to solve the challenge when we understand the code. For this purposes, we modified the source code to use a terminal for input or output, instead of socket.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>

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

/*
 * 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;

  printf("%d < %d\n", byte, len);
  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");

  printf("success: %d\n", success);

  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;

  read(fd, &len, sizeof(len));
  len = ntohs(len);
  buffer = malloc(len);

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

  read(fd, buffer, len);

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

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

}

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

  srandom(time(NULL));

  run(0);
}

After a few tries, we got:

user@protostar:/tmp$ ruby -e 'print [30].pack("n") + 23.chr + "\x05" + "net3\x00" + "\x0d" +"awesomesauce\x00" + "\x09" + "password\x00"' | ltrace -f ./net3-modified 
[pid 27179] __libc_start_main(0x804895c, 1, 0xbffff874, 0x80489a0, 0x8048990 <unfinished ...>
[pid 27179] time(NULL)                                                                                                        = 1435334239
[pid 27179] srandom(1435334239)                                                                 = <void>
[pid 27179] read(0, "", 2)                                                                      = 2
[pid 27179] ntohs(7680)                                                                         = 30
[pid 27179] malloc(30)                                                                          = 0x0804a008
[pid 27179] read(0, "\027\005net3", 30)                                                         = 30
[pid 27179] printf("%d < %d\n", 5, 295 < 29
)                                                                                        = 7
[pid 27179] malloc(5)                                                                           = 0x0804a030
[pid 27179] strcpy(0x0804a030, "net3")                                                          = 0x0804a030
[pid 27179] printf("%d < %d\n", 13, 2313 < 23
)                                                                                       = 8
[pid 27179] malloc(13)                                                                          = 0x0804a040
[pid 27179] strcpy(0x0804a040, "awesomesauce")                                                  = 0x0804a040
[pid 27179] printf("%d < %d\n", 9, 99 < 9
)                                                                                         = 6
[pid 27179] malloc(9)                                                                           = 0x0804a058
[pid 27179] strcpy(0x0804a058, "password")                                                      = 0x0804a058
[pid 27179] strcmp("net3", "net3")                                                              = 0
[pid 27179] strcmp("awesomesauce", "awesomesauce")                                              = 0
[pid 27179] strcmp("password", "password")                                                      = 0
[pid 27179] printf("success: %d\n", 0success: 0
)                                                                                        = 11
[pid 27179] free(0x0804a030)                                                                    = <void>
[pid 27179] free(0x0804a040)                                                                    = <void>
[pid 27179] free(0x0804a058)                                                                    = <void>
[pid 27179] strlen("successful")                                                                = 10
[pid 27179] ntohs(11)                                                                           = 2816
[pid 27179] strlen("successful")                                                                = 10
[pid 27179] strlen("successful")                                                                = 10
[pid 27179] writev(1, 0xbffff744, 3, 0xb7f06b2c, 30
                                                   !successful)                          = 13
[pid 27179] +++ exited (status 13) +++
user@protostar:/tmp$ ruby -e 'print [30].pack("n") + 23.chr + "\x05" + "net3\x00" + "\x0d" +"awesomesauce\x00" + "\x09" + "password\x00"' | ./net3-modified 
5 < 29
13 < 23
9 < 9
success: 0

!successful
user@protostar:/tmp$ ruby -e 'print [30].pack("n") + 23.chr + "\x05" + "net3\x00" + "\x0d" +"awesomesauce\x00" + "\x09" + "password"' | nc 0 2996
!successful

Protostar Net4

This level has only the binary without description, but after disassembling we realize, that there is nothing to solve:

0804975a <run>:
 804975a:       55                      push   ebp
 804975b:       89 e5                   mov    ebp,esp
 804975d:       5d                      pop    ebp
 804975e:       c3                      ret