Decrypting Wireless Passwords on Windows

Introduction

We shall only focus on versions of Windows from Vista up to 10 since the location of wireless credentials prior to this are stored in the registry.

Enumerate Interfaces

C:\>reg query HKLM\SOFTWARE\Microsoft\Wlansvc\Interfaces

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{012C8D7D-D104-47F8-83FE-B9751F699A2F}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{12487653-A815-4A9D-84E1-736D96554215}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{13ECC539-1C25-4036-9615-D85A5D1B47CE}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{295A80EE-8DC0-465F-ACF2-70DA1F036948}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{3306C2A4-E4A5-42B3-A087-67EB542F0EC0}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{40F69FB8-F23A-433E-94D9-D3983F220874}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{66BA1985-E08C-4F57-9E36-2C229EB1BFCE}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{6E318579-A15B-43AA-BC85-5A38133888A6}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{AFEED710-CC5E-41BD-8975-E6EE0487B5C9}
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{F6B2D705-CCA4-439D-B7AF-4950A98CE00C}

To perform this programatically in C.

DWORD EnumInterfaces(VOID) {
  HKEY         hSubKey;
  DWORD        dwError, dwIndex, cbSize; 
  WCHAR        adapterGuid[256], profileList[4096*4];
  PWCHAR       pProfileGuid;
  std::wstring description;
  
  dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
      L"SOFTWARE\\Microsoft\\Wlansvc\\Interfaces", 0, 
      KEY_ENUMERATE_SUB_KEYS | KEY_WOW64_64KEY, &hSubKey);
  
  if (dwError != ERROR_SUCCESS) {
    xstrerror(L"RegOpenKeyEx(\"SOFTWARE\\Microsoft\\Wlansvc\\Interfaces\"");
    return 0;
  }
  
  dwIndex = 0;
  
  for (;;) {
    cbSize = sizeof(adapterGuid) / sizeof(wchar_t);
    dwError = RegEnumKeyEx(hSubKey, dwIndex, adapterGuid, 
        &cbSize, NULL, NULL, NULL, NULL);
    
    if (dwError != ERROR_SUCCESS) break; 
  
    if (dwError == ERROR_SUCCESS) {
      description = GetAdapterDescription(adapterGuid);
      
      cbSize = sizeof(profileList) / sizeof(wchar_t);
      dwError = RegGetValue(hSubKey, adapterGuid, L"ProfileList", 
          RRF_RT_REG_MULTI_SZ, 0, profileList, &cbSize);
          
      if (dwError == ERROR_SUCCESS) {
        pProfileGuid = profileList;
      wprintf(L"\n\n  %s %s", description.c_str(), adapterGuid);
      
      wprintf(L"\n  %-20s  %-10s  %-20s  %-64s  %-20s", 
          std::wstring(20, L'-').c_str(), 
          std::wstring(10, L'-').c_str(),  
          std::wstring(20, L'-').c_str(), 
          std::wstring(20, L'-').c_str(),
          std::wstring(20, L'-').c_str());
      
      wprintf(L"\n  %-20s  %-10s  %-20s  %-64s  %-20s", 
          L"SSID", L"Auth", L"Encryption", L"Key(Ascii)", L"Key(Hex)");
          
      wprintf(L"\n  %-20s  %-10s  %-20s  %-64s  %-20s", 
          std::wstring(20, L'-').c_str(), 
          std::wstring(10, L'-').c_str(),  
          std::wstring(20, L'-').c_str(), 
          std::wstring(20, L'-').c_str(),
          std::wstring(20, L'-').c_str());
          
        for (;;) {
          DumpWLANProfile(adapterGuid, pProfileGuid);
          pProfileGuid += wcslen(pProfileGuid) + 1;
          if (pProfileGuid[0] == 0) break;
        }
      } 
    }
    dwIndex++;
  }
  RegCloseKey(hSubKey);
  return 0;
}

Each GUID represents a unique wireless adapter. To obtain a textual representation of this, we need to look at

reg query HKLM\SYSTEM\Select

HKEY_LOCAL_MACHINE\SYSTEM\Select
    Current          REG_DWORD    0x2
    Default          REG_DWORD    0x2
    Failed           REG_DWORD    0x1
    LastKnownGood    REG_DWORD    0x3

If we take the first GUID from the WLAN interfaces and query it.

C:\>reg query "HKLM\SYSTEM\ControlSet002\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\{012C8D7D-D104-47F8-83FE-B9751F699A2F}\Connection

HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\{012C8D7D-D104-47F8-83FE-B9751F699A2F}\Connection
    DefaultNameResourceId    REG_DWORD    0x70e
    DefaultNameIndex         REG_DWORD    0x6
    Name                     REG_SZ       Wireless Network Connection 6
    PnpInstanceID            REG_SZ       {5D624F94-8850-40C3-A3FA-A4FD2080BAF3}\VWIFIMP\7&79A56D7&0&03
    MediaSubType             REG_DWORD    0x2

The PnpInstanceID

C:\>reg query "HKLM\SYSTEM\ControlSet002\Enum\{5D624F94-8850-40C3-A3FA-A4FD2080BAF3}\VWIFIMP\7&79A56D7&0&03"

HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Enum\{5D624F94-8850-40C3-A3FA-A4FD2080BAF3}\VWIFIMP\7&79A56D7&0&03
    DeviceDesc             REG_SZ       @netvwifimp.inf,%vwifimp.devicedesc%;Microsoft Virtual WiFi Miniport Adapter
    LocationInformation    REG_SZ       VWiFi Bus 0
    Capabilities           REG_DWORD    0xa0
    UINumber               REG_DWORD    0x3
    HardwareID             REG_MULTI_SZ {5d624f94-8850-40c3-a3fa-a4fd2080baf3}\vwifimp
    CompatibleIDs          REG_MULTI_SZ {5d624f94-8850-40c3-a3fa-a4fd2080baf3}\vwifimp
    ContainerID            REG_SZ       {df3771ea-c60e-5fae-9656-83aa71808f29}
    ConfigFlags            REG_DWORD    0x0
    ClassGUID              REG_SZ       {4d36e972-e325-11ce-bfc1-08002be10318}
    Driver                 REG_SZ       {4d36e972-e325-11ce-bfc1-08002be10318}\0023
    FriendlyName           REG_SZ       Microsoft Virtual WiFi Miniport Adapter #3
    Class                  REG_SZ       Net
    Mfg                    REG_SZ       @netvwifimp.inf,%msft%;Microsoft
    Service                REG_SZ       vwifimp

HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Enum\{5D624F94-8850-40C3-A3FA-A4FD2080BAF3}\VWIFIMP\7&79A56D7&0&03\Device Parameters
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Enum\{5D624F94-8850-40C3-A3FA-A4FD2080BAF3}\VWIFIMP\7&79A56D7&0&03\LogConf
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Enum\{5D624F94-8850-40C3-A3FA-A4FD2080BAF3}\VWIFIMP\7&79A56D7&0&03\Properties

Programatically in C

std::wstring GetAdapterDescription(std::wstring guid) {
  static       DWORD dwCtrlIdx = 0;
  LSTATUS      lStatus;
  DWORD        cbSize;
  std::wstring description = L"<unavailable>";
  wchar_t      path[1024], pnpInstance[1024], deviceDesc[1024];
  PWCHAR       pDesc;
  
  if (dwCtrlIdx == 0) {
    cbSize = sizeof(DWORD);
    lStatus = SHGetValue(HKEY_LOCAL_MACHINE, L"SYSTEM\\Select", 
        L"Default", 0, &dwCtrlIdx, &cbSize);
    if (lStatus != ERROR_SUCCESS) {
      dwCtrlIdx = 1;
    }
  }
  
  _snwprintf(path, sizeof(path) / sizeof(wchar_t), 
      L"SYSTEM\\ControlSet%03i\\Control\\Network\\"
      L"{4D36E972-E325-11CE-BFC1-08002BE10318}\\%s\\Connection", 
      dwCtrlIdx, guid.c_str());

  cbSize = sizeof(pnpInstance) / sizeof(wchar_t);
  lStatus = SHGetValue(HKEY_LOCAL_MACHINE, path, L"PnpInstanceID", 
      0, pnpInstance, &cbSize);
  if (lStatus == ERROR_SUCCESS) {
    _snwprintf(path, 1024, L"SYSTEM\\ControlSet%03i\\Enum\\%s", 
        dwCtrlIdx, pnpInstance);
  
    cbSize = sizeof(deviceDesc) / sizeof(wchar_t);
    lStatus = SHGetValue(HKEY_LOCAL_MACHINE, path, L"DeviceDesc", 
        0, &deviceDesc, &cbSize);
    pDesc = wcsrchr(deviceDesc, L';');
    if (pDesc != 0) {
      description = ++pDesc;
    }
  }
  return description;
}

So there we have the description of adapter, and how to get profiles?

reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{012C8D7D-D104-47F8-83FE-B9751F699A2F}"

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{012C8D7D-D104-47F8-83FE-B9751F699A2F}
    PortType              REG_DWORD    0x2
    ProfileList           REG_MULTI_SZ    {AC1D44A3-9444-43E8-8496-E998433A5188}
    Parameters            REG_BINARY    0000000001000000010000000000000003000000
    Scan Interval         REG_BINARY    60EA0000
    Fail To Reset Time    REG_BINARY    60EA0000

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wlansvc\Interfaces\{012C8D7D-D104-47F8-83FE-B9751F699A2F}\Profiles

This profile list contains a string which corresponds to a file stored on disk with the profile.

dir C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces
    Volume in drive C has no label.
    Volume Serial Number is CFED-1E13

    Directory of C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces

    07/01/2017 02:25 PM .
    07/01/2017 02:25 PM ..
    09/30/2015 11:54 PM {012C8D7D-D104-47F8-83FE-B9751F699A2F}
    02/08/2015 10:31 AM {12487653-A815-4A9D-84E1-736D96554215}
    01/26/2016 12:13 AM {13ECC539-1C25-4036-9615-D85A5D1B47CE}
    04/15/2016 07:34 PM {295A80EE-8DC0-465F-ACF2-70DA1F036948}
    09/10/2015 07:50 PM {3306C2A4-E4A5-42B3-A087-67EB542F0EC0}
    04/15/2016 07:21 PM {40F69FB8-F23A-433E-94D9-D3983F220874}
    07/23/2013 06:54 PM {6E318579-A15B-43AA-BC85-5A38133888A6}
    07/01/2017 02:25 PM {AFEED710-CC5E-41BD-8975-E6EE0487B5C9}

Programming in C

