Mar 27, 2016

PE file signing

When I was investigating DLL loading/initializing in my previous post, I learned that it was not possible to set a breakpoint in kernel32's entry point function. This function was obviously executed before the first chance exception in WinDbg.

I was thinking that if not WinDbg was able to break into the kernel32's entry point function, maybe I could insert the OP-code for debug break in the executable myself? When investigating the possibilities, I learned how to ensure an executable (PE file) is signed, and this is the subject for this post. I'm using WinDbg, Hex Edit and sigcheck on my Windows Vista 32-bit machine.

Well, it all started when I was investigating the order of the execution of entry point functions during the application loading and startup. Ntdll.dll was always loaded first, then kernel32.dll. According to WinDbg, the entry point function for ntdll.dll was 0, meaning ntdll.dll has no entry point function. However, kernel32.dll has an entry point function, but it was executed (and finished) before the first chance exception.

Was it possible to insert 0xCC (op-code for debug break) inside the kernel32.dll's entry point function? If it would have been possible, then WinDbg could break in the entry point function.

While editing the kernel32.dll's entry point function in Hex Edit by Commercial Software Pty, I did not want to overwrite any existing code, I just wanted to insert the 0xCC in the beginning of the entry point function. So when I was about to insert the 0xCC code in the kernel32.dll entry point function, using the Insert Mode, I realized this would of course not work. Since the Insert Mode would have moved the complete .text section 2 bytes, no code that referenced to a certain position in the file would work anymore!

Okay, this was not the only issue. Let's say it was possible to insert 0xCC in the .text section without corrupting anything else, then I needed to ensure this copy of kernel32.dll was loaded into the memory for an application (and not the original one in system32 folder). Normally it is possible to ensure a certain version of a DLL is loaded for an application, by placing the DLL in the application's folder. The DLL search process will first search the current folder for the DLL before proceeding the search. However, this is not the case for kernel32.dll. Even though I've placed a copy of kernel32.dll in the application folder, the loader will not load that copy, the loader will load the correct one from the system32 folder. The reason for this is because kernel32.dll is a well-known DLL, meaning it will always search in the system32 folder.

Well, enough about DLL loading and searching. I was still determined to edit a PE file in some way, but apparently it was not possible to do so in the .text section without corrupt the PE file. However, there are other sections in a PE file that was not affected in the same way.

In my previous posts, I've done some research on notepad.exe, I will continue to do so in this post.

Opening notepad.exe in Hex Edit, we can see part of the .rsrc section below.

Hex Edit - notepad.exe
It should be completely harmless to edit a character here, it should have no impact on the execution.

We can see the string "All rights reserved" above. Before doing any modifications, I've made a copy of notepad.exe to my test folder. Using Hex Edit, I have edit the string to "all rights reserved" (using Overwrite mode).

Hex Edit - notepad.exe is modified
When executing notepad.exe, it is launched correctly without any problems.

At this point, I was starting to think if Windows could recognize that the PE file has been modified? The answer is of course yes! Notepad.exe is signed to ensure that the copy is the original one. If we would found a copy of notepad.exe which is unsigned, we know that this has been modified (and maybe risky to execute).

Using the tool sigcheck by Sysinternals, we can verify if the PE file is signed.

Below I've used sigcheck on the original notepad.exe (from the system32 folder), it is clearly signed.

Sigcheck - notepad.exe is signed

When using sigcheck on the modified notepad.exe (in my test folder), we see that it is unsigned. In other words, sigcheck can easily find PE files which has been modified.

Sigcheck - notepad.exe is modified and unsigned
So how is sigcheck implemented? Which API is sigcheck using to determine whether a PE file is signed or not?

Before digging into sigchecks implementation, I will just present some basic knowledge about signing.

A PE file can be signed in two ways, either by adding an embedded digital signature or by using security catalog files (.CAT files).

By opening the properties dialog of a file, we can see if a file has an embedded digital signature. If the tab "Digital signatures" is present, we know that the file has an embedded signature, an example of this is shown below. Note that my language is in Swedish, it says "Digitala signaturer", this is translated in english to "Digital signatures".

Properties dialog - Embedded signing
My main focus in this post is signing using security catalog files, so I will not discuss embedded signing further.

Notepad.exe is also signed (according to sigcheck), but has no "Digital signatures" tab in the properties dialog.

Properties dialog - notepad.exe is not embedded sign
Notepad.exe is signed using security catalog files. Those catalog files resides in the catroot folders in the system32 folder.
catroot folders
We can easily see which catalog file notepad.exe is belonging to, by using sigcheck again.

Sigcheck- showing the .CAT filename for notepad.exe
By browsing to the path and double click the .CAT file, we can see its hash.
Finding the .CAT file

Showing the security catalog
The hash marked above, is the same has that sigcheck gives (PESHA1), see below.

Sigcheck - Showing hashes
Well, that's it about .CAT files. When I was able to understand embedded signing and signing using security catalogs, I started to wonder how sigcheck was built? Which API and functions was sigcheck using?

So how does sigcheck determine if a PE file is signed?

I must admit, I've spent a lot of time together with my friend WinDbg when debugging sigcheck together with the original/modified version of notepad.exe. I've stepped through the machine code several times, made some notes about the executed functions and branching until I got a clue what was going on. Then I got into detail of the important functions (reading MSDN), their return values and the consequences.

Before going into some detail of the executed functions, I summarize my conclusions in a short version below.

To verify if notepad.exe is signed, the WinVerifyTrust function is used. This function resides in wintrust.dll. First sigcheck tries to verify the signing from the file i.e. notepad.exe (using WinVerifyTrust). Then sigcheck calls WinVerifyTrust again, but this time only to verify the hash (not actual signing). Both these calls fails, since WinVerifyTrust returns a non zero value. The execution moves on to CryptCATAdminCalcHashFromFileHandle, which calculates the hash from notepad.exe. This hash is then passed to the function CryptCATAdminEnumCatalogFromHash, which returns a HCATINFO. HCATINFO is valid if a catalog context could be found from the hash, which is true in this case. So the execution continues by calling WinVerifyTrust again, this time by verify notepad.exe using security catalogs. This time WinVerifyTrust succeeds and returns a zero value meaning sigcheck could verify that notepad.exe was signed. Finally another call is made the WinVerifyTrust to clean up the memory.

This was the short version, if you are up for it, here follows the long version.

We start by Open the executable (sigcheck.exe) and defines the arguments and start directory.

WinDbg - Setting up debugging for sigcheck.exe
As mentioned in the short version, the functions of interest is WinVerifyTrust, CryptCATAdminCalcHashFromFileHandle and CryptCATAdminEnumCatalogFromHash, so I'm going to set a break point in these functions, so we easily can see which information is processed by these functions.

The WinVerifyTrust function is well documented on MSDN. When it returns 0, we know that verification was OK. The parameters in WinVerifyTrust is repeated below for convenience;

LONG WINAPI WinVerifyTrust(
  _In_ HWND   hWnd,
  _In_ GUID   *pgActionID,
  _In_ LPVOID pWVTData
);

WinDbg - Setting breakpoints
When starting the execution, we first encounter a break in WinVerifyTrust using the arguments according to below.

hWnd = 0,
GUID = 0x0044d8f0 {00AAC56B-CD44-11d0-8CC2-00C04FC295EE}
pWVTData = 0x0012A18C



WinDbg - WinVerifyTrust

The GUID, is defined in sigcheck.exe, and when I'm searching for this GUID on the net, I find that this GUID corresponds to WINTRUST_ACTION_GENERIC_VERIFY_V2 GUID action, which is defined as "Verify a file or object using the Authenticode policy provider." according to MSDN.

The input pWVTData = 0x0012A18C does not tell us much, but when dumping the memory, and comparing to the WIN_TRUST data structure, we can get more information.

The WIN_TRUST data structure is well documented on MSDN as well, and is given below for convenience.

WINTRUST_DATA structure
From the dumped memory and the definition above, we can conclude following;

cbStruct = 0x30;
pPolicyCallbackData = 0x00;
pSIPClientData = 0x00;
dwUIChoice =0x02;
fdwRevocationChecks = 0x40;
dwUnionChoice = 0x01;
pFile = 0x0012a178;
dwStateAction = 0x01; //WTD_STATEACTION_VERIFY
hWVTStateData = 0x00;
pwszURLReference = 0x00;
dwProvFlags = 0x00;
dwUIContext = 0x00;
pSignatureSettings = 0x00;

