All notes

Malware Execution

In most malware produced by the red-team, the goal is to execute a shellcode that will connect back to the C2 framework. The whole trick is how to do it to avoid detection.

1. Shellcode Execution

Shellcode execution is a very broad topic. There are a lot of different techniques on how to do it. Actually the vast majority of ways to execute a shellcode boil down to a few basic steps. The various techniques differ in the way these steps are executed, the WinAPI functions used, etc.

  1. Allocate executable memory for the shellcode;
  2. Write the shellcode to the allocated memory;
  3. Execute the shellcode memory;

The whole game is to perform all these necessary operations in such a way that the EDRs do not realize that they are dealing with malware. Various tricks are used for this purpose. Basic technique

1.1. Local thread injection

One of the most basic shellcode execution techniques is to create a local thread. This is just an example, this technique is very well known but it illustrates very well the basics of the whole process:

// 1. Allocate memory
PVOID shellcode_mem = VirtualAlloc(
  NULL,
  shellcode_size,
  MEM_COMMIT | MEM_RESERVE,
  PAGE_READWRITE // Using PAGE_EXECUTE_READWRITE is an indicator of malware
);
 
// 2. Copy original shellcode to the allocated memory
memcpy(shellcode_mem, SHELLCODE, shellcode_size);
 
// 3. Make memory executable
VirtualProtect(
  shellcode_mem,
  shellcode_size,
  PAGE_EXECUTE_READWRITE,
  NULL
);
 
// 4. Create a new thread and execute the shellcode memory
HANDLE thread = CreateThread(NULL, NULL, shellcode_mem, NULL, NULL, NULL);
 
// 5. Wait for the thread
WaitForSingleObject(thread, INFINITE);

1.2. Shellcode placement

Malware payload can be stored in different PE sections, e.g.: .data,.rdata, .text, .rsrc

NOTE: The compiler might decide to store global variables and constants in .text section anyway. There is no 100% sure method to determine where the data will be finally stored.

[object Object], section

To store payload in the .data section initialize global variable. This section is readable and writable.

unsigned char shellcode[] = { 0xde, 0xad, 0xbe, 0xef }
 
int main() { /*...*/ }

[object Object], section

To store payload in .rdata section initialize global constant. This section in read-only.

const unsigned char shellcode[] = { 0xde, 0xad, 0xbe, 0xef }
 
int main() { /*...*/ }

[object Object], section

To store data in .text (code) section, one must explicitly instruct the compiler to do this. This section is executable. It's also possible to use stack-based local variables to store shellcode on stack.

#pragma section(".text")
__declspec(allocate(".text")) const unsigned char shellcode[] = {
    0xde, 0xad, 0xbe, 0xef
}
 
int main() { /*...*/ }

[object Object], section

Visual Studio has an option to embed binary resources (icons, etc.) into PE .rsrc section. This section is read-only. The payload cannot be accessed directly at runtime. Instead, several WinAPI functions (especially from libloaderapi.h) must be used to access it.

External Resources

Shellcode can also be kept outside the main executable file of the malware, in some other external resources (websites, cache files, config files). It then requires additional operations to access it. So on the one hand, AV won't detect shellcode statically in the executable file (because it's not there!), but the execution gets more noisy as it wants to access network resources.

Actually, the only limitation is our imagination. Threat actors use all manner of ways to deliver shellcode through various channels.

2. EDRs basics

EDRs use a whole spectrum of different kinds of techniques to detect malware.

Static analysis:

  • Malicious code signatures, known shellcode signatures
  • Suspicious Import Address Table (IAT) / Export Address Table (EAT) records
  • Suspicious strings
  • Unusual PE sections: names, sizes, entropy
  • Binary signing

Dynamic analysis:

  • ETW monitoring:
    • Suspicious child processes spawning
    • Suspicious network resources calling
    • Suspicious disk operations
    • Suspicious chains of Windows activities
  • DLL API hooking
  • In-memory scanning
  • Call stack analysis
  • Network monitoring

There are services that allow us to see how our sample is detected by various AV/EDR software:

3. Avoiding static analysis

3.1. Language choice

TODO:

Rust vs C++

3.2. String obfuscation

A simple technique to make detection harder is to obfuscate constant strings across the codebase. All strings, for example, can be XORed or encoded to some unreadable format. Modern EDRs can decode strings. It's good to check our obfuscation techniques with FLOSS tool that automatically extracts obfuscated strings from malware.

More on this topic:

3.3. Execution flow obfuscation

3.4. IAT hidding

The Import Address Table (IAT) is a structure in PE file format that contains information about external functions used by the binary and the DLLs exporting them. These information are used to signature and detect malicious binaries.

We want to execute specific WinAPI functions but we don't want to admit that in the IAT. One way to do this is to use dynamic linking:

fnVirtualAllocEx pVirtualAllocEx = GetProcAddress(GetModuleHandleA("KERNEL32.DLL"), "VirtualAllocEx");
 
pVirtualAllocEx(...);

It's better than nothing but still GetProcAddress and GetModuleHandle functions are linked statically using IAT. That's not good. Although, because kernel32.dll and ntdll.dll are always present in the process memory we can implement our own custom stealth WinAPI function resolver to avoid static detection.

3.5. IAT camouflage

Using the IAT hidding technique, the compiled binary may have very few or no IAT entries. This is very suspicious. The solution to this problem is to use a bunch of benign WinAPI functions via static linking so that they appear in the IAT. You can use functions with NULL parameters or call functions in a branch that will not be executed.

Beware of dead-code elimination! Most compilers by default remove code that they think will never be executed.

3.6. Packing

4. Avoiding dynamic analysis

4.1. Anti-sandbox techniques

Anti-sandbox techniques are primarily focused on detecting and avoiding dynamic analysis by sandboxed VMs of advanced EDR products. Malware by detecting the fact that it is running on a virtual machine can change its behavior to benign actions. Since the sandbox is a resource-intensive environment, malware can also wait a sufficiently long time before actually running malicious actions (delayed exection).

Techniques:

4.2. Anti-debugger techniques

Anti-debugger techniques are used to keep the malware unexplored longer for analysts and researchers. All these techniques can be circumvented, but time is of the essence, and various tricks can be used to delay the analysts so that the malware and the infection method used are not analyzed quickly enough.

Techniques:

5. Development resources