How to validate credentials with CredUIPromptForWindowsCredentials - c++

I don't know why I can't unpack the authentification buffer used in CredUIPromptForWindowsCredentials with CredUnPackAuthenticationBufferW, I always get ERROR_INSUFFICIENT_BUFFER error.
I will appreciate your help.
std::wstring caption = L"Caption";
std::wstring msg= L"Msg";
CREDUI_INFOW credui = {};
credui.cbSize = sizeof(credui);
credui.hwndParent = nullptr;
credui.pszMessageText = msg.c_str();
credui.pszCaptionText = caption.c_str();
credui.hbmBanner = nullptr;
ULONG authPackage = 0;
LPVOID outCredBuffer = nullptr;
ULONG outCredSize = 0;
BOOL save = false;
LPWSTR pszUserName = nullptr;
DWORD pcchlMaxUserName = 0;
LPWSTR pszDomainName = nullptr;
DWORD pcchMaxDomainName = 0;
LPWSTR pszPassword = nullptr;
DWORD pcchMaxPassword = 0;
DWORD result = CredUIPromptForWindowsCredentialsW(&credui,
0,
&authPackage,
nullptr,
0,
&outCredBuffer,
&outCredSize,
&save,
CREDUIWIN_ENUMERATE_ADMINS);
std::cout <<CredUnPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS
,outCredBuffer
,outCredSize
,pszUserName
,&pcchlMaxUserName
,pszDomainName
,&pcchMaxDomainName
,pszPassword
,&pcchMaxPassword) << std::endl;
std::cout << GetLastError() << std::endl; // out put 122 == ERROR_INSUFFICIENT_BUFFER

this is typical winapi pattern - api must return some information in the memory buffer. but instead allocate buffer yourself - it obligate caller to allocate buffer.
so caller must allocate buffer itself and pass it pointer and size to api.
api check buffer size - if it large enough fill information to buffer, otherwise return ERROR_INSUFFICIENT_BUFFER (assume that no another errors) or sometime ERROR_MORE_DATA. which which concrete error reurned ERROR_INSUFFICIENT_BUFFER or ERROR_MORE_DATA usual direct documented for api call. different between this 2 errors: ERROR_INSUFFICIENT_BUFFER - mean no any info filled to buffer at all, when ERROR_MORE_DATA mean some data is returned, but incomplete.
and api return to user, via some out parameter, required buffer size in this case. frequently this is done via the same inout parameter - pointer to DWORD. in input specifies the size of user allocated buffer, in output - specifies the required size of buffer or size of returned data
frequently which buffer size is required - unknown at begin. so we need or call api with 0 size buffers(s) first, or allocate some, supposedly sufficient buffer size. if buffer will be insuffient - reallocate or extend it and call api again. for some api (like CredUnPackAuthenticationBufferW) the required output buffer does not change with time (if input parameters not changed), but usual output buffer size may change between calls - even second call with buffer size returned by first call can fail with buffer size error (because returned data may grow between calls). in this case need call api in do/while(error == ERROR_INSUFFICIENT_BUFFER/ERROR_MORE_DATA) loop. but even in case output buffer does not change with time we can better do this is loop with single api call inside, instead 2 api calls.
for concrete case code can look like
ULONG cred()
{
CREDUI_INFO ci = { sizeof(ci) };
BOOL bSave = FALSE;
PVOID pvOutAuthBuffer;
ULONG ulOutAuthBufferSize;
ULONG ulAuthPackage = 0;
ULONG dwError = CredUIPromptForWindowsCredentials(
&ci, NOERROR, &ulAuthPackage, 0, 0,
&pvOutAuthBuffer, &ulOutAuthBufferSize,
&bSave, CREDUIWIN_ENUMERATE_ADMINS );
if (dwError == NOERROR)
{
ULONG cchUserName = 0;
ULONG cchPassword = 0;
ULONG cchDomain = 0;
static volatile UCHAR guz = 0;
PWSTR stack = (PWSTR)alloca(guz);
PWSTR szUserName = 0, szPassword = 0, szDomainName = 0;
ULONG cchNeed, cchAllocated = 0;
do
{
if (cchAllocated < (cchNeed = cchUserName + cchPassword + cchDomain))
{
szUserName = (PWSTR)alloca((cchNeed - cchAllocated) * sizeof(WCHAR));
cchAllocated = (ULONG)(stack - szUserName);
szPassword = szUserName + cchUserName;
szDomainName = szPassword + cchPassword;
}
dwError = CredUnPackAuthenticationBuffer(
CRED_PACK_PROTECTED_CREDENTIALS,
pvOutAuthBuffer, ulOutAuthBufferSize,
szUserName, &cchUserName,
szDomainName, &cchDomain,
szPassword, &cchPassword)
? NOERROR : GetLastError();
if (dwError == NOERROR)
{
DbgPrint("%S#%S %S\n", szDomainName, szUserName, szPassword);
break;
}
} while (dwError == ERROR_INSUFFICIENT_BUFFER);
CoTaskMemFree(pvOutAuthBuffer);
}
return dwError;
}

