-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add internal resolver and util packages
- Loading branch information
Showing
14 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |