Ido-Moshe-Github/CiDllDemo

Functions for signatures in catalogs

mihaly044 opened this issue · 4 comments

I've followed your fantastic writeup and also wrote a function to calculate PE digests for CiCheckSignedFile.
But what do I do when the signature isn't contained within the security directory of the PE (+ VA) but in a catalog file? I suppose I need to use CiVerifyHashInCatalog and CiFindPageHashesInCatalog but I have been unable to find any documentation on those exports.
Could you please give us prototypes for the above mentioned functions?

Is the file signed by microsoft, if so you can just use the CiValidateFileObject ? Also, can you share the function to calculate the PE digests ?

The CiValidateFileObject function works flawlessly however it is not available on pre-win10 OSes.
So my only choice is to use CiCheckSignedFile, but that does not work for files signed with a catalog file, because the security directory of such files is empty.

Furthermore, I have been trying to reverse engineer the function prototype for CiVerifyHashInCatalog myself and came up with this:

NTSTATUS _stdcall CiVerifyHashInCatalog(
    PVOID digest,                       // rcx
    int digestSize,                     // rdx
    int digestIdentifier,               // r8d
    PVOID probablySomeOutValue          // r9d
);

However this always returns STATUS_INVALID_IMAGE_HASH even when I feed it with a knowingly correct hash. I'm clueless at this point and need someone to release actual function prototypes for other exports from CI.dll too.

I have published the code for calculating PE digests here: link

@mihaly044
Thanks.

Since I'm extremely busy at the moment I won't be available to thoroughly research this. However, I gave it a quick look:
You can see that parameter 7 of CiVerifyHashInCatalog is PolicyInfo. I suspect that a10 is also a PolicyInfo and that generally, parameters a7-a12 are all output parameters - a8 being a UNICODE_STRING, a9 might be a timestamp (though I cannot confirm this without trying the function).
a4 seems to be a BOOL which might be related to whether you are in system process's context or not. a5 also seems to be a BOOL which controls some behavior of the function - unclear to me.

The inner function MinCrypK_VerifyHashInCatalog is also able to receive the catalog name (UNICODE_STRING) as parameter 8, but it seems CiVerifyHashInCatalog chooses to pass nullptr there - probably because it looks for the catalog itself.

I'd say your best chances at cracking this are:

  1. Finding a driver which uses it
  2. Looking at the memory of the parameters you pass to the function (the out parameters). The fact that you get STATUS_INVALID_IMAGE_HASH and not some INVALID_PARAMETER is a good start - and you might see something interesting in the memory view.
  3. Crossing the inner functions with other places that use them in order to gather clues about their logic.

Good luck and let us know what you discover :)

@Ido-Moshe-Github, @eduardomsb
I think I have got it working to an extent.

Before reading any further, please keep in mind that I am not an expert in reverse engineering, nor security. I'm just fiddling around trying to see what works and what doesn't. Take my assumptions with a grain of salt. They might be misleading or plain wrong.

Okay so here's my findings:

  • I think the function prototype for CiVerifyHashInCatalog might look something like this:
/**
* Vista-Win8.1 only. Use CiValidateFileObject on Win10!
* Checks if the SHA-1 message digest is contained within a verified system catalog
*/
EXTERN_C DECLSPEC_IMPORT NTSTATUS __fastcall CiVerifyHashInCatalog(
	_In_ PCUCHAR digest,      // pointer to the digest itself
	_In_ int digestSize,		// 14h for SHA1
	_In_ int digestIdentifier,	// 8004h for SHA1
	_In_ int a4,			// system context?
	_In_ int a5,			// always 0
	_In_ int a6,			// always 2007Fh
	_Out_ POLICY_INFORMATION * policyInfo,	
	_Out_opt_ UNICODE_STRING* catalogName,
	_Out_ LARGE_INTEGER* signingTime,
	_Out_ POLICY_INFORMATION * policyInfo2
);
// the __stdcall modifier has been omitted here as it compiles to __fastcall on x64 anyway.
  • AppID.sys does import from CI.dll and uses respective functions in appid!SrpVerifyDll and other places. This is a kernel driver Microsoft® AppLocker® uses.
  • This is just a wild guess: On x64 Windows, the system redirects paths from System32 to SysWOW64 for 32 bit applications. Hence, System32/cmd.exe becomes SysWOW64/cmd.exe and so on. This would cause ambiguities when calculating the PE image hash. Attaching to the system process bypasses this redirection:
KAPC_STATE apc;
KeStackAttachProcess(PsInitialSystemProcess, &apc);
// Do what needs to be done here. Calc PE hash, check certificates, etc.
KeUnstackDetachProcess(&apc);
  • If a call CiVerifyHashInCatalog has resulted in a STATUS_INVALID_IMAGE_HASH, AppID calls it again with setting param a4 to 1. I suppose it attempts to free up previously allocated resources.

  • Parameter 6 seems to be a 4 bytes long bitmask for controlling what kind of catalogs shall the function check for. Passing 2007Fh seems to work. I do not know the exact meaning of this number nor if there are any other types.

  • The ptrToCertChainMembers member does not seem to point to a valid location on success.

  • It does only ever return STATUS_SUCCESS on Vista, 8, 8.1 and 7 OSes. CiValidateFileObject however works in all scenarios where available.

Summarizing all that, I think I have achieved what I wanted:
Also check for catalogs on pre-10 OSes and I can keep using CiValidateFileObject on Win10.
I now consider this issue solved.