void DumpWLANProfile(
  wchar_t adapterGuid[], 
  wchar_t profileGuid[]) 
{
  wchar_t                   path[MAX_PATH];
  wchar_t                   programData[MAX_PATH];
  HRESULT                   hr;
  CComPtr<IXMLDOMDocument2> pDoc;
  VARIANT_BOOL              bIsSuccessful;
  
  SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, 
      NULL, SHGFP_TYPE_CURRENT, programData);
      
  _snwprintf(path, MAX_PATH, 
    L"%s\\Microsoft\\Wlansvc\\Profiles\\Interfaces\\%s\\%s.xml", 
    programData, adapterGuid, profileGuid);

  hr = CoInitialize(NULL);
  if (FAILED(hr)) {
    wprintf(L"\nCoInitialize() failed : %08x", hr);
    return;
  }  
  
  hr = CoCreateInstance(CLSID_DOMDocument30, 
      NULL, CLSCTX_INPROC_SERVER,
      IID_IXMLDOMDocument2, (void**)&pDoc);
      
  if (SUCCEEDED(hr)) {    
    hr = pDoc->load(CComVariant(path), &bIsSuccessful);
    
    if (SUCCEEDED(hr) && bIsSuccessful)
    {
      profile_properties(pDoc, 0);
      profile_properties(pDoc, 1);
    } else {
      wprintf(L"\n  IXMLDOMDocument2->load() failed : %08x", hr);
    }
    pDoc = NULL;
  } else {
    wprintf(L"\n  CoCreateInstance() failed : %08x", hr);
  }
  CoUninitialize();
}

There are 2 different name space

// required to parse WLAN profiles
#define WLAN_NS   L"xmlns:s=\"http://www.microsoft.com/networking/WLAN/profile/v1\""
#define WLANAP_NS L"xmlns:s=\"http://www.microsoft.com/networking/WLANAP/profile/v1\""

void profile_properties(CComPtr<IXMLDOMDocument2> pDoc, DWORD idx)
{
  PWCHAR       xml[2]={WLAN_NS, WLANAP_NS};
  PWCHAR       profiles[2]={L"WLANProfile", L"WLANAPProfile"};
  HRESULT      hr;
  CComVariant  ns;
  PWCHAR       pt;
  std::wstring ssid, auth, enc, key;
  
  ns = xml[idx];
  pt = profiles[idx];
  hr = pDoc->setProperty(BSTR(L"SelectionNamespaces"), ns);

  if (SUCCEEDED(hr)) {    
    ssid = get_text(pDoc, pt, L"/s:SSIDConfig/s:SSID/s:name");
    auth = get_text(pDoc, pt, L"/s:MSM/s:security/s:authEncryption/s:authentication");
    enc  = get_text(pDoc, pt, L"/s:MSM/s:security/s:authEncryption/s:encryption");
    key  = get_text(pDoc, pt, L"/s:MSM/s:security/s:sharedKey/s:keyMaterial");
    
    if (!ssid.empty()) {
      wprintf(L"\n  %-20s  %-10s  %-20s", ssid.c_str(), auth.c_str(), enc.c_str());
    
      if (!key.empty()) {
        DecryptKey(key);
      }
    }
  } else {
    wprintf(L"\n  IXMLDOMDocument2->setProperty() failed : %08x", hr);
  }
  ns = NULL;
}

Demonstration

Source code

Requires MSVC with support for ATL to compile. See here.

Posted in crypto api, networking, programming, security, windows, wireless | Leave a comment

RSA Key Exchange with Windows Crypto API and OpenSSL Part 1

Introduction

Microsoft Crypto API (CAPI) was first released with the Windows NT4 operating system in 1996. The OpenSSL project, which was originally a fork of SSLeay by Eric Young and Tim Hudson, was initiated in 1998 and has since become one of the most widely distributed cryptographic libraries available.

I recently required a Windows application using CAPI that can sign and verify files using the RSA digital signature algorithm, but It needed to read RSA keys and signatures generated by OpenSSL. The keys are stored using Abstract Syntax Notation One (ASN.1) and Privacy Enhanced Mail (PEM) format. The signatures are stored in binary using big-endian convention.

In this post, we’ll focus specifically on RSA key generation, the importation/exportation of RSA keys and the key management standards used to exchange keys in a platform independent manner.

I understand RSA is being phased out in favor of Elliptic Curve Cryptography (ECC) which may be discussed in a future post. I’m also aware ECC will eventually be phased out in favor of quantum resistant cryptography which is still under a lot of research and development, but that could be 10+ years away and RSA still offers good security margin, albeit with less efficiency.

Part 2 will focus specifically on generation and exchange of session keys over TCP for symmetric encryption, but the bulk of work needed to reach that stage is really within this post.

Key Management Standards

The 2 main issues developers appear to complain about for interoperability between OpenSSL and Microsoft Crypto API are:

  1. Signatures exported by OpenSSL functions use big-endian convention, Microsoft Crypto API uses little-endian
  2. Public and Private Keys exported by OpenSSL functions use ASN.1 structures, Microsoft CryptoAPI use their own structures or what are referred to as “Blobs”

However, both API support the following standards for public key management:

We just need to import and export keys using these standard formats to successfully exchange keys between the 2 API.

Then there’s the issue of textual encoding using Privacy Enhanced Mail (PEM) format which is defined in RFC1421, RFC1422 and RFC1423.

Essentially, this encoding uses the base64 algorithm.

RSA Digital Signatures and RSA Key Exchange

When signing a file, we derive a cryptographic hash from its data. This hash is then encrypted using an RSA private key and modular exponentiation. The resulting ciphertext is called a signature. Verification of the signature involves decryption using an RSA public key and Modular Exponentiation.

