1

I'm calling CredUnPackAuthenticationBuffer as per this question:

Public Declare PtrSafe Function CredUnPackAuthenticationBuffer Lib "credui" Alias "CredUnPackAuthenticationBufferW" ( _
ByVal dwFlags As LongPtr, _
ByVal pAuthBuffer As LongPtr, _
ByVal cbAuthBuffer As LongPtr, _
ByRef pszUserName As LongPtr, _
ByRef pcchMaxUserName As LongPtr, _
ByRef pszDomainName As LongPtr, _
ByRef pcchMaxDomainName As LongPtr, _
ByRef pszPassword As LongPtr, _
ByRef pcchMaxPassword As LongPtr) _
As LongPtr

Dim res As LongPtr
        Dim usernameBuf As LongPtr
        Dim domainBuf As LongPtr
        Dim passwordBuf As LongPtr
        Dim max As Long
        max = 100
        Dim flags As Long
        flags = CRED_PACK_GENERIC_CREDENTIALS
        
        res = CredUnPackAuthenticationBuffer(flags, ppvOutAuthBuffer, pulOutAuthBufferSize, usernameBuf, MAX_USER_NAME, domainBuf, MAX_DOMAIN, passwordBuf, MAX_PASSWORD)
        Dim error As Long
        error = Err.LastDllError

This call succeeds, error is 0 and usernameBuf, domainBuf and passwordBuf have values.

I'm trying to get the text from these pointers. From MSDN, I know these values are pointers to a null-terminated string. I've tried calling the following code (from here) to get the string data, but byteCount is always 0.

Private Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _
 (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As Long)

Private Declare PtrSafe Function lstrlenW Lib "kernel32.dll" (ByVal lpString As LongPtr) As Long


Public Function StringFromPointerW(ByVal pointerToString As LongPtr) As String

    Const BYTES_PER_CHAR As Integer = 2

    Dim tmpBuffer()    As Byte
    Dim byteCount      As Long
 
    ' determine size of source string in bytes
    byteCount = lstrlenW(pointerToString) * BYTES_PER_CHAR
    
    If byteCount > 0 Then
        ' Resize the buffer as required
        ReDim tmpBuffer(0 To byteCount - 1) As Byte
        
        ' Copy the bytes from pointerToString to tmpBuffer
        Call CopyMemory(VarPtr(tmpBuffer(0)), pointerToString, byteCount)
    End If
 
    ' Straigth assigment Byte() to String possible - Both are Unicode!
    StringFromPointerW = tmpBuffer

End Function

What am I doing wrong? I've tried passing the buffers as String, and StrPtr(), which crashes Word.

Jay
  • 2,077
  • 5
  • 24
  • 39
  • 2
    The function expects `LPWSTR` for `pszUserName`, `pszDomainName` and `pcchMaxPassword`. That means `ByVal As LongPtr`, and you must pass for the values `StrPtr`s of strings that already have the respective number of characters allocated. – GSerg Oct 26 '20 at 14:35
  • Ahh ok, that's the one combination I didn't try. Is there a "correct" way to allocate characters in the string? – Jay Oct 26 '20 at 14:36
  • 2
    Yes, `s = string$(100, vbNullChar)`. – GSerg Oct 26 '20 at 14:37
  • 2
    The respective three buffer length parameters are `ByRef As Long`, not `As LongPtr`. `dwFlags`, `cbAuthBuffer` and the function return value are also `Long`. – GSerg Oct 26 '20 at 14:41
  • that's got it, thanks! – Jay Oct 26 '20 at 14:58

2 Answers2

3

The function expects LPWSTR for pszUserName, pszDomainName and pszPassword. That means ByVal As LongPtr, and you must pass for the values StrPtrs of strings that already have the respective number of characters allocated.
The respective three buffer length parameters are ByRef As Long, not As LongPtr. dwFlags, cbAuthBuffer and the function return value are also Long.
You also should not pass the MAX_ constants directly, as these arguments are supposed to be writable.

Public Declare PtrSafe Function CredUnPackAuthenticationBuffer Lib "credui" Alias "CredUnPackAuthenticationBufferW" ( _
    ByVal dwFlags As Long, _
    ByVal pAuthBuffer As LongPtr, _
    ByVal cbAuthBuffer As Long, _
    ByVal pszUserName As LongPtr, _
    ByRef pcchMaxUserName As Long, _
    ByVal pszDomainName As LongPtr, _
    ByRef pcchMaxDomainName As Long, _
    ByVal pszPassword As LongPtr, _
    ByRef pcchMaxPassword As Long) _
As Long
Dim res As Long
Dim usernameBuf As String
Dim domainBuf As String
Dim passwordBuf As String

Dim flags As Long
flags = CRED_PACK_GENERIC_CREDENTIALS

Dim usernameBufLen As Long : usernameBufLen = MAX_USER_NAME
Dim domainBufLen As Long : domainBufLen = MAX_DOMAIN
Dim passwordBufLen As Long : passwordBufLen = MAX_PASSWORD

usernameBuf = String$(usernameBufLen, vbNullChar)
domainBuf = String$(domainBufLen, vbNullChar)
passwordBuf = String$(passwordBufLen, vbNullChar)

res = CredUnPackAuthenticationBuffer( _
    flags, _
    ppvOutAuthBuffer, _
    pulOutAuthBufferSize, _
    StrPtr(usernameBuf), _
    usernameBufLen, _
    StrPtr(domainBuf), _
    domainBufLen, _
    StrPtr(passwordBuf), _
    passwordBufLen _
)

You don't need a StringFromPointerW. The buffers already contain usable data.

GSerg
  • 76,472
  • 17
  • 159
  • 346
-1

I have had similar problems reading a registry string entry. The normal DECLARE is

  Declare PtrSafe Function RegQueryValueEx Lib "advapi32.dll" Alias _
  "RegQueryValueExA" (ByVal hKey As LongPtr, ByVal lpValueName As String, _
  ByVal lpReserved As LongPtr, lpType As Long _
  , lpData As Any, lpcbData As Long) As Long 

My solution is to add an additional DECLARE where I have replaced the "lpData As Any" by "ByVal lpData As String":

  Declare PtrSafe Function RegQueryValueExString Lib "advapi32.dll" Alias _
  "RegQueryValueExA" (ByVal hKey As LongPtr, ByVal lpValueName As String, _
  ByVal lpReserved As LongPtr, lpType As Long _
  , ByVal lpData As String, lpcbData As Long) As Long

Then I can use e.g.

  Dim myString as String
  myString = String(500, " ")
  err1 = RegQueryValueExString(hKey1, vbNullString, 0&, REG_SZ, myString, 499)
  myString = NullTrim(myString)

As in VBA the 1st NULL character does not terminate a string, that NullTrim() function is required:

Function NullTrim(a As String) As String
  Dim i As Integer
  i = InStr(a, vbNullChar)
  If i = 0 Then
    NullTrim = a 'Suspicious, possibly the buffer was too small
  ElseIf i = 1 Then
    NullTrim = ""
  Else
    NullTrim = Left(a, i - 1)
  End If
End Function
Horst Schmid
  • 194
  • 1
  • 4
  • This has nothing to do with the question. Apart from that, you didn't have to redeclare that argument `byval as string`, you could simply call the originally declared function as `RegQueryValueExString(..., ByVal mystring, 499)`. The whole thing works only because it's an `A` function [rather than](https://stackoverflow.com/a/66262985/11683) a `W` function. Truncating at the first null is also wrong, as the function returns, in the last argument that you are ignoring, the number of characters written in the buffer, and you must take that many of them with `Left$`. – GSerg Feb 24 '23 at 20:50