├── .gitignore ├── LICENSE ├── README.md ├── RunOF ├── RunOF.sln └── RunOF │ ├── App.config │ ├── Internals │ ├── BofRunner.cs │ ├── Coff.cs │ ├── FileDescriptorRedirector.cs │ ├── IAT.cs │ ├── ImageParts.cs │ ├── NativeDeclarations.cs │ └── ParsedArgs.cs │ ├── Program.cs │ ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx │ └── RunOF.csproj ├── beacon_funcs ├── Makefile ├── beacon_funcs.c └── beacon_funcs.h ├── demo_files ├── Makefile ├── beacon.h └── demo_bof.c └── tests └── test-bof.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore beacon_funcs obj files for now 2 | beacon_funcs/*.o 3 | 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | ## 7 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 8 | 9 | # User-specific files 10 | *.rsuser 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Mono auto generated files 20 | mono_crash.* 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | [Ww][Ii][Nn]32/ 30 | [Aa][Rr][Mm]/ 31 | [Aa][Rr][Mm]64/ 32 | bld/ 33 | [Bb]in/ 34 | [Oo]bj/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET Core 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio LightSwitch build output 300 | **/*.HTMLClient/GeneratedArtifacts 301 | **/*.DesktopClient/GeneratedArtifacts 302 | **/*.DesktopClient/ModelManifest.xml 303 | **/*.Server/GeneratedArtifacts 304 | **/*.Server/ModelManifest.xml 305 | _Pvt_Extensions 306 | 307 | # Paket dependency manager 308 | .paket/paket.exe 309 | paket-files/ 310 | 311 | # FAKE - F# Make 312 | .fake/ 313 | 314 | # CodeRush personal settings 315 | .cr/personal 316 | 317 | # Python Tools for Visual Studio (PTVS) 318 | __pycache__/ 319 | *.pyc 320 | 321 | # Cake - Uncomment if you are using it 322 | # tools/** 323 | # !tools/packages.config 324 | 325 | # Tabs Studio 326 | *.tss 327 | 328 | # Telerik's JustMock configuration file 329 | *.jmconfig 330 | 331 | # BizTalk build output 332 | *.btp.cs 333 | *.btm.cs 334 | *.odx.cs 335 | *.xsd.cs 336 | 337 | # OpenCover UI analysis results 338 | OpenCover/ 339 | 340 | # Azure Stream Analytics local run output 341 | ASALocalRun/ 342 | 343 | # MSBuild Binary and Structured Log 344 | *.binlog 345 | 346 | # NVidia Nsight GPU debugger configuration file 347 | *.nvuser 348 | 349 | # MFractors (Xamarin productivity tool) working folder 350 | .mfractor/ 351 | 352 | # Local History for Visual Studio 353 | .localhistory/ 354 | 355 | # BeatPulse healthcheck temp database 356 | healthchecksdb 357 | 358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 359 | MigrationBackup/ 360 | 361 | # Ionide (cross platform F# VS Code tools) working folder 362 | .ionide/ 363 | 364 | # Fody - auto-generated XML schema 365 | FodyWeavers.xsd 366 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Nettitude 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RunOF 2 | 3 | A tool to run object files, mainly beacon object files (BOF), in .Net. 4 | 5 | ## Usage 6 | 7 | ``` 8 | -h Show this help text 9 | -v Show very verbose logs. In debug builds will also pause before starting the OF to allow you to attach a debugger 10 | 11 | One of these is required: 12 | -f Path to an object file to load 13 | -a Base64 encoded object file 14 | 15 | Optional arguments: 16 | 17 | -t Set thread timeout (in seconds) - default 30 if not specified 18 | -e Set entry function name - defaults to go 19 | 20 | These are passed to the object file *in the order they are on the command line*. 21 | 22 | -i:123 A 32 bit integer (e.g. 123 passed to object file) 23 | -s:12 A 16 bit integer (e.g. 12 passed to object file) 24 | -z:hello An ASCII string (e.g. hello passed to object file) 25 | -Z:hello A string that's converted to wchar (e.g. (wchar_t)hello passed to object file) 26 | -b:aGVsbG8= A base64 encoded binary blob (decoded binary passed to object file) 27 | 28 | To specify an empty string just leave it blank (e.g. -Z: ) 29 | ``` 30 | 31 | Some BOF files take arguments, which would normally be parsed and "packed" by the aggressor script. In RunOF you need to provide these, in the same order that they would be packed in the aggressor script. 32 | 33 | So, for example, if a cna file has the following bof_pack statement: 34 | 35 | ``` 36 | $args = bof_pack($1, "Zs", $targetdir, $subdirs); 37 | ``` 38 | 39 | Then you would specify the command line as: 40 | 41 | ``` 42 | RunOF.exe -f -Z:targetdir -s:subdirs 43 | ``` 44 | 45 | where targetdir would be a path (like C:\) and -s a 16 bit integer (in this case, 1 to recurse). The ordering and types are important - they must be specified in the same order as in the pack statement for the BOF to read them successfully. 46 | 47 | If you need to specify an empty parameter (e.g. an empty string) then leave it blank (e.g. -Z: ). 48 | 49 | If the BOF file attempts to read an argument that isn't provided then zero is provided for numeric types and an empty string (single null character) for string types. 50 | 51 | ### Debugging 52 | 53 | To enable copious log messages use the -v command line option. 54 | 55 | If you have a debug build, then if the -v flag is passed it will pause before starting the OF thread to allow you to attach a debugger. You can then set a breakpoint at the thread entry address to debug the loaded object file. 56 | 57 | ## TODO list 58 | 59 | Main things to do are: 60 | 61 | - [ ] Passing arguments to BOFs 62 | - [ ] A testing framework (i.e. run a load of BOFs and check it all works) 63 | - [ ] Command line & integration into Posh (mirror RunPE) 64 | - [ ] General tidy up (especially logging) 65 | 66 | ## Components 67 | 68 | ### beacon_funcs 69 | 70 | This is the "glue" that sits between our unmanaged object file and managed executable. It contains: 71 | - A wrapper function that does some housekeeping and runs the object file entry point 72 | - An exception handler so if something goes wrong in the OF it can return an error code and message 73 | - Implementations of the Beacon* functions (e.g. BeaconPrintf) that are normally provided by Cobalt Strike 74 | 75 | Since these will be called from the BOF, they need to be unmanaged code. Therefore, these are written in C, and compiled into an object file using the provided Makefile. This object file is then loaded into memory in the same way as a "normal" BOF, and the addresses of the various Beacon* functions stored to provide to the BOF later. 76 | 77 | ### RunOF 78 | 79 | A .Net application that loads the object file into memory, gets it ready for execution and executes it in a new thread. 80 | 81 | ## How it all works 82 | 83 | Object files in Windows are defined by the COFF standard. This is not intended to be directly executed, but it is possible to load and execute as follows: 84 | 85 | ### Find section info 86 | 87 | A COFF file consists of a set of sections (text, rodata, bss etc.) that contain the code and data needed to execute. 88 | 89 | ### Find symbol information 90 | 91 | A COFF file contains a set of symbols which relate to functions and variables that are either defined within the file (e.g. our "go" function) or that need to be imported 92 | 93 | ### Load into memory 94 | 95 | In order to set permissions later, the sections of the COFF file need to loaded into memory on page aligned boundaries (unlike a PE, COFF sections are not page aligned). This is done by allocating a number of pages large enough to contain the section contents and copying into that region. 96 | 97 | For now, memory is set to RW so we can write relocations to it. 98 | 99 | ### Resolve relocations 100 | 101 | Because an object file is designed to be linked together with others in arbitrary order, each section ends with an array of relocation records that define how to update references to other symbols within the section. These must be processed and the address of the symbol written according to the relocation rule. This also allows us to determine if the symbol is "internal" to the object file, or whether it needs to be resolved. There are two types of resolution: 102 | 103 | - Win32 API calls, in the format LIBRARY$Function. These are currently just resolved with LoadLibrary and GetProcAddress. 104 | - Beacon* functions. These are resolved to the addresses loaded in the beacon_functions object file. 105 | 106 | For all imports, a function pointer to the function needs to be returned rather than the function's address. Therefore, the loader also implements a basic "import address table" (IAT) which is simply an array of function pointers. 107 | 108 | ### Set permissions 109 | 110 | The sections have memory permissions (e.g. RW / R / RX) set as per the header flags. 111 | 112 | ### Locate entry function 113 | 114 | In order to pass arguments to the BOF, we needed to implement a wrapper function that exists in our beacon_funcs object and takes our global argument buffer pointer (which exists in our data section) and supplies it to the BOF's go function as a function argument (e.g. in a register for x64 or on the stack for x86). We need therefore to update our go_wrapper function with a pointer to the target BOF's entry function. 115 | 116 | Usually, a BOF's entry function is called "go", but it is possible to specify an alternative with the -e command line flag. 117 | 118 | 119 | 120 | ### Execute! 121 | 122 | The code is now executed in a new thread, with a timer set (default 30 seconds, can be changed with the -t flag). 123 | 124 | ### Retrieve output 125 | 126 | The BeaconOutput functions in beacon_funcs write any output the BOF generates into a global_output_buffer, which is allocated on the heap. This buffer can be reallocated to make space for more output, so the .Net assembly must read its new location and size from the BOF's memory before reading the output. 127 | 128 | ### Cleanup 129 | 130 | All memory allocated is zeroed and freed before the application exits. 131 | 132 | ### BOF Arguments 133 | 134 | BOF files can accept arguments, that in Cobalt land are "packed" before use with the bof_pack command. Fundamentally, this is a buffer containing data values, which can be unpacked in the BOF by using BeaconDataInt, BeaconDataShort etc. 135 | 136 | **Note** This interface has been implemented completely independantly of Cobalt (no reverse engineering), so any assumptions/bugs etc. are ours. 137 | 138 | Implementation: 139 | 140 | The datap data struct remains the same as that defined in beacon.h: 141 | ``` 142 | datap struct { 143 | char * original; // pointer to start ("so we can free it" - but when?) 144 | char * buffer; // current ptr position 145 | int length; // remaining length of data 146 | int size; // todal size of the buffer 147 | } 148 | ``` 149 | 150 | We implement these functions: 151 | 152 | - BeaconDataParse (initialises a data parser - I think less relevant in our context but it will be called) 153 | - BeaconDataExtract (return a char* or wchar*) 154 | - BeaconDataInt (int32) 155 | - BeaconDataLength (data left to parse) 156 | - BeaconDataShort (int16) 157 | 158 | The bof_pack function outlines five types of data that can be packed with a format specifier: 159 | 160 | | Type | Desciption | Unpack with | 161 | |------|------------------------|-------------------| 162 | | b | binary data | BeaconDataExtract | 163 | | i | 4-byte int | BeaconDataInt | 164 | | s | 2-byte int | BeaconDataShort | 165 | | z | zero term string | BeaconDataExtract | 166 | | Z | zert-term wchar string | BeaconDataExtract | 167 | 168 | 169 | 170 | The .Net code "serialises" arguments into TLV values, allocates some unmanaged memory and writes its address into a global pointer variable that is in the beacon_functions OF's memory. The TLV encoding is something like this (pseudocode) 171 | 172 | 173 | ``` 174 | 175 | 176 | 177 | enum data_types { 178 | BINARY_DATA, 179 | 4BYTE_INT, 180 | 2BYTE_INT, 181 | STRING, 182 | WSTRING 183 | } // as a uint32 - a bit overkill for five values! 184 | 185 | utin32_t length; 186 | 187 | // example (will actually be little endian in practise I guess): 188 | 189 | 190 | T:STRING | LENGTH | Value | 191 | 00 00 00 03 | 00 00 00 06 | Hello\0 | 192 | ``` 193 | 194 | Multiple TLVs are stacked together one after the other in the allocated memory region. The BeaconData* functions then extract values from this memory region. There are a number of constraints imposed by this scheme: 195 | - BOF arguments *must* be provided in the order that the BOF expects to receive them. For example, if the BOF calls BeaconDataInt then BeaconDataShort the arguments must be passed as int then short. If they are passed in the wrong order than BeaconDataInt will fail because the type of the first argument will not match. 196 | - Arguments have a maximum length of around 4GB. That should be plenty! 197 | 198 | 199 | ## Useful References 200 | 201 | - https://docs.microsoft.com/en-us/windows/win32/debug/pe-format (especially "Other Contents of the File" section) 202 | - https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=msvc-160 203 | -------------------------------------------------------------------------------- /RunOF/RunOF.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30320.27 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunOF", "RunOF\RunOF.csproj", "{3A190F78-B02A-489D-B681-12D82730465D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {3A190F78-B02A-489D-B681-12D82730465D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {3A190F78-B02A-489D-B681-12D82730465D}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {3A190F78-B02A-489D-B681-12D82730465D}.Debug|x64.ActiveCfg = Debug|x64 21 | {3A190F78-B02A-489D-B681-12D82730465D}.Debug|x64.Build.0 = Debug|x64 22 | {3A190F78-B02A-489D-B681-12D82730465D}.Debug|x86.ActiveCfg = Debug|x86 23 | {3A190F78-B02A-489D-B681-12D82730465D}.Debug|x86.Build.0 = Debug|x86 24 | {3A190F78-B02A-489D-B681-12D82730465D}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {3A190F78-B02A-489D-B681-12D82730465D}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {3A190F78-B02A-489D-B681-12D82730465D}.Release|x64.ActiveCfg = Release|x64 27 | {3A190F78-B02A-489D-B681-12D82730465D}.Release|x64.Build.0 = Release|x64 28 | {3A190F78-B02A-489D-B681-12D82730465D}.Release|x86.ActiveCfg = Release|x86 29 | {3A190F78-B02A-489D-B681-12D82730465D}.Release|x86.Build.0 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {516A860D-8D4B-4C31-A095-0898D062DC46} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /RunOF/RunOF/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /RunOF/RunOF/Internals/BofRunner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Linq; 5 | using System.Text; 6 | using System.IO; 7 | using System.Threading.Tasks; 8 | using System.Reflection; 9 | 10 | namespace RunOF.Internals 11 | { 12 | class BofRunner 13 | { 14 | private readonly Coff beacon_helper; 15 | private Coff bof; 16 | public IntPtr entry_point; 17 | private readonly IAT iat; 18 | public ParsedArgs parsed_args; 19 | public BofRunner(ParsedArgs parsed_args) 20 | { 21 | Logger.Debug("Initialising bof runner"); 22 | this.parsed_args = parsed_args; 23 | 24 | // first we need a basic IAT to hold function pointers 25 | // this needs to be done here so we can share it between our two object files 26 | this.iat = new IAT(); 27 | 28 | // First init our beacon helper object file 29 | // This has the code for things like BeaconPrintf, BeaconOutput etc. 30 | // It also has a wrapper for the bof entry point (go_wrapper) that allows us to pass arguments. 31 | byte[] beacon_funcs; 32 | string [] resource_names = Assembly.GetExecutingAssembly().GetManifestResourceNames(); 33 | if (resource_names.Contains("RunOF.beacon_funcs")) 34 | { 35 | var ms = new MemoryStream(); 36 | Stream resStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("RunOF.beacon_funcs"); 37 | resStream.CopyTo(ms); 38 | beacon_funcs = ms.ToArray(); 39 | } else 40 | { 41 | throw new Exception("Unable to load beacon_funcs resource"); 42 | } 43 | 44 | try 45 | { 46 | this.beacon_helper = new Coff(beacon_funcs, this.iat); 47 | 48 | } catch (Exception e) 49 | { 50 | throw e; 51 | } 52 | 53 | // Serialise the arguments we want to send to our object file 54 | // Find our helper functions and entry wrapper (go_wrapper) 55 | this.entry_point = this.beacon_helper.ResolveHelpers(parsed_args.SerialiseArgs(), parsed_args.debug); 56 | 57 | // this needs to be called after we've finished monkeying around with the BOF's memory 58 | this.beacon_helper.SetPermissions(); 59 | 60 | } 61 | 62 | public void LoadBof() 63 | { 64 | 65 | Logger.Debug("Loading boff object..."); 66 | // create new coff 67 | this.bof = new Coff(this.parsed_args.file_bytes, this.iat); 68 | Logger.Debug($"Loaded BOF with entry {this.entry_point.ToInt64():X}"); 69 | // stitch up our go_wrapper and go functions 70 | this.bof.StitchEntry(this.parsed_args.entry_name); 71 | 72 | this.bof.SetPermissions(); 73 | } 74 | 75 | public BofRunnerOutput RunBof(uint timeout) 76 | { 77 | Logger.Debug($"Starting bof in new thread @ {this.entry_point.ToInt64():X}"); 78 | Logger.Debug(" --- MANAGED CODE END --- "); 79 | IntPtr hThread = NativeDeclarations.CreateThread(IntPtr.Zero, 0, this.entry_point, IntPtr.Zero, 0, IntPtr.Zero); 80 | var resp = NativeDeclarations.WaitForSingleObject(hThread, (uint)(parsed_args.thread_timeout)); 81 | 82 | if (resp == (uint)NativeDeclarations.WaitEventEnum.WAIT_TIMEOUT) 83 | { 84 | Logger.Info($"BOF timed out after {parsed_args.thread_timeout / 1000} seconds"); 85 | } 86 | 87 | Console.Out.Flush(); 88 | Logger.Debug(" --- MANAGED CODE START --- "); 89 | 90 | int ExitCode; 91 | 92 | NativeDeclarations.GetExitCodeThread(hThread, out ExitCode); 93 | 94 | 95 | if (ExitCode < 0) 96 | { 97 | Logger.Info($"Bof thread exited with code {ExitCode} - see above for exception information. "); 98 | 99 | } 100 | 101 | 102 | // try reading from our shared buffer 103 | // the buffer may have moved (e.g. if realloc'd) so we need to get its latest address 104 | var output_addr = Marshal.ReadIntPtr(beacon_helper.global_buffer); 105 | // NB this is the size of the allocated buffer, not its contents, and we'll read all of its size - this may or may not be an issue depending on what is written 106 | var output_size = Marshal.ReadInt32(beacon_helper.global_buffer_size_ptr); 107 | 108 | Logger.Debug($"Output buffer size {output_size} located at {output_addr.ToInt64():X}"); 109 | 110 | List output = new List(); 111 | 112 | byte c; 113 | int i = 0; 114 | while ((c = Marshal.ReadByte(output_addr + i)) != '\0' && i < output_size) { 115 | output.Add(c); 116 | i++; 117 | } 118 | 119 | // Now cleanup all memory... 120 | 121 | BofRunnerOutput Response = new BofRunnerOutput(); 122 | 123 | Response.Output = Encoding.ASCII.GetString(output.ToArray()); 124 | Response.ExitCode = ExitCode; 125 | 126 | ClearMemory(); 127 | 128 | return Response; 129 | 130 | } 131 | 132 | private void ClearMemory() 133 | { 134 | /* things that need cleaning up: 135 | - beacon_funcs BOF 136 | - the bof we ran 137 | - all of our input/output buffers 138 | - our IAT table 139 | */ 140 | // this.beacon_helper.base_addr, t 141 | this.beacon_helper.Clear(); 142 | this.bof.Clear(); 143 | this.iat.Clear(); 144 | 145 | } 146 | } 147 | 148 | class BofRunnerOutput 149 | { 150 | internal string Output; 151 | internal int ExitCode; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /RunOF/RunOF/Internals/Coff.cs: -------------------------------------------------------------------------------- 1 | using RunOF.Internals; 2 | using System; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace RunOF.Internals 9 | { 10 | class Coff 11 | { 12 | private IMAGE_FILE_HEADER file_header; 13 | private List section_headers; 14 | private List symbols; 15 | private long string_table; 16 | internal IntPtr base_addr; 17 | internal int size; 18 | private MemoryStream stream; 19 | private BinaryReader reader; 20 | private ARCH MyArch; 21 | private ARCH BofArch; 22 | private string ImportPrefix; 23 | private string HelperPrefix; 24 | private string EntryWrapperSymbol = "go_wrapper"; 25 | private string EntrySymbol = "go"; 26 | private List permissions = new List(); 27 | //private IntPtr iat; 28 | private IAT iat; 29 | public IntPtr global_buffer { get; private set; } 30 | public IntPtr global_buffer_size_ptr {get; private set;} 31 | public int global_buffer_size { get; set; } = 1024; 32 | public IntPtr argument_buffer { get; private set; } 33 | public int argument_buffer_size { get; set; } 34 | private string InternalDLLName { get; set; } = "RunOF"; 35 | 36 | private enum ARCH: int 37 | { 38 | I386 = 0, 39 | AMD64 = 1 40 | } 41 | 42 | public Coff(byte[] file_contents, IAT iat) 43 | { 44 | try 45 | { 46 | Logger.Debug($"--- Loading object file from byte array ---"); 47 | 48 | if (iat != null) 49 | { 50 | this.iat = iat; 51 | } 52 | else 53 | { 54 | this.iat = new IAT(); 55 | } 56 | 57 | this.MyArch = Environment.Is64BitProcess ? ARCH.AMD64 : ARCH.I386; 58 | 59 | // do some field setup 60 | this.stream = new MemoryStream(file_contents); 61 | this.reader = new BinaryReader(this.stream); 62 | 63 | this.section_headers = new List(); 64 | this.symbols = new List(); 65 | 66 | // Allocate some memory, for now just the whole size of the object file. 67 | // TODO - could just do the memory for the sections and not the header? 68 | // TODO - memory permissions 69 | 70 | 71 | // copy across 72 | //Marshal.Copy(file_contents, 0, base_addr, file_contents.Length); 73 | 74 | // setup some objects to help us understand the file 75 | this.file_header = Deserialize(file_contents); 76 | 77 | // check the architecture 78 | Logger.Debug($"Got file header. Architecture {this.file_header.Machine}"); 79 | 80 | if (!ArchitectureCheck()) 81 | { 82 | Logger.Error($"Object file architecture {this.BofArch} does not match process architecture {this.MyArch}"); 83 | throw new NotImplementedException(); 84 | } 85 | 86 | // Compilers use different prefixes to symbols depending on architecture. 87 | // There might be other naming conventions for functions imported in different ways, but I'm not sure. 88 | if (this.BofArch == ARCH.I386) 89 | { 90 | this.ImportPrefix = "__imp__"; 91 | this.HelperPrefix = "_"; // This I think means a global function 92 | } 93 | else if (this.BofArch == ARCH.AMD64) 94 | { 95 | this.ImportPrefix = "__imp_"; 96 | this.HelperPrefix = String.Empty; 97 | } 98 | 99 | if (this.file_header.SizeOfOptionalHeader != 0) 100 | { 101 | Logger.Error($"[x] Bad object file: has an optional header??"); 102 | throw new Exception("Object file had an optional header, not standards-conforming"); 103 | } 104 | 105 | // Setup our section header list. 106 | Logger.Debug($"Parsing {this.file_header.NumberOfSections} section headers"); 107 | FindSections(); 108 | 109 | Logger.Debug($"Parsing {this.file_header.NumberOfSymbols} symbols"); 110 | FindSymbols(); 111 | 112 | // The string table has specified offset, it's just located directly after the last symbol header - so offset is sym_table_offset + (num_symbols * sizeof(symbol)) 113 | Logger.Debug($"Setting string table offset to 0x{(this.file_header.NumberOfSymbols * Marshal.SizeOf(typeof(IMAGE_SYMBOL))) + this.file_header.PointerToSymbolTable:X}"); 114 | this.string_table = (this.file_header.NumberOfSymbols * Marshal.SizeOf(typeof(IMAGE_SYMBOL))) + this.file_header.PointerToSymbolTable; 115 | 116 | // We allocate and copy the file into memory once we've parsed all our section and string information 117 | // This is so we can use the section information to only map the stuff we need 118 | 119 | //size = (uint)file_contents.Length; 120 | 121 | // because we need to page align our sections, the overall size may be larger than the filesize 122 | // calculate our overall size here 123 | int total_pages = 0; 124 | foreach (var section_header in this.section_headers) 125 | { 126 | int section_pages = (int)section_header.SizeOfRawData / Environment.SystemPageSize; 127 | if (section_header.SizeOfRawData % Environment.SystemPageSize != 0) 128 | { 129 | section_pages++; 130 | } 131 | 132 | total_pages = total_pages + section_pages; 133 | } 134 | 135 | Logger.Debug($"We need to allocate {total_pages} pages of memory"); 136 | size = total_pages * Environment.SystemPageSize; 137 | 138 | base_addr = NativeDeclarations.VirtualAlloc(IntPtr.Zero, (uint)(total_pages * Environment.SystemPageSize), NativeDeclarations.MEM_RESERVE, NativeDeclarations.PAGE_EXECUTE_READWRITE); 139 | Logger.Debug($"Mapped image base @ 0x{base_addr.ToInt64():x}"); 140 | int num_pages = 0; 141 | 142 | for (int i =0; i 0) 282 | { 283 | Logger.Debug($"Allocating argument buffer of length {serialised_args.Length}"); 284 | this.argument_buffer = NativeDeclarations.VirtualAlloc(IntPtr.Zero, (uint)serialised_args.Length, NativeDeclarations.MEM_COMMIT, NativeDeclarations.PAGE_READWRITE); 285 | // Copy our data into it 286 | Marshal.Copy(serialised_args, 0, this.argument_buffer, serialised_args.Length); 287 | 288 | var symbol_addr = new IntPtr(this.base_addr.ToInt64() + symbol.Value + this.section_headers[(int)symbol.SectionNumber - 1].PointerToRawData); 289 | Marshal.WriteIntPtr(symbol_addr, this.argument_buffer); 290 | } // TODO - leave dangling if don't have any arguments? A little dangerous, but our code should check the length first.... 291 | argument_buffer_found = true; 292 | 293 | } 294 | else if (symbol_name == this.HelperPrefix + "argument_buffer_length") 295 | { 296 | Logger.Debug($"Setting argument length to {(uint)serialised_args.Length}"); 297 | this.argument_buffer_size = serialised_args.Length; 298 | 299 | var symbol_addr = new IntPtr(this.base_addr.ToInt64() + symbol.Value + this.section_headers[(int)symbol.SectionNumber - 1].PointerToRawData); 300 | // CAUTION - the sizeo of what you write here MUST match the definition in beacon_funcs.h for argument_buffer_len (currently a uint32_t) 301 | 302 | Marshal.WriteInt32(symbol_addr, this.argument_buffer_size); 303 | argument_buffer_length_found = true; 304 | } 305 | else if (symbol_name == this.HelperPrefix+"global_buffer_len") 306 | { 307 | var symbol_addr = new IntPtr(this.base_addr.ToInt64() + symbol.Value + this.section_headers[(int)symbol.SectionNumber - 1].PointerToRawData); 308 | // write the maximum size of the buffer TODO - this shouldn't be hardcoded 309 | //Logger.Debug("Found maxlen"); 310 | //Logger.Debug($"\t[=] Address: {symbol_addr.ToInt64():X}"); 311 | // CAUTION - the sizeo of what you write here MUST match the definition in beacon_funcs.h for global_buffer_maxlen (currently a uint32_t) 312 | Marshal.WriteInt32(symbol_addr, this.global_buffer_size); 313 | this.global_buffer_size_ptr = symbol_addr; 314 | global_buffer_len_found = true; 315 | 316 | } 317 | else if (symbol_name == this.HelperPrefix+this.EntryWrapperSymbol) 318 | { 319 | entry_addr = new IntPtr(this.base_addr.ToInt64() + symbol.Value + this.section_headers[(int)symbol.SectionNumber - 1].PointerToRawData); 320 | Logger.Debug($"Resolved entry address ({this.HelperPrefix + this.EntryWrapperSymbol}) to {entry_addr.ToInt64():X}"); 321 | } 322 | else if (symbol_name == this.HelperPrefix + "global_debug_flag") { 323 | var symbol_addr = new IntPtr(this.base_addr.ToInt64() + symbol.Value + this.section_headers[(int)symbol.SectionNumber - 1].PointerToRawData); 324 | 325 | 326 | if (debug) 327 | { 328 | Marshal.WriteInt32(symbol_addr, 1); 329 | } else 330 | { 331 | Marshal.WriteInt32(symbol_addr, 0); 332 | } 333 | } 334 | 335 | } 336 | if (!global_buffer_found || !global_buffer_len_found || !argument_buffer_found || !argument_buffer_length_found) throw new Exception($"Unable to find a required symbol in your helper object: global_buffer: {global_buffer_found} \nglobal_buffer_len: {global_buffer_len_found} \nargument_buffer: {argument_buffer_found} \nargument_buffer_length: {argument_buffer_length_found}"); 337 | if (entry_addr == IntPtr.Zero) throw new Exception($"Unable to find entry point {this.HelperPrefix+this.EntryWrapperSymbol}"); 338 | return entry_addr; 339 | } 340 | 341 | public void StitchEntry(string Entry) 342 | { 343 | IntPtr entry = new IntPtr(); 344 | Logger.Debug($"Finding our entry point ({Entry}() function)"); 345 | 346 | foreach (var symbol in symbols) 347 | { 348 | 349 | // find the __go symbol address that represents our entry point 350 | if (GetSymbolName(symbol).Equals(this.HelperPrefix + Entry)) 351 | { 352 | Logger.Debug($"\tFound our entry symbol {this.HelperPrefix + Entry}"); 353 | // calculate the address 354 | // the formula is our base_address + symbol value + section_offset 355 | int i = this.symbols.IndexOf(symbol); 356 | entry = (IntPtr)(this.base_addr.ToInt64() + symbol.Value + this.section_headers[(int)symbols[i].SectionNumber - 1].PointerToRawData); // TODO not sure about this cast 357 | Logger.Debug($"\tFound address {entry.ToInt64():x}"); 358 | 359 | // now need to update our IAT with this address 360 | this.iat.Update(this.InternalDLLName, Entry, entry); 361 | 362 | break; 363 | } 364 | 365 | } 366 | 367 | if (entry == IntPtr.Zero) 368 | { 369 | Logger.Error($"Unable to find entry point! Does your bof have a {Entry}() function?"); 370 | throw new Exception("Unable to find entry point"); 371 | } 372 | 373 | 374 | } 375 | 376 | internal void Clear() 377 | { 378 | 379 | // Note the global_buffer must be cleared *before* the COFF as we need to read its location from the COFF's memory 380 | if (this.global_buffer != IntPtr.Zero) 381 | { 382 | 383 | Logger.Debug($"Zeroing and freeing loaded global buffer at 0x{this.global_buffer.ToInt64():X} with size 0x{this.global_buffer_size:X}"); 384 | 385 | // the global_buffer can move around if the BOF reallocs to make it bigger so we need to read its final location from memory 386 | var output_addr = Marshal.ReadIntPtr(this.global_buffer); 387 | var output_size = Marshal.ReadInt32(this.global_buffer_size_ptr); 388 | 389 | NativeDeclarations.ZeroMemory(output_addr, output_size); 390 | var heap_handle = NativeDeclarations.GetProcessHeap(); 391 | 392 | NativeDeclarations.HeapFree(heap_handle, 0, output_addr); 393 | } 394 | 395 | if (this.argument_buffer != IntPtr.Zero) 396 | { 397 | Logger.Debug($"Zeroing and freeing arg buffer at 0x{this.argument_buffer.ToInt64():X} with size 0x{this.argument_buffer_size:X}"); 398 | 399 | NativeDeclarations.ZeroMemory(this.argument_buffer, this.argument_buffer_size); 400 | NativeDeclarations.VirtualFree(this.argument_buffer, 0, NativeDeclarations.MEM_RELEASE); 401 | } 402 | 403 | Logger.Debug($"Zeroing and freeing loaded COFF image at 0x{this.base_addr:X} with size 0x{this.size:X}"); 404 | 405 | // Make sure mem is writeable 406 | foreach (var perm in this.permissions) 407 | { 408 | NativeDeclarations.VirtualProtect(perm.Addr, (UIntPtr)(perm.Size), NativeDeclarations.PAGE_READWRITE, out _); 409 | 410 | } 411 | // zero out memory 412 | NativeDeclarations.ZeroMemory(this.base_addr, (int)this.size); 413 | NativeDeclarations.VirtualFree(this.base_addr, 0, NativeDeclarations.MEM_RELEASE); 414 | 415 | 416 | } 417 | 418 | 419 | private bool ArchitectureCheck() 420 | { 421 | this.BofArch = this.file_header.Machine == IMAGE_FILE_MACHINE.IMAGE_FILE_MACHINE_AMD64 ? ARCH.AMD64 : ARCH.I386; 422 | 423 | if (this.BofArch == this.MyArch) return true; 424 | return false; 425 | 426 | } 427 | 428 | private void FindSections() 429 | { 430 | this.stream.Seek(Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)), SeekOrigin.Begin); // the first section header is located directly after the IMAGE_FILE_HEADER 431 | for (int i=0; i < this.file_header.NumberOfSections; i++) 432 | { 433 | this.section_headers.Add(Deserialize(reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER))))); 434 | } 435 | 436 | // TODO - initialise BSS section as zero. For now, not a problem as Cobalt doesn't do this so you're told to init anything to use; 437 | } 438 | 439 | private void FindSymbols() 440 | { 441 | this.stream.Seek(this.file_header.PointerToSymbolTable, SeekOrigin.Begin); 442 | 443 | for (int i = 0; i < this.file_header.NumberOfSymbols; i++) 444 | { 445 | this.symbols.Add(Deserialize(reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_SYMBOL))))); 446 | } 447 | Logger.Debug($"Created list of {this.symbols.Count} symbols"); 448 | 449 | } 450 | 451 | 452 | private void ResolveRelocs(IMAGE_SECTION_HEADER section_header) 453 | { 454 | if (section_header.NumberOfRelocations > 0) 455 | { 456 | Logger.Debug($"Processing {section_header.NumberOfRelocations} relocations for {Encoding.ASCII.GetString(section_header.Name)} section from offset {section_header.PointerToRelocations:X}"); 457 | this.stream.Seek(section_header.PointerToRelocations, SeekOrigin.Begin); 458 | 459 | for (int i = 0; i < section_header.NumberOfRelocations; i++) 460 | { 461 | var struct_bytes = reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_RELOCATION))); 462 | 463 | IMAGE_RELOCATION reloc = Deserialize(struct_bytes); 464 | Logger.Debug($"Got reloc info: {reloc.VirtualAddress:X} - {reloc.SymbolTableIndex:X} - {reloc.Type} - @ { (this.base_addr + (int)section_header.PointerToRawData + (int)reloc.VirtualAddress).ToInt64():X}"); 465 | 466 | if ((int)reloc.SymbolTableIndex > this.symbols.Count || (int)reloc.SymbolTableIndex < 0) 467 | { 468 | throw new Exception($"Unable to parse relocation # {i+1} symbol table index - {reloc.SymbolTableIndex}"); 469 | } 470 | IMAGE_SYMBOL reloc_symbol = this.symbols[(int)reloc.SymbolTableIndex]; 471 | var symbol_name = GetSymbolName(reloc_symbol); 472 | Logger.Debug($"Relocation name: {symbol_name}"); 473 | if (reloc_symbol.SectionNumber == IMAGE_SECTION_NUMBER.IMAGE_SYM_UNDEFINED) 474 | { 475 | 476 | IntPtr func_addr; 477 | 478 | if (symbol_name.StartsWith(this.ImportPrefix + "Beacon") || symbol_name.StartsWith(this.ImportPrefix + "toWideChar")) 479 | { 480 | 481 | Logger.Debug("We need to provide this function"); 482 | // we need to write the address of the IAT entry for the function to this location 483 | 484 | var func_name = symbol_name.Replace(this.ImportPrefix, String.Empty); 485 | func_addr = this.iat.Resolve(this.InternalDLLName, func_name); 486 | 487 | } 488 | else if (symbol_name == this.ImportPrefix + this.EntrySymbol) 489 | { 490 | // this entry is found in out beacon_funcs object, and needs filling in with a ptr to the address of the go function in our actual BOF. 491 | // We don't know this yet (until that is loaded), so we add an entry to the IAT we'll fill in later. 492 | 493 | // in this case, it seems to want the address itself?? 494 | func_addr = this.iat.Add(this.InternalDLLName, this.EntrySymbol, IntPtr.Zero); 495 | 496 | 497 | } 498 | else 499 | { 500 | // This is a win32 api function 501 | 502 | Logger.Debug("Win32API function"); 503 | 504 | string symbol_cleaned = symbol_name.Replace(this.ImportPrefix, ""); 505 | string dll_name; 506 | string func_name; 507 | if (symbol_cleaned.Contains("$")) 508 | { 509 | 510 | string[] symbol_parts = symbol_name.Replace(this.ImportPrefix, "").Split('$'); 511 | 512 | 513 | try 514 | { 515 | dll_name = symbol_parts[0]; 516 | func_name = symbol_parts[1].Split('@')[0]; // some compilers emit the number of bytes in the param list after the fn name 517 | } 518 | catch (Exception e) 519 | { 520 | 521 | throw new Exception($"Unable to parse function name {symbol_name} as DLL$FUNCTION while processing relocations - {e}"); 522 | } 523 | } 524 | else 525 | { 526 | // TODO - some of the CS SA BOFs have no prefix?? Is this what CobalStrike does?kh 527 | dll_name = "KERNEL32"; 528 | func_name = symbol_cleaned.Split('@')[0]; 529 | 530 | } 531 | 532 | func_addr = this.iat.Resolve(dll_name, func_name); 533 | 534 | } 535 | 536 | // write our address to the relocation 537 | IntPtr reloc_location = this.base_addr + (int)section_header.PointerToRawData + (int)reloc.VirtualAddress; 538 | Int64 current_value = Marshal.ReadInt32(reloc_location); 539 | Logger.Debug($"Current value: {current_value:X}"); 540 | 541 | // How we write our relocation depends on the relocation type and architecture 542 | // Note - "in the wild" most of these are not used, which makes it a bit difficult to test. 543 | // For example, in all the BOF files I've seen only four are actually used. 544 | // An exception will be thrown if not supported 545 | // TODO - we should refactor this, but my head is hurting right now. 546 | // TODO - need to check when in 64 bit mode that any 32 bit relocation's don't overflow (will .net do this for free?) 547 | 548 | switch (reloc.Type) 549 | { 550 | #if _I386 551 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_ABSOLUTE: 552 | // The relocation is ignored 553 | break; 554 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_DIR16: 555 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_REL16: 556 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_SEG12: 557 | // The relocation is not supported; 558 | break; 559 | 560 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_DIR32: 561 | // The target's 32-bit VA. 562 | 563 | Marshal.WriteInt32(reloc_location, func_addr.ToInt32()); 564 | break; 565 | 566 | 567 | 568 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_REL32: 569 | // TODO - not seen this "in the wild" 570 | Marshal.WriteInt32(reloc_location, (func_addr.ToInt32()-4) - reloc_location.ToInt32()); 571 | break; 572 | 573 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_DIR32NB: 574 | // The target's 32-bit RVA. 575 | Marshal.WriteInt32(reloc_location, (func_addr.ToInt32() - 4) - reloc_location.ToInt32() - this.base_addr.ToInt32()); 576 | break; 577 | 578 | // These relocations will fall through as unhandled for now 579 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_SECTION: 580 | // The 16-bit section index of the section that contains the target. This is used to support debugging information. 581 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_SECREL: 582 | // The 32-bit offset of the target from the beginning of its section. This is used to support debugging information and static thread local storage. 583 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_TOKEN: 584 | // The CLR token. 585 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_SECREL7: 586 | // A 7-bit offset from the base of the section that contains the target. 587 | 588 | 589 | #elif _AMD64 590 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_REL32: 591 | Marshal.WriteInt32(reloc_location, (int)((func_addr.ToInt64()-4) - (reloc_location.ToInt64()))); // subtract the size of the relocation (relative to the end of the reloc) 592 | break; 593 | 594 | #endif 595 | default: 596 | throw new Exception($"Unable to process function relocation type {reloc.Type} - please file a bug report."); 597 | } 598 | Logger.Debug($"\tWrite relocation to {reloc_location.ToInt64():X}"); 599 | 600 | 601 | } 602 | else 603 | { 604 | Logger.Debug("\tResolving internal reference"); 605 | IntPtr reloc_location = this.base_addr + (int)section_header.PointerToRawData + (int)reloc.VirtualAddress; 606 | Logger.Debug($"reloc_location: 0x{reloc_location.ToInt64():X}, section offset: 0x{section_header.PointerToRawData:X} reloc VA: {reloc.VirtualAddress:X}"); 607 | #if _I386 608 | Int32 current_value = Marshal.ReadInt32(reloc_location); 609 | Int32 object_addr; 610 | #elif _AMD64 611 | Int64 current_value = Marshal.ReadInt64(reloc_location); 612 | Int32 current_value_32 = Marshal.ReadInt32(reloc_location); 613 | Int64 object_addr; 614 | #endif 615 | switch (reloc.Type) 616 | { 617 | #if _I386 618 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_ABSOLUTE: 619 | // The relocation is ignored 620 | break; 621 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_DIR16: 622 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_REL16: 623 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_SEG12: 624 | // The relocation is not supported; 625 | break; 626 | 627 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_DIR32: 628 | // The target's 32-bit VA 629 | Marshal.WriteInt32(reloc_location, current_value + this.base_addr.ToInt32() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData); 630 | break; 631 | 632 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_REL32: 633 | // The target's 32-bit RVA 634 | object_addr = current_value + this.base_addr.ToInt32() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 635 | Marshal.WriteInt32(reloc_location, (object_addr-4) - reloc_location.ToInt32() ); 636 | break; 637 | 638 | 639 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_DIR32NB: 640 | // The target's 32-bit RVA. 641 | object_addr = current_value + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 642 | Marshal.WriteInt32(reloc_location, (object_addr - 4) - reloc_location.ToInt32()); 643 | break; 644 | 645 | // These relocations will fall through as unhandled for now 646 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_SECTION: 647 | // The 16-bit section index of the section that contains the target. This is used to support debugging information. 648 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_SECREL: 649 | // The 32-bit offset of the target from the beginning of its section. This is used to support debugging information and static thread local storage. 650 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_TOKEN: 651 | // The CLR token. 652 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_I386_SECREL7: 653 | // A 7-bit offset from the base of the section that contains the target. 654 | #elif _AMD64 655 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_ABSOLUTE: 656 | // The relocation is ignored 657 | break; 658 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_ADDR64: 659 | // The 64-bit VA of the relocation target. 660 | Marshal.WriteInt64(reloc_location, current_value + this.base_addr.ToInt64() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData); 661 | break; 662 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_ADDR32: 663 | // The 32-bit VA of the relocation target. 664 | // TODO how does this not overflow? 665 | object_addr = current_value_32 + this.base_addr.ToInt64() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 666 | Marshal.WriteInt32(reloc_location, (int)(object_addr)); 667 | break; 668 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_ADDR32NB: 669 | // The 32-bit address without an image base (RVA). 670 | object_addr = current_value_32 + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 671 | Marshal.WriteInt32(reloc_location, (int)(object_addr - reloc_location.ToInt64())); 672 | break; 673 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_REL32: 674 | // The 32-bit relative address from the byte following the relocation. 675 | object_addr = current_value_32 + this.base_addr.ToInt64() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 676 | Marshal.WriteInt32(reloc_location, (int)((object_addr - 4) - (reloc_location.ToInt64()))); // subtract the size of the relocation 677 | break; 678 | //_1 through _5 written from the spec, not seen in the wild to test 679 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_REL32_1: 680 | // The 32-bit address relative to byte distance 1 from the relocation. 681 | object_addr = current_value_32 + this.base_addr.ToInt64() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 682 | Marshal.WriteInt32(reloc_location, (int)((object_addr - 3) - (reloc_location.ToInt64()))); // subtract the size of the relocation 683 | break; 684 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_REL32_2: 685 | // The 32-bit address relative to byte distance 2 from the relocation. 686 | object_addr = current_value_32 + this.base_addr.ToInt64() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 687 | Marshal.WriteInt32(reloc_location, (int)((object_addr - 2) - (reloc_location.ToInt64()))); // subtract the size of the relocation 688 | break; 689 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_REL32_3: 690 | // The 32-bit address relative to byte distance 3 from the relocation. 691 | object_addr = current_value_32 + this.base_addr.ToInt64() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 692 | Marshal.WriteInt32(reloc_location, (int)((object_addr - 1) - (reloc_location.ToInt64()))); // subtract the size of the relocation 693 | break; 694 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_REL32_4: 695 | // The 32-bit address relative to byte distance 4 from the relocation. 696 | object_addr = current_value_32 + this.base_addr.ToInt64() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 697 | Marshal.WriteInt32(reloc_location, (int)((object_addr) - (reloc_location.ToInt64()))); // subtract the size of the relocation 698 | break; 699 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_REL32_5: 700 | // The 32-bit address relative to byte distance 5 from the relocation. 701 | object_addr = current_value_32 + this.base_addr.ToInt64() + (int)this.section_headers[(int)reloc_symbol.SectionNumber - 1].PointerToRawData; 702 | Marshal.WriteInt32(reloc_location, (int)((object_addr + 1) - (reloc_location.ToInt64()))); // subtract the size of the relocation 703 | break; 704 | // These feel like they're unlikely to be used. I've never seen them, and some of them don't make a lot of sense in the context of what we're doing. 705 | // Ghidra/IDA don't implement all of these either 706 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_SECTION: 707 | // The 16-bit section index of the section that contains the target. This is used to support debugging information. 708 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_SECREL: 709 | // The 32-bit offset of the target from the beginning of its section. This is used to support debugging information and static thread local storage. 710 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_SECREL7: 711 | // A 7-bit unsigned offset from the base of the section that contains the target. 712 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_TOKEN: 713 | // CLR tokens. 714 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_SREL32: 715 | // A 32-bit signed span-dependent value emitted into the object. 716 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_PAIR: 717 | // A pair that must immediately follow every span-dependent value. 718 | case IMAGE_RELOCATION_TYPE.IMAGE_REL_AMD64_SSPAN32: 719 | // A 32-bit signed span-dependent value that is applied at link time. 720 | #endif 721 | 722 | default: 723 | throw new Exception($"Unhandled relocation type {reloc.Type} - please file a bug report"); 724 | 725 | } 726 | } 727 | 728 | } 729 | 730 | } 731 | } 732 | 733 | private string GetSymbolName(IMAGE_SYMBOL symbol) 734 | { 735 | if (symbol.Name[0] == 0 && symbol.Name[1] == 0 && symbol.Name[2] == 0 && symbol.Name[3] == 0) 736 | { 737 | // the last four bytes of the Name field contain an offset into the string table. 738 | uint offset = BitConverter.ToUInt32(symbol.Name, 4); 739 | long position = this.stream.Position; 740 | this.stream.Seek(this.string_table + offset, SeekOrigin.Begin); 741 | 742 | // read a C string 743 | List characters = new List(); 744 | byte c; 745 | while ((c = reader.ReadByte()) != '\0') 746 | { 747 | characters.Add(c); 748 | } 749 | 750 | String output = Encoding.ASCII.GetString(characters.ToArray()); 751 | this.stream.Seek(position, SeekOrigin.Begin); 752 | return output; 753 | 754 | } else 755 | { 756 | return Encoding.ASCII.GetString(symbol.Name).Replace("\0", String.Empty); 757 | } 758 | 759 | } 760 | 761 | private static T Deserialize (byte[] array) 762 | where T:struct 763 | { 764 | GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned); 765 | return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); 766 | } 767 | 768 | 769 | } 770 | 771 | class Permissions 772 | { 773 | internal IntPtr Addr; 774 | internal uint Characteristics; 775 | internal int Size; 776 | internal String SectionName; 777 | 778 | public Permissions(IntPtr addr, uint characteristics, int size, String section_name) 779 | { 780 | this.Addr = addr; 781 | this.Characteristics = characteristics; 782 | this.Size = size; 783 | this.SectionName = section_name; 784 | } 785 | } 786 | } 787 | -------------------------------------------------------------------------------- /RunOF/RunOF/Internals/FileDescriptorRedirector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace RunOF.Internals 10 | { 11 | class FileDescriptorPair 12 | { 13 | public IntPtr Read { get; set; } 14 | 15 | public IntPtr Write { get; set; } 16 | } 17 | 18 | class FileDescriptorRedirector 19 | { 20 | private const int STD_INPUT_HANDLE = -10; 21 | private const int STD_OUTPUT_HANDLE = -11; 22 | private const int STD_ERROR_HANDLE = -12; 23 | private const uint BYTES_TO_READ = 1024; 24 | 25 | 26 | private IntPtr oldGetStdHandleOUT; 27 | private IntPtr oldGetStdHandleIN; 28 | private IntPtr oldGetStdHandleERROR; 29 | 30 | private FileDescriptorPair kpStdOutPipes; 31 | private FileDescriptorPair kpStdInPipes; 32 | private Task readTask; 33 | 34 | public bool RedirectFileDescriptors() 35 | { 36 | oldGetStdHandleOUT = GetStdHandleOUT(); 37 | oldGetStdHandleIN = GetStdHandleIN(); 38 | oldGetStdHandleERROR = GetStdHandleERROR(); 39 | 40 | #if DEBUG 41 | Console.WriteLine("[*] Creating STDOut Pipes to redirect to"); 42 | #endif 43 | kpStdOutPipes = CreateFileDescriptorPipes(); 44 | if (kpStdOutPipes == null) 45 | { 46 | return false; 47 | } 48 | 49 | #if DEBUG 50 | Console.WriteLine("[*] Creating STDIn Pipes to redirect to"); 51 | #endif 52 | kpStdInPipes = CreateFileDescriptorPipes(); 53 | if (kpStdInPipes == null) 54 | { 55 | return false; 56 | } 57 | 58 | return RedirectDescriptorsToPipes(kpStdOutPipes.Write, kpStdInPipes.Write, kpStdOutPipes.Write); 59 | } 60 | 61 | public string ReadDescriptorOutput() 62 | { 63 | #if DEBUG 64 | Console.WriteLine("[*] Retrieving the 'subprocess' stdout & stderr"); 65 | #endif 66 | while (!readTask.IsCompleted) 67 | { 68 | #if DEBUG 69 | Console.WriteLine("[*] Waiting for the task reading from pipe to finish..."); 70 | #endif 71 | Thread.Sleep(2000); 72 | } 73 | return readTask.Result; 74 | } 75 | 76 | public void ResetFileDescriptors() 77 | { 78 | #if DEBUG 79 | Console.WriteLine("[*] Reset StdError, StdOut, StdIn"); 80 | #endif 81 | RedirectDescriptorsToPipes(oldGetStdHandleOUT, oldGetStdHandleIN, oldGetStdHandleERROR); 82 | 83 | ClosePipes(); 84 | 85 | } 86 | 87 | private static IntPtr GetStdHandleOUT() 88 | { 89 | return NativeDeclarations.GetStdHandle(STD_OUTPUT_HANDLE); 90 | } 91 | private static IntPtr GetStdHandleERROR() 92 | { 93 | return NativeDeclarations.GetStdHandle(STD_ERROR_HANDLE); 94 | } 95 | 96 | internal void ClosePipes() 97 | { 98 | #if DEBUG 99 | Console.WriteLine("[*] Closing StdOut pipes"); 100 | #endif 101 | CloseDescriptors(kpStdOutPipes); 102 | #if DEBUG 103 | Console.WriteLine("[*] Closing StdIn pipes"); 104 | #endif 105 | CloseDescriptors(kpStdInPipes); 106 | } 107 | 108 | internal void StartReadFromPipe() 109 | { 110 | this.readTask = Task.Factory.StartNew(() => 111 | { 112 | string output = ""; 113 | 114 | byte[] buffer = new byte[BYTES_TO_READ]; 115 | byte[] outBuffer; 116 | 117 | var ok = NativeDeclarations.ReadFile(kpStdOutPipes.Read, buffer, BYTES_TO_READ, out uint bytesRead, IntPtr.Zero); 118 | 119 | if (!ok) 120 | { 121 | Console.WriteLine($"[-] Unable to read from 'subprocess' pipe"); 122 | return ""; 123 | } 124 | #if DEBUG 125 | Console.WriteLine($"[*] Read {bytesRead} bytes from 'subprocess' pipe"); 126 | #endif 127 | if (bytesRead != 0) 128 | { 129 | outBuffer = new byte[bytesRead]; 130 | Array.Copy(buffer, outBuffer, bytesRead); 131 | output += Encoding.Default.GetString(outBuffer); 132 | } 133 | 134 | while (ok) 135 | { 136 | ok = NativeDeclarations.ReadFile(kpStdOutPipes.Read, buffer, BYTES_TO_READ, out bytesRead, IntPtr.Zero); 137 | #if DEBUG 138 | Console.WriteLine($"[*] Read {bytesRead} bytes from 'subprocess' pipe"); 139 | #endif 140 | if (bytesRead != 0) 141 | { 142 | outBuffer = new byte[bytesRead]; 143 | Array.Copy(buffer, outBuffer, bytesRead); 144 | output += Encoding.Default.GetString(outBuffer); 145 | } 146 | } 147 | return output; 148 | }); 149 | } 150 | 151 | private static IntPtr GetStdHandleIN() 152 | { 153 | return NativeDeclarations.GetStdHandle(STD_INPUT_HANDLE); 154 | } 155 | 156 | private static void CloseDescriptors(FileDescriptorPair stdoutDescriptors) 157 | { 158 | // Need to close write before read else it hangs as could still be writing 159 | if (stdoutDescriptors.Write != IntPtr.Zero) 160 | { 161 | NativeDeclarations.CloseHandle(stdoutDescriptors.Write); 162 | #if DEBUG 163 | Console.WriteLine("[+] CloseHandle write"); 164 | #endif 165 | } 166 | 167 | if (stdoutDescriptors.Read != IntPtr.Zero) 168 | { 169 | NativeDeclarations.CloseHandle(stdoutDescriptors.Read); 170 | #if DEBUG 171 | Console.WriteLine("[+] CloseHandle read"); 172 | #endif 173 | } 174 | } 175 | 176 | private static FileDescriptorPair CreateFileDescriptorPipes() 177 | { 178 | NativeDeclarations.SECURITY_ATTRIBUTES lpSecurityAttributes = new NativeDeclarations.SECURITY_ATTRIBUTES(); 179 | lpSecurityAttributes.nLength = Marshal.SizeOf(lpSecurityAttributes); 180 | lpSecurityAttributes.bInheritHandle = 1; 181 | 182 | var outputStdOut = NativeDeclarations.CreatePipe(out IntPtr read, out IntPtr write, ref lpSecurityAttributes, 0); 183 | if (!outputStdOut) 184 | { 185 | #if DEBUG 186 | 187 | Console.WriteLine("[-] Cannot create File Descriptor pipes"); 188 | #endif 189 | return null; 190 | } 191 | #if DEBUG 192 | else 193 | { 194 | Console.WriteLine("[+] Created File Descriptor pipes: "); 195 | Console.WriteLine($"\t[*] Read: 0x{read.ToString("X")}"); 196 | Console.WriteLine($"\t[*] Write: 0x{write.ToString("X")}"); 197 | } 198 | #endif 199 | return new FileDescriptorPair 200 | { 201 | Read = read, 202 | Write = write 203 | }; 204 | } 205 | 206 | private static bool RedirectDescriptorsToPipes(IntPtr hStdOutPipes, IntPtr hStdInPipes, IntPtr hStdErrPipes) 207 | { 208 | bool bStdOut = NativeDeclarations.SetStdHandle(STD_OUTPUT_HANDLE, hStdOutPipes); 209 | if (bStdOut) 210 | { 211 | #if DEBUG 212 | Console.WriteLine($"[+] SetStdHandle STDOUT to 0x{hStdOutPipes.ToInt64():X} "); 213 | } 214 | else 215 | { 216 | Console.WriteLine($"[-] Unable to SetStdHandle STDOUT to 0x{hStdOutPipes.ToInt64():X} "); 217 | return false; 218 | #endif 219 | } 220 | 221 | bool bStdError = NativeDeclarations.SetStdHandle(STD_ERROR_HANDLE, hStdErrPipes); 222 | if (bStdError) 223 | { 224 | #if DEBUG 225 | Console.WriteLine($"[+] SetStdHandle STDERROR to 0x{hStdErrPipes.ToInt64():X}"); 226 | } 227 | else 228 | { 229 | Console.WriteLine($"[-] Unable to SetStdHandle STDERROR to 0x{hStdErrPipes.ToInt64():X} "); 230 | return false; 231 | #endif 232 | } 233 | 234 | bool bStdIn = NativeDeclarations.SetStdHandle(STD_INPUT_HANDLE, hStdInPipes); 235 | if (bStdIn) 236 | { 237 | #if DEBUG 238 | Console.WriteLine($"[+] SetStdHandle STDIN to 0x{hStdInPipes.ToInt64():X} "); 239 | } 240 | else 241 | { 242 | Console.WriteLine($"[-] Unable to SetStdHandle STDIN to 0x{hStdInPipes.ToInt64():X} "); 243 | return false; 244 | #endif 245 | } 246 | return true; 247 | } 248 | 249 | } 250 | } -------------------------------------------------------------------------------- /RunOF/RunOF/Internals/IAT.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace RunOF.Internals 10 | { 11 | class IAT 12 | { 13 | private readonly IntPtr iat_addr; 14 | private int iat_pages; 15 | private int iat_count; 16 | private readonly Dictionary iat_entries; 17 | public IAT() 18 | { 19 | this.iat_pages = 2; 20 | this.iat_addr = NativeDeclarations.VirtualAlloc(IntPtr.Zero, (uint)(this.iat_pages * Environment.SystemPageSize), NativeDeclarations.MEM_COMMIT, NativeDeclarations.PAGE_EXECUTE_READWRITE); 21 | this.iat_count = 0; 22 | this.iat_entries = new Dictionary(); 23 | } 24 | public IntPtr Resolve(string dll_name, string func_name) 25 | { 26 | // do we already have it in our IAT table? It not lookup and add 27 | if (!this.iat_entries.ContainsKey(dll_name + "$" + func_name)) 28 | { 29 | Logger.Debug($"Resolving {func_name} from {dll_name}"); 30 | 31 | IntPtr dll_handle = NativeDeclarations.LoadLibrary(dll_name); 32 | IntPtr func_ptr = NativeDeclarations.GetProcAddress(dll_handle, func_name); 33 | if (func_ptr == null || func_ptr.ToInt64() == 0) 34 | { 35 | throw new Exception($"Unable to resolve {func_name} from {dll_name}"); 36 | } 37 | Logger.Debug($"\tGot function address {func_ptr.ToInt64():X}"); 38 | Add(dll_name, func_name, func_ptr); 39 | } 40 | 41 | return this.iat_entries[dll_name + "$" + func_name]; 42 | 43 | } 44 | 45 | // This can also be called directly for functions where you already know the address (e.g. helper functions) 46 | public IntPtr Add(string dll_name, string func_name, IntPtr func_address) 47 | { 48 | #if _I386 49 | Logger.Debug($"Adding {dll_name+ "$" + func_name} at address {func_address.ToInt64():X} to IAT address {this.iat_addr.ToInt64() + (this.iat_count * 4):X}"); 50 | 51 | if (this.iat_count * 4 > (this.iat_pages * Environment.SystemPageSize)) 52 | { 53 | throw new Exception("Run out of space for IAT entries!"); 54 | } 55 | Marshal.WriteInt32(this.iat_addr + (this.iat_count * 4), func_address.ToInt32()); 56 | this.iat_entries.Add(dll_name + "$" + func_name, this.iat_addr + (this.iat_count * 4)); 57 | this.iat_count++; 58 | 59 | return this.iat_entries[dll_name + "$" + func_name]; 60 | 61 | 62 | #elif _AMD64 63 | Logger.Debug($"Adding {dll_name + "$" + func_name} at address {func_address.ToInt64():X} to IAT address {this.iat_addr.ToInt64() + (this.iat_count * 8):X}"); 64 | 65 | 66 | // check we have space in our IAT table 67 | if (this.iat_count * 8 > (this.iat_pages * Environment.SystemPageSize)) 68 | { 69 | throw new Exception("Run out of space for IAT entries!"); 70 | } 71 | 72 | Marshal.WriteInt64(this.iat_addr + (this.iat_count * 8), func_address.ToInt64()); 73 | this.iat_entries.Add(dll_name + "$" + func_name, this.iat_addr + (this.iat_count * 8)); 74 | this.iat_count++; 75 | return this.iat_entries[dll_name + "$" + func_name]; 76 | 77 | 78 | #endif 79 | 80 | 81 | } 82 | 83 | public void Update(string dll_name, string func_name, IntPtr func_address) 84 | { 85 | if (!this.iat_entries.ContainsKey(dll_name + "$" + func_name)) throw new Exception($"Unable to update IAT entry for {dll_name + "$" + func_name} as don't have an existing entry for it"); 86 | // Write the new address into our IAT memory. 87 | // we don't need to update our internal iat_entries dict as that is just a mapping of name to IAT memory location. 88 | #if _I386 89 | Logger.Debug($"Updating symbol {dll_name + "$" + func_name} @ {this.iat_entries[dll_name + "$" + func_name].ToInt64():X} from {Marshal.ReadInt32(this.iat_entries[dll_name + "$" + func_name]):X} to {func_address.ToInt32():X}"); 90 | 91 | Marshal.WriteInt32(this.iat_entries[dll_name + "$" + func_name], func_address.ToInt32()); 92 | #elif _AMD64 93 | Logger.Debug($"Updating symbol {dll_name + "$" + func_name} from {Marshal.ReadInt64(this.iat_entries[dll_name + "$" + func_name]):X} to {func_address.ToInt64():X}"); 94 | 95 | Marshal.WriteInt64(this.iat_entries[dll_name + "$" + func_name], func_address.ToInt64()); 96 | #endif 97 | } 98 | 99 | internal void Clear() 100 | { 101 | Logger.Debug($"Zeroing and freeing IAT at 0x{this.iat_addr.ToInt64():X} size {this.iat_pages * Environment.SystemPageSize}"); 102 | // zero out memory 103 | NativeDeclarations.ZeroMemory(this.iat_addr, this.iat_pages * Environment.SystemPageSize); 104 | 105 | // free it 106 | NativeDeclarations.VirtualFree(this.iat_addr, 0, NativeDeclarations.MEM_RELEASE); 107 | 108 | 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /RunOF/RunOF/Internals/ImageParts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RunOF 5 | { 6 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 7 | public struct IMAGE_FILE_HEADER 8 | { 9 | public IMAGE_FILE_MACHINE Machine; 10 | public UInt16 NumberOfSections; 11 | public UInt32 TimeDateStamp; 12 | public UInt32 PointerToSymbolTable; 13 | public UInt32 NumberOfSymbols; 14 | public UInt16 SizeOfOptionalHeader; 15 | public UInt16 Characteristics; 16 | } 17 | public enum IMAGE_FILE_MACHINE : ushort 18 | { 19 | IMAGE_FILE_MACHINE_UNKNOWN = 0x0, 20 | IMAGE_FILE_MACHINE_I386 = 0x14c, 21 | IMAGE_FILE_MACHINE_AMD64 = 0x8664, 22 | } 23 | 24 | 25 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 26 | public struct IMAGE_SECTION_HEADER 27 | { 28 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] 29 | public byte[] Name; 30 | public UInt32 PhysicalAddressVirtualSize; 31 | public UInt32 VirtualAddress; 32 | public UInt32 SizeOfRawData; 33 | public UInt32 PointerToRawData; 34 | public UInt32 PointerToRelocations; 35 | public UInt32 PointerToLinenumbers; 36 | public UInt16 NumberOfRelocations; 37 | public UInt16 NumberOfLinenumbers; 38 | public UInt32 Characteristics; 39 | } 40 | 41 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 42 | public struct IMAGE_SYMBOL 43 | { 44 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] 45 | public byte[] Name; 46 | public UInt32 Value; 47 | public IMAGE_SECTION_NUMBER SectionNumber; 48 | public IMAGE_SYMBOL_TYPE Type; 49 | public byte StorageClass; 50 | public byte NumberofAuxSymbols; 51 | } 52 | 53 | 54 | public enum IMAGE_SECTION_NUMBER : short 55 | { 56 | IMAGE_SYM_UNDEFINED = 0, 57 | IMAGE_SYM_ABSOLUTE = -1, 58 | IMAGE_SYM_DEBUG = -2, 59 | } 60 | 61 | public enum IMAGE_SYMBOL_TYPE : ushort 62 | { 63 | IMAGE_SYM_TYPE_NULL = 0x0, 64 | IMAGE_SYM_TYPE_VOID = 0x1, 65 | IMAGE_SYM_TYPE_CHAR = 0x2, 66 | IMAGE_SYM_TYPE_SHORT = 0x3, 67 | IMAGE_SYM_TYPE_INT = 0x4, 68 | IMAGE_SYM_TYPE_LONG = 0x5, 69 | IMAGE_SYM_TYPE_FLOAT = 0x6, 70 | IMAGE_SYM_TYPE_DOUBLE = 0x7, 71 | IMAGE_SYM_TYPE_STRUCT = 0x8, 72 | IMAGE_SYM_TYPE_UNION = 0x9, 73 | IMAGE_SYM_TYPE_ENUM = 0xA, 74 | IMAGE_SYM_TYPE_MOE = 0xB, 75 | IMAGE_SYM_TYPE_BYTE = 0xC, 76 | IMAGE_SYM_TYPE_WORD = 0xD, 77 | IMAGE_SYM_TYPE_UINT = 0xE, 78 | IMAGE_SYM_TYPE_DWORD = 0xF, 79 | IMAGE_SYM_TYPE_FUNC = 0x20, // A special MS extra 80 | } 81 | 82 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 83 | public struct IMAGE_RELOCATION 84 | { 85 | public UInt32 VirtualAddress; 86 | public UInt32 SymbolTableIndex; 87 | public IMAGE_RELOCATION_TYPE Type; // TODO this is architecture dependant 88 | 89 | } 90 | 91 | public enum IMAGE_RELOCATION_TYPE : ushort 92 | { 93 | // Why does Microsoft list these in decimal for I386 and hex for AMD64? 94 | #if _I386 95 | /* I386 relocation types */ 96 | IMAGE_REL_I386_ABSOLUTE = 0, 97 | IMAGE_REL_I386_DIR16 = 1, 98 | IMAGE_REL_I386_REL16 = 2, 99 | IMAGE_REL_I386_DIR32 = 6, 100 | IMAGE_REL_I386_DIR32NB = 7, 101 | IMAGE_REL_I386_SEG12 = 9, 102 | IMAGE_REL_I386_SECTION = 10, 103 | IMAGE_REL_I386_SECREL = 11, 104 | IMAGE_REL_I386_TOKEN = 12, 105 | IMAGE_REL_I386_SECREL7 = 13, 106 | IMAGE_REL_I386_REL32 = 20, 107 | #elif _AMD64 108 | 109 | /* AMD64 relocation types */ 110 | IMAGE_REL_AMD64_ABSOLUTE = 0x0000, 111 | IMAGE_REL_AMD64_ADDR64 = 0x0001, 112 | IMAGE_REL_AMD64_ADDR32 = 0x0002, 113 | IMAGE_REL_AMD64_ADDR32NB = 0x0003, 114 | IMAGE_REL_AMD64_REL32 = 0x0004, 115 | IMAGE_REL_AMD64_REL32_1 = 0x0005, 116 | IMAGE_REL_AMD64_REL32_2 = 0x0006, 117 | IMAGE_REL_AMD64_REL32_3 = 0x0007, 118 | IMAGE_REL_AMD64_REL32_4 = 0x0008, 119 | IMAGE_REL_AMD64_REL32_5 = 0x0009, 120 | IMAGE_REL_AMD64_SECTION = 0x000A, 121 | IMAGE_REL_AMD64_SECREL = 0x000B, 122 | IMAGE_REL_AMD64_SECREL7 = 0x000C, 123 | IMAGE_REL_AMD64_TOKEN = 0x000D, 124 | IMAGE_REL_AMD64_SREL32 = 0x000E, 125 | IMAGE_REL_AMD64_PAIR = 0x000F, 126 | IMAGE_REL_AMD64_SSPAN32 = 0x0010, 127 | #endif 128 | 129 | } 130 | 131 | 132 | } -------------------------------------------------------------------------------- /RunOF/RunOF/Internals/NativeDeclarations.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Runtime.ConstrainedExecution; 4 | using System.Runtime.InteropServices; 5 | using System.Security; 6 | 7 | namespace RunOF.Internals 8 | { 9 | unsafe class NativeDeclarations 10 | { 11 | 12 | 13 | internal const uint MEM_COMMIT = 0x1000; 14 | internal const uint MEM_RESERVE = 0x2000; 15 | internal const uint MEM_RELEASE = 0x00008000; 16 | 17 | 18 | 19 | internal const uint PAGE_EXECUTE_READWRITE = 0x40; 20 | internal const uint PAGE_READWRITE = 0x04; 21 | internal const uint PAGE_EXECUTE_READ = 0x20; 22 | internal const uint PAGE_EXECUTE = 0x10; 23 | internal const uint PAGE_EXECUTE_WRITECOPY = 0x80; 24 | internal const uint PAGE_NOACCESS = 0x01; 25 | internal const uint PAGE_READONLY = 0x02; 26 | internal const uint PAGE_WRITECOPY = 0x08; 27 | 28 | internal const uint IMAGE_SCN_MEM_EXECUTE = 0x20000000; 29 | internal const uint IMAGE_SCN_MEM_READ = 0x40000000; 30 | internal const uint IMAGE_SCN_MEM_WRITE = 0x80000000; 31 | 32 | 33 | [StructLayout(LayoutKind.Sequential)] 34 | public unsafe struct IMAGE_BASE_RELOCATION 35 | { 36 | public uint VirtualAdress; 37 | public uint SizeOfBlock; 38 | } 39 | 40 | [DllImport("kernel32.dll")] 41 | [return: MarshalAs(UnmanagedType.Bool)] 42 | public static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle); 43 | 44 | [DllImport("kernel32.dll", SetLastError = true)] 45 | public static extern IntPtr GetStdHandle(int nStdHandle); 46 | 47 | [StructLayout(LayoutKind.Sequential)] 48 | public struct SECURITY_ATTRIBUTES 49 | { 50 | public int nLength; 51 | public unsafe byte* lpSecurityDescriptor; 52 | public int bInheritHandle; 53 | } 54 | 55 | [DllImport("kernel32.dll", SetLastError = true)] 56 | public static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, 57 | uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); 58 | 59 | [DllImport("kernel32.dll")] 60 | public static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, 61 | ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize); 62 | 63 | [DllImport("ntdll.dll", SetLastError = true)] 64 | public static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, IntPtr processInformation, uint processInformationLength, IntPtr returnLength); 65 | 66 | [DllImport("kernel32")] 67 | public static extern IntPtr VirtualAlloc(IntPtr lpStartAddr, uint size, uint flAllocationType, uint flProtect); 68 | 69 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] 70 | internal static extern bool VirtualFree(IntPtr pAddress, uint size, uint freeType); 71 | [DllImport("kernel32.dll")] 72 | public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem); 73 | 74 | [DllImport("kernel32")] 75 | public static extern IntPtr GetProcessHeap(); 76 | 77 | [DllImport("kernel32")] 78 | public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, uint dwBytes); 79 | 80 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 81 | public static extern IntPtr LoadLibrary(string lpFileName); 82 | 83 | [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] 84 | public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 85 | 86 | [DllImport("kernel32.dll", SetLastError = true)] 87 | public static extern IntPtr GetCurrentProcess(); 88 | 89 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 90 | public static extern IntPtr GetCommandLine(); 91 | 92 | [DllImport("kernel32.dll", SetLastError = true)] 93 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 94 | [SuppressUnmanagedCodeSecurity] 95 | [return: MarshalAs(UnmanagedType.Bool)] 96 | public static extern bool CloseHandle(IntPtr hObject); 97 | 98 | [DllImport("kernel32")] 99 | public static extern IntPtr CreateThread( 100 | 101 | IntPtr lpThreadAttributes, 102 | uint dwStackSize, 103 | IntPtr lpStartAddress, 104 | IntPtr param, 105 | uint dwCreationFlags, 106 | IntPtr lpThreadId 107 | ); 108 | 109 | [DllImport("kernel32.dll")] 110 | public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); 111 | 112 | [DllImport("kernel32.dll")] 113 | public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); 114 | 115 | [DllImport("kernel32.dll")] 116 | public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength); 117 | 118 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 119 | public static extern IntPtr GetModuleHandle(string lpModuleName); 120 | 121 | [DllImport("kernel32")] 122 | public static extern uint WaitForSingleObject( 123 | 124 | IntPtr hHandle, 125 | uint dwMilliseconds 126 | ); 127 | 128 | 129 | 130 | [DllImport("kernel32.dll")] 131 | public static extern bool GetExitCodeThread(IntPtr hThread, out int lpExitcode); 132 | 133 | 134 | [DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)] 135 | public static extern void ZeroMemory(IntPtr dest, int size); 136 | 137 | 138 | 139 | 140 | [StructLayout(LayoutKind.Sequential)] 141 | public struct PROCESS_BASIC_INFORMATION 142 | { 143 | public uint ExitStatus; 144 | public IntPtr PebAddress; 145 | public UIntPtr AffinityMask; 146 | public int BasePriority; 147 | public UIntPtr UniqueProcessId; 148 | public UIntPtr InheritedFromUniqueProcessId; 149 | } 150 | 151 | [StructLayout(LayoutKind.Sequential)] 152 | public struct UNICODE_STRING : IDisposable 153 | { 154 | public ushort Length; 155 | public ushort MaximumLength; 156 | private IntPtr buffer; 157 | 158 | public UNICODE_STRING(string s) 159 | { 160 | Length = (ushort)(s.Length * 2); 161 | MaximumLength = (ushort)(Length + 2); 162 | buffer = Marshal.StringToHGlobalUni(s); 163 | } 164 | 165 | public void Dispose() 166 | { 167 | Marshal.FreeHGlobal(buffer); 168 | buffer = IntPtr.Zero; 169 | } 170 | 171 | public override string ToString() 172 | { 173 | return Marshal.PtrToStringUni(buffer); 174 | } 175 | 176 | } 177 | 178 | public enum AllocationProtectEnum : uint 179 | { 180 | PAGE_EXECUTE = 0x00000010, 181 | PAGE_EXECUTE_READ = 0x00000020, 182 | PAGE_EXECUTE_READWRITE = 0x00000040, 183 | PAGE_EXECUTE_WRITECOPY = 0x00000080, 184 | PAGE_NOACCESS = 0x00000001, 185 | PAGE_READONLY = 0x00000002, 186 | PAGE_READWRITE = 0x00000004, 187 | PAGE_WRITECOPY = 0x00000008, 188 | PAGE_GUARD = 0x00000100, 189 | PAGE_NOCACHE = 0x00000200, 190 | PAGE_WRITECOMBINE = 0x00000400 191 | } 192 | 193 | public enum HeapAllocFlags : uint 194 | { 195 | HEAP_GENERATE_EXCEPTIONS = 0x00000004, 196 | HEAP_NO_SERIALIZE = 0x00000001, 197 | HEAP_ZERO_MEMORY = 0x00000008, 198 | 199 | } 200 | 201 | public enum WaitEventEnum : uint 202 | { 203 | WAIT_ABANDONED = 0x00000080, 204 | WAIT_OBJECT_0 = 00000000, 205 | WAIT_TIMEOUT = 00000102, 206 | WAIT_FAILED = 0xFFFFFFFF, 207 | } 208 | 209 | public enum StateEnum : uint 210 | { 211 | MEM_COMMIT = 0x1000, 212 | MEM_FREE = 0x10000, 213 | MEM_RESERVE = 0x2000 214 | } 215 | 216 | public enum TypeEnum : uint 217 | { 218 | MEM_IMAGE = 0x1000000, 219 | MEM_MAPPED = 0x40000, 220 | MEM_PRIVATE = 0x20000 221 | } 222 | 223 | public struct MEMORY_BASIC_INFORMATION 224 | { 225 | public IntPtr BaseAddress; 226 | public IntPtr AllocationBase; 227 | public AllocationProtectEnum AllocationProtect; 228 | public IntPtr RegionSize; 229 | public StateEnum State; 230 | public AllocationProtectEnum Protect; 231 | public TypeEnum Type; 232 | } 233 | } 234 | 235 | 236 | 237 | } 238 | 239 | -------------------------------------------------------------------------------- /RunOF/RunOF/Internals/ParsedArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace RunOF.Internals 9 | { 10 | 11 | class ParsedArgs 12 | { 13 | internal string filename; 14 | internal byte[] file_bytes; 15 | internal int thread_timeout = 30000; 16 | internal string entry_name = "go"; 17 | private const int ERROR_INVALID_COMMAND_LINE = 0x667; 18 | internal List of_args; 19 | public bool debug = false; 20 | 21 | public ParsedArgs(string[] args) 22 | { 23 | 24 | // Set our log level 25 | if (args.Contains("-v")) 26 | { 27 | Logger.Level = Logger.LogLevels.DEBUG; 28 | this.debug = true; 29 | } 30 | 31 | if (args.Contains("-h")) 32 | { 33 | PrintUsage(); 34 | throw new ArgumentNullException(); 35 | } 36 | 37 | Logger.Debug($"Parsing {args.Length} Arguments: {string.Join(" ", args)}"); 38 | of_args = new List(); 39 | // Mandatory arguments are either file (-f) or base 64 encoded bytes(-b) 40 | if (!args.Contains("-f") && !args.Contains("-a")) 41 | { 42 | PrintUsage(); 43 | throw new ArgumentException("Invalid Command Line"); 44 | } 45 | 46 | if (args.Contains("-f")) 47 | { 48 | try 49 | { 50 | filename = ExtractArg(args, "-f"); 51 | try 52 | { 53 | file_bytes = File.ReadAllBytes(filename); 54 | } catch (Exception e) 55 | { 56 | Logger.Error($"Unable to read file {filename} : {e}"); 57 | throw new ArgumentException($"Unable to open provided filename: {e} "); 58 | } 59 | 60 | } 61 | catch 62 | { 63 | PrintUsage(); 64 | throw new ArgumentException("Unable to extract filename from arguments (use -f "); 65 | } 66 | } else if (args.Contains("-a")) 67 | { 68 | try 69 | { 70 | file_bytes = Convert.FromBase64String(ExtractArg(args, "-a")); 71 | } catch (Exception e) 72 | { 73 | PrintUsage(); 74 | throw new ArgumentException($"Unable to extract binary object file from arguments (use -a \n {e}"); 75 | } 76 | 77 | } 78 | 79 | 80 | // Set our thread timeout (seconds). 81 | // This can be a number, or -1 82 | if (args.Contains("-t")) 83 | { 84 | try 85 | { 86 | int t = int.Parse(ExtractArg(args, "-t")); 87 | if (t>=0) 88 | { 89 | this.thread_timeout = t * 1000; 90 | } else if (t==-1) 91 | { 92 | this.thread_timeout = -1; 93 | } else 94 | { 95 | Logger.Error("Timeout cannot be less than -1, ignoring"); 96 | } 97 | 98 | } catch (Exception e) 99 | { 100 | PrintUsage(); 101 | throw new ArgumentException("Unable to handle timeout argument \n {e}"); 102 | } 103 | } 104 | 105 | if (args.Contains("-e")) 106 | { 107 | try 108 | { 109 | this.entry_name = ExtractArg(args, "-e"); 110 | 111 | } catch(Exception e) 112 | { 113 | PrintUsage(); 114 | throw new ArgumentException($"Unable to handle entry point argument \n {e}"); 115 | } 116 | } 117 | 118 | // Now read in any optional arguments that get provided to the OF. 119 | 120 | foreach (var arg in args) 121 | { 122 | if (arg.StartsWith("-b:")) //binary data, base64 123 | { 124 | try 125 | { 126 | of_args.Add(new OfArg(Convert.FromBase64String(arg.Substring(3)))); 127 | 128 | } catch (Exception e) 129 | { 130 | Logger.Error($"Unable to parse OF argument -b as a base64 array: {e}"); 131 | } 132 | } else if (arg.StartsWith("-i:")) //uint32 133 | { 134 | try 135 | { 136 | of_args.Add(new OfArg(UInt32.Parse(arg.Substring(3)))); 137 | } 138 | catch (Exception e) 139 | { 140 | Logger.Error($"Unable to parse OF argument -i as a uint32: {e}"); 141 | } 142 | 143 | } else if (arg.StartsWith("-s:")) //uint16 144 | { 145 | try 146 | { 147 | of_args.Add(new OfArg(UInt16.Parse(arg.Substring(3)))); 148 | } 149 | catch (Exception e) 150 | { 151 | Logger.Error($"Unable to parse OF argument -s as a uint16: {e}"); 152 | } 153 | } 154 | else if (arg.StartsWith("-z:")) //ASCII string 155 | { 156 | try 157 | { 158 | of_args.Add(new OfArg(arg.Substring(3) + "\0")); // ensure is null-terminated 159 | 160 | } 161 | catch (Exception e) 162 | { 163 | Logger.Error($"Unable to parse OF argument -z as a string: {e}"); 164 | } 165 | } else if (arg.StartsWith("-Z:")) //UTF-16 string 166 | { 167 | try 168 | { 169 | of_args.Add(new OfArg(Encoding.Unicode.GetBytes(arg.Substring(3) + "\0\0"))); // ensure is null-terminated 170 | } 171 | catch (Exception e) 172 | { 173 | Logger.Error($"Unable to parse OF argument -Z as a string: {e}"); 174 | } 175 | 176 | } 177 | 178 | 179 | } 180 | 181 | 182 | } 183 | 184 | public byte[] SerialiseArgs() 185 | { 186 | List output_bytes = new List(); 187 | Logger.Debug($"Serialising {this.of_args.Count} object file arguments "); 188 | // convert our list of arguments into a byte array 189 | foreach (var of_arg in this.of_args) 190 | { 191 | Logger.Debug($"\tSerialising arg of type {of_arg.arg_type} [{(UInt32)of_arg.arg_type}:X]"); 192 | // Add the type 193 | output_bytes.AddRange(BitConverter.GetBytes((UInt32)of_arg.arg_type)); 194 | // Add the length 195 | output_bytes.AddRange(BitConverter.GetBytes((UInt32)of_arg.arg_data.Count())); 196 | // Add the data 197 | output_bytes.AddRange(of_arg.arg_data); 198 | } 199 | return output_bytes.ToArray(); 200 | 201 | } 202 | 203 | private string ExtractArg(string[] args, string key) 204 | { 205 | if (!args.Contains(key)) throw new Exception($"Args array does not contains key {key}"); 206 | if (args.Count() > Array.IndexOf(args, key)) 207 | { 208 | return args[Array.IndexOf(args, key) + 1]; 209 | } 210 | else 211 | { 212 | throw new Exception($"Key {key} does not have a value"); 213 | } 214 | 215 | } 216 | 217 | private void PrintUsage() 218 | { 219 | Console.Write(@" 220 | ____ ____ ______ 221 | / __ \__ ______ / __ \/ ____/ 222 | / /_/ / / / / __ \/ / / / /_ 223 | / _, _/ /_/ / / / / /_/ / __/ 224 | /_/ |_|\__,_/_/ /_/\____/_/ 225 | 226 | 227 | Run object files from c# (inc. Beacon Object Files) 228 | 229 | Usage: 230 | ------ 231 | 232 | -h Show this help text 233 | -v Show very verbose logs. In debug builds will also pause before starting the OF to allow you to attach a debugger 234 | 235 | One of these is required: 236 | -f Path to an object file to load 237 | -a Base64 encoded object file 238 | 239 | Optional arguments: 240 | 241 | -t Set thread timeout (in seconds) - default 30 if not specified 242 | -e Set entry function name - defaults to go 243 | 244 | These are passed to the object file *in the order they are on the command line*. 245 | 246 | -i:123 A 32 bit integer (e.g. 123 passed to object file) 247 | -s:12 A 16 bit integer (e.g. 12 passed to object file) 248 | -z:hello An ASCII string (e.g. hello passed to object file) 249 | -Z:hello A string that's converted to wchar (e.g. (wchar_t)hello passed to object file) 250 | -b:aGVsbG8= A base64 encoded binary blob (decoded binary passed to object file) 251 | 252 | To specify an empty string just leave it blank (e.g. -Z: ) 253 | 254 | "); 255 | 256 | 257 | } 258 | } 259 | 260 | class OfArg 261 | { 262 | 263 | public enum ArgType: UInt32 264 | { 265 | BINARY, 266 | INT32, 267 | INT16, 268 | STR, 269 | WCHR_STR, 270 | 271 | } 272 | 273 | public byte[] arg_data; 274 | 275 | public ArgType arg_type; 276 | public OfArg(UInt32 arg_data) 277 | { 278 | arg_type = ArgType.INT32; 279 | this.arg_data = BitConverter.GetBytes(arg_data); 280 | } 281 | 282 | public OfArg(UInt16 arg_data) 283 | { 284 | arg_type = ArgType.INT16; 285 | this.arg_data = BitConverter.GetBytes(arg_data); 286 | 287 | } 288 | 289 | public OfArg(string arg_data) 290 | { 291 | arg_type = ArgType.BINARY; 292 | this.arg_data = Encoding.ASCII.GetBytes(arg_data+"\0"); 293 | } 294 | 295 | 296 | 297 | public OfArg(byte[] arg_data) 298 | { 299 | arg_type = ArgType.BINARY; 300 | this.arg_data = arg_data; 301 | } 302 | 303 | } 304 | 305 | } 306 | -------------------------------------------------------------------------------- /RunOF/RunOF/Program.cs: -------------------------------------------------------------------------------- 1 | using RunOF.Internals; 2 | using System; 3 | using System.Runtime.CompilerServices; 4 | using System.Diagnostics; 5 | 6 | namespace RunOF 7 | { 8 | class Program 9 | { 10 | private const int ERROR_INVALID_COMMAND_LINE = 0x667; 11 | 12 | static int Main(string[] args) 13 | { 14 | 15 | #if _X86 16 | Logger.Info("Starting RunOF [x86]"); 17 | #elif _AMD64 18 | Logger.Info("Starting RunOF [x64]"); 19 | 20 | #endif 21 | 22 | #if DEBUG 23 | Logger.Level = Logger.LogLevels.DEBUG; 24 | #endif 25 | 26 | ParsedArgs ParsedArgs; 27 | try 28 | { 29 | ParsedArgs = new ParsedArgs(args); 30 | 31 | } catch (ArgumentNullException) 32 | { 33 | return 0; 34 | } catch (Exception e) 35 | { 36 | Logger.Error($"Unable to parse application arguments: \n {e}"); 37 | return -1; 38 | }; 39 | 40 | 41 | Logger.Info($"Loading object file {ParsedArgs.filename}"); 42 | 43 | try 44 | { 45 | BofRunner bof_runner = new BofRunner(ParsedArgs); 46 | // bof_runner.LoadBof(filename); 47 | 48 | bof_runner.LoadBof(); 49 | 50 | Logger.Info($"About to start BOF in new thread at {bof_runner.entry_point.ToInt64():X}"); 51 | // We only want the press enter to start if a debug build and -v flag supplied, as we might want logs from a non-interactive session 52 | #if DEBUG 53 | if (ParsedArgs.debug) 54 | { 55 | 56 | Logger.Debug("Press enter to start it (✂️ attach debugger here...)"); 57 | Console.ReadLine(); 58 | } 59 | #endif 60 | 61 | 62 | var Result = bof_runner.RunBof(30); 63 | 64 | Console.WriteLine("------- BOF OUTPUT ------"); 65 | Console.WriteLine($"{Result.Output}"); 66 | Console.WriteLine("------- BOF OUTPUT FINISHED ------"); 67 | #if DEBUG 68 | if (ParsedArgs.debug) 69 | { 70 | Logger.Debug("Press enter to continue..."); 71 | Console.ReadLine(); 72 | } 73 | #endif 74 | Logger.Info("Thanks for playing!"); 75 | 76 | // Use our thread exit code as our app exit code so we can check for errors easily 77 | return Result.ExitCode; 78 | 79 | 80 | } catch (Exception e) 81 | { 82 | Logger.Error($"Error! {e}"); 83 | return -1; 84 | } 85 | 86 | } 87 | 88 | 89 | } 90 | public static class Logger 91 | { 92 | public enum LogLevels 93 | { 94 | ERROR, 95 | INFO, 96 | DEBUG 97 | } 98 | 99 | public static LogLevels Level { get; set; } = LogLevels.INFO; 100 | 101 | 102 | static Logger() 103 | { 104 | 105 | } 106 | 107 | public static void Debug(string Message, [CallerMemberName] string caller = "") 108 | { 109 | var methodInfo = new StackTrace().GetFrame(1).GetMethod(); 110 | var className = methodInfo.ReflectedType.Name; 111 | if (Level >= LogLevels.DEBUG) Console.WriteLine($"[=] [{className}:{methodInfo}] {Message}"); 112 | } 113 | 114 | public static void Info(string Message) 115 | { 116 | if (Level >= LogLevels.INFO) Console.WriteLine($"[*] {Message}"); 117 | } 118 | 119 | public static void Error(string Message) 120 | { 121 | if (Level >= LogLevels.ERROR) Console.WriteLine($"[!!] {Message}"); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /RunOF/RunOF/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PoshBOF")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PoshBOF")] 13 | [assembly: AssemblyCopyright("Copyright © 2021")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("3a190f78-b02a-489d-b681-12d82730465d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /RunOF/RunOF/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace RunOF.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RunBOF.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /RunOF/RunOF/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /RunOF/RunOF/RunOF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {3A190F78-B02A-489D-B681-12D82730465D} 8 | Exe 9 | RunOF 10 | RunOF 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | publish\ 16 | true 17 | Disk 18 | false 19 | Foreground 20 | 7 21 | Days 22 | false 23 | false 24 | true 25 | 0 26 | 1.0.0.%2a 27 | false 28 | false 29 | true 30 | 31 | 32 | AnyCPU 33 | true 34 | full 35 | false 36 | bin\Debug\ 37 | TRACE;DEBUG;_I386 38 | prompt 39 | 4 40 | true 41 | 42 | 43 | AnyCPU 44 | pdbonly 45 | true 46 | bin\Release\ 47 | TRACE 48 | prompt 49 | 4 50 | true 51 | 52 | 53 | true 54 | bin\x64\Debug\ 55 | TRACE;DEBUG;_AMD64 56 | true 57 | full 58 | x64 59 | 7.3 60 | prompt 61 | MinimumRecommendedRules.ruleset 62 | true 63 | 64 | 65 | bin\x64\Release\ 66 | TRACE;_AMD64 67 | true 68 | true 69 | pdbonly 70 | x64 71 | 7.3 72 | prompt 73 | MinimumRecommendedRules.ruleset 74 | true 75 | 76 | 77 | true 78 | bin\x86\Debug\ 79 | TRACE;DEBUG;_I386 80 | true 81 | full 82 | x86 83 | 7.3 84 | prompt 85 | MinimumRecommendedRules.ruleset 86 | true 87 | 88 | 89 | bin\x86\Release\ 90 | TRACE;_I386 91 | true 92 | true 93 | pdbonly 94 | x86 95 | 7.3 96 | prompt 97 | MinimumRecommendedRules.ruleset 98 | true 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | True 122 | True 123 | Resources.resx 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | ResXFileCodeGenerator 132 | Resources.Designer.cs 133 | 134 | 135 | 136 | 137 | False 138 | Microsoft .NET Framework 4.7.2 %28x86 and x64%29 139 | true 140 | 141 | 142 | False 143 | .NET Framework 3.5 SP1 144 | false 145 | 146 | 147 | 148 | 149 | 150 | 151 | beacon_funcs 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | beacon_funcs 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /beacon_funcs/Makefile: -------------------------------------------------------------------------------- 1 | # Simple makefile to build the beacon function objects (TODO - simplify) 2 | 3 | CC = i686-w64-mingw32-gcc 4 | CC64 = x86_64-w64-mingw32-gcc 5 | .PHONY: all 6 | all: beacon_funcs beacon_funcs_64 7 | 8 | beacon_funcs: beacon_funcs.c 9 | $(CC) -c beacon_funcs.c -o beacon_funcs.x86.o 10 | 11 | beacon_funcs_64: beacon_funcs.c 12 | $(CC64) -c beacon_funcs.c -o beacon_funcs.x64.o 13 | 14 | .PHONY: clean 15 | clean: 16 | rm -rf beacon_funcs.*.o 17 | -------------------------------------------------------------------------------- /beacon_funcs/beacon_funcs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "beacon_funcs.h" 7 | 8 | /* Helper functions */ 9 | 10 | char empty_string[2] = "\x00\x00"; 11 | 12 | // Handle exceptions so we don't crash our calling app. 13 | // This is perhaps a little bit ott 14 | LONG WINAPI VectoredExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo) { 15 | LPTSTR errorText = NULL; 16 | MSVCRT$printf("\n EXCEPTION \n --------- \n "); 17 | HMODULE hNtDll = KERNEL32$LoadLibraryA("NTDLL.DLL"); 18 | if (hNtDll != NULL) { 19 | 20 | DWORD msg_len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, hNtDll, NTDLL$RtlNtStatusToDosError(ExceptionInfo->ExceptionRecord->ExceptionCode), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, NULL); 21 | if (errorText != NULL) { 22 | MSVCRT$printf("Exception while running object file: %s @ %p [0x%X]\n --------- \n\n", errorText, ExceptionInfo->ExceptionRecord->ExceptionAddress, ExceptionInfo->ExceptionRecord->ExceptionCode); 23 | } 24 | 25 | } else { 26 | 27 | MSVCRT$printf("Exception while running object file: \n @ %p [0x%X]\n --------- \n\n", ExceptionInfo->ExceptionRecord->ExceptionAddress, ExceptionInfo->ExceptionRecord->ExceptionCode); 28 | } 29 | //KERNEL32$ExitThread(-1); 30 | //. Changed to terminate thread as was having issues with exit thread trying to free heap allocations, which was fine, except when the exception is a heap corruption.. 31 | KERNEL32$TerminateThread(thread_handle, -1); 32 | } 33 | 34 | 35 | void debugPrintf(char *fmt, ...) { 36 | va_list argp; 37 | 38 | if (global_debug_flag) { 39 | va_start(argp, fmt); 40 | MSVCRT$vprintf(fmt, argp); 41 | va_end(argp); 42 | } 43 | } 44 | 45 | // we have a wrapper around our go function to change our globals into parameters 46 | // because we can't pass args in a usual way to the new thread 47 | // We can also do some housekeeping/setup here 48 | void go_wrapper() { 49 | void *exception_handler = KERNEL32$AddVectoredExceptionHandler(0, VectoredExceptionHandler); 50 | debugPrintf("[*] --- UNMANAGED CODE START --- \n"); 51 | debugPrintf("[*] --- Calling BOF go() function --- \n"); 52 | 53 | //thread_handle = KERNEL32$GetCurrentThread(); 54 | 55 | // setup our output buffer 56 | // global_buffer should have already been allocated by the loader 57 | global_buffer_cursor = global_buffer; 58 | global_buffer_remaining = global_buffer_len; 59 | 60 | go(argument_buffer, argument_buffer_length); 61 | debugPrintf("[*] BOF finished\n"); 62 | // TEST CODE TO CAUSE A CRASH 63 | /* 64 | while (1) { 65 | HANDLE process_heap = KERNEL32$GetProcessHeap(); 66 | MSVCRT$printf("CRASH!\n"); 67 | global_buffer = KERNEL32$HeapReAlloc(process_heap, HEAP_ZERO_MEMORY, global_buffer - 100, global_buffer_len +1000); 68 | 69 | 70 | char *ptr = 0; 71 | 72 | *ptr = 0; 73 | }*/ 74 | debugPrintf("[*] UNMANAGED CODE END\n"); 75 | KERNEL32$RemoveVectoredExceptionHandler(exception_handler); 76 | KERNEL32$ExitThread(0); 77 | 78 | } 79 | 80 | 81 | void ReallocOutputBuffer(size_t increment_size) { 82 | HANDLE process_heap = KERNEL32$GetProcessHeap(); 83 | increment_size = increment_size + 1024; 84 | debugPrintf("[*] Reallocating global output buffer to new size %d\n", global_buffer_len + increment_size); 85 | 86 | size_t cursor_offset = global_buffer_cursor - global_buffer; 87 | 88 | global_buffer = KERNEL32$HeapReAlloc(process_heap, HEAP_ZERO_MEMORY, global_buffer, global_buffer_len + increment_size); 89 | 90 | if (global_buffer == NULL) { 91 | MSVCRT$printf("[!!] Unable to realloc output buffer - exiting BOF\n"); 92 | KERNEL32$ExitThread(-1); 93 | } 94 | 95 | global_buffer_cursor = global_buffer + cursor_offset; 96 | global_buffer_len += increment_size; 97 | global_buffer_remaining += increment_size; 98 | } 99 | 100 | // Output functions 101 | 102 | // Instead of printing to the console, this saves the fomatted string into the global buffer 103 | void BeaconPrintf(int type, char *fmt, ...) { 104 | va_list argp; 105 | va_start(argp, fmt); 106 | 107 | char callback_output[] = "\n[ ] CALLBACK_OUTPUT:\t"; 108 | char callback_output_oem[] = "\n[ ] CALLBACK_OUTPUT_OEM:\t"; 109 | char callback_error[] = "\n[!] CALLBACK_ERROR:\t"; 110 | char callback_output_utf8[] = "\n[ ] CALLBACK_OUTPUT_UTF8:\t"; 111 | char callback_output_unknown[] = "\n[!] UNKNOWN TYPE:\t"; 112 | 113 | char *callback_type_text; 114 | 115 | 116 | switch (type) { 117 | // from beacon.h 118 | case 0x0: 119 | callback_type_text = callback_output; 120 | break; 121 | case 0x1e: 122 | callback_type_text = callback_output_oem; 123 | break; 124 | case 0x0d: 125 | callback_type_text = callback_error; 126 | break; 127 | case 0x20: 128 | callback_type_text = callback_output_utf8; 129 | break; 130 | default: 131 | callback_type_text = callback_output_unknown; 132 | debugPrintf("[!] Unknown callback type %d supplied in BeaconPrintf\n", type); 133 | break; 134 | } 135 | 136 | 137 | // Check length and realloc here 138 | if ((MSVCRT$strlen(callback_type_text) + MSVCRT$vsnprintf(NULL, 0, fmt, argp)) > global_buffer_remaining) { 139 | ReallocOutputBuffer(MSVCRT$strlen(callback_type_text) + MSVCRT$vsnprintf(NULL, 0, fmt, argp)); 140 | } 141 | 142 | 143 | size_t written = 0; 144 | 145 | written = MSVCRT$_snprintf(global_buffer_cursor, global_buffer_remaining, callback_type_text); 146 | if (written <0) { 147 | MSVCRT$printf("[!] Error copying type text in BeaconPrintf\n"); 148 | return; 149 | } 150 | global_buffer_cursor += written; 151 | global_buffer_remaining -= written; 152 | 153 | written = MSVCRT$vsnprintf(global_buffer_cursor, global_buffer_len, fmt, argp); 154 | if (written <0) { 155 | MSVCRT$printf("[!] Error copying message text in BeaconPrintf\n"); 156 | return; 157 | } 158 | global_buffer_cursor += written; 159 | global_buffer_remaining -= written; 160 | 161 | 162 | va_end(argp); 163 | return; 164 | }; 165 | 166 | void BeaconOutput(int type, char *data, int len) { 167 | debugPrintf("in BeaconOutput - received %d bytes\n", len); 168 | //hexdump(data, len); 169 | // check we have space in out output buffer 170 | if (len > global_buffer_remaining) { 171 | ReallocOutputBuffer(len); 172 | } 173 | MSVCRT$memcpy(global_buffer_cursor, data, len); 174 | global_buffer_cursor += len; 175 | global_buffer_remaining -= len; 176 | } 177 | 178 | void hexdump(char * buffer, int len) { 179 | if (global_debug_flag) { 180 | MSVCRT$printf("--\n"); 181 | for (int i =0 ; i< len; i++) { 182 | MSVCRT$printf("%02x ", buffer[i]); 183 | } 184 | MSVCRT$printf("--\n"); 185 | } 186 | } 187 | 188 | // Data API 189 | 190 | void BeaconDataParse (datap * parser, char * buffer, int size) { 191 | debugPrintf("[*] Initialising DataParser...global arg length: %d, local length: %d\n", argument_buffer_length, size); 192 | 193 | // we want to set our parser fields to point to the right stuff... 194 | parser->original = buffer; // The original buffer 195 | parser->buffer = buffer; // current pointer into our buffer 196 | parser->length = size; // remaining length of data 197 | parser->size = size; // total size of the buffer 198 | 199 | hexdump(buffer, size); 200 | hexdump(parser->buffer, size); 201 | 202 | debugPrintf("[*] Finished initialising DataParser\n"); 203 | } 204 | 205 | int BeaconDataLength (datap *parser) { 206 | return parser->length; 207 | } 208 | 209 | char * BeaconDataExtract (datap *parser, int * size) { 210 | 211 | empty_string[0] = '\x00'; // We need to explicitly init this here, as it gets put in the BSS which our loader doesn't set to zero 212 | empty_string[1] = '\x00'; // Set two bytes beacuse some BOF clients might treat this as a wchar 213 | debugPrintf("[*] BeaconDataExtract...%d / %d bytes read\n", parser->size - parser->length, parser->size); 214 | 215 | // check we have enough space left in our buffer - need at least space for the type and the length 216 | if (parser->length > 2 * sizeof(uint32_t)) { 217 | // read a UINT from our current data buffer position to give us the type 218 | uint32_t arg_type = *(uint32_t *)parser->buffer; 219 | if (arg_type == BINARY) { 220 | // we need to increment the buffer pointer only if we're in the right type 221 | parser->buffer = parser->buffer + sizeof(uint32_t); 222 | uint32_t arg_len = *(uint32_t *)parser->buffer; 223 | debugPrintf("[*] Have a binary variable (type %d) of length %d\n", arg_type, arg_len); 224 | // check have enough space left in our buffer 225 | if (parser->length - 2*sizeof(uint32_t) >= arg_len) { 226 | // we have a choice here, we can either return a pointer to the data in the buffer 227 | // or allocate some more memory, and point back at that. 228 | // I'm not too sure what cobalt does, so just returning ptr to buffer! 229 | parser->buffer = parser->buffer + sizeof(uint32_t); 230 | 231 | if (size != NULL) *size = arg_len; 232 | 233 | char *return_ptr = parser->buffer; 234 | hexdump(return_ptr, arg_len); 235 | parser->buffer = parser->buffer + arg_len; 236 | parser->length = parser->length - (arg_len + 2*sizeof(uint32_t)); 237 | debugPrintf("[*] Returning %d byte 'binary' value\n", arg_len); 238 | return return_ptr; 239 | } else { 240 | debugPrintf("[!] Unable to extract binary data - buffer len: %d \n", parser->length); 241 | if (size != NULL) *size = 1; 242 | return empty_string; 243 | } 244 | } else { 245 | debugPrintf("[!] Unable to extract binary data - wrong type: %d \n", arg_type); 246 | if (size != NULL) *size = 1; 247 | return empty_string; 248 | 249 | } 250 | } 251 | 252 | debugPrintf("[!] Unable to extract binary data - length too short: %d\n", parser->length); 253 | if (size != NULL) *size = 1; 254 | return empty_string; 255 | } 256 | 257 | int32_t BeaconDataInt(datap *parser) { 258 | debugPrintf("[*] BeaconDataInt...%d / %d bytes read\n", parser->size - parser->length, parser->size); 259 | 260 | if (parser->length >= 3 * sizeof(uint32_t)) { 261 | 262 | uint32_t arg_type = *(uint32_t *)parser->buffer; 263 | if (arg_type == INT_32) { 264 | // we need to increment the buffer pointer only if we're in the right type 265 | parser->buffer = parser->buffer + sizeof(uint32_t); 266 | // check the length 267 | uint32_t arg_len = *(uint32_t *)parser->buffer; 268 | parser->buffer = parser->buffer + sizeof(uint32_t); 269 | 270 | if (arg_len != sizeof(uint32_t)) { 271 | // TODO - rewind the buffer pointer? things have gone badly wrong anyway and we'll probably crash?? 272 | return 0; 273 | } 274 | 275 | uint32_t arg_data = *(uint32_t *)parser->buffer; 276 | parser->buffer = parser->buffer + sizeof(uint32_t); 277 | parser->length = parser->length - (3 * sizeof(uint32_t)); 278 | debugPrintf("[*] Returning %d\n", arg_data); 279 | return arg_data; 280 | } else { 281 | debugPrintf("[!] Asked for 4-byte integer, but have type %d, returning 0\n", arg_type); 282 | return 0; 283 | } 284 | } 285 | 286 | debugPrintf("[!] Asked for int, but not enough left in our buffer so returning 0\n"); 287 | 288 | return 0; 289 | } 290 | 291 | int16_t BeaconDataShort(datap *parser) { 292 | debugPrintf("[*] BeaconDataShort...%d / %d bytes read\n", parser->size - parser->length, parser->size); 293 | 294 | if (parser->length >= (2*sizeof(uint32_t) + sizeof(uint16_t))) { 295 | uint32_t arg_type = *(uint32_t *)parser->buffer; 296 | if (arg_type == INT_16) { 297 | // we need to increment the buffer pointer only if we're in the right type 298 | parser->buffer = parser->buffer + sizeof(uint32_t); 299 | // check the length 300 | uint32_t arg_len = *(uint32_t *)parser->buffer; 301 | parser->buffer = parser->buffer + sizeof(uint32_t); 302 | 303 | if (arg_len != sizeof(uint16_t)) { 304 | // TODO - rewind the buffer pointer? things have gone badly wrong anyway and we'll probably crash?? 305 | return 0; 306 | } 307 | 308 | uint16_t arg_data = *(uint16_t *)parser->buffer; 309 | parser->buffer = parser->buffer + sizeof(uint16_t); 310 | parser->length = parser->length - (2*sizeof(uint32_t) + sizeof(uint16_t)); 311 | debugPrintf("[*] Returning %d\n", arg_data); 312 | return arg_data; 313 | } else { 314 | debugPrintf("[!] Asked for 2-byte integer, but have type %d, returning 0\n", arg_type); 315 | return 0; 316 | } 317 | } 318 | debugPrintf("[!] Asked for short, but not enough left in our buffer so returning 0\n"); 319 | 320 | return 0; 321 | } 322 | 323 | // Format API 324 | 325 | // internal helper function 326 | void _reallocFormatBuffer(formatp * format, int increment) { 327 | int current_offset = format->size - format->length; 328 | format->original = MSVCRT$realloc(format->original, increment + format->size); 329 | 330 | if (format->original == NULL) { 331 | MSVCRT$puts("[!] Error reallocating format buffer \n"); 332 | return; 333 | } 334 | 335 | format->buffer = format->original + current_offset; 336 | 337 | format->size = format->size + increment; 338 | format->length += increment; 339 | 340 | } 341 | 342 | void BeaconFormatAlloc(formatp * format, int maxsz) { 343 | format->original = MSVCRT$calloc(maxsz, 1); 344 | format->buffer = format->original; 345 | format->length = 0; 346 | format->size = maxsz; 347 | } 348 | 349 | void BeaconFormatReset(formatp * format) { 350 | format->length = 0; 351 | format->buffer = format->original; 352 | } 353 | 354 | void BeaconFormatFree(formatp * format) { 355 | MSVCRT$free(format->original); 356 | // I'm not too sure if should set these or not - but this seems safer 357 | format->buffer = NULL; 358 | format->length = 0; 359 | format->size = 0; 360 | return; 361 | } 362 | 363 | void BeaconFormatAppend(formatp * format, char * text, int len) { 364 | int increment; 365 | if (len >= format->length) { 366 | // realloc to make space 367 | // Our smalled realloc is 1024 368 | if (len - format->length < 1024) { 369 | increment = 1024; 370 | } else { 371 | increment = len + 1024; 372 | } 373 | _reallocFormatBuffer(format, increment); 374 | } 375 | 376 | MSVCRT$memcpy(format->buffer, text, len); 377 | return; 378 | } 379 | 380 | void BeaconFormatPrintf(formatp * format, char * fmt, ... ) { 381 | va_list argp; 382 | size_t required_size = MSVCRT$vsnprintf(NULL, 0, fmt, argp); 383 | if (required_size < format->length) { 384 | _reallocFormatBuffer(format, required_size + 1024); 385 | } 386 | MSVCRT$vsnprintf(format->buffer, format->length, fmt, argp); 387 | return; 388 | } 389 | 390 | char * BeaconFormatToString(formatp *format, int * size) { 391 | // TODO is this really right? or is there some processing 392 | 393 | *size = MSVCRT$strlen(format->original); 394 | 395 | return format->original; 396 | } 397 | 398 | void BeaconFormatInt(formatp *format, int value) { 399 | size_t required_size = MSVCRT$_snprintf(NULL, 0, "%d", value); 400 | if (required_size < format->length) { 401 | _reallocFormatBuffer(format, required_size + 1024); 402 | } 403 | MSVCRT$_snprintf(format->buffer, format->length, "%d", value); 404 | return; 405 | } 406 | 407 | 408 | // Token Functions 409 | // not sure if/how to implement these 410 | BOOL BeaconUseToken(HANDLE token) { 411 | MSVCRT$puts("[!] BeaconUseToken is unimplemented - ignoring request\n"); 412 | return FALSE; 413 | } 414 | 415 | void BeaconRevertToken() { 416 | MSVCRT$puts("[!] BeaconRevertToken is unimplemented - ignoring request\n"); 417 | return; 418 | } 419 | 420 | BOOL BeaconIsAdmin() { 421 | MSVCRT$puts("[!] BeaconIsAdmin is unimplemented - ignoring request\n"); 422 | return FALSE; 423 | } 424 | 425 | // Utility Functions 426 | BOOL toWideChar(char *src, wchar_t *dst, int max) { 427 | if (src == NULL || dst == NULL) return FALSE; 428 | // max is given *in bytes*, so divide by two to get max for MBTWC 429 | KERNEL32$MultiByteToWideChar(CP_ACP, 0, src, -1, dst, max / 2); 430 | 431 | } 432 | -------------------------------------------------------------------------------- /beacon_funcs/beacon_funcs.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | WINBASEAPI int __cdecl MSVCRT$puts(const char * str); 5 | WINBASEAPI int __cdecl MSVCRT$vprintf(const char * __restrict__ format,va_list arg); 6 | WINBASEAPI int __cdecl MSVCRT$printf(const char * __restrict__ format, ...); 7 | WINBASEAPI int __cdecl MSVCRT$memcpy(void * __restrict__ _Dst,const void * __restrict__ _Src,size_t _MaxCount); 8 | WINBASEAPI int __cdecl MSVCRT$vsnprintf(char * __restrict__ d,size_t n,const char * __restrict__ format,va_list arg); 9 | WINBASEAPI int __cdecl MSVCRT$_snprintf(char * __restrict__ d,size_t n,const char * __restrict__ format,...); 10 | WINBASEAPI size_t __cdecl MSVCRT$strlen(const char * __restrict__ str); 11 | WINBASEAPI void *__cdecl MSVCRT$calloc(size_t _NumOfElements, size_t _SizeOfElements); 12 | WINBASEAPI void *__cdecl MSVCRT$realloc(void *memblock, size_t size); 13 | WINBASEAPI void *__cdecl MSVCRT$free(void *memblock); 14 | 15 | WINBASEAPI void * WINAPI KERNEL32$VirtualAlloc (LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); 16 | WINBASEAPI void * WINAPI KERNEL32$AddVectoredExceptionHandler (ULONG First, PVECTORED_EXCEPTION_HANDLER Handler); 17 | WINBASEAPI ULONG WINAPI KERNEL32$RemoveVectoredExceptionHandler (void * Handler); 18 | WINBASEAPI HANDLE WINAPI KERNEL32$GetProcessHeap (); 19 | WINBASEAPI HANDLE WINAPI KERNEL32$HeapReAlloc (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, size_t dwBytes); 20 | WINBASEAPI HANDLE WINAPI KERNEL32$GetCurrentThread(); 21 | WINBASEAPI VOID WINAPI KERNEL32$TerminateThread(HANDLE hThread, DWORD dwExitCode); 22 | WINBASEAPI void WINAPI KERNEL32$ExitThread(DWORD dwExitCode); 23 | WINBASEAPI HANDLE WINAPI KERNEL32$LoadLibraryA(LPCSTR lpLibFileName); 24 | WINBASEAPI int WINAPI KERNEL32$MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCCH lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar); 25 | WINBASEAPI ULONG WINAPI NTDLL$RtlNtStatusToDosError(NTSTATUS Status); 26 | 27 | char *global_buffer; 28 | char *global_buffer_cursor; 29 | uint32_t global_buffer_len; 30 | uint32_t global_buffer_remaining; 31 | 32 | char *argument_buffer; 33 | uint32_t argument_buffer_length; 34 | 35 | uint32_t global_debug_flag; 36 | 37 | enum arg_types { 38 | BINARY, 39 | INT_32, 40 | INT_16, 41 | STR, 42 | WCHR_STR 43 | }; 44 | 45 | HANDLE thread_handle; 46 | // declare this as an import, so that our loader can fill in its address 47 | DECLSPEC_IMPORT void go(IN PCHAR Buffer, IN ULONG Length); 48 | void hexdump(char *buffer, int len); 49 | 50 | /* 51 | * Beacon Object Files (BOF) 52 | * ------------------------- 53 | * A Beacon Object File is a light-weight post exploitation tool that runs 54 | * with Beacon's inline-execute command. 55 | * 56 | * Cobalt Strike 4.1. 57 | */ 58 | 59 | /* data API */ 60 | typedef struct { 61 | char * original; /* the original buffer [so we can free it] */ 62 | char * buffer; /* current pointer into our buffer */ 63 | int length; /* remaining length of data */ 64 | int size; /* total size of this buffer */ 65 | } datap; 66 | 67 | extern void BeaconDataParse(datap * parser, char * buffer, int size); 68 | extern int BeaconDataInt(datap * parser); 69 | extern short BeaconDataShort(datap * parser); 70 | extern int BeaconDataLength(datap * parser); 71 | extern char * BeaconDataExtract(datap * parser, int * size); 72 | 73 | /* format API */ 74 | typedef struct { 75 | char * original; /* the original buffer [so we can free it] */ 76 | char * buffer; /* current pointer into our buffer */ 77 | int length; /* remaining length of data */ 78 | int size; /* total size of this buffer */ 79 | } formatp; 80 | 81 | extern void BeaconFormatAlloc(formatp * format, int maxsz); 82 | extern void BeaconFormatReset(formatp * format); 83 | extern void BeaconFormatFree(formatp * format); 84 | extern void BeaconFormatAppend(formatp * format, char * text, int len); 85 | extern void BeaconFormatPrintf(formatp * format, char * fmt, ...); 86 | extern char * BeaconFormatToString(formatp * format, int * size); 87 | extern void BeaconFormatInt(formatp * format, int value); 88 | 89 | /* Output Functions */ 90 | #define CALLBACK_OUTPUT 0x0 91 | #define CALLBACK_OUTPUT_OEM 0x1e 92 | #define CALLBACK_ERROR 0x0d 93 | #define CALLBACK_OUTPUT_UTF8 0x20 94 | 95 | extern void BeaconPrintf(int type, char * fmt, ...); 96 | extern void BeaconOutput(int type, char * data, int len); 97 | 98 | /* Token Functions */ 99 | extern BOOL BeaconUseToken(HANDLE token); 100 | void BeaconRevertToken(); 101 | BOOL BeaconIsAdmin(); 102 | 103 | /* Spawn+Inject Functions */ 104 | DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length); 105 | DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len); 106 | DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len); 107 | DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo); 108 | 109 | /* Utility Functions */ 110 | extern BOOL toWideChar(char * src, wchar_t * dst, int max); 111 | 112 | -------------------------------------------------------------------------------- /demo_files/Makefile: -------------------------------------------------------------------------------- 1 | CC = i686-w64-mingw32-gcc 2 | CC64 = x86_64-w64-mingw32-gcc 3 | 4 | .PHONY: all 5 | all: demo_bof demo_bof_64 6 | 7 | demo_bof: demo_bof.c 8 | $(CC) -c demo_bof.c -o demo_bof.x86.o 9 | 10 | demo_bof_64: demo_bof.c 11 | $(CC64) -c demo_bof.c -o demo_bof.x64.o 12 | 13 | .PHONY: clean 14 | clean: 15 | rm -rf demo_bof.*.o 16 | -------------------------------------------------------------------------------- /demo_files/beacon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Beacon Object Files (BOF) 3 | * ------------------------- 4 | * A Beacon Object File is a light-weight post exploitation tool that runs 5 | * with Beacon's inline-execute command. 6 | * 7 | * Cobalt Strike 4.1. 8 | */ 9 | 10 | /* data API */ 11 | typedef struct { 12 | char * original; /* the original buffer [so we can free it] */ 13 | char * buffer; /* current pointer into our buffer */ 14 | int length; /* remaining length of data */ 15 | int size; /* total size of this buffer */ 16 | } datap; 17 | 18 | DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size); 19 | DECLSPEC_IMPORT int BeaconDataInt(datap * parser); 20 | DECLSPEC_IMPORT short BeaconDataShort(datap * parser); 21 | DECLSPEC_IMPORT int BeaconDataLength(datap * parser); 22 | DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size); 23 | 24 | /* format API */ 25 | typedef struct { 26 | char * original; /* the original buffer [so we can free it] */ 27 | char * buffer; /* current pointer into our buffer */ 28 | int length; /* remaining length of data */ 29 | int size; /* total size of this buffer */ 30 | } formatp; 31 | 32 | DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz); 33 | DECLSPEC_IMPORT void BeaconFormatReset(formatp * format); 34 | DECLSPEC_IMPORT void BeaconFormatFree(formatp * format); 35 | DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, char * text, int len); 36 | DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, char * fmt, ...); 37 | DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size); 38 | DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value); 39 | 40 | /* Output Functions */ 41 | #define CALLBACK_OUTPUT 0x0 42 | #define CALLBACK_OUTPUT_OEM 0x1e 43 | #define CALLBACK_ERROR 0x0d 44 | #define CALLBACK_OUTPUT_UTF8 0x20 45 | 46 | DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...); 47 | DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len); 48 | 49 | /* Token Functions */ 50 | DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token); 51 | DECLSPEC_IMPORT void BeaconRevertToken(); 52 | DECLSPEC_IMPORT BOOL BeaconIsAdmin(); 53 | 54 | /* Spawn+Inject Functions */ 55 | DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length); 56 | DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len); 57 | DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len); 58 | DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo); 59 | 60 | /* Utility Functions */ 61 | DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max); 62 | -------------------------------------------------------------------------------- /demo_files/demo_bof.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "beacon.h" 3 | 4 | void go(char * args, int alen) { 5 | DWORD nSize = MAX_COMPUTERNAME_LENGTH + 1; 6 | char buffer[MAX_COMPUTERNAME_LENGTH+1]; 7 | 8 | BeaconPrintf(CALLBACK_OUTPUT, "Hello World"); 9 | 10 | BOOL res = GetComputerNameA(buffer, &nSize); 11 | 12 | if (!res) { 13 | BeaconPrintf(CALLBACK_ERROR, "Unable to run GetComputerNameA"); 14 | return; 15 | } 16 | 17 | BeaconPrintf(CALLBACK_OUTPUT, "Hostname: %s", buffer); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /tests/test-bof.ps1: -------------------------------------------------------------------------------- 1 | # Script to test RunOF 2 | 3 | # This is designed to be used with the CS-SA bof files 4 | 5 | function Test-BOF { 6 | Param 7 | ( 8 | [Parameter(Mandatory=$true, Position=0)] 9 | [string] $BofName, 10 | [Parameter(Mandatory=$false, Position=1)] 11 | [array] $Params 12 | ) 13 | 14 | "Running BOF $BofName..." 15 | 16 | $x86Exe = 'Z:\documents\RT\runof\RunOF\RunOF\bin\x86\Release\RunBOF.exe' 17 | $x64Exe = 'Z:\documents\RT\runof\RunOF\RunOF\bin\x64\Release\RunBOF.exe' 18 | 19 | $BofBasePath = "Z:\tools\CS-Situational-Awareness-BOF\SA\" 20 | 21 | 22 | & "$x86Exe" "-f" $BofBasePath$BofName'\'$BofName'.x86.o' $Params >> log.txt 23 | 24 | if (-not $?) 25 | { 26 | "`tx86: Error..." 27 | } else { 28 | "`tx86: OK" 29 | } 30 | 31 | & "$x64Exe" "-f" $BofBasePath$BofName'\'$BofName'.x64.o' $Params >> log.txt 32 | 33 | if (-not $?) 34 | { 35 | "`tx64: Error..." 36 | } else { 37 | "`tx64: OK" 38 | } 39 | 40 | } 41 | 42 | 43 | Test-Bof "cacls" "-Z:C:\\Windows\\system32\\notepad.exe" 44 | Test-Bof "dir" "-Z:C:\\Windows\\system32\\notepad.exe" 45 | Test-Bof "driversigs" 46 | Test-Bof "env" 47 | Test-Bof "ipconfig" 48 | Test-Bof "ldapsearch" "-Z:*" # don't have a ldap server... 49 | Test-Bof "listdns" 50 | Test-Bof "listmods" 51 | Test-Bof "netstat" 52 | Test-Bof "netview" "-t 30" # This one's a bit slow... 53 | Test-Bof "routeprint" 54 | Test-Bof "nslookup" @("-z:google.com", "-s:1", "-t", "20") 55 | Test-Bof "sc_enum" 56 | Test-Bof "sc_query" 57 | Test-Bof "whoami" 58 | Test-Bof "windowlist" 59 | Test-Bof "resources" 60 | Test-Bof "nslookup" @("-z:nettitude.com", "-s:1", "-t", "20") 61 | Test-Bof "uptime" 62 | Test-Bof "sc_qc" @("-z:", "-z:BthAvctpSvc") 63 | Test-Bof "netuser" @("-Z:jdsnape") 64 | Test-Bof "sc_qdescription" @("-z:", "-z:BluetoothUserService_74eb1e") 65 | Test-Bof "sc_qfailure" @("-z:", "-z:BluetoothUserService_74eb1e") 66 | Test-Bof "sc_qtriggerinfo" @("-z:", "-z:BluetoothUserService_74eb1e") 67 | Test-Bof "sc_query" @("-z:", "-z:BluetoothUserService_74eb1e") 68 | # NOTE - 32 bit build running on 64 bit windows will only have access to the 32-bit registry (inside Wow6432Node) 69 | Test-Bof "reg_query" @("-i:2", "-z:SOFTWARE\\AiPersist") 70 | Test-Bof "schtasksenum" 71 | Test-Bof "schtasksquery" @("-Z:", "-Z:\Microsoft\Windows\termsrv\RemoteFX\RemoteFXvGPUDisableTask") 72 | 73 | 74 | 75 | 76 | # NOT WORKING 77 | 78 | 79 | # I think the netgroup ones come from the cna file TODO !!!! 80 | #Test-Bof "netgrouplist" 81 | #Test-Bof "netsession" 82 | 83 | # WMI errors 84 | Test-Bof "tasklist" 85 | 86 | #Test-Bof "wmi_query" --------------------------------------------------------------------------------