I am aware that NT header has all constants defined like SE_TAKE_OWNERSHIP_NAME, and so there are functions available to convert these into human readable form (Take ownership of files or other objects).
My question is how to enumerate these names? With different versions of Windows, not all SE-names would be applicable (i.e. privileges may not be available on particular NT system).
Whilst it is true that Windows7/2008 is the latest and appropriate header for the same would list all of them - and if the application runs on a lower platform, the function taking SE-names would simply fail for given name if given OS doesn't support (like LsaEnumerateAccountsWithUserRight would fail).
But how to make application future compatible that can facilitate listing all privileges for future versions of Windows OS?
Use LsaEnumeratePrivileges (defined in ntlsa.h, which is in the WDK - inc/api):
NTSTATUS
NTAPI
LsaEnumeratePrivileges(
__in LSA_HANDLE PolicyHandle,
__inout PLSA_ENUMERATION_HANDLE EnumerationContext,
__out PVOID *Buffer,
__in ULONG PreferedMaximumLength,
__out PULONG CountReturned
);
The buffer that you get is an array of POLICY_PRIVILEGE_DEFINITION structures:
typedef struct _POLICY_PRIVILEGE_DEFINITION
{
LSA_UNICODE_STRING Name;
LUID LocalValue;
} POLICY_PRIVILEGE_DEFINITION, *PPOLICY_PRIVILEGE_DEFINITION;
For example:
#include <ntlsa.h>
NTSTATUS status;
LSA_HANDLE policyHandle;
LSA_ENUMERATION_HANDLE enumerationContext = 0;
PPOLICY_PRIVILEGE_DEFINITION buffer;
ULONG countReturned;
ULONG i;
LsaOpenPolicy(..., &policyHandle);
while (TRUE)
{
status = LsaEnumeratePrivileges(policyHandle, &enumerationContext, &buffer, 256, &countReturned);
if (status == STATUS_NO_MORE_ENTRIES)
break; // no more privileges
if (!NT_SUCCESS(status))
break; // error
for (i = 0; i < countReturned; i++)
{
// Privilege definition in buffer[i]
}
LsaFreeMemory(buffer);
}
LsaClose(policyHandle);
Working code:
#include <Windows.h>
#include <iostream>
#include <ntstatus.h>
typedef LONG NTSTATUS, *PNTSTATUS;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _STRING {
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
} STRING, *PSTRING;
typedef LARGE_INTEGER OLD_LARGE_INTEGER;
typedef LARGE_INTEGER POLD_LARGE_INTEGER;
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#include <ntlsa.h>
LSA_HANDLE GetPolicyHandle() {
LSA_OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS ntsResult;
LSA_HANDLE lsahPolicyHandle;
// Object attributes are reserved, so initialize to zeros.
ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));
// Get a handle to the Policy object.
ntsResult = LsaOpenPolicy(NULL, // Name of the target system.
&ObjectAttributes, // Object attributes.
POLICY_ALL_ACCESS, // Desired access permissions.
&lsahPolicyHandle // Receives the policy handle.
);
if (ntsResult != STATUS_SUCCESS) {
// An error occurred. Display it as a win32 error code.
wprintf(L"OpenPolicy returned %lu\n", LsaNtStatusToWinError(ntsResult));
return NULL;
}
return lsahPolicyHandle;
}
void main() {
NTSTATUS status;
LSA_HANDLE policyHandle;
LSA_ENUMERATION_HANDLE enumerationContext = 0;
PPOLICY_PRIVILEGE_DEFINITION buffer;
ULONG countReturned;
ULONG i;
policyHandle = GetPolicyHandle();
while (TRUE) {
status = LsaEnumeratePrivileges(policyHandle, &enumerationContext,
(PVOID *)&buffer, 256, &countReturned);
if (status == STATUS_NO_MORE_ENTRIES)
break; // no more privileges
if (!NT_SUCCESS(status))
break; // error
for (i = 0; i < countReturned; i++) {
// Privilege definition in buffer[i]
std::wcout << L"KEY:" << buffer[i].Name.Buffer << L" HIGH VALUE:"
<< buffer[i].LocalValue.HighPart << L"LOW VALUE:"
<< buffer[i].LocalValue.LowPart << std::endl;
}
LsaFreeMemory(buffer);
}
LsaClose(policyHandle);
}
Related
I am working on a certificate-based authentication project for domain users against Active Directory without the use of smartcards. I am trying to access the issued domain user certificate container and pass the value in a serialized format to LsaLogonUser for authentication.
First, I am enrolling the domain user certificate by creating a new certificate template described below:-
Duplicate a new certificate template from "User" certificate template.
In the Properties of new certificate template window, go to the General tab and change the following settings:
Check the "Publish certificate in Active Directory" box and enter a template name.
Go to the Request Handling tab and change the following settings:
Change "Purpose" to "Signature and encryption".
Check the box "Allow private key to be exported".
Select "Prompt the user during enrollment".
Go to the Subject Name tab and change the following settings:
Select "Build from this Active Directory information".
Change "Subject name format" to "None".
Check the "User principal name (UPN)" box.
Go to the Extensions tab and edit "Application Policies" so that the only listed policies are "Client Authentication" and "Smart Card Logon". (Remove any default policies as necessary.)
Go to the Cryptography tab and change the following settings:
Change "Minimum key size" to 2048.
Select "Requests must use one of the following providers: "
Select "Microsoft Enhanced Cryptographic Provider v1.0" as "Providers".
After making the above changes, issue/publish the new certificate template in Active Directory Certificate Authority.
The domain user enrolls using the new certificate template and the user certificate is stored in the current user's Certificate Store.
After the enrollment process is complete, I am trying to access the CSP container handle of the user certificate and using it for KERB_CERTIFICATE_LOGON structure for packing and serializing the authentication data specified by LsaLogonUser. Here is the code:
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <Ntsecapi.h>
#include <iostream>
#include <intsafe.h>
#include <WinCred.h>
#include <winbase.h>
#pragma comment(lib, "Crypt32")
#define NEGOSSP_NAME_A "Negotiate"
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif
#include <vcruntime_exception.h>
#pragma comment(lib, "Secur32.lib")
using namespace std;
// 1-Byte packing for this structure
#pragma pack(push, KerbCspInfo, 1)
typedef struct _KERB_SMARTCARD_CSP_INFO {
DWORD dwCspInfoLen;
DWORD MessageType;
union {
PVOID ContextInformation;
ULONG64 SpaceHolderForWow64;
};
DWORD flags;
DWORD KeySpec;
ULONG nCardNameOffset;
ULONG nReaderNameOffset;
ULONG nContainerNameOffset;
ULONG nCSPNameOffset;
TCHAR bBuffer[sizeof(DWORD)];
} KERB_SMARTCARD_CSP_INFO, * PKERB_SMARTCARD_CSP_INFO;
#pragma pack(pop, KerbCspInfo)
HRESULT UnicodeStringInitWithString(
_In_ PWSTR pwz,
_Out_ UNICODE_STRING* pus
)
{
HRESULT hr;
if (pwz)
{
size_t lenString = wcslen(pwz);
USHORT usCharCount;
hr = SizeTToUShort(lenString, &usCharCount);
if (SUCCEEDED(hr))
{
USHORT usSize;
hr = SizeTToUShort(sizeof(wchar_t), &usSize);
if (SUCCEEDED(hr))
{
hr = UShortMult(usCharCount, usSize, &(pus->Length)); // Explicitly NOT including NULL terminator
if (SUCCEEDED(hr))
{
pus->MaximumLength = pus->Length;
pus->Buffer = pwz;
hr = S_OK;
}
else
{
hr = HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
}
}
}
}
else
{
hr = E_INVALIDARG;
}
return hr;
}
static void _UnicodeStringPackedUnicodeStringCopy(
__in const UNICODE_STRING& rus,
__in PWSTR pwzBuffer,
__out UNICODE_STRING* pus
)
{
pus->Length = rus.Length;
pus->MaximumLength = rus.Length;
pus->Buffer = pwzBuffer;
std::CopyMemory(pus->Buffer, rus.Buffer, pus->Length);
}
// Set the SeTcbPrivilege of the current process
BOOL SetSeTcbPrivilege()
{
TOKEN_PRIVILEGES tp;
LUID luid;
HANDLE hProcessToken;
int x;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES,
&hProcessToken))
{
_tprintf(_T("OpenProcessToken failed with error 0x%.8X\n"), GetLastError());
cin >> x;
return FALSE;
}
if (!LookupPrivilegeValue(
NULL,
SE_TCB_NAME,
&luid))
{
_tprintf(_T("LookupPrivilegeValue failed with error 0x%.8X\n"), GetLastError());
CloseHandle(hProcessToken);
cin >> x;
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Enable the privilege
if (!AdjustTokenPrivileges(
hProcessToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
_tprintf(_T("AdjustTokenPrivileges failed with error 0x%.8X\n"), GetLastError());
CloseHandle(hProcessToken);
cin >> x;
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
_tprintf(_T("The token does not have the privilege \"SeTcbPrivilege\". \n"));
CloseHandle(hProcessToken);
cin >> x;
return FALSE;
}
CloseHandle(hProcessToken);
return TRUE;
}
// Build the authentication data used by LsaLogonUser
BOOL ConstructAuthCertInfo(const KERB_CERTIFICATE_UNLOCK_LOGON& rkiulIn,LPBYTE* ppbAuthInfo, ULONG* pulAuthInfoLen)
{
UNICODE_STRING DomainName;
UNICODE_STRING UserName;
UNICODE_STRING Password;
NTSTATUS Status;
ULONG_PTR Ptr;
ULONG AuthInfoLength;
wstring WProvName;
DWORD ProvSize = 0;
wstring containerName;
DWORD contSize = 0;
HCRYPTPROV hProv;
DWORD dwKeySpec;
// Fetching the certificate context from the certificate store
// and recieving the CSP handle for the user certificate
HCERTSTORE hStoreHandle = NULL;
PCCERT_CONTEXT pCertContext = NULL;
LPCTSTR pszStoreName = _T("MY");
CERT_CREDENTIAL_INFO certInfo;
HCRYPTHASH hHash;
BOOL bStatus;
DWORD dwHashLen = CERT_HASH_LENGTH;
LPWSTR szMarshaledCred = NULL;
HANDLE hToken;
hStoreHandle = CertOpenSystemStore(NULL, pszStoreName);
if (hStoreHandle)
{
// Since my certificate store has only one user certificate, tring to retrieve PCERT_CONTEXT for it
pCertContext = CertFindCertificateInStore(
hStoreHandle,
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
0,
CERT_FIND_ANY,
NULL,
NULL);
if (pCertContext)
{
BOOL bFreeHandle;
// acquire private key container to this certificate
if (CryptAcquireCertificatePrivateKey(pCertContext, CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG, NULL, &hProv, &dwKeySpec, &bFreeHandle))
{
cout << "The private key container handle is accessed.. "<<endl<<"The dwKeySpec Value is: "<<dwKeySpec<<endl;
//Fetching the container name where the certificate is stored
if (CryptGetProvParam(hProv, PP_CONTAINER, NULL, &contSize, 0))
{
BYTE* contName = (BYTE*)malloc(contSize);
if (CryptGetProvParam(hProv, PP_CONTAINER, contName, &contSize, 0))
{
containerName = wstring(contName, contName + contSize);
wcout << "The container name is: " << containerName << endl;
}
else
{
cout << "Could not get the container name. Exiting.."<<endl;
return FALSE;
}
}
else
{
cout << "Could not get the container size. Exiting.."<<endl;
return FALSE;
}
//Fetching the CSP name where the certificate is stored
if (CryptGetProvParam(hProv, PP_NAME, NULL, &ProvSize, 0))
{
BYTE* ProvName = (BYTE*)malloc(ProvSize);
if (CryptGetProvParam(hProv, PP_NAME, ProvName, &ProvSize, 0))
{
WProvName = wstring(ProvName, ProvName + ProvSize);
wcout << "The name of the CSP is: " << WProvName << endl;
}
else
{
cout << "The CSP name could not be retrieved. Exiting.." << endl;
return FALSE;
}
}
else
{
cout << "The CSP name size could not be retrieved. Exiting.." << endl;
return FALSE;
}
}
else
{
cout << "Could not get the Private Key Container handle. Exiting...";
return FALSE;
}
}
else
{
cout << "Could not find the user certificate in the Certificate store. Exiting.." << endl;
return FALSE;
}
}
// Creating and initializing a PKERB_SMARTCARD_CSP_INFO struct for passing as an argument to KERB_CERTIFICATE_LOGON struct
// This struct stores information regarding the CSP and container of the user certs
DWORD dwReaderLen = (DWORD)_tcslen(L"") + 1;
DWORD dwCardLen = (DWORD)_tcslen(L"") + 1;
DWORD dwProviderLen = (DWORD)_tcslen(WProvName.c_str()) + 1;
DWORD dwContainerLen = (DWORD)_tcslen(containerName.c_str()) + 1;
DWORD dwBufferSize = dwReaderLen + dwCardLen + dwProviderLen + dwContainerLen;
PKERB_SMARTCARD_CSP_INFO CspInfo = (PKERB_SMARTCARD_CSP_INFO)malloc(sizeof(KERB_SMARTCARD_CSP_INFO) + dwBufferSize * sizeof(TCHAR));
std::memset(CspInfo, 0, sizeof(KERB_SMARTCARD_CSP_INFO));
CspInfo->dwCspInfoLen = sizeof(KERB_SMARTCARD_CSP_INFO) + dwBufferSize * sizeof(TCHAR);
CspInfo->MessageType = 1;
CspInfo->KeySpec = dwKeySpec;
CspInfo->nCardNameOffset = ARRAYSIZE(CspInfo->bBuffer);
CspInfo->nReaderNameOffset = CspInfo->nCardNameOffset + dwCardLen;
CspInfo->nContainerNameOffset = CspInfo->nReaderNameOffset + dwReaderLen;
CspInfo->nCSPNameOffset = CspInfo->nContainerNameOffset + dwContainerLen;
std::memset(CspInfo->bBuffer, 0, sizeof(CspInfo->bBuffer));
_tcscpy_s(&CspInfo->bBuffer[CspInfo->nCardNameOffset], dwBufferSize + 4 - CspInfo->nCardNameOffset, L"");
_tcscpy_s(&CspInfo->bBuffer[CspInfo->nReaderNameOffset], dwBufferSize + 4 - CspInfo->nReaderNameOffset, L"");
_tcscpy_s(&CspInfo->bBuffer[CspInfo->nContainerNameOffset], dwBufferSize + 4 - CspInfo->nContainerNameOffset, containerName.c_str());
_tcscpy_s(&CspInfo->bBuffer[CspInfo->nCSPNameOffset], dwBufferSize + 4 - CspInfo->nCSPNameOffset, WProvName.c_str());
const KERB_CERTIFICATE_LOGON* pkilIn = &rkiulIn.Logon;
// alloc space for struct plus extra for the three strings
DWORD cb = sizeof(rkiulIn) +
pkilIn->DomainName.Length +
pkilIn->UserName.Length +
pkilIn->Pin.Length +
CspInfo->dwCspInfoLen;
KERB_CERTIFICATE_UNLOCK_LOGON* pkiulOut = (KERB_CERTIFICATE_UNLOCK_LOGON*)CoTaskMemAlloc(cb);
if (pkiulOut)
{
std::ZeroMemory(&pkiulOut->LogonId, sizeof(LUID));
//
// point pbBuffer at the beginning of the extra space
//
BYTE* pbBuffer = (BYTE*)pkiulOut + sizeof(*pkiulOut);
//
// set up the Logon structure within the KERB_CERTIFICATE_UNLOCK_LOGON
//
KERB_CERTIFICATE_LOGON* pkilOut = &pkiulOut->Logon;
pkilOut->MessageType = pkilIn->MessageType;
pkilOut->Flags = pkilIn->Flags;
//
// copy each string,
// fix up appropriate buffer pointer to be offset,
// advance buffer pointer over copied characters in extra space
//
_UnicodeStringPackedUnicodeStringCopy(pkilIn->DomainName, (PWSTR)pbBuffer, &pkilOut->DomainName);
pkilOut->DomainName.Buffer = (PWSTR)(pbBuffer - (BYTE*)pkiulOut);
pbBuffer += pkilOut->DomainName.Length;
_UnicodeStringPackedUnicodeStringCopy(pkilIn->UserName, (PWSTR)pbBuffer, &pkilOut->UserName);
pkilOut->UserName.Buffer = (PWSTR)(pbBuffer - (BYTE*)pkiulOut);
pbBuffer += pkilOut->UserName.Length;
_UnicodeStringPackedUnicodeStringCopy(pkilIn->Pin, (PWSTR)pbBuffer, &pkilOut->Pin);
pkilOut->Pin.Buffer = (PWSTR)(pbBuffer - (BYTE*)pkiulOut);
pbBuffer += pkilOut->Pin.Length;
pkilOut->CspData = (PUCHAR)(pbBuffer - (BYTE*)pkiulOut);
pkilOut->CspDataLength = CspInfo->dwCspInfoLen;
std::memcpy(pbBuffer,CspInfo, CspInfo->dwCspInfoLen);
*ppbAuthInfo = (BYTE*)pkilOut;
*pulAuthInfoLen = cb;
return TRUE;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
NTSTATUS nStatus;
CHAR szProcName[] = "LsaTestLogonProcess";
CHAR szPackageName[] = NEGOSSP_NAME_A;
CHAR szOriginName[] = "LsaSmartCardLogonTest";
LSA_STRING lsaProcName = { strlen(szProcName), strlen(szProcName) + 1, szProcName };
LSA_STRING lsaPackageName = { strlen(szPackageName), strlen(szPackageName) + 1, szPackageName };
LSA_STRING lsaOriginName = { strlen(szOriginName), strlen(szOriginName) + 1, szOriginName };
HANDLE lsaHandle;
ULONG ulAuthPackage;
LPBYTE pbAuthInfo = NULL;
ULONG ulAuthInfoLen = 0;
LSA_OPERATIONAL_MODE dummy;
TOKEN_SOURCE tokenSource;
LPVOID pProfileBuffer = NULL;
ULONG ulProfileBufferLen = 0;
LUID logonId;
HANDLE hLogonToken;
QUOTA_LIMITS quotas;
NTSTATUS subStatus;
HANDLE hToken = NULL;
int x;
wchar_t doman[] = L"TECNICS";
wchar_t usernme[] = L"";
wchar_t pn[] = L"";
PWSTR domain = doman;
PWSTR username = usernme;
PWSTR pin = pn;
//Certificate Based Auth
KERB_CERTIFICATE_UNLOCK_LOGON ckiul;
KERB_CERTIFICATE_LOGON* ckil = &ckiul.Logon;
ckil->MessageType = KerbCertificateLogon;
ckil->Flags = 2;
// Converting Domain Name to Unicode Format
UnicodeStringInitWithString(domain, &ckil->DomainName);
// Passing empty UserName as per https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-kerb_certificate_logon
UnicodeStringInitWithString(username, &ckil->UserName);
// Passing empty PIN since no PIN asked during enrollment
UnicodeStringInitWithString(pin, &ckil->Pin);
//End
if (!SetSeTcbPrivilege())
{
cin >> x;
return 0;
}
std::memcpy(tokenSource.SourceName, "LsaTest", 8);
AllocateLocallyUniqueId(&tokenSource.SourceIdentifier);
nStatus = LsaRegisterLogonProcess(&lsaProcName,
&lsaHandle,
&dummy);
if (nStatus == STATUS_SUCCESS)
{
nStatus = LsaLookupAuthenticationPackage(lsaHandle,
&lsaPackageName,
&ulAuthPackage);
if (nStatus == STATUS_SUCCESS)
{
if (!ConstructAuthCertInfo(ckiul, &pbAuthInfo, &ulAuthInfoLen))
{
cout << "Could not serialize the authentication data. Exiting...";
cin >> x;
return 0;
}
nStatus = LsaLogonUser(lsaHandle,
&lsaOriginName,
Interactive,
ulAuthPackage,
pbAuthInfo,
ulAuthInfoLen,
NULL,
&tokenSource,
&pProfileBuffer,
&ulProfileBufferLen,
&logonId,
&hLogonToken,
"as,
&subStatus);
if (nStatus == STATUS_SUCCESS)
{
if (pProfileBuffer)
LsaFreeReturnBuffer(pProfileBuffer);
_tprintf(_T("User logged on successfully!!\n"));
cin >> x;
CloseHandle(hLogonToken);
}
else
{
_tprintf(_T("LsaLogonUser failed with error 0x%.8X. SubStatus = 0x%.8X\n"), LsaNtStatusToWinError(nStatus), LsaNtStatusToWinError(subStatus));
cin >> x;
}
HeapFree(GetProcessHeap(), 0, pbAuthInfo);
}
else
{
_tprintf(_T("LsaLookupAuthenticationPackage failed with error 0x%.8X\n"), LsaNtStatusToWinError(nStatus));
cin >> x;
}
LsaDeregisterLogonProcess(lsaHandle);
}
else
{
_tprintf(_T("LsaRegisterLogonProcess failed with error 0x%.8X\n"), LsaNtStatusToWinError(nStatus));
cin >> x;
}
return 0;
}
NOTE: The user needs to have SeTcbPriveledge and admin rights to run the program. You can refer to this article to set the SeTcbPriveledge for the domain user.
After running the above code in the domain user's environment, LSALogonUser fails and I get the following status values:
- Status: 0x0000052E/ERROR_LOGON_FAILURE (The user name or password is incorrect.)
- SubStatus: 0x8009000d/NTE_NO_KEY (Key does not exist.)
I have also pushed a repo on Github which consists of the code in with Visual Studio extensions. You can clone the repo from here.
Can someone suggest a better approach or hints to make this work?
Im trying to use the control flow guard api to set a area of memory to be execution valid.
I used these examples for reverence material:
https://github.com/BreakingMalwareResearch/CFGExceptions/blob/master/CFGExceptions/main.cpp
https://github.com/trailofbits/cfg-showcase/blob/master/cfg_valid_targets.cpp#L111
Here is my code:
#include <stdio.h>
#include <Windows.h>
#include <psapi.h>
#include "cfgTest.h"
BOOL GetMemoryAllocationBaseAndRegionSize(PVOID, PVOID*, PSIZE_T);
INT main() {
HANDLE hProcess;
NTSTATUS ntStatus = ERROR_SUCCESS;
STARTUPINFOA startupInfo;
PROCESS_INFORMATION processInformation = { 0 };
CFG_CALL_TARGET_INFO cfgCallTargetInfoList[1];
DWORD dwPid = 0;
SIZE_T stRegionSize = NULL;
PVOID pvAllocationBase = NULL;
// Function pointers
SETPROCESSVALIDCALLTARGETS pSetProcessValidCallTargets = NULL;
PVOID pvAddressToAddCfgExceptionTo = NULL;
//
// Get address of SetProcessValidCallTargets
//
CONST HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
CONST HMODULE hKernelbase = LoadLibraryW(L"Kernelbase.dll");
if (hKernelbase && hNtdll) {
pSetProcessValidCallTargets = (SETPROCESSVALIDCALLTARGETS)GetProcAddress(hKernelbase, "SetProcessValidCallTargets");
pvAddressToAddCfgExceptionTo = GetProcAddress(hNtdll, "NtSetContextThread");
}
else {
return ERROR_MOD_NOT_FOUND;
}
FreeLibrary(hNtdll);
FreeLibrary(hKernelbase);
// Get memory allocation base and region size by calling VirtualProtect.
if (GetMemoryAllocationBaseAndRegionSize(pvAddressToAddCfgExceptionTo, &pvAllocationBase, &stRegionSize) == FALSE) {
ntStatus = ERROR_UNHANDLED_ERROR;
goto lblCleanup;
}
//
// Add cfg exception
//
cfgCallTargetInfoList[0].Flags = CFG_CALL_TARGET_VALID;
cfgCallTargetInfoList[0].Offset = (ULONG_PTR)pvAddressToAddCfgExceptionTo - (ULONG_PTR)pvAllocationBase;;
if (pSetProcessValidCallTargets(GetCurrentProcess(), pvAllocationBase, stRegionSize, 1, cfgCallTargetInfoList) == FALSE) {
printf("%d", GetLastError());
ntStatus = ERROR_UNHANDLED_ERROR;
goto lblCleanup;
}
lblCleanup:
if (processInformation.hProcess) {
CloseHandle(processInformation.hProcess);
}
if (processInformation.hThread) {
CloseHandle(processInformation.hThread);
}
return ntStatus;
}
BOOL GetMemoryAllocationBaseAndRegionSize(PVOID pvAddress, PVOID* ppvAllocationBase, PSIZE_T pstRegionSize) {
SIZE_T stErr = 0;
MEMORY_BASIC_INFORMATION tMemoryBasicInformation = { 0 };
stErr = VirtualQuery(pvAddress, &tMemoryBasicInformation, sizeof(tMemoryBasicInformation));
if (0 == stErr) {
return FALSE;
}
*ppvAllocationBase = tMemoryBasicInformation.AllocationBase;
*pstRegionSize = tMemoryBasicInformation.RegionSize;
return TRUE;
}
#pragma once
typedef enum _VIRTUAL_MEMORY_INFORMATION_CLASS
{
VmPrefetchInformation,
VmPagePriorityInformation,
VmCfgCallTargetInformation
} VIRTUAL_MEMORY_INFORMATION_CLASS;
typedef struct _MEMORY_RANGE_ENTRY
{
PVOID VirtualAddress;
SIZE_T NumberOfBytes;
} MEMORY_RANGE_ENTRY, * PMEMORY_RANGE_ENTRY;
typedef struct _VM_INFORMATION
{
DWORD dwNumberOfOffsets;
DWORD dwMustBeZero;
PDWORD pdwOutput;
PCFG_CALL_TARGET_INFO ptOffsets;
} VM_INFORMATION, * PVM_INFORMATION;
typedef NTSTATUS(NTAPI* NTSETINFORMATIONVIRTUALMEMORY)(
HANDLE hProcess,
VIRTUAL_MEMORY_INFORMATION_CLASS VmInformationClass,
ULONG_PTR NumberOfEntries,
PMEMORY_RANGE_ENTRY VirtualAddresses,
PVOID VmInformation,
ULONG VmInformationLength
);
typedef BOOL(WINAPI* SETPROCESSVALIDCALLTARGETS)(
HANDLE hProcess,
PVOID VirtualAddress,
SIZE_T RegionSize,
ULONG NumberOfOffsets,
PCFG_CALL_TARGET_INFO OffsetInformation
);
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
My problem is SetProcessValidCallTargets returns FALSE and GetLastError() tells me its 87 which means an invalid parameter has been passed. But i have no idea what is going wrong. Could anyone see whats going on?
I want to allocate some memory inside a specific module of a process instead of the process in general. The following Windows C++ code can allocate memory inside a process given its process id:
#include "pch.h"
#include <windows.h>
#include <winternl.h>
#include <processthreadsapi.h>
#include <iostream>
#include <conio.h>
#pragma comment(lib, "ntdll.lib")
typedef NTSTATUS (NTAPI *nt_alloc_virtual_memory_func)(HANDLE process_handle, PVOID* base_address, ULONG_PTR zero_bits,
PSIZE_T region_size, ULONG allocation_type, ULONG protect);
typedef NTSTATUS (NTAPI *nt_free_virtual_memory_func)(HANDLE process_handle, PVOID* base_address, PSIZE_T region_size,
ULONG free_type);
void enable_allocating_executable_memory()
{
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY mp;
ZeroMemory(&mp, sizeof mp);
mp.ProhibitDynamicCode = FALSE;
SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &mp, sizeof mp);
}
long allocate_memory(char** arguments, const HANDLE process_handle, PVOID process_memory, SIZE_T& allocation_size)
{
const auto memory_size = arguments[3];
allocation_size = strtoul(memory_size, nullptr, 10);
const auto nt_allocate_virtual_memory = reinterpret_cast<nt_alloc_virtual_memory_func>(GetProcAddress(
GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory"));
const auto allocation_status = nt_allocate_virtual_memory(process_handle, &process_memory, 0, &allocation_size,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NT_SUCCESS(allocation_status))
{
std::cout << std::hex << process_memory << std::endl;
}
return allocation_status;
}
int free_memory(const int argument_count, char** arguments,
const HANDLE process_handle, SIZE_T& mem_size)
{
const auto address_string = arguments[3];
const auto process_address = strtoull(address_string, nullptr, 16);
auto process_memory_address = reinterpret_cast<PVOID>(process_address);
if (argument_count < 4)
{
return EXIT_FAILURE;
}
const auto memory_size = arguments[4];
mem_size = strtoul(memory_size, nullptr, 10);
const auto nt_free_virtual_memory = reinterpret_cast<nt_free_virtual_memory_func>(GetProcAddress(
GetModuleHandle(L"ntdll.dll"), "NtFreeVirtualMemory"));
const auto status = nt_free_virtual_memory(process_handle, &process_memory_address, &mem_size, MEM_RELEASE);
return status;
}
int main(const int argument_count, char* arguments[])
{
if (argument_count < 4)
{
return EXIT_FAILURE;
}
const auto process_id_string = arguments[1];
const auto process_id = strtoul(process_id_string, nullptr, 10);
enable_allocating_executable_memory();
const auto process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_id);
if (process_handle == nullptr)
{
std::cout << "Cannot open process with process id " << process_id << std::endl;
exit(EXIT_FAILURE);
}
const PVOID process_memory = nullptr;
SIZE_T mem_size;
const auto command = arguments[2];
if (strcmp(command, "--allocate") == 0)
{
allocate_memory(arguments, process_handle, process_memory, mem_size);
}
else if (strcmp(command, "--free") == 0)
{
return free_memory(argument_count, arguments, process_handle, mem_size);
}
return EXIT_SUCCESS;
}
NtAllocateVirtualMemory does not seem to accept an argument for a module. What else can be used?
The reasoning behind this is that I don't want to have jmps going from one module to another after I allocated some memory but rather stay as locally as possible. This also makes jmp instructions shorter in terms of their sizes in memory.
I want to allocate some memory inside a specific module
You cannot do this, when a module is mapped it's memory is allocated. You can't allocate memory inside the module, the module exists inside it's allocated pages and no where else. Any allocated pages will be outside of the module.
Alternatively if you want to use memory which is already allocated but is not used, this called a code cave. It's typically an area of memory inside a module which is filled with zeros. So you can scan for a code cave by finding a certain length of redundant zeros inside the module and then you can write to that memory.
This is done frequently and is especially useful if the page has the execute bit set as you won't have to change any permissions which could be deemed risky.
This is also done frequently in injectors which use "scatter mapping" where it only uses these code caves to inject code.
I want to get the entry point to a 64bit process I wrote from a 32bit process, the same way you'd use EnumProcessModule and take the memory addr of the main module.
My end goal is to read a byte from memory in my 64bit process from an offset to it (entry+Offset).
But my NtWow64ReadVirtualMemory64 function keeps failing.
I think it has something to do with my entry memory addr.
#define PROC_BASIC_INFO 0
#define NT_WOW64_QUERY_INFORMATION_PROCESS_64_NAME "NtWow64QueryInformationProcess64"
#define NT_WOW64_READ_VIRTUAL_MEMORY_64_NAME "NtWow64ReadVirtualMemory64"
typedef UINT64 SYM;
typedef SIZE_T SIZE_T64;
HWND WINDOW_HANDLE;
HANDLE PROC_HANDLE;
DWORD PROC_ID;
UINT address;
UINT64 address64;
SIZE_T bytesRead;
SIZE_T64 bytesRead64;
using namespace std;
//initialize variables for importing of essential 64 bit reading functions
//from ntdll
typedef NTSTATUS(NTAPI *FUNC_NtReadVirtualMemory64)
(
IN HANDLE ProcessHandle,
IN PVOID64 BaseAddress,
OUT PVOID Buffer,
IN ULONGLONG BufferLength,
OUT PULONGLONG ReturnLength OPTIONAL
);
typedef NTSTATUS (NTAPI *FUNC_NtWow64QueryInformationProcess64)
(
IN HANDLE ProcessHandle,
IN ULONG ProcessInformationClass,
OUT PVOID ProcessInformation64,
IN ULONG Length,
OUT PULONG ReturnLength OPTIONAL
);
struct PROCESS_BASIC_INFORMATION64 {
SYM Reserved1;
SYM PebBaseAddress;
SYM Reserved2[2];
SYM UniqueProcessId;
SYM Reserved3;
/*
NTSTATUS ExitStatus;
ULONG64 PebBaseAddress;
ULONG64 AffinityMask;
LONG BasePriority;
UINT64 Reserved1;
ULONG64 UniqueProcessId;
ULONG64 InheritedFromUniqueProcessId;
*/
};
HINSTANCE ntdll = LoadLibrary("ntdll.dll");
FUNC_NtWow64QueryInformationProcess64 NtWow64QueryInformationProcess64 = (FUNC_NtWow64QueryInformationProcess64)GetProcAddress(ntdll, NT_WOW64_QUERY_INFORMATION_PROCESS_64_NAME);
FUNC_NtReadVirtualMemory64 NtReadVirtualMemory64 = (FUNC_NtReadVirtualMemory64)GetProcAddress(ntdll, NT_WOW64_READ_VIRTUAL_MEMORY_64_NAME);
int Init32To64MemoryRead(const char* windowClass, const char* caption, SYM addressOffset)
{
DWORD cbNeeded;
DWORD dwdResult;
HMODULE mainModule;
BOOL enumResult;
ULONG read_length=0;
HINSTANCE ntdll;
PROCESS_BASIC_INFORMATION64 procInfo;
ZeroMemory(&procInfo, sizeof(procInfo));
//Get the window handle
WINDOW_HANDLE = FindWindow(windowClass, NULL);
if (WINDOW_HANDLE == NULL)
{
//Window was not foud
return 10;
}
//Get the process ID
dwdResult = GetWindowThreadProcessId(WINDOW_HANDLE, &PROC_ID);
if (dwdResult == 0)
{
//Getting Process ID failed
return 20;
}
//Open the process
PROC_HANDLE = OpenProcess(PROCESS_ALL_ACCESS, false, PROC_ID);
if (PROC_HANDLE == NULL)
{
//Process failed to open
return 30;
}
DWORD result;
//Query Proc Information to get .exe entry point
result = NtWow64QueryInformationProcess64( PROC_HANDLE, 0, &procInfo, sizeof(procInfo), &read_length);
if (result != 0)
{
cerr << "Query Information Process has failed" << endl;
return 40;
}
address64 = (procInfo.PebBaseAddress + addressOffset);
cerr << address64 << endl;
string number;
stringstream stristream;
stristream << address64;
stristream >> number;
byte testByte = 0;
(byte)ReadMemory64<byte>(testByte);
system("PAUSE");
return 1;
}
template <typename _ret_t> _ret_t ReadMemory64(_ret_t& ret)
{
NTSTATUS result = NtReadVirtualMemory64(PROC_HANDLE, (void*)address64, &ret, 8, NULL);
///* Debug # when too lazy for breakpoints
cerr <<"value: " << ret << endl;
cerr << "Error Code: " << GetLastError() << endl;
if (result != 0)
{
cerr << "ReadMemory Failed.\r\nAddress: " << address64 << "\r\nSize: " << sizeof(_ret_t) << "\r\nResult: " << result << endl;
cerr << "NtReadVirtualMemory64 has failed" << endl;
system("PAUSE");
} //*/
return ret;
};
I'd like to know what I am doing wrong.
Edit:
Upon further inspection, I noticed that NtWow64ReadVirtualMemory, does not store a value in the variable "ret" used as the buffer.
I ran a simple test and figured out that the value of my buffer-"ret" was not changed when inserted to the function "NtWow64ReadVirtualMemory64".
The code did compile and run without errors(compile and runtime) except for NtReadMemory64 returning a weird number (there is no documentation available for the ntdll NtWow64 functions, so goolgling it did not yield anything useful).
So i figured I am either providing a faulty buffer or am not reading from a valid memory addr.
since I did initialize the buffer explicitly outside of the function, I figured
that my problem is the latter(not providing a valid memory address).
I was using the following when calling NtReadVirtualMemory
NTSTATUS result = NtReadVirtualMemory64(PROC_HANDLE, (void*)address64, &ret, 8, NULL);
apparently, when calling NtWow64ReadVirtualMemory64, I cast the addr to a 32 bit void pointer (void*)address64 , and since address64 is a UINT64-type , the cast truncated the address, and I was trying to read off of a memory segment I wasn't able to read
I resolved it by changing the cast to (PVOID64)address64
which casts it to a native 64bit pointer.
simpler than I thought, but finding it was hell after days of googling and reviewing the code.
Edit:
this didn't cut it since my address is wrong.
I need to get the ".exe"s' entry point through the location of the process' main module in memory.
looking at the how to now.
any help is appreciated!
In Windows, is there a way to check for the existence of an environment variable for another process? Just need to check existence, not necessarily get value.
I need to do this from code.
If you know the virtual address at which the environment is stored, you can use OpenProcess and ReadProcessMemory to read the environment out of the other process. However, to find the virtual address, you'll need to poke around in the Thread Information Block of one of the process' threads.
To get that, you'll need to call GetThreadContext() after calling SuspendThread(). But in order to call those, you need a thread handle, which you can get by calling CreateToolhelp32Snapshot with the TH32CS_SNAPTHREAD flag to create a snapshot of the process, Thread32First to get the thread ID of the first thread in the process, and OpenThread to get a handle to the thread.
Here is a working example which the printed output can be used to check existence as well as read the value, (build it as the same architecture as the executable's process identifier you must target):
getenv.cpp
#include <string>
#include <vector>
#include <cwchar>
#include <windows.h>
#include <winternl.h>
using std::string;
using std::wstring;
using std::vector;
using std::size_t;
// define process_t type
typedef DWORD process_t;
// #define instead of typedef to override
#define RTL_DRIVE_LETTER_CURDIR struct {\
WORD Flags;\
WORD Length;\
ULONG TimeStamp;\
STRING DosPath;\
}\
// #define instead of typedef to override
#define RTL_USER_PROCESS_PARAMETERS struct {\
ULONG MaximumLength;\
ULONG Length;\
ULONG Flags;\
ULONG DebugFlags;\
PVOID ConsoleHandle;\
ULONG ConsoleFlags;\
PVOID StdInputHandle;\
PVOID StdOutputHandle;\
PVOID StdErrorHandle;\
UNICODE_STRING CurrentDirectoryPath;\
PVOID CurrentDirectoryHandle;\
UNICODE_STRING DllPath;\
UNICODE_STRING ImagePathName;\
UNICODE_STRING CommandLine;\
PVOID Environment;\
ULONG StartingPositionLeft;\
ULONG StartingPositionTop;\
ULONG Width;\
ULONG Height;\
ULONG CharWidth;\
ULONG CharHeight;\
ULONG ConsoleTextAttributes;\
ULONG WindowFlags;\
ULONG ShowWindowFlags;\
UNICODE_STRING WindowTitle;\
UNICODE_STRING DesktopName;\
UNICODE_STRING ShellInfo;\
UNICODE_STRING RuntimeData;\
RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[32];\
ULONG EnvironmentSize;\
}\
// shortens a wide string to a narrow string
static inline string shorten(wstring wstr) {
int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
vector<char> buf(nbytes);
return string { buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, NULL, NULL) };
}
// replace all occurrences of substring found in string with specified new string
static inline string string_replace_all(string str, string substr, string nstr) {
size_t pos = 0;
while ((pos = str.find(substr, pos)) != string::npos) {
str.replace(pos, substr.length(), nstr);
pos += nstr.length();
}
return str;
}
// func that splits string by first occurrence of equals sign
vector<string> string_split_by_first_equalssign(string str) {
size_t pos = 0;
vector<string> vec;
if ((pos = str.find_first_of("=")) != string::npos) {
vec.push_back(str.substr(0, pos));
vec.push_back(str.substr(pos + 1));
}
return vec;
}
// checks whether process handle is 32-bit or not
static inline bool IsX86Process(HANDLE process) {
BOOL isWow = true;
SYSTEM_INFO systemInfo = { 0 };
GetNativeSystemInfo(&systemInfo);
if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
return isWow;
IsWow64Process(process, &isWow);
return isWow;
}
// helper to open processes based on pid with full debug privileges
static inline HANDLE OpenProcessWithDebugPrivilege(process_t pid) {
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), NULL, NULL);
CloseHandle(hToken);
return OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
}
// get wide character string of pids environ based on handle
static inline wchar_t *GetEnvironmentStringsW(HANDLE proc) {
PEB peb;
SIZE_T nRead;
ULONG res_len = 0;
PROCESS_BASIC_INFORMATION pbi;
RTL_USER_PROCESS_PARAMETERS upp;
HMODULE p_ntdll = GetModuleHandleW(L"ntdll.dll");
typedef NTSTATUS (__stdcall *tfn_qip)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
tfn_qip pfn_qip = tfn_qip(GetProcAddress(p_ntdll, "NtQueryInformationProcess"));
NTSTATUS status = pfn_qip(proc, ProcessBasicInformation, &pbi, sizeof(pbi), &res_len);
if (status) { return NULL; }
ReadProcessMemory(proc, pbi.PebBaseAddress, &peb, sizeof(peb), &nRead);
if (!nRead) { return NULL; }
ReadProcessMemory(proc, peb.ProcessParameters, &upp, sizeof(upp), &nRead);
if (!nRead) { return NULL; }
PVOID buffer = upp.Environment;
ULONG length = upp.EnvironmentSize;
wchar_t *res = new wchar_t[length / 2 + 1];
ReadProcessMemory(proc, buffer, res, length, &nRead);
if (!nRead) { return NULL; }
res[length / 2] = 0;
return res;
}
// get env of pid as a narrow string
string env_from_pid(process_t pid) {
string envs;
HANDLE proc = OpenProcessWithDebugPrivilege(pid);
wchar_t *wenvs = NULL;
if (IsX86Process(GetCurrentProcess())) {
if (IsX86Process(proc)) {
wenvs = GetEnvironmentStringsW(proc);
}
} else {
if (!IsX86Process(proc)) {
wenvs = GetEnvironmentStringsW(proc);
}
}
string arg;
if (wenvs == NULL) {
return "";
} else {
arg = shorten(wenvs);
}
size_t i = 0;
do {
size_t j = 0;
vector<string> envVec = string_split_by_first_equalssign(arg);
for (const string &env : envVec) {
if (j == 0) {
if (env.find_first_of("%<>^&|:") != string::npos) { continue; }
if (env.empty()) { continue; }
envs += env;
} else { envs += "=\"" + string_replace_all(env, "\"", "\\\"") + "\"\n"; }
j++;
}
i += wcslen(wenvs + i) + 1;
arg = shorten(wenvs + i);
} while (wenvs[i] != L'\0');
if (envs.back() == '\n') { envs.pop_back(); }
if (wenvs != NULL) { delete[] wenvs; }
CloseHandle(proc);
return envs;
}
// test function (can be omitted)
int main(int argc, char **argv) {
if (argc == 2) {
printf("%s", env_from_pid(stoul(string(argv[1]), nullptr, 10)).c_str());
printf("%s", "\r\n");
} else {
printf("%s", env_from_pid(GetCurrentProcessId()).c_str());
printf("%s", "\r\n");
}
return 0;
}
buildx86.sh
g++ getenv.cpp -o getenv.exe -std=c++17 -static-libgcc -static-libstdc++ -static -m32
buildx64.sh
g++ getenv.cpp -o getenv.exe -std=c++17 -static-libgcc -static-libstdc++ -static -m64
Quotes are added around the printed value for clarity, and escaping is applied to inner quotes.
With a utility:
You can use Process Explorer.
Right click on the process, go to Properties... and there is an Environment tab which lists the environment variables for that process.
With code:
There doesn't appear to be a Win32 API call to do this directly, but apparently you get fiddle with the results of GetProcessStrings to get access to this information. This CodeProject article has some code to get you started.