When exchanging session keys, the client side will generate a value derived from a cryptographic pseudo-random number generator (CSPRNG). This value will be used as the symmetric encryption key. It’s then encrypted using an RSA public key and modular exponentiatation before being sent to a remote server. The server will perform RSA decryption using the private key to recover the same session key.

Byte Order

As stated already, OpenSSL exports signatures using the Big-Endian convention whereas Microsoft Crypto API uses Little-Endian.

High end servers and mainframes in the 80s and 90s used Big-Endian architectures like SPARC, MIPS and POWER. The legacy of this are many cryptography libraries using Big-Endian convention to store data on disk.

To accomodate this on Windows which predominantly runs on X86 architecture, we use the following piece of code to swap the order of bytes after signing and before verification.

Then we have no problem verifying signatures generated by OpenSSL.

RSA key context

The following structure is defined to hold RSA keys.

RSA Key Generation

CAPI uses 65537 as the public exponent in key generation so we need to use the same for OpenSSL.

Reading and writing PEM files

Before using the CAPI functions, we need to decode the PEM files into ASN.1 encoded structures. The CryptStringToBinary and CryptBinaryToString APIs can convert to and from PEM, however these were only made available since Windows XP and Windows 2003.

See PEM_read_file and PEM_write_file functions for more details.

Importing public and private keys

  • Crypto API

For the Public key, decode the ASN.1 structure into a Public Key Info structure before importing to CAPI key object using CryptImportPublicKeyInfo API.

Private Key, decode the ASN.1 structure into a Private Key Info structure. Convert the PrivateKey value into a CAPI Private Key Blob before importing into a CAPI key object.

Unfortunately, there’s no CryptImportPrivateKeyInfo API, hence the extra call to CryptDecodeObjectEx.

  • OpenSSL

OpenSSL offers a much simpler solution with a single API call for both private and public keys. We also don’t have to decode the PEM format before hand. Nice, eh?

Exporting keys

  • Crypto API

Since 2000, we can use CryptExportPKCS8 to export the private key.

Exporting the public key using CryptExportPublicKeyInfo before encoding with ASN.1.

  • OpenSSL

Signing a file

  • Crypto API

  • OpenSSL

Verifying signature

  • Crypto API

  • OpenSSL

RSA Tool Usage

  • Key Generation

 

  • Signing a file

  • Verifying signature

Summary

The purpose of this post was to cover the main problems of key exchange between OpenSSL and Microsoft Crypto API.

For symmetric key exchange, so long as we use ASN.1 encoding for the exchange of public and private keys and remember that OpenSSL uses Big-Endian convention instead of Little-Endian by CAPI, there isn’t a significant problem.

In part 2, we’ll examine how to perform End-To-End encryption of network traffic between a windows machine using Crypto API and Linux using OpenSSL.

Source code for the RSA tool can be found here

Posted in crypto api, cryptography, openssl, programming, security, windows | Tagged , , , , , , | Leave a comment

Impersonating the LocalSystem account

Introduction

If you just want to execute a command under the LocalSystem account, you can use psexec with -s parameter, but the motivation for writing this code was for decryption of Wireless passwords which are encrypted using CryptProtectData API under the LocalSystem account.

The following are some ways to execute code under context of LocalSystem user.

  1. Create a new service using the Service Control Manager (SCM) API.
  2. Inject code into a LocalSystem process using CreateRemoteThread API.
  3. Create new process with LocalSystem privileges using CreateProcessAsUser API.
  4. Impersonate LocalSystem using existing LocalSystem process token

The 4th option is what we’ll discuss here since it’s much easier to use than the 1st one and certainly safer and more reliable than the 2nd. The 3rd is fine if you want to create a new process as LocalSystem but if you only need to perform the task temporarily, impersonation is a better option.

Elevated Privilege

Before doing anything else, we should ensure our process is elevated.
If this fails, the rest of our code won’t work.

/**
 *
 *  Determines if process token is elevated
 *  Returns TRUE or FALSE
 *
 */
BOOL isElevated(VOID) {
  HANDLE          hToken;
  BOOL            bResult = FALSE;
  TOKEN_ELEVATION te;
  DWORD           dwSize;
    
  if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
    if (GetTokenInformation(hToken, TokenElevation, &te, 
        sizeof(TOKEN_ELEVATION), &dwSize)) {
      bResult = te.TokenIsElevated != 0;
    }
    CloseHandle(hToken);
  }
  return bResult;
}

Debug Privilege

To obtain a handle for a LocalSystem process, we need to enable the debug privilege of our process token which is disabled by default. Again, this requires Administrator privileges.

/**
 *
 *  Enables or disables a named privilege in token
 *  Returns TRUE or FALSE
 *
 */
BOOL SetPrivilege(wchar_t szPrivilege[], BOOL bEnable) {
  HANDLE           hToken;
  BOOL             bResult;
  LUID             luid;
  TOKEN_PRIVILEGES tp;
  
  bResult = OpenProcessToken(GetCurrentProcess(), 
    TOKEN_ADJUST_PRIVILEGES, &hToken);
  
  if (bResult) {    
    bResult = LookupPrivilegeValue(NULL, szPrivilege, &luid);
    if (bResult) {
      tp.PrivilegeCount           = 1;
      tp.Privileges[0].Luid       = luid;
      tp.Privileges[0].Attributes = (bEnable) ? SE_PRIVILEGE_ENABLED : 0;

      bResult = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL);
    }
    CloseHandle(hToken);
  }
  return bResult;
}

