Files
raven/pkg/ntapi/user_callback_runtime_test.go
T

162 lines
4.8 KiB
Go

//go:build guest_windows
package ntapi
import (
"fmt"
"testing"
"github.com/mirkobrombin/Raven/pkg/arch"
"github.com/mirkobrombin/Raven/pkg/mmu"
"github.com/mirkobrombin/Raven/pkg/vfs"
"github.com/mirkobrombin/Raven/pkg/x64"
)
func TestUserCallbackRuntimeWindowProcPE32AndPE64(t *testing.T) {
for _, mode32 := range []bool{true, false} {
t.Run(fmt.Sprintf("PE32=%t", mode32), func(t *testing.T) {
mem := mmu.New()
mem.Mode32 = mode32
pebSize := uint64(PebSize)
if mode32 {
pebSize = Peb32Size
}
peb := mem.Mmap(0, pebSize, mmu.PermRead|mmu.PermWrite)
state := &ProcessState{PEBAddr: peb, Is32: mode32}
ntdll := GenerateNtdll(mem)
d := NewDispatcher(mem, vfs.New(), NewHandleTable(), nil, state, ntdll, GenerateWin32u(mem))
runtime, err := InstallUserCallbackRuntime(mem, state, ntdll.Exports["NtCallbackReturn"])
if err != nil {
t.Fatal(err)
}
assertInstalledCallbackRuntime(t, mem, state, runtime)
wndProc := installTestWindowProc(t, mem, mode32, 0x12345678)
stack := mem.Mmap(0, 4*mmu.PageSize, mmu.PermRead|mmu.PermWrite)
stop := mem.Mmap(0, mmu.PageSize, mmu.PermRead|mmu.PermWrite|mmu.PermExecute)
if err := mem.WriteBytes(stop, []byte{0x0F, 0x01, 0xC1}); err != nil {
t.Fatal(err)
}
cpu := x64.New(mem)
cpu.Mode32 = mode32
cpu.SetSP(stack + 3*mmu.PageSize)
cpu.SetPC(stop)
cpu.Regs[x64.RAX] = 0xA5A5
cpu.SetVmcallHandler(func(trapped arch.CPU) {
if trapped.GetPC() == stop+3 {
trapped.Stop()
return
}
d.Handle(trapped)
})
before := cpu.Snapshot()
input := EncodeWindowProcCallbackArguments(mode32, WindowProcCallbackArguments{
Proc: wndProc,
Wnd: 0x10203,
Msg: 0x401,
WParam: 0x11223344,
LParam: 0x55667788,
})
var completed UserCallbackReturn
if _, err := d.BeginUserCallbackWithCompletion(cpu, runtime.Dispatcher, User32CallbackWindowProc, input,
func(cpu *x64.CPU, callbackReturn UserCallbackReturn) {
completed = callbackReturn
result, readErr := ReadWindowProcCallbackResult(mem, mode32, callbackReturn)
if readErr != nil {
t.Error(readErr)
return
}
cpu.SetSyscallReturn(result)
}); err != nil {
t.Fatal(err)
}
if err := cpu.Run(); err != nil {
t.Fatal(err)
}
if completed.Status != StatusSuccess || completed.Length != uint32(len(input)) {
t.Fatalf("callback return = %+v", completed)
}
if callbackReturn, ok := d.TakeUserCallbackReturn(cpu); ok {
t.Fatalf("completion callback also queued return %+v", callbackReturn)
}
if result := cpu.Regs[x64.RAX]; result != 0x12345678 {
t.Fatalf("WndProc result = 0x%x, want 0x12345678", result)
}
cpu.Regs[x64.RAX] = 0xA5A5
cpu.SetPC(stop)
after := cpu.Snapshot()
if after != before {
t.Fatal("callback runtime did not restore CPU state")
}
})
}
}
func TestInstallUserCallbackRuntimePreservesExistingTable(t *testing.T) {
for _, mode32 := range []bool{true, false} {
t.Run(fmt.Sprintf("PE32=%t", mode32), func(t *testing.T) {
mem := mmu.New()
mem.Mode32 = mode32
peb := mem.Mmap(0, PebSize, mmu.PermRead|mmu.PermWrite)
state := &ProcessState{PEBAddr: peb, Is32: mode32}
existing := mem.Mmap(0, mmu.PageSize, mmu.PermRead)
if mode32 {
if err := mem.Write32(peb+Peb32KernelCallbackTable, uint32(existing)); err != nil {
t.Fatal(err)
}
} else if err := mem.Write64(peb+PebKernelCallbackTable, existing); err != nil {
t.Fatal(err)
}
runtime, err := InstallUserCallbackRuntime(mem, state, 0x123400)
if err != nil {
t.Fatal(err)
}
if runtime.CallbackTable != existing {
t.Fatalf("callback table = 0x%x, want existing 0x%x", runtime.CallbackTable, existing)
}
})
}
}
func assertInstalledCallbackRuntime(t *testing.T, mem *mmu.MMU, state *ProcessState, runtime UserCallbackRuntime) {
t.Helper()
tableAddress := state.PEBAddr + PebKernelCallbackTable
if state.Is32 {
tableAddress = state.PEBAddr + Peb32KernelCallbackTable
}
table, err := readCallbackPointer(mem, tableAddress, state.Is32)
if err != nil {
t.Fatal(err)
}
if table != runtime.CallbackTable {
t.Fatalf("PEB callback table = 0x%x, want 0x%x", table, runtime.CallbackTable)
}
entry, err := readCallbackPointer(mem, table, state.Is32)
if err != nil {
t.Fatal(err)
}
if entry != runtime.WindowProc {
t.Fatalf("callback[0] = 0x%x, want 0x%x", entry, runtime.WindowProc)
}
}
func installTestWindowProc(t *testing.T, mem *mmu.MMU, mode32 bool, result uint32) uint64 {
t.Helper()
address := mem.Mmap(0, mmu.PageSize, mmu.PermRead|mmu.PermWrite|mmu.PermExecute)
code := []byte{0xB8, byte(result), byte(result >> 8), byte(result >> 16), byte(result >> 24)}
if mode32 {
code = append(code, 0xC2, 0x10, 0x00)
} else {
code = append(code, 0xC3)
}
if err := mem.WriteBytes(address, code); err != nil {
t.Fatal(err)
}
return address
}