Skip to content

Commit

Permalink
feat: ported ntdll module parsing to go assembly
Browse files Browse the repository at this point in the history
  • Loading branch information
f1zm0 committed Apr 8, 2023
1 parent 13923a5 commit 21f66df
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 136 deletions.
20 changes: 20 additions & 0 deletions acheron.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,24 @@ type Acheron struct {
resolver resolver.Resolver
}

type (
// Option is a configuration option which can be used within
// a call to the constructor to configure the Acheron instance.
Option func(*options)
)

type options struct {
hasher hashing.Hasher
}

// New returns a new Acheron instance that can be used as a proxy to perform
// indirect syscalls for native api functions, or an error if the initialization fails.
func New(opts ...Option) (*Acheron, error) {
// defaults
options := &options{
hasher: hashing.NewDjb2(),
}

for _, o := range opts {
o(options)
}
Expand All @@ -28,3 +40,11 @@ func New(opts ...Option) (*Acheron, error) {
}, nil
}
}

// WithHashFunction returns an Option that sets a custom hashing (or obfuscation)
// function that will be used when resolving native api procedures by hash.
func WithHashFunction(f hashing.Hasher) Option {
return func(o *options) {
o.hasher = f
}
}
103 changes: 82 additions & 21 deletions internal/resolver/asm64.s
Original file line number Diff line number Diff line change
@@ -1,32 +1,93 @@
// func GetInMemoryOrderModuleListPtr() uintptr
TEXT ·GetInMemoryOrderModuleListPtr(SB),$0-8
// PEB
MOVQ 0x60(GS), AX
// func getNtdllBaseAddr() uintptr
TEXT ·getNtdllBaseAddr(SB),$0

// TEB->ProcessEnvironmentBlock
XORQ AX, AX
MOVQ 0x30(GS), AX
MOVQ 0x60(AX), AX

// PEB->Ldr
MOVQ 0x18(AX), AX

// PEB->Ldr->InMemoryOrderModuleList
MOVQ 0x20(AX), AX

// PEB->Ldr->InMemoryOrderModuleList->Flink (ntdll.dll)
MOVQ (AX), AX

// PEB->Ldr->InMemoryOrderModuleList->Flink DllBase
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
// func getModuleEATAddr (moduleBase uintptr) uintptr
TEXT ·getModuleEATAddr(SB),$0-8
MOVQ moduleBase+0(FP), AX

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

// next Flink
MOVQ (AX), AX
INCQ R10
JMP next_entry
// AX = IMAGE_DOS_HEADER->e_lfanew offset
MOVB 0x3C(AX), R15

endloop:
MOVQ AX, CX
// start of LDR_DATA_TABLE_ENTRY struct
SUBQ $0x10, CX
MOVQ CX, ret+16(FP)
RET
// R15 = ntdll base + R15
ADDQ AX, R15

// R15 = R15 + OptionalHeader + DataDirectory offset
ADDQ $0x88, R15

// AX = ntdll base + IMAGE_DATA_DIRECTORY.VirtualAddress
ADDL 0x0(R15), R14
ADDQ R14, AX

MOVQ AX, ret+8(FP)
RET


// func getEATNumberOfFunctions(exportsBase uintptr) uint32
TEXT ·getEATNumberOfFunctions(SB),$0-8
MOVQ exportsBase+0(FP), AX

XORQ R15, R15

// R15 = exportsBase + IMAGE_EXPORT_DIRECTORY.NumberOfFunctions
MOVL 0x14(AX), R15

MOVL R15, ret+8(FP)
RET


// func getEATAddressOfFunctions(moduleBase,exportsBase uintptr) uintptr
TEXT ·getEATAddressOfFunctions(SB),$0-16
MOVQ moduleBase+0(FP), AX
MOVQ exportsBase+8(FP), R8

XORQ SI, SI

// R15 = exportsBase + IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
MOVL 0x1c(R8), SI

// AX = exportsBase + AddressOfFunctions offset
ADDQ SI, AX

MOVQ AX, ret+16(FP)
RET


// func getEATAddressOfNames(moduleBase,exportsBase uintptr) uintptr
TEXT ·getEATAddressOfNames(SB),$0-16
MOVQ moduleBase+0(FP), AX
MOVQ exportsBase+8(FP), R8

XORQ SI, SI

// SI = exportsBase + IMAGE_EXPORT_DIRECTORY.AddressOfNames
MOVL 0x20(R8), SI

// AX = exportsBase + AddressOfNames offset
ADDQ SI, AX

MOVQ AX, ret+16(FP)
RET
14 changes: 14 additions & 0 deletions internal/resolver/gates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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 {
// TODO: do implementation
return []uintptr{}
}
65 changes: 32 additions & 33 deletions internal/resolver/ldr.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,40 @@
package resolver

import (
"unsafe"
"fmt"

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
// 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
}
87 changes: 29 additions & 58 deletions internal/resolver/ssnsort/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@ 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 {
Expand All @@ -36,67 +30,44 @@ func NewResolver(h hashing.Hasher) (resolver.Resolver, error) {
}

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

hNtdll, err := r.getNtdllModuleHandle()
if err != nil {
return err
}
// var zwStubs []wt.InMemProc

ex, err := hNtdll.File.Exports()
_, err := resolver.GetNtdllModuleHandle()
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]
}
// 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)
// if len(r.safeGates) == 0 {
// return errors.New("could not found syscall;ret gadgets")
// }

// 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) {
Expand Down
16 changes: 16 additions & 0 deletions internal/resolver/stubs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package resolver

// getNtdllBase signature.
func getNtdllBaseAddr() uintptr

// getModuleEATAddr signature.
func getModuleEATAddr(modBaseAddr uintptr) uintptr

// getEATNumberOfFunctions signature.
func getEATNumberOfFunctions(exportsBase uintptr) uint32

// getEATAddressOfFunctions signature.
func getEATAddressOfFunctions(moduleBase, exportsBase uintptr) uintptr

// getEATAddressOfNames signature.
func getEATAddressOfNames(moduleBase, exportsBase uintptr) uintptr
24 changes: 0 additions & 24 deletions options.go

This file was deleted.

0 comments on commit 21f66df

Please sign in to comment.