Obtaining process id of process name

If we’re an elevated process and can enable SeDebugPrivilege in our process token, we then obtain the process id of a known LocalSystem process. The Local Security Account Subsystem (LSASS) exists on all windows since the release of NT and is perfect.

/**
 *
 *  Obtain process id of process name
 *
 *  Returns process id or zero
 *
 */
DWORD GetProcessId(wchar_t szName[]) {
  DWORD          dwId = 0;
  HANDLE         hSnap;
  BOOL           bResult;
  PROCESSENTRY32 pe32;
  
  hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  
  if (hSnap != INVALID_HANDLE_VALUE) {
    pe32.dwSize = sizeof(PROCESSENTRY32);
    
    bResult = Process32First(hSnap, &pe32);
    while (bResult) {
      if (lstrcmpi(pe32.szExeFile, szName) == 0) {
        dwId = pe32.th32ProcessID;
        break;
      }
      bResult = Process32Next(hSnap, &pe32);
    }
    CloseHandle(hSnap);
  }
  return dwId;
}

Impersonation of token

All we need now is to open the process, open the process token for impersonation and call ImpersonateLoggedOnUser API.

BOOL ImpersonateSystem(VOID) {
  BOOL   bImpersonating = FALSE;
  HANDLE hToken, hProcess;
  // get id of a LocalSystem process
  DWORD  dwId = GetProcessId(L"lsass.exe");
  
  if (dwId != 0) {
    // enable debug privilege
    if (SetPrivilege(SE_DEBUG_NAME, TRUE)) {
      // attempt to open process
      hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwId);
      if (hProcess != NULL) {
        // attempt to open process token
        if (OpenProcessToken(hProcess, 
            TOKEN_IMPERSONATE | TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
          // attempt to impersonate LocalSystem
          bImpersonating = ImpersonateLoggedOnUser(hToken);
          if (!bImpersonating) {
            Error(L"ImpersonateLoggedOnUser failed : ", GetLastError());
          }
          CloseHandle(hToken);
        } else {
          Error(L"OpenProcessToken failed : ", GetLastError());
        }
        CloseHandle(hProcess);
      } else {
        Error(L"OpenProcess(\"lsass.exe\") failed : ", GetLastError());
      }
    } else {
      Error(L"SetPrivilege(SE_DEBUG_NAME, TRUE) failed : ", GetLastError());
    }
  } else {
    Error(L"GetProcessId(\"lsass.exe\") failed : ", GetLastError());
  }
  return bImpersonating;
}

Once we’ve executed CryptUnprotectData API under the context of LocalSystem privileges we can call RevertToSelf API or just exit the thread/application.

Posted in programming, security, windows | Tagged , , , | Leave a comment

Software Inventory on Windows using C++

Introduction

For local and remote queries, you can use the Windows Management Instrumentation (WMI) class Win32_Product but this will only provide you with a list of applications that were installed using Microsoft Installer (MSI) packages.

Applications deployed with their own installer force you to parse the registry manually and weed out the entries you’re not interested in. The control panel applet appwiz.cpl works by parsing the registry but unfortunately does not work for a remote system.

Although I don’t provide a tool to list applications on a remote system, the information here should be enough for you to implement by yourself using the RegConnectRegistry API or somthing similar using StdReg in VBScript or powershell.

Registry keys

Applications installed which are only accessible to the user get stored in NTUSER.DAT located inside the user’s profile which is a registry hive and gets loaded by the system when user logs on. You can find it under HKEY_CURRENT_USER or in HKEY_USERS under the user’s Security Identifier (SID) if you open up regedit.exe

System wide applications available to all users get stored in the SOFTWARE registry hive which you’ll find under HKEY_LOCAL_MACHINE

Since the introduction of 64-bit Windows, there are 2 locations required to store information about applications installed on the system.

The native key is

SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

For 32-bit or legacy mode applications installed on 64-bit long mode Windows, the entries are stored under

SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

Bear in mind, the code here will only list system wide applications and not those which only appear in the user’s registry hive NTUSER.DAT

Avoiding hot patches

This might not work in all scenarios but what I’d do for older XP systems is query the key name for application entry and if it contained “KB” which is abbreviation for Knowledge Base, I would assume it to be a hot patch and ignore it.

The other check for “KB” is in the DisplayName property.

Even though it’s a potentially unreliable method to use, it worked well enough for me in most situations.

Avoiding System Components

The SystemComponent property is a DWORD(32-bit) which is either 0 or 1 to indicate a hot patch or update you may not be interested in. Our code avoids listing these entries in the same way appwiz.cpl does.

/**
 *
 *  Enumerate all 32 and 64-bit products installed
 *
 */
