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
@@ -0,0 +1,112 @@
|
||||
//go:build windows && amd64
|
||||
|
||||
package injector
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/moond4rk/hackbrowserdata/crypto/windows/abe_native/bootstrap"
|
||||
"github.com/moond4rk/hackbrowserdata/utils/winapi"
|
||||
)
|
||||
|
||||
// The C payload reads BootstrapParams by field offset while Go writes it as a flat byte
|
||||
// slice, so this pins the encoding to the cgo-generated offsets rather than to field order.
|
||||
func TestEncodeBootstrapParams(t *testing.T) {
|
||||
const scratchBase = 0xDEADBEEF000
|
||||
|
||||
params, err := encodeBootstrapParams(scratchBase)
|
||||
if err != nil {
|
||||
t.Fatalf("encodeBootstrapParams: %v", err)
|
||||
}
|
||||
if len(params) != bootstrap.ParamsSize {
|
||||
t.Fatalf("params size = %d, want %d (C sizeof(BootstrapParams))", len(params), bootstrap.ParamsSize)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
offset int
|
||||
want uintptr
|
||||
}{
|
||||
{"scratch_base", bootstrap.ParamScratchBase, scratchBase},
|
||||
{"LoadLibraryA", bootstrap.ParamLoadLibraryA, winapi.AddrLoadLibraryA()},
|
||||
{"GetProcAddress", bootstrap.ParamGetProcAddress, winapi.AddrGetProcAddress()},
|
||||
{"VirtualAlloc", bootstrap.ParamVirtualAlloc, winapi.AddrVirtualAlloc()},
|
||||
{"VirtualProtect", bootstrap.ParamVirtualProtect, winapi.AddrVirtualProtect()},
|
||||
{"NtFlushInstructionCache", bootstrap.ParamNtFlushIC, winapi.AddrNtFlushInstructionCache()},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
got := binary.LittleEndian.Uint64(params[tc.offset : tc.offset+8])
|
||||
if got != uint64(tc.want) {
|
||||
t.Errorf("%s at offset 0x%x = %#x, want %#x", tc.name, tc.offset, got, tc.want)
|
||||
}
|
||||
if tc.want == 0 {
|
||||
t.Errorf("%s resolved to a null address", tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPutBootstrapParamBounds(t *testing.T) {
|
||||
const want = uintptr(0x0123456789ABCDEF)
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
offset int
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "last valid field", size: 16, offset: 8},
|
||||
{name: "negative offset", size: 8, offset: -1, wantErr: true},
|
||||
{name: "short block", size: 7, offset: 0, wantErr: true},
|
||||
{name: "one past end", size: 8, offset: 1, wantErr: true},
|
||||
{name: "maximum offset", size: 8, offset: int(^uint(0) >> 1), wantErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
params := make([]byte, tt.size)
|
||||
err := putBootstrapParam(params, tt.offset, want)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Fatal("putBootstrapParam returned nil error")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("putBootstrapParam: %v", err)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint64(params[tt.offset:]); got != uint64(want) {
|
||||
t.Errorf("encoded value = %#x, want %#x", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Every field must land inside the struct and none may overlap — a stale offset constant
|
||||
// would otherwise corrupt a neighboring pointer instead of failing loudly.
|
||||
func TestBootstrapParamOffsetsAreDisjoint(t *testing.T) {
|
||||
offsets := []int{
|
||||
bootstrap.ParamScratchBase,
|
||||
bootstrap.ParamLoadLibraryA,
|
||||
bootstrap.ParamGetProcAddress,
|
||||
bootstrap.ParamVirtualAlloc,
|
||||
bootstrap.ParamVirtualProtect,
|
||||
bootstrap.ParamNtFlushIC,
|
||||
}
|
||||
seen := make(map[int]bool, len(offsets))
|
||||
for _, off := range offsets {
|
||||
if off%8 != 0 {
|
||||
t.Errorf("offset 0x%x is not 8-byte aligned", off)
|
||||
}
|
||||
if off < 0 || off > bootstrap.ParamsSize-bootstrapParamFieldSize {
|
||||
t.Errorf("offset 0x%x overruns params block of %d bytes", off, bootstrap.ParamsSize)
|
||||
}
|
||||
if seen[off] {
|
||||
t.Errorf("offset 0x%x used by more than one field", off)
|
||||
}
|
||||
seen[off] = true
|
||||
}
|
||||
if len(seen)*bootstrapParamFieldSize != bootstrap.ParamsSize {
|
||||
t.Errorf("%d fields cover %d bytes, but C sizeof(BootstrapParams) is %d",
|
||||
len(seen), len(seen)*bootstrapParamFieldSize, bootstrap.ParamsSize)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user