Explaining Sockets and Terrifying You With C

One of the things any aspiring security professional or network admin should know is - what is exactly a socket and what do they do?. This is tutorial is a different approach that I have been toying with on how to best explain to beginning IT or security students who are just beginning, but have no experience using "low-level" languages like C. One of my esteemed professor's once said: "God intended us to program in C" and I believe that today's programmers should begin with a language which will teach them responsibility. C is almost as close to the hardware you can go albeit learning assembly language and therefore the most basic understanding of what a socket is combined with a tidbit of knowledge in C.

What Is A Socket??

According to Wikipedia: A network socket is an endpoint of an inter-process communication flow across a computer network. Today, most communication between computers is based on the Internet Protocol; therefore most network sockets are Internet sockets.

A socket address is the combination of an IP address and a port number, much like one end of a telephone connection is the combination of a phone number and a particular extension. Based on this address, internet sockets deliver incoming data packets to the appropriate application process or thread.

OK so a socket is essentially, an interface address bound to a port and therefore a socket is the combination of the two!

What Does A Socket Mean To Me??

Well if a socket is an IP and a port - if you see one on your system, that means that you have an application running that is able to send or receive connections from the network. If your auditing your system(s), and there is a previously unknown application with network access - you should check it out!

Lets Create A TCP Echo Server and Client

While there are many tutorials on the Internet that beat this subject up until it is bloody, I want to work my way through it in layman's terms and explain how a security professional or IT technician can detect or monitor which sockets are open on their systems. In other words, your going to create a TCP Echo Server in C using sockets!

If you are in any modern Linux distribution, install an editor or IDE of your choice - I prefer Geany, but gedit would be okay. Also install gcc.

Create two files: server.c & client.c with the following code in each of them. I'll explain it as I go:

  1. /* Almost always default includes for any C program */
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6.  
  7. /* These three includes provide the functionality required by network sockets */
  8. #include <sys/types.h>
  9. #include <sys/socket.h>
  10. #include <netinet/in.h>
  11.  
  12. /* This function is here just to print out any errors */
  13. void error(const char *msg)
  14. {
  15.     perror(msg);
  16.     exit(1);
  17. }
  18.  
  19. /* In C, each program has a main that accepts a number of arguments that are
  20.  * passed in through stdin */
  21. int main(int argc, char *argv[])
  22. {
  23.  
  24. return (0);
  25. }

So here is what you have done, you have added the necessary includes that sockets require in C. Think of these like includes in PHP or the imports in Java. The last two includes are the most important for sockets; sys/socket.h and netinet/in.h

Following that you have created a function that will print out any errors should they occur; these errors could mean that a socket is previously in use or you don't have permission to create a socket for example. After that there is a main function that accepts two arguments: int argc and char *argv[ ]. Every C program has a main function which contains the code that will be executed when the binary is ran. It returns a 0 before it exits, and its two parameters are:

  • argc is an integer, which is the number of arguments when the binary was executed - the command or binary is included in the count.
  • argv[] is an array of the arguments being given to the program on execution

Inside the main, at the top of the function add the following variable declarations:

  1.    /* integers that will be our socket descriptors */
  2.     int sockfd, newsockfd;
  3.     /* integer that will be our port number */
  4.     int portno;
  5.  
  6.     /* Size_t type that will contain client length */
  7.     socklen_t clilen;
  8.  
  9.     /* Buffer that will contain our client/server data */
  10.     char buffer[256];
  11.  
  12.     /* Socket address structures for the server and client sockets */
  13.     struct sockaddr_in serv_addr, cli_addr;
  14.    
  15.     /* Integer that will store any bytes transferred over the network */
  16.     int n;

Each function is fairly self-explanatory, but take notice of the buffer and sockaddr variables. Below the variable declarations, add the following if statement - it checks to see if you the user has given the application a port number when the application was executed.

  1.    /* Check number of arguments, if there is less than two then exit
  2.      * Note: the binary counts as one. IE: ./server (1) port (2) */
  3.     if (argc < 2) {
  4.         fprintf(stderr, "ERROR, no port provided\n");
  5.         exit(1);
  6.     }

