Files
raven/pkg/ntapi/user_callback_runtime.go
T

281 lines
8.2 KiB
Go

//go:build guest_windows
package ntapi
import (
"encoding/binary"
"fmt"
"github.com/mirkobrombin/Raven/pkg/mmu"
)
const User32CallbackWindowProc uint32 = 0
const (
userCallbackRuntimeSize = uint64(mmu.PageSize)
userCallbackDispatcherOffset = uint64(0)
userCallbackWindowProcOffset = uint64(0x100)
userCallbackTableOffset = uint64(0x200)
windowProcCallback32Size = uint32(32)
windowProcCallback64Size = uint32(64)
windowProcCallback32Result = uint64(28)
windowProcCallback64Result = uint64(56)
)
type UserCallbackRuntime struct {
Dispatcher uint64
CallbackTable uint64
WindowProc uint64
}
type WindowProcCallbackArguments struct {
Proc uint64
IsAnsiProc bool
Wnd uint64
Msg uint32
WParam uint64
LParam uint64
}
func InstallUserCallbackRuntime(mem *mmu.MMU, state *ProcessState, ntCallbackReturn uint64) (UserCallbackRuntime, error) {
if mem == nil {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: nil memory")
}
if state == nil || state.PEBAddr == 0 {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: nil PEB")
}
if ntCallbackReturn == 0 {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: nil NtCallbackReturn")
}
if state.Is32 && (state.PEBAddr > uint64(^uint32(0)) || ntCallbackReturn > uint64(^uint32(0))) {
return UserCallbackRuntime{}, fmt.Errorf("install PE32 user callback runtime: address exceeds 32-bit range")
}
base := mem.Mmap(0, userCallbackRuntimeSize, mmu.PermRead|mmu.PermWrite)
if base == 0 {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: executable allocation failed")
}
installed := false
defer func() {
if !installed {
mem.Munmap(base, userCallbackRuntimeSize)
}
}()
if state.Is32 && base+userCallbackRuntimeSize-1 > uint64(^uint32(0)) {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: executable allocation failed")
}
runtime := UserCallbackRuntime{
Dispatcher: base + userCallbackDispatcherOffset,
CallbackTable: base + userCallbackTableOffset,
WindowProc: base + userCallbackWindowProcOffset,
}
var (
dispatcherCode []byte
windowProcCode []byte
callbackTable uint64
err error
)
if state.Is32 {
dispatcherCode = buildUserCallbackDispatcher32(uint32(state.PEBAddr), uint32(ntCallbackReturn))
windowProcCode = buildWindowProcCallback32(uint32(ntCallbackReturn))
callbackTable, err = readCallbackPointer(mem, state.PEBAddr+Peb32KernelCallbackTable, true)
} else {
dispatcherCode = buildUserCallbackDispatcher64(state.PEBAddr, ntCallbackReturn)
windowProcCode = buildWindowProcCallback64(ntCallbackReturn)
callbackTable, err = readCallbackPointer(mem, state.PEBAddr+PebKernelCallbackTable, false)
}
if err != nil {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: read callback table: %w", err)
}
if err := mem.WriteBytes(runtime.Dispatcher, dispatcherCode); err != nil {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: write dispatcher: %w", err)
}
if err := mem.WriteBytes(runtime.WindowProc, windowProcCode); err != nil {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: write window proc callback: %w", err)
}
if callbackTable == 0 {
if state.Is32 {
if err := mem.Write32(runtime.CallbackTable, uint32(runtime.WindowProc)); err != nil {
return UserCallbackRuntime{}, err
}
} else {
if err := mem.Write64(runtime.CallbackTable, runtime.WindowProc); err != nil {
return UserCallbackRuntime{}, err
}
}
} else {
runtime.CallbackTable = callbackTable
}
if err := mem.Mprotect(base, userCallbackRuntimeSize, mmu.PermRead|mmu.PermExecute); err != nil {
return UserCallbackRuntime{}, fmt.Errorf("install user callback runtime: protect code: %w", err)
}
if callbackTable == 0 {
if state.Is32 {
if err := mem.Write32(state.PEBAddr+Peb32KernelCallbackTable, uint32(runtime.CallbackTable)); err != nil {
return UserCallbackRuntime{}, err
}
} else if err := mem.Write64(state.PEBAddr+PebKernelCallbackTable, runtime.CallbackTable); err != nil {
return UserCallbackRuntime{}, err
}
}
installed = true
return runtime, nil
}
func EncodeWindowProcCallbackArguments(is32 bool, args WindowProcCallbackArguments) []byte {
if is32 {
buf := make([]byte, windowProcCallback32Size)
binary.LittleEndian.PutUint32(buf[0:], uint32(args.Proc))
if args.IsAnsiProc {
binary.LittleEndian.PutUint32(buf[4:], 1)
}
binary.LittleEndian.PutUint32(buf[8:], uint32(args.Wnd))
binary.LittleEndian.PutUint32(buf[12:], args.Msg)
binary.LittleEndian.PutUint32(buf[16:], uint32(args.WParam))
binary.LittleEndian.PutUint32(buf[20:], uint32(args.LParam))
binary.LittleEndian.PutUint32(buf[24:], uint32(^uint32(0)))
return buf
}
buf := make([]byte, windowProcCallback64Size)
binary.LittleEndian.PutUint64(buf[0:], args.Proc)
if args.IsAnsiProc {
binary.LittleEndian.PutUint32(buf[8:], 1)
}
binary.LittleEndian.PutUint64(buf[16:], args.Wnd)
binary.LittleEndian.PutUint32(buf[24:], args.Msg)
binary.LittleEndian.PutUint64(buf[32:], args.WParam)
binary.LittleEndian.PutUint64(buf[40:], args.LParam)
binary.LittleEndian.PutUint32(buf[48:], uint32(^uint32(0)))
return buf
}
func ReadWindowProcCallbackResult(mem *mmu.MMU, is32 bool, callbackReturn UserCallbackReturn) (uint64, error) {
size := windowProcCallback64Size
offset := windowProcCallback64Result
if is32 {
size = windowProcCallback32Size
offset = windowProcCallback32Result
}
if callbackReturn.Address == 0 || callbackReturn.Length < size {
return 0, fmt.Errorf("window proc callback returned buffer 0x%x length %d", callbackReturn.Address, callbackReturn.Length)
}
if is32 {
value, err := mem.Read32(callbackReturn.Address + offset)
return uint64(value), err
}
return mem.Read64(callbackReturn.Address + offset)
}
func readCallbackPointer(mem *mmu.MMU, address uint64, is32 bool) (uint64, error) {
if is32 {
value, err := mem.Read32(address)
return uint64(value), err
}
return mem.Read64(address)
}
func buildUserCallbackDispatcher32(peb, ntCallbackReturn uint32) []byte {
code := []byte{
0x8B, 0x54, 0x24, 0x04,
0xB8,
}
code = appendUint32(code, peb)
code = append(code,
0x8B, 0x40, byte(Peb32KernelCallbackTable),
0xFF, 0x74, 0x24, 0x0C,
0xFF, 0x74, 0x24, 0x0C,
0xFF, 0x14, 0x90,
0x50,
0x6A, 0x00,
0x6A, 0x00,
0xB8,
)
code = appendUint32(code, ntCallbackReturn)
return append(code, 0xFF, 0xD0, 0xCC)
}
func buildWindowProcCallback32(ntCallbackReturn uint32) []byte {
code := []byte{
0x53,
0x56,
0x8B, 0x5C, 0x24, 0x0C,
0x8B, 0x74, 0x24, 0x10,
0xFF, 0x73, 0x14,
0xFF, 0x73, 0x10,
0xFF, 0x73, 0x0C,
0xFF, 0x73, 0x08,
0xFF, 0x13,
0x89, 0x43, 0x1C,
0x6A, 0x00,
0x56,
0x53,
0xB8,
}
code = appendUint32(code, ntCallbackReturn)
return append(code, 0xFF, 0xD0, 0xCC)
}
func buildUserCallbackDispatcher64(peb, ntCallbackReturn uint64) []byte {
code := []byte{
0x4C, 0x8B, 0x54, 0x24, 0x20,
0x44, 0x8B, 0x5C, 0x24, 0x28,
0x8B, 0x44, 0x24, 0x2C,
0x49, 0xBC,
}
code = appendUint64(code, peb)
code = append(code,
0x4D, 0x8B, 0x64, 0x24, byte(PebKernelCallbackTable),
0x49, 0x8B, 0x04, 0xC4,
0x4C, 0x89, 0xD1,
0x44, 0x89, 0xDA,
0x48, 0x83, 0xEC, 0x20,
0xFF, 0xD0,
0x48, 0x83, 0xC4, 0x20,
0x31, 0xC9,
0x31, 0xD2,
0x41, 0x89, 0xC0,
0x48, 0xB8,
)
code = appendUint64(code, ntCallbackReturn)
return append(code, 0xFF, 0xD0, 0xCC)
}
func buildWindowProcCallback64(ntCallbackReturn uint64) []byte {
code := []byte{
0x53,
0x56,
0x48, 0x89, 0xCB,
0x89, 0xD6,
0x48, 0x8B, 0x03,
0x48, 0x8B, 0x4B, 0x10,
0x8B, 0x53, 0x18,
0x4C, 0x8B, 0x43, 0x20,
0x4C, 0x8B, 0x4B, 0x28,
0x48, 0x83, 0xEC, 0x28,
0xFF, 0xD0,
0x48, 0x89, 0x43, 0x38,
0x48, 0x89, 0xD9,
0x89, 0xF2,
0x45, 0x31, 0xC0,
0x48, 0xB8,
}
code = appendUint64(code, ntCallbackReturn)
return append(code, 0xFF, 0xD0, 0xCC)
}
func appendUint32(code []byte, value uint32) []byte {
var encoded [4]byte
binary.LittleEndian.PutUint32(encoded[:], value)
return append(code, encoded[:]...)
}
func appendUint64(code []byte, value uint64) []byte {
var encoded [8]byte
binary.LittleEndian.PutUint64(encoded[:], value)
return append(code, encoded[:]...)
}