As we can see, the dwUnionChoice is 0x01, which corresponds to WTD_CHOICE_FILE meaning, "Use the file pointed to by pFile" according to MSDN.

pFile in this case is 0x0012a178.

The pFile member is a pointer to a WINTRUST_FILE_INFO struct, which is defined at MSDN, but is given here for convenience.

typedef struct WINTRUST_FILE_INFO_ {
  DWORD   cbStruct;
  LPCWSTR pcwszFilePath;
  HANDLE  hFile;
  GUID    *pgKnownSubject;
} WINTRUST_FILE_INFO, *PWINTRUCT_FILE_INFO;

Dumping memory at 0x0012a178 gives us;


WinDbg - Memory dump of WINTRUST_FILE_INFO
We can conclude following;

cbStruct = 0x10;
pcwszFilePath = 0x0012bf00;
hFile = 0x00;
*pgKnownSubject = 0x00;

Looking into memory at location 0x0012bf00, we see that the filename is notepad.exe.


WinDbg - Memory showing the WINTRUST_FILE_INFO member pcwszFilePath
So from this first break, we can conclude that WinVerifyTrust was trying to verify the signature from the file.

When stepping through WinVerifyTrust, this call fails, it returns 0x800b0100.

WinDbg - return value from WinVerifyTrust
MSDN says "If the trust provider verifies that the subject is trusted for the specified action, the return value is zero. No other value besides zero should be considered a successful return."

This particular error code corresponds to TRUST_E_NOSIGNATURE according to some research I've made on the net.

Stepping further in WinDbg, we encounter the next breakpoint in WinVerifyTrust with the arguments according to below.

hWnd = 0,
GUID = 0x0044d8f0 {00AAC56B-CD44-11d0-8CC2-00C04FC295EE}
pWVTData = 0x0012A18C



WinDbg - WinVerifyTrust
The GUID, is the same as in the previous call, as well as the pWVTData pointer.

However, when dumping the pWVTData, we can see that one member differ from the previous call. This is marked in the red rectangle above.

From the dumped memory and the definition above, we can conclude following;

cbStruct = 0x30;
pPolicyCallbackData = 0x00;
pSIPClientData = 0x00;
dwUIChoice =0x02;
fdwRevocationChecks = 0x40;
dwUnionChoice = 0x01;
pFile = 0x0012a178;
dwStateAction = 0x01; //WTD_STATEACTION_VERIFY
hWVTStateData = 0x00;
pwszURLReference = 0x00;
dwProvFlags = 0x00000200; //WTD_HASH_ONLY_FLAG
dwUIContext = 0x00;
pSignatureSettings = 0x00;

According to MSDN, the value of dwProvFlags corresponds to WTD_HASH_ONLY_FLAG, which means "Only the hash is verified.".

So from this second break, we can conclude that WinVerifyTrust was trying to verify the signature from the file in a slightly different way than the first call.

When stepping through WinVerifyTrust, this call fails, it returns 0x800b0100.

WinDbg - return value from WinVerifyTrust
Obviously, even this call fails.

Stepping further in WinDbg, we encounter the next breakpoint.

This time, WinDbg breaks in CryptCATAdminCalcHashFromFileHandle. This function is documented on MSDN and its parameters is given below for convenience.

BOOL CryptCATAdminCalcHashFromFileHandle(
  _In_    HANDLE hFile,
  _Inout_ DWORD  *pcbHash,
  _In_    BYTE   *pbHash,
  _In_    DWORD  dwFlags
);

Below, we have just finished CryptCATAdminCalcHashFromFileHandle, and dumped the memory to show the calculated hash.



WinDbg - CryptCATAdminCalcHashFromFileHandle
When continue the execution, WinDbg break in the function CryptCATAdminEnumCatalogFromHash next time.

According to MSDN, CryptCATAdminEnumCatalogFromHash parameters is defined as below.

HCATINFO CryptCATAdminEnumCatalogFromHash(
  _In_ HCATADMIN hCatAdmin,
  _In_ BYTE      *pbHash,
  _In_ DWORD     cbHash,
  _In_ DWORD     dwFlags,
  _In_ HCATINFO  *phPrevCatInfo
);



