Introduction to Windows API | TryHackMe Walkthrough
The Windows API enables direct interaction with core components of the Windows operating system, making it a popular tool among various users, such as red teamers, threat actors, blue teamers, software developers, and solution providers. In this post, we solve TryHackMe Introduction to Windows API as well.
Its seamless integration with the Windows system allows for a wide range of applications. The Win32 API, in particular, is commonly utilized for tasks such as offensive tool and malware development, EDR (Endpoint Detection & Response) engineering, and general software development. For a comprehensive overview of its use cases, refer to the Windows API Index.
SubSystem & Hardware Interaction with Windows APIs
Programs frequently require access to Windows subsystems or hardware but are limited to ensure system stability. To address this, Microsoft introduced the Win32 API, a library that acts as a bridge between user-mode applications and the kernel.
Windows organizes hardware access into two primary modes: user mode and kernel mode. These modes define the level of access an application or driver has to hardware, the kernel, and memory. API or system calls serve as intermediaries between these modes, transferring information to the system for processing in kernel mode.
When examining how programming languages interact with the Win32 API, the process becomes more complex. The application must first pass through the language runtime before interacting with the API.
For additional details on the runtime, refer to Runtime Detection Evasion.
Windows API Components
The Win32 API, often referred to as the Windows API, relies on several integral components that establish its structure and organization.
To understand it better, let’s take a top-down approach. At the top layer is the API itself, while at the bottom layer are the parameters that define specific API calls.
LayerExplanationAPIA top-level/general term or theory used to describe any call found in the win32 API structure.Header files or importsDefines libraries to be imported at run-time, defined by header files or library imports. Uses pointers to obtain the function address.Core DLLsA group of four DLLs that define call structures. (KERNEL32, USER32, and ADVAPI32). These DLLs define kernel and user services that are not contained in a single subsystem.Supplemental DLLsOther DLLs defined as part of the Windows API. Controls separate subsystems of the Windows OS. ~36 other defined DLLs. (NTDLL, COM, FVEAPI, etc.)Call StructuresDefines the API call itself and parameters of the call.API CallsThe API call used within a program, with function addresses obtained from pointers.In/Out ParametersThe parameter values that are defined by the call structures.
Windows OS Libraries
Each API call within the Win32 library is stored in memory and requires a pointer to its memory address. However, obtaining these pointers is complicated by ASLR (Address Space Layout Randomization), which randomizes memory locations to enhance security. Different programming languages or packages employ distinct methods to bypass ASLR.
In this discussion, we will focus on two of the most common approaches: P/Invoke and the Windows header file.
Windows Header File
Microsoft introduced the Windows header file, commonly referred to as the Windows loader, as a direct solution to challenges posed by ASLR. At a high level, the loader operates at runtime by identifying the API calls being made and creating a thunk table to resolve function addresses or pointers.
Fortunately, it’s not necessary to delve deeply into the inner workings of this process to use API calls effectively.
By simply including the windows.h file at the beginning of an unmanaged program, developers can invoke any Win32 function with ease.
P/Invoke
Microsoft defines P/Invoke (Platform Invoke) as “a technology that allows you to access structs, callbacks, and functions in unmanaged libraries from your managed code.”
P/Invoke simplifies the process of calling unmanaged functions, such as those in the Win32 API, from managed code. It begins by importing the necessary DLL that contains the desired unmanaged function or Win32 API call. This process provides a straightforward way to bridge the gap between managed and unmanaged code.
Here is an example of how a DLL can be imported with specific options:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
This declaration demonstrates importing the user32.dll library to access the MessageBox function. P/Invoke handles the details of invoking this unmanaged function from the managed environment.
API Calls in the Win32 Library
API calls form the second major component of the Win32 library, providing extensibility and flexibility to address a wide variety of use cases. Most of these API calls are thoroughly documented in resources like the Windows API documentation and pinvoke.net.
In this task, we will explore the basics of API call naming conventions and the use of in/out parameters.
Naming Schemes and Representational Characters
The functionality of an API call can be extended by altering its naming scheme and appending specific representational characters.
Below is a table summarizing the naming conventions supported by Microsoft for these extensions:
CharacterDescriptionAIndicates ANSI (American National Standards Institute) encoding for string parameters.WIndicates Unicode encoding for string parameters.ExRepresents an “extended” version of a standard function with additional features.32Refers to 32-bit specific versions of a function.
This naming system allows developers to select functions that best match their needs, such as those supporting Unicode or enhanced functionality.
Creating API Calls in C and C++
Microsoft provides low-level programming languages like C and C++ with a set of pre-configured libraries to simplify access to API calls. The windows.h header file, discussed earlier, is key to defining the structure of these calls and obtaining function pointers.
To include the windows.h file in your program, add the following line at the top of your code:
#include <windows.h>
Objective: Creating a Pop-Up Window
Let’s create a simple pop-up window with the title “Test” using the CreateWindowExA function.
First, let’s revisit the in/out parameters of this API call to understand how it works:
HWND CreateWindowExA(
DWORD dwExStyle, // Extended window style
LPCSTR lpClassName, // Window class name
LPCSTR lpWindowName, // Window title (the title bar text)
DWORD dwStyle, // Style of the window
int x, // X-coordinate of the window
int y, // Y-coordinate of the window
int nWidth, // Width of the window
int nHeight, // Height of the window
HWND hWndParent, // Handle to the parent window
HMENU hMenu, // Handle to a menu
HINSTANCE hInstance, // Handle to the instance of the module
LPVOID lpParam // Pointer to additional parameters
);
This function creates a window with various customizable properties. To meet our objective, the title bar will display “Test”.
Example Implementation
Here’s a basic implementation of the CreateWindowExA function in a C program:
#include <windows.h>
int main() {
// Registering the window class
const char* className = "MyWindowClass";
WNDCLASS wc = { 0 };
wc.lpfnWndProc = DefWindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = className; RegisterClass(&wc); // Creating the window
HWND hwnd = CreateWindowExA(
0, // Extended style
className, // Window class name
"Test", // Window title
WS_OVERLAPPEDWINDOW, // Window style
CW_USEDEFAULT, // X position
CW_USEDEFAULT, // Y position
500, // Width
300, // Height
NULL, // Parent window
NULL, // Menu
GetModuleHandle(NULL), // Application instance
NULL // Additional parameters
); // Showing the window
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd); // Message loop
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} return 0;
}
This code registers a window class, creates a window titled “Hello THM!”, and displays it on the screen. It also includes a basic message loop to keep the window running until it is closed by the user.
.NET and PowerShell API Implementations
P/Invoke allows managed code, such as C#, to call unmanaged functions from DLLs by importing the DLLs and mapping pointers to their API calls. Below is a simple example demonstrating how P/Invoke can be used to invoke a Windows API function.
Example Code
using System;
using System.Runtime.InteropServices;
class Program
{
// Import the MessageBox function from user32.dll
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); static void Main(string[] args)
{
// Call the MessageBox function
MessageBox(IntPtr.Zero, "Hello, THM!", "P/Invoke Example", 0);
}
}
Explanation of Components
DllImport Attribute
Function Declaration
Calling the Function
Commonly Abused Win32 API Calls
Certain API calls within the Win32 library are often exploited for malicious purposes. Organizations like SANS and MalAPI.io have worked to document and categorize these calls to help identify and mitigate such threats.
Below is a table summarizing some of the most frequently abused API calls, categorized by their prevalence in real-world samples:
API CallDescriptionVirtualAllocExAllocates memory in a remote process, commonly used for injecting malicious code.WriteProcessMemoryWrites data into the memory of a target process, often a step in process injection.CreateRemoteThreadCreates a thread in another process, frequently used to execute injected code.OpenProcessOpens a handle to a target process, used for various malicious operations.LoadLibraryLoads a DLL into the process’s address space, often abused for loading malicious libraries.GetProcAddressRetrieves the address of a function in a DLL, used to dynamically resolve API calls.NtQuerySystemInformationGathers detailed system information, often leveraged for reconnaissance.SetWindowsHookExInstalls a hook procedure to monitor or manipulate events, abused for keylogging.RegSetValueExModifies the Windows registry, frequently used for persistence mechanisms.CreateFileCreates or opens files, often exploited to access or manipulate sensitive data.
Observations
Malware Case Study
Keylogger
To analyze the keylogger, it’s crucial to identify the API calls and hooks it employs. Since the keylogger is written in C#, it uses P/Invoke to bridge managed code with the unmanaged Windows API.
Below is a snippet of the P/Invoke definitions from the malware’s source code:
using System;
using System.Runtime.InteropServices;
class KeyloggerAPI
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName); // Delegate for the hook procedure
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
}
Key API Calls in the Snippet
SetWindowsHookEx
UnhookWindowsHookEx
CallNextHookEx
GetModuleHandle
Observations
By analyzing these definitions, we gain insight into the keylogger’s functionality and its reliance on the Windows API for implementing malicious hooks.
Shellcode Launcher
To analyze the shellcode launcher, the process involves identifying the API calls it implements. Like the keylogger analysis, this malware sample also uses P/Invoke to interact with the unmanaged Windows API.
Below is a snippet of the P/Invoke definitions from the shellcode launcher’s source code:
using System;
using System.Runtime.InteropServices;
class ShellcodeLauncherAPI
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId); [DllImport("kernel32.dll", SetLastError = true)]
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); [DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
}
Key API Calls in the Snippet
VirtualAlloc
CreateThread
Abuse Potential: Often used to execute shellcode in a new thread.
WaitForSingleObject
hHandle: Handle to the object (e.g., thread created with CreateThread).
dwMilliseconds: Time to wait (e.g., INFINITE for no timeout).
VirtualProtect
Observations
By analyzing these definitions, it becomes evident that the shellcode launcher leverages API calls focused on memory and thread manipulation, common tactics in malware execution.
Introduction to Windows API TryHackMe | Room Answers
Room answers can be found here.
Check out also