feat: add zw exports parsing logic to resolver

This commit is contained in:
f1zm0
2023-04-09 19:22:21 +02:00
parent f995154072
commit 19f45c70eb
13 changed files with 175 additions and 186 deletions
+29 -11
View File
@@ -24,8 +24,8 @@ TEXT ·getNtdllBaseAddr(SB),NOSPLIT,$0
RET
// func getModuleEATAddr (moduleBase uintptr) uintptr
TEXT ·getModuleEATAddr(SB),NOSPLIT,$0-8
// func getModuleExportsDirAddr (moduleBase uintptr) uintptr
TEXT ·getModuleExportsDirAddr(SB),NOSPLIT,$0-8
MOVQ moduleBase+0(FP), AX
XORQ R15, R15
@@ -41,28 +41,28 @@ TEXT ·getModuleEATAddr(SB),NOSPLIT,$0-8
ADDQ $0x88, R15
// AX = ntdll base + IMAGE_DATA_DIRECTORY.VirtualAddress
ADDL 0x0(R15), R14
ADDL (R15), R14
ADDQ R14, AX
MOVQ AX, ret+8(FP)
RET
// func getEATNumberOfFunctions(exportsBase uintptr) uint32
TEXT ·getEATNumberOfFunctions(SB),NOSPLIT,$0-8
// func getExportsNumberOfNames(exportsBase uintptr) uint32
TEXT ·getExportsNumberOfNames(SB),NOSPLIT,$0-8
MOVQ exportsBase+0(FP), AX
XORQ R15, R15
// R15 = exportsBase + IMAGE_EXPORT_DIRECTORY.NumberOfFunctions
MOVL 0x14(AX), R15
// R15 = exportsBase + IMAGE_EXPORT_DIRECTORY.NumberOfNames
MOVL 0x18(AX), R15
MOVL R15, ret+8(FP)
RET
// func getEATAddressOfFunctions(moduleBase,exportsBase uintptr) uintptr
TEXT ·getEATAddressOfFunctions(SB),NOSPLIT,$0-16
// func getExportsAddressOfFunctions(moduleBase,exportsBase uintptr) uintptr
TEXT ·getExportsAddressOfFunctions(SB),NOSPLIT,$0-16
MOVQ moduleBase+0(FP), AX
MOVQ exportsBase+8(FP), R8
@@ -78,8 +78,8 @@ TEXT ·getEATAddressOfFunctions(SB),NOSPLIT,$0-16
RET
// func getEATAddressOfNames(moduleBase,exportsBase uintptr) uintptr
TEXT ·getEATAddressOfNames(SB),NOSPLIT,$0-16
// func getExportsAddressOfNames(moduleBase,exportsBase uintptr) uintptr
TEXT ·getExportsAddressOfNames(SB),NOSPLIT,$0-16
MOVQ moduleBase+0(FP), AX
MOVQ exportsBase+8(FP), R8
@@ -93,3 +93,21 @@ TEXT ·getEATAddressOfNames(SB),NOSPLIT,$0-16
MOVQ AX, ret+16(FP)
RET
// func getExportsAddressOfNameOrdinals(moduleBase, exportsBase uintptr) uintptr
TEXT ·getExportsAddressOfNameOrdinals(SB),NOSPLIT,$0-16
MOVQ moduleBase+0(FP), AX
MOVQ exportsBase+8(FP), R8
XORQ SI, SI
// SI = exportsBase + IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals
MOVL 0x24(R8), SI
// AX = exportsBase + AddressOfNames offset
ADDQ SI, AX
MOVQ AX, ret+16(FP)
RET
+1 -7
View File
@@ -1,14 +1,8 @@
package resolver
import (
wt "github.com/f1zm0/acheron/internal/types"
)
const SYSCALL_STUB_SIZE = 0x20
// FindSyscallRetGadgets finds syscall;ret gadgets in ntdll.dll
// that can be "recycled" to ensure syscalls goes through ntdll.
func FindSyscallRetGadgets(hNtdll *wt.PEModule) []uintptr {
func FindSyscallRetGadgets(stubs map[int64]*ZwStub) []uintptr {
// TODO: do implementation
return []uintptr{}
}
-40
View File
@@ -1,40 +0,0 @@
package resolver
import (
"fmt"
wt "github.com/f1zm0/acheron/internal/types"
)
// GetNtdlloduleHandle returns a PEModule struct with information about in-memory
// ntdll.dll's module, or an error if for some reason an error occur while reading it.
func GetNtdllModuleHandle() (*wt.PEModule, error) {
moduleBase := getNtdllBaseAddr()
fmt.Printf("ntdll base: 0x%x\r\n\r\n", moduleBase)
exportsBase := getModuleEATAddr(moduleBase)
fmt.Printf("EAT base: 0x%x\r\n\r\n", exportsBase)
numberOfFunctions := getEATNumberOfFunctions(exportsBase)
fmt.Printf("Number of functions: %d\r\n\r\n", numberOfFunctions)
addressOfFunctions := getEATAddressOfFunctions(moduleBase, exportsBase)
fmt.Printf("Address of functions: 0x%x\r\n\r\n", addressOfFunctions)
addressOfNames := getEATAddressOfNames(moduleBase, exportsBase)
fmt.Printf("Address of names: 0x%x\r\n\r\n", addressOfNames)
// rr := rrd.NewRawReader(modBaseAddr, modSize)
// p, err := pe.NewFileFromMemory(rr)
// if err != nil {
// return nil, errors.New("error reading module from memory")
// }
// return &wt.PEModule{
// BaseAddr: modBaseAddr,
// File: p,
// }, nil
return &wt.PEModule{}, nil
}
+58
View File
@@ -0,0 +1,58 @@
package resolver
import (
"github.com/f1zm0/acheron/pkg/hashing"
"github.com/f1zm0/acheron/pkg/memory"
)
type NtModule struct {
BaseAddr uintptr
ExportsBaseAddr uintptr
NumberOfNames uint32
AddressOfFunctions uintptr
AddressOfNames uintptr
AddressOfNameOrdinals uintptr
ZwStubs map[int64]*ZwStub
}
type ZwStub struct {
RVA uint32
VA uintptr
SSN uint16
}
// ParseNtdllModule returns a NtModule struct with the relevant information
// about the in-memory ntdll.dll module.
func ParseNtdllModule(hashFn hashing.Hasher) *NtModule {
var m NtModule
m.BaseAddr = getNtdllBaseAddr()
m.ExportsBaseAddr = getModuleExportsDirAddr(m.BaseAddr)
m.NumberOfNames = getExportsNumberOfNames(m.ExportsBaseAddr)
m.AddressOfFunctions = getExportsAddressOfFunctions(m.BaseAddr, m.ExportsBaseAddr)
m.AddressOfNames = getExportsAddressOfNames(m.BaseAddr, m.ExportsBaseAddr)
m.AddressOfNameOrdinals = getExportsAddressOfNameOrdinals(m.BaseAddr, m.ExportsBaseAddr)
m.ZwStubs = make(
map[int64]*ZwStub,
m.NumberOfNames/4, // Zw* functions are less than ~25% of the total so we can save some memory
)
for i := uint32(0); i < m.NumberOfNames; i++ {
fn := memory.ReadCStringAt(m.BaseAddr, memory.ReadDwordAt(m.AddressOfNames, i*4))
if fn[0] != 'Z' || fn[1] != 'w' {
continue
}
fnHash := hashFn.HashByteString(fn)
nameOrd := memory.ReadWordAt(m.AddressOfNameOrdinals, i*2)
rva := memory.ReadDwordAt(m.AddressOfFunctions, uint32(nameOrd*4))
m.ZwStubs[fnHash] = &ZwStub{
RVA: rva,
VA: memory.RVA2VA(m.BaseAddr, uint32(rva)),
}
}
return &m
}
+6 -13
View File
@@ -4,7 +4,6 @@ import (
"errors"
"github.com/f1zm0/acheron/internal/resolver"
wt "github.com/f1zm0/acheron/internal/types"
"github.com/f1zm0/acheron/pkg/hashing"
)
@@ -13,7 +12,7 @@ type ssnSortResolver struct {
hasher hashing.Hasher
// map of Zw* InMemProc structs indexed by their name's hash
zwStubs map[int64]wt.InMemProc
zwStubs map[int64]*resolver.ZwStub
// slice with addresses of clean gadgets
safeGates []uintptr
@@ -22,7 +21,9 @@ type ssnSortResolver struct {
var _ resolver.Resolver = (*ssnSortResolver)(nil)
func NewResolver(h hashing.Hasher) (resolver.Resolver, error) {
r := &ssnSortResolver{}
r := &ssnSortResolver{
hasher: h,
}
if err := r.init(); err != nil {
return nil, err
}
@@ -30,17 +31,9 @@ func NewResolver(h hashing.Hasher) (resolver.Resolver, error) {
}
func (r *ssnSortResolver) init() error {
// var zwStubs []wt.InMemProc
ntMod := resolver.ParseNtdllModule(r.hasher)
_ = ntMod
_, err := resolver.GetNtdllModuleHandle()
if err != nil {
return err
}
// exports, err := hNtdll.File.Exports()
// if err != nil {
// return err
// }
// for _, exp := range exports {
// memAddr := int64(hNtdll.BaseAddr) + int64(exp.VirtualAddress)
// r.safeGates = resolver.FindSyscallRetGadgets(hNtdll)
+6 -9
View File
@@ -1,16 +1,13 @@
package resolver
// getNtdllBase signature.
func getNtdllBaseAddr() uintptr
// getModuleEATAddr signature.
func getModuleEATAddr(modBaseAddr uintptr) uintptr
func getModuleExportsDirAddr(modBaseAddr uintptr) uintptr
// getEATNumberOfFunctions signature.
func getEATNumberOfFunctions(exportsBase uintptr) uint32
func getExportsNumberOfNames(exportsBase uintptr) uint32
// getEATAddressOfFunctions signature.
func getEATAddressOfFunctions(moduleBase, exportsBase uintptr) uintptr
func getExportsAddressOfFunctions(moduleBase, exportsBase uintptr) uintptr
// getEATAddressOfNames signature.
func getEATAddressOfNames(moduleBase, exportsBase uintptr) uintptr
func getExportsAddressOfNames(moduleBase, exportsBase uintptr) uintptr
func getExportsAddressOfNameOrdinals(moduleBase, exportsBase uintptr) uintptr
-11
View File
@@ -1,11 +0,0 @@
package types
import (
"github.com/Binject/debug/pe"
)
// PEModule is a struct that contains the base address of a PE module and a pointer to the PE file.
type PEModule struct {
BaseAddr uintptr
File *pe.File
}
-32
View File
@@ -1,32 +0,0 @@
package types
import (
"bytes"
"encoding/binary"
"io"
rrd "github.com/f1zm0/acheron/pkg/rawreader"
)
// InMemProc is a struct that contains the name, base address and SSN of a function.
type InMemProc struct {
Name string
BaseAddr uintptr
GateAddr uintptr
SSN int
}
func (p *InMemProc) IsHooked() bool {
safeBytes := []byte{0x4c, 0x8b, 0xd1, 0xb8}
stub := make([]byte, len(safeBytes))
rr := rrd.NewRawReader(p.BaseAddr, len(safeBytes))
sr := io.NewSectionReader(rr, 0, 1<<63-1)
binary.Read(sr, binary.LittleEndian, &stub)
if bytes.Compare(stub, safeBytes) == 0 {
return true
}
return false
}
-15
View File
@@ -1,15 +0,0 @@
package types
import "golang.org/x/sys/windows"
// UnicodeString is a struct that represents a Windows Unicode string.
type UnicodeString struct {
Length uint16
MaximumLength uint16
Buffer *uint16
}
// String returns the string representation of the UnicodeString.
func (s UnicodeString) String() string {
return windows.UTF16PtrToString(s.Buffer)
}
+51
View File
@@ -0,0 +1,51 @@
#include "textflag.h"
// func RVA2VA(moduleBase uintptr, rva uint32) uintptr
TEXT ·RVA2VA(SB),NOSPLIT,$0-16
MOVQ moduleBase+0(FP), AX
XORQ DI, DI
MOVL rva+8(FP), DI
ADDQ DI, AX
MOVQ AX, ret+16(FP)
RET
// func ReadDwordAt(start uintptr, offset uint32) uint32
TEXT ·ReadDwordAt(SB),NOSPLIT,$0-16
MOVQ start+0(FP), AX
MOVL offset+8(FP), R8
XORQ DI, DI
ADDQ R8, AX
MOVL (AX), DI
MOVL DI, ret+16(FP)
RET
// func ReadWordAt(start uintptr, offset uint32) uint16
TEXT ·ReadWordAt(SB),NOSPLIT,$0-16
MOVQ start+0(FP), AX
MOVL offset+8(FP), R8
XORQ DI, DI
ADDQ R8, AX
MOVW (AX), DI
MOVW DI, ret+16(FP)
RET
// func ReadByteAt(start uintptr, offset uint32) uint8
TEXT ·ReadByteAt(SB),NOSPLIT,$0-16
MOVQ start+0(FP), AX
MOVL offset+8(FP), R8
XORQ DI, DI
ADDQ R8, AX
MOVB (AX), DI
MOVB DI, ret+16(FP)
RET
+15
View File
@@ -0,0 +1,15 @@
package memory
// ReadCStringAt reads a null-terminated ANSI string from memory.
func ReadCStringAt(start uintptr, offset uint32) []byte {
var buf []byte
for {
ch := ReadByteAt(start, offset)
if ch == 0 {
break
}
buf = append(buf, ch)
offset++
}
return buf
}
+9
View File
@@ -0,0 +1,9 @@
package memory
func RVA2VA(moduleBase uintptr, rva uint32) uintptr
func ReadDwordAt(start uintptr, offset uint32) uint32
func ReadWordAt(start uintptr, offset uint32) uint16
func ReadByteAt(start uintptr, offset uint32) byte
-48
View File
@@ -1,48 +0,0 @@
package rawreader
import (
"errors"
"io"
"reflect"
"unsafe"
)
// RawReader struct and functions below are taken from:
// https://github.com/awgh/rawreader/blob/master/rawreader.go
// RawReader struct uses reflect to read data from underlying memory
type RawReader struct {
sliceHeader *reflect.SliceHeader
rawPtr uintptr
Data []byte
Length int
}
// NewRawReader returns a reference to a new populated RawReader
func NewRawReader(start uintptr, length int) *RawReader {
sh := &reflect.SliceHeader{
Data: start,
Len: length,
Cap: length,
}
data := *(*[]byte)(unsafe.Pointer(sh))
return &RawReader{sliceHeader: sh, rawPtr: start, Data: data, Length: length}
}
// ReadAt func reads a file with a seek offset
func (f *RawReader) ReadAt(p []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errors.New("RawReader.ReadAt: negative offset")
}
reqLen := len(p)
buffLen := int64(f.Length)
if off >= buffLen {
return 0, io.EOF
}
n = copy(p, f.Data[off:])
if n < reqLen {
err = io.EOF
}
return n, err
}