Sunday, August 23, 2015

Pointer to a pointer to a pointer

I never thought I'd ever use the datatype void**** in a real project. Yet I did. That's the datatype of a pointer to a smart pointer to a COM object, which contains a pointer to the virtual function table, which is an array of function pointers.

Are you lost yet? Let's recap. The virtual function table is an array of pointers, function pointers to be precise. The pointer to it, which by convention constitutes the first data element in a COM object, is a pointer to an array of pointers, type void**. The interface pointer to a COM object points at the object itself, which is to say, points at the first data element, so it's void***. A smart pointer object holds the interface pointer as its first and only data element (no virtual functions there), so a pointer to a smart pointer object is a pointer to its first data element, so it's void****.

Friday, August 14, 2015

Lack of a typelib is not a security fault

Amazing discovery of the day: Microsoft Word pops up a security prompt for a perfectly good macro if a reference to a typelib can't be resolved.

Here's the scenario. I've built a managed DLL with a COM-visible component to be called from a MS Word macro. When managed DLLs are built in the usual way, they don't have a type library resource in them. Dynamic type discovery for created Automation objects is supported by the framework, but the regular typelib-in-file discovery would fail - for DLLs, it's looking for a custom resource of type "TYPELIB" with ID 1. That's why you can't open a managed DLL in OLEView and see the types.

You can, however, export a .TLB file from a managed DLL either with the "tlbexp" utility, or with "regasm". Both come with the .NET framework. Google it up.

Enter MS Word and Visual Basic for Applications aka VBA, it's tried and true macro language. In order to reference a component library during macro development, one needs the typelib registered; the "References" window of the VBA editor presents a list of typelibs. So on the dev machine, I have the typelib registered anyway.

The runtime environment is not the same as the dev environment, one would think. On the runtime machine, do you need the typelib registered, too? Trick question. In theory, no. Once CLSIDs/IIDs of all relevant objects are stored in the project, and enums are resolved to integers, it should be possible to call methods without a typelib. In fact, JavaScript has no problem with creating and invoking a component without a registered typelib. But not VBA.

When invoked on a machine where the managed coclass is registered, but the respective typelib is not, you get the following message:

"The macro cannot be found or has been disabled because of your Macro security settings."

It's really neither. It's a dependency to the macro that can't be found.

The remedy is obvious: use tlbexp to generate a typelib, ship it along with the DLL, register when installing. A TLB file can't register itself, so some manual registry fiddling would be in order.

Tuesday, July 7, 2015

Abusing operator overloading for expression building

Sharepoint lists have a built-in SOAP API. In order to query a list, one has to build a query in XML, along the following lines:

<And><Eq><FieldRef Name="ID"/><Value>100</Value></Eq><NotNull><FieldRef Name="Name"/></NotNull></And>

Those strings are hard to follow, and building them is error prone. To the rescue comes operator overloading in C#. The idea is: field names are wrapped in class instances, comparison between those and numbers/strings produces a condition object, AND/OR between those produces compound condition objects, on the final stage you serialize that chain into an XML string.

The only caveat is, C# doesn't allow for && and || overloading, so we have to overload the bitwize operators & and | instead. The precedence is still what one would expect.


With that in place, the expression above can be written like that:

CAMLField f_ID = F("ID"), f_Name = F("Name");
(f_ID == 100 & f_Name.NotNull).ToString();

Wednesday, January 14, 2015

Windows Phone vs. CE legacy

The Core Connectivity API totally works for Windows Phone 8.

They've changed CLSIDs of all classes and IIDs of all interfaces, but the new ones can be fairly easily seen in the registry. There are several new interfaces (ICcConnection2/3/4, notably) that make more sense for the Windows Phone security model. There's no arbitrary file/registry access that I can see (those methods return E_ACCESSDENIED), but some methods still work.

This new CoreCon has version numbers 10/11/12, which probably corresponds to Visual Studio 10/12/13 that it comes with. The DLLs are under C:\Program Files (x86)\Common Files\microsoft shared\Phone Tools\CoreCon\12.0\Bin. Most amazingly, the main DLL - ConMan2.dll - has debug symbols on Microsoft Symbol Server. Poking around it in a debugger or in a disassembler is a pleasure, considering.

Let's see how far this takes me. The LaunchApplication() method works, but I wonder what rights in the filesystem do I have...

Saturday, September 27, 2014

Some knowledge deserves to be spread

Upgraded my motherboard recently. With Windows, it's always a gamble. In recent versions, it often just works - Windows boots, some hardware doesn't work but once you install all the OEM drivers, it does.

In my case, not so much. After the hardware swap, Windows booted straight into the blue screen of death. Here's what helped: I booted into recovery mode, downloaded all OEM drivers on a different machine, copied them to a thumb drive, and ran the following command on the recovery console's command line:

dism /image:c:\ /add-driver /Driver:X:\ /recurse

Where C: is the Windows drive, and X: was the thumb drive. And voilĂ , Windows booted up on the next attempt.

Helpful idea found here. Spreading the knowledge because it deserves to be spread.

Thursday, June 19, 2014

Base64 encoding in VBA

Today's useful snippet: Base64 encoding in VBA (not VB.NET; the latter has a builtin API for that).

Encodes an array of bytes into a string. Doesn't make any assumptions about the bounds of the source array. Processes the entire array; can be trivially modified to deal with a array slice.