void Products::enumEntries(HKEY hRegistry)
{
  HKEY  hApplications;
  DWORD dwEntries[2];
  
  getTotalEntries(dwEntries);

  // enumerate 32 and 64-bit applications
  for (DWORD i=0; i<2; i++) 
  {
    // open key for enumerate and query access
    dwError = RegOpenKeyEx (hRegistry, lpszAppKeys[i], 0,
        KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_WOW64_64KEY,
        &hApplications);
        
    if (dwError == ERROR_SUCCESS)
    {                
      // loop through each entry for this key
      for (DWORD dwKeyIndex=0; i<dwEntries[i]; dwKeyIndex++) 
      {
        // get key name
        DWORD   cbName = MAX_KEY_LEN;
        wchar_t wszKeyName[MAX_KEY_LEN];

        dwError = RegEnumKeyEx (hApplications, dwKeyIndex, wszKeyName, 
            &cbName, NULL, NULL, NULL, NULL);
        
        // nothing left?
        if (dwError == ERROR_NO_MORE_ITEMS)
          break;

        HKEY hEntry;
        std::wstring keyName = wszKeyName;

        // if not a name with "KB" in it
        // not incredibly reliable but will do for now
        if (keyName.find(L"KB") == std::wstring::npos) 
        {
          // open to query values
          dwError = RegOpenKeyEx (hApplications, wszKeyName, 
              0, KEY_QUERY_VALUE,&hEntry);
              
          if (dwError == ERROR_SUCCESS) 
          {            
            // exclude SystemComponents
            DWORD dwValue = 0;
            DWORD dwSize = sizeof(DWORD);
            
            RegQueryValueEx (hEntry, L"SystemComponent", NULL, 
                NULL,(LPBYTE)&dwValue,&dwSize);
            
            // if not a system component
            if (dwValue != 1) 
            {              
              ProductEntry entry;
            
              // clear string values incase set from previous entry
              entry.name.clear();
              entry.publisher.clear();
              entry.version.clear();

              // query display name, if we don't get this value, 
              // we don't save anything and continue to next entry
              wchar_t wszDisplayName[MAX_KEY_LEN];
              dwSize = MAX_KEY_LEN;

              dwError = RegQueryValueEx (hEntry, L"DisplayName", NULL,
                  0, (LPBYTE)&wszDisplayName, &dwSize);
                  
              if (dwError == ERROR_SUCCESS) 
              {              
                // if display name doesn't have "KB" in it
                // could be a problem since other applications could have KB in it
                entry.name = wszDisplayName;
                
                if (entry.name.find(L"KB") == std::wstring::npos) {

                  // query the version
                  wchar_t wszVersion[MAX_KEY_LEN];
                  dwSize = MAX_KEY_LEN;
                
                  // just continue if we don't get it
                  dwError = RegQueryValueEx (hEntry, L"DisplayVersion", NULL, 0,
                      (LPBYTE)&wszVersion, &dwSize);
                  
                  if (dwError == ERROR_SUCCESS)    
                    entry.version = wszVersion;

                  // if not listed get the publisher and save
                  if (!isListed(entry)) {

                    // query the publisher value
                    wchar_t wszPublisher[MAX_KEY_LEN];
                    dwSize = MAX_KEY_LEN;

                    if (RegQueryValueEx (hEntry, L"Publisher", NULL, 0,
                        (LPBYTE)&wszPublisher, &dwSize) == ERROR_SUCCESS)
                      entry.publisher = wszPublisher;

                    // find the max length of strings so far
                    nName      = max(entry.name.length(),     nName);
                    nPublisher = max(entry.publisher.length(),nPublisher);
                    nVersion   = max(entry.version.length(),  nVersion);
                  
                    // add to list
                    entries.push_back(entry);
                  }
                } // we found "KB" in display value..don't want these.
              } // end query of display
            } // SystemComponent
            RegCloseKey(hEntry);
          } // end if of open entry
        } // we found "KB" in key name, don't want these
      } // end for          
      RegCloseKey(hApplications);
    } // end open
  } // end for
  
  sortEntries();
}

appwiz.cpl listed 213 applications whereas the above code listed 217. Didn’t thoroughly check where the differences were but it’s good enough for me 🙂

See products.cpp here.

Posted in programming, windows | Tagged , | Leave a comment

Listing processes on Windows in C

Introduction

I was writing something recently which required obtaining a list of running processes on Windows. The problem was that it had to run on systems as early as Windows NT right up to Windows 10 and there’s no single API you can use for this.

I also had to pick a compiler that would support Windows NT since Microsoft generally avoid supporting legacy operating systems with their compilers after a certain point.

API options

