Skip to content

Commit

Permalink
feat: add internal resolver and util packages
Browse files Browse the repository at this point in the history
  • Loading branch information
f1zm0 committed Apr 5, 2023
1 parent f6305de commit f3c3edb
Show file tree
Hide file tree
Showing 14 changed files with 383 additions and 0 deletions.
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/f1zm0/acheron

go 1.20

require (
github.com/Binject/debug v0.0.0-20211007083345-9605c99179ee
golang.org/x/sys v0.6.0
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/Binject/debug v0.0.0-20211007083345-9605c99179ee h1:neBp9wDYVY4Uu1gGlrL+IL4JeZslz+hGEAjBXGAPWak=
github.com/Binject/debug v0.0.0-20211007083345-9605c99179ee/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
32 changes: 32 additions & 0 deletions internal/resolver/asm64.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// func GetInMemoryOrderModuleListPtr() uintptr
TEXT ·GetInMemoryOrderModuleListPtr(SB),$0-8
// PEB
MOVQ 0x60(GS), AX
// PEB->Ldr
MOVQ 0x18(AX), AX
// PEB->Ldr->InMemoryOrderModuleList
MOVQ 0x20(AX), AX
MOVQ AX, ret+0(FP)
RET

// func GetLdrTableEntryPtr(listptr uintptr, i int64) *LdrDataTableEntry
TEXT ·GetLdrTableEntryPtr(SB),$0-24

MOVQ listptr+0(FP), AX

XORQ R10, R10
next_entry:
CMPQ R10, i+8(FP)
JE endloop

// next Flink
MOVQ (AX), AX
INCQ R10
JMP next_entry

endloop:
MOVQ AX, CX
// start of LDR_DATA_TABLE_ENTRY struct
SUBQ $0x10, CX
MOVQ CX, ret+16(FP)
RET
41 changes: 41 additions & 0 deletions internal/resolver/ldr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package resolver

import (
"unsafe"

wt "github.com/f1zm0/acheron/internal/types"
)

// GetLdrTableEntryPtr signature.
func GetLdrTableEntryPtr(listptr uintptr, i int64) *wt.LdrDataTableEntry

// GetInMemoryOrderModuleListPtr signature.
func GetInMemoryOrderModuleListPtr() uintptr

// GetLdrTableEntries returns a slice of LdrDataTableEntries for
// custom implementation of GetModuleHandle function.
func GetLdrTableEntries() []*wt.LdrDataTableEntry {
entries := []*wt.LdrDataTableEntry{}
var (
entry *wt.LdrDataTableEntry
firstEntry *wt.LdrDataTableEntry
)

// addr of Ldr->InMemoryOrderModuleList
modListPtr := GetInMemoryOrderModuleListPtr()

firstEntry = GetLdrTableEntryPtr(modListPtr, 0)
entries = append(entries, firstEntry)

i := int64(1)
for {
entry = GetLdrTableEntryPtr(modListPtr, i)
if entry == firstEntry || unsafe.Pointer(entry.DllBase) == unsafe.Pointer(nil) {
break
}
entries = append(entries, entry)
i = i + 1
}

return entries
}
9 changes: 9 additions & 0 deletions internal/resolver/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package resolver

type Resolver interface {
// GetSyscallSSN returns the syscall SSN.
GetSyscallSSN(funcNameHash int64) (uint16, error)

// GetSafeGate returns the address of an unhooked syscall;ret gadget in ntdll.dll
GetSafeGate() uintptr
}
112 changes: 112 additions & 0 deletions internal/resolver/ssnsort/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package ssnsort

import (
"errors"
"sort"
"strings"
"unsafe"

"github.com/f1zm0/acheron/internal/resolver"
wt "github.com/f1zm0/acheron/internal/types"
"github.com/f1zm0/acheron/pkg/hashing"
rrd "github.com/f1zm0/acheron/pkg/rawreader"

"github.com/Binject/debug/pe"
)

type ssnSortResolver struct {
// hashing provider
hasher hashing.Hasher

// map of Zw* InMemProc structs indexed by their name's hash
zwStubs map[int64]wt.InMemProc

// slice with addresses of clean gadgets
safeGates []uintptr
}

var _ resolver.Resolver = (*ssnSortResolver)(nil)

func NewResolver(h hashing.Hasher) (resolver.Resolver, error) {
r := &ssnSortResolver{}
if err := r.init(); err != nil {
return nil, err
}
return r, nil
}

func (r *ssnSortResolver) init() error {
var zwStubs []wt.InMemProc

hNtdll, err := r.getNtdllModuleHandle()
if err != nil {
return err
}

ex, err := hNtdll.File.Exports()
if err != nil {
return err
}
for _, exp := range ex {
memAddr := int64(hNtdll.BaseAddr) + int64(exp.VirtualAddress)
// TODO: check if stub has syscall;ret gadget, search adiacent ones if not
// add clean ones to safeGates

if strings.HasPrefix(exp.Name, "Zw") {
zwStubs = append(zwStubs, wt.InMemProc{
Name: exp.Name,
BaseAddr: uintptr(memAddr),
})
}
}

sort.Slice(zwStubs, func(i, j int) bool {
return zwStubs[i].BaseAddr < zwStubs[j].BaseAddr
})

for idx := range zwStubs {
zwStubs[idx].SSN = idx
r.zwStubs[r.hasher.HashString(zwStubs[idx].Name)] = zwStubs[idx]
}

return nil
}

func (r *ssnSortResolver) getNtdllModuleHandle() (*wt.PEModule, error) {
entries := resolver.GetLdrTableEntries()
ntdllHash := r.hasher.HashByteString(
[]byte{0x6e, 0x74, 0x64, 0x6c, 0x6c, 0x2e, 0x64, 0x6c, 0x6c, 0x00}, // ntdll.dll
)
for _, entry := range entries {
if r.hasher.HashString(entry.BaseDllName.String()) == ntdllHash {
modBaseAddr := uintptr(unsafe.Pointer(entry.DllBase))
modSize := int(uintptr(unsafe.Pointer(entry.SizeOfImage)))
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 nil, errors.New("module not found, probably not loaded.")
}

// GetSyscallSSN returns the syscall ID of a native API function by its djb2 hash.
// If the function is not found, 0 is returned.
func (r *ssnSortResolver) GetSyscallSSN(fnHash int64) (uint16, error) {
if v, ok := r.zwStubs[fnHash]; ok {
return uint16(v.SSN), nil
}
return 0, errors.New("could not find SSN")
}

func (r *ssnSortResolver) GetSafeGate() uintptr {
// FIXME: this panics as safeGates is empty
return r.safeGates[0]
}
22 changes: 22 additions & 0 deletions internal/types/ldr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package types

type LdrDataTableEntry struct {
InLoadOrderLinks ListEntry
InMemoryOrderLinks ListEntry
InInitializationOrderLinks ListEntry
DllBase *uintptr
EntryPoint *uintptr
SizeOfImage *uintptr
FullDllName UnicodeString
BaseDllName UnicodeString
Flags uint32
LoadCount uint16
TlsIndex uint16
HashLinks ListEntry
TimeDateStamp uint64
}

type ListEntry struct {
Flink *ListEntry
Blink *ListEntry
}
11 changes: 11 additions & 0 deletions internal/types/pe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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 changes: 32 additions & 0 deletions internal/types/proc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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 changes: 15 additions & 0 deletions internal/types/unicodestr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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)
}
25 changes: 25 additions & 0 deletions pkg/hashing/djb2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package hashing

type djb2 struct{}

var _ Hasher = (*djb2)(nil)

// NewDjb2 returns a new djb2 hasher.
// Algorithm taken from http://www.cse.yorku.ca/~oz/hash.html
func NewDjb2() Hasher {
return &djb2{}
}

// HashByteString hashes a byte string using the djb2 algorithm.
func (d *djb2) HashByteString(s []byte) int64 {
var hash int64 = 5381
for _, c := range s {
hash = ((hash << 5) + hash) + int64(c)
}
return hash
}

// HashString hashes a string using the djb2 algorithm.
func (d *djb2) HashString(s string) int64 {
return d.HashByteString([]byte(s))
}
9 changes: 9 additions & 0 deletions pkg/hashing/hasher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hashing

type Hasher interface {
// HashString hashes a string using the djb2 algorithm.
HashString(s string) int64

// HashByteString hashes a byte string using the djb2 algorithm.
HashByteString(s []byte) int64
}
15 changes: 15 additions & 0 deletions pkg/memory/memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package loader

import (
"unsafe"
)

// WriteMemory writes the provided memory to the specified memory address.
// It does NOT check permissions, may cause panic if memory is not writable etc.
func WriteMemory(inbuf []byte, destination uintptr) {
for index := uint32(0); index < uint32(len(inbuf)); index++ {
writePtr := unsafe.Pointer(destination + uintptr(index))
v := (*byte)(writePtr)
*v = inbuf[index]
}
}
48 changes: 48 additions & 0 deletions pkg/rawreader/rawreader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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
}

0 comments on commit f3c3edb

Please sign in to comment.