Polymorphism (Part 1)

In this series of blog posts we are going to be talking about Polymorphism and how it can be used for AV and EDR evasion.

In the first blog post we are going to be looking at covering a very basic introduction to Assembly and then using C++ and inline Assembly to change the value of a variable printed to the screen once the binary is run.

While the use for these code segments at this stage do not have any real world application, in later blog posts we will be showing you how encrypt shellcode, perform memory injection, combine Assembly, Polymorphism and Native API calls to allow our final binary to stay undetected both statically and dynamically.

To understand the basics, let’s briefly introduce the Assembly language (asm) and some of the elements it comprises. For the example that’s about to be shown, it’s only necessary to briefly define a couple of elements:

  • Registers: these are small memory storage areas in the processor. Their purpose is to temporarily store values (as if they were high-level coding language variables). Depending on the processor architecture, they can be either 32 or 64 bits long.
  • Stack: it is a conceptual area of main memory, allocated by the Operating System when a program is started.

There exist plenty of instructions that modify the stack in order to achieve certain tasks, but for the sake of simplicity, let’s just talk about the MOV instruction in Intel Architecture.

mov destination, source

It roughly corresponds to: put the source into the destination. Looks easy. Actually you can think of it like you were operating with C pointers: you can play with the addresses or with the elements stored at those addresses.

Now let's see if we can take a simple C++ program, and change the value of a variable that is printed when run using inline Assembly:

#include <iostream>
using namespace std;
int main()
{
    
    char hello[] = "Hello";
    char goodbye[] = "Goodbye";
    char* a = hello;
    char* b = goodbye;
    __asm {
        mov eax, [a];
        mov ebx, [b];
        mov [b], eax;
        mov [a], ebx; 
    };
    cout << a;
    getchar();
    return 1;
}

At the beginning of the main() function, the Operating System allocates memory to manage all the operations that will be held in the function (Stack). Two arrays of characters are defined, as long as the corresponding pointers to their memory location.

char hello[] = "Hello";
char goodbye[] = "Goodbye";
char* a = hello;
char* b = goodbye;

We are using C++, but actually the instructions are always translated into Assembly to be able to be executed by the processor. In this first post we won’t go any further into detail, just keep in mind that there is a lot going on in the Stack. 

What happens if we print the address of a? Something like 00CFFDB0 will show up: that is the address location of the beginning of the string.

What we want to do now is simply to put the value pointed by a (“Hello”) into b, and viceversa, using assembly.

    __asm {
        mov eax, [a];
        mov ebx, [b];
        mov [b], eax;
        mov [a], ebx;  
    };

Here we need to understand a couple more things:

  • eax and ebx are registers, thus the small portion of memory where you temporarily store values.
  • eax refers to the content of eax while [eax] refers to the value at the memory location stored in eax.

So:

mov eax, [a]; //put the location of “Hello” (supppose it is OOFDF968 ) into eax.

Actually, since the memory is usually managed in chunks of 4 bytes, there is the need for two (consecutive) chunks to save it:  OOFDF968 for “Hell” and OOFDF96C for “o”.

mov ebx, [b]; //put the location of “Goodbye” into ebx
       mov [b], eax; //the location of the address in b becomes the location of “Hello”
       mov [a], ebx; //the location of the address in b becomes the location of “Goodbye”

We may notice that, even if the concept of swapping is easy, reading the assembly ‘translation’ is not so straightforward.

Now that we have modified the content of the two addresses, if we print a, the program will display “Goodbye” rather than "Hello".

While this is only a very short and brief introduction to this area, in the subsequent posts we will be introducing XOR & AES encryption, and using Native API calls to develop evasive malware and using inline Assembly for some trickery and combining some intelligent protocol checks for using with Cobalt Strike shellcode.