#RbMm - you're right! I tested it with LogonUser, and it works perfectly. Thanks.
And for a ready solution, I got this :
bool Authenticate_ADMIN_User(std::wstring caption, std::wstring msg, int maxReAsks = 0)
{
CREDUI_INFOW credui = {};
credui.cbSize = sizeof(credui);
credui.hwndParent = nullptr;
credui.pszMessageText = msg.c_str();
credui.pszCaptionText = caption.c_str();
credui.hbmBanner = nullptr;
ULONG authPackage = 0,
outCredSize = 0;
LPVOID outCredBuffer = nullptr;
BOOL save = false;
DWORD err = 0;
int tries = 0;
bool reAsk = false;
do
{
tries++;
if(CredUIPromptForWindowsCredentialsW(&credui,
err,
&authPackage,
nullptr,
0,
&outCredBuffer,
&outCredSize,
&save,
CREDUIWIN_ENUMERATE_ADMINS)
!= ERROR_SUCCESS)
return false;
ULONG cchUserName = 0;
ULONG cchPassword = 0;
ULONG cchDomain = 0;
ULONG cchNeed, cchAllocated = 0;
static volatile UCHAR guz = 0;
PWSTR stack = (PWSTR)alloca(guz);
PWSTR szUserName = nullptr, szPassword = nullptr, szDomainName = nullptr;
BOOL ret;
do{
if (cchAllocated < (cchNeed = cchUserName + cchPassword + cchDomain))
{
szUserName = (PWSTR)alloca((cchNeed - cchAllocated) * sizeof(WCHAR));
cchAllocated = (ULONG)(stack - szUserName);
szPassword = szUserName + cchUserName;
szDomainName = szPassword + cchPassword;
}
ret = CredUnPackAuthenticationBuffer(
CRED_PACK_PROTECTED_CREDENTIALS , outCredBuffer, outCredSize, szUserName, &cchUserName,
szDomainName, &cchDomain, szPassword,
&cchPassword);
}while(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
SecureZeroMemory(outCredBuffer, outCredSize);
CoTaskMemFree(outCredBuffer);
HANDLE handle = nullptr;
if (LogonUser(szUserName,
szDomainName,
szPassword,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&handle))
{
CloseHandle(handle);
return true;
}
else
{
err = ERROR_LOGON_FAILURE;
reAsk = true;
}
}while(reAsk && tries < maxReAsks);
return false;
}

Related

LsaLogonUser in a WCP context to sign in into windows is not working

When I boot my computer, I am attempting to have sign in into Windows without having to enter credentials. These credentials are available and entered from other means (which excludes the solution of using the winlogon default registry values), in a "single sign-on" kind of deal. Anyways, I am therefore attempting to go about it by creating a Windows Credential Provider (WCP). I got this to run. In this WCP I am applying the LsaLogonUser function with MSV1_0_INTERACTIVE_LOGON. Everything returns the result of success but it does not make the computer to actually log on. I might've misunderstood how it all works so I was wondering if anyone know why it doesn't work. My thoughts is that the issue potentially is that the session only lasts as long as the scope does. That is, as long as the token exists. So when the LogonUI (by Winlogon) process finishes it's execution, it is automatically signed out or something. What am I missing?
Here is a big chunk of code of how it executes:
void login(std::wstring domain, std::wstring username, std::wstring secret)
{
//Get a handle to LSA
HANDLE hLSA = nullptr;
NTSTATUS status = LsaConnectUntrusted(&hLSA);
if (status != 0)
{
int winError = LsaNtStatusToWinError(status);
LLLOG(L"Error calling LsaConnectUntrusted. Error code: " + std::to_wstring(winError) );
return;
}
if (!hLSA)
{
LLLOG(L"hLSA is NULL");
return;
}
//Build LsaLogonUser parameters
LSA_STRING originName = {};
char originNameStr[] = "WCP";
originName.Buffer = originNameStr;
originName.Length = (USHORT)strlen(originNameStr);
originName.MaximumLength = originName.Length;
ULONG authPackage = 0;
PLSA_STRING authPackageName = new LSA_STRING();
char authPackageBuf[] = MSV1_0_PACKAGE_NAME;
authPackageName->Buffer = authPackageBuf;
authPackageName->Length = (USHORT)strlen(authPackageBuf);
authPackageName->MaximumLength = (USHORT)strlen(authPackageBuf);
status = LsaLookupAuthenticationPackage(hLSA, authPackageName, &authPackage);
if (status != 0)
{
int winError = LsaNtStatusToWinError(status);
LLLOG(L"Call to LsaLookupAuthenticationPackage failed. Error code: " + std::to_wstring(winError));
return;
}
DWORD authBufferSize = 0;
PVOID authBuffer = CreateNtlmLogonStructure(domain, username, secret, &authBufferSize);
LLLOG(L"authBufferSize: " + std::to_wstring(authBufferSize));
//Get TokenSource
HANDLE hProcess = GetCurrentProcess();
HANDLE procToken = nullptr;
BOOL success = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &procToken);
if (!success)
{
DWORD errorCode = GetLastError();
LLLOG(L"Call to OpenProcessToken failed. Errorcode: " + std::to_wstring(errorCode));
return;
}
TOKEN_SOURCE tokenSource = {};
DWORD realSize = 0;
success = GetTokenInformation(procToken, TokenSource, &tokenSource, sizeof(tokenSource), &realSize);
if (!success)
{
LLLOG(L"Call to GetTokenInformation failed.");
return;
}
//
PVOID profileBuffer = NULL;
ULONG profileBufferSize = 0;
LUID loginId;
HANDLE token = NULL;
QUOTA_LIMITS quotaLimits;
NTSTATUS subStatus = 0;
status = LsaLogonUser(
hLSA,
&originName,
Interactive,
authPackage,
authBuffer,
authBufferSize,
0,
&tokenSource,
&profileBuffer,
&profileBufferSize,
&loginId,
&token,
&quotaLimits,
&subStatus);
if (status != 0)
{
NTSTATUS winError = LsaNtStatusToWinError(status);
LLLOG(L"Error calling LsaLogonUser. Error code: " + std::to_wstring(winError));
return;
}
LLLOG(L"Success!");
LsaFreeReturnBuffer(profileBuffer);
CloseHandle(token);
HeapFree(GetProcessHeap(), 0, authBuffer);
LLLOG(L"Cleanup complete.");
return;
}
//size will be set to the size of the structure created
PVOID CreateNtlmLogonStructure(std::wstring domain, std::wstring username, std::wstring password, DWORD* size)
{
size_t wcharSize = sizeof(wchar_t);
size_t totalSize = sizeof(MSV1_0_INTERACTIVE_LOGON) + ((domain.length() + username.length() + password.length()) * wcharSize);
MSV1_0_INTERACTIVE_LOGON* ntlmLogon = (PMSV1_0_INTERACTIVE_LOGON)(new BYTE[totalSize]);
size_t offset = sizeof(MSV1_0_INTERACTIVE_LOGON);
ntlmLogon->MessageType = MsV1_0InteractiveLogon;
offset += WriteUnicodeString(domain, &(ntlmLogon->LogonDomainName), ntlmLogon, offset);
offset += WriteUnicodeString(username, &(ntlmLogon->UserName), ntlmLogon, offset);
offset += WriteUnicodeString(password, &(ntlmLogon->Password), ntlmLogon, offset);
*size = (DWORD)totalSize;
return ntlmLogon;
}
size_t WriteUnicodeString(std::wstring str, UNICODE_STRING* uniStr, PVOID baseAddress, size_t offset)
{
const wchar_t* buffer = str.c_str();
size_t size = str.length() * sizeof(wchar_t);
uniStr->Length = (USHORT)size;
uniStr->MaximumLength = (USHORT)size;
uniStr->Buffer = (PWSTR)((UINT_PTR)baseAddress + offset);
memcpy((PVOID)((UINT_PTR)baseAddress + offset), str.c_str(), size);
return size;
}

How to get all (non-disabled) user SIDs via Windows API?

I'm looking for a way to retrieve all user SIDs on a system via the Windows API.
Retrieving all user SIDs can be done with via wmic useraccount get sid. Is there a way of getting this information via the Windows API instead?
Additionally, the wmic command returns the SIDs of all accounts, including disabled accounts - wmic useraccount get disabled,sid will show which accounts are disabled. It would be a bonus if a solution could advise on how to retrieve the SIDs of accounts that are not disabled, but this is not crucial.
You could use the function:
NET_API_STATUS NET_API_FUNCTION NetUserEnum(
LPCWSTR servername,
DWORD level,
DWORD filter,
LPBYTE *bufptr,
DWORD prefmaxlen,
LPDWORD entriesread,
LPDWORD totalentries,
PDWORD resume_handle
);
with servername = NULL to enumerate local computer accounts, then use:
BOOL LookupAccountNameW(
LPCWSTR lpSystemName,
LPCWSTR lpAccountName,
PSID Sid,
LPDWORD cbSid,
LPWSTR ReferencedDomainName,
LPDWORD cchReferencedDomainName,
PSID_NAME_USE peUse
);
to retrieve SID's.
Refer to https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuserenum and https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamew for details and examples.
In function NetUserEnum, setting the parameter level=1 will return detailed information about user accounts, and the bufptr parameter will point to an array of USER_INFO_1 structures.
Examining the member usri1_flags of structure USER_INFO_1 with mask UF_ACCOUNTDISABLE give the status of account.
Following RbMm comment, note that specifying in function NetUserEnum the parameter level=3, the bufptr parameter will point to an array of USER_INFO_3 structures, that contains user RID's.
The member usri3_user_id contains the relative ID (RID) of the user, and the member usri3_primary_group_id contains the RID of the Primary Global Group for the user. Using these values you don't need to call LookupAccountNameW.
The efficiency is boosted using suggestions from RbMm in the comments below.
There are several ways.
A simple one is with NetQueryDisplayInformation
Test sample (Windows 10, VS 2015) =>
NET_API_STATUS NetStatus;
DWORD dwIndex = 0;
DWORD dwEntriesRequested = 0xFFFFFFFF;
DWORD dwPreferredMaximumLength = 0xFFFFFFFF;
DWORD dwReturnedEntryCount;
PVOID pNDU = NULL;
do {
NetStatus = NetQueryDisplayInformation(NULL, 1, dwIndex, dwEntriesRequested, dwPreferredMaximumLength, &dwReturnedEntryCount, &pNDU);
if (NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA)
break;
for (int i = 0; i < dwReturnedEntryCount; i++)
{
PNET_DISPLAY_USER NetDisplayUser = (PNET_DISPLAY_USER)(((LPBYTE)pNDU) + sizeof(NET_DISPLAY_USER) * i);
PSID pSID = ConvertNameToSID(NetDisplayUser->usri1_name);
LPWSTR pszSid = NULL;
ConvertSidToStringSid(pSID, &pszSid);
BOOL bIsAccountDisabled = ((NetDisplayUser->usri1_flags & UF_ACCOUNTDISABLE) != 0) ? TRUE : FALSE;
WCHAR wsBuffer[MAX_PATH];
wsprintf(wsBuffer, L"%4.4ld %-20.20ws SID : %ws - Disabled : %ws - Comment : %ws\n",
NetDisplayUser->usri1_next_index,
NetDisplayUser->usri1_name,
pszSid,
(bIsAccountDisabled ? L"True" : L"False"),
NetDisplayUser->usri1_comment
);
LocalFree(pSID);
OutputDebugString(wsBuffer);
dwIndex = NetDisplayUser->usri1_next_index;
}
NetApiBufferFree(pNDU);
} while (NetStatus == ERROR_MORE_DATA);
PSID ConvertNameToSID(LPTSTR lpszName)
{
WCHAR wszDomainName[256];
DWORD dwSizeDomain = sizeof(wszDomainName) / sizeof(TCHAR);
DWORD dwSizeSid = 0;
SID_NAME_USE sidName;
LookupAccountName(NULL, lpszName, NULL, &dwSizeSid, wszDomainName, &dwSizeDomain, &sidName);
PSID pSid;
pSid = (PSID)LocalAlloc(LPTR, dwSizeSid);
LookupAccountName(NULL, lpszName, pSid, &dwSizeSid, wszDomainName, &dwSizeDomain, &sidName);
return pSid;
}
for enumerate user accounts in SAM (Security Account Manager) database we can use or NetQueryDisplayInformation (more fast) or NetUserEnum (if we need more detail user information). or SAM api (fastest, include ntsam.h and link with samlib.lib )
note that if we have user (RID) we not need use LookupAccountName - this is very not efficient in this case (many heavy remote calls internal - LsaOpenPolicy, LsaLookupNames2, LsaClose . internal LsaLookupNames2 use anyway SAM api SamLookupNamesInDomain).
really all what we need - first get domain SID and than append user RID to it. get domain SID we can by LsaQueryInformationPolicy with PolicyAccountDomainInformation for SID of the account domain (computer) - always exist and with PolicyDnsDomainInformation or PolicyPrimaryDomainInformation for get SID of the primary domain (exist only if computer part of Domain)
void PrintUsersInDomain(PUNICODE_STRING ServerName, PSID DomainSid)
{
PWSTR szServerName = 0;
if (ServerName)
{
if (ULONG Length = ServerName->Length)
{
szServerName = ServerName->Buffer;
// if not null terminated
if (Length + sizeof(WCHAR) < ServerName->MaximumLength || *(PWSTR)((PBYTE)szServerName + Length))
{
szServerName = (PWSTR)alloca(Length + sizeof(WCHAR));
memcpy(szServerName, ServerName->Buffer, Length);
*(PWSTR)((PBYTE)szServerName + Length) = 0;
}
}
}
UCHAR SubAuthorityCount = *GetSidSubAuthorityCount(DomainSid);
ULONG DestinationSidLength = GetSidLengthRequired(SubAuthorityCount + 1);
PSID UserSid = alloca(DestinationSidLength);
CopySid(DestinationSidLength, UserSid, DomainSid);
++*GetSidSubAuthorityCount(UserSid);
PULONG pRid = GetSidSubAuthority(UserSid, SubAuthorityCount);
PVOID Buffer;
ULONG Index = 0, ReturnedEntryCount;
NET_API_STATUS status;
do
{
switch (status = NetQueryDisplayInformation(szServerName, 1, Index,
64, MAX_PREFERRED_LENGTH, &ReturnedEntryCount, &Buffer))
{
case NOERROR:
case ERROR_MORE_DATA:
if (ReturnedEntryCount)
{
PNET_DISPLAY_USER pndu = (PNET_DISPLAY_USER)Buffer;
do
{
//if (!(pndu->usri1_flags & UF_ACCOUNTDISABLE))
{
*pRid = pndu->usri1_user_id;
PWSTR szSid;
if (ConvertSidToStringSidW(UserSid, &szSid))
{
DbgPrint("\t[%08x] %S %S\n", pndu->usri1_flags, pndu->usri1_name, szSid);
LocalFree(szSid);
}
}
Index = pndu->usri1_next_index;
} while (pndu++, --ReturnedEntryCount);
}
NetApiBufferFree(Buffer);
}
} while (status == ERROR_MORE_DATA);
}
void PrintUsersInDomain_fast(PUNICODE_STRING ServerName, PSID DomainSid)
{
SAM_HANDLE ServerHandle, DomainHandle = 0;
//SAM_SERVER_ENUMERATE_DOMAINS|SAM_SERVER_LOOKUP_DOMAIN
NTSTATUS status = SamConnect(ServerName, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, 0);
DbgPrint("SamConnect(%wZ) = %x\n", ServerName, status);
if (0 <= status)
{
status = SamOpenDomain(ServerHandle, DOMAIN_READ|DOMAIN_EXECUTE, DomainSid, &DomainHandle);
SamCloseHandle(ServerHandle);
}
if (0 <= status)
{
UCHAR SubAuthorityCount = *GetSidSubAuthorityCount(DomainSid);
ULONG DestinationSidLength = GetSidLengthRequired(SubAuthorityCount + 1);
PSID UserSid = alloca(DestinationSidLength);
CopySid(DestinationSidLength, UserSid, DomainSid);
++*GetSidSubAuthorityCount(UserSid);
PULONG pRid = GetSidSubAuthority(UserSid, SubAuthorityCount);
PVOID Buffer;
ULONG Index = 0, TotalAvailable, TotalReturned, ReturnedEntryCount;
do
{
if (0 <= (status = SamQueryDisplayInformation(DomainHandle,
DomainDisplayUser,
Index,
2,
0x10000,
&TotalAvailable,
&TotalReturned,
&ReturnedEntryCount,
&Buffer)))
{
if (ReturnedEntryCount)
{
PSAM_DISPLAY_USER psdu = (PSAM_DISPLAY_USER)Buffer;
do
{
//if (!(psdu->AccountControl & USER_ACCOUNT_DISABLED))
{
*pRid = psdu->Rid;
PWSTR szSid;
if (ConvertSidToStringSidW(UserSid, &szSid))
{
DbgPrint("\t[%08x] %wZ %S\n", psdu->AccountControl, &psdu->AccountName, szSid);
LocalFree(szSid);
}
}
Index = psdu->Index;
} while (psdu++, --ReturnedEntryCount);
}
SamFreeMemory(Buffer);
}
} while (status == STATUS_MORE_ENTRIES);
SamCloseHandle(DomainHandle);
}
}
void PrintUsers()
{
LSA_HANDLE PolicyHandle;
LSA_OBJECT_ATTRIBUTES ObjectAttributes = { sizeof(ObjectAttributes) };
NTSTATUS status;
if (0 <= (status = LsaOpenPolicy(0, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &PolicyHandle)))
{
union {
PVOID buf;
PPOLICY_DNS_DOMAIN_INFO pddi;
PPOLICY_ACCOUNT_DOMAIN_INFO padi;
};
if (0 <= LsaQueryInformationPolicy(PolicyHandle, PolicyAccountDomainInformation, &buf))
{
DbgPrint("DomainName=<%wZ>\n", &padi->DomainName);
if (padi->DomainSid)
{
PrintUsersInDomain_fast(&padi->DomainName, padi->DomainSid);
PrintUsersInDomain(&padi->DomainName, padi->DomainSid);
}
LsaFreeMemory(buf);
}
if (0 <= LsaQueryInformationPolicy(PolicyHandle, PolicyDnsDomainInformation, &buf))
{
DbgPrint("DomainName=<%wZ>\n", &pddi->Name);
if (pddi->Sid)
{
PrintUsersInDomain_fast(&pddi->Name, pddi->Sid);
PrintUsersInDomain(&pddi->Name, pddi->Sid);
}
LsaFreeMemory(buf);
}
LsaClose(PolicyHandle);
}
}
typedef struct SAM_DISPLAY_USER {
ULONG Index;
ULONG Rid;
ULONG AccountControl; /* User account control bits */
UNICODE_STRING AccountName;
UNICODE_STRING AdminComment;
UNICODE_STRING FullName;
} *PSAM_DISPLAY_USER;

Windows biometric service runs SensorAdapterStartCapture in a loop when calling WinBioCaptureSample

I am implementing a Windows Biometric Driver using the umdf sample from github.
When I call WinBioCaptureSample the next plugin's methods run in a loop.
SensorAdapterClearContext
EngineAdapterClearContext
SensorAdapterStartCapture
SensorAdapterFinishCapture
I used TraceView to debug my driver and it shows the next trace messages when is stuck in a loop.
00000001 driver 352840 439560 1 1 04\05\2018-16:46:13:12 CBiometricDevice::OnGetSensorStatus Called.
00000002 driver 352840 439560 1 2 04\05\2018-16:46:13:12 CBiometricDevice::OnGetAttributes Called.
00000003 driver 352840 439560 1 3 04\05\2018-16:46:13:12 CBiometricDevice::OnCaptureDataBuffer too small - must be at least 0x18.
00000004 driver 352840 439560 1 4 04\05\2018-16:46:13:12 CBiometricDevice::OnCaptureData Called.
00000005 driver 352840 439560 4 5 04\05\2018-16:46:13:28 CBiometricDevice::OnGetSensorStatus Called.
00000006 driver 352840 439560 1 6 04\05\2018-16:46:13:29 CBiometricDevice::OnCaptureDataBuffer too small - must be at least 0x18.
00000007 driver 352840 439560 1 7 04\05\2018-16:46:13:29 CBiometricDevice::OnCaptureData Called.
00000008 driver 352840 439560 1 8 04\05\2018-16:46:13:30 CBiometricDevice::OnGetSensorStatus Called.
00000009 driver 352840 439560 4 9 04\05\2018-16:46:13:30 CBiometricDevice::OnCaptureDataBuffer too small - must be at least 0x18.
00000010 driver 352840 439560 1 10 04\05\2018-16:46:13:31 CBiometricDevice::OnCaptureData Called.
...
The method CBiometricDevice::OnGetSensorStatus always returns WINBIO_SENSOR_READY
diagnostics->WinBioHresult = S_OK;
diagnostics->SensorStatus = WINBIO_SENSOR_READY;
MyRequest.SetInformation(diagnostics->PayloadSize);
MyRequest.SetCompletionHr(S_OK);
Next is the method CBiometricDevice::OnCaptureData
DWORD WINAPI
CaptureSleepThread(
LPVOID lpParam
)
{
CBiometricDevice *device = (CBiometricDevice *) lpParam;
PCAPTURE_SLEEP_PARAMS sleepParams = device->GetCaptureSleepParams();
if (sleepParams->SleepValue > 60)
{
sleepParams->SleepValue = 60;
}
Sleep(sleepParams->SleepValue * 1000);
UCHAR szBuffer[] = { 0x08, 0x01, 0x00, 0x02 };
ULONG cbRead = 4;
sleepParams->captureData->WinBioHresult = S_OK;
sleepParams->captureData->SensorStatus = WINBIO_SENSOR_ACCEPT;
sleepParams->captureData->RejectDetail = 0;
sleepParams->captureData->CaptureData.Size = cbRead;
RtlCopyMemory(sleepParams->captureData->CaptureData.Data, szBuffer, cbRead);
device->CompletePendingRequest(sleepParams->Hr, sleepParams->Information);
return 0;
}
CBiometricDevice::OnCaptureData(
_Inout_ IWDFIoRequest *FxRequest
)
{
ULONG controlCode = 0;
PWINBIO_CAPTURE_PARAMETERS captureParams = NULL;
SIZE_T inputBufferSize = 0;
PWINBIO_CAPTURE_DATA captureData = NULL;
SIZE_T outputBufferSize = 0;
bool requestPending = false;
EnterCriticalSection(&m_RequestLock);
if (m_PendingRequest == NULL)
{
if (m_SleepThread != INVALID_HANDLE_VALUE)
{
LeaveCriticalSection(&m_RequestLock);
// TODO: Add code to signal thread to exit.
WaitForSingleObject(m_SleepThread, INFINITE);
CloseHandle(m_SleepThread);
m_SleepThread = INVALID_HANDLE_VALUE;
EnterCriticalSection(&m_RequestLock);
}
if (m_PendingRequest == NULL)
{
m_PendingRequest = FxRequest;
m_PendingRequest->MarkCancelable(this);
}
else
{
requestPending = true;
}
}
else
{
requestPending = true;
}
LeaveCriticalSection(&m_RequestLock);
if (requestPending)
{
FxRequest->Complete(WINBIO_E_DATA_COLLECTION_IN_PROGRESS);
return;
}
GetIoRequestParams(FxRequest,
&controlCode,
(PUCHAR *)&captureParams,
&inputBufferSize,
(PUCHAR *)&captureData,
&outputBufferSize);
if (inputBufferSize < sizeof (WINBIO_CAPTURE_PARAMETERS))
{
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Invalid argument(s).");
CompletePendingRequest(E_INVALIDARG, 0);
return;
}
if (outputBufferSize < sizeof(DWORD))
{
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Output buffer NULL or too small to return size information.");
CompletePendingRequest(E_INVALIDARG, 0);
return;
}
if (outputBufferSize < sizeof (WINBIO_CAPTURE_DATA))
{
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Buffer too small - must be at least 0x%x.", sizeof (WINBIO_CAPTURE_DATA));
DWORD cbSize = 262144;//obtained from MAXIMUM_TRANSFER_SIZE policy of WinUsb
captureData->PayloadSize = (DWORD) sizeof(WINBIO_CAPTURE_DATA) + cbSize;
CompletePendingRequest(S_OK, sizeof(DWORD));
return;
}
RtlZeroMemory(captureData, outputBufferSize);
captureData->PayloadSize = (DWORD) outputBufferSize;// (DWORD) sizeof(WINBIO_CAPTURE_DATA);
captureData->WinBioHresult = WINBIO_E_NO_CAPTURE_DATA;
captureData->SensorStatus = WINBIO_SENSOR_FAILURE;
captureData->RejectDetail= 0;
captureData->CaptureData.Size = 0;
if (captureParams->Purpose == WINBIO_NO_PURPOSE_AVAILABLE)
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_PURPOSE;
}
else if ((captureParams->Format.Type != WINBIO_ANSI_381_FORMAT_TYPE) ||
(captureParams->Format.Owner != WINBIO_ANSI_381_FORMAT_OWNER))
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_DATA_FORMAT;
}
else if (captureParams->Flags != WINBIO_DATA_FLAG_RAW)
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_DATA_TYPE;
}
struct _WINUSB_PIPE_INFORMATION InputPipeInfo, OutputPipeInfo;
m_pIUsbInputPipe->GetInformation(&InputPipeInfo);
m_pIUsbOutputPipe->GetInformation(&OutputPipeInfo);
m_SleepParams.PipeInId = InputPipeInfo.PipeId;
m_SleepParams.PipeOutId = OutputPipeInfo.PipeId;
m_SleepParams.hDeviceHandle = m_pIUsbInterface->GetWinUsbHandle();
m_SleepParams.cbSize = (DWORD) outputBufferSize;
m_SleepParams.SleepValue = 5;
m_SleepParams.Hr = S_OK;
m_SleepParams.Information = captureData->PayloadSize;
m_SleepParams.captureData = captureData;
m_SleepThread = CreateThread(NULL, // default security attributes
0, // use default stack size
CaptureSleepThread, // thread function name
this, // argument to thread function
0, // use default creation flags
NULL); // returns the thread identifier
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC! Called.");
}
The methods SensorAdapterStartCapture and SensorAdapterFinishCapture returns S_OK
static HRESULT
WINAPI
SensorAdapterStartCapture(
_Inout_ PWINBIO_PIPELINE Pipeline,
_In_ WINBIO_BIR_PURPOSE Purpose,
_Out_ LPOVERLAPPED *Overlapped
)
{
HRESULT hr = S_OK;
WINBIO_SENSOR_STATUS sensorStatus = WINBIO_SENSOR_FAILURE;
WINBIO_CAPTURE_PARAMETERS captureParameters = { 0 };
BOOL result = TRUE;
DWORD bytesReturned = 0;
// Verify that pointer arguments are not NULL.
if (!ARGUMENT_PRESENT(Pipeline) ||
!ARGUMENT_PRESENT(Purpose) ||
!ARGUMENT_PRESENT(Overlapped))
{
return E_POINTER;
}
// Retrieve the context from the pipeline.
PWINIBIO_SENSOR_CONTEXT sensorContext =
(PWINIBIO_SENSOR_CONTEXT)Pipeline->SensorContext;
// Verify the state of the pipeline.
if (sensorContext == NULL ||
Pipeline->SensorHandle == INVALID_HANDLE_VALUE)
{
return WINBIO_E_INVALID_DEVICE_STATE;
}
*Overlapped = NULL;
// Synchronously retrieve the status.
hr = SensorAdapterQueryStatus(Pipeline, &sensorStatus);
if (FAILED(hr))
{
return hr;
}
// Determine whether the sensor requires calibration.
//if (sensorStatus == WINBIO_SENSOR_NOT_CALIBRATED)
//{
// Call a custom function that sends IOCTLs to
// the sensor to calibrate it. This operation is
// synchronous.
//hr = _SensorAdapterCalibrate(Pipeline);
// Retrieve the status again to determine whether the
// sensor is ready.
//if (SUCCEEDED(hr))
//{
// hr = SensorAdapterQueryStatus(Pipeline, &sensorStatus);
//}
//if (FAILED(hr))
//{
// return hr;
//}
//}
if (sensorStatus == WINBIO_SENSOR_BUSY)
{
return WINBIO_E_DEVICE_BUSY;
}
if (sensorStatus != WINBIO_SENSOR_READY)
{
return WINBIO_E_INVALID_DEVICE_STATE;
}
// Determine whether the data format has been previously determined.
// If it has not, find a format supported by both the engine and
// the sensor.
if ((sensorContext->Format.Owner == 0) &&
(sensorContext->Format.Type == 0))
{
// Retrieve the format preferred by the engine.
hr = Pipeline->EngineInterface->QueryPreferredFormat(
Pipeline,
&sensorContext->Format,
&sensorContext->VendorFormat
);
if (SUCCEEDED(hr))
{
// Call a private function that queries the sensor driver
// and attaches an attribute array to the sensor context.
// This operation is synchronous.
hr = _SensorAdapterGetAttributes(Pipeline);
}
if (SUCCEEDED(hr))
{
// Search the sensor attributes array for the format
// preferred by the engine adapter.
DWORD i = 0;
for (i = 0; i < sensorContext->AttributesBuffer->SupportedFormatEntries; i++)
{
if ((sensorContext->AttributesBuffer->SupportedFormat[i].Owner == sensorContext->Format.Owner) &&
(sensorContext->AttributesBuffer->SupportedFormat[i].Type == sensorContext->Format.Type))
{
break;
}
}
if (i == sensorContext->AttributesBuffer->SupportedFormatEntries)
{
// No match was found. Use the default.
sensorContext->Format.Owner = WINBIO_ANSI_381_FORMAT_OWNER;
sensorContext->Format.Type = WINBIO_ANSI_381_FORMAT_TYPE;
}
}
else
{
return hr;
}
}
// Set up the parameter-input block needed for the IOCTL.
captureParameters.PayloadSize = sizeof(WINBIO_CAPTURE_PARAMETERS);
captureParameters.Purpose = Purpose;
captureParameters.Format.Owner = sensorContext->Format.Owner;
captureParameters.Format.Type = sensorContext->Format.Type;
CopyMemory(&captureParameters.VendorFormat, &sensorContext->VendorFormat, sizeof(WINBIO_UUID));
captureParameters.Flags = WINBIO_DATA_FLAG_RAW;
// Determine whether a buffer has already been allocated for this sensor.
if (sensorContext->CaptureBuffer == NULL)
{
DWORD allocationSize = 0;
sensorContext->CaptureBufferSize = 0;
// This sample assumes that the sensor driver returns
// a fixed-size DWORD buffer containing the required
// size of the capture buffer if it receives a buffer
// that is smaller than sizeof(WINBIO_CAPTURE_DATA).
//
// Call the driver with a small buffer to get the
// allocation size required for this sensor.
//
// Because this operation is asynchronous, you must block
// and wait for it to complete.
result = DeviceIoControl(
Pipeline->SensorHandle,
IOCTL_BIOMETRIC_CAPTURE_DATA,
&captureParameters,
sizeof(WINBIO_CAPTURE_PARAMETERS),
&allocationSize,
sizeof(DWORD),
&bytesReturned,
&sensorContext->Overlapped
);
if (!result && GetLastError() == ERROR_IO_PENDING)
{
SetLastError(ERROR_SUCCESS);
result = GetOverlappedResult(
Pipeline->SensorHandle,
&sensorContext->Overlapped,
&bytesReturned,
TRUE
);
}
if (!result || bytesReturned != sizeof(DWORD))
{
// An error occurred.
hr = _AdapterGetHresultFromWin32(GetLastError());
return hr;
}
// Make sure that you allocate at least the minimum buffer
// size needed to get the payload structure.
if (allocationSize < sizeof(WINBIO_CAPTURE_DATA))
{
allocationSize = sizeof(WINBIO_CAPTURE_DATA);
}
// Allocate the buffer.
sensorContext->CaptureBuffer = (PWINBIO_CAPTURE_DATA)_AdapterAlloc(allocationSize);
if (!sensorContext->CaptureBuffer)
{
sensorContext->CaptureBufferSize = 0;
return E_OUTOFMEMORY;
}
sensorContext->CaptureBufferSize = allocationSize;
}
else
{
// The buffer has already been allocated. Clear the buffer contents.
SensorAdapterClearContext(Pipeline);
}
// Send the capture request. Because this is an asynchronous operation,
// the IOCTL call will return immediately regardless of
// whether the I/O has completed.
result = DeviceIoControl(
Pipeline->SensorHandle,
IOCTL_BIOMETRIC_CAPTURE_DATA,
&captureParameters,
sizeof(WINBIO_CAPTURE_PARAMETERS),
sensorContext->CaptureBuffer,
(DWORD)sensorContext->CaptureBufferSize,
&bytesReturned,
&sensorContext->Overlapped
);
if (result ||
(!result && GetLastError() == ERROR_IO_PENDING))
{
*Overlapped = &sensorContext->Overlapped;
return S_OK;
}
else
{
hr = _AdapterGetHresultFromWin32(GetLastError());
return hr;
}
}
static HRESULT
WINAPI
SensorAdapterFinishCapture(
_Inout_ PWINBIO_PIPELINE Pipeline,
_Out_ PWINBIO_REJECT_DETAIL RejectDetail
)
{
HRESULT hr = S_OK;
//WINBIO_SENSOR_STATUS sensorStatus = WINBIO_SENSOR_FAILURE;
WINBIO_CAPTURE_PARAMETERS captureParameters = { 0 };
BOOL result = TRUE;
DWORD bytesReturned = 0;
// Verify that pointer arguments are not NULL.
if (!ARGUMENT_PRESENT(Pipeline) ||
!ARGUMENT_PRESENT(RejectDetail))
{
return E_POINTER;
}
// Retrieve the context from the pipeline.
PWINIBIO_SENSOR_CONTEXT sensorContext =
(PWINIBIO_SENSOR_CONTEXT)Pipeline->SensorContext;
// Verify the state of the pipeline.
if (sensorContext == NULL ||
Pipeline->SensorHandle == INVALID_HANDLE_VALUE)
{
return WINBIO_E_INVALID_DEVICE_STATE;
}
// Initialize the RejectDetail argument.
*RejectDetail = 0;
// Wait for I/O completion. This sample assumes that the I/O operation was
// started using the code example shown in the SensorAdapterStartCapture
// documentation.
SetLastError(ERROR_SUCCESS);
result = GetOverlappedResult(
Pipeline->SensorHandle,
&sensorContext->Overlapped,
&bytesReturned,
TRUE
);
if (!result)
{
// There was an I/O error.
return _AdapterGetHresultFromWin32(GetLastError());
}
if (bytesReturned == sizeof(DWORD))
{
// The buffer is not large enough. This can happen if a device needs a
// bigger buffer depending on the purpose. Allocate a larger buffer and
// force the caller to reissue their I/O request.
DWORD allocationSize = sensorContext->CaptureBuffer->PayloadSize;
// Allocate at least the minimum buffer size needed to retrieve the
// payload structure.
if (allocationSize < sizeof(WINBIO_CAPTURE_DATA))
{
allocationSize = sizeof(WINBIO_CAPTURE_DATA);
}
// Free the old buffer and allocate a new one.
_AdapterRelease(sensorContext->CaptureBuffer);
sensorContext->CaptureBuffer = NULL;
sensorContext->CaptureBuffer =
(PWINBIO_CAPTURE_DATA)_AdapterAlloc(allocationSize);
if (sensorContext->CaptureBuffer == NULL)
{
sensorContext->CaptureBufferSize = 0;
return E_OUTOFMEMORY;
}
sensorContext->CaptureBufferSize = allocationSize;
return WINBIO_E_BAD_CAPTURE;
}
// Normalize the status value before sending it back to the biometric service.
if (sensorContext->CaptureBuffer != NULL &&
sensorContext->CaptureBufferSize >= sizeof(WINBIO_CAPTURE_DATA))
{
switch (sensorContext->CaptureBuffer->SensorStatus)
{
case WINBIO_SENSOR_ACCEPT:
{
// The capture was acceptable.
DWORD cbRead = sensorContext->CaptureBuffer->CaptureData.Size;
UCHAR * data = sensorContext->CaptureBuffer->CaptureData.Data;
//wprintf(L"%d ", cbRead);
break;
}
case WINBIO_SENSOR_REJECT:
// The capture was not acceptable. Overwrite the WinBioHresult value
// in case it has not been properly set.
sensorContext->CaptureBuffer->WinBioHresult = WINBIO_E_BAD_CAPTURE;
break;
case WINBIO_SENSOR_BUSY:
// The device is busy. Reset the WinBioHresult value in case it
// has not been properly set.
sensorContext->CaptureBuffer->WinBioHresult = WINBIO_E_DEVICE_BUSY;
break;
case WINBIO_SENSOR_READY:
case WINBIO_SENSOR_NOT_CALIBRATED:
case WINBIO_SENSOR_FAILURE:
default:
// There has been a device failure. Reset the WinBioHresult value
// in case it has not been properly set.
sensorContext->CaptureBuffer->WinBioHresult = WINBIO_E_INVALID_DEVICE_STATE;
break;
}
*RejectDetail = sensorContext->CaptureBuffer->RejectDetail;
hr = sensorContext->CaptureBuffer->WinBioHresult;
}
else
{
// The buffer is not large enough or the buffer pointer is NULL.
hr = WINBIO_E_INVALID_DEVICE_STATE;
}
return hr;
}
I used the next code from this github project
HRESULT CaptureSample()
{
HRESULT hr = S_OK;
WINBIO_SESSION_HANDLE sessionHandle = NULL;
WINBIO_UNIT_ID unitId = 0;
WINBIO_REJECT_DETAIL rejectDetail = 0;
PWINBIO_BIR sample = NULL;
SIZE_T sampleSize = 0;
// Connect to the system pool.
hr = WinBioOpenSession(
WINBIO_TYPE_FINGERPRINT, // Service provider
WINBIO_POOL_SYSTEM, // Pool type
WINBIO_FLAG_RAW, // Access: Capture raw data //To call WinBioCaptureSample function successfully, you must open the session handle by specifying WINBIO_FLAG_RAW
//WINBIO_FLAG_RAW: The client application captures raw biometric data using WinBioCaptureSample.
NULL, // Array of biometric unit IDs //NULL if the PoolType parameter is WINBIO_POOL_SYSTEM
0, // Count of biometric unit IDs//zero if the PoolType parameter is WINBIO_POOL_SYSTEM.
WINBIO_DB_DEFAULT, // Default database
&sessionHandle // [out] Session handle
);
if(FAILED(hr))
{
std::cout << "WinBioOpenSession failed. hr = 0x" << std::hex << hr << std::dec << "\n";
if(sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
if(sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
return hr;
}
// Capture a biometric sample.
std::cout << "Calling WinBioCaptureSample - Swipe sensor...\n";
hr = WinBioCaptureSample(
sessionHandle,
WINBIO_NO_PURPOSE_AVAILABLE,
WINBIO_DATA_FLAG_RAW,//WINBIO_DATA_FLAG_RAW
&unitId,
&sample,
&sampleSize,
&rejectDetail
);
if(FAILED(hr))
{
if(hr == WINBIO_E_BAD_CAPTURE)
std:: cout << "Bad capture; reason: " << rejectDetail << "\n";
else
std::cout << "WinBioCaptureSample failed.hr = 0x" << std::hex << hr << std::dec << "\n";
if(sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
if(sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
return hr;
}
std::cout << "Swipe processed - Unit ID: " << unitId << "\n";
std::cout << "Captured " << sampleSize << " bytes.\n";
if(sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
if(sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
return hr;
}
int main()
{
EnumerateSensors();
CreateDirectoryA("data", NULL);
CaptureSample();
}
Sometimes my code is stuck in a loop and other times is not :(
Any hint is welcomed Thanks.
It seems I might have found a temporary solution to my problem.
First what I did is to change the sensor mode from basic to advanced.
[DriverPlugInAddReg]
HKR,WinBio\Configurations,DefaultConfiguration,,"0"
HKR,WinBio\Configurations\0,SensorMode,0x10001,2 ; Basic - 1, Advanced - 2
And the loop won't start
But I also noticed If I comment the call to the Sleep method inside CaptureSleepThread the loop starts again.
My guess is that Windows Biometric Service is expecting the method CaptureSleepThread to take some time to be considered successful but if the method ends very fast it is considered to be failed despite of the successful responses S_OK and WINBIO_SENSOR_ACCEPT and Windows Biometric Service will retry again calling to SensorAdapterStartCapture causing a loop.
DWORD WINAPI
CaptureSleepThread(
LPVOID lpParam
)
{
CBiometricDevice *device = (CBiometricDevice *) lpParam;
PCAPTURE_SLEEP_PARAMS sleepParams = device->GetCaptureSleepParams();
//
// 1 minute or half a minute delay is the trick.
//
if (sleepParams->SleepValue > 60)
{
sleepParams->SleepValue = 60;
}
Sleep(sleepParams->SleepValue * 1000);
UCHAR szBuffer[] = { 0x08, 0x01, 0x00, 0x02 };
ULONG cbRead = 4;
sleepParams->captureData->WinBioHresult = S_OK;
sleepParams->captureData->SensorStatus = WINBIO_SENSOR_ACCEPT;
sleepParams->captureData->RejectDetail = 0;
sleepParams->captureData->CaptureData.Size = cbRead;
RtlCopyMemory(sleepParams->captureData->CaptureData.Data, szBuffer, cbRead);
device->CompletePendingRequest(sleepParams->Hr, sleepParams->Information);
return 0;
}
Other things I observed can cause a loop is when the call fails and not proper response is set.
//No delay is going to cause a loop
//Sleep(sleepParams->SleepValue * 1000);
//zeroes buffer is considered failed
UCHAR szBuffer[] = { 0x00, 0x00, 0x00, 0x00 };
//zero size is a failed read
ULONG cbRead = 0;
//returning other than S_FALSE or S_OK will cause a loop
sleepParams->captureData->WinBioHresult = S_FALSE;
sleepParams->captureData->SensorStatus = WINBIO_SENSOR_ACCEPT;
sleepParams->captureData->RejectDetail = 0;
sleepParams->captureData->CaptureData.Size = cbRead;
//It is going to fail if CaptureData.Data is empty
//RtlCopyMemory(sleepParams->captureData->CaptureData.Data, szBuffer, cbRead);
Also attaching a debugger like WinDbg or Visual Studio will cause a loop so is better to debug using only Trace messages and TraceView tool and attach a debugger when necessary and a loop will be expected in this case, just ignore it.

Enumerating process handles, weird issue

I scan for opened handles to my process and print them in the console.
I start my process
I attach cheat engine
I run the enumeration of opened handles
I see which process has a handle to my process
The weird issue at this point is as follows, check the code:
array<Accessor^>^ AntiCheat::ScanHandles()
{
List<Accessor^>^ accessorList = gcnew List<Accessor^>();
if (!EnableDebugPrivilege(true))
printf("EnableDebugPrivilege failed: %d\n", GetLastError());
tNtQuerySystemInformation oNtQuerySystemInformation = (tNtQuerySystemInformation)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQuerySystemInformation");
PSYSTEM_HANDLE_INFORMATION handleInfo = new SYSTEM_HANDLE_INFORMATION;
SYSTEM_INFORMATION_CLASS infoClass = (SYSTEM_INFORMATION_CLASS)16; // SystemHandleInformation
DWORD size = sizeof(SYSTEM_HANDLE_INFORMATION);
DWORD needed = 0;
NTSTATUS status = oNtQuerySystemInformation(infoClass, handleInfo, size, &needed);
while (!NT_SUCCESS(status))
{
if (needed == 0)
return nullptr;
// The previously supplied buffer wasn't enough.
delete handleInfo;
size = needed + 1024;
handleInfo = (PSYSTEM_HANDLE_INFORMATION)new BYTE[size];
status = oNtQuerySystemInformation(infoClass, handleInfo, size, &needed);
}
HANDLE currentProcess = GetCurrentProcess();
DWORD currentProcessId = GetProcessId(currentProcess);
for (DWORD i = 0; i < handleInfo->dwCount; i++)
{
//printf(".");
SYSTEM_HANDLE handle = handleInfo->Handles[i];
HANDLE procHandle = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, handle.dwProcessId);
if (GetLastError() == ERROR_ACCESS_DENIED)
continue;
HANDLE dupl = 0;
if (!DuplicateHandle(procHandle, (HANDLE)handle.wValue, currentProcess, &dupl, 0, false, DUPLICATE_SAME_ACCESS))
continue;
DWORD procId = GetProcessId(dupl);
if (procId == currentProcessId)
{
printf("accessing us\n");
char processName[MAX_PATH];
GetModuleFileNameEx((HMODULE)procHandle, NULL, processName, MAX_PATH);
accessorList->Add(gcnew Accessor(gcnew String(processName), handle.GrantedAccess));
}
CloseHandle(dupl);
}
return accessorList->ToArray();
}
If I uncomment the line with printf(".");, I see 3 opened handles to my process (cheatengine). If it's commented (runs way faster), there is no opened handle. However I don't know why this affects my code. Im surprised, does anyone know why this happens? Or how to find out how to find the handles without my printf("."); line?
Another issue is: each time I call the function, the number of allocated bytes duplicates. And I don't know why.
I see logic problems with your code.
You are not ignoring array items where handle.dwProcessId equals currentProcessId, so you end up opening handles to your own process. Since you are only interested in looking for other processes, you should be ignoring items where handle.dwProcessId is equal to currentProcessId.
You are not checking if OpenProcess() fails for any reason other than ERROR_ACCESS_DENIED. Do not call GetLastError() unless OpenProcess() actually returns NULL first.
You are not closing an opened handle if DuplicateHandle() fails. And why are you duplicating each source handle just to call GetProcessId() on it? You already have their process IDs from the array, so the whole DuplicateHandle()+GetProcessId() is completely unnecessary.
You are taking the wrong approach anyway. Have a look at this discussion:
Enumerating the processes referencing an object
Use NtQuerySystemInformation with SystemInformationClass set to SystemHandleInformation. This fills in an array of SYSTEM_HANDLE_INFORMATION structures, which are defined as:
typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE_INFORMATION;
Search for the entry corresponding to the handle you opened with ProcessID equal to GetCurrentProcessId(), then find all entries with the same Object pointer.
Although the discussion shows the wrong declaration for SYSTEM_HANDLE_INFORMATION. The following article shows the correct one:
HOWTO: Enumerate handles
#define SystemHandleInformation 16
typedef NTSTATUS (NTAPI *_NtQuerySystemInformation)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
/* The following structure is actually called SYSTEM_HANDLE_TABLE_ENTRY_INFO, but SYSTEM_HANDLE is shorter. */
typedef struct _SYSTEM_HANDLE
{
ULONG ProcessId;
BYTE ObjectTypeNumber;
BYTE Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG HandleCount; /* Or NumberOfHandles if you prefer. */
SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
With that said, try something more like this:
array<Accessor^>^ AntiCheat::ScanHandles()
{
List<Accessor^>^ accessorList = gcnew List<Accessor^>();
if (!EnableDebugPrivilege(true))
printf("EnableDebugPrivilege failed: %d\n", GetLastError());
tNtQuerySystemInformation oNtQuerySystemInformation = (tNtQuerySystemInformation) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQuerySystemInformation");
DWORD currentProcessId = GetCurrentProcessId();
HANDLE currentProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, currentProcessId);
PVOID currentProcessAddr = nullptr;
DWORD size = sizeof(SYSTEM_HANDLE_INFORMATION);
DWORD needed = 0;
PSYSTEM_HANDLE_INFORMATION handleInfo = (PSYSTEM_HANDLE_INFORMATION) new BYTE[size];
SYSTEM_INFORMATION_CLASS infoClass = (SYSTEM_INFORMATION_CLASS) 16; // SystemHandleInformation
NTSTATUS status = oNtQuerySystemInformation(infoClass, handleInfo, size, &needed);
while (status == STATUS_INFO_LENGTH_MISMATCH)
{
// The previously supplied buffer wasn't enough.
delete[] handleInfo;
size += 1024;
handleInfo = (PSYSTEM_HANDLE_INFORMATION) new BYTE[size];
status = oNtQuerySystemInformation(infoClass, handleInfo, size, &needed);
}
if (status != 0)
{
delete[] handleInfo;
return nullptr;
}
for (DWORD i = 0; i < handleInfo->dwCount; i++)
{
SYSTEM_HANDLE &handle = handleInfo->Handles[i];
if ((handle.dwProcessId == currentProcessId) &&
(currentProcess == (HANDLE)handle.wValue))
{
currentProcessAddr = handle.pAddress;
break;
}
}
for (DWORD i = 0; i < handleInfo->dwCount; i++)
{
SYSTEM_HANDLE &handle = handleInfo->Handles[i];
if ((handle.dwProcessId != currentProcessId) &&
(handle.pAddress == currentProcessAddr))
{
printf("accessing us\n");
HANDLE procHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, handle.dwProcessId);
if (procHandle != 0)
{
char processName[MAX_PATH+1];
DWORD len = GetModuleFileNameEx((HMODULE)procHandle, NULL, processName, MAX_PATH);
CloseHandle(procHandle);
processName[len] = '\0';
accessorList->Add(gcnew Accessor(gcnew String(processName), handle.GrantedAccess));
}
else
accessorList->Add(gcnew Accessor(gcnew String("unknown"), handle.GrantedAccess));
}
}
CloseHandle(currentProcess);
delete[] handleInfo;
return accessorList->ToArray();
}

AddFontMemResourceEx

The following code should work to load a font from a binary resource stored in my executable to system memory according to all of the examples I have found but it is not working. "myfont" is the name of the ttf associated with IDR_FONT in the resource file.
DWORD Count ;
HRSRC Resource = FindResource(GetModuleHandle(NULL),MAKEINTRESOURCE(IDR_FONT),"BINARY") ;
DWORD Length = SizeofResource(GetModuleHandle(NULL),Resource) ;
HGLOBAL Address = LoadResource(GetModuleHandle(NULL),Resource) ;
HANDLE Handle = AddFontMemResourceEx(Address,Length,0,&Count) ;
if(Handle==0)
{
MessageBox(hWnd,"Font load failed", "Error",NULL);
}
LOGFONT logfont; //set window font
logfont.lfCharSet = DEFAULT_CHARSET;
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logfont.lfEscapement = 0;
memcpy(logfont.lfFaceName, "myfont", LF_FACESIZE);
logfont.lfHeight = 14;
logfont.lfItalic = FALSE;
logfont.lfOrientation = 0;
logfont.lfOutPrecision = OUT_TT_PRECIS;
logfont.lfQuality = PROOF_QUALITY;
logfont.lfStrikeOut = FALSE;
logfont.lfUnderline = FALSE;
logfont.lfWeight = FW_DONTCARE;
hFont = CreateFontIndirect(&logfont);
Any ideas what I am doing incorrectly?
There are two problems with your code.
You are not checking any of the API functions for failure. Most likely, your call to FindResource() is failing because "BINARY" is not a standard resource type. User-defined resources should use RCDATA instead:
HRSRC Resource = FindResource(GetModuleHandle(NULL), MAKEINTRESOURCE(IDR_FONT), RT_RCDATA);
Or maybe FONT if it is an actual standard FONT resource:
HRSRC Resource = FindResource(GetModuleHandle(NULL), MAKEINTRESOURCE(IDR_FONT), RT_FONT);
The actual name of the resource type depends on the content of the .RC file you used to add the resource to the executable, though.
The other problem, and more importatly, is that you are not actually accessing the raw data of the resource so you can pass the real font data to AddFontMemResourceEx(). You need to use LockResource() for that.
Try something more like this instead:
HANDLE AddResourceFont(LPCTSTR ResID, DWORD *Installed)
{
if (Installed) *Installed = 0;
HMODULE hMod = GetModuleHandle(NULL);
DWORD Count, ErrorCode;
HRSRC Resource = FindResource(hMod, ResID, RT_RCDATA); // or RT_FONT or whatever your actual resource type is
if (!Resource)
{
ErrorCode = GetLastError();
//...
return NULL;
}
DWORD Length = SizeofResource(hMod, Resource);
if ((Length == 0) && (GetLastError() != 0))
{
ErrorCode = GetLastError();
//...
return NULL;
}
HGLOBAL Address = LoadResource(hMod, Resource);
if (!Address)
{
ErrorCode = GetLastError();
//...
return NULL;
}
PVOID FontData = LockResource(Address);
if (!FontData)
{
ErrorCode = GetLastError();
//...
return NULL;
}
HANDLE Handle = AddFontMemResourceEx(FontData, Length, 0, &Count);
if (!Handle)
{
ErrorCode = GetLastError();
//...
return NULL;
}
if (Installed) *Installed = Count;
return Handle;
}
.
DWORD Count = 0;
HANDLE hFont = AddResourceFont(MAKEINTRESOURCE(IDR_FONT), &Count);
if (hFont)
{
//...
RemoveFontMemResourceEx(hFont);
}