|
#include "CertInjector.h" |
|
|
|
#include "MinHook.h" |
|
|
|
#include <string> |
|
#include <unordered_set> |
|
|
|
#define COUNT_OF(arr) (sizeof(arr) / sizeof(*arr)) |
|
|
|
// #define PRINT_DEBUG |
|
#ifdef PRINT_DEBUG |
|
#define CONSOLE_DEBUG |
|
#define SHOW_CERTS |
|
#ifdef CONSOLE_DEBUG |
|
#define DebugMsgA(caption, fmt, ...) (printf("[" caption "] " fmt "\r\n", __VA_ARGS__)) |
|
#define DebugMsgW(caption, fmt, ...) (_wprintf_p(L"[" caption L"] " fmt L"\r\n", __VA_ARGS__)) |
|
#else |
|
#define _CRT_SECURE_NO_WARNINGS |
|
#define DebugMsgA(caption, ...) do { char buf[500] = { 0 }; snprintf(buf, sizeof(buf), __VA_ARGS__); MessageBoxA(NULL, buf, caption, MB_OK); } while (0) |
|
#define DebugMsgW(caption, ...) do { WCHAR buf[500] = { 0 }; _snwprintf_s(buf, sizeof(buf), __VA_ARGS__); MessageBoxW(NULL, buf, caption, MB_OK); } while (0) |
|
#endif |
|
#else |
|
#define DebugMsgA(...) |
|
#define DebugMsgW(...) |
|
#endif |
|
|
|
thread_local INT iActiveStage = 0; |
|
static std::unordered_set<std::string> ussTrustedCertBuffers; |
|
static decltype(&CertGetCertificateChain) fpCertGetCertificateChain; |
|
static decltype(&CertAddEncodedCertificateToStore) fpCertAddEncodedCertificateToStore; |
|
|
|
static BOOL WINAPI DetourCertGetCertificateChain( |
|
HCERTCHAINENGINE hChainEngine, |
|
PCCERT_CONTEXT pCertContext, |
|
LPFILETIME pTime, |
|
HCERTSTORE hAdditionalStore, |
|
PCERT_CHAIN_PARA pChainPara, |
|
DWORD dwFlags, |
|
LPVOID pvReserved, |
|
PCERT_CHAIN_CONTEXT *ppChainContext |
|
) |
|
{ |
|
BOOL bRet = fpCertGetCertificateChain(hChainEngine, pCertContext, pTime, hAdditionalStore, pChainPara, dwFlags, pvReserved, const_cast<PCCERT_CHAIN_CONTEXT *>(ppChainContext)); |
|
INT iCurrentActiveStage = iActiveStage; |
|
if (iCurrentActiveStage == 1) iCurrentActiveStage = 0; |
|
INT iToBeActiveStage = iCurrentActiveStage; |
|
if (bRet && ppChainContext && *ppChainContext) { |
|
DWORD dwErrorStatus = (*ppChainContext)->TrustStatus.dwErrorStatus; |
|
BOOL bConsiderChange = (dwErrorStatus == CERT_TRUST_IS_PARTIAL_CHAIN || dwErrorStatus == CERT_TRUST_IS_UNTRUSTED_ROOT || dwErrorStatus == CERT_TRUST_NO_ERROR) && pCertContext && pCertContext->pbCertEncoded && pCertContext->cbCertEncoded; |
|
DWORD dwCorrectStatus = iCurrentActiveStage == 0 ? CERT_TRUST_IS_PARTIAL_CHAIN : CERT_TRUST_IS_UNTRUSTED_ROOT; |
|
DebugMsgA("Note", "bConsiderChange: %d, dwCorrectStatus: %d, iCurrentActiveStage: %d, need change: %d, detail: %u", bConsiderChange ? 1 : 0, dwCorrectStatus ? 1 : 0, iCurrentActiveStage, dwErrorStatus != dwCorrectStatus ? 1 : 0, dwErrorStatus); |
|
if (dwErrorStatus == dwCorrectStatus) bConsiderChange = FALSE; |
|
if (bConsiderChange) { |
|
BOOL bCorrectable = ussTrustedCertBuffers.find(std::string(reinterpret_cast<char*>(pCertContext->pbCertEncoded), pCertContext->cbCertEncoded)) != ussTrustedCertBuffers.end(); |
|
DebugMsgA("Note", "count %d", (*ppChainContext)->cChain); |
|
for (DWORD i = 0; !bCorrectable && i < (*ppChainContext)->cChain; i++) { |
|
PCERT_SIMPLE_CHAIN pSimpleChain = (*ppChainContext)->rgpChain[i]; |
|
if (!pSimpleChain->cElement) continue; |
|
PCCERT_CONTEXT pBaseContext = pSimpleChain->rgpElement[0]->pCertContext; |
|
BOOL bStartWithCert = pCertContext->cbCertEncoded == pBaseContext->cbCertEncoded && !memcmp(pCertContext->pbCertEncoded, pBaseContext->pbCertEncoded, pCertContext->cbCertEncoded); |
|
BOOL bHaveMatch = FALSE; |
|
for (DWORD j = 0; j < pSimpleChain->cElement; j++) { |
|
PCCERT_CONTEXT pCurContext = pSimpleChain->rgpElement[j]->pCertContext; |
|
if (ussTrustedCertBuffers.find(std::string(reinterpret_cast<char*>(pCurContext->pbCertEncoded), pCurContext->cbCertEncoded)) != ussTrustedCertBuffers.end()) { |
|
bHaveMatch = TRUE; |
|
#ifndef SHOW_CERTS |
|
break; |
|
#endif |
|
} |
|
#ifdef SHOW_CERTS |
|
DWORD cbString = 0; |
|
if (!CryptBinaryToStringA(pCurContext->pbCertEncoded, pCurContext->cbCertEncoded, CRYPT_STRING_BASE64HEADER, NULL, &cbString)) { |
|
DebugMsgA("Note", "CryptBinaryToStringA failed"); |
|
} |
|
LPSTR pbString = new char[cbString + 10]; |
|
if (!CryptBinaryToStringA(pCurContext->pbCertEncoded, pCurContext->cbCertEncoded, CRYPT_STRING_BASE64HEADER, pbString, &cbString)) { |
|
DebugMsgA("Note", "CryptBinaryToStringA failed"); |
|
} |
|
pbString[cbString] = 0; |
|
DebugMsgA("Cert", "%d:\r\n%s", j, pbString); |
|
delete[] pbString; |
|
#endif |
|
} |
|
DebugMsgA("Note", "start: %d, elements: %d, have match: %d", bStartWithCert ? 1 : 0, (int)pSimpleChain->cElement, bHaveMatch); |
|
if (bStartWithCert && bHaveMatch) { |
|
bCorrectable = TRUE; |
|
} |
|
} |
|
if (bCorrectable) { |
|
(*ppChainContext)->TrustStatus.dwErrorStatus = dwCorrectStatus; |
|
DebugMsgA("Note", "changed, bCorrectable: %d", bCorrectable); |
|
} |
|
} |
|
if (iToBeActiveStage == 0 && (*ppChainContext)->TrustStatus.dwErrorStatus == CERT_TRUST_IS_PARTIAL_CHAIN) iToBeActiveStage = 1; |
|
else iToBeActiveStage = 0; |
|
} |
|
else iToBeActiveStage = 0; |
|
if (iToBeActiveStage != iCurrentActiveStage) { |
|
iActiveStage = iToBeActiveStage; |
|
} |
|
return bRet; |
|
} |
|
|
|
static BOOL WINAPI DetourCertAddEncodedCertificateToStore( |
|
HCERTSTORE hCertStore, |
|
DWORD dwCertEncodingType, |
|
const BYTE* pbCertEncoded, |
|
DWORD cbCertEncoded, |
|
DWORD dwAddDisposition, |
|
PCCERT_CONTEXT* ppCertContext |
|
) |
|
{ |
|
BOOL bRet = fpCertAddEncodedCertificateToStore(hCertStore, dwCertEncodingType, pbCertEncoded, cbCertEncoded, dwAddDisposition, ppCertContext); |
|
if (bRet && iActiveStage == 1) { |
|
iActiveStage = 2; |
|
for (auto&& sCertBuffer : ussTrustedCertBuffers) { |
|
fpCertAddEncodedCertificateToStore(hCertStore, dwCertEncodingType, reinterpret_cast<const BYTE *>(sCertBuffer.data()), static_cast<DWORD>(sCertBuffer.size()), dwAddDisposition, ppCertContext); |
|
} |
|
} |
|
else iActiveStage = 0; |
|
return bRet; |
|
} |
|
|
|
static void AddCertFile(LPCWSTR wszCertCrtPath) |
|
{ |
|
HANDLE hFileHandle = CreateFileW(wszCertCrtPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); |
|
if (hFileHandle == INVALID_HANDLE_VALUE) return; |
|
DebugMsgW(L"Adding Cert", L"%s", wszCertCrtPath); |
|
DWORD cbCertBufferSize = GetFileSize(hFileHandle, NULL); |
|
char* pbCertBuffer = static_cast<char*>(malloc(cbCertBufferSize)); |
|
if (!ReadFile(hFileHandle, pbCertBuffer, cbCertBufferSize, &cbCertBufferSize, 0)) |
|
{ |
|
free(pbCertBuffer); |
|
CloseHandle(hFileHandle); |
|
return; |
|
} |
|
if (pbCertBuffer && *pbCertBuffer == '-') |
|
{ |
|
DWORD cbBinary = 0; |
|
if (!CryptStringToBinaryA(reinterpret_cast<char*>(pbCertBuffer), cbCertBufferSize, CRYPT_STRING_BASE64HEADER, NULL, &cbBinary, NULL, NULL)) |
|
{ |
|
MessageBox(NULL, __TEXT("CryptStringToBinaryA Calc Length Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
} |
|
char* pbCertDerBuffer = static_cast<char*>(malloc(cbBinary)); |
|
if (!CryptStringToBinaryA(reinterpret_cast<char*>(pbCertBuffer), cbCertBufferSize, CRYPT_STRING_BASE64HEADER, reinterpret_cast<BYTE*>(pbCertDerBuffer), &cbBinary, NULL, NULL)) |
|
{ |
|
MessageBox(NULL, __TEXT("CryptStringToBinaryA Convert Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
} |
|
free(pbCertBuffer); |
|
pbCertBuffer = pbCertDerBuffer; |
|
cbCertBufferSize = cbBinary; |
|
} |
|
CloseHandle(hFileHandle); |
|
ussTrustedCertBuffers.emplace(pbCertBuffer, pbCertBuffer + cbCertBufferSize); |
|
} |
|
|
|
void DoCertInjector() |
|
{ |
|
WCHAR wszCertCrtPath[MAX_PATH]; |
|
if (!GetModuleFileNameW(NULL, wszCertCrtPath, COUNT_OF(wszCertCrtPath))) |
|
{ |
|
MessageBox(NULL, __TEXT("Get Module File Name Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
WCHAR* pwcLastSep = wcsrchr(wszCertCrtPath, L'\\'); |
|
WCHAR* pwcLastPosixSep = wcsrchr(wszCertCrtPath, L'/'); |
|
if (pwcLastPosixSep > pwcLastSep) |
|
{ |
|
pwcLastSep = pwcLastPosixSep; |
|
} |
|
if (pwcLastSep && *pwcLastSep) |
|
{ |
|
pwcLastSep[1] = 0; |
|
} |
|
WCHAR wszSearchPattern[MAX_PATH] = { 0 }; |
|
if (!wcscpy_s(wszSearchPattern, wszCertCrtPath) && !wcscat_s(wszSearchPattern, L"*.crt")) { |
|
WIN32_FIND_DATAW findData = { 0 }; |
|
HANDLE hFileFind = FindFirstFileW(wszSearchPattern, &findData); |
|
if (INVALID_HANDLE_VALUE != hFileFind) |
|
{ |
|
WCHAR* pwcAppendPtr = wszCertCrtPath + wcslen(wszCertCrtPath); |
|
size_t szExtraSize = MAX_PATH - (pwcAppendPtr - wszCertCrtPath); |
|
do |
|
{ |
|
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) |
|
{ |
|
BOOL matched = FALSE; |
|
for (const WCHAR* prefix : { L"hexrays", L"hexvault", L"lumina" }) { |
|
if (!wcsncmp(prefix, findData.cFileName, wcslen(prefix))) { |
|
matched = TRUE; |
|
break; |
|
} |
|
} |
|
if (matched && !wcscpy_s(pwcAppendPtr, szExtraSize, findData.cFileName)) { |
|
AddCertFile(wszCertCrtPath); |
|
} |
|
} |
|
} while (FindNextFileW(hFileFind, &findData)); |
|
FindClose(hFileFind); |
|
*pwcAppendPtr = 0; |
|
} |
|
} |
|
if (!wcscat_s(wszCertCrtPath, L"trusted_certs\\")) |
|
{ |
|
if (!wcscpy_s(wszSearchPattern, wszCertCrtPath) && !wcscat_s(wszSearchPattern, L"*.crt")) { |
|
WIN32_FIND_DATAW findData = { 0 }; |
|
HANDLE hFileFind = FindFirstFileW(wszSearchPattern, &findData); |
|
if (INVALID_HANDLE_VALUE != hFileFind) |
|
{ |
|
WCHAR* pwcAppendPtr = wszCertCrtPath + wcslen(wszCertCrtPath); |
|
size_t szExtraSize = MAX_PATH - (pwcAppendPtr - wszCertCrtPath); |
|
do |
|
{ |
|
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) |
|
{ |
|
if (!wcscpy_s(pwcAppendPtr, szExtraSize, findData.cFileName)) { |
|
AddCertFile(wszCertCrtPath); |
|
} |
|
} |
|
} while (FindNextFileW(hFileFind, &findData)); |
|
FindClose(hFileFind); |
|
} |
|
} |
|
} |
|
if (MH_OK != MH_Initialize()) |
|
{ |
|
MessageBox(NULL, __TEXT("MH Hook Init Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
if (MH_OK != MH_CreateHook(CertGetCertificateChain, &DetourCertGetCertificateChain, |
|
reinterpret_cast<LPVOID*>(&fpCertGetCertificateChain))) |
|
{ |
|
MessageBox(NULL, __TEXT("CertGetCertificateChain Hook Init Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
if (MH_OK != MH_CreateHook(CertAddEncodedCertificateToStore, &DetourCertAddEncodedCertificateToStore, |
|
reinterpret_cast<LPVOID*>(&fpCertAddEncodedCertificateToStore))) |
|
{ |
|
MessageBox(NULL, __TEXT("CertAddEncodedCertificateToStore Hook Init Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
if (MH_OK != MH_EnableHook(CertGetCertificateChain)) |
|
{ |
|
MessageBox(NULL, __TEXT("CertGetCertificateChain Hook Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
if (MH_OK != MH_EnableHook(CertAddEncodedCertificateToStore)) |
|
{ |
|
MessageBox(NULL, __TEXT("CertAddEncodedCertificateToStore Hook Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
} |
|
|
|
void UnDoCertInjector() |
|
{ |
|
if (MH_OK != MH_DisableHook(CertAddEncodedCertificateToStore)) |
|
{ |
|
MessageBox(NULL, __TEXT("CertAddEncodedCertificateToStore Hook Disable Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
if (MH_OK != MH_DisableHook(CertGetCertificateChain)) |
|
{ |
|
MessageBox(NULL, __TEXT("CertGetCertificateChain Hook Disable Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
if (MH_OK != MH_RemoveHook(CertAddEncodedCertificateToStore)) |
|
{ |
|
MessageBox(NULL, __TEXT("CertAddEncodedCertificateToStore Hook Remove Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
if (MH_OK != MH_RemoveHook(CertGetCertificateChain)) |
|
{ |
|
MessageBox(NULL, __TEXT("CertGetCertificateChain Hook Remove Failed"), __TEXT("IDA Cert Injector"), MB_OK); |
|
return; |
|
} |
|
ussTrustedCertBuffers.clear(); |
|
} |
I'd like to share my implementation for linux as a plugin here:
source will be uploaded to
rad:z8qxfJjGpnnRadF51KiW5jJaUuap
once ready. or poke me if you are interested and couldn't wait (it does not build on your machine yet! i will publish the source as soon as i finished the win32 implementation and necessary abstractions)