Architecture, Memory Management, APIs & Processes
Windows Architecture
A processor inside a machine running the Windows operating system can operate under two different modes: User Mode and Kernel Mode.
Applications run in user mode, and operating system components run in kernel mode.
When an application wants to accomplish a task, such as creating a file, it cannot do so on its own. The only entity that can complete the task is the kernel, so applications must follow the following function call flow:
1. User Processes --> A program/application executed by the user such as Notepad, Google Chrome or Microsoft Word.
2. Subsystem DLLs --> DLLs that contain API functions that are called by user processes. An example of this would be kernel32.dll
exporting the CreateFile Windows API (WinAPI) function, other common subsystem DLLs are ntdll.dll
, advapi32.dll
, and user32.dll
.
3. Ntdll.dll --> A system-wide DLL which is the lowest layer available in user mode. This is a special DLL that creates the transition from user mode to kernel mode. This is often referred to as the Native API or NTAPI.
4. Executive Kernel --> This is what is known as the Windows Kernel and it calls other drivers and modules available within kernel mode to complete tasks. The Windows kernel is partially stored in a file called ntoskrnl.exe
under "C:\Windows\System32".
- Function Call Flow
For example, an application that creates a file would run through the following flow:
The user application calls the
CreateFile
WinAPI function which is available inkernel32.dll
.Kernel32.dll
is a critical DLL that exposes applications to the WinAPI and is therefore can be seen loaded by most applications.Next, CreateFile calls its equivalent NTAPI function,
NtCreateFile
, which is provided throughntdll.dll
.Ntdll.dll
then executes an assemblysysenter
(x86) orsyscall
(x64) instruction, which transfers execution to kernel mode.The kernel
NtCreateFile
function is then used which calls kernel drivers and modules to perform the requested task.
- Directly Invoking The Native API (NTAPI)
It's important to note that applications can invoke syscalls (i.e. NTDLL functions) directly without having to go through the Windows API. The Windows API simply acts as a wrapper for the Native API.
The Native API is more difficult to use because it is not officially documented by Microsoft. Furthermore, Microsoft advises against the use of Native API functions because they can be changed at any time without warning (There are benefits of directly invoking the Native API for MalDev).
Windows Memory Management
Understanding how Windows handles memory is crucial to building advanced malware.
- Virtual Memory & Paging
Memory in modern operating systems is not mapped directly to physical memory (i.e the RAM). Instead, virtual memory addresses are used by processes that are mapped to physical memory addresses. The goal is to save as much physical memory as possible.
Virtual memory may be mapped to physical memory but can also be stored on disk. With virtual memory addressing it becomes possible for multiple processes to share the same physical address while having a unique virtual memory address. Virtual memory relies on the concept of Memory paging which divides memory into chunks of 4kb called "pages".
- Page State
The pages residing within a process's virtual address space can be in one of 3 states:
1. Free --> The page is neither committed nor reserved. The page is not accessible to the process. It is available to be reserved, committed, or simultaneously reserved and committed. Attempting to read from or write to a free page can result in an access violation exception.
2. Reserved --> The page has been reserved for future use. The range of addresses cannot be used by other allocation functions. The page is not accessible and has no physical storage associated with it. It is available to be committed.
3. Committed --> Memory charges have been allocated from the overall size of RAM and paging files on disk. The page is accessible and access is controlled by one of the memory protection constants. The system initializes and loads each committed page into physical memory only during the first attempt to read or write to that page. When the process terminates, the system releases the storage for committed pages.
- Page Protection Options
Once the pages are committed, they need to have their protection option set.
List of memory protection constants: https://learn.microsoft.com/en-us/windows/win32/memory/memory-protection-constants
Examples:
PAGE_NOACCESS
--> Disables all access to the committed region of pages. An attempt to read from, write to or execute the committed region will result in an access violation.
PAGE_EXECUTE_READWRITE
--> Enables Read, Write and Execute. This is highly discouraged from being used and is generally an IoC because it's uncommon for memory to be both writable and executable at the same time.
PAGE_READONLY
--> Enables read-only access to the committed region of pages. An attempt to write to the committed region results in an access violation.
- Memory Protection
Modern operating systems generally have built-in memory protections to thwart exploits and attacks.
Data Execution Prevention (DEP)--> DEP is a system-level memory protection feature that is built into the operating system starting with Windows XP and Windows Server 2003. If the page protection option is set to PAGE_READONLY, then DEP will prevent code from executing in that memory region.
Address space layout randomization (ASLR) - ASLR is a memory protection technique used to prevent the exploitation of memory corruption vulnerabilities. ASLR randomly arranges the address space positions of key data areas of a process, including the base of the executable and the positions of the stack, heap and libraries.
- x86 vs x64 Memory Space
x86 processes have a smaller memory space of 4GB (0xFFFFFFFF
).
x64 has a vastly larger memory space of 128TB (0xFFFFFFFFFFFFFFFF
).
- Memory Allocation Example
The first step in interacting with memory is allocating memory (reserving a memory inside the running process).
Ways to allocate memory via C functions and Windows APIs:
pAddress
will be the base address of the memory block that was allocated. Using this pointer several actions can be taken such as reading, writing, and executing. The type of actions that can be performed will depend on the protection assigned to the allocated memory region.
- Memory Writing Example
The next step after memory allocation is generally writing to that buffer. Several options can be used to write to memory but for this example, memcpy
:
- Freeing Allocated Memory
When the application is done using an allocated buffer, it is highly recommended to deallocate or free the buffer to avoid memory leaks.
Depending on what function was used to allocate memory, it will have a corresponding memory deallocation function. For example:
Allocating with
malloc
requires the use of thefree
function.Allocating with
HeapAlloc
requires the use of theHeapFree
function.Allocating with
LocalAlloc
requires the use of theLocalFree
function.
Windows APIs
The Windows API provides developers with a way for their applications to interact with the Windows operating system. For example, if the application needs to display something on the screen, modify a file or query the registry all of these actions can be done via the Windows API.
Windows API List: https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-api-list
- Windows Data Types
https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
Common data types:
Below is a summary table:
Win32 API Type | Standard C Equivalent | Description |
DWORD | unsigned long | A 32-bit unsigned integer. |
SIZE_T | size_t | Used to represent the size of an object. |
VOID, PVOID | void, void* | Represents the absence of type or a pointer to any type. |
HANDLE | void* | A pointer/handle to a system resource. |
HMODULE | void* | A handle to a module (DLL/EXE), essentially a pointer. |
LPCSTR/PCSTR | const char* | Pointer to a constant null-terminated string of ANSI characters. |
LPSTR/PSTR | char* | Pointer to a non-constant null-terminated string of ANSI characters. |
LPCWSTR/PCWSTR | const wchar_t* | Pointer to a constant null-terminated string of Unicode characters. |
LPWSTR/PWSTR | wchar_t* | Pointer to a non-constant null-terminated string of Unicode characters. |
wchar_t | wchar_t | Used to represent wide characters. |
ULONG_PTR | Dependent on platform | Unsigned integer the same size as a pointer. Use uintptr_tin C99 or later. |
- Data Types Pointers
The Windows API allows a developer to declare a data type directly or a pointer to the data type. This is reflected in the data type names where the data types that start with "P" represent pointers to the actual data type while the ones that don't start with "P" represent the actual data type itself.
This is useful when working with Windows APIs that have parameters that are pointers to a data type. Examples "P" data types and its non-pointer equivalent:
PHANDLE
is the same asHANDLE*
.PSIZE_T
is the same asSIZE_T*
.PDWORD
is the same asDWORD*
.
- ANSI & Unicode Functions
Windows API functions have two versions: "A" for ANSI and "W" for Unicode. ANSI functions handle 8-bit characters, and Unicode functions handle 16-bit characters. For example, CreateFileA uses ANSI strings, while CreateFileW uses Unicode strings. The size of data passed can vary.
- In and Out Parameters
Windows APIs have in and out parameters.
IN
parameter is a parameter that is passed into a function and is used for input.
OUT
parameter is a parameter used to return a value back to the caller of.
- Windows API Debugging Errors
When functions fail they often return a non-verbose error. The error code must be retrieved using the GetLastError function.
Windows's System Error Codes List: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
Example:
- Windows Native API Debugging Errors
NTAPIs are mostly exported from ntdll.dll. Unlike Windows APIs, these functions cannot have their error code fetched via GetLastError
. Instead, they return the error code directly which is represented by the NTSTATUS
data type.
A successful system call will return the value STATUS_SUCCESS
, which is 0, if the call failed it will return a non zero value (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55).
Error checking for system calls:
Another way to check the return value of NTAPIs is through the NT_SUCCESS
macro. The macro returns TRUE
if the function succeeded, and FALSE
it fails:
Windows Processes
A Windows process is a program or application that is running on a Windows machine.
A process can be started by either a user or by the system itself. The process consumes
resources such as memory, disk space, and processor time to complete a task.
Process Threads
Windows processes are composed of one or more threads, which operate concurrently.
A thread is an independent set of instructions that can be executed within a process.
Threads within a process can communicate and share data.
Thread execution is scheduled and managed by the operating system in the context of a process.
Process Memory
Windows processes utilize memory to store both data and instructions.
Memory is allocated to a process when it is created, and the amount allocated can be determined by the process.
Memory management is handled using virtual and physical memory.
Virtual memory enables the OS to use more memory than physically available by creating a virtual address space, which applications can access.
This virtual address space is divided into "pages" that are allocated to processes.
Memory Types in Windows Processes
Private Memory: Dedicated to a single process and cannot be shared with other processes. It's used for storing process-specific data.
Mapped Memory: Can be shared among multiple processes. This type facilitates data sharing between processes, such as shared libraries, memory segments, and files. While visible to other processes, it's protected from being modified by them.
Image Memory: Contains the code and data of an executable file and is used to store a process's program code, data, and resources. Image memory often relates to DLL files loaded into a process's address space.
Process Environment Block (PEB)
The Process Environment Block (PEB) in Windows is a data structure that stores crucial information about a running process, including parameters, startup details, heap allocation data, loaded DLLs, and more. It's used by the OS to manage processes and by the Windows loader to launch applications. Each process has its own PEB, that will contain its own set of information about it.
- PEB Structure:
The reserved members of this struct can be ignored.
The non-reserved members are explained below.
BeingDebugged:
BeingDebugged is a flag in the PEB structure that indicates whether a Windows process is being debugged or not. It's set to 1 (TRUE) when the process is being debugged and 0 (FALSE) when it's not. It helps the Windows loader decide whether to launch the application with a debugger attached.
Ldr:
Ldr is a pointer to a PEB_LDR_DATA structure, which holds information about the loaded DLL modules in a process, including the list of DLLs, their base addresses, and sizes. This is used by the Windows loader to keep track of loaded DLLs.
ProcessParameters:
ProcessParameters is a data structure in the PEB that contains command line parameters passed to the process when it was created. It's useful for actions like command line spoofing.
AtlThunkSListPtr & AtlThunkSListPtr32:
AtlThunkSListPtr and AtlThunkSListPtr32 are used by the ATL module to store a linked list of thunking functions, which are used to call functions in different address spaces, often from DLL files.
PostProcessInitRoutine:
PostProcessInitRoutine stores a pointer to a function that the operating system calls after TLS (Thread Local Storage) initialization has been completed for all threads in the process. This function can be used for additional process initialization tasks.
SessionId:
SessionId is a unique identifier in the PEB assigned to a single session to track user activity during that session.
- Undocumented Structures
When referencing the Windows documentation for a structure, one may encounter several reserved members within the structure. These reserved members are often presented as arrays of BYTE or PVOID data types. This practice is implemented by Microsoft to maintain confidentiality and prevent users from understanding the structure to avoid modifications to these reserved members.
One way to determine what the PEB's reserved members hold is through the !peb
command in WinDbg.
Thread Environment Block (TEB)
Thread Environment Block (TEB) is a data structure in Windows that stores information about a thread. It contains the thread's environment, security context, and other related information. It is stored in the thread's stack and is used by the Windows kernel to manage threads.
- TEB Structure
The reserved members of this struct can be ignored.
The non-reserved members are explained below.
ProcessEnvironmentBlock (PEB):
PEB is a pointer to the PEB structure located inside the Thread Environment Block (TEB). It stores information about the currently running process.
TlsSlots:
TLS (Thread Local Storage) Slots are found within the TEB and are used to store thread-specific data. Each thread in Windows has its own TEB with its set of TLS slots, allowing applications to store thread-specific information.
TlsExpansionSlots:
These slots in the TEB are reserved for system DLLs and are used to store thread-local storage data for a thread.
Process And Thread Handles:
On the Windows operating system, each process has a distinct process identifier or process ID (PID) which the operating system assigns when the process is created. PIDs are used to distinguish one running process from another. The same concept applies to a running thread, where a running thread has a unique ID that is used to differentiate it from the rest of the existing threads (in any process) on the system.
These identifiers can be used to open a handle to a process or a thread using the following WinAPIs:
OpenProcess - Opens an existing process object handle via its identifier.
OpenThread - Opens an existing thread object handle via its identifier.
Handles should always be closed once their use is no longer required to avoid handle leaking. This is achieved via the CloseHandle WinAPI call.
Last updated