WinDbg - CryptCATAdminEnumCatalogFromHash
Above, we see that CryptCATAdminEnumCatalogFromHash takes the calculated hash (at memory location 0012a1e8), which was calculated from CryptCATAdminCalcHashFromFileHandle.

Stepping further and execute CryptCATAdminEnumCatalogFromHash, we see that this function returns a non zero value, i.e. we have successfully found a HCATINFO.


WinDbg - return value from CryptCATAdminEnumCatalogFromHash

Since we found a valid HCATINFO, sigcheck proceeds and make another call to WinVerifyTrust, this time according to the arguments below.

hWnd = 0,
GUID = 0x0044d8e0 {00AAC56B-CD44-11d0-8CC2-00C04FC295EE}
pWVTData = 0x0012A18C




WinDbg - WinVerifyTrust
This is the same parameters as in the other two calls to WinVerifyTrust.

The GUID, is the same as in the previous call to WinVerifyTrust, as well as the pWVTData pointer.

However, when dumping the pWVTData, we can see that some member differ from the previous calls. This is marked in the red rectangle above.

From the dumped memory and the definition above, we can conclude following;

cbStruct = 0x30;
pPolicyCallbackData = 0x00;
pSIPClientData = 0x00;
dwUIChoice =0x02;
fdwRevocationChecks = 0x01;
dwUnionChoice = 0x02;
pCatalog = 0x00129E70;
dwStateAction = 0x01; //WTD_STATEACTION_VERIFY
hWVTStateData = 0x00;
pwszURLReference = 0x00;
dwProvFlags = 0x00;
dwUIContext = 0x00;
pSignatureSettings = 0x00;

As we can see, the dwUnionChoice is 0x02, which corresponds to WTD_CHOICE_CATALOG, meaning, "Use the catalog pointed to by pCatalog" according to MSDN.

pCatalog in this case is 0x00129E70.

The pCatalog member is a pointer to a WINTRUST_CATALOG_INFO struct, which is defined at MSDN, but is given here for convenience.

typedef struct WINTRUST_CATALOG_INFO_ {
  DWORD                                 cbStruct;
  DWORD                                 dwCatalogVersion;
  LPCWSTR                               pcwszCatalogFilePath;
  LPCWSTR                               pcwszMemberTag;
  LPCWSTR                               pcwszMemberFilePath;
  HANDLE                                hMemberFile;
  _Field_size(cbCalculatedFileHash)BYTE *pbCalculatedFileHash;
  DWORD                                 cbCalculatedFileHash;
  PCCTL_CONTEXT                         pcCatalogContext;
  HCATADMIN                             hCatAdmin;
} WINTRUST_CATALOG_INFO, *PWINTRUST_CATALOG_INFO;

Dumping memory at 0x00129e70 gives us;

WinDbg - Memory dump of WINTRUST_CATALOG_INFO
We can conclude following;

cbStruct = 0x24;
dwCatalogVersion =  0x00;
pcwszCatalogFilePath = 0x00129f3c;
pcwszMemberTag = 0x00129ea4;
pcwszMemberFilePath = 0x0012bf00;
hMemberFile = 0x00;
*pbCalculatedFileHash = 0x0012a1e8;
cbCalculatedFileHash = 0x00000014;
pcCatalogContext = 0x00;
hCatAdmin = 0x0012a358;

From previous call, we already know that 0x0012bf00 corresponds to the filename notepad.exe.

Looking into memory at location 0x00129f3c, we see that the pcwszCatalogFilePath ;

WinDbg - Memory showing the member pcwszCatalogFilePath

Looking into memory at location 0x0012a1e8, we see that the *pbCalculatedFileHash;

WinDbg - Memory showing the member pbCalculatedFileHash
From the screenshot above, we can see that the hash = 0x6B40..., i.e, is the same as the one in PESHA1 in the screenshot from sigcheck.

When stepping through WinVerifyTrust, this call succeeds, it returns 0.

WinDbg - return value from WinVerifyTrust
 So this is the first call that succeeds.

Stepping further in WinDbg, we encounter the final breakpoint.

The fourth time, WinVerifyTrust is called using;

hWnd = 0,
GUID = 0x0044d8f0 {00AAC56B-CD44-11d0-8CC2-00C04FC295EE}
pWVTData = 0x0012A18C



