Internals
Windows machines make up a majority of corporate infrastructure, both red and blue teams need to understand Windows internals and how they can be (ab)used. The red team can (ab)use Windows to aid in evasion and exploitation when crafting offensive tools or exploits. The blue team needs to understand how Windows works to effectively defend against attackers.
Windows internals are core to how the Windows operating system works. These internals cannot change without compromising how the operating system operates at a bare-bones level. Because of this, the Windows internals are a rewarding target for attackers.
Attackers can easily abuse the functionality of Windows internals components for nefarious reasons. For more information about this, check out the Abusing Windows Internals room.
Processes
A process maintains and represents the execution of a program. An application can contain one or more processes. A process has many components that it gets broken down into to be stored and interacted with. The Microsoft docs break down Processes and Threads.
Processes are core to how Windows functions, most functionality of Windows can be encompassed as an application and has a corresponding process. A few examples of default applications that start processes:
MsMpEng (Microsoft Defender)
wininit (keyboard and mouse)
lsass (credential storage)
Attackers can target processes to evade detections and hide malware as legitimate processes. A small list of potential attack vectors attackers could employ against processes:
Each critical component of processes and their purpose:
Process Component
Purpose
Private Virtual Address Space
Virtual memory addresses that the process is allocated.
Executable Program
Defines code and data stored in the virtual address space.
Open Handles
Defines handles to system resources accessible to the process.
Security Context
The access token defines the user, security groups, privileges, and other security information.
Process ID
Unique numerical identifier of the process.
Threads
Section of a process scheduled for execution.
A process at a lower level as it resides in the virtual address space. This depicts what a process looks like in memory.
Component
Purpose
Code
Code to be executed by the process.
Global Variables
Stored variables.
Process Heap
Defines the heap where data is stored.
Process Resources
Defines further resources of the process.
Environment Block
Data structure to define process information.
The task manager can report on many components and information about a process. A brief list of essential process details.
Value/Component
Purpose
Example
Name
Define the name of the process, typically inherited from the application
conhost.exe
PID
Unique numerical value to identify the process
7408
Status
Determines how the process is running (running, suspended, etc.)
Running
User name
User that initiated the process. Can denote privilege of the process
SYSTEM
Some utilities available that make observing processes easier; including Process Hacker 2, Process Explorer, and Procmon.
Threads
A thread is an executable unit employed by a process and scheduled based on device factors. Device factors can vary based on CPU and memory specifications, priority and logical factors, and more. The simplified definition of a thread is "controlling the execution of a process.".
Since threads control execution, this is a commonly targeted component. Thread abuse can be used on its own to aid in code execution, or it is more widely used to chain with other API calls as part of other techniques. Threads share the same details and resources as their parent process.
Thread unique values and data:
Component
Purpose
Stack
All data relevant and specific to the thread (exceptions, procedure calls, etc.)
Thread Local Storage
Pointers for allocating storage to a unique data environment
Stack Argument
Unique value assigned to each thread
Context Structure
Holds machine register values maintained by the kernel
Virtual Memory
Virtual memory allows other internal components to interact with memory as if it was physical memory without the risk of collisions between applications.
Virtual memory provides each process with a private virtual address space. A memory manager is used to translate virtual addresses to physical addresses. By having a private virtual address space and not directly writing to physical memory, processes have less risk of causing damage.
The memory manager will also use pages or transfers to handle memory. Applications may use more virtual memory than physical memory allocated; the memory manager will transfer or page virtual memory to the disk to solve this problem. We can visualize this concept in the diagram below.
The theoretical maximum virtual address space is 4 GB on a 32-bit x86 system.
This address space is split in half, the lower half (0x00000000 - 0x7FFFFFFF) is allocated to processes. The upper half (0x80000000 - 0xFFFFFFFF) is allocated to OS memory utilization.
Administrators can alter this allocation layout for applications that require a larger address space through settings (increaseUserVA) or the Address Windowing Extensions (AWE).
The theoretical maximum virtual address space is 256 TB on a 64-bit modern system.
The exact address layout ratio from the 32-bit system is allocated to the 64-bit system.
Although this concept does not directly translate to Windows internals or concepts, it is crucial to understand. If understood correctly, it can be leveraged to aid in abusing Windows internals.
DLLs (Dynamic Link Libraries)
The Microsoft docs describe a DLL as "a library that contains code and data that can be used by more than one program at the same time."
DLLs are used as one of the core functionalities behind application execution in Windows. From the Windows documentation, "The use of DLLs helps promote modularization of code, code reuse, efficient memory usage, and reduced disk space. So, the operating system and the programs load faster, run faster, and take less disk space on the computer."
When a DLL is loaded as a function in a program, it is assigned as a dependency. Since a program is dependent on a DLL, attackers can target the DLLs rather than the applications to control some aspect of execution or functionality.
DLLs are created no different than any other project/application as in, they only require slight syntax modification to work.
An example of a DLL from the Visual C++ Win32 Dynamic-Link Library project:
A header file for the DLL. It will define what functions are imported and exported:
DLLs can be loaded in a program using load-time dynamic linking or run-time dynamic linking.
When loaded using load-time dynamic linking, explicit calls to the DLL functions are made from the application. We can only achieve this type of linking by providing a header (.h) and import library (.lib) file.
An example of calling an exported DLL function from an application:
When loaded using run-time dynamic linking, a separate function (LoadLibrary
or LoadLibraryEx
) is used to load the DLL at run time. Once loaded, we need to use GetProcAddress
to identify the exported DLL function to call.
An example of loading and importing a DLL function in an application:
In malicious code, threat actors will often use run-time dynamic linking over load-time dynamic linking because a malicious program may need to transfer files between memory regions, and transferring a single DLL is more manageable than importing using other file requirements.
Portable Executable Format
The Portable Executable (PE) format defines the information about the executable and stored data. This also defines the structure of how data components are stored. It is an overarching structure for executable and object files. The PE and Common Object File Format (COFF) files make up the PE format.
PE data is most commonly seen in the hex dump of an executable file. Below we will break down a hex dump of calc.exe into the sections of PE data.
The structure of PE data is broken up into seven components:
The DOS Header
This defines the type of file. The MZ
DOS header defines the file format as .exe
.
A DOS header example from a hex dump section:
The DOS Stub
A program run by default at the beginning of a file that prints a compatibility message. This does not affect any functionality of the file for most users. The DOS stub prints the message This program cannot be run in DOS mode
.
A DOS stub example from a hex dump section:
The PE File Header
This provides PE header information of the binary. Defines the format of the file, contains the signature and image file header, and other information headers. The PE file header is the section with the least human-readable output.
A PE file header example from the PE
stub in the hex dump section:
The Image Optional Header is an important part of the PE File Header
The Data Dictionaries are part of the image optional header. They point to the image data directory structure.
The Section Table
Defines the available sections and information in the image.
A section definition example from the table in the hex dump section:
Since the headers have defined the format and function of the file, the sections can define the contents and data of the file.
Section
Purpose
.text
Contains executable code and entry point
.data
Contains initialized data (strings, variables, etc.)
.rdata or .idata
Contains imports (Windows API) and DLLs.
.reloc
Contains relocation information
.rsrc
Contains application resources (images, etc.)
.debug
Contains debug information
Interacting with Windows Internals
Interacting with Windows internals may seem daunting, but it has been dramatically simplified. The most accessible and researched option to interact with Windows Internals is to interface through Windows API calls. The Windows API provides native functionality to interact with the Windows operating system. The API contains the Win32 API and, less commonly, the Win64 API. More info for the Windows API can be found here.
Most Windows internals components require interacting with physical hardware and memory.
The Windows kernel will control all programs and processes and bridge all software and hardware interactions. This is especially important since many Windows internals require interaction with memory in some form.
An application by default normally cannot interact with the kernel or modify physical hardware and requires an interface. This problem is solved through the use of processor modes and access levels.
A Windows processor has a user and kernel mode. The processor will switch between these modes depending on access and requested mode.
The switch between user mode and kernel mode is often facilitated by system and API calls. In documentation, this point is sometimes referred to as the "Switching Point."
User mode
Kernel Mode
No direct hardware access
Direct hardware access
Creates a process in a private virtual address space
Ran in a single shared virtual address space
Access to "owned memory locations"
Access to entire physical memory
Applications started in user mode or "userland" will stay in that mode until a system call is made or interfaced through an API. When a system call is made, the application will switch modes.
When looking at how languages interact with the Win32 API, this process can become further warped, such as the application will go through the language runtime before going through the API. The most common example is C# executing through the CLR before interacting with the Win32 API and making system calls.
POC
We can inject a message box into our local process to demonstrate a proof-of-concept to interact with memory.
The steps to write a message box to memory:
Allocate local process memory for the message box.
Write/copy the message box to allocated memory.
Execute the message box from local process memory.S
Step one, we can use OpenProcess
to obtain the handle of the specified process.
Step two, we can use VirtualAllocEx
to allocate a region of memory with the payload buffer.
Step three, we can use WriteProcessMemory
to write the payload to the allocated region of memory.
Step four, we can use CreateRemoteThread
to execute our payload from memory.
Last updated