So what API are available to use?

  • Performance Data
  • Available since Windows NT but undocumented. Mark Russinovich’s pslist uses this so it can also work remotely provided the Remote Registry Service is enabled.

  • Win32_Process
  • Seems to be available since Windows NT. Tried checking with VBScript but cscript.exe wasn’t installed. This would be ideal if you needed to list processes on a remote system but is also much harder to use when programming with C++ instead of a scripting language such as VBScript or Powershell.

  • Process32First
  • You can certainly use this for 32 and 64-bit now since Windows XP but it’s not available on Windows NT.

  • EnumProcesses
  • Apparently the PSAPI.DLL file where this API resides can be installed from CD-ROM but it’s not installed by default which means we can’t depend on it.

  • NtQuerySystemInformation
  • Available since Windows NT so we can use it for that but doesn’t work for 64-bit systems.

    The solution is to use NtQuerySystemInformation if executing on Wow64 or legacy systems and to use Process32First if we’re building for 64-bit systems.

    Process Entry Structure

    Rather than work with 2 different structures, I only copy the module name and process id to a new structure which is then parsed by the callee. The module names are in unicode format.

    typedef struct _PROCENTRY_T {
      DWORD id;
      WCHAR name[MAX_PATH];
    } PROCENTRY, *PPROCENTRY;
    

    Legacy Mode

    So here’s a little function in C called GetProcessList which uses those 2 API to retrieve a list of running processes.

    PPROCENTRY GetProcessList(VOID)
    {
      pNtQuerySystemInformation   NtQuerySystemInformation;
      pRtlCompareUnicodeString    RtlCompareUnicodeString;
      ULONG                       len=0, total=0, pe_size=0;
      NTSTATUS                    status;
      LPVOID                      list=NULL;
      PSYSTEM_PROCESS_INFORMATION p;
      PPROCENTRY                  pe;
      DWORD                       i;
      
      NtQuerySystemInformation = 
          (pNtQuerySystemInformation)GetProcAddress(
          GetModuleHandle("ntdll"), "NtQuerySystemInformation");
          
      if (!NtQuerySystemInformation) {
        // we couldn't resolve API address
        return NULL;
      }
      
      list = xmalloc(2048);
      
      do {
        len += 2048;
        list = xrealloc (list, len);
        
        if (list==NULL) {
          // we couldn't reallocate memory
          break;
        }
        status = NtQuerySystemInformation(SystemProcessInformation, list, len, &total);
      } while (status == STATUS_INFO_LEN_MISMATCH);
      
      if (status < 0) {
        // we were unable to obtain list of process
        xfree(list);
        return NULL;
      }
      
      p       = (PSYSTEM_PROCESS_INFORMATION)list;
      pe_size = sizeof(PROCENTRY);
      pe      = xmalloc(pe_size);
      
      for (i=0;;) 
      {
        if (p->ProcessName.Buffer != 0)
        {
          // copy process id and module name
          pe[i].id = p->ProcessId; 
          lstrcpy(pe[i].name, p->ProcessName.Buffer);
          
          pe_size += sizeof(PROCENTRY);
          pe = xrealloc(pe, pe_size);
          i++;
          if (pe==NULL) {
            break;
          }        
        }
        // no more entries? break
        if (p->NextEntryDelta==0) break;
        
        // advance to next entry
        p = (PSYSTEM_PROCESS_INFORMATION)(((char *)p) + p->NextEntryDelta);
      }
      
      xfree(list);
      return pe;
    }
    

    Long Mode

    PPROCENTRY GetProcessList(VOID)
    {
      HANDLE         hSnap;
      PROCESSENTRY32 pe32;
      PPROCENTRY     pe=NULL;
      DWORD          i=0, pe_size;
      
      hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
      if (hSnap != INVALID_HANDLE_VALUE)
      {
        pe32.dwSize = sizeof(PROCESSENTRY32);
    
        if (Process32First(hSnap, &pe32))
        {
          i       = 0;
          pe_size = sizeof(PROCENTRY);
          pe      = xmalloc(pe_size);
      
          do {
            if (pe32.th32ProcessID==0) continue;
            
            pe[i].id = pe32.th32ProcessID; 
            lstrcpy(pe[i].name, pe32.szExeFile);
            
            pe_size += sizeof(PROCENTRY);
            pe = xrealloc(pe, pe_size);
            i++;
            if (pe==NULL) {
              break;
            }            
          } while (Process32Next(hSnap, &pe32));
        }
        CloseHandle(hSnap);
      }
      return pe;
    }
    

    Demonstration

    The following is small example of using the above functions.

    int main(void)
    {
      PPROCENTRY pe;  
      PPROCENTRY list = GetProcessList();
      
      if (list==NULL) {
        printf ("\nUnable to retrieve list of process");
        return 0;
      }
      printf ("\nList of processes");
      printf ("\n=================");
      for (pe=list; pe->id; pe++) {
        printf ("\n%-30ws - %i", pe->name, pe->id);
      }
      xfree(list);  
      return 0;
    }
    

    See pslist.c here

    To compile for just listing processes, use CL /DTEST pslist.c

    Posted in programming, windows | Tagged , , , , | Leave a comment

    Windows ICMP API in C/C++

    Introduction

    In the old days, pinging a computer on windows required building an ICMP packet from scratch and using RAW sockets to send the packet to its destination. Worse was that you then had to listen for a response and parse this manually.

    Thankfully, Microsoft since Windows 2000 made available an ICMP helper library so you don’t have to perform any hard work anymore. Well, admittedly, it’s much easier to use .NET than Win32 API but if you’re curious about how to do it with C/C++, read on.

    Ping class and reply structure

    Because we can sometimes resolve multiple addresses for one hostname, I’ve defined a structure to hold information about each response.

    It contains basic information like the IP address as a string, the hostname (if available) and an status message.

    typedef struct _ICMP_REPLY {
      DWORD dwCode;
      wchar_t Message[256];
      wchar_t Dns[255];
      wchar_t Ip[255];
      struct _ICMP_REPLY *next;
    } ICMP_REPLY, *PICMP_REPLY;
    

    The class with properties and methods.

    class Ping {
      private:
        BOOL FlushDnsCache ();
        PICMP_REPLY rlist, current;
        void Add (PICMP_REPLY);
        void Clear (void);
        int SendEcho(ICMP_REPLY*, PADDRINFOW, int);
        HANDLE IcmpCreate(int);
      public:
        Ping() { rlist=NULL; }
        ~Ping();
        
        BOOL Send (wchar_t address[], int);
        BOOL Send (wchar_t address[], int, DWORD timeout);
        
        PICMP_REPLY GetReplies (void);
    };
    

    Flushing the DNS cache

    Let’s say a machine has rebooted and you’re waiting for it to come back up and it just won’t respond to a ping anymore. Chances are your cache is out of date. While this may not be a problem, it’s still always good practice to flush the DNS cache before trying again.

    So from the command line, you can use

    ipconfig /flushdns

    But if you don’t want to execute this command through an API, you can call an undocumented API DnsFlushResolverCache.

    /**
     *
     * Uses undocumented API from DNSAPI.DLL
     *
     * Same as : ipconfig /flushdns
     *
     */
    BOOL Ping::FlushDnsCache (VOID) {
      BOOL bResult = FALSE;
    
      BOOL (WINAPI *Flush) ();
      HMODULE hDNS = LoadLibrary (L"dnsapi");
    
      if (hDNS != NULL) {
        *(FARPROC *)&Flush = GetProcAddress (hDNS, "DnsFlushResolverCache");
        
        if (Flush != NULL) {
          bResult = Flush ();
        }
        FreeLibrary (hDNS);
      }
      return bResult;
    }
    

    Create an ICMP file

    First thing to do is create an ICMP file handle. The following function will return handle for either IPV4 or IPV6 depending on value of family parameter which should be AF_INET or AF_INET6.

    // create an ICMP handle for ipv4 or ipv6
    HANDLE Ping::IcmpCreate(int family) 
    {
      if (family==AF_INET) {
        return IcmpCreateFile();
      } else {
        return Icmp6CreateFile();
      }
    }
    

    Resolving network address for hostname

    Steps to resolve all network addresses for a hostname

    1. Resolve all addresses using GetAddrInfo
    2. Resolve name for each address using GetNameInfo
    3. Convert network address to string using WSAAddressToString
    BOOL Ping::Send (wchar_t address[], int family, DWORD timeout)
    {
      ADDRINFOW  hints;
      PADDRINFOW e, list = NULL;
      ICMP_REPLY r;
      wchar_t    host[NI_MAXHOST], serv[NI_MAXSERV], ip[INET6_ADDRSTRLEN];
      DWORD      res, size, idx;
        
      // clear any previous entries
      Clear();
      
      if (FlushDnsCache ()) 
      {
        ZeroMemory (&hints, sizeof (hints));
    
        hints.ai_family   = family;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        
        // resolve all available addresses
        if (GetAddrInfo (address, NULL, &hints, &list) == NO_ERROR) 
        {    
          // loop through each entry
          for (e=list; e!=NULL; e=e->ai_next) 
          {  
            // resolve name if available
            res = GetNameInfo (e->ai_addr, sizeof (SOCKADDR), host,
                NI_MAXHOST, serv, NI_MAXSERV, NI_NUMERICSERV);
            
            // copy name to structure
            StrCpy (r.Dns, (res == NO_ERROR) ? host : L"unresolved");
            
            // convert ip address to string
            size = sizeof (ip);
            
            res = WSAAddressToString (e->ai_addr, 
                (DWORD)e->ai_addrlen, NULL, ip, &size);
            
            StrCpy (r.Ip, 
              (res == NO_ERROR) ? ip : family==AF_INET ? L"0.0.0.0" : L"::");
              
            if (SendEcho(&r, e, timeout)) {
              Add (&r);        
            }
          }
          FreeAddrInfo (list);
        } else {
          printf ("\n%i", GetLastError());
        }
      }
      return rlist != NULL;
    }
    

    Sending echo

    • Create ICMP file handle
    • If IPV4, use IcmpSendEcho, if IPV6, use Icmp6SendEcho2
    int Ping::SendEcho(ICMP_REPLY *r, PADDRINFOW addr, int timeout)
    {
      wchar_t             req_data[4];
      PICMP_ECHO_REPLY    pReply4;
      PICMPV6_ECHO_REPLY  pReply6;
      LPVOID              reply[sizeof(ICMPV6_ECHO_REPLY) + sizeof(req_data) * 2];
      HANDLE              hIcmpFile;
      struct sockaddr_in  v4;
      struct sockaddr_in6 v6, sa;  
      DWORD               reply_size, idx;
      
      reply_size = sizeof(reply);
      
      // create icmp file handle
      hIcmpFile = IcmpCreate(addr->ai_family);
      
      if (hIcmpFile==NULL) return 0;
      
      if (addr->ai_family==AF_INET)
      {
        // send ipv4
        memcpy (&v4, addr->ai_addr, addr->ai_addrlen);
        
        IcmpSendEcho (hIcmpFile,
          (IPAddr)v4.sin_addr.S_un.S_addr, req_data, 
          sizeof(req_data), NULL, 
          reply, reply_size, timeout);
          
          pReply4 = (PICMP_ECHO_REPLY)reply;
          r->dwCode = pReply4->Status;
      } else {
        // send ipv6
        memcpy(&v6, addr->ai_addr, addr->ai_addrlen);
        
        sa.sin6_addr     = in6addr_any;
        sa.sin6_family   = AF_INET6;
        sa.sin6_flowinfo = 0;
        sa.sin6_port     = 0;
        
        Icmp6SendEcho2 (hIcmpFile, NULL, NULL, NULL, 
          &sa, &v6, req_data, sizeof(req_data),
          NULL, reply, reply_size, timeout);
              
        pReply6 = (PICMPV6_ECHO_REPLY)reply; 
        r->dwCode = pReply6->Status;    
      }
     
      StrCpy (r->Message, L"Check status code");
    
      for (idx=0; idx<sizeof(pStatus)/sizeof(STATUS_MSG); idx++) 
      {
        if (r->dwCode == pStatus[idx].dwCode) 
        {
          StrCpy (r->Message, pStatus[idx].pMessage);
        }
      }
      IcmpCloseHandle (hIcmpFile);
      return 1;  
    }
    

    Demo

    Simple demonstration using both ipv4 and ipv6

    See sources here

    Posted in networking, programming, windows | Tagged , , , | Leave a comment