WinDbg - WinVerifyTrust
However, when dumping the pWVTData, we can see that some member differ from the previous calls. This is marked in the red rectangle above.

From the dumped memory and the definition above, we can conclude following;

cbStruct = 0x30;
pPolicyCallbackData = 0x00;
pSIPClientData = 0x00;
dwUIChoice =0x02;
fdwRevocationChecks = 0x01;
dwUnionChoice = 0x02;
pCatalog = 0x00129E70;
dwStateAction = 0x02; //WTD_STATE_ACTION_CLOSE
hWVTStateData = 0x001924c0;
pwszURLReference = 0x00;
dwProvFlags = 0x00;
dwUIContext = 0x00;
pSignatureSettings = 0x00;

Above, dwStateAction differ, 0x02 corresponds to WTD_STATE_ACTION_CLOSE ,which means "Free the hWVTStateData member previously allocated with the WTD_STATEACTION_VERIFY action. This action must be specified for every use of the WTD_STATEACTION_VERIFY action." according to MSDN.

Also hWVTStateData differ, in previous calls it was 0x00, in this call it is 0x001924c0, which is the handle to the state data.

This call to WinVerifyTrust is probably not to do the verification, it is a call to make sure we clean up the memory.

Let's checkout the contents of the pCatalog.

Dumping memory at 0x00129e70 gives us;

WinDbg - Memory dump of WINTRUST_CATALOG_INFO
This does not seems like a WINTRUST_CATALOG_INFO struct.

The valid contents of pCatalog has probably been removed, and it is not important in this call, since we only want to clean up the memory. As we can see in the screenshot, we don't care about the return value (eax). Immediately after the WinVerifyTrust function, a xor eax, eax instruction is executed, meaning we loose (and don't care) about the return value.

So we can conclude from this four calls that, the first one tries to verify the file, like the second call. The difference is just that the second call only tries only to verify to hash. Both calls fails. The third one verifies the file from the catalogs, which is successful. The last one just clean up the memory.

Before ending this post, I will do a similar procedure with the notepad.exe which is modified and check out the difference.

Do you remember? In the modified version of notepad.exe, I replaced the a to an A, which shown again below.
Hex Edit - notepad.exe

Hex Edit - notepad.exe is modified
Now, let's start WinDbg and set breakpoints according to below

WinDbg - Setting breakpoints
When starting the execution, we encounter two calls to WinVerifyTrust, exactly as when debugging sigcheck with the original notepad.exe. Those two calls fails, like when we tried to verify the original notepad.exe.
The difference appears when we call CryptCATAdminCalcHashFromFileHandle.



WinDbg - CryptCATAdminCalcHashFromFileHandle
As we can see above, this is not the same hash as when we were using the original notepad.exe.

This hash is later passed to CryptCATAdminEnumCatalogFromHash (just as when we were verifying the original notepad.exe), which does not find an security catalog

WinDbg - return value from CryptCATAdminEnumCatalogFromHash
According to MSDN CryptCATAdminEnumCatalogFromHash return value is defined as "The return value is a handle to the catalog context or NULL, if there are no more catalogs to enumerate or retrieve".

In this case, the return value is NULL, which means we don't find any security catalog for this calculated hash. Further, since the we don't find any security catalog, we don't proceed with any further calls to WinVerifyTrust as when we were verifying the original notepad.exe

This became a long post, and I must confess I've spent several hours of debugging sigcheck. However, I've learned a lot. I also learned that there are a lot more to learn.

You are welcome to leave comments, complaints or questions!

3 comments:

  1. Incredibly useful, many thanks!

    ReplyDelete
  2. Great writeup! I was wondering if you knew how sigcheck dumps all of the hashes from a catalog when running with -d argument? Appreciate any info you might have!

    ReplyDelete
  3. good writeup.

    Just a few pointers that helped me:

    1. WinVerifyTrust is __fastcall which on x64 means that the GUID will be in rdx and the pWVTData param will be in r8.

    2. if you load symbols for ole32 you can visually display WINTRUST_DATA using dt WINTRUST_DATA @r8 (or in your case dt WINTRUST_DATA 0x0012A18C) instead of parsing it manually.

    cheers!

    ReplyDelete