10.FingerPrint Locks

Keylogger attacks in network security have become more popular over time. Therefore, businesses must implement procedures and tactics to prevent these network security issues from harming a server.

This article will discuss anti-debugging techniques for keyloggers so you can help your organization improve its security posture.

What is a Keylogger Attack?

Keyloggers, or keystroke logging, is a data collection software that keeps track of the keys you hit on your keyboard. Cybercriminals will record anything you type so they can utilize that data to learn account numbers, credit card information, and login credentials that could permit them to damage your system. Hackers can send malicious code through phishing emails that immediately install attacks once the recipient opens links or attachments. Threat actors write down the keystrokes, pass the data through encryption, and send it to another computer that unencrypted the information to use in the future. This type of threat works over malware and ransomware, so victims must pay a ransom to return their data.

What is the Purpose of a Keylogger Email?

This software, though typically carrying a negative connotation, can help users analyze and debug computer activity in a legal, legitimate format. Keyloggers can intercept or alter electronic data and collect application information to prevent cloud security breaches and learn more about how users interact with the system.

What Is Anti-Debugging?

LinuxmalwareMalware analysts must debug malware codes to run step-by-step malware, facilitate malware behavior and capabilities, and introduce changes across memory spaces, variables, and configurations. As a result, preventing malware authors from debugging is crucial to keeping a system secure.

Anti-debugging focuses on preventing or terminating malicious activity involving debuggers to ensure data and network security on your server. A few techniques are generalized to any debugger, while others are specific to a particular debugger version. Here are a few methods you can implement to stop cloud security breaches on your server:

  • Timing analysis
  • Detecting known processes
  • Checking process status
  • Self-debugging code
  • Detecting breakpoints
  • Detecting code patching
  • In-memory hypervisor
  • Non-standard architecture emulation

We will discuss the first couple of options in this article, and part 2 will review the rest.

What is Timing Analysis?

Timing analysis seeks to detect pauses and long delays in program execution so you can decide how to alter server behavior to stop keylogging attacks in their tracks. This method is the easiest to implement but receives the most false positives and can be disabled quickly. Here is how you can set up timing analysis in your coding:


#include <chrono>
#include <filesystem>
#include <string>
#include <thread>

using namespace std::chrono_literals;

int main() {
    std::chrono::steady_clock::time_point begin, end;
    std::cout << "Hello, World!" << std::endl;

    while (true) {
        begin = std::chrono::steady_clock::now();
        for (auto &p : std::filesystem::directory_iterator("/proc")) {
            std::string pid = p.path().filename().string();

            try {
                long _pid = std::stol(pid);
                std::cout << _pid << " " << std::flush;
            }
            catch (...) {
                continue;
            }
        }
        std::this_thread::sleep_for(1s);
        end = std::chrono::steady_clock::now();

        unsigned long duration = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count();

        if (duration > 1010000) {
            std::cerr << "Debugging attempt detected (" << duration << " us)" << std::endl;
            break;
        }
    }

    return 0;
}

How Can I Check Common Debugger Processes?

You might be more familiar with the *IsDebuggerPresent* configuration as a Windows user. The technique here is the Linux equivalent, where Linux will indicate the tracer process ID in the **/proc/self/status** file. The tracer's PID field will display the debugger or tracer process ID if one is present in your processes. If there is not one present, you will see a 0. Here is an example of this implementation:


#include <cstring>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <linux/limits.h>
#include <unistd.h>

bool hasEnding (std::string const &fullString, std::string const &ending) {
    if (fullString.length() >= ending.length()) {
        return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
    } else {
        return false;
    }
}

bool isUnderDebugger()
{
    bool result = false;
    /*
     * /proc/self/stat has PID of parent process as fourth parameter.
     */
    std::string stat;
    std::ifstream file("/proc/self/stat");

    for(int i = 0; i < 4; ++i)
        file >> stat;

    std::string parent_path = std::string("/proc/") + stat + "/exe";
    char path[PATH_MAX + 1];
    memset(path, 0, PATH_MAX + 1);
    readlink(parent_path.c_str(), path, PATH_MAX);

    std::vector<std::string> debuggers = {"gdb", "lldb-server"};

    for (auto &p: debuggers)
    {
        if (hasEnding(std::string(path), p))
        {
            result = true;
            break;
        }
    }

    return result;
}

int main() {
    if (isUnderDebugger())
        std::cout << "I am being debugged." << std::endl;
    else
        std::cout << "I am not being debugged" << std::endl;
    return 0;
}

How Do I Check the Status of Processing?

If you are a Windows user, you might be more familiar with the *IsDebuggerPresent* configuration. The technique here is the Linux equivalent, where Linux will indicate the tracer process ID in the **/proc/self/status** file. The tracer’s PID field will display the debugger or tracer process ID if there is one present in your processes. If there is not one present, you will see a 0. Here is an example of this implementation:


#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

bool isUnderDebugger()
{
    bool result = false;

    std::string line;
    std::ifstream file("/proc/self/status");

    while (std::getline(file, line))
    {
        std::istringstream _stream(line);
        std::string tag, value;
        _stream >> tag >> value;
        
        if (tag == "TracerPid:" && value != "0")
            result = true;
    }

    return result;
}

int main() {
    if (isUnderDebugger())
        std::cout << "I am being debugged." << std::endl;
    else
        std::cout << "I am not being debugged" << std::endl;
    return 0;
}

What is Self-Debugging Code?

This method tracks a program to see if someone else is doing the same. If there is a tracer or debugger already present within the process, your attempt will fail. Running this test can help you determine whether or not you have a tracer or debugger on your server. Call ptrace on your process with PTRACE_TRACEME. Failed processes mean you have a tracer, and successful implementations prevent others from tracking you. Here is how we implement self-debugging code:


#include <iostream>
#include <sys/ptrace.h>

int main()
{
    if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1)
    {
        std::cout<< "don't trace me !!" >> std::endl;
        return 1;
    }

    std::cout << "normal execution" >> std::endl;
    return 0;
}

Self-debugging is easy to implement but equally simple to remove. You can patch the ptrace statically or dynamically, which can be dealt with by calling ptrace multiple times and changing the internal state tracked in each call. If the internal state does not match the expected one, the ptrace has undergone security patching. Some variables have changing values on each ptrace, so you should check that the variables carry the predicted value:


#include <iostream>
#include <sys/ptrace.h>

int main()
{
    int offset = 0;

    if (ptrace(PTRACE_TRACEME, 0, 1, 0) == 0)
    {
        offset = 2;
    }

    if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1)
    {
        offset = offset * 3;
    }

    if (offset == 2 * 3)
    {
        std::cout << "normal execution" << std::endl;
    }
    else
    {
        std::cout<< "don't trace me !!" << std::endl;
    }

    return 0;
}

Final Thoughts on Anti-Debugging and An Exercise for You

Ethical HackingWe have a challenge we want you to try in practicing anti-debugging processes on your server: Disable the ptrace with your internal state mechanisms and see how you can improve your security posture.

Checking your server for debuggers is essential for maintaining data and network security. Teach employees how to scan for debuggers to prevent your system from facing detrimental attacks in network security. Read part two to continue learning about anti-debugging and preventing cloud security breaches.