Here I share notes on the course objectives of the ‘Malware development course’ offered by Maldev Academy. Here I show how I complete the module objectives in the course. I also make more general notes on the course material
module 1
Module 2
Read up on the differences between low-level and high-level programming languages
”
A high-level programming language is a programming language with strong abstraction from the details of the computer. In contrast to low-level programming languages, it may use natural language elements, be easier to use, or may automate (or even hide entirely) significant areas of computing systems (e.g. memory management), making the process of developing a program simpler and more understandable than when using a lower-level language. The amount of abstraction provided defines how “high-level” a programming language is.
”
Source
”
A low-level programming language is a programming language that provides little or no abstraction from a computer’s instruction set architecture, memory or underlying physical hardware; commands or functions in the language are structurally similar to a processor’s instructions.[vague][citation needed] These languages provide the programmer with full control over program memory and the underlying machine code instructions. Because of the low level of abstraction (hence the term “low-level”) between the language and machine language, low-level languages are sometimes described as being “close to the hardware”.
”
So, to summarize: high-level programming languages offer benefits like ease and understandability, while low-level programming languages provide more control.
Read about Antivirus (AV) and Endpoint Detection and Response (EDR) security solutions
Source
”
Antivirus software was originally developed to detect and remove computer viruses, hence the name. However, with the proliferation of other malware, antivirus software started to protect against other computer threats. Some products also include protection from malicious URLs, spam, and phishing.
Antivirus software itself usually runs at the highly trusted kernel level of the operating system to allow it access to all the potential malicious process and files, creating a potential avenue of attack.[158] The US National Security Agency (NSA) and the UK Government Communications Headquarters (GCHQ) intelligence agencies, respectively, have been exploiting anti-virus software to spy on users.[159] Anti-virus software has highly privileged and trusted access to the underlying operating system, which makes it a much more appealing target for remote attacks.[160] Additionally anti-virus software is “years behind security-conscious client-side applications like browsers or document readers. It means that Acrobat Reader, Microsoft Word or Google Chrome are harder to exploit than 90 percent of the anti-virus products out there”, according to Joxean Koret, a researcher with Coseinc, a Singapore-based information security consultancy.
”
Cloud antivirus is a technology that uses lightweight agent software on the protected computer, while offloading the majority of data analysis to the provider’s infrastructure.[
Some antivirus vendors maintain websites with free online scanning capability of the entire computer, critical areas only, local disks, folders or files.
Content Disarm & Reconstruction (CDR) technology protects a network from malware by removing components from inbound files which do not rigorously conform with the standards of that file type. It does so by rebuilding the original files without any illegitimate components present. Part of the CDR process may also involve flattening and converting the reconstructed files to Portable Document Format (PDF) for maximum safety.
Identification methods
Signature-based detection
Heuristics
Rootkit detection
Real-time protection
Machine learning detection
”
To summarize: Antivirus software has several capabilities, including real-time protection, signature-based and machine learning detection, rootkit detection, heuristics (less exact, more fuzzy recognition, to prevent f.e. metamorphic malicious software. In addition to issues with false and true positive rates (and unknown unknowns), the level of access and control of antivirus software and its own security can lead to far-reaching problems.
Module 3
Install the tools mentioned in the module
I used the prebuilt Maldev VM, so no need 🙂
Launch each tool and analyze the layout and available settings
Process Hacker 2
Features/options of interest:
‘Inspect executable file…’
‘Pagefiles’
‘Create Service’
PE-bear
‘Compare’
‘From all loaded…’ → ‘Dump all sections to…’ & ‘Dump disassembly to…’
Sysinternals
Sysinternals features too many tools to view all the options or features of. Instead here I want to focus on tools I’m personally not familiar with.
// First, create a structure named 'exampleStructure.struct exampleStructure { int exampleNum; char exampleChar; };int main() {// Create a structure variable of exampleStructure named 's1'.struct exampleStructure s1;// Assign values to members of 's1'.s1.myNum = 13;s1.myLetter = "J";// Print these values.printf("My number is: %d\n", s1.myNum);printf("My letter is: %c\n", s1.myLetter);}return 0;
#include <stdio.h>void swap(int firstVariable, int secondVariable){ // create a temporary variable to hold one of the values to perform the swap int tempVariable; printf("[Inside swap function...]\n"); // printf("Value of tempVariable is: %d \n", tempVariable); // printf("Memory address of tempVariable is: %p \n", tempVariable); printf("\n"); printf("\nBefore any operations...\n"); printf("tempVariable is: %d \n", tempVariable); printf("firstVariable is: %d \n", firstVariable); printf("secondVariable is: %d \n", secondVariable); tempVariable = firstVariable; /* temporarily save the value of the first variable */ printf("after tempvariable = firstVariable...\n"); printf("tempVariable is: %d \n", tempVariable); firstVariable = secondVariable; /* swap the vale of the first variable with the value of the second variable */ secondVariable = tempVariable; /* put the value of the first variable into the second variable */ printf("\nafter firstVariable = secondVariable and secondVariable = tempVariable...\n"); printf("Value of firstVariable is: %d \n", firstVariable); printf("Value of secondVariable is: %d \n", secondVariable); printf("Memory address of firstVariable is: %p \n", firstVariable); printf("Memory address of secondVariable is: %p \n", secondVariable); printf("\n");// return 0;}int main(void){ int a = 100; int b = 200;printf("[Inside main function, before swap...]\n"); printf("Value of a: %d \n", a); printf("Memory address of a: %p \n", &a); printf("Value of b: %d \n", b); printf("Memory address of b: %p \n", &b); printf("\n"); // call function to swap values swap(a, b); // check values outside the function after swap function is run printf("[Inside main function, after swap...]\n"); printf("Value of a: %d \n", a); printf("Memory address of a: %p \n", &a); printf("Value of b: %d \n", b); printf("Memory address of b: %p \n", &b); printf("\n"); return 0;}
Code:
Output:
-*- mode: quickrun-; default-directory: "/Users/joostagterhoek/code/maldev-academy-code/module-4-coding-basics-objectives/" -*-Quickrun started at Tue Mar 10 21:28:28[Inside main function, before swap...]Value of a: 100Memory address of a: 0x7ff7b3afb558Value of b: 200Memory address of b: 0x7ff7b3afb554[Inside swap function...]Before any operations...tempVariable is: 32759firstVariable is: 100secondVariable is: 200after tempvariable = firstVariable...tempVariable is: 100after firstVariable = secondVariable and secondVariable = tempVariable...Value of firstVariable is: 200Value of secondVariable is: 100Memory address of firstVariable is: 0xc8Memory address of secondVariable is: 0x64[Inside main function, after swap...]Value of a: 100Memory address of a: 0x7ff7b3afb558Value of b: 200Memory address of b: 0x7ff7b3afb554Quickrun finished at Tue Mar 10 21:28:29
n is a function parameter specified in the function square.
int result = square(10);
The number 10 is an argument of the called function square.
Example of pass by value vs. pass by argument:
Pass by value example code:
#include <stdio.h>int square(int n);int main(){int n = 10;int result = square(n);printf("n in the main function is: %d.\n", n);printf("The result is: %d.\n", result);}int square(int n){n = n * n;printf("n in the square function is: %d.\n", n);return n;}
Further research
%p prints virtual addresses of pointers in the example code above.
![failure] Linker error `NtAllocateVirtualMemory:
‘error LNK2019: unresolved external symbol _NtAllocateVirtualMemory referenced in function _main’
The problem/solution might be the presence of the Windows Development Kit (WDK) and/or the Windows software development kit (SDK). According to documentation (https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/) ‘this header file is used by Windows file system and filter driver developers’.
Compare VirtualAlloc’s parameters with its NTAPI equivalent. Are they the same or different?
The parameters are different: NtAllocateVirtualMemory takes more parameters and they have other purposes in the function. From the documentation:
VirtualALloc:
”
[in, optional] lpAddress
The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, the address is rounded down to the next page boundary. To determine the size of a page and the allocation granularity on the host computer, use the GetSystemInfo function. If this parameter is NULL, the system determines where to allocate the region.
If this address is within an enclave that you have not initialized by calling InitializeEnclave, VirtualAlloc allocates a page of zeros for the enclave at that address. The page must be previously uncommitted, and will not be measured with the EEXTEND instruction of the Intel Software Guard Extensions programming model.
If the address is within an enclave that you initialized, then the allocation operation fails with the ERROR_INVALID_ADDRESS error. That is true for enclaves that do not support dynamic memory management (i.e. SGX1). SGX2 enclaves will permit allocation, and the page must be accepted by the enclave after it has been allocated.
[in] dwSize
The size of the region, in bytes. If the lpAddress parameter is NULL, this value is rounded up to the next page boundary. Otherwise, the allocated pages include all pages containing one or more bytes in the range from lpAddress to lpAddress+dwSize. This means that a 2-byte range straddling a page boundary causes both pages to be included in the allocated region.
[in] flAllocationType
The type of memory allocation. This parameter must contain one of the following values.
”
NtAllocateVirtualMemory:
”
[in] ProcessHandle
A handle for the process for which the mapping should be done. Use the NtCurrentProcess macro, defined in Ntddk.h, to specify the current process.
[in, out] BaseAddress
A pointer to a variable that will receive the base address of the allocated region of pages. If the initial value of BaseAddress is non-NULL, the region is allocated starting at the specified virtual address rounded down to the next host page size address boundary. If the initial value of BaseAddress is NULL, the operating system will determine where to allocate the region.
[in] ZeroBits
The number of high-order address bits that must be zero in the base address of the section view. Used only when the operating system determines where to allocate the region, as when BaseAddress* is NULL. Note that when ZeroBits is larger than 32, it becomes a bitmask.
[in, out] RegionSize
A pointer to a variable that will receive the actual size, in bytes, of the allocated region of pages. The initial value of RegionSize specifies the size, in bytes, of the region and is rounded up to the next host page size boundary. RegionSize cannot be zero on input.
[in] AllocationType
A bitmask containing flags that specify the type of allocation to be performed for the specified region of pages. The following table describes the most common flags. See VirtualAlloc for a full list of possible flags and descriptions.
”
The documentation shows that the approach is different:
VirtualAlloc takes a starting address and size of a memory region and the type of memory allocation. NtAllocateVirtualMemory instead takes a handle to the process for which mapping should be done, a pointer to a variable that receives the base address of the allocated region of memory pages, along with other ‘metadata’ like size and the type of allocation. It appears NtALlocateVirtualMemory relates more to allocating memory to a process where VirtualAlloc relates more to simply allocating memory.
Module 6
Use the demonstrated memory allocation functions to allocate memory.
Free the allocated memory buffers using the appropriate freeing function
#include <windows.h>#include <stdio.h>#include <malloc.h>// Allocating a memory buffer of *100* bytes.int main() {PVOID pAddress1 = malloc(100);printf("[+] Memory address of pAddress1 is: 0x%p \n", pAddress1);size_t size1 = _msize(pAddress1);printf("Allocated size to pAddress1 is: %zu\n", size1);PVOID pAddress2 = HeapAlloc(GetProcessHeap(), 0, 100);printf("[+] Memory address of pAddress2 is: 0x%p \n", pAddress2);size_t size2 = _msize(pAddress2);printf("Allocated size to pAddress1 is: %zu\n", size2);HANDLE hHeap = GetProcessHeap();SIZE_T size2_2 = HeapSize(hHeap, 0, pAddress2);printf("Allocated size to pAddress1 using HeapSize is: %zu\n", size2_2);PVOID pAddress3 = LocalAlloc(LPTR, 100);printf("[+] Memory address of pAddress3 is: 0x%p \n", pAddress3);size_t size3 = _msize(pAddress1);printf("Allocated size to pAddress3 is: %zu\n", size3);// Free the allocated memory buffers using the appropriate function.// To free memory allocated with malloc: free.printf("[+] Freeing pAddress1 [+]\n");free(pAddress1);printf("[+] Freed pAddress1 [+]\n");// To free memory allocated with HeapAlloc: HeapFreeprintf("[+] Freeing pAddress2 [+]\n");if (HeapFree(hHeap, 0, pAddress2) == NULL) { printf("+] Freed pAddress2 [+]");}}
// A short program to demonstrate dynamic memory allocation// using a structured exception handler.#include <windows.h>#include <tchar.h>#include <stdio.h>#include <stdlib.h> // For exit#define PAGELIMIT 80 // Number of pages to ask forLPTSTR lpNxtPage; // Address of the next page to ask forDWORD dwPages = 0; // Count of pages gotten so farDWORD dwPageSize; // Page size on this computerINT PageFaultExceptionFilter(DWORD dwCode){ LPVOID lpvResult; // If the exception is not a page fault, exit. if (dwCode != EXCEPTION_ACCESS_VIOLATION) { _tprintf(TEXT("Exception code = %d.\n"), dwCode); return EXCEPTION_EXECUTE_HANDLER; } _tprintf(TEXT("Exception is a page fault.\n")); // If the reserved pages are used up, exit. if (dwPages >= PAGELIMIT) { _tprintf(TEXT("Exception: out of pages.\n")); return EXCEPTION_EXECUTE_HANDLER; } // Otherwise, commit another page. lpvResult = VirtualAlloc( (LPVOID) lpNxtPage, // Next page to commit dwPageSize, // Page size, in bytes MEM_COMMIT, // Allocate a committed page PAGE_READWRITE); // Read/write access if (lpvResult == NULL ) { _tprintf(TEXT("VirtualAlloc failed.\n")); return EXCEPTION_EXECUTE_HANDLER; } else { _tprintf(TEXT("Allocating another page.\n")); } // Increment the page count, and advance lpNxtPage to the next page. dwPages++; lpNxtPage = (LPTSTR) ((PCHAR) lpNxtPage + dwPageSize); // Continue execution where the page fault occurred. return EXCEPTION_CONTINUE_EXECUTION;}VOID ErrorExit(LPTSTR lpMsg){ _tprintf(TEXT("Error! %s with error code of %ld.\n"), lpMsg, GetLastError ()); exit (0);}VOID _tmain(VOID){ LPVOID lpvBase; // Base address of the test memory LPTSTR lpPtr; // Generic character pointer BOOL bSuccess; // Flag DWORD i; // Generic counter SYSTEM_INFO sSysInfo; // Useful information about the system GetSystemInfo(&sSysInfo); // Initialize the structure. _tprintf (TEXT("This computer has page size %d.\n"), sSysInfo.dwPageSize); dwPageSize = sSysInfo.dwPageSize; // Reserve pages in the virtual address space of the process. lpvBase = VirtualAlloc( NULL, // System selects address PAGELIMIT*dwPageSize, // Size of allocation MEM_RESERVE, // Allocate reserved pages PAGE_NOACCESS); // Protection = no access if (lpvBase == NULL ) ErrorExit(TEXT("VirtualAlloc reserve failed.")); lpPtr = lpNxtPage = (LPTSTR) lpvBase; // Use structured exception handling when accessing the pages. // If a page fault occurs, the exception filter is executed to // commit another page from the reserved block of pages. for (i=0; i < PAGELIMIT*dwPageSize; i++) { __try { // Write to memory. lpPtr[i] = 'a'; } // If there's a page fault, commit another page and try again. __except ( PageFaultExceptionFilter( GetExceptionCode() ) ) { // This code is executed only if the filter function // is unsuccessful in committing the next page. _tprintf (TEXT("Exiting process.\n")); ExitProcess( GetLastError() ); } } // Release the block of pages when you are finished using them. bSuccess = VirtualFree( lpvBase, // Base address of block 0, // Bytes of committed pages MEM_RELEASE); // Decommit the pages _tprintf (TEXT("Release %s.\n"), bSuccess ? TEXT("succeeded") : TEXT("failed") );}
The output:
Relevant notes on unfamiliar functions, unclear code flow, etcetera:
#include tchar.h
From the documentation:
Generic-Text Mappings in tchar.h
To simplify the transporting of code for international use, the Microsoft run-time library provides Microsoft-specific generic-text mappings for many data types, routines, and other objects. You can use these mappings, which are defined in tchar.h, to write generic code that can be compiled for single-byte, multibyte, or Unicode character sets, depending on a manifest constant that you define by using a #define statement. Generic-text mappings are Microsoft extensions that are not ANSI compatible.
{ __try { // Write to memory. lpPtr[i] = 'a'; } // If there's a page fault, commit another page and try again. __except ( PageFaultExceptionFilter( GetExceptionCode() ) ) { // This code is executed only if the filter function // is unsuccessful in committing the next page. _tprintf (TEXT("Exiting process.\n")); ExitProcess( GetLastError() ); } }
From the documentation:
Reserving and Commiting Memory
The example uses structured exception-handling syntax to commit pages from the reserved region.
Whenever a page fault exception occurs during the execution of the __try block, the filter function in the expression preceding the __except block is executed.
If the filter function can allocate another page, execution continues in the __try block at the point where the exception occurred. Otherwise, the exception handler in the __except block is executed.
The documentation might have an error here: the PageFaultExceptionFilter-function is called not preceding, but inside the _except block.
Write to the allocated memory region using the RtlFillMemory WinAPI
The following addition works, but there are some warnings/errors that need fixing.
Corresponding formal and actual parameters have different types. The compiler passes the actual parameter without change. The receiving function converts the parameter type to the type expected.
Although the GlobalAlloc, LocalAlloc, and HeapAlloc functions ultimately allocate memory from the same heap, each provides a slightly different functionality. For example, HeapAlloc can be instructed to raise an exception if memory could not be allocated, a capability not available with LocalAlloc. LocalAlloc supports allocation of handles which permit the underlying memory to be moved by a reallocation without changing the handle value, a capability not available with HeapAlloc.
The VirtualAlloc function allows you to specify additional options for memory validation. (…) The malloc function has the disadvantage of being run-time dependent. (…) The CoTaskMemAlloc function (…) is the only way to share memory in a COM-based application (…).
Registers a callback function to be called when a secured memory range is freed or its protections are changed.
CopyDeviceMemory
Copies memory from one location to another without interference from compiler optimizations in situations where the developer needs to additionally be sure that alignment faults will not be generated when accessing device memory.
CopyMemory
Copies a block of memory from one location to another.
CopyVolatileMemory
Copies the contents of a source memory block to a destination memory block.
CreateMemoryResourceNotification
Creates a memory resource notification object.
FillDeviceMemory
Sets the contents of a buffer without interference from compiler optimizations in situations where the developer needs to additionally be sure that alignment faults will not be generated when accessing device memory.
FillMemory
Fills a block of memory with a specified value.
FillVolatileMemory
Fills a block of memory with the specified fill value.
GetLargePageMinimum
Retrieves the minimum size of a large page.
GetPhysicallyInstalledSystemMemory
Retrieves the amount of RAM that is physically installed on the computer.
GetSystemFileCacheSize
Retrieves the current size limits for the working set of the system cache.
GetWriteWatch
Retrieves the addresses of the pages that have been written to in a region of virtual memory.
GlobalMemoryStatusEx
Obtains information about the system’s current usage of both physical and virtual memory.
MoveMemory
Moves a block of memory from one location to another.
MoveVolatileMemory
Copies the contents of a source memory block to a destination memory block, and supports overlapping source and destination memory blocks.
QueryMemoryResourceNotification
Retrieves the state of the specified memory resource object.
RemoveSecureMemoryCacheCallback
Unregisters a callback function that was previously registered with the AddSecureMemoryCacheCallback function.
ResetWriteWatch
Resets the write-tracking state for a region of virtual memory.
SecureMemoryCacheCallback
An application-defined function that is called when a secured memory range is freed or its protections are changed.
SecureZeroMemory
Fills a block of memory with zeros.
SecureZeroMemory2
Fills a block of memory with zeros in a way that is guaranteed to be secure.
SetSystemFileCacheSize
Limits the size of the working set for the file system cache.
ZeroDeviceMemory
Sets the contents of a buffer to zeros without interference from compiler optimizations in situations where the developer needs to additionally be sure that alignment faults will not be generated when accessing device memory.
ZeroMemory
Fills a block of memory with zeros.
ZeroVolatileMemory
Fills a block of memory with zeros.
Module ?
Review Microsoft’s documentation for the list of Windows data types
The data types supported by Windows are used to define function return values, function and message parameters, and structure members. They define the size and meaning of these elements. (…) The following table contains the following types: character, integer, Boolean, pointer, and handle.
Below is a summarized table of data types used in this case or otherwise familiar.
Data type
Description
APIENTRY
The calling convention for system functions. This type is declared in WinDef.h as follows: #define APIENTRY WINAPI
CALLBACK
The calling convention for callback functions. This type is declared in WinDef.h as follows: #define CALLBACK __stdcall CALLBACK, WINAPI, and APIENTRY are all used to define functions with the __stdcall calling convention. Most functions in the Windows API are declared using WINAPI. You may wish to use CALLBACK for the callback functions that you implement to help identify the function as a callback function.
HANDLE
A handle to an object. This type is declared in WinNT.h as follows:typedef PVOID HANDLE;
LPSTR
A pointer to a null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts. This type is declared in WinNT.h as follows: typedef CHAR *LPSTR;
Creates a new process and its primary thread. The new process runs in the security context of the calling process. If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token. To run the new process in the security context of the user represented by the impersonation token, use the CreateProcessAsUserA function or CreateProcessWithLogonW function.
Below should identify the most important difference between CreateProcessA and CreateProcessW.
[in, out, optional] lpCommandLine
The command line to be executed.
The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.
View CreateProcessW’s last parameter, lpProcessInformation. Why is this considered an OUT parameter?
lpProcessInformation
A pointer to a PROCESS_INFORMATION structure that receives identification information about the new process.
Handles in PROCESS_INFORMATION must be closed with CloseHandle when they are no longer needed.
So, why is this considered an OUT parameter? CreateProcessA has the same parameter. Let’s see what we can find out about the PROCESS_INFORMATION-structure.
Contains information about a newly created process and its primary thread. It is used with the CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, or CreateProcessWithTokenW function.
There is no clear reason why lpProcessInformation is considered an OUT-parameter. Perhaps it has to do with the handles in PROCESS_INFORMATION that must be closed with CloseHandle when they are no longer needed. The ‘Remarks’-section does mention the PROCESS_INFORMATION-structure:
Remarks
The process is assigned a process identifier. The identifier is valid until the process terminates. It can be used to identify the process, or specified in the OpenProcess function to open a handle to the process. The initial thread in the process is also assigned a thread identifier. It can be specified in the OpenThread function to open a handle to the thread. The identifier is valid until the thread terminates and can be used to uniquely identify the thread within the system. These identifiers are returned in the PROCESS_INFORMATION structure.
So what we can glean from this, is that the OUT-parameter lpProcessInformation of CreateProcessA/W points to a structure which elements are identifiers that can be used to identify a process or specified in the OpenProcess-function to open a handle to the process.
Retrieve the name of the current user using the GetUserNameA WinAPI
Retrieves the name of the user associated with the current thread. Use the GetUserNameEx function to retrieve the user name in a specified format. Additional information is provided by the IADsADSystemInfo interface.
This documentation does not explain how to use the function to return the name of the current user. Luckily the page does point to some example code that uses the function.
TCHAR* and other TCHAR-mentions apparently point to now outdated macros having to do with mapping strings to ANSI or Unicode, depending on the platform.
GetUserName takes two variables: infoBuf (defined as a character array of the type TCHAR with a size of INFO_BUFFER_SIZE defined earlier as 32767) and &bufCharCount which is the type DWORD and is initialized with the value of INFO_BUFFER_SIZE (32767 (which is ‘2^15-1, so the maximum value of a 16-bit signed integer’ source).
To explain the syntax of if (!::GetUserName( infoBuf, &bufCharCount) ) using Codeconvert:
‘The :: scope resolution The :: scope resolution operator is typically used in C++ to specify the global namespace. In pure C, this operator is not valid, so this snippet is likely from C++ code or C code compiled with a C++ compiler. Using ::GetUserName ensures that the global GetUserName function is called, avoiding any possible name clashes with other functions named GetUserName in local or class scopes.’
‘The ! negates the return value. So, !::GetUserName(…) evaluates to true if GetUserName fails (returns zero). This means the code inside the if block will execute only when the username retrieval fails.’
#include <windows.h>#include <tchar.h>#include <stdio.h>const TCHAR* envVarStrings[] ={ TEXT("OS = %OS%"), TEXT("PATH = %PATH%"), TEXT("HOMEPATH = %HOMEPATH%"), TEXT("TEMP = %TEMP%")};#define ENV_VAR_STRING_COUNT (sizeof(envVarStrings)/sizeof(TCHAR*))#define INFO_BUFFER_SIZE 32767TCHAR infoBuf[INFO_BUFFER_SIZE] = {'\0'};void printError(const TCHAR* msg );void main( ){ DWORD i = 0; DWORD bufCharCount = INFO_BUFFER_SIZE; // Get and display the name of the computer. if( !::GetComputerName( infoBuf, &bufCharCount ) ) printError( TEXT("GetComputerName") ); _tprintf( TEXT("\nComputer name: %s"), infoBuf ); // Get and display the user name. bufCharCount = INFO_BUFFER_SIZE; if( !::GetUserName( infoBuf, &bufCharCount ) ) printError( TEXT("GetUserName") ); _tprintf( TEXT("\nUser name: %s"), infoBuf ); // Get and display the system directory. if( !::GetSystemDirectory( infoBuf, INFO_BUFFER_SIZE ) ) printError( TEXT("GetSystemDirectory") ); _tprintf( TEXT("\nSystem Directory: %s"), infoBuf ); // Get and display the Windows directory. if( !::GetWindowsDirectory( infoBuf, INFO_BUFFER_SIZE ) ) printError( TEXT("GetWindowsDirectory") ); _tprintf( TEXT("\nWindows Directory: %s"), infoBuf ); // Expand and display a few environment variables. _tprintf( TEXT("\n\nSmall selection of Environment Variables:") ); for( i = 0; i < ENV_VAR_STRING_COUNT; ++i ) { bufCharCount = ::ExpandEnvironmentStrings(envVarStrings[i], infoBuf, INFO_BUFFER_SIZE ); if( bufCharCount > INFO_BUFFER_SIZE ) _tprintf( TEXT("\n\t(Buffer too small to expand: \"%s\")"), envVarStrings[i] ); else if( !bufCharCount ) printError( TEXT("ExpandEnvironmentStrings") ); else _tprintf( TEXT("\n %s"), infoBuf ); } _tprintf( TEXT("\n\n"));}void printError(const TCHAR* msg ){ TCHAR sysMsg[MAX_PATH] = {'\0'}; TCHAR* p = sysMsg; DWORD eNum = ::GetLastError(); ::FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, eNum, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), sysMsg, MAX_PATH, nullptr ); // Trim the end of the line and terminate it with a null // 9 - \t (horizontal tab) // [0 - 32) - All characters in this area excepting 9 // 46 - . (dot) while (*p++) { if ((*p != 9 && *p < 32) || *p == 46) { *p = 0; break; } } // Display the message _tprintf( TEXT("\n\t%s failed with error %d (%s)"), msg, eNum, sysMsg ); p = nullptr;}
Funnily enough (define funny), everything about this example works, except for the GetUserName-function and -code:
Including the code using GetUserName, I get this error:
Linker errors abound! Let’s see if we can figure this out.
‘the link error is because the Win32 API lives in Advapi32.lib and the Windows Forms (/clr) template does not add that library by default.’
‘If you mix ANSI and Unicode, you can use the encoding-neutral alias GetUserName with TCHAR/_T(), but keep the whole call site consistent to avoid A/W mismatches, (…)’
Create personal code without the TCHAR-nonsense
Explain code
#include <windows.h>#include <stdio.h>int main() {char b[256];DWORD n = sizeof(b);if (!GetUserNameA(b, &n);printf("Error");printf("User's name is: %s", b);}
The above code removes the need of TCHAR and related macros and instead uses simple printf statements. What it does:
initialize a character buffer of 256 elements a
Initializes a DWORD n the size of a
if a call to GetUserNameA doesn’t work, print an error message
Otherwise, print the characters inside the b-variable which should hold the user ‘associated with the current thread’, meaning the current user outside of user impersonation scenarios.
By combining the NTSTATUS into a single 32-bit numbering space, the following NTSTATUS values are defined. Most values also have a defined default message that can be used to map the value to a human-readable text message. When this is done, the NTSTATUS value is also known as a message identifier. (…) In the following descriptions, a percentage sign that is followed by one or more alphanumeric characters (for example, “%1” or “%hs”) indicates a variable that is replaced by text at the time the value is returned.
You don’t distinguish between them. The wait functions return STATUS_WAIT_0, STATUS_WAIT_1, and so on up to STATUS_WAIT_63. And these status codes have values, 0, 1, and so on up to 63. So, if the code is in the range STATUS_WAIT_0 to STATUS_WAIT_63 inclusive, you can identify the index of the wait object that became signaled by using the expression StatusCode - STATUS_WAIT_0.
Pass an incorrect parameter and check what value the GetLastError WinAPI returns.
The code above works, but it doesn’t display a useful error message on it’s own. To achieve that, we can use the FormatMessage() that the documentation recommends (source to ‘to retrieve the description text for the error in your application, use the FormatMessage function with the FORMAT_MESSAGE_FROM_SYSTEM flag’. We can simply achieve this by adding the example code from [
Source
Identifies a string as Unicode when UNICODE is defined by a preprocessor directive during compilation. Otherwise, the macro identifies a string as an ANSI string.
To explain the previous errors where functions (GetUserNameA, FormatMessage) give cl.exe errors on unresolved external symbols, some DuckAI notes:
Because FormatMessage (and many Win32 APIs) is declared in a header but implemented in a separate library. The compiler (cl.exe) only translates source to object code and relies on the linker to resolve external function references. Adding /link user32.lib (or specifying the library on the link line) tells the linker where to find the compiled implementation for the FormatMessage symbol so it can produce a runnable EXE.
Concise points:
windows.h gives the prototype so your code compiles.
The linker must find the actual function in an import library (user32.lib or kernel32.lib depending on the function).
If the linker can’t find the symbol you get “unresolved external” errors; supplying the correct .lib resolves them.
Using Visual Studio’s IDE or a proper build system normally adds the required .lib automatically; with cl.exe you must pass the library with /link user32.lib (or add it to the LINK environment) or use the appropriate pragma in source:
#pragma comment(lib, "user32.lib")
or call the function that actually lives in kernel32.lib (FormatMessage is in Kernel32 on modern Windows), so you can also link kernel32.lib instead:
#pragma comment(lib,"kernel32.lib")
Use the pragma or /link to avoid unresolved externals
Source
Pragma directives specify machine-specific or operating system-specific compiler features. A line that starts with pragma specifies a pragma directive. The Microsoft-specific __pragma keyword enables you to code pragma directives within macro definitions. The standard _Pragma preprocessor operator, introduced in C99 and adopted by C++11, is similar.
Module 7
Open any EXE file with PE-Bear, click ‘DOS Header’ and locate the bytes 0x4D and 0x5A
Find the value of ‘e_lfanew’ (Hint: Look for the offset 0x3C)
Under the ‘File Hdr’ tab in PE-Bear, look at the value of ‘Sections Count’. Verify that number with the number of sections in the ‘Sections Hdrs’ tab.
‘File Hdr’ → ‘Sections Count’: 6
‘Sections Hdrs’:
View the imported DLLs and Windows APIs under the ‘Imports’ tab
Module 8
Review the 4 entry points for a DLL
Read the documentation on LoadLibrary, GetModuleHandle and GetProcAddress
Some more notes from this chapter of the documentation:
”
Rules are generally composed of two sections: strings definition and condition.
The strings definition section can be omitted if the rule doesn’t rely on any string, but the condition section is always required. The strings definition section is where the strings that will be part of the rule are defined. Each string has an identifier consisting of a $ character followed by a sequence of alphanumeric characters and underscores, these identifiers can be used in the condition section to refer to the corresponding string. Strings can be defined in text or hexadecimal.(…) Text strings are enclosed in double quotes just like in the C language. Hex strings are enclosed by curly brackets, and they are composed by a sequence of hexadecimal numbers that can appear contiguously or separated by spaces. Decimal numbers are not allowed in hex strings.
The condition section is where the logic of the rule resides. This section must contain a boolean expression telling under which circumstances a file or process satisfies the rule or not. Generally, the condition will refer to previously defined strings by using their identifiers. In this context the string identifier acts as a boolean variable which evaluate to true if the string was found in the file or process memory, or false if otherwise.
”
Other important aspects of rules:
”
Global rules give you the possibility of imposing restrictions in all your rules at once. (…) they will be evaluated before the rest of the rules, which in turn will be evaluated only if all global rules are satisifed.
Private rules are (…) not reported by YARA when they match on a give file.
Rule tags (…) can be used later to filter YARA’s output and show only the rules that you are interested in.
“
Search the following file hash on VirusTotal: e8ac867e5f51bdcf5ab7b06a8bced131
Click the ‘Behavior’ tab on the file’s results in VirusTotal. What suspicious/malicious behavior does this file perform?
Below are some aspects of suspicious/malicious behavior this file performs.
Info
VirusTotal categorizes malware behavior in the ‘Behavior’ tab not with Mitre Att&ck-framework taxonomy (f.e. T1659 for ‘Content Injection’) but instead an unfamiliar ‘OB0001’ for ‘Anti-Behavior Analysis. These codes link back to the ‘MBC Project’ or ‘Malware Behavior Catalog (link to Github-repo.
About the relationship ATT&CK-MBC: “As a publicly available framework, The Malware Behavior Catalog (MBC) aims to directly and explicitly define malware behaviors and code characteristics to support malware analysis-oriented use cases. MBC defines behaviors outside ATT&CK’s scope and enhances some ATT&CK techniques and sub-techniques to be malware-focused.”
[in] dwDesiredAccess
The access to the process object. This access right is checked against the security descriptor for the process. This parameter can be one or more of the process access rights.
If the caller has enabled the SeDebugPrivilege privilege, the requested access is granted regardless of the contents of the security descriptor.
[in] bInheritHandle
If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do not inherit this handle.
[in] dwProcessId
The identifier of the local process to be opened.
If the specified process is the System Idle Process (0x00000000), the function fails and the last error code is ERROR_INVALID_PARAMETER. If the specified process is the System process or one of the Client Server Run-Time Subsystem (CSRSS) processes, this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them.
If you are using GetCurrentProcessId as an argument to this function, consider using GetCurrentProcess instead of OpenProcess, for improved performance.
Return value
If the function succeeds, the return value is an open handle to the specified process.
If the function fails, the return value is NULL. To get extended error information, call GetLastError.
Optional: Run notepad.exe and then use OpenProcess to get a handle to the process (Hint: copy notepad’s PID from Process Hacker)
To print a handle in C using printf(), you typically convert the handle to a pointer type and use the %p format specifier.
For example, if you have a handle defined as HANDLE h = …;, you would write printf(“Handle: %p\n”, (void*)h); to display it.
To reiterate, this module names three types of memory for processes:
Private memory → dedicated to a single process.
Mapped memory → shared libraries, shared memory segments, and shared files. Mapped memory is visible to other processes, but is protected from being modified by other processes.
Source”A memory-mapped file contains the contents of a file in virtual memory. This mapping between a file and memory space enables an application, including multiple processes, to modify the file by reading and writing directly to the memory.(…)There are two types of memory-mapped files:- Persisted memory-mapped filesPersisted files (…) are associated with a source file on a disk. When the last process has finished working with the file, the data is saved to the source file on the disk. These memory-mapped files are suitable for working with extremely large source files.- Non-persisted memory-mapped filesNon-persisted files (…) are not associated with a file on a disk. When the last process has finished working with the file, the data is lost and the file is reclaimed by garbage collection. These files are suitable for creating shared memory for inter-process communications (IPC).
_To work with a memory-mapped file, you must create a view of the entire memory-mapped file or a part of it. (…) Use stream access views for sequential access to a file; this is recommended for non-persisted files and IPC. Random access views are preferred for working with persisted files. _
(…)
”
This documentation page also features some example code (unfortunately C#, which I’m unfamiliar with). Some notes from this code:
“Persisted Memory-Mapped Files”
create a random access view from a 256 megabytes offset to the 768th megabyte (the offset plus a 512 megabyte length): var accessor = mmf.CreateViewAccessor(offset, length)
Image memory → contains the code and data of an executable file, DLL files loaded into address space.
BeingDebugged
Indicates whether the specified process is currently being debugged. The PEB structure, however, is an internal operating-system structure whose layout may change in the future. It is best to use the CheckRemoteDebuggerPresent function instead.
Ldr
A pointer to a PEB_LDR_DATA structure that contains information about the loaded modules for the process.
ProcessParameters
A pointer to an RTL_USER_PROCESS_PARAMETERS structure that contains process parameter information such as the command line.
AtlThunkSListPtr
AtlThunkSListPtr32
PostProcessInitRoutine
Not supported.
SessionId
The Terminal Services session identifier associated with the current process.
[This structure may be altered in future versions of Windows. Applications should use the alternate functions listed in this topic.]
The Thread Environment Block (TEB) structure describes the state of a thread.
ProcessEnvironmentBlock
A pointer to the PEB structure that contains information for the process as a whole.
TlsSlots[64]
Data for Thread Local Storage. Call the TlsGetValue function to access it.
ReservedForOle
Do not use. Call CoGetContextToken instead.
TlsExpansionSlots
Additional data for Thread Local Storage. Call the TlsGetValue function to access it.
Remarks
The definition of this structure may change from one version of Windows to the next. Do not assume a maximum size for this structure. To see the members of this structure, refer to winternal.h.
You should not directly access this structure. To access the values of the TlsSlots and TlsExpansionSlots fields, call TlsGetValue. To access the value of the ReservedForOle field, call CoGetContextToken.
In the following versions of Windows, the offset of the 32-bit TEB address within the 64-bit TEB is 0. This can be used to directly access the 32-bit TEB of a WOW64 thread. This might change in later versions of Windows.
Review of the PEB and TEB structures
The TEB structure contains a pointer to the PEB structure
Both structures are stated not to be directly accessed as the definition may change per Windows version
Both structures have several members that are reserved ‘for internal use by the operating system’ (these members are of type PVOID, ULONG)
The header for both structures is winternl.h
Module 11
Compare Microsoft’s documentation on PEB with the unofficial one on Process Hacker’s Header Files
Second, the unofficial PEB structure definition from the Process Hacker/System Informer:
/** * Process Environment Block (PEB) structure. * * \sa https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb */typedef struct _PEB{ // // The process was cloned with an inherited address space. // BOOLEAN InheritedAddressSpace; // // The process has image file execution options (IFEO). // BOOLEAN ReadImageFileExecOptions; // // The process has a debugger attached. // BOOLEAN BeingDebugged; union { BOOLEAN BitField; struct { BOOLEAN ImageUsesLargePages : 1; // The process uses large image regions (4 MB). BOOLEAN IsProtectedProcess : 1; // The process is a protected process. BOOLEAN IsImageDynamicallyRelocated : 1; // The process image base address was relocated. BOOLEAN SkipPatchingUser32Forwarders : 1; // The process skipped forwarders for User32.dll functions. 1 for 64-bit, 0 for 32-bit. BOOLEAN IsPackagedProcess : 1; // The process is a packaged store process (APPX/MSIX). BOOLEAN IsAppContainerProcess : 1; // The process has an AppContainer token. BOOLEAN IsProtectedProcessLight : 1; // The process is a protected process (light). BOOLEAN IsLongPathAwareProcess : 1; // The process is long path aware. }; }; // // Handle to a mutex for synchronization. // HANDLE Mutant; // // Pointer to the base address of the process image. // PVOID ImageBaseAddress; // // Pointer to the process loader data. // PPEB_LDR_DATA Ldr; // // Pointer to the process parameters. // PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // // Reserved. // PVOID SubSystemData; // // Pointer to the process default heap. // PHEAP ProcessHeap; // // Pointer to a critical section used to synchronize access to the PEB. // PRTL_CRITICAL_SECTION FastPebLock; // // Pointer to a singly linked list used by ATL. // PSLIST_HEADER AtlThunkSListPtr; // // Handle to the Image File Execution Options key. // HANDLE IFEOKey; // // Cross process flags. // union { ULONG CrossProcessFlags; struct { ULONG ProcessInJob : 1; // The process is part of a job. ULONG ProcessInitializing : 1; // The process is initializing. ULONG ProcessUsingVEH : 1; // The process is using VEH. ULONG ProcessUsingVCH : 1; // The process is using VCH. ULONG ProcessUsingFTH : 1; // The process is using FTH. ULONG ProcessPreviouslyThrottled : 1; // The process was previously throttled. ULONG ProcessCurrentlyThrottled : 1; // The process is currently throttled. ULONG ProcessImagesHotPatched : 1; // The process images are hot patched. // RS5 ULONG ReservedBits0 : 24; }; }; // // User32 KERNEL_CALLBACK_TABLE (ntuser.h) // union { PKERNEL_CALLBACK_TABLE KernelCallbackTable; PVOID UserSharedInfoPtr; }; // // Reserved. // ULONG SystemReserved; // // Pointer to the Active Template Library (ATL) singly linked list (32-bit) // ULONG AtlThunkSListPtr32; // // Pointer to the API Set Schema. // PAPI_SET_NAMESPACE ApiSetMap; // // Counter for TLS expansion. // ULONG TlsExpansionCounter; // // Pointer to the TLS bitmap. // PRTL_BITMAP TlsBitmap; // // Bits for the TLS bitmap. // ULONG TlsBitmapBits[2]; // // Reserved for CSRSS. // PVOID ReadOnlySharedMemoryBase; // // Pointer to the USER_SHARED_DATA for the current SILO. // PSILO_USER_SHARED_DATA SharedData; // // Reserved for CSRSS. // PVOID* ReadOnlyStaticServerData; // // Pointer to the ANSI code page data. // PCPTABLEINFO AnsiCodePageData; // // Pointer to the OEM code page data. // PCPTABLEINFO OemCodePageData; // // Pointer to the Unicode case table data. // PNLSTABLEINFO UnicodeCaseTableData; // // The total number of system processors. // ULONG NumberOfProcessors; // // Global flags for the system. // union { ULONG NtGlobalFlag; struct { ULONG StopOnException : 1; // FLG_STOP_ON_EXCEPTION ULONG ShowLoaderSnaps : 1; // FLG_SHOW_LDR_SNAPS ULONG DebugInitialCommand : 1; // FLG_DEBUG_INITIAL_COMMAND ULONG StopOnHungGUI : 1; // FLG_STOP_ON_HUNG_GUI ULONG HeapEnableTailCheck : 1; // FLG_HEAP_ENABLE_TAIL_CHECK ULONG HeapEnableFreeCheck : 1; // FLG_HEAP_ENABLE_FREE_CHECK ULONG HeapValidateParameters : 1; // FLG_HEAP_VALIDATE_PARAMETERS ULONG HeapValidateAll : 1; // FLG_HEAP_VALIDATE_ALL ULONG ApplicationVerifier : 1; // FLG_APPLICATION_VERIFIER ULONG MonitorSilentProcessExit : 1; // FLG_MONITOR_SILENT_PROCESS_EXIT ULONG PoolEnableTagging : 1; // FLG_POOL_ENABLE_TAGGING ULONG HeapEnableTagging : 1; // FLG_HEAP_ENABLE_TAGGING ULONG UserStackTraceDb : 1; // FLG_USER_STACK_TRACE_DB ULONG KernelStackTraceDb : 1; // FLG_KERNEL_STACK_TRACE_DB ULONG MaintainObjectTypeList : 1; // FLG_MAINTAIN_OBJECT_TYPELIST ULONG HeapEnableTagByDll : 1; // FLG_HEAP_ENABLE_TAG_BY_DLL ULONG DisableStackExtension : 1; // FLG_DISABLE_STACK_EXTENSION ULONG EnableCsrDebug : 1; // FLG_ENABLE_CSRDEBUG ULONG EnableKDebugSymbolLoad : 1; // FLG_ENABLE_KDEBUG_SYMBOL_LOAD ULONG DisablePageKernelStacks : 1; // FLG_DISABLE_PAGE_KERNEL_STACKS ULONG EnableSystemCritBreaks : 1; // FLG_ENABLE_SYSTEM_CRIT_BREAKS ULONG HeapDisableCoalescing : 1; // FLG_HEAP_DISABLE_COALESCING ULONG EnableCloseExceptions : 1; // FLG_ENABLE_CLOSE_EXCEPTIONS ULONG EnableExceptionLogging : 1; // FLG_ENABLE_EXCEPTION_LOGGING ULONG EnableHandleTypeTagging : 1; // FLG_ENABLE_HANDLE_TYPE_TAGGING ULONG HeapPageAllocs : 1; // FLG_HEAP_PAGE_ALLOCS ULONG DebugInitialCommandEx : 1; // FLG_DEBUG_INITIAL_COMMAND_EX ULONG DisableDbgPrint : 1; // FLG_DISABLE_DBGPRINT ULONG CritSecEventCreation : 1; // FLG_CRITSEC_EVENT_CREATION ULONG LdrTopDown : 1; // FLG_LDR_TOP_DOWN ULONG EnableHandleExceptions : 1; // FLG_ENABLE_HANDLE_EXCEPTIONS ULONG DisableProtDlls : 1; // FLG_DISABLE_PROTDLLS } NtGlobalFlags; }; // // Timeout for critical sections. // LARGE_INTEGER CriticalSectionTimeout; // // Reserved size for heap segments. // SIZE_T HeapSegmentReserve; // // Committed size for heap segments. // SIZE_T HeapSegmentCommit; // // Threshold for decommitting total free heap. // SIZE_T HeapDeCommitTotalFreeThreshold; // // Threshold for decommitting free heap blocks. // SIZE_T HeapDeCommitFreeBlockThreshold; // // Number of process heaps. // ULONG NumberOfHeaps; // // Maximum number of process heaps. // ULONG MaximumNumberOfHeaps; // // Pointer to an array of process heaps. ProcessHeaps is initialized // to point to the first free byte after the PEB and MaximumNumberOfHeaps // is computed from the page size used to hold the PEB, less the fixed // size of this data structure. // PVOID* ProcessHeaps; // // Pointer to the system GDI shared handle table. // PGDI_HANDLE_ENTRY GdiSharedHandleTable; // // Pointer to the process starter helper. // PPS_PROCESS_START_ROUTINE ProcessStarterHelper; // // The maximum number of GDI function calls during batch operations (GdiSetBatchLimit) // ULONG GdiDCAttributeList; // // Pointer to the loader lock critical section. // PRTL_CRITICAL_SECTION LoaderLock; // // Major version of the operating system. // ULONG OSMajorVersion; // // Minor version of the operating system. // ULONG OSMinorVersion; // // Build number of the operating system. // USHORT OSBuildNumber; // // CSD version of the operating system. // USHORT OSCSDVersion; // // Platform ID of the operating system. // ULONG OSPlatformId; // // Subsystem version of the current process image (PE Headers). // ULONG ImageSubsystem; // // Major version of the current process image subsystem (PE Headers). // ULONG ImageSubsystemMajorVersion; // // Minor version of the current process image subsystem (PE Headers). // ULONG ImageSubsystemMinorVersion; // // Affinity mask for the current process. // KAFFINITY ActiveProcessAffinityMask; // // Temporary buffer for GDI handles accumulated in the current batch. // GDI_HANDLE_BUFFER GdiHandleBuffer; // // Pointer to the post-process initialization routine available for use by the application. // PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; // // Pointer to the TLS expansion bitmap. // PRTL_BITMAP TlsExpansionBitmap; // // Bits for the TLS expansion bitmap. TLS_EXPANSION_SLOTS // ULONG TlsExpansionBitmapBits[32]; // // Session ID of the current process. // ULONG SessionId; // // Application compatibility flags. KACF_* // ULARGE_INTEGER AppCompatFlags; // // Application compatibility flags. KACF_* // ULARGE_INTEGER AppCompatFlagsUser; // // Pointer to the Application SwitchBack Compatibility Engine. // PSHIM_PROCESS_CONTEXT pShimData; // // Pointer to the Application Compatibility Engine. // PAPPCOMPAT_EXE_DATA AppCompatInfo; // // CSD version string of the operating system. // UNICODE_STRING CSDVersion; // // Pointer to the process activation context. // PACTIVATION_CONTEXT_DATA ActivationContextData; // // Pointer to the process assembly storage map. // PASSEMBLY_STORAGE_MAP ProcessAssemblyStorageMap; // // Pointer to the system default activation context. // PACTIVATION_CONTEXT_DATA SystemDefaultActivationContextData; // // Pointer to the system assembly storage map. // PASSEMBLY_STORAGE_MAP SystemAssemblyStorageMap; // // Minimum stack commit size. // SIZE_T MinimumStackCommit; // // since 19H1 (previously FlsCallback to FlsHighIndex) // PVOID SparePointers[2]; // // Pointer to the patch loader data. // PLDR_PATCH_TABLE PatchLoaderData; // // Pointer to the CHPE V2 process information. CHPEV2_PROCESS_INFO // PVOID ChpeV2ProcessInfo; // // Packaged process feature state. // ULONG AppModelFeatureState; // // SpareUlongs // ULONG SpareUlongs[2]; // // Active code page. // USHORT ActiveCodePage; // // OEM code page. // USHORT OemCodePage; // // Code page case mapping. // USHORT UseCaseMapping; // // Unused NLS field. // USHORT UnusedNlsField; // // Pointer to the application WER registration data. // PWER_PEB_HEADER_BLOCK WerRegistrationData; // // Pointer to the application WER assert pointer. // PWER_REGISTRATION_DATA WerShipAssertPtr; // // Pointer to the EC bitmap on ARM64. (Windows 11 and above) // union { PVOID pContextData; // Pointer to the switchback compatibility engine (Windows 7 and below) PVOID EcCodeBitMap; // Pointer to the EC bitmap on ARM64 (Windows 11 and above) // since WIN11 }; // // Reserved. // PVOID ImageHeaderHash; // // ETW tracing flags. // union { ULONG TracingFlags; struct { ULONG HeapTracingEnabled : 1; // ETW heap tracing enabled. ULONG CritSecTracingEnabled : 1; // ETW lock tracing enabled. ULONG LibLoaderTracingEnabled : 1; // ETW loader tracing enabled. ULONG SpareTracingBits : 29; }; }; // // Reserved for CSRSS. // ULONGLONG CsrServerReadOnlySharedMemoryBase; // // Thread pool worker list lock. // PRTL_SRWLOCK TppWorkerpListLock; // // Thread pool worker list. // LIST_ENTRY TppWorkerpList; // // Wait on address hash table. (RtlWaitOnAddress) // PVOID WaitOnAddressHashTable[128]; // // Pointer to the telemetry coverage header. // since RS3 // PTELEMETRY_COVERAGE_HEADER TelemetryCoverageHeader; // // Cloud file flags. (ProjFs and Cloud Files) // since RS4 // ULONG CloudFileFlags; // // Cloud file diagnostic flags. // ULONG CloudFileDiagFlags; // // Placeholder compatibility mode. (ProjFs and Cloud Files) // CHAR PlaceholderCompatibilityMode; // // Reserved for placeholder compatibility mode. // CHAR PlaceholderCompatibilityModeReserved[7]; // // Pointer to leap second data. // since RS5 // PLEAP_SECOND_DATA LeapSecondData; // // Leap second flags. // union { ULONG LeapSecondFlags; struct { ULONG SixtySecondEnabled : 1; // Leap seconds enabled. ULONG Reserved : 31; }; }; // // Global flags for the process. // ULONG NtGlobalFlag2; // // Extended feature disable mask (AVX). // since WIN11 // ULONGLONG ExtendedFeatureDisableMask;} PEB, *PPEB;
Some general notes first:
The unofficial documentation mentions several UNION-type elements, like BitField. Unions are user-defined data types that can hold different data types, which unlike a structure, stores all its members in the same memory location.
The particular BitField-member contains the following struct:
struct { BOOLEAN ImageUsesLargePages : 1; // The process uses large image regions (4 MB). BOOLEAN IsProtectedProcess : 1; // The process is a protected process. BOOLEAN IsImageDynamicallyRelocated : 1; // The process image base address was relocated. BOOLEAN SkipPatchingUser32Forwarders : 1; // The process skipped forwarders for User32.dll functions. 1 for 64-bit, 0 for 32-bit. BOOLEAN IsPackagedProcess : 1; // The process is a packaged store process (APPX/MSIX). BOOLEAN IsAppContainerProcess : 1; // The process has an AppContainer token. BOOLEAN IsProtectedProcessLight : 1; // The process is a protected process (light). BOOLEAN IsLongPathAwareProcess : 1; // The process is long path aware. };
Like structures, unions and bit-fields have named fields, but they treat them differently. Bit-fields allow programmers to specify each field’s size measured in bits. Unions can only save one field at a time, allocating only enough space to save the largest and “wasting” space when saving the smaller ones.
A structure allocates enough memory to contain all the fields simultaneously. It’s like a basket that can hold many items and allows the program to handle them as a group.
In contrast, a union typically specifies two or more fields of different types but only allocates enough memory to contain the largest. All fields share this memory, implying that a union can only hold one field value at a time.
Second, we compare the Reserved-members and the unofficial PEB-struct-members to identify some members of interest to investigate further with comments explaining their functionality:
- `Reserved1[2]`
- `BOOLEAN InheritedAddressSpace`
- `BOOLEAN ReadImageFileExecOptions`
- `Reserved2[1]`
- `BOOLEAN BitField`
- `Reserved3[2]`
- `HANDLE Mutant`
- `PVOID ImageBaseAddress`
- `Reserved[4]`
- `PVOID SubSystemData` (Reserved)
- `PHEAP ProcessHeap` (Pointer to the process default heap)
- `PRTL_CRITICAL_SECTION FastPebLock` (Pointer to a critical section used to synchronize access to the PEB.)
- `PSLIST_HEADER AtlThunkSListPtr` (Pointer to a singly linked list used by ATL)
- `Reserved5`
- `HANDLE IFEOKey` (Handle to the Image File Execution Options key)
- `Reserved6`
- union `ULONG CrossProcessFlags` (Cross process flags)
- `Reserved7`
- union `PKERNEL_CALLBACK_TABLE KernelCallbackTable` (ntuser.h)
- `Reserved8`
- `ULONG SystemReserved` (Reserved)
- `Reserved9[45]`
- Too many to name here (45!) but some of interest:
- `PAPI_SET_NAMESPACE ApiSetMap` (Pointer to the API Set Schema)
- `ULONG NtGlobalFlag` (Global flags for the system)
You can use a mutex object to protect a shared resource from simultaneous access by multiple threads or processes. Each thread must wait for ownership of the mutex before it can execute the code that accesses the shared resource. For example, if several threads share access to a database, the threads can use a mutex object to permit only one thread at a time to write to the database.
Some example code on how to create and use mutexes can be found in this Microsoft-documentation.
To conclude the answer to this objective question:
The Reserved2[x] PEB-struct members refer to separate members that are defined in the unofficial documentation, like InheritedAddressSpace and ReadImageFileExecOptions.
The reserved, undocumented members have to do with characteristics of the process in question: the images base address, process ‘flags’ set via a the fields of a bitfield
Briefly review the alternative documentation sources provided in the module
Process Hacker’s Header Files
Doesn’t feature any documentation, same source of previously documented PEB-struct-members not explained in official documentation.
Undocumented.ntinternals.net - Some structures may be outdated
The Undocumented Functions
Microsoft Windows NT/2000/XP/Win7
Microsoft Windows NT/2000/XP/Win7
Currently includes: UserMode (Kernel Mode soon)
This is an advanced, low-level programer’s guide to Windows NT Kernel, Native API and drivers.
All remarks, fixes and comments are very welcome.
So for example the page ‘NtQuerySystemInformation’-page:
NTSYSAPI NTSTATUSNTAPINtQuerySystemInformation( IN SYSTEM_INFORMATION_CLASS SystemInformationClass, OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL );
NtQuerySystemInformation is used to check some system informations avaiable only in KernelMode (above 0x80000000). All avaiable (or all known) information classes are described in SYSTEM_INFORMATION_CLASS.
ReactOS’s Documentation
From the ‘Main Page’-page:
p!note] source: https://doxygen.reactos.org/index.html
This is a cross-reference of the ReactOS source code produced using the excellent Doxygen package. It is refreshed on a daily basis.
You can search the documentation using the menu and Search box on the left side of the page.
Vergilius Project - Although mainly for Windows kernel structures, it remains a valuable resource.
![note] Source: https://www.vergiliusproject.com/about
About
This project provides a collection of Microsoft Windows kernel structures, unionsand enumerations. Most of them are not officially documented and cannot be found in Windows Driver Kit (WDK) headers. The target audience of this site is driver developers and kernel researches.
Where did we get the information?
Information about kernel structures, unions and enumerations is extracted frompublicly available PDB files. We wrote a sophisticated parser that reconstructs C/C++ code from PDB files. So far it”s the best known reconstructor as it can handle const/volatile modifiers, bit fields, function pointers, unions, arrays and anonymous types.
PDB-files, for reference, are ‘program database files’. From Microsoft documentation:
Program database (.pdb) files, also called symbol files, map identifiers and statements in your project’s source code to corresponding identifiers and instructions in compiled apps. These mapping files link the debugger to your source code, which enables debugging.
When you build a project from the Visual Studio IDE with the standard Debug build configuration, the compiler creates the appropriate symbol files.
To make this information useful for the purposes of this course, we can take an ‘opaque’ structure like EPROCESS, which Microsoft states ‘serves as the the process object for a process’ (https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/eprocess). Looking at the documentation for the structure in the Vergilius project, see the definition below. Some notes:
The memory addresses in hex make it clear that, as before, a union can have members that appear to occupy the same memory address (ox1f0 for ULONG Flags2 for example).