After the above code, add the following snippet of code that will create a socket. If you recall Linux's internals, everything in Linux uses file descriptors - sockets use them too! The socket function call will create a TCP socket with no options - this is set using the AF_INET parameter which states that the socket will be using the TCP/IP family and SOCK_STREAM which specifies that the socket will be TCP. Then following that there is an if statement that checks for an error opening the socket.

  1.    /* Create a TCP/IP TCP socket and assign it to sockfd using socket() */
  2.     sockfd = socket(AF_INET, SOCK_STREAM, 0);
  3.  
  4.     /* If socket returns an error, print one out */
  5.     if (sockfd < 0) {
  6.         error("ERROR opening socket");
  7.     }

Next, we setup and configure the server's socket_address structure. Essentially, this code specifies that the socket will use the port number argument supplied by the user, for it to use any network interface and using the bind function call - bind the socket address structure to the file descriptor. Add the following code again below the previous snippet.

  1. /* Clear and set the serv_addr structure to all zeros in memory */
  2.     bzero((char *) &serv_addr, sizeof(serv_addr));
  3.  
  4.     portno = atoi(argv[1]);     // Set the port number to this
  5.  
  6.     /* Setup server structure so the socket can be bound to the socket descriptor */
  7.     serv_addr.sin_family = AF_INET;
  8.     serv_addr.sin_addr.s_addr = INADDR_ANY;
  9.     serv_addr.sin_port = htons(portno); // portno must be converted to network byte order
  10.  
  11.     /* Bind the server file descriptor to the server socket address */
  12.     if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
  13.         error("ERROR on binding");
  14.     }

The following chunk of code sets the socket to listen for up to five connections, and to accept any new connections and assign them to sockfd.

  1. /* Listen for up to five incoming connections */
  2.     listen(sockfd, 5);         
  3.     clilen = sizeof(cli_addr);
  4.  
  5.         /* Accept new client connections */
  6.     newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
  7.  
  8.         /* Print error if there is an error on accepting a new client connection */
  9.     if (newsockfd < 0) {
  10.         error("ERROR on accept");
  11.     }

Between the above code and the return 0 statement, add the following code that zeros the receiving buffer, reads any incoming data to the buffer, sends the string "I got your message" back to the client using the write function and closes both sockets.

  1.         /* Zero the buffer to make sure there is no extra garbage
  2.          * that was previously in memory */
  3.     bzero(buffer, 256);
  4.    
  5.     /* Read from the buffer 255 bytes (from the incoming connection)
  6.      * Note: 255 to prevent an overflow ;) */
  7.     n = read(newsockfd, buffer, 255);
  8.  
  9.         /* If no bytes were read - print an error */
  10.     if (n < 0) {
  11.         error("ERROR reading from socket");
  12.         printf("Here is the message: %s\n", buffer);
  13.     }
  14.  
  15.     printf("We received: %s\n", buffer);
  16.        
  17.         /* Now write "I got your message" back to the client (this is 18 bytes) */
  18.     n = write(newsockfd, "I got your message", 18);
  19.  
  20.         /* If no bytes were not sent - print an error */
  21.     if (n < 0) {
  22.         error("ERROR writing to socket");
  23.     }
  24.    
  25.     /* Remember to close the socket descriptors for clean programming */
  26.     close(newsockfd);
  27.     close(sockfd);

Now that you have all of the essential code - you will need a client! We could duplicate the code and create one OR we can cheat and use Netcat or NC to generate text for the server. To do this see if you have nc installed by typing nc in the terminal and press Enter. I won't tell you what netcat is great for (its like a networking swiss army knife or backdoor), but if your bored look at the man page for it.

Now we will compile the application with gcc, execute it and send it a test message:

  1. In one terminal:
  2. gcc server.c -o server
  3. sudo ./server 6000
  4.  
  5. In a second terminal:
  6. echo "Test message" | nc 127.0.0.1 6000
  7.  
  8. In both terminals you should see a message in the output.

