mirror of
https://github.com/f1zm0/acheron
synced 2026-06-08 14:09:51 +00:00
feat: add zw exports parsing logic to resolver
This commit is contained in:
+29
-11
@@ -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,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{}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user