├── .gitignore ├── README.md ├── Structs.nim ├── BeaconFunctions.nim └── Main.nim /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .vscode/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NiCOFF 2 | 3 | Basically, NiCOFF is a COFF and BOF file loader written in Nim. NiCOFF reads a BOF or COFF file, parses and executes it in the memory. Whole project is based on [Yasser](https://twitter.com/Yas_o_h)'s and [Kevin](https://twitter.com/kev169)'s COFF Loader projects. Both the loader and beacon functions in these projects were rewritten in Nim. 4 | 5 | # Compilation 6 | 7 | You can directly compile the source code with the following command: 8 | 9 | `nim c -d:release -o:NiCOFF.exe Main.nim` 10 | 11 | In case you get the error "cannot open file", you should also install required dependencies: 12 | 13 | `nimble install ptr_math winim` 14 | 15 | # Usage 16 | 17 | NiCOFF can take up to three arguments which are BOF or COFF file path, started function entry (you may want to change function pointer), and optional BOF arguments (you can check [Kevin's script](https://github.com/trustedsec/COFFLoader/blob/main/beacon_generate.py)). 18 | 19 | ``` 20 | PS C:\Users\test\Desktop\NiCOFF\bin> .\NiCOFF.exe .\ipconfig.x64.o go 21 | ______ _ ______ _____ _______ _______ 22 | | ___ \(_)/ _____) ___ \(_______|_______) 23 | | | | |_| / | | | |_____ _____ 24 | | | | | | | | | | | ___) | ___) 25 | | | | | | \____| |___| | | | | 26 | |_| |_|_|\______)_____/|_| |_| 27 | 28 | @R0h1rr1m 29 | 30 | [+] File is read! 31 | [+] Sections are copied! 32 | [+] Relocations for section: .text 33 | [+] Relocations for section: .data 34 | [+] Relocations for section: .bss 35 | [+] Relocations for section: .xdata 36 | [+] Relocations for section: .pdata 37 | [+] Relocations for section: .rdata 38 | [+] Relocations for section: /4 39 | [+] Relocations are done! 40 | [+] Trying to find the entry: go 41 | [+] go entry found! 42 | [+] Executing... 43 | [+] COFF File is Executed! 44 | [+] Output Below: 45 | 46 | ``` 47 | 48 | # References 49 | 50 | - https://github.com/trustedsec/COFFLoader 51 | - https://github.com/Yaxser/COFFLoader2 52 | - https://0xpat.github.io/Malware_development_part_8/ 53 | - https://blog.cloudflare.com/how-to-execute-an-object-file-part-1/ 54 | -------------------------------------------------------------------------------- /Structs.nim: -------------------------------------------------------------------------------- 1 | # The bycopy pragma can be applied to an object or tuple type and instructs the compiler to pass the type by value to procs 2 | # * means global type 3 | # Some structs here should be exist in winim/lean but anyway, maybe someone needs such thing . Oi! 4 | type 5 | #[ 6 | typedef struct { 7 | UINT16 Machine; 8 | UINT16 NumberOfSections; 9 | UINT32 TimeDateStamp; 10 | UINT32 PointerToSymbolTable; 11 | UINT32 NumberOfSymbols; 12 | UINT16 SizeOfOptionalHeader; 13 | UINT16 Characteristics; 14 | } FileHeader; 15 | ]# 16 | FileHeader* {.bycopy,packed.} = object 17 | Machine*: uint16 18 | NumberOfSections*: uint16 19 | TimeDateStamp*: uint32 20 | PointerToSymbolTable*: uint32 21 | NumberOfSymbols*: uint32 22 | SizeOfOptionalHeader*: uint16 23 | Characteristics*: uint16 24 | 25 | #[ 26 | typedef struct { 27 | char Name[8]; //8 bytes long null-terminated string 28 | UINT32 VirtualSize; //total size of section when loaded into memory, 0 for COFF, might be different because of padding 29 | UINT32 VirtualAddress; //address of the first byte of the section before relocations are applied, should be set to 0 30 | UINT32 SizeOfRawData; //The size of the section for COFF files 31 | UINT32 PointerToRawData; //Pointer to the beginning of the section for COFF 32 | UINT32 PointerToRelocations; //File pointer to the beginning of relocation entries 33 | UINT32 PointerToLinenumbers; //The file pointer to the beginning of line-number entries for the section. T 34 | UINT16 NumberOfRelocations; //The number of relocation entries for the section. This is set to zero for executable images. 35 | UINT16 NumberOfLinenumbers; //The number of line-number entries for the section. This value should be zero for an image because COFF debugging information is deprecated. 36 | UINT32 Characteristics; //The flags that describe the characteristics of the section 37 | } SectionHeader; 38 | ]# 39 | SectionHeader* {.bycopy,packed.} = object 40 | Name*: array[8,char] 41 | VirtualSize*: uint32 42 | VirtualAddress*: uint32 43 | SizeOfRawData*: uint32 44 | PointerToRawData*: uint32 45 | PointerToRelocations*: uint32 46 | PointerToLinenumbers*: uint32 47 | NumberOfRelocations*: uint16 48 | NumberOfLinenumbers*: uint16 49 | Characteristics*: uint32 50 | 51 | #[ 52 | typedef struct { 53 | union { 54 | char Name[8]; //8 bytes, name of the symbol, represented as a union of 3 structs 55 | UINT32 value[2]; //TODO: what does this represent?! 56 | } first; 57 | UINT32 Value; //meaning depends on the section number and storage class 58 | UINT16 SectionNumber; //signed int, some values have predefined meaning 59 | UINT16 Type; // 60 | UINT8 StorageClass; // 61 | UINT8 NumberOfAuxSymbols; 62 | } SymbolTableEntry; 63 | ]# 64 | 65 | UnionFirst* {.final,union,pure.} = object 66 | Name*: array[8,char] 67 | value*: array[2,uint32] 68 | 69 | 70 | 71 | SymbolTableEntry* {.bycopy, packed.} = object 72 | First*: UnionFirst 73 | Value*: uint32 74 | SectionNumber*: uint16 75 | Type*: uint16 76 | StorageClass*: uint8 77 | NumberOfAuxSymbols*: uint8 78 | 79 | #[ 80 | typedef struct { 81 | UINT32 VirtualAddress; 82 | UINT32 SymbolTableIndex; 83 | UINT16 Type; 84 | } RelocationTableEntry; 85 | ]# 86 | 87 | RelocationTableEntry* {.bycopy, packed.} = object 88 | VirtualAddress*: uint32 89 | SymbolTableIndex*: uint32 90 | Type*: uint16 91 | 92 | SectionInfo* {.bycopy.} = object 93 | Name*: string 94 | SectionOffset*: uint64 95 | SectionHeaderPtr*: ptr SectionHeader 96 | 97 | 98 | const 99 | IMAGE_REL_AMD64_ABSOLUTE = 0x0000 100 | IMAGE_REL_AMD64_ADDR64 = 0x0001 101 | IMAGE_REL_AMD64_ADDR32 = 0x0002 102 | IMAGE_REL_AMD64_ADDR32NB = 0x0003 103 | # Most common from the looks of it, just 32-bit relative address from the byte following the relocation 104 | IMAGE_REL_AMD64_REL32 = 0x0004 105 | # Second most common, 32-bit address without an image base. Not sure what that means... 106 | IMAGE_REL_AMD64_REL32_1 = 0x0005 107 | IMAGE_REL_AMD64_REL32_2 = 0x0006 108 | IMAGE_REL_AMD64_REL32_3 = 0x0007 109 | IMAGE_REL_AMD64_REL32_4 = 0x0008 110 | IMAGE_REL_AMD64_REL32_5 = 0x0009 111 | IMAGE_REL_AMD64_SECTION = 0x000A 112 | IMAGE_REL_AMD64_SECREL = 0x000B 113 | IMAGE_REL_AMD64_SECREL7 = 0x000C 114 | IMAGE_REL_AMD64_TOKEN = 0x000D 115 | IMAGE_REL_AMD64_SREL32 = 0x000E 116 | IMAGE_REL_AMD64_PAIR = 0x000F 117 | IMAGE_REL_AMD64_SSPAN32 = 0x0010 118 | 119 | # Storage classes. 120 | 121 | IMAGE_SYM_CLASS_END_OF_FUNCTION = cast[byte](-1) 122 | IMAGE_SYM_CLASS_NULL = 0x0000 123 | IMAGE_SYM_CLASS_AUTOMATIC = 0x0001 124 | IMAGE_SYM_CLASS_EXTERNAL = 0x0002 125 | IMAGE_SYM_CLASS_STATIC = 0x0003 126 | IMAGE_SYM_CLASS_REGISTER = 0x0004 127 | IMAGE_SYM_CLASS_EXTERNAL_DEF = 0x0005 128 | IMAGE_SYM_CLASS_LABEL = 0x0006 129 | IMAGE_SYM_CLASS_UNDEFINED_LABEL = 0x0007 130 | IMAGE_SYM_CLASS_MEMBER_OF_STRUCT = 0x0008 131 | IMAGE_SYM_CLASS_ARGUMENT = 0x0009 132 | IMAGE_SYM_CLASS_STRUCT_TAG = 0x000A 133 | IMAGE_SYM_CLASS_MEMBER_OF_UNION = 0x000B 134 | IMAGE_SYM_CLASS_UNION_TAG = 0x000C 135 | IMAGE_SYM_CLASS_TYPE_DEFINITION = 0x000D 136 | IMAGE_SYM_CLASS_UNDEFINED_STATIC = 0x000E 137 | IMAGE_SYM_CLASS_ENUM_TAG = 0x000F 138 | IMAGE_SYM_CLASS_MEMBER_OF_ENUM = 0x0010 139 | IMAGE_SYM_CLASS_REGISTER_PARAM = 0x0011 140 | IMAGE_SYM_CLASS_BIT_FIELD = 0x0012 141 | IMAGE_SYM_CLASS_FAR_EXTERNAL = 0x0044 142 | IMAGE_SYM_CLASS_BLOCK = 0x0064 143 | IMAGE_SYM_CLASS_FUNCTION = 0x0065 144 | IMAGE_SYM_CLASS_END_OF_STRUCT = 0x0066 145 | IMAGE_SYM_CLASS_FILE = 0x0067 146 | IMAGE_SYM_CLASS_SECTION = 0x0068 147 | IMAGE_SYM_CLASS_WEAK_EXTERNAL = 0x0069 148 | IMAGE_SYM_CLASS_CLR_TOKEN = 0x006B -------------------------------------------------------------------------------- /BeaconFunctions.nim: -------------------------------------------------------------------------------- 1 | import winim/lean 2 | import ptr_math 3 | import system 4 | 5 | #[ 6 | typedef struct { 7 | char* original; /* the original buffer [so we can free it] */ 8 | char* buffer; /* current pointer into our buffer */ 9 | int length; /* remaining length of data */ 10 | int size; /* total size of this buffer */ 11 | } datap; 12 | ]# 13 | 14 | # I directly turned beacon_compatibility.c to Nim. There are some empty functions beacuse the implementations there were not given, and I'm not familiar with these functions. 15 | type 16 | Datap* {.bycopy,packed.} = object 17 | original*: ptr char 18 | buffer*: ptr char 19 | length*: int 20 | size*: int 21 | #[ 22 | typedef struct { 23 | char* original; /* the original buffer [so we can free it] */ 24 | char* buffer; /* current pointer into our buffer */ 25 | int length; /* remaining length of data */ 26 | int size; /* total size of this buffer */ 27 | } formatp; 28 | ]# 29 | Formatp* {.bycopy,packed.} = object 30 | original*: ptr char 31 | buffer*: ptr char 32 | length*: int 33 | size*: int 34 | 35 | 36 | var beaconCompatibilityOutput:ptr char = nil 37 | var beaconCompatibilitySize: int = 0 38 | var beaconCompatibilityOffset: int = 0 39 | 40 | 41 | # void BeaconDataParse(datap* parser, char* buffer, int size) 42 | proc BeaconDataParse(parser:ptr Datap,buffer: ptr char,size:int):void{.stdcall.} = 43 | if(cast[uint64](parser) == 0): 44 | return 45 | parser.original = buffer 46 | parser.buffer = buffer 47 | parser.length = size-4 48 | parser.size = size-4 49 | parser.buffer += 4 50 | return 51 | 52 | # int BeaconDataInt(datap* parser); 53 | proc BeaconDataInt(parser:ptr Datap):int{.stdcall.} = 54 | var returnValue:int = 0 55 | if(parser.length < 4): 56 | return returnValue 57 | copyMem(addr(returnValue),parser.buffer,4) 58 | parser.length-=4 59 | parser.buffer+=4 60 | return returnValue 61 | 62 | # short BeaconDataShort(datap* parser); 63 | proc BeaconDataShort(parser:ptr Datap):int16{.stdcall.} = 64 | var returnValue:int16 = 0 65 | if(parser.length < 2): 66 | return returnValue 67 | copyMem(addr(returnValue),parser.buffer,2) 68 | parser.length-=2 69 | parser.buffer+=2 70 | return returnValue 71 | 72 | # int BeaconDataLength(datap* parser); 73 | proc BeaconDataLength(parser:ptr Datap):int{.stdcall.} = 74 | return parser.length 75 | 76 | # char* BeaconDataExtract(datap* parser, int* size); 77 | proc BeaconDataExtract(parser:ptr Datap,size:ptr int):ptr char{.stdcall.} = 78 | var length:int32 = 0 79 | var outData: ptr char = nil 80 | if(parser.length < 4): 81 | return NULL 82 | copyMem(addr(length),parser.buffer,4) 83 | parser.buffer += 4 84 | outData = parser.buffer 85 | if(outData == NULL): 86 | return NULL 87 | parser.length -= 4 88 | parser.length -= length 89 | parser.buffer += length 90 | if(size != NULL and outData != NULL): 91 | size[] = length 92 | return outData 93 | 94 | # void BeaconFormatAlloc(formatp* format, int maxsz); 95 | proc BeaconFormatAlloc(format:ptr Formatp,maxsz:int):void{.stdcall.} = 96 | if(format == NULL): 97 | return 98 | #format.original = cast[ptr char](HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,maxsz)) 99 | format.original = cast[ptr char](alloc(maxsz)) 100 | var cursorPtr:ptr byte = cast[ptr byte](format.original) 101 | for i in countup(0,maxsz-1): 102 | cursorPtr[] = 0x00 103 | cursorPtr+=1 104 | format.buffer = format.original 105 | format.length = 0 106 | format.size = maxsz 107 | 108 | # void BeaconFormatReset(formatp* format); 109 | proc BeaconFormatReset(format:ptr Formatp):void{.stdcall.} = 110 | var cursorPtr:ptr byte = cast[ptr byte](format.original) 111 | for i in countup(0,format.size-1): 112 | cursorPtr[] = 0x00 113 | cursorPtr+=1 114 | format.buffer = format.original 115 | format.length = format.size 116 | 117 | # void BeaconFormatFree(formatp* format); 118 | proc BeaconFormatFree(format:ptr Formatp):void{.stdcall.} = 119 | if(format == NULL): 120 | return 121 | if(cast[uint64](format.original) != 0): 122 | dealloc(format.original) 123 | #HeapFree(GetProcessHeap(),cast[DWORD](NULL),cast[LPVOID](format.original)) 124 | format.original = NULL 125 | format.buffer = NULL 126 | format.length = 0 127 | format.size = 0 128 | 129 | # void BeaconFormatAppend(formatp* format, char* text, int len); 130 | proc BeaconFormatAppend(format:ptr Formatp,text:ptr char,len:int):void{.stdcall.} = 131 | copyMem(format.buffer,text,len) 132 | format.buffer+=len 133 | format.length+=len 134 | 135 | # void BeaconPrintf(int type, char* fmt, ...); 136 | # Reference: https://forum.nim-lang.org/t/7352 137 | type va_list* {.importc: "va_list", header: "".} = object 138 | proc va_start(format: va_list, args: ptr char) {.stdcall, importc, header: "stdio.h"} 139 | proc va_end(ap: va_list) {.stdcall, importc, header: "stdio.h"} 140 | proc vprintf(format: cstring, args: va_list) {.stdcall, importc, header: "stdio.h"} 141 | proc vsnprintf(buffer: cstring; size: int; fmt: cstring; args: va_list): int {.stdcall, importc, dynlib: "msvcrt".} 142 | 143 | 144 | # void BeaconFormatPrintf(formatp* format, char* fmt, ...); 145 | proc BeaconFormatPrintf(format:ptr Formatp,fmt:ptr char):void{.stdcall, varargs.} = 146 | var length:int = 0 147 | var args: va_list 148 | va_start(args, fmt) 149 | length = vsnprintf(NULL, 0, fmt, args) 150 | va_end(args) 151 | if(format.length + length > format.size): 152 | return 153 | va_start(args, fmt) 154 | discard vsnprintf(format.buffer,length,fmt,args) 155 | va_end(args) 156 | format.length+=length 157 | format.buffer+=length 158 | 159 | # char* BeaconFormatToString(formatp* format, int* size); 160 | proc BeaconFormatToString(format:ptr Formatp,size:ptr int):ptr char{.stdcall.} = 161 | size[] = format.length 162 | return format.original 163 | 164 | 165 | # uint32_t swap_endianess(uint32_t indata); 166 | proc SwapEndianess(indata:uint32):uint32{.stdcall.} = 167 | var testInt:uint32 = cast[uint32](0xaabbccdd) 168 | var outInt:uint32 = indata 169 | if(cast[ptr uint8](unsafeaddr(testInt))[] == 0xdd): 170 | cast[ptr uint8](unsafeaddr(outInt))[] = (cast[ptr uint8](unsafeaddr(indata))+3)[] 171 | (cast[ptr uint8](unsafeaddr(outInt))+1)[] = (cast[ptr uint8](unsafeaddr(indata))+2)[] 172 | (cast[ptr uint8](unsafeaddr(outInt))+2)[] = (cast[ptr uint8](unsafeaddr(indata))+1)[] 173 | (cast[ptr uint8](unsafeaddr(outInt))+3)[] = cast[ptr uint8](unsafeaddr(indata))[] 174 | return outint 175 | 176 | 177 | # void BeaconFormatInt(formatp* format, int value); 178 | proc BeaconFormatInt(format:ptr Formatp,value:int):void{.stdcall.} = 179 | var indata:uint32 = cast[uint32](value) 180 | var outdata:uint32 = 0 181 | if(format.length + 4 > format.size): 182 | return 183 | outdata = SwapEndianess(indata) 184 | copyMem(format.buffer,addr(outdata),4) 185 | format.length += 4 186 | format.buffer += 4 187 | 188 | const 189 | CALLBACK_OUTPUT = 0x0 190 | CALLBACK_OUTPUT_OEM = 0x1e 191 | CALLBACK_ERROR = 0x0d 192 | CALLBACK_OUTPUT_UTF8 = 0x20 193 | 194 | 195 | proc BeaconPrintf(typeArg:int,fmt:ptr char):void{.stdcall, varargs.} = 196 | var length:int = 0 197 | var tempPtr:ptr char = nil 198 | var args: va_list 199 | va_start(args, fmt) 200 | vprintf(fmt, args) 201 | va_end(args) 202 | 203 | va_start(args, fmt) 204 | length = vsnprintf(NULL,0,fmt,args) 205 | va_end(args) 206 | tempPtr = cast[ptr char](realloc(beaconCompatibilityOutput,beaconCompatibilitySize+length+1)) 207 | if(tempPtr == nil): 208 | return 209 | beaconCompatibilityOutput = tempPtr 210 | for i in countup(0,length): 211 | (beaconCompatibilityOutput + beaconCompatibilityOffset + i)[] = cast[char](0x00) 212 | va_start(args, fmt) 213 | length = vsnprintf(beaconCompatibilityOutput+beaconCompatibilityOffset,length,fmt,args) 214 | beaconCompatibilitySize += length 215 | beaconCompatibilityOffset += length 216 | va_end(args) 217 | 218 | 219 | 220 | 221 | #void BeaconOutput(int type, char* data, int len); 222 | proc BeaconOutput(typeArg:int,data:ptr char,len:int):void{.stdcall.} = 223 | var tempPtr:ptr char = nil 224 | tempPtr = cast[ptr char](realloc(beaconCompatibilityOutput,beaconCompatibilitySize + len + 1)) 225 | beaconCompatibilityOutput = tempPtr 226 | if(tempPtr == nil): 227 | return 228 | for i in countup(0,len): 229 | (beaconCompatibilityOutput + beaconCompatibilityOffset + i)[] = cast[char](0x00) 230 | copyMem(beaconCompatibilityOutput+beaconCompatibilityOffset,data,len) 231 | beaconCompatibilitySize += len 232 | beaconCompatibilityOffset += len 233 | #[ 234 | if(beacon_compatibility_output != nil): 235 | tempPtr = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,beacon_compatibility_output,) 236 | ]# 237 | 238 | # Token Functions 239 | 240 | # BOOL BeaconUseToken(HANDLE token); 241 | proc BeaconUseToken(token: HANDLE):BOOL{.stdcall.} = 242 | SetThreadToken(NULL,token) 243 | return TRUE 244 | 245 | # void BeaconRevertToken(); 246 | proc BeaconRevertToken():void{.stdcall.} = 247 | RevertToSelf() 248 | 249 | # BOOL BeaconIsAdmin(); 250 | # Not implemented 251 | proc BeaconIsAdmin():BOOL{.stdcall.} = 252 | return FALSE 253 | 254 | # Spawn+Inject Functions 255 | # void BeaconGetSpawnTo(BOOL x86, char* buffer, int length); 256 | proc BeaconGetSpawnTo(x86: BOOL, buffer:ptr char, length:int):void{.stdcall.} = 257 | var tempBufferPath:string = "" 258 | if(cast[uint64](buffer) == 0): 259 | return 260 | if(x86): 261 | tempBufferPath = "C:\\Windows\\SysWOW64\\rundll32.exe" 262 | if(tempBufferPath.len > length): 263 | return 264 | copyMem(buffer,unsafeaddr(tempBufferPath[0]),tempBufferPath.len) 265 | else: 266 | tempBufferPath = "C:\\Windows\\System32\\rundll32.exe" 267 | if(tempBufferPath.len > length): 268 | return 269 | copyMem(buffer,unsafeaddr(tempBufferPath[0]),tempBufferPath.len) 270 | 271 | # BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO* sInfo, PROCESS_INFORMATION* pInfo); 272 | proc BeaconSpawnTemporaryProcess(x86: BOOL, ignoreToken:BOOL, sInfo:ptr STARTUPINFOA, pInfo: ptr PROCESS_INFORMATION):BOOL{.stdcall.} = 273 | var bSuccess:BOOL = FALSE 274 | if(x86): 275 | bSuccess = CreateProcessA(NULL,"C:\\Windows\\SysWOW64\\rundll32.exe",NULL,NULL,TRUE,CREATE_NO_WINDOW,NULL,NULL,sInfo,pInfo) 276 | else: 277 | bSuccess = CreateProcessA(NULL,"C:\\Windows\\System32\\rundll32.exe",NULL,NULL,TRUE,CREATE_NO_WINDOW,NULL,NULL,sInfo,pInfo) 278 | return bSuccess 279 | 280 | # void BeaconInjectProcess(HANDLE hProc, int pid, char* payload, int p_len, int p_offset, char* arg, int a_len); 281 | # Not implemented 282 | proc BeaconInjectProcess(hProc: HANDLE, pid:int, payload:ptr char, p_len: int,p_offset: int, arg:ptr char, a_len:int):void{.stdcall.} = 283 | return 284 | 285 | # void BeaconInjectTemporaryProcess(PROCESS_INFORMATION* pInfo, char* payload, int p_len, int p_offset, char* arg, int a_len); 286 | # Not implemented 287 | proc BeaconInjectTemporaryProcess(pInfo: ptr PROCESS_INFORMATION, payload:ptr char, p_len: int,p_offset: int, arg:ptr char, a_len:int):void{.stdcall.} = 288 | return 289 | 290 | # void BeaconCleanupProcess(PROCESS_INFORMATION* pInfo); 291 | proc BeaconCleanupProcess(pInfo: ptr PROCESS_INFORMATION):void{.stdcall.} = 292 | CloseHandle(pInfo.hThread) 293 | CloseHandle(pInfo.hProcess) 294 | 295 | # Utility Functions 296 | # BOOL toWideChar(char* src, wchar_t* dst, int max); TODO FIX 297 | # Not implemented 298 | proc toWideChar(src:ptr char,dst: ptr char ,max: int):BOOL{.stdcall.} = 299 | return FALSE 300 | 301 | 302 | # char* BeaconGetOutputData(int* outsize); 303 | proc BeaconGetOutputData*(outSize:ptr int):ptr char{.stdcall.} = 304 | var outData:ptr char = beaconCompatibilityOutput 305 | if(cast[uint64](outSize) != 0): 306 | outsize[] = beaconCompatibilitySize 307 | beaconCompatibilityOutput = NULL 308 | beaconCompatibilitySize = 0 309 | beaconCompatibilityOffset = 0 310 | return outData 311 | 312 | var functionAddresses*:array[23,tuple[name: string, address: uint64]] = [ 313 | ("BeaconDataParse", cast[uint64](BeaconDataParse)), 314 | ("BeaconDataInt", cast[uint64](BeaconDataInt)), 315 | ("BeaconDataShort", cast[uint64](BeaconDataShort)), 316 | ("BeaconDataLength", cast[uint64](BeaconDataLength)), 317 | ("BeaconDataExtract", cast[uint64](BeaconDataExtract)), 318 | ("BeaconFormatAlloc", cast[uint64](BeaconFormatAlloc)), 319 | ("BeaconFormatReset", cast[uint64](BeaconFormatReset)), 320 | ("BeaconFormatFree", cast[uint64](BeaconFormatFree)), 321 | ("BeaconFormatAppend", cast[uint64](BeaconFormatAppend)), 322 | ("BeaconFormatPrintf", cast[uint64](BeaconFormatPrintf)), 323 | ("BeaconFormatToString", cast[uint64](BeaconFormatToString)), 324 | ("BeaconFormatInt", cast[uint64](BeaconFormatInt)), 325 | ("BeaconPrintf", cast[uint64](BeaconPrintf)), 326 | ("BeaconOutput", cast[uint64](BeaconOutput)), 327 | ("BeaconUseToken", cast[uint64](BeaconUseToken)), 328 | ("BeaconRevertToken", cast[uint64](BeaconRevertToken)), 329 | ("BeaconIsAdmin", cast[uint64](BeaconIsAdmin)), 330 | ("BeaconGetSpawnTo", cast[uint64](BeaconGetSpawnTo)), 331 | ("BeaconSpawnTemporaryProcess", cast[uint64](BeaconSpawnTemporaryProcess)), 332 | ("BeaconInjectProcess", cast[uint64](BeaconInjectProcess)), 333 | ("BeaconInjectTemporaryProcess", cast[uint64](BeaconInjectTemporaryProcess)), 334 | ("BeaconCleanupProcess", cast[uint64](BeaconCleanupProcess)), 335 | ("toWideChar", cast[uint64](toWideChar)) 336 | ] -------------------------------------------------------------------------------- /Main.nim: -------------------------------------------------------------------------------- 1 | import winim/lean 2 | import ptr_math 3 | import std/streams 4 | import std/strutils 5 | import os 6 | import system 7 | import Structs 8 | import BeaconFunctions 9 | 10 | type COFFEntry = proc(args:ptr byte, argssize: uint32) {.stdcall.} 11 | 12 | 13 | proc PrintBanner():void = 14 | var banner = """ 15 | ______ _ ______ _____ _______ _______ 16 | | ___ \(_)/ _____) ___ \(_______|_______) 17 | | | | |_| / | | | |_____ _____ 18 | | | | | | | | | | | ___) | ___) 19 | | | | | | \____| |___| | | | | 20 | |_| |_|_|\______)_____/|_| |_| 21 | 22 | @R0h1rr1m 23 | """ 24 | echo banner 25 | 26 | proc DisplayHelp():void = 27 | echo "[!] Usage: ",getAppFilename()," " 28 | 29 | proc ReadFileFromDisk(filePath:string):seq[byte] = 30 | var strm = newFileStream(filePath, fmRead) 31 | if(not isNil(strm)): 32 | var fileBuffer:string = strm.readAll() 33 | var returnValue:seq[byte] = @(fileBuffer.toOpenArrayByte(0,fileBuffer.high)) 34 | return returnValue 35 | return @[] 36 | 37 | proc HexStringToByteArray(hexString:string,hexLength:int):seq[byte] = 38 | var returnValue:seq[byte] = @[] 39 | for i in countup(0,hexLength-1,2): 40 | try: 41 | #cho hexString[i..i+1] 42 | returnValue.add(fromHex[uint8](hexString[i..i+1])) 43 | except ValueError: 44 | return @[] 45 | #fromHex[uint8] 46 | return returnValue 47 | 48 | # By traversing the relocations of text section, we can count the external functions 49 | proc GetNumberOfExternalFunctions(fileBuffer:seq[byte],textSectionHeader:ptr SectionHeader):uint64 = 50 | var returnValue:uint64=0 51 | var symbolTableCursor:ptr SymbolTableEntry = nil 52 | var symbolTable:ptr SymbolTableEntry = cast[ptr SymbolTableEntry](unsafeAddr(fileBuffer[0]) + cast[int]((cast[ptr FileHeader](unsafeAddr(fileBuffer[0]))).PointerToSymbolTable)) 53 | var relocationTableCursor:ptr RelocationTableEntry = cast[ptr RelocationTableEntry](unsafeAddr(fileBuffer[0]) + cast[int](textSectionHeader.PointerToRelocations)) 54 | for i in countup(0,cast[int](textSectionHeader.NumberOfRelocations-1)): 55 | symbolTableCursor = cast[ptr SymbolTableEntry](symbolTable + cast[int](relocationTableCursor.SymbolTableIndex)) 56 | # Condition for an external symbol 57 | if(symbolTableCursor.StorageClass == IMAGE_SYM_CLASS_EXTERNAL and symbolTableCursor.SectionNumber == 0): 58 | returnValue+=1 59 | relocationTableCursor+=1 60 | return returnValue * cast[uint64](sizeof(ptr uint64)) 61 | 62 | proc GetExternalFunctionAddress(symbolName:string):uint64 = 63 | var prefixSymbol:string = "__imp_" 64 | var prefixBeacon:string = "__imp_Beacon" 65 | var prefixToWideChar:string = "__imp_toWideChar" 66 | var libraryName:string = "" 67 | var functionName:string = "" 68 | var returnAddress:uint64 = 0 69 | var symbolWithoutPrefix:string = symbolName[6..symbolName.len-1] 70 | if(not symbolName.startsWith(prefixSymbol)): 71 | echo "[!] Function with unknown naming convention! [",symbolName,"]" 72 | return returnAddress 73 | # Check is it our cs function implementation 74 | if(symbolName.startsWith(prefixBeacon) or symbolName.startsWith(prefixToWideChar)): 75 | for i in countup(0,22): 76 | if(symbolWithoutPrefix == functionAddresses[i].name): 77 | return functionAddresses[i].address 78 | else: 79 | try: 80 | # Why removePrefix doesn't work with 2 strings argument? 81 | var symbolSubstrings:seq[string] = symbolWithoutPrefix.split({'@','$'},2) 82 | libraryName = symbolSubstrings[0] 83 | functionName = symbolSubstrings[1] 84 | except: 85 | echo "[!] Symbol splitting problem! [",symbolName,"]" 86 | return returnAddress 87 | var libraryHandle:HMODULE = LoadLibraryA(addr(libraryName[0])) 88 | if(libraryHandle != 0): 89 | returnAddress = cast[uint64](GetProcAddress(libraryHandle,addr(functionName[0]))) 90 | if(returnAddress == 0): 91 | echo "[!] Error on Function address! [",functionName,"]" 92 | return returnAddress 93 | else: 94 | echo "[!] Error on loading library! [",libraryName,"]" 95 | return returnAddress 96 | 97 | 98 | proc Read32Le(p:ptr uint8):uint32 = 99 | var val1:uint32 = cast[uint32](p[0]) 100 | var val2:uint32 = cast[uint32](p[1]) 101 | var val3:uint32 = cast[uint32](p[2]) 102 | var val4:uint32 = cast[uint32](p[3]) 103 | return (val1 shl 0) or (val2 shl 8) or (val3 shl 16) or (val4 shl 24) 104 | 105 | proc Write32Le(dst:ptr uint8,x:uint32):void = 106 | dst[0] = cast[uint8](x shr 0) 107 | dst[1] = cast[uint8](x shr 8) 108 | dst[2] = cast[uint8](x shr 16) 109 | dst[3] = cast[uint8](x shr 24) 110 | 111 | proc Add32(p:ptr uint8, v:uint32) = 112 | Write32le(p,Read32le(p)+v) 113 | 114 | proc ApplyGeneralRelocations(patchAddress:uint64,sectionStartAddress:uint64,givenType:uint16,symbolOffset:uint32):void = 115 | var pAddr8:ptr uint8 = cast[ptr uint8](patchAddress) 116 | var pAddr64:ptr uint64 = cast[ptr uint64](patchAddress) 117 | case givenType: 118 | of IMAGE_REL_AMD64_REL32: 119 | Add32(pAddr8, cast[uint32](sectionStartAddress + cast[uint64](symbolOffset) - patchAddress - 4)) 120 | return 121 | of IMAGE_REL_AMD64_ADDR32NB: 122 | Add32(pAddr8, cast[uint32](sectionStartAddress - patchAddress - 4)) 123 | return 124 | of IMAGE_REL_AMD64_ADDR64: 125 | pAddr64[] = pAddr64[] + sectionStartAddress 126 | return 127 | else: 128 | echo "[!] No code for type: ",givenType 129 | 130 | var allocatedMemory:LPVOID = nil 131 | 132 | proc RunCOFF(functionName:string,fileBuffer:seq[byte],argumentBuffer:seq[byte]):bool = 133 | var fileHeader:ptr FileHeader = cast[ptr FileHeader](unsafeAddr(fileBuffer[0])) 134 | var totalSize:uint64 = 0 135 | # Some COFF files may have Optional Header to just increase the size according to MSDN 136 | var sectionHeaderArray:ptr SectionHeader = cast[ptr SectionHeader] (unsafeAddr(fileBuffer[0])+cast[int](fileHeader.SizeOfOptionalHeader)+sizeof(FileHeader)) 137 | var sectionHeaderCursor:ptr SectionHeader = sectionHeaderArray 138 | var textSectionHeader:ptr SectionHeader = nil 139 | var sectionInfoList: seq[SectionInfo] = @[] 140 | var tempSectionInfo:SectionInfo 141 | var memoryCursor:uint64 = 0 142 | var symbolTable:ptr SymbolTableEntry = cast[ptr SymbolTableEntry](unsafeAddr(fileBuffer[0]) + cast[int](fileHeader.PointerToSymbolTable)) 143 | var symbolTableCursor:ptr SymbolTableEntry = nil 144 | var relocationTableCursor:ptr RelocationTableEntry = nil 145 | var sectionIndex:int = 0 146 | var isExternal:bool = false 147 | var isInternal:bool = false 148 | var patchAddress:uint64 = 0 149 | var stringTableOffset:int = 0 150 | var symbolName:string = "" 151 | var externalFunctionCount:int = 0 152 | var externalFunctionStoreAddress:ptr uint64 = nil 153 | var tempFunctionAddr:uint64 = 0 154 | var delta:uint64 = 0 155 | var tempPointer:ptr uint32 = nil 156 | var entryAddress:uint64 = 0 157 | var sectionStartAddress:uint64 = 0 158 | # Calculate the total size for allocation 159 | for i in countup(0,cast[int](fileHeader.NumberOfSections-1)): 160 | if($(addr(sectionHeaderCursor.Name[0])) == ".text"): 161 | # Seperate saving for text section header 162 | textSectionHeader = sectionHeaderCursor 163 | # Save the section info 164 | tempSectionInfo.Name = $(addr(sectionHeaderCursor.Name[0])) 165 | tempSectionInfo.SectionOffset = totalSize 166 | tempSectionInfo.SectionHeaderPtr = sectionHeaderCursor 167 | sectionInfoList.add(tempSectionInfo) 168 | # Add the size 169 | totalSize+=sectionHeaderCursor.SizeOfRawData 170 | sectionHeaderCursor+=1 171 | if(textSectionHeader.isNil()): 172 | echo "[!] Text section is not found!" 173 | return false 174 | # We need to store external function addresses too 175 | allocatedMemory = VirtualAlloc(NULL, cast[UINT32](totalSize+GetNumberOfExternalFunctions(fileBuffer,textSectionHeader)), MEM_COMMIT or MEM_RESERVE or MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE) 176 | if(allocatedMemory == NULL): 177 | echo "[!] Failed for memory allocation!" 178 | return false 179 | # Now copy the sections 180 | sectionHeaderCursor = sectionHeaderArray 181 | externalFunctionStoreAddress = cast[ptr uint64](totalSize+cast[uint64](allocatedMemory)) 182 | for i in countup(0,cast[int](fileHeader.NumberOfSections-1)): 183 | copyMem(cast[LPVOID](cast[uint64](allocatedMemory)+memoryCursor),unsafeaddr(fileBuffer[0])+cast[int](sectionHeaderCursor.PointerToRawData),sectionHeaderCursor.SizeOfRawData) 184 | memoryCursor += sectionHeaderCursor.SizeOfRawData 185 | sectionHeaderCursor+=1 186 | echo "[+] Sections are copied!" 187 | # Start relocations 188 | for i in countup(0,cast[int](fileHeader.NumberOfSections-1)): 189 | # Traverse each section for its relocations 190 | echo " [+] Relocations for section: ",sectionInfoList[i].Name 191 | relocationTableCursor = cast[ptr RelocationTableEntry](unsafeAddr(fileBuffer[0]) + cast[int](sectionInfoList[i].SectionHeaderPtr.PointerToRelocations)) 192 | for relocationCount in countup(0, cast[int](sectionInfoList[i].SectionHeaderPtr.NumberOfRelocations)-1): 193 | symbolTableCursor = cast[ptr SymbolTableEntry](symbolTable + cast[int](relocationTableCursor.SymbolTableIndex)) 194 | sectionIndex = cast[int](symbolTableCursor.SectionNumber - 1) 195 | isExternal = (symbolTableCursor.StorageClass == IMAGE_SYM_CLASS_EXTERNAL and symbolTableCursor.SectionNumber == 0) 196 | isInternal = (symbolTableCursor.StorageClass == IMAGE_SYM_CLASS_EXTERNAL and symbolTableCursor.SectionNumber != 0) 197 | patchAddress = cast[uint64](allocatedMemory) + sectionInfoList[i].SectionOffset + cast[uint64](relocationTableCursor.VirtualAddress - sectionInfoList[i].SectionHeaderPtr.VirtualAddress) 198 | if(isExternal): 199 | # If it is function 200 | stringTableOffset = cast[int](symbolTableCursor.First.value[1]) 201 | symbolName = $(cast[ptr byte](symbolTable+cast[int](fileHeader.NumberOfSymbols))+stringTableOffset) 202 | tempFunctionAddr = GetExternalFunctionAddress(symbolName) 203 | if(tempFunctionAddr != 0): 204 | (externalFunctionStoreAddress + externalFunctionCount)[] = tempFunctionAddr 205 | delta = cast[uint64]((externalFunctionStoreAddress + externalFunctionCount)) - cast[uint64](patchAddress) - 4 206 | tempPointer = cast[ptr uint32](patchAddress) 207 | tempPointer[] = cast[uint32](delta) 208 | externalFunctionCount+=1 209 | else: 210 | echo "[!] Unknown symbol resolution! [",symbolName,"]" 211 | return false 212 | else: 213 | if(sectionIndex >= sectionInfoList.len or sectionIndex < 0): 214 | echo "[!] Error on symbol section index! [",sectionIndex,"]" 215 | return false 216 | sectionStartAddress = cast[uint64](allocatedMemory) + sectionInfoList[sectionIndex].SectionOffset 217 | if(isInternal): 218 | for internalCount in countup(0,sectionInfoList.len-1): 219 | if(sectionInfoList[internalCount].Name == ".text"): 220 | sectionStartAddress = cast[uint64](allocatedMemory) + sectionInfoList[internalCount].SectionOffset 221 | break 222 | ApplyGeneralRelocations(patchAddress,sectionStartAddress,relocationTableCursor.Type,symbolTableCursor.Value) 223 | relocationTableCursor+=1 224 | echo "[+] Relocations are done!" 225 | for i in countup(0,cast[int](fileHeader.NumberOfSymbols-1)): 226 | symbolTableCursor = symbolTable + i 227 | if(functionName == $(addr(symbolTableCursor.First.Name[0]))): 228 | echo "[+] Trying to find the entry: ",functionName 229 | entryAddress = cast[uint64](allocatedMemory) + sectionInfoList[symbolTableCursor.SectionNumber-1].SectionOffset + symbolTableCursor.Value 230 | if(entryAddress == 0): 231 | echo "[!] Entry not found!" 232 | return false 233 | var entryPtr:COFFEntry = cast[COFFEntry](entryAddress) 234 | echo "[+] ",functionName," entry found! " 235 | echo "[+] Executing..." 236 | if(argumentBuffer.len == 0): 237 | entryPtr(NULL,0) 238 | else: 239 | entryPtr(unsafeaddr(argumentBuffer[0]),cast[uint32](argumentBuffer.len)) 240 | return true 241 | 242 | when isMainModule: 243 | PrintBanner() 244 | if(paramCount() < 2 or paramCount() > 3): 245 | DisplayHelp() 246 | quit(0) 247 | # Read the file 248 | var fileBuffer:seq[byte] = ReadFileFromDisk(paramStr(1)) 249 | var argumentBuffer:seq[byte] = @[] 250 | if(fileBuffer.len == 0): 251 | echo "[!] Error on file read! [",paramStr(1),"]" 252 | quit(0) 253 | echo "[+] File is read!" 254 | if(paramCount() == 3): 255 | # Unhexlify the arguments 256 | argumentBuffer = HexStringToByteArray(paramStr(3),paramStr(3).len) 257 | if(argumentBuffer.len == 0): 258 | echo "[!] Error on unhexlifying the argument!" 259 | quit(0) 260 | echo "[+] Argument is unhexlified!" 261 | # Run COFF file 262 | if(not RunCOFF(paramStr(2),fileBuffer,argumentBuffer)): 263 | echo "[!] Error on executing file!" 264 | VirtualFree(allocatedMemory, 0, MEM_RELEASE) 265 | quit(0) 266 | echo "[+] COFF File is Executed!" 267 | var outData:ptr char = BeaconGetOutputData(NULL); 268 | if(outData != NULL): 269 | echo "[+] Output Below:\n\n" 270 | echo $outData 271 | VirtualFree(allocatedMemory, 0, MEM_RELEASE) 272 | 273 | 274 | 275 | 276 | --------------------------------------------------------------------------------