Now that you have verified your server works correctly, run it again, but do not send it a message. Lets change gears and put on the security hat - how can you tell that there are open sockets on the system? The answer is, you can use lsof , ps and netstat. The netstat tool provides visibility into the GNU/Linux networking subsystem. With netstat, you can view currently active connections (on a per-protocol basis), view connections in a particular state (such as server sockets in the listening state), and many others. Here are some of the options that netstat provides and the features they enable.

  1. View all TCP sockets currently active
  2. $ netstat --tcp
  3.  
  4. View all UDP sockets
  5. $ netstat --udp
  6.  
  7. View all TCP sockets in the listening state
  8. $ netstat --listening
  9.  
  10. View the multicast group membership information
  11. $ netstat --groups
  12.  
  13. Display the list of masqueraded connections
  14. $ netstat --masquerade
  15.  
  16. View statistics for each protocol
  17. $ netstat --statistics
  18.  
  19. Alternatively - port 6000 is listed below:
  20. [rbrash@ord Desktop]$ netstat -ltn
  21. Active Internet connections (only servers)
  22. Proto Recv-Q Send-Q Local Address           Foreign Address         State      
  23. tcp        0      0 0.0.0.0:49001           0.0.0.0:*               LISTEN    
  24. tcp        0      0 0.0.0.0:875             0.0.0.0:*               LISTEN    
  25. tcp        0      0 0.0.0.0:55085           0.0.0.0:*               LISTEN    
  26. tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN    
  27. tcp        0      0 0.0.0.0:6000            0.0.0.0:*               LISTEN    
  28. tcp        0      0 0.0.0.0:20048           0.0.0.0:*               LISTEN    
  29. tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN    
  30. tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN    
  31. tcp        0      0 0.0.0.0:36151           0.0.0.0:*               LISTEN    
  32. tcp        0      0 0.0.0.0:2049            0.0.0.0:*               LISTEN    
  33. tcp6       0      0 :::53578                :::*                    LISTEN    
  34. tcp6       0      0 :::111                  :::*                    LISTEN    
  35. tcp6       0      0 :::20048                :::*                    LISTEN    
  36. tcp6       0      0 :::22                   :::*                    LISTEN    
  37. tcp6       0      0 :::631                  :::*                    LISTEN    
  38. tcp6       0      0 :::33112                :::*                    LISTEN    
  39. tcp6       0      0 :::2049                 :::*                    LISTEN    

One thing to note that running binaries can be running using a name different than that of the binary itself - lsof is one tool that can help you find them as well.

  1. [rbrash@ord Desktop]$ sudo lsof -c server
  2. COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
  3. server  8870 root  cwd    DIR  253,2     4096 24641550 /home/rbrash/Desktop
  4. server  8870 root  rtd    DIR  253,1     4096        2 /
  5. server  8870 root  txt    REG  253,2     9108 24641712 /home/rbrash/Desktop/server
  6. server  8870 root  mem    REG  253,1   158440  2231555 /usr/lib64/ld-2.15.so
  7. server  8870 root  mem    REG  253,1  2065544  2231556 /usr/lib64/libc-2.15.so
  8. server  8870 root    0u   CHR  136,0      0t0        3 /dev/pts/0
  9. server  8870 root    1u   CHR  136,0      0t0        3 /dev/pts/0
  10. server  8870 root    2u   CHR  136,0      0t0        3 /dev/pts/0
  11. server  8870 root    3u  IPv4 291154      0t0      TCP *:x11 (LISTEN)

Conclusion

Hopefully by now - you have a better understanding of what sockets are and what you can do to find them running on your system - note that in linux is common for normal system processes to use sockets for communication!

I borrowed liberally from this tutorial, but if you want more information - here it is: http://www.linuxhowtos.org/C_C++/socket.htm

Blog tags: 

Comments

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.