Public Function ToBase64(Src() As Byte) As String
    Const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

    Dim SrcLen As Long, i As Long, Remainder As Long
    Dim RLen As Long, Triad As Long, LastSrcIndex As Long
    SrcLen = UBound(Src) - LBound(Src) + 1
    Remainder = SrcLen Mod 3
    'Index of the first byte of the leftover 1- or 2-byte chunk
    RLen = LBound(Src) + SrcLen - Remainder
    LastSrcIndex = RLen - 3
    ToBase64 = ""
    
    'The 3 byte chunks of the block
    For i = LBound(Src) To LastSrcIndex Step 3
        Triad = (CLng(Src(i)) * &H10000) Or _
            (CLng(Src(i + 1)) * &H100) Or Src(i + 2)
        ToBase64 = ToBase64 & _
            Mid(ALPHABET, ((Triad \ &H40000) And &H3F) + 1, 1) & _
            Mid(ALPHABET, ((Triad \ &H1000) And &H3F) + 1, 1) & _
            Mid(ALPHABET, ((Triad \ &H40) And &H3F) + 1, 1) & _
            Mid(ALPHABET, (Triad And &H3F) + 1, 1)
    Next
    
    'The remainder, if any
    If Remainder = 1 Then
        ToBase64 = ToBase64 & _
            Mid(ALPHABET, ((Src(RLen) \ 4) And &H3F) + 1, 1) & _
            Mid(ALPHABET, ((Src(RLen) * &H10) And &H3F) + 1, 1) & "=="
    ElseIf Remainder = 2 Then
        Triad = (CLng(Src(RLen)) * &H100) Or Src(RLen + 1)
        ToBase64 = ToBase64 & _
            Mid(ALPHABET, ((Triad \ &H400) And &H3F) + 1, 1) & _
            Mid(ALPHABET, ((Triad \ &H10) And &H3F) + 1, 1) & _
            Mid(ALPHABET, ((Triad * 4) And &H3F) + 1, 1) & "="
    End If
End Function

A useful variation would use a preallocated buffer for the encoded chunk, with assignment to Mid() instead of concatenation. When encoding large pieces (>100KB), it makes sense to save on string allocation and copying.

The formula for the length of a Base64-encoded string is: ((SourceLength + 2) \ 3) * 4.

Friday, June 6, 2014

CryptoAPI issue I've found

Continuing with the crypto theme, this is a story about an issue in Microsoft CryptoAPI that I've discovered a few years ago. It was originally posted at an MSDN forum, but I thought I'd rather republish it here. I've used some pretty deep magic to get to this result.

I was debugging a problem that my customer had. He was trying to move an SSL client certificate from one WinXP SP2 box to another. He exported the certificate into a PFX file. When he was trying to import, he got the following message:

"An internal error occurred. The private key that you are importing might require a cryptographic service provider that is not installed on your system."

The private key in question was 1024-bit RSA with a SHA1 signature - very plain vanilla. We've tried the steps in KB919074 to no effect.

I've written a simple test program that would ask for certificate file path and an export password, then would try to read the certificate into a temp store by means of PFXImportCertStore(). It would fail with error 0x8009000b, NTE_BAD_KEY_STATE.

Further analysis traced the error to the following call stack:

0x77ab0b9c CRYPT32.dll+0x30b9c - CryptProtectData()
0xffeb7ad rsaenh.dll+0x1b7ad - MyCryptProtectData()
0xffebda2 rsaenh.dll+0x1bda2 - TryDPAPI()
0xffdd599 rsaenh.dll+0xd599 - OpenUserKeyGroup()
0xffdeb3e rsaenh.dll+0xeb3e - NTagLogonUser()
0xffded6e rsaenh.dll+0xed6e - CPAcquireContext()
0x77de8307 ADVAPI32.dll+0x18307 - CryptAcquireContextA()
0x77de8675 ADVAPI32.dll+0x18675 - CryptAcquireContextW()
0x77a866c6 CRYPT32.dll+0x66c6 - HCryptProv_Query_Func()
0x77af5609 CRYPT32.dll+0x75609 - ???
0x77aef215 CRYPT32.dll+0x6f215 - CryptImportPKCS8()
0x77af5af3 CRYPT32.dll+0x75af3 - CertImportSafeContents()
0x77aef800 CRYPT32.dll+0x6f800 - PFXImportCertStore()
0x401193 ImpCert.exe+0x1193 (that's my code)

So CryptProtectData fails with NTE_BAD_KEY_STATE, which causes CryptAcquireContext to fail, which causes the import to fail. And that's even before the CryptoAPI starts to do anything about the certificate we're importing. Why would it call CryptProtectData during context acquisition - beats me.

CryptProtectData() calls, via RPC, into the Protected Storage service, which resides in the LSASS. From the disassembly of lsasrv.dll: SPCryptProtect() calls into GetSpecifiedMasterKey(), which can return NTE_BAD_KEY_STATE.

Finally, the answer: The file
c:\Documents and Settings\(username)\Application Data\Microsoft\Protect\CREDHIST
was read-only. Once they cleared the read-only flag, everything was fine, and the certificate imported as expected.

A similar issue is described in the Intuit knowledge base.
Original write-up at the MSDN forum is here.