/* relay.c by Cecylia Bocovich * * Once a flow has been tagged, this code will extract covert data from the header * of HTTP GET requests and insert downstream data into leaf resources */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "relay.h" #include "slitheen.h" #include "flow.h" #include "crypto.h" #include "util.h" /** Called when a TLS application record is received for a * tagged flow. Upstream packets will be checked for covert * requests to censored sites, downstream packets will be * replaced with data from the censored queue or with garbage * * Inputs: * f: the tagged flow * info: the processed received application packet * * Output: * 0 on success, 1 on failure */ int replace_packet(flow *f, struct packet_info *info){ if (info == NULL || info->tcp_hdr == NULL){ return 0; } #ifdef DEBUG fprintf(stdout,"Flow: %x:%d > %x:%d (%s)\n", info->ip_hdr->src.s_addr, ntohs(info->tcp_hdr->src_port), info->ip_hdr->dst.s_addr, ntohs(info->tcp_hdr->dst_port), (info->ip_hdr->src.s_addr != f->src_ip.s_addr)? "incoming":"outgoing"); fprintf(stdout,"ID number: %u\n", htonl(info->ip_hdr->id)); fprintf(stdout,"Sequence number: %u\n", htonl(info->tcp_hdr->sequence_num)); fprintf(stdout,"Acknowledgement number: %u\n", htonl(info->tcp_hdr->ack_num)); fflush(stdout); #endif if(info->app_data_len <= 0){ return 0; } /* if outgoing, decrypt and look at header */ if(info->ip_hdr->src.s_addr == f->src_ip.s_addr){ read_header(f, info); return 0; } else { #ifdef DEBUG printf("Current sequence number: %d\n", f->downstream_seq_num); printf("Received sequence number: %d\n", htonl(info->tcp_hdr->sequence_num)); #endif uint32_t offset = htonl(info->tcp_hdr->sequence_num) - f->downstream_seq_num; if(offset == 0) f->downstream_seq_num += info->app_data_len; /* if incoming, replace with data from queue */ //if(htonl(tcp_hdr->sequence_num) >= f->seq_num){ process_downstream(f, offset, info); //}//TODO: need to do something about replaying packets (maybe store previously sent data?? #ifdef DEBUG2 //TODO: fix uint8_t *p = (uint8_t *) info->tcp_hdr; fprintf(stdout, "ip hdr length: %d\n", htons(info->ip_hdr->len)); fprintf(stdout, "Injecting the following packet:\n"); for(int i=0; i< htons(info->ip_hdr->len)-1; i++){ fprintf(stdout, "%02x ", p[i]); } fprintf(stdout, "\n"); fflush(stdout); #endif } return 0; } /** Reads the HTTP header of upstream data and searches for * a covert request in an x-slitheen header. Sends this * request to the indicated site and saves the response to * the censored queue * * Inputs: * f: the tagged flow * info: the processed received packet * * Ouput: * 0 on success, 1 on failure */ int read_header(flow *f, struct packet_info *info){ uint8_t *p = info->app_data; if (info->tcp_hdr == NULL){ return 0; } uint8_t *record_ptr = NULL; struct record_header *record_hdr; uint32_t record_length; if(f->upstream_remaining > 0){ //check to see whether the previous record has finished if(f->upstream_remaining > info->app_data_len){ //ignore entire packet for now queue_block *new_block = emalloc(sizeof(queue_block)); uint8_t *block_data = emalloc(info->app_data_len); memcpy(block_data, p, info->app_data_len); new_block->len = info->app_data_len; new_block->offset = 0; new_block->data = block_data; new_block->next = NULL; //add block to upstream data chain if(f->upstream_queue == NULL){ f->upstream_queue = new_block; } else { queue_block *last = f->upstream_queue; while(last->next != NULL){ last = last->next; } last->next = new_block; } f->upstream_remaining -= info->app_data_len; return 0; } else { //process what we have record_hdr = (struct record_header*) f->upstream_queue->data; record_length = RECORD_LEN(record_hdr); record_ptr = emalloc(record_length+ RECORD_HEADER_LEN); queue_block *current = f->upstream_queue; int32_t offset =0; while(f->upstream_queue != NULL){ memcpy(record_ptr+offset, current->data, current->len); offset += current->len; free(current->data); f->upstream_queue = current->next; free(current); current = f->upstream_queue; } memcpy(record_ptr+offset, p, f->upstream_remaining); p = record_ptr; record_hdr = (struct record_header*) p; f->upstream_remaining = 0; } } else { //check to see if the new record is too long record_hdr = (struct record_header*) p; record_length = RECORD_LEN(record_hdr); if(record_length > info->app_data_len){ //add info to upstream queue queue_block *new_block = emalloc(sizeof(queue_block)); uint8_t *block_data = emalloc(info->app_data_len); memcpy(block_data, p, info->app_data_len); new_block->len = info->app_data_len; new_block->offset = record_length; //re-appropriate this for len of record new_block->data = block_data; new_block->next = NULL; //add block to upstream queue if(f->upstream_queue == NULL){ f->upstream_queue = new_block; } else { queue_block *last = f->upstream_queue; while(last->next != NULL){ last = last->next; } last->next = new_block; } f->upstream_remaining = record_length - new_block->len; return 0; } } p+= RECORD_HEADER_LEN; uint8_t *decrypted_data = emalloc(record_length); memcpy(decrypted_data, p, record_length); int32_t decrypted_len = encrypt(f, decrypted_data, decrypted_data, record_length, 0, record_hdr->type, 0); if(decrypted_len<0){ if(record_ptr != NULL) free(record_ptr); free(decrypted_data); return 0; } if(record_hdr->type == 0x15){ printf("received alert\n"); for(int i=0; i %x:%d )\n",info->ip_hdr->src.s_addr,ntohs(info->tcp_hdr->src_port), info->ip_hdr->dst.s_addr, ntohs(info->tcp_hdr->dst_port)); printf("%s\n", decrypted_data+EVP_GCM_TLS_EXPLICIT_IV_LEN); #endif /* search through decrypted data for x-ignore */ char *header_ptr = strstr((const char *) decrypted_data+EVP_GCM_TLS_EXPLICIT_IV_LEN, "X-Slitheen"); uint8_t *upstream_data; if(header_ptr == NULL){ printf("Slitheen header not found(%x:%d > %x:%d) \n",info->ip_hdr->src.s_addr,info->tcp_hdr->src_port, info->ip_hdr->dst.s_addr, info->tcp_hdr->dst_port); fflush(stdout); if(record_ptr != NULL) free(record_ptr); free(decrypted_data); return 0; } #ifdef DEBUG printf("UPSTREAM: Found x-slitheen header\n"); fflush(stdout); fprintf(stdout,"UPSTREAM Flow: %x:%d > %x:%d (%s)\n", info->ip_hdr->src.s_addr,ntohs(info->tcp_hdr->src_port), info->ip_hdr->dst.s_addr, ntohs(info->tcp_hdr->dst_port) ,(info->ip_hdr->src.s_addr != f->src_ip.s_addr)? "incoming":"outgoing"); fprintf(stdout, "Sequence number: %d\n", ntohs(info->tcp_hdr->sequence_num)); #endif header_ptr += strlen("X-Slitheen: "); if(*header_ptr == '\r' || *header_ptr == '\0'){ #ifdef DEBUG printf("No messages\n"); #endif free(decrypted_data); return 0; } int32_t num_messages = 1; char *messages[50]; //TODO:make not just 10? messages[0] = header_ptr; char *c = header_ptr; while(*c != '\r' && *c != '\0'){ if(*c == ' '){ *c = '\0'; messages[num_messages] = c+1; num_messages ++; } c++; } c++; *c = '\0'; #ifdef DEBUG printf("UPSTREAM: Found %d messages\n", num_messages); #endif for(int i=0; i< num_messages-1; i++){ char *message = messages[i]; //b64 decode the data int32_t decode_len = strlen(message); if(message[decode_len-2] == '='){ decode_len = decode_len*3/4 - 2; } else if(message[decode_len-1] == '='){ decode_len = decode_len*3/4 - 1; } else { decode_len = decode_len*3/4; } upstream_data = emalloc(decode_len + 1); BIO *bio, *b64; bio = BIO_new_mem_buf(message, -1); b64 = BIO_new(BIO_f_base64()); bio = BIO_push(b64, bio); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); int32_t output_len = BIO_read(bio, upstream_data, strlen(message)); BIO_free_all(bio); #ifdef DEBUG printf("Decoded to get %d bytes:\n", output_len); for(int j=0; j< output_len; j++){ printf("%02x ", upstream_data[j]); } printf("\n"); fflush(stdout); #endif p = upstream_data; if(i== 0){ //this is the Slitheen ID #ifdef DEBUG printf("Slitheen ID:"); for(int j=0; j< output_len; j++){ printf("%02x ", p[j]); } printf("\n"); #endif //find stream table or create new one client *last = clients->first; while(last != NULL){ if(!memcmp(last->slitheen_id, p, output_len)){ f->streams = last->streams; f->downstream_queue = last->downstream_queue; break; #ifdef DEBUG } else { for(int j=0; j< output_len; j++){ printf("%02x ", last->slitheen_id[j]); } printf(" != "); for(int j=0; j< output_len; j++){ printf("%02x ", p[j]); } printf("\n"); #endif } last = last->next; } if(f->streams == NULL){ //create new client client *new_client = emalloc(sizeof(client)); memcpy(new_client->slitheen_id, p, output_len); new_client->streams = emalloc(sizeof(stream_table)); new_client->streams->first = NULL; new_client->downstream_queue = emalloc(sizeof(data_queue)); new_client->downstream_queue->first_block = NULL; new_client->next = NULL; //add to client table if(clients->first == NULL){ clients->first = new_client; } else { client *last = clients->first; while(last->next != NULL){ last = last->next; } last->next = new_client; } //set f's stream table f->streams = new_client->streams; f->downstream_queue = new_client->downstream_queue; } free(upstream_data); continue; } while(output_len > 0){ struct sl_up_hdr *sl_hdr = (struct sl_up_hdr *) p; uint8_t stream_id = sl_hdr->stream_id; uint16_t stream_len = ntohs(sl_hdr->len); p += sizeof(struct sl_up_hdr); output_len -= sizeof(struct sl_up_hdr); stream_table *streams = f->streams; //If a thread for this stream id exists, get the thread info and pipe data int32_t stream_pipe = -1; stream *last = streams->first; if(streams->first != NULL){ if(last->stream_id == stream_id){ stream_pipe = last->pipefd; } else { while(last->next != NULL){ last = last->next; if(last->stream_id == stream_id){ stream_pipe = last->pipefd; break; } } } } if(stream_pipe != -1){ //check to see if this is a close message //if(stream_len == 0){ //close(stream_pipe); //remove from stream id table //if(last == streams->first){ // streams->first = last->next; //} else { // prev->next = last->next; //} //printf("Freed (1) %p\n", last); //fflush(stdout); //free(last); //break; //} if(stream_len ==0){ close(stream_pipe); break; } #ifdef DEBUG printf("Found stream id %d\n", last->stream_id); #endif int32_t bytes_sent = write(stream_pipe, p, stream_len); if(bytes_sent < 0){ printf("Error sending bytes to stream pipe\n"); } } else if(stream_len > 0){ /*Else, spawn a thread to handle the proxy to this site*/ pthread_t proxy_thread; int32_t pipefd[2]; if(pipe(pipefd) < 0){ free(decrypted_data); if(record_ptr != NULL) free(record_ptr); return 1; } uint8_t *initial_data = emalloc(stream_len); //TODO: ended here. create macro that just exits memcpy(initial_data, p, stream_len); struct proxy_thread_data *thread_data = emalloc(sizeof(struct proxy_thread_data)); thread_data->initial_data = initial_data; thread_data->initial_len = stream_len; thread_data->stream_id = stream_id; thread_data->pipefd = pipefd[0]; thread_data->streams = f->streams; thread_data->downstream_queue = f->downstream_queue; pthread_create(&proxy_thread, NULL, proxy_covert_site, (void *) thread_data); pthread_detach(proxy_thread); //add stream to table stream *new_stream = emalloc(sizeof(stream)); new_stream->stream_id = stream_id; new_stream->pipefd = pipefd[1]; new_stream->next = NULL; if(streams->first == NULL){ streams->first = new_stream; } else { stream *last = streams->first; while(last->next != NULL){ last = last->next; } last->next = new_stream; } } else{ printf("Error, stream len 0\n"); break; } output_len -= stream_len; p += stream_len; } free(upstream_data); } //save a reference to the proxy threads in a global table free(decrypted_data); if(record_ptr != NULL) free(record_ptr); return 0; } /** Called by spawned pthreads in read_header to send upstream * data to the censored site and receive responses. Downstream * data is stored in the slitheen id's downstream_queue. Function and * thread will terminate when the client closes the connection * to the covert destination * * Input: * A struct that contains the following information: * - the tagged flow * - the initial upstream data (including connect request) * - the read end of the pipe * */ void *proxy_covert_site(void *data){ struct proxy_thread_data *thread_data = (struct proxy_thread_data *) data; uint8_t *p = thread_data->initial_data; uint16_t data_len = thread_data->initial_len; uint8_t stream_id = thread_data->stream_id; int32_t bytes_sent; stream_table *streams = thread_data->streams; data_queue *downstream_queue = thread_data->downstream_queue; struct socks_req *clnt_req = (struct socks_req *) p; p += 4; data_len -= 4; int32_t handle = -1; //see if it's a connect request if(clnt_req->cmd != 0x01){ goto err; } struct sockaddr_in dest; dest.sin_family = AF_INET; uint8_t domain_len; switch(clnt_req->addr_type){ case 0x01: //IPv4 dest.sin_addr.s_addr = *((uint32_t*) p); p += 4; data_len -= 4; break; case 0x03: //domain name domain_len = p[0]; p++; data_len --; uint8_t *domain_name = emalloc(domain_len+1); memcpy(domain_name, p, domain_len); domain_name[domain_len] = '\0'; struct hostent *host; host = gethostbyname((const char *) domain_name); dest.sin_addr = *((struct in_addr *) host->h_addr); p += domain_len; data_len -= domain_len; free(domain_name); break; case 0x04: //IPv6 goto err;//TODO: fix this break; } //now set the port dest.sin_port = *((uint16_t *) p); p += 2; data_len -= 2; handle = socket(AF_INET, SOCK_STREAM, 0); if(handle < 0){ goto err; } struct sockaddr_in my_addr; socklen_t my_addr_len = sizeof(my_addr); int32_t error = connect (handle, (struct sockaddr *) &dest, sizeof (struct sockaddr)); if(error <0){ goto err; } getsockname(handle, (struct sockaddr *) &my_addr, &my_addr_len); #ifdef OLD uint8_t *response = emalloc(11); //now send the reply to the client response[0] = 0x05; response[1] = 0x00;//TODO: make this accurate response[2] = 0x00; response[3] = 0x01; *((uint32_t *) (response + 4)) = my_addr.sin_addr.s_addr; *((uint16_t *) (response + 8)) = my_addr.sin_port; printf("Downstream response (id %d):\n", stream_id); for(int i=0; i< 10; i++){ printf("%02x ", response[i]); } printf("\n"); fflush(stdout); //No longer need to send response queue_block *new_block = emalloc(sizeof(queue_block)); new_block->len = 10; new_block->offset = 0; new_block->data = response; new_block->next = NULL; new_block->stream_id = stream_id; if(downstream_queue->first_block == NULL){ downstream_queue->first_block = new_block; } else{ queue_block *last = downstream_queue->first_block; while(last->next != NULL) last = last->next; last->next = new_block; } #endif //see if there were extra upstream bytes if(data_len > 0){ #ifdef DEBUG printf("Data len is %d\n", data_len); printf("Upstream bytes: "); for(int i=0; i< data_len; i++){ printf("%02x ", p[i]); } printf("\n"); #endif bytes_sent = send(handle, p, data_len, 0); if( bytes_sent <= 0){ goto err; } } uint8_t *buffer = emalloc(BUFSIZ); int32_t buffer_len = BUFSIZ; //now select on reading from the pipe and from the socket for(;;){ fd_set readfds; fd_set writefds; int32_t nfds = (handle > thread_data->pipefd) ? handle +1 : thread_data->pipefd + 1; FD_ZERO(&readfds); FD_ZERO(&writefds); FD_SET(thread_data->pipefd, &readfds); FD_SET(handle, &readfds); FD_SET(handle, &writefds); if (select(nfds, &readfds, &writefds, NULL, NULL) < 0){ printf("select error\n"); break; } if(FD_ISSET(thread_data->pipefd, &readfds) && FD_ISSET(handle, &writefds)){ //we have upstream data ready for writing int32_t bytes_read = read(thread_data->pipefd, buffer, buffer_len); if(bytes_read > 0){ #ifdef DEBUG printf("PROXY (id %d): read %d bytes from pipe\n", stream_id, bytes_read); for(int i=0; i< bytes_read; i++){ printf("%02x ", buffer[i]); } printf("\n"); printf("%s\n", buffer); #endif bytes_sent = send(handle, buffer, bytes_read, 0); if( bytes_sent <= 0){ break; } else if (bytes_sent < bytes_read){ //TODO: should update buffer and keep //track of length of upstream data break; } } else { printf("PROXY (id %d): read %d bytes from pipe\n", stream_id, bytes_read); break; } } if (FD_ISSET(handle, &readfds)){ //we have downstream data read for saving int32_t bytes_read; bytes_read = recv(handle, buffer, buffer_len, 0); if(bytes_read > 0){ uint8_t *new_data = emalloc(bytes_read); memcpy(new_data, buffer, bytes_read); #ifdef DEBUG printf("PROXY (id %d): read %d bytes from censored site\n",stream_id, bytes_read); for(int i=0; i< bytes_read; i++){ printf("%02x ", buffer[i]); } printf("\n"); #endif //make a new queue block queue_block *new_block = emalloc(sizeof(queue_block)); new_block->len = bytes_read; new_block->offset = 0; new_block->data = new_data; new_block->next = NULL; new_block->stream_id = stream_id; if(downstream_queue->first_block == NULL){ downstream_queue->first_block = new_block; } else{ queue_block *last = downstream_queue->first_block; while(last->next != NULL) last = last->next; last->next = new_block; } } else { printf("PROXY (id %d): read %d bytes from censored site\n",stream_id, bytes_read); break; } } } printf("Closing connection for stream %d\n", stream_id); //remove self from list stream *last = streams->first; stream *prev = last; if(streams->first != NULL){ if(last->stream_id == stream_id){ streams->first = last->next; printf("Freeing (2) %p\n", last); free(last); } else { while(last->next != NULL){ prev = last; last = last->next; if(last->stream_id == stream_id){ prev->next = last->next; printf("Freeing (2) %p\n", last); free(last); break; } } } } if(thread_data->initial_data != NULL){ free(thread_data->initial_data); } free(thread_data); free(buffer); close(handle); pthread_detach(pthread_self()); pthread_exit(NULL); return 0; err: //remove self from list last = streams->first; prev = last; if(streams->first != NULL){ if(last->stream_id == stream_id){ streams->first = last->next; free(last); } else { while(last->next != NULL){ prev = last; last = last->next; if(last->stream_id == stream_id){ prev->next = last->next; free(last); break; } } } } if(thread_data->initial_data != NULL){ free(thread_data->initial_data); } free(thread_data); if(handle > 0){ close(handle); } pthread_detach(pthread_self()); pthread_exit(NULL); return 0; } /** Replaces downstream record contents with data from the * censored queue, padding with garbage bytes if no more * censored data exists. * * Inputs: * f: the tagged flow * data: a pointer to the received packet's application * data * data_len: the length of the packet's application data * offset: if the packet is misordered, the number of * application-level bytes in missing packets * * Output: * Returns 0 on sucess */ int process_downstream(flow *f, int32_t offset, struct packet_info *info){ uint8_t changed = 0; uint8_t *p = info->app_data; uint32_t remaining_packet_len = info->app_data_len; if(f->remaining_record_len > 0){ //ignore bytes until the end of the record if(f->remaining_record_len > remaining_packet_len){ //ignore entire packet if(f->outbox_len > 0){ changed = 1; memcpy(p, f->outbox + f->outbox_offset, remaining_packet_len); f->outbox_len -= remaining_packet_len; f->outbox_offset += remaining_packet_len; } f->remaining_record_len -= remaining_packet_len; remaining_packet_len -= remaining_packet_len; } else { if(f->outbox_len > 0){ changed = 1; memcpy(p, f->outbox + f->outbox_offset, f->remaining_record_len); f->outbox_len = 0; f->outbox_offset=0; free(f->outbox); } p += f->remaining_record_len; remaining_packet_len -= f->remaining_record_len; f->remaining_record_len = 0; } } while(remaining_packet_len > 0){ //while bytes remain in the packet if(remaining_packet_len < RECORD_HEADER_LEN){ #ifdef DEBUG printf("partial record header: \n"); for(int i= 0; i< remaining_packet_len; i++){ printf("%02x ", p[i]); } printf("\n"); fflush(stdout); #endif f->partial_record_header = emalloc(RECORD_HEADER_LEN); memcpy(f->partial_record_header, p, remaining_packet_len); f->partial_record_header_len = remaining_packet_len; remaining_packet_len -= remaining_packet_len; break; } struct record_header *record_hdr; if(f->partial_record_header_len > 0){ memcpy(f->partial_record_header+ f->partial_record_header_len, p, RECORD_HEADER_LEN - f->partial_record_header_len); record_hdr = (struct record_header *) f->partial_record_header; } else { record_hdr = (struct record_header*) p; } uint32_t record_len = RECORD_LEN(record_hdr); #ifdef DEBUG fprintf(stdout,"Flow: %x > %x (%s)\n", info->ip_hdr->src.s_addr, info->ip_hdr->dst.s_addr, (info->ip_hdr->src.s_addr != f->src_ip.s_addr)? "incoming":"outgoing"); fprintf(stdout,"ID number: %u\n", htonl(info->ip_hdr->id)); fprintf(stdout,"Sequence number: %u\n", htonl(info->tcp_hdr->sequence_num)); fprintf(stdout,"Acknowledgement number: %u\n", htonl(info->tcp_hdr->ack_num)); fprintf(stdout, "Record:\n"); for(int i=0; i< RECORD_HEADER_LEN; i++){ printf("%02x ", ((uint8_t *) record_hdr)[i]); } printf("\n"); #endif p += (RECORD_HEADER_LEN - f->partial_record_header_len); remaining_packet_len -= (RECORD_HEADER_LEN - f->partial_record_header_len); uint8_t *record_ptr = p; //points to the beginning of record data uint32_t remaining_record_len = record_len; if(record_len > remaining_packet_len){ f->remaining_record_len = record_len - remaining_packet_len; if(f->httpstate == PARSE_HEADER || f->httpstate == BEGIN_CHUNK || f->httpstate == END_CHUNK){ f->httpstate = FORFEIT_REST; } else if( f->httpstate == MID_CONTENT || f->httpstate == MID_CHUNK){ f->remaining_response_len -= record_len - 24; //len of IV and padding if(f->remaining_response_len >= 0 && f->replace_response){ //#ifdef nothing //make a huge record, encrypt it, and then place it in the outbox f->outbox = emalloc(record_len+1); f->outbox_len = record_len; f->outbox_offset = 0; fill_with_downstream(f, f->outbox + EVP_GCM_TLS_EXPLICIT_IV_LEN , record_len - (EVP_GCM_TLS_EXPLICIT_IV_LEN+ 16)); //for now hard coded length of padding. TODO: fix this //encrypt int32_t n = encrypt(f, f->outbox, f->outbox, record_len - 16, 1, record_hdr->type, 1); if(n < 0){ fprintf(stdout,"outbox encryption failed\n"); } else { memcpy(p, f->outbox, remaining_packet_len); changed = 1; f->outbox_len -= remaining_packet_len; f->outbox_offset += remaining_packet_len; } //#endif } if(f->remaining_response_len == 0){ if(f->httpstate == MID_CHUNK) f->httpstate = END_CHUNK; else { f->httpstate = PARSE_HEADER; } } if(f->remaining_response_len < 0){ f->remaining_response_len = 0; f->httpstate = FORFEIT_REST; } } remaining_packet_len -= remaining_packet_len; if(f->partial_record_header_len > 0){ f->partial_record_header_len = 0; free(f->partial_record_header); } break; } //now decrypt the record int32_t n = encrypt(f, record_ptr, record_ptr, record_len, 1, record_hdr->type, 0); if(n < 0){ //do something smarter here if(f->partial_record_header_len > 0){ f->partial_record_header_len = 0; free(f->partial_record_header); } return 0; } changed = 1; #ifdef DEBUG_DOWNSTREAM printf("Decryption succeeded\n"); printf("Bytes:\n"); for(int i=0; i< n; i++){ printf("%02x ", record_ptr[EVP_GCM_TLS_EXPLICIT_IV_LEN+i]); } printf("\n"); printf("Text:\n"); printf("%s\n", record_ptr+EVP_GCM_TLS_EXPLICIT_IV_LEN); #endif p += EVP_GCM_TLS_EXPLICIT_IV_LEN; char *len_ptr, *needle; remaining_record_len = n; while(remaining_record_len > 0){ switch(f->httpstate){ case PARSE_HEADER: //determine whether it's transfer encoded or otherwise //figure out what the content-type is len_ptr = strstr((const char *) p, "Content-Type: image"); if(len_ptr != NULL){ f->replace_response = 1; memcpy(len_ptr + 14, "slitheen", 8); char *c = len_ptr + 14+8; while(c[0] != '\r'){ c[0] = ' '; c++; } } else { f->replace_response = 0; } len_ptr = strstr((const char *) p, "Transfer-Encoding"); if(len_ptr != NULL){ if(!memcmp(len_ptr + 19, "chunked", 7)){ //now find end of header //printf("chunked encoding\n"); len_ptr = strstr((const char *) p, "\r\n\r\n"); if(len_ptr != NULL){ f->httpstate = BEGIN_CHUNK; remaining_record_len -= (((uint8_t *)len_ptr - p) + 4); p = (uint8_t *) len_ptr + 4; } } } else { len_ptr = strstr((const char *) p, "Content-Length"); if(len_ptr != NULL){ len_ptr += 15; f->remaining_response_len = strtol((const char *) len_ptr, NULL, 10); //printf("content-length: %d\n", f->remaining_response_len); len_ptr = strstr((const char *) p, "\r\n\r\n"); if(len_ptr != NULL){ f->httpstate = MID_CONTENT; remaining_record_len -= (((uint8_t *)len_ptr - p) + 4); p = (uint8_t *) len_ptr + 4; } else { remaining_record_len = 0; //printf("Missing end of header. Sending to FORFEIT_REST\n"); f->httpstate = FORFEIT_REST; } } else { //printf("No content length of transfer encoding field, sending to FORFEIT_REST\n"); f->httpstate = FORFEIT_REST; remaining_record_len = 0; } } break; case MID_CONTENT: //check if content is replaceable if(f->remaining_response_len > remaining_record_len){ if(f->replace_response){ fill_with_downstream(f, p, remaining_record_len); #ifdef DEBUG printf("Replaced with:\n"); for(int i=0; i< remaining_record_len; i++){ printf("%02x ", p[i]); } printf("\n"); #endif } f->remaining_response_len -= remaining_record_len; p += remaining_record_len; remaining_record_len = 0; } else { if(f->replace_response){ fill_with_downstream(f, p, remaining_record_len); #ifdef DEBUG printf("Replaced with:\n"); for(int i=0; i< remaining_record_len; i++){ printf("%02x ", p[i]); } printf("\n"); #endif } remaining_record_len -= f->remaining_response_len; p += f->remaining_response_len; f->httpstate = PARSE_HEADER; f->remaining_response_len = 0; } break; case BEGIN_CHUNK: { int32_t chunk_size = strtol((const char *) p, NULL, 16); if(chunk_size == 0){ f->httpstate = END_BODY; } else { f->httpstate = MID_CHUNK; } f->remaining_response_len = chunk_size; needle = strstr((const char *) p, "\r\n"); if(needle != NULL){ remaining_record_len -= ((uint8_t *) needle - p + 2); p = (uint8_t *) needle + 2; } else { remaining_record_len = 0; printf("Couldn't find chunk, sending to FORFEIT_REST\n"); f->httpstate = FORFEIT_REST; } } break; case MID_CHUNK: if(f->remaining_response_len > remaining_record_len){ if(f->replace_response){ fill_with_downstream(f, p, remaining_record_len); #ifdef DEBUG printf("Replaced with:\n"); for(int i=0; i< remaining_record_len; i++){ printf("%02x ", p[i]); } printf("\n"); #endif } f->remaining_response_len -= remaining_record_len; p += remaining_record_len; remaining_record_len = 0; } else { if(f->replace_response){ fill_with_downstream(f, p, f->remaining_response_len); #ifdef DEBUG printf("Replaced with:\n"); for(int i=0; i< f->remaining_response_len; i++){ printf("%02x ", p[i]); } printf("\n"); #endif } remaining_record_len -= f->remaining_response_len; p += f->remaining_response_len; f->remaining_response_len = 0; f->httpstate = END_CHUNK; } break; case END_CHUNK: needle = strstr((const char *) p, "\r\n"); if(needle != NULL){ f->httpstate = BEGIN_CHUNK; p += 2; remaining_record_len -= 2; } else { remaining_record_len = 0; //printf("Couldn't find end of chunk, sending to FORFEIT_REST\n"); f->httpstate = FORFEIT_REST; } break; case END_BODY: needle = strstr((const char *) p, "\r\n"); if(needle != NULL){ f->httpstate = PARSE_HEADER; p += 2; remaining_record_len -= 2; } else { remaining_record_len = 0; //printf("Couldn't find end of body, sending to FORFEIT_REST\n"); f->httpstate = FORFEIT_REST; } break; case FORFEIT_REST: case USE_REST: remaining_record_len = 0; break; default: break; } } if((n = encrypt(f, record_ptr, record_ptr, n + EVP_GCM_TLS_EXPLICIT_IV_LEN, 1, record_hdr->type, 1)) < 0){ if(f->partial_record_header_len > 0){ f->partial_record_header_len = 0; free(f->partial_record_header); } return 0; } p = record_ptr + record_len; remaining_packet_len -= record_len; if(f->partial_record_header_len > 0){ f->partial_record_header_len = 0; free(f->partial_record_header); } } if(changed){ tcp_checksum(info); } return 0; } /** Fills a given pointer with downstream data of the specified length. If no downstream data * exists, pads it with garbage bytes. All downstream data is accompanied by a stream id and * lengths of both the downstream data and garbage data * * Inputs: * data: a pointer to where the downstream data should be entered * length: The length of the downstream data required * */ int fill_with_downstream(flow *f, uint8_t *data, int32_t length){ uint8_t *p = data; int32_t remaining = length; struct slitheen_header *sl_hdr; data_queue *downstream_queue = f->downstream_queue; //Fill as much as we can from the censored_queue while((remaining > SLITHEEN_HEADER_LEN) && downstream_queue != NULL && downstream_queue->first_block != NULL){ queue_block *first_block = downstream_queue->first_block; int32_t block_length = first_block->len; int32_t offset = first_block->offset; #ifdef DEBUG printf("Censored queue is at %p.\n", first_block); printf("This block has %d bytes left\n", block_length - offset); printf("We need %d bytes\n", remaining - SLITHEEN_HEADER_LEN); #endif sl_hdr = (struct slitheen_header *) p; sl_hdr->stream_id = first_block->stream_id; sl_hdr->len = 0x00; sl_hdr->garbage = 0x00; p += SLITHEEN_HEADER_LEN; remaining -= SLITHEEN_HEADER_LEN; if(block_length > offset + remaining){ //use part of the block, update offset memcpy(p, first_block->data+offset, remaining); first_block->offset += remaining; p += remaining; sl_hdr->len = remaining; remaining -= remaining; } else { //use all of the block and free it memcpy(p, first_block->data+offset, block_length - offset); free(first_block->data); downstream_queue->first_block = first_block->next; free(first_block); p += (block_length - offset); sl_hdr->len = (block_length - offset); remaining -= (block_length - offset); } sl_hdr->len = htons(sl_hdr->len); #ifdef DEBUG printf("DWNSTRM: slitheen header: "); for(int i=0; i< SLITHEEN_HEADER_LEN; i++){ printf("%02x ",((uint8_t *) sl_hdr)[i]); } printf("\n"); printf("Sending %d downstream bytes:", ntohs(sl_hdr->len)); for(int i=0; i< ntohs(sl_hdr->len); i++){ printf("%02x ", ((uint8_t *) sl_hdr)[i+SLITHEEN_HEADER_LEN]); } printf("\n"); #endif } //now, if we need more data, fill with garbage if(remaining > SLITHEEN_HEADER_LEN ){ //TODO: note, we may also be receiving misordered packets. Take Ian's suggestion into account here sl_hdr = (struct slitheen_header *) p; sl_hdr->stream_id = 0x00; remaining -= SLITHEEN_HEADER_LEN; sl_hdr->len = htons(remaining); sl_hdr->garbage = htons(remaining); #ifdef DEBUG printf("DWNSTRM: slitheen header: "); for(int i=0; i< SLITHEEN_HEADER_LEN; i++){ printf("%02x ", p[i]); } printf("\n"); #endif p += SLITHEEN_HEADER_LEN; memset(p, 'A', remaining); } return 0; } /** Computes the TCP checksum of the data according to RFC 793 * sum all 16-bit words in the segment, pad the last word if * needed * * there is a pseudo-header prefixed to the segment and * included in the checksum: * * +--------+--------+--------+--------+ * | Source Address | * +--------+--------+--------+--------+ * | Destination Address | * +--------+--------+--------+--------+ * | zero | PTCL | TCP Length | * +--------+--------+--------+--------+ */ uint16_t tcp_checksum(struct packet_info *info){ uint16_t tcp_length = info->app_data_len + info->size_tcp_hdr; struct in_addr src = info->ip_hdr->src; struct in_addr dst = info->ip_hdr->dst; uint8_t proto = IPPROTO_TCP; //set the checksum to zero info->tcp_hdr->chksum = 0; //sum pseudoheader uint32_t sum = (ntohl(src.s_addr)) >> 16; sum += (ntohl(src.s_addr)) &0xFFFF; sum += (ntohl(dst.s_addr)) >> 16; sum += (ntohl(dst.s_addr)) & 0xFFFF; sum += proto; sum += tcp_length; //sum tcp header (with zero-d checksum) uint8_t *p = (uint8_t *) info->tcp_hdr; for(int i=0; i < info->size_tcp_hdr; i+=2){ sum += (uint16_t) ((p[i] << 8) + p[i+1]); } //now sum the application data p = info->app_data; for(int i=0; i< info->app_data_len-1; i+=2){ sum += (uint16_t) ((p[i] << 8) + p[i+1]); } if(info->app_data_len %2 != 0){ sum += (uint16_t) (p[info->app_data_len - 1]) << 8; } //now add most significant to last significant bits sum = (sum >> 16) + (sum & 0xFFFF); sum += sum >>16; //now subtract from 0xFF sum = 0xFFFF - sum; //set chksum to calculated value info->tcp_hdr->chksum = ntohs(sum); return (uint16_t) sum; }