Passing SCSI commands over network - c++

I want to share cdrom device over network.
On the client side I create root enumerated device (scsi bus). On the server side (where cdrom device resides) I replace device stack's FDO with my own (in other words - cdrom.sys is replaced by another driver).
Requests are redirected from client to server using windows sockets.
The format of data transferred over network (from client to server): USER_HEADER, USER_SCSI_REQUEST_BLOCK, [data to be transferred to device]
The format of data transferred over network (from server to client):
USER_HEADER, USER_SCSI_REQUEST_BLOCK, [data to be transferred from device / sense data]
The structures are defined as follows:
struct USER_HEADER
{
ULONG Id;
ULONG Length;
ULONG MajorFunction;
ULONG MinorFunction;
ULONG IoControlCode;
ULONG InputBufferLength;
ULONG OutputBufferLength;
NTSTATUS Status;
ULONG Information;
};
struct USER_SCSI_REQUEST_BLOCK
{
UCHAR Function;
UCHAR SrbStatus;
UCHAR ScsiStatus;
UCHAR PathId;
UCHAR TargetId;
UCHAR Lun;
UCHAR QueueTag;
UCHAR QueueAction;
UCHAR CdbLength;
UCHAR SenseInfoBufferLength;
ULONG SrbFlags;
ULONG DataTransferLength;
ULONG TimeOutValue;
ULONG QueueSortKey;
UCHAR Cdb[16];
};
Client side code to pack and unpack requests sent from cdrom.sys:
PVOID GetBuffer(MDL *pSourceMdl, MDL *pTargetMdl, PVOID pBuffer, ULONG Length, BOOLEAN *pUnmap)
{
PVOID pBuffer2;
if (pSourceMdl->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | MDL_SOURCE_IS_NONPAGED_POOL))
{
pBuffer2 = (UCHAR*)pSourceMdl->MappedSystemVa + ((UCHAR*)pBuffer - ((UCHAR*)pSourceMdl->StartVa + pSourceMdl->ByteOffset));
*pUnmap = FALSE;
}
else
{
IoBuildPartialMdl(pSourceMdl, pTargetMdl, pBuffer, Length);
pBuffer2 = MmMapLockedPagesSpecifyCache(pTargetMdl, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);
*pUnmap = TRUE;
}
return pBuffer2;
}
void PackRequest(IRP *pIrp, USER_HEADER *pUserHeader, STORAGE_EXTENSION *pStorageExtension)
{
BOOLEAN Unmap;
PVOID pBuffer;
IO_STACK_LOCATION *pStack;
USER_SCSI_REQUEST_BLOCK *pUserSrb;
SCSI_REQUEST_BLOCK *pSrb;
pStack = IoGetCurrentIrpStackLocation(pIrp);
pIrp->Tail.Overlay.DriverContext[0] = (PVOID)pStorageExtension->Id;
pUserHeader->Id = pStorageExtension->Id;
++pStorageExtension->Id;
pUserHeader->Status = 0;
pUserHeader->Information = 0;
pUserHeader->MajorFunction = pStack->MajorFunction;
pUserHeader->MinorFunction = pStack->MinorFunction;
if (pStack->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
{
pUserHeader->IoControlCode = 0;
pUserHeader->InputBufferLength = 0;
pUserHeader->OutputBufferLength = 0;
pUserHeader->Length = sizeof(USER_HEADER) + sizeof(USER_SCSI_REQUEST_BLOCK);
pUserSrb = (USER_SCSI_REQUEST_BLOCK*)((UCHAR*)pUserHeader + sizeof(USER_HEADER));
pSrb = pStack->Parameters.Scsi.Srb;
pUserSrb->Function = pSrb->Function;
pUserSrb->SrbStatus = pSrb->SrbStatus;
pUserSrb->ScsiStatus = pSrb->ScsiStatus;
pUserSrb->PathId = pSrb->PathId;
pUserSrb->TargetId = pSrb->TargetId;
pUserSrb->Lun = pSrb->Lun;
pUserSrb->QueueTag = pSrb->QueueTag;
pUserSrb->QueueAction = pSrb->QueueAction;
pUserSrb->CdbLength = pSrb->CdbLength;
pUserSrb->SenseInfoBufferLength = pSrb->SenseInfoBufferLength;
pUserSrb->SrbFlags = pSrb->SrbFlags;
pUserSrb->DataTransferLength = pSrb->DataTransferLength;
pUserSrb->TimeOutValue = pSrb->TimeOutValue;
if ((pSrb->DataTransferLength) && (pSrb->SrbFlags & SRB_FLAGS_DATA_OUT))
{
pBuffer = GetBuffer(pIrp->MdlAddress, pStorageExtension->pMdl, pSrb->DataBuffer, pSrb->DataTransferLength, &Unmap);
memcpy((UCHAR*)pUserSrb + sizeof(USER_SCSI_REQUEST_BLOCK), pBuffer, pSrb->DataTransferLength);
if (Unmap) MmUnmapLockedPages(pBuffer, pStorageExtension->pMdl);
pUserHeader->Length += pSrb->DataTransferLength;
}
pUserSrb->QueueSortKey = pSrb->QueueSortKey;
memcpy(pUserSrb->Cdb, pSrb->Cdb, sizeof(pSrb->Cdb));
}
else
{
pUserHeader->IoControlCode = pStack->Parameters.DeviceIoControl.IoControlCode;
pUserHeader->InputBufferLength = pStack->Parameters.DeviceIoControl.InputBufferLength;
pUserHeader->OutputBufferLength = pStack->Parameters.DeviceIoControl.OutputBufferLength;
pUserHeader->Length = sizeof(USER_HEADER);
if ((pUserHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY) ||
(pUserHeader->IoControlCode == IOCTL_STORAGE_ENABLE_IDLE_POWER))
{
pUserHeader->Length += pUserHeader->InputBufferLength;
memcpy((UCHAR*)pUserHeader + sizeof(USER_HEADER), pIrp->AssociatedIrp.SystemBuffer, pUserHeader->InputBufferLength);
}
else if ((pUserHeader->IoControlCode != IOCTL_STORAGE_POWER_ACTIVE) &&
(pUserHeader->IoControlCode != IOCTL_SCSI_GET_ADDRESS))
{
__debugbreak();
}
}
}
void UnpackRequest(USER_HEADER *pUserHeader, IRP *pIrp, STORAGE_EXTENSION *pStorageExtension)
{
BOOLEAN Unmap;
PVOID pBuffer;
IO_STACK_LOCATION *pStack;
USER_SCSI_REQUEST_BLOCK *pUserSrb;
SCSI_REQUEST_BLOCK *pSrb;
pStack = IoGetCurrentIrpStackLocation(pIrp);
if (pUserHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
{
pUserSrb = (USER_SCSI_REQUEST_BLOCK*)((UCHAR*)pUserHeader + sizeof(USER_HEADER));
pSrb = pStack->Parameters.Scsi.Srb;
pSrb->SrbStatus = pUserSrb->SrbStatus;
pSrb->ScsiStatus = pUserSrb->ScsiStatus;
pSrb->SenseInfoBufferLength = pUserSrb->SenseInfoBufferLength;
pSrb->DataTransferLength = pUserSrb->DataTransferLength;
if (NT_SUCCESS(pUserHeader->Status))
{
if ((pUserSrb->DataTransferLength) && (pUserSrb->SrbFlags & SRB_FLAGS_DATA_IN))
{
pBuffer = GetBuffer(pIrp->MdlAddress, pStorageExtension->pMdl, pSrb->DataBuffer, pUserSrb->DataTransferLength, &Unmap);
memcpy(pBuffer, (UCHAR*)pUserSrb + sizeof(USER_SCSI_REQUEST_BLOCK), pUserSrb->DataTransferLength);
if (Unmap) MmUnmapLockedPages(pBuffer, pStorageExtension->pMdl);
}
else
{
if (pUserSrb->Function == SRB_FUNCTION_CLAIM_DEVICE) pSrb->DataBuffer = pStack->DeviceObject;
}
}
else
{
if ((pUserSrb->SenseInfoBufferLength) && (pUserSrb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID))
{
memcpy(pSrb->SenseInfoBuffer, (UCHAR*)pUserSrb + sizeof(USER_SCSI_REQUEST_BLOCK), pUserSrb->SenseInfoBufferLength);
}
}
}
else
{
if (NT_SUCCESS(pUserHeader->Status))
{
if ((pUserHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS) ||
(pUserHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY))
{
memcpy(pIrp->AssociatedIrp.SystemBuffer, (UCHAR*)pUserHeader + sizeof(USER_HEADER), pUserHeader->Information);
}
}
}
}
Server side code to allocate request and IO completion routine:
NTSTATUS AllocateRequest(DEVICE_EXTENSION *pDeviceExtension, IRP *pIrp, IRP **ppIrp2)
{
IRP *pIrp2;
PVOID pBuffer;
NTSTATUS Status;
IO_STACK_LOCATION *pStack;
SCSI_REQUEST_BLOCK *pSrb;
USER_SCSI_REQUEST_BLOCK *pUserSrb;
DEVICE_OBJECT *pDeviceObject;
USER_HEADER *pUserHeader;
pUserHeader = (USER_HEADER*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
pDeviceObject = pDeviceExtension->pLowerDeviceObject;
pIrp2 = IoAllocateIrp(pDeviceObject->StackSize, FALSE);
if (pIrp2)
{
pStack = IoGetNextIrpStackLocation(pIrp2);
pStack->DeviceObject = pDeviceObject;
pIrp2->Tail.Overlay.Thread = PsGetCurrentThread();
pStack->MajorFunction = pUserHeader->MajorFunction;
pStack->MinorFunction = pUserHeader->MinorFunction;
if (pUserHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
{
pUserSrb = (USER_SCSI_REQUEST_BLOCK*)((UCHAR*)pUserHeader + sizeof(USER_HEADER));
pSrb = (SCSI_REQUEST_BLOCK*)((UCHAR*)pUserSrb + (sizeof(USER_SCSI_REQUEST_BLOCK) + pUserSrb->DataTransferLength + pUserSrb->SenseInfoBufferLength));
pSrb->Length = sizeof(SCSI_REQUEST_BLOCK);
pSrb->Function = pUserSrb->Function;
pSrb->SrbStatus = pUserSrb->SrbStatus;
pSrb->ScsiStatus = pUserSrb->ScsiStatus;
pSrb->PathId = pUserSrb->PathId;
pSrb->TargetId = pUserSrb->TargetId;
pSrb->Lun = pUserSrb->Lun;
pSrb->QueueTag = pUserSrb->QueueTag;
pSrb->QueueAction = pUserSrb->QueueAction;
pSrb->CdbLength = pUserSrb->CdbLength;
pSrb->SenseInfoBufferLength = pUserSrb->SenseInfoBufferLength;
pSrb->SrbFlags = pUserSrb->SrbFlags;
pSrb->DataTransferLength = pUserSrb->DataTransferLength;
pSrb->TimeOutValue = pUserSrb->TimeOutValue;
if (pUserSrb->DataTransferLength)
{
pSrb->DataBuffer = (UCHAR*)pIrp->MdlAddress->StartVa + pIrp->MdlAddress->ByteOffset + (sizeof(USER_HEADER) + sizeof(USER_SCSI_REQUEST_BLOCK));
IoBuildPartialMdl(pIrp->MdlAddress, pDeviceExtension->pMdl, pSrb->DataBuffer, pUserSrb->DataTransferLength);
pIrp2->MdlAddress = pDeviceExtension->pMdl;
}
else pSrb->DataBuffer = NULL;
if (pUserSrb->SenseInfoBufferLength)
{
pSrb->SenseInfoBuffer = (UCHAR*)pUserSrb + (sizeof(USER_SCSI_REQUEST_BLOCK) + pUserSrb->DataTransferLength);
}
else pSrb->SenseInfoBuffer = NULL;
pSrb->NextSrb = NULL;
pSrb->OriginalRequest = pIrp2;
pSrb->SrbExtension = NULL;
pSrb->QueueSortKey = pUserSrb->QueueSortKey;
memcpy(pSrb->Cdb, pUserSrb->Cdb, sizeof(pSrb->Cdb));
pStack->Parameters.Scsi.Srb = pSrb;
}
else
{
pStack->Parameters.DeviceIoControl.IoControlCode = pUserHeader->IoControlCode;
pBuffer = (UCHAR*)pUserHeader + sizeof(USER_HEADER);
if (pUserHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS)
{
pStack->Parameters.DeviceIoControl.OutputBufferLength = pUserHeader->OutputBufferLength;
pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
}
else if (pUserHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY)
{
pStack->Parameters.DeviceIoControl.InputBufferLength = pUserHeader->InputBufferLength;
pStack->Parameters.DeviceIoControl.OutputBufferLength = pUserHeader->OutputBufferLength;
pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
}
else if (pUserHeader->IoControlCode == IOCTL_STORAGE_ENABLE_IDLE_POWER)
{
pStack->Parameters.DeviceIoControl.InputBufferLength = pUserHeader->InputBufferLength;
pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
}
}
*ppIrp2 = pIrp2;
Status = STATUS_SUCCESS;
}
else Status = STATUS_INSUFFICIENT_RESOURCES;
return Status;
}
NTSTATUS IoCompletionRoutine3(DEVICE_OBJECT *pDeviceObject, IRP *pIrp2, void *pContext)
{
IRP *pIrp;
USER_HEADER *pUserHeader;
DEVICE_EXTENSION *pDeviceExtension;
USER_SCSI_REQUEST_BLOCK *pUserSrb;
SCSI_REQUEST_BLOCK *pSrb;
pDeviceExtension = (DEVICE_EXTENSION*)pIrp2->Tail.Overlay.DriverContext[0];
pIrp = (IRP*)pIrp2->Tail.Overlay.DriverContext[1];
pUserHeader = (USER_HEADER*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
pUserHeader->Status = pIrp2->IoStatus.Status;
pUserHeader->Information = pIrp2->IoStatus.Information;
if (pUserHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
{
pUserSrb = (USER_SCSI_REQUEST_BLOCK*)((UCHAR*)pUserHeader + sizeof(USER_HEADER));
pSrb = (SCSI_REQUEST_BLOCK*)((UCHAR*)pUserSrb + (sizeof(USER_SCSI_REQUEST_BLOCK) + pUserSrb->DataTransferLength + pUserSrb->SenseInfoBufferLength));
pUserSrb->SrbStatus = pSrb->SrbStatus;
pUserSrb->ScsiStatus = pSrb->ScsiStatus;
pUserSrb->SenseInfoBufferLength = pSrb->SenseInfoBufferLength;
pUserSrb->DataTransferLength = pSrb->DataTransferLength;
pUserHeader->Length = sizeof(USER_HEADER) + sizeof(USER_SCSI_REQUEST_BLOCK);
if (NT_SUCCESS(pUserHeader->Status))
{
if ((pSrb->DataTransferLength) && (pSrb->SrbFlags & SRB_FLAGS_DATA_IN))
{
pUserHeader->Length += pUserSrb->DataTransferLength;
}
}
else
{
if ((pSrb->SenseInfoBufferLength) && (pSrb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID))
{
pUserHeader->Length += pUserSrb->SenseInfoBufferLength;
if (pSrb->DataTransferLength)
{
memmove((UCHAR*)pUserSrb + sizeof(USER_SCSI_REQUEST_BLOCK), (UCHAR*)pUserSrb + (sizeof(USER_SCSI_REQUEST_BLOCK) + pUserSrb->DataTransferLength), pUserSrb->SenseInfoBufferLength);
}
}
}
}
else
{
pUserHeader->Length = sizeof(USER_HEADER);
if (pUserHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS)
{
pUserHeader->Length += pUserHeader->Information;
}
else if (pUserHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY)
{
pUserHeader->Length += pUserHeader->Information;
}
}
IoFreeIrp(pIrp2);
CompleteRequest(pIrp, STATUS_SUCCESS, 0);
IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
Everything runs fine (requests are passed between server and client, no BSOD, etc), but cdrom device just does not show up on the client side. I thought it might be something with srb data buffer access. Can you help me to figure it out? Thank you.

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;
}

Using SendMailMapi() in C++Builder 10.4.1 with and without the Windows 10 SDK installed

I have 2 computers, both with C++Builder 10.4.1 installed, only 1 has Windows 10 SDK.
When I compile and run my program with the following code, SendMailMapi() throws an Access Violation.
On systems without the SDK, the code compiles and runs just fine. Any ideas what the problem is?
#include <Winapi.Mapi.hpp>
#include <mapi.h>
...
lots of stuff here
...
SendMailMapi("email address", "mail subject", "Text", FileName(attachment));
...
bool __fastcall Tmyform::SendMailMapi(AnsiString ToAddr, AnsiString Subj, AnsiString Msg, AnsiString AttFile)
{
//
AnsiString fp, fn;
int dwResult;
fn = Application->ExeName;
if (fn.Pos(".\\")) {
fn = fn.Delete(fn.Pos(".\\"), 2);
}
fn = ExtractFilePath(fn);
fp = ExtractFilePath(AttFile);
if (fp.Pos(".\\")) {
fp = fp.Delete(fp.Pos(".\\"), 2);
}
fn = fn + fp + ExtractFileName(AttFile);
HINSTANCE hMAPI;
LPMAPISENDMAIL pSendMail;
TMapiMessage *message = new TMapiMessage;
message->flFlags = 0;
TMapiFileTagExt *filetag = new TMapiFileTagExt;
//dwResult = message->flFlags;
filetag->ulReserved = 0;
filetag->cbEncoding = 0;
filetag->cbTag = 0;
filetag->lpTag = NULL;
filetag->lpEncoding = NULL;
TMapiFileDesc *file = new TMapiFileDesc;
file->ulReserved = 0;
file->flFlags = 0;
file->nPosition = -1;
file->lpszPathName = fn.c_str();
file->lpszFileName = fn.c_str();
file->lpFileType = filetag;
TMapiRecipDesc *Recpt = new TMapiRecipDesc;
Recpt->ulReserved = 0;
Recpt->ulRecipClass = MAPI_TO;
Recpt->lpszName = ""; // (wchar_t *)
Recpt->lpszAddress = ToAddr.c_str(); // (wchar_t *)
Recpt->ulEIDSize = 0;
Recpt->lpEntryID = NULL;
hMAPI = LoadLibraryA( "MAPI32.DLL" );
pSendMail = (LPMAPISENDMAIL)GetProcAddress( hMAPI, "MAPISendMail" );
message->lpszSubject = Subj.c_str(); // (wchar_t *)
message->lpszNoteText = Msg.c_str(); // (wchar_t *)
message->lpszMessageType = ""; // (wchar_t *)
message->nRecipCount = 1;
message->lpRecips = Recpt;
message->nFileCount = 1;
message->lpFiles = file;
message->lpOriginator = NULL;
//dwResult = message->flFlags;
dwResult = pSendMail( lhSessionNull, (DWORD)0, message, MAPI_LOGON_UI | MAPI_DIALOG, 0 );
if( dwResult == SUCCESS_SUCCESS )
{
// ...yay! :)
}
else
{
// ...we always fail here with: MAPI_E_FAILURE
}
}
Found it, sorry for delay
some more detail to make this thing happy!

How to logon by using sub authentication package on windows? Call of LsaLogonUser failed with nt_status 0xC00000A7 (STATUS_BAD_VALIDATION_CLASS)

I experimented on Windows 7 and also Windows Server 2008
First step:
I compiled the windows pass-through subauth example and released the subauth.dll, copy it to c:\windows\system32 and add the registry key Auth155 with string value "subauth" on HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0
Second step:
I write the test program to execute login procedure by using the self-defined subauth, the main source code as the following:
size_t wcsByteLen( const wchar_t* str )
{
return wcslen( str ) * sizeof(wchar_t);
}
void InitUnicodeString(UNICODE_STRING& str, const wchar_t* value,
BYTE* buffer, size_t& offset )
{
size_t size = wcsByteLen( value );
str.Length = str.MaximumLength = (USHORT)size;
str.Buffer = (PWSTR)(buffer + offset);
memcpy( str.Buffer, value, size );
offset += size;
}
LSA_STRING* create_lsa_string(const char* value)
{
char* buf = new char[100];
LSA_STRING* str = (LSA_STRING*)buf;
str->Length = strlen(value);
str->MaximumLength = str->Length;
str->Buffer = buf + sizeof(LSA_STRING);
memcpy(str->Buffer, value, str->Length);
return str;
}
MSV1_0_SUBAUTH_LOGON * create_logon_info(int * size)
{
BYTE * buf = new BYTE[2000];
size_t offset = sizeof(MSV1_0_SUBAUTH_LOGON);
MSV1_0_SUBAUTH_LOGON * p_logon_info = (MSV1_0_SUBAUTH_LOGON*)buf;
WCHAR* domain = L"WIN7U-20140106B";
WCHAR* user = L"test";
WCHAR* workstation = L"WIN7U-20140106B";
CHAR* auth1 = "auth1";
CHAR* auth2 = "auth2";
p_logon_info->MessageType = MSV1_0_LOGON_SUBMIT_TYPE::MsV1_0SubAuthLogon;
InitUnicodeString(p_logon_info->LogonDomainName, domain, buf, offset);
InitUnicodeString(p_logon_info->UserName, user, buf, offset);
InitUnicodeString(p_logon_info->Workstation, workstation, buf, offset);
InitString(p_logon_info->AuthenticationInfo1, auth1, buf, offset);
InitString(p_logon_info->AuthenticationInfo2, auth2, buf, offset);
memset(p_logon_info->ChallengeToClient, 0, MSV1_0_CHALLENGE_LENGTH);
p_logon_info->ParameterControl = 0;
p_logon_info->SubAuthPackageId = 155;
*size = sizeof(MSV1_0_SUBAUTH_LOGON) +
wcsByteLen(domain) + wcsByteLen(user) + wcsByteLen(workstation) +
strlen(auth1) + strlen(auth2);
return p_logon_info;
}
void logon_test_ex()
{
NTSTATUS status;
HANDLE handle;
LSA_OPERATIONAL_MODE mode;
int logon_info_size = 0;
MSV1_0_SUBAUTH_LOGON * p_logon_info = create_logon_info(&logon_info_size);
LSA_STRING* name = create_lsa_string("subauthtest");
//status = LsaConnectUntrusted(&handle);
status = LsaRegisterLogonProcess(name, &handle, &mode);
if(status != STATUS_SUCCESS)
{
print_error("LsaRegisterLogonProcess", status);
}
LSA_STRING* package_name = create_lsa_string(MSV1_0_PACKAGE_NAME);
ULONG package_id = 0;
status = LsaLookupAuthenticationPackage(handle, package_name, &package_id);
if(status != STATUS_SUCCESS)
{
print_error("LsaLookupAuthenticationPackage", status);
}
printf("package id : %d\n", package_id);
MSV1_0_SUBAUTH_REQUEST request ;
request.MessageType = MsV1_0GenericPassthrough; //MsV1_0SubAuth;
request.SubAuthPackageId = 155;
request.SubAuthSubmitBuffer = new UCHAR[20];
request.SubAuthInfoLength = 20;
ULONG* ProtocolReturnBuffer = NULL;
ULONG ReturnBufferLength = 0;
NTSTATUS ProtocolStatus = 0;
status = LsaCallAuthenticationPackage(handle, package_id, &request, sizeof(MSV1_0_SUBAUTH_REQUEST),
(PVOID*)&ProtocolReturnBuffer, &ReturnBufferLength, &ProtocolStatus);
if(status != STATUS_SUCCESS)
{
print_error("LsaCallAuthenticationPackage", status);
}
printf("ProtocolStatus : %x\n", ProtocolStatus);
LSA_STRING* origin_name = create_lsa_string("TTY1");
TOKEN_SOURCE source = {0};
strcpy_s(source.SourceName, "Test");
bool test = AllocateLocallyUniqueId(&source.SourceIdentifier);
void * ProfileBuffer = NULL;
ULONG ProfileBufferLen = 0;
LUID id = {0};
HANDLE token = NULL;
QUOTA_LIMITS limit = {0};
NTSTATUS substatus = 0;
status = LsaLogonUser(handle, origin_name, SECURITY_LOGON_TYPE::Batch, package_id,
p_logon_info, 1000,
NULL, &source, (PVOID*)&ProfileBuffer, &ProfileBufferLen,
&id, &token, &limit, &substatus);
if(status != STATUS_SUCCESS)
{
print_error("LsaLogonUser", status);
}
printf("done\n");
}
Finally:
I run the test program by using PsExec as local system account, in order to grant the SeTcbPriviledge
D:\Software\PSTools>PsExec.exe -i -d -s "E:\Projects\subauthtest\x64\Debug\subauthtest.exe"
The problem is that we always get the failed result by calling LsaLogonUser, the NT status value is 0xC00000A7(STATUS_BAD_VALIDATION_CLASS)
What's the problem for my logon test program ?
Any ideas or suggestions ?
Thanks very much
Bitwise left shift your subauth package id by 24 bits and then set the result to the ParameterControl variable of MSV1_0_SUBAUTH_LOGON structure, this configuration is undocumented.
You can use the MSV1_0_SUBAUTHENTICATION_DLL_SHIFT macro, as the following:
 
suauth_ptr->SubAuthPackageId = subauth_id;
suauth_ptr->ParameterControl = subauth_id<<MSV1_0_SUBAUTHENTICATION_DLL_SHIFT;

STATUS_ACCESS_DENIED on a call to NtQueryMutant

Disclaimer:
The only reason for the question and the code below to exist is an external component used in my application, which cannot be replaced, at least in the near future. This component's logic intercepts WinAPI calls from the application and performs various tasks based on these calls.
One of the things the component does, it creates mutex for each thread initialized inside the application. However, it doesn't close the mutexes, which results in handles leak.
Therefore, in order to prevent the leak and because I don't have access to the component's source code, I have to invent ugly workarounds and use esoteric API's.
End of disclaimer
I am trying to check state of mutexes in my application. In order to do this without changing the state of each of the objects I check, I have to use the NtQueryMutant method from ntdll.dll.
Based on examples here and here I wrote the following code to achieve this:
enum MUTANT_INFORMATION_CLASS
{
MutantBasicInformation
};
struct MUTANT_BASIC_INFORMATION {
LONG CurrentCount;
BOOLEAN OwnedByCaller;
BOOLEAN AbandonedState;
};
typedef NTSTATUS(WINAPI*QueryMutexHandler)(HANDLE, MUTANT_INFORMATION_CLASS, PVOID, ULONG, PULONG);
//somewhere in the code:
QueryMutexHandler queryMutex = reinterpret_cast<QueryMutexHandler>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryMutant"));
MUTANT_BASIC_INFORMATION mutantInfo;
NTSTATUS status = queryMutex(objectHandleCopy, MutantBasicInformation, &mutantInfo, sizeof(MUTANT_BASIC_INFORMATION), nullptr);
if (NT_SUCCESS(status))
{
//never arriving here
}
The status I receive here is always -1073741790 (0xFFFF FFFF C000 0022) which is, except being negative number, looks exactly like STATUS_ACCESS_DENIED.
That is very strange, because previously in code I use both NtQuerySystemInformation and NtQueryObject without any problem.
Additional details: my OS is Windows 7 SP1, the mutexes I try to query belong to the process I am performing the query from.
for effective test Mutant you need it handle and it access mask. you can got it from SYSTEM_HANDLE_INFORMATION_EX structure. if we already have MUTANT_QUERY_STATE - can direct query, if no - need reopen handle with MUTANT_QUERY_STATE
NTSTATUS QueryMutant(HANDLE hMutant, ULONG GrantedAccess, MUTANT_BASIC_INFORMATION* pmbi)
{
if (GrantedAccess & MUTANT_QUERY_STATE)
{
return ZwQueryMutant(hMutant, MutantBasicInformation, pmbi, sizeof(MUTANT_BASIC_INFORMATION), 0);
}
NTSTATUS status = ZwDuplicateObject(NtCurrentProcess(), hMutant, NtCurrentProcess(),&hMutant,
MUTANT_QUERY_STATE, 0, 0);
if (0 <= status)
{
status = ZwQueryMutant(hMutant, MutantBasicInformation, pmbi, sizeof(MUTANT_BASIC_INFORMATION), 0);
ZwClose(hMutant);
}
return status;
}
and you not need all time use NtQueryObject for determinate type of handle. you can use SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex . for get OBJECT_TYPE_INFORMATION by this index. for this you need only once call ZwQueryObject(0, ObjectAllTypeInformation, ) at start, but exist problem how convert SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex to array index (zero bassed). begin from win8.1 'OBJECT_TYPE_INFORMATION.TypeIndex' is valid and match to SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex, but for early version - you need once get SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex for some known object type and calc delta
static volatile UCHAR guz;
NTSTATUS getProcessIndex(USHORT& ObjectTypeIndex)
{
HANDLE hProcess;
NTSTATUS status = ZwDuplicateObject(NtCurrentProcess(), NtCurrentProcess(), NtCurrentProcess(), &hProcess, 0, 0, DUPLICATE_SAME_ACCESS);
if (0 <= status)
{
PVOID stack = alloca(guz);
DWORD cb = 0, rcb = 0x10000;
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, cb, &rcb)))
{
if (ULONG NumberOfHandles = (ULONG)pshti->NumberOfHandles)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles = pshti->Handles;
ULONG_PTR UniqueProcessId = GetCurrentProcessId();
do
{
if (Handles->UniqueProcessId == UniqueProcessId && (HANDLE)Handles->HandleValue == hProcess)
{
ObjectTypeIndex = Handles->ObjectTypeIndex;
goto __break;
}
} while (Handles++, --NumberOfHandles);
}
}
} while (STATUS_INFO_LENGTH_MISMATCH == status);
__break:
ZwClose(hProcess);
}
return status;
}
class ZOBJECT_ALL_TYPES_INFORMATION
{
OBJECT_TYPE_INFORMATION* _TypeInformation;
DWORD _NumberOfTypes, _TypeIndexDelta;
public:
operator DWORD()
{
return _NumberOfTypes;
}
operator OBJECT_TYPE_INFORMATION*()
{
return _TypeInformation;
}
DWORD operator[](OBJECT_TYPE_INFORMATION* TypeInformation)
{
return (DWORD)(TypeInformation - _TypeInformation) + _TypeIndexDelta;
}
OBJECT_TYPE_INFORMATION* operator[](DWORD Index)
{
return Index < _NumberOfTypes ? _TypeInformation + Index : 0;
}
ULONG TypeIndexToIndex(DWORD ObjectTypeIndex)
{
return ObjectTypeIndex -= _TypeIndexDelta;
}
OBJECT_TYPE_INFORMATION* operator[](PCUNICODE_STRING TypeName);
ZOBJECT_ALL_TYPES_INFORMATION();
~ZOBJECT_ALL_TYPES_INFORMATION();
};
ZOBJECT_ALL_TYPES_INFORMATION g_AOTI;
OBJECT_TYPE_INFORMATION* ZOBJECT_ALL_TYPES_INFORMATION::operator[](PCUNICODE_STRING TypeName)
{
if (DWORD NumberOfTypes = _NumberOfTypes)
{
OBJECT_TYPE_INFORMATION* TypeInformation = _TypeInformation;
do
{
if (RtlEqualUnicodeString(TypeName, &TypeInformation->TypeName, TRUE))
{
return TypeInformation;
}
} while (TypeInformation++, -- NumberOfTypes);
}
return 0;
}
ZOBJECT_ALL_TYPES_INFORMATION::ZOBJECT_ALL_TYPES_INFORMATION()
{
_TypeInformation = 0, _NumberOfTypes = 0;
USHORT ProcessTypeIndex;
if (0 > getProcessIndex(ProcessTypeIndex))
{
return ;
}
NTSTATUS status;
PVOID stack = alloca(guz);
union {
PVOID pv;
OBJECT_TYPES_INFORMATION* poati;
};
DWORD cb = 0, rcb = 0x2000;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQueryObject(0, ObjectAllTypeInformation, poati, cb, &rcb)))
{
if (DWORD NumberOfTypes = poati->NumberOfTypes)
{
if (OBJECT_TYPE_INFORMATION* TypeInformation = (OBJECT_TYPE_INFORMATION*)LocalAlloc(0, rcb))
{
_NumberOfTypes = NumberOfTypes;
_TypeInformation = TypeInformation;
ULONG Index = 0;
union {
ULONG_PTR uptr;
OBJECT_TYPE_INFORMATION* pti;
};
union {
PWSTR buf;
PBYTE pb;
PVOID pv;
};
pti = poati->TypeInformation;
pv = TypeInformation + NumberOfTypes;
do
{
STATIC_UNICODE_STRING_(Process);
if (RtlEqualUnicodeString(&Process, &pti->TypeName, TRUE))
{
_TypeIndexDelta = ProcessTypeIndex - Index;
}
ULONG Length = pti->TypeName.Length, MaximumLength = pti->TypeName.MaximumLength;
memcpy(buf, pti->TypeName.Buffer, Length);
*TypeInformation = *pti;
TypeInformation++->TypeName.Buffer = buf;
pb += Length;
uptr += (sizeof(OBJECT_TYPE_INFORMATION) + MaximumLength + sizeof(PVOID)-1) & ~ (sizeof(PVOID)-1);
} while (Index++, --NumberOfTypes);
}
}
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
}
ZOBJECT_ALL_TYPES_INFORMATION::~ZOBJECT_ALL_TYPES_INFORMATION()
{
if (_TypeInformation)
{
LocalFree(_TypeInformation);
}
}
and finally use next code, without NtQueryObject:
void TestMutant()
{
NTSTATUS status;
PVOID stack = alloca(guz);
DWORD cb = 0, rcb = 0x10000;
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, cb, &rcb)))
{
if (ULONG NumberOfHandles = (ULONG)pshti->NumberOfHandles)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles = pshti->Handles;
ULONG_PTR UniqueProcessId = GetCurrentProcessId();
do
{
if (Handles->UniqueProcessId == UniqueProcessId)
{
if (OBJECT_TYPE_INFORMATION* poti = g_AOTI[g_AOTI.TypeIndexToIndex(Handles->ObjectTypeIndex)])
{
STATIC_UNICODE_STRING_(Mutant);
if (RtlEqualUnicodeString(&Mutant, &poti->TypeName, TRUE))
{
MUTANT_BASIC_INFORMATION mbi;
QueryMutant((HANDLE)Handles->HandleValue, Handles->GrantedAccess, &mbi);
}
}
}
} while (Handles++, --NumberOfHandles);
}
}
} while (STATUS_INFO_LENGTH_MISMATCH == status);
}
can test with
void Az()
{
HANDLE hMutant;
if (0 <= ZwCreateMutant(&hMutant, SYNCHRONIZE, 0, TRUE))
{
TestMutant();
ZwClose(hMutant);
}
}

Adding Response from TSA to CRYPT_SIGN_MESSAGE_PARA for CryptSignMessage (c++, Crypto Api)

I'm struggling how must I add the response from a TSA server to my CryptSignMessage?
Using PKCS#7. I currently have my message digest and I successfully sign it with CryptSignMessage from crypto api. Like so:
// Initialize the signature structure.
CRYPT_SIGN_MESSAGE_PARA SigParams;
SigParams.cbSize = sizeof(CRYPT_SIGN_MESSAGE_PARA);
SigParams.dwMsgEncodingType = MY_ENCODING_TYPE;
SigParams.pSigningCert = hContext;
SigParams.HashAlgorithm.pszObjId = szOID_RSA_SHA1RSA;
SigParams.HashAlgorithm.Parameters.cbData = NULL;
SigParams.cMsgCert = 1;
SigParams.rgpMsgCert = &hContext;
SigParams.dwInnerContentType = 0;
SigParams.cMsgCrl = 0;
SigParams.cUnauthAttr = 0;
SigParams.dwFlags = 0;
SigParams.pvHashAuxInfo = NULL;
SigParams.cAuthAttr = 0;
SigParams.rgAuthAttr = NULL;
// First, get the size of the signed BLOB.
if(CryptSignMessage(
&SigParams,
FALSE,
1,
MessageArray,
MessageSizeArray,
NULL,
&cbSignedMessageBlob))
{
printf("%d bytes needed for the encoded BLOB.", cbSignedMessageBlob);
}
else
{
MyHandleError();
fReturn = false;
exit_SignMessage();
}
// Allocate memory for the signed BLOB.
if(!(pbSignedMessageBlob =
(BYTE*)malloc(cbSignedMessageBlob)))
{
MyHandleError();
exit_SignMessage();
}
// Get the signed message BLOB.
if(CryptSignMessage(
&SigParams,
TRUE,
1,
MessageArray,
MessageSizeArray,
pbSignedMessageBlob,
&cbSignedMessageBlob))
{
printf("The message was signed successfully. \n");
// pbSignedMessageBlob now contains the signed BLOB.
fReturn = true;
}
else
{
MyHandleError();
fReturn = false;
exit_SignMessage();
}
Now I want to use a TSA server to timestamp my digest, but I'm not really sure how to include this. Say I have a rfc3161 TimeStamp request; I send this to my TSA and I receive a rfc3161 TimeStamp response (probably using libcurl). How should incorporate the response into my SigParams? Must I extract the TimeStampToken and then store that as an unauthenticated counter signature? Something like:
CRYPT_ATTR_BLOB cablob[1];
CRYPT_ATTRIBUTE ca[1];
cablob[0].cbData = tstResponseSize;
cablob[0].pbData = tstResponse; // the response from TSA
ca[0].pszObjId = "1.2.840.113549.9.6"; // object identifier for counter signature
ca[0].cValue = 1;
ca[0].rgValue = cablob;
And then set the SigParams:
SigParams.cUnauthAtt = 1;
SigParams.rgUnauthAttr = ca;
Any advice would be greatly appreciated.
Thanks,
Magda
I struggled with this for a couple of days. There are not that many examples out there, so here is my solution. Hope it helps :)
HCRYPTMSG hMsg = ::CryptMsgOpenToDecode(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CMSG_DETACHED_FLAG, 0, NULL, NULL, NULL);
if (NULL == hMsg)
{
throw std::exception("Failed to open messsage to decode");
}
if (!::CryptMsgUpdate(hMsg, signedData.pbData, signedData.cbData, TRUE))
{
throw std::exception("Failed to add signature block to message");
}
//get the digest from the signature
PCRYPT_TIMESTAMP_CONTEXT pTsContext = NULL;
DWORD encDigestSize = 0;
if (::CryptMsgGetParam(hMsg, CMSG_ENCRYPTED_DIGEST, 0, NULL, &encDigestSize))
{
std::unique_ptr<BYTE> pEncDigest(new BYTE[encDigestSize]);
if (::CryptMsgGetParam(hMsg, CMSG_ENCRYPTED_DIGEST, 0, pEncDigest.get(), &encDigestSize))
{
//get timestamp
if (::CryptRetrieveTimeStamp(L"http://sha256timestamp.ws.symantec.com/sha256/timestamp",
TIMESTAMP_NO_AUTH_RETRIEVAL,
0, //timeout?????
szOID_NIST_sha256,
NULL,
pEncDigest.get(),
encDigestSize,
&pTsContext,
NULL,
NULL))
{
CRYPT_ATTR_BLOB cryptBlob = {};
cryptBlob.cbData = pTsContext->cbEncoded;
cryptBlob.pbData = pTsContext->pbEncoded;
CRYPT_ATTRIBUTE cryptAttribute = {};
cryptAttribute.pszObjId = "1.2.840.113549.1.9.16.2.14"; //id-smime-aa-timeStampToken
cryptAttribute.cValue = 1;
cryptAttribute.rgValue = &cryptBlob;
DWORD encodedAttributeSize = 0;
std::unique_ptr<BYTE> encodedAttribute;
if (::CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_ATTRIBUTE, &cryptAttribute, NULL, &encodedAttributeSize))
{
encodedAttribute.reset(new BYTE[encodedAttributeSize]);
if (::CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_ATTRIBUTE, &cryptAttribute, encodedAttribute.get(), &encodedAttributeSize))
{
CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR_PARA unauthenticatedParam = { 0 };
unauthenticatedParam.cbSize = sizeof(unauthenticatedParam);
unauthenticatedParam.dwSignerIndex = 0; //only have 1 cert
unauthenticatedParam.blob.cbData = encodedAttributeSize;
unauthenticatedParam.blob.pbData = encodedAttribute.get();
if (::CryptMsgControl(hMsg, 0, CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, &unauthenticatedParam))
{
DWORD encodedMessageLength = 0;
if (::CryptMsgGetParam(hMsg, CMSG_ENCODED_MESSAGE, 0, NULL, &encodedMessageLength))
{
std::unique_ptr<BYTE> pData(new BYTE[encodedMessageLength]);
if (::CryptMsgGetParam(hMsg, CMSG_ENCODED_MESSAGE, 0, pData.get(), &encodedMessageLength))
{
//save pData/encodedMessageLength here to file
}
}
}
}
}
}
}
}
if (NULL != pTsContext)
{
::CryptMemFree(pTsContext);
}
if (NULL != hMsg)
{
::CryptMsgClose(hMsg);
}