Проблема при использовании DLL-враппера для Crypt32.dll

vdix
Дата: 25.11.2019 13:56:26
Приветствую, коллеги. Есть программа, функционал которой расширяется плагинами, одно из таких расширений должно поддерживать шифрование с помощью CryptProtectData/CryptUnprotectData из Data Protection API (Crypt32.dll). Сперва написали тестовый модуль и статически прописали загрузку Crypt32.dll - всё работает отлично, написали обёртку - и тут случился облом. Операционка возвращает "System Error. Code: 127. The specified procedure could not be found" после не успешного вызова CryptProtectData() - по идее CryptUnprotectData() делает то же самое, просто до него дело не доходит. Сделал динамическую загрузку Crypt32.dll - результат тот же, если функции Crypt32.dll вызываются непосредственно из EXE - всё замечательно работает, если вызываем через обёртку из DLL - опять проблема. Иногда вместо ошибки 127 возвращается "System Error. Code: 87. The parameter is incorrect", но эта ошибка зачастую лечится перезапуском Делфи и ребилдом проекта.

Если вдруг имеет какое значение, то проект собирался на Делфи 10.2. Буду рад любой подсказке, ибо сам весь в смущении и непонятности :)

Выглядит вся эта кухня вот так:

DPCrypt.pas
unit DPCrypt;

{$DEFINE STATIC_LINK} // comment for dynamic DLL loading

interface

uses
  EncdDecd, Winapi.Windows, System.SysUtils;

type
  TLargeByteArray = array [0..Pred(MaxInt)] of Byte;
  PLargeByteArray = ^TLargeByteArray;
  _CRYPTOAPI_BLOB = packed record
    cbData: DWORD;
    pbData: PByte;
  end;
  TCryptoApiBlob       = _CRYPTOAPI_BLOB;
  PCrypyoApiBlob       = ^TCryptoApiBlob;
  CRYPT_INTEGER_BLOB   = _CRYPTOAPI_BLOB;
  PCRYPT_INTEGER_BLOB  = ^CRYPT_INTEGER_BLOB;
  CRYPT_UINT_BLOB      = _CRYPTOAPI_BLOB;
  PCRYPT_UINT_BLOB     = ^CRYPT_INTEGER_BLOB;
  CRYPT_OBJID_BLOB     = _CRYPTOAPI_BLOB;
  PCRYPT_OBJID_BLOB    = ^CRYPT_INTEGER_BLOB;
  CERT_NAME_BLOB       = _CRYPTOAPI_BLOB;
  PCERT_NAME_BLOB      = ^CRYPT_INTEGER_BLOB;
  CERT_RDN_VALUE_BLOB  = _CRYPTOAPI_BLOB;
  PCERT_RDN_VALUE_BLOB = ^CRYPT_INTEGER_BLOB;
  CERT_BLOB            = _CRYPTOAPI_BLOB;
  PCERT_BLOB           = ^CRYPT_INTEGER_BLOB;
  CRL_BLOB             = _CRYPTOAPI_BLOB;
  PCRL_BLOB            = ^CRYPT_INTEGER_BLOB;
  DATA_BLOB            = _CRYPTOAPI_BLOB;
  PDATA_BLOB           = ^CRYPT_INTEGER_BLOB;
  CRYPT_DATA_BLOB      = _CRYPTOAPI_BLOB;
  PCRYPT_DATA_BLOB     = ^CRYPT_INTEGER_BLOB;
  CRYPT_HASH_BLOB      = _CRYPTOAPI_BLOB;
  PCRYPT_HASH_BLOB     = ^CRYPT_INTEGER_BLOB;
  CRYPT_DIGEST_BLOB    = _CRYPTOAPI_BLOB;
  PCRYPT_DIGEST_BLOB   = ^CRYPT_INTEGER_BLOB;
  CRYPT_DER_BLOB       = _CRYPTOAPI_BLOB;
  PCRYPT_DER_BLOB      = ^CRYPT_INTEGER_BLOB;
  CRYPT_ATTR_BLOB      = _CRYPTOAPI_BLOB;
  PCRYPT_ATTR_BLOB     = ^CRYPT_INTEGER_BLOB;

  _CRYPTPROTECT_PROMPTSTRUCT = packed record
    cbSize:        DWORD;
    dwPromptFlags: DWORD;
    hwndApp:       HWND;
    szPrompt:      LPCWSTR;
  end;
  TCryptProtectPromptStruct  = _CRYPTPROTECT_PROMPTSTRUCT;
  PCryptProtectPromptStruct  = ^TCryptProtectPromptStruct;
  CRYPTPROTECT_PROMPTSTRUCT  = _CRYPTPROTECT_PROMPTSTRUCT;
  PCRYPTPROTECT_PROMPTSTRUCT = ^_CRYPTPROTECT_PROMPTSTRUCT;

function CryptProtectString(S: PWideChar): PWideChar; //stdcall;
function CryptUnprotectString(S: PWideChar): PWideChar; //stdcall;

implementation

{$IFDEF STATIC_LINK}
function CryptProtectData(pDataIn: PDATA_BLOB; szDataDescr: LPCWSTR; pOptionalEntropy: PDATA_BLOB; pReserved: Pointer; pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall; external 'Crypt32.dll';
function CryptUnprotectData(pDataIn: PDATA_BLOB; var ppszDataDescr: LPWSTR; pOptionalEntropy: PDATA_BLOB; pReserved: Pointer; pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall; external 'Crypt32.dll';
{$ELSE}
type
  TCryptProtectData = function(pDataIn: PDATA_BLOB; szDataDescr: LPCWSTR; pOptionalEntropy: PDATA_BLOB; pReserved: Pointer; pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall;
  TCryptUnprotectData = function(pDataIn: PDATA_BLOB; var ppszDataDescr: LPWSTR; pOptionalEntropy: PDATA_BLOB; pReserved: Pointer; pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall;
var
  CryptProtectData: TCryptProtectData;
  CryptUnprotectData: TCryptUnprotectData;
  Crypt32DLL: THandle;

function LoadCrypt32DLL: Boolean;
begin
  Crypt32DLL := LoadLibrary('Crypt32.dll');
  Result := Crypt32DLL <> 0;
  if Result then
  begin
    CryptProtectData := GetProcAddress(Crypt32DLL, 'CryptProtectData');
    Result := @CryptProtectData <> nil;
    if not Result then
       Exit;
    CryptUnprotectData := GetProcAddress(Crypt32DLL, 'CryptUnprotectData');
    Result := @CryptUnprotectData <> nil;
  end;
end;
{$ENDIF}

function dpApiProtectData(var fpDataIn: TBytes): tBytes;
var
  dataIn,               // Input buffer (clear-text/data)
  dataOut: DATA_BLOB;   // Output buffer (encrypted)
begin
  dataOut.cbData := 0;
  dataOut.pbData := nil;

  dataIn.cbData := Length(fpDataIn); // How much data (in bytes) we want to encrypt
  dataIn.pbData := @fpDataIn[0];     // Pointer to the data itself - the address of the first element of the input byte array

  if not CryptProtectData(@dataIn, nil, nil, nil, nil, 0, @dataOut) then
    RaiseLastOSError;

  SetLength(Result, dataOut.cbData);
  Move(dataOut.pbData^, Result[0], dataOut.cbData);
  LocalFree(HLOCAL(dataOut.pbData));                  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261(v=vs.85).aspx
end;

function dpApiUnprotectData(fpDataIn: TBytes): tBytes;
var
  dataIn,               // Input buffer (clear-text/data)
  dataOut: DATA_BLOB;   // Output buffer (encrypted)
  lpwszDesc: PWideChar;
begin
  dataOut.cbData := 0;
  dataOut.pbData := nil;

  dataIn.cbData := Length(fpDataIn);
  dataIn.pbData := @fpDataIn[0];

 if not CryptUnprotectData(@dataIn, lpwszDesc, nil, nil, nil, 0, @dataOut) then
    RaiseLastOSError;

  SetLength(Result, dataOut.cbData);
  Move(dataOut.pbData^, Result[0], dataOut.cbData);
  LocalFree(HLOCAL(dataOut.pbData));                  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380882%28v=vs.85%29.aspx
end;


function CryptProtectString(S: PWideChar): PWideChar; //stdcall;
var
  strIn: String;
  bytesEncrypted: TBytes;
  strOut: AnsiString;
begin
  {$IFNDEF STATIC_LINK}if not LoadCrypt32DLL then Exit;{$ENDIF}
  Result := '';
  strIn := S;
  SetLength(bytesEncrypted, Length(strIn) * SizeOf(Char));
  Move(strIn[1], bytesEncrypted[0], Length(strIn) * SizeOf(Char));
  bytesEncrypted := dpApiProtectData(bytesEncrypted);  // Encrypt data
  SetString(strOut, PAnsiChar(bytesEncrypted), Length(bytesEncrypted));
  Result := StrNew(PWideChar(EncodeString(strOut)));
  {$IFNDEF STATIC_LINK}if Crypt32DLL <> 0 then FreeModule(Crypt32DLL);{$ENDIF}
end;

function CryptUnprotectString(S: PWideChar): PWideChar; //stdcall;
var
  strIn: AnsiString;
  bytesEncrypted: TBytes;
  strOut: String;
begin
  {$IFNDEF STATIC_LINK}if not LoadCrypt32DLL then Exit;{$ENDIF}
  Result := '';
  strIn := DecodeString(S);
  SetLength(bytesEncrypted, Length(strIn));
  Move(strIn[1], bytesEncrypted[0], Length(strIn));
  bytesEncrypted := dpApiUnprotectData(bytesEncrypted);  // Decrypt data
  SetLength(strOut, Length(bytesEncrypted) div SizeOf(Char));
  Move(bytesEncrypted[0], strOut[1], length(bytesEncrypted));
  Result := StrNew(PWideChar(strOut));
  {$IFNDEF STATIC_LINK}if Crypt32DLL <> 0 then FreeModule(Crypt32DLL);{$ENDIF}
end;

end.


TestApp.dpr
program TestApp;

{$APPTYPE CONSOLE}

{.$DEFINE USE_DLL} // remove the dot for DLL

uses
  {$IFNDEF USE_DLL} DPCrypt, {$ENDIF}
  Windows, Messages, SysUtils;

{$IFDEF USE_DLL}
type
  TPluginFn = function(S: PWideChar): PWideChar; stdcall;

var
  DPAPI: THandle;
  dpApiProtectString,
  dpApiUnprotectString: TPluginFn;

function LoadDPAPI_DLL: Boolean;
begin
  DPAPI := LoadLibrary('DPAPI.dll');
  Result := DPAPI <> 0;
  if Result then
  begin
    dpApiProtectString := GetProcAddress(DPAPI, 'dpApiProtectString');
    dpApiUnprotectString := GetProcAddress(DPAPI, 'dpApiUnprotectString');
  end
  else
    WriteLn(SysErrorMessage(GetLastError));
end;
{$ENDIF}


var
  S: String;
begin
  S := 'Hello world';
  WriteLn('original_string:  ' + S);

  {$IFDEF USE_DLL}

  if LoadDPAPI_DLL then
  try
    S := dpApiProtectString(PWideChar(S));
    WriteLn('protected_string:  ' + S);
    S := dpApiUnprotectString(PWideChar(S));
    WriteLn('unprotected_string:  ' + S);
  finally
    if DPAPI <> 0 then
      FreeLibrary(DPAPI);
  end;

  {$ELSE}

  S := CryptProtectString(PWideChar(S));
  WriteLn('protected_string:  ' + S);
  S := CryptUnprotectString(PWideChar(S));
  WriteLn('unprotected_string:  ' + S);

  {$ENDIF}

  ReadLn;
end.


DPAPI.dpr
library DPAPI;

uses
  DPCrypt;

{$R *.res}

function dpApiProtectString(S: PWideChar): PWideChar; stdcall;
begin
  Result := CryptProtectString(S);
end;

function dpApiUnprotectString(S: PWideChar): PWideChar; stdcall;
begin
  Result := CryptUnprotectString(S);
end;


exports
  dpApiProtectString,
  dpApiUnprotectString;

end.
Dimitry Sibiryakov
Дата: 25.11.2019 14:14:26

vdix
Буду рад любой подсказке

Наибольшая вероятность у неинициализированных переменных.
Второе на очереди - порча памяти.

Posted via ActualForum NNTP Server 1.5

vdix
Дата: 25.11.2019 14:22:25
Да вроде как всё инициализируется, память выделяется и освобождается корректно и делается это под каждый запрос в отдельности, у самого враппера никаких переменных нет, только локальные переменные функций, которые тут же и освобождаются.
Dimitry Sibiryakov
Дата: 25.11.2019 14:43:46

DrMemory я бы попробовал напустить.

Posted via ActualForum NNTP Server 1.5

Barmaley57
Дата: 25.11.2019 14:48:48
vdix, не поленился - скомпилил (D2009). У меня всё работает. В любых вариант. Да и в глаз с ходу ничего стремного не бросается.
goldmi45
Дата: 25.11.2019 15:00:19
vdix
"System Error. Code: 127. The specified procedure could not be found"

автор
Crypt32DLL := LoadLibrary('Crypt32.dll');

может, не та dll подгружается...
vdix
Дата: 25.11.2019 15:24:16
Dimitry Sibiryakov
DrMemory я бы попробовал напустить.

Dr. Memory крашится под моей Win10 x64 если передать параметр -ignore_kernel, а без него ругается, что неизвестное ядро и нужно бы с этим параметром запускать.

goldmi45
vdix
"System Error. Code: 127. The specified procedure could not be found"

автор
Crypt32DLL := LoadLibrary('Crypt32.dll');

может, не та dll подгружается...

Да другой у меня в системе нет. И грузится она с одного пути. Просто в одном случае напрямую, во втором - из обёртки.

Barmaley57
vdix, не поленился - скомпилил (D2009). У меня всё работает. В любых вариант. Да и в глаз с ходу ничего стремного не бросается.

А вот это странно. Благодарю! Хотя D2009 уже юникодная, тут не подкопаешься. Больше того, я только что собрал этот же проект на ХЕ2 и он тоже работает! Видимо, проблема в самой Delphi 10.2. Не исключено, что она исправлена в последней версии, но у меня её пока что нет...

Что же, спасибо всем за участие! Не сказать, что проблема решена, но как минимум один выход из создавшейся ситуации есть.
Dimitry Sibiryakov
Дата: 25.11.2019 15:35:22

vdix
Dr. Memory крашится под моей Win10 x64 если передать параметр -ignore_kernel, а без него
ругается, что неизвестное ядро и нужно бы с этим параметром запускать.

А, да, есть у него дурная привычка крашиться на оконных приложениях. Консольные вроде бы
должны работать.

Ну, тогда всегда остаётся старая добрая отладка записью в лог всего, чего ни попадя.

Posted via ActualForum NNTP Server 1.5