MCPcopy Index your code
hub / github.com/Octoberfest7/Inline-Execute-PE

github.com/Octoberfest7/Inline-Execute-PE @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
35 symbols 53 edges 5 files 5 documented · 14%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Inline-Execute-PE

DISCLAIMER:

This project is complex and failure to understand how it works and adequately test it can result in you crashing Beacons and losing access!

I highly encourage you to read all of the documentation up until the "Design Considerations and Commentary" section!

Introduction

Inline-Execute-PE is a suite of Beacon Object Files (BOF's) and an accompanying Aggressor script for CobaltStrike that enables Operators to load unmanaged Windows executables into Beacon memory and execute them, retrieving the output and rendering it in the Beacon console.

This enables Operators to use many third party tools (Mimikatz, Dsquery, Sysinternals tools, etc) without needing to drop them to disk, reformat them to position independent code using a tool like Donut, or create a new process to run them.

These executables are mapped into Beacon memory so that they can be ran repeatedly without needing to send them over the network, allocate new memory, and create a new conhost.exe process each time.

Executables loaded into Beacons are accessible and able to be ran by all CobaltStrike Clients connected to the CobaltStrike Team Server.

Inline-Execute-PE was designed around x64 Beacons and x64 Windows C or C++ executables compiled using Mingw or Visual Studio. This project does not support x86 executables or x64 executables written in a different language or compiled using a different compiler.

Setup

Clone the repository and optionally run make in order to recompile the BOF's.

Load Inline-Execute-PE.cna into the CobaltStrike client. Ensure the directory that CobaltStrike is running from is writable by your user; Inline-Execute-PE creates a text file there (petable.txt) in order to ensure availability of the data required by Inline-Execute-PE to function.

Commands

Inline-Execute-PE comprises of 3 target-facing commands which run BOF's, and 3 internal commands that manipulate the project data-structure:

Target-facing:
1. peload 2. perun 3. peunload

Internal data-structure: 1. petable 2. peconfig 3. pebroadcast

peload

peload is the beginning of Inline-Execute-PE. This command is used to load a PE into Beacon memory. It performs the following major actions:

  1. Sends the specified PE over the network to Beacon OR sends the name of the PE to read from disk on the target machine
  2. Creates a structure in Beacon memory to hold various pointers and handles required by Inline-Execute-PE throughout it's lifecycle
  3. Allocates memory in Beacon and writes PE to it with RW protection
  4. XOR encrypts PE in memory using a user-specified key
  5. Allocates another chunk of memory and copies the XOR encrypted PE to it. This is necessary in order to be able to "revert" the PE for subsequent executions
  6. Spawns a conhost.exe child process under Beacon in order to initialize stdin/stdout/stderr
  7. Redirects stdout and stderr to an anonymous pipe so that PE output may be captured

perun

perun is the second step in Inline-Execute-PE. It performs the following major actions:

  1. Sends command line arguments over the network to Beacon
  2. XOR decrypts PE in memory
  3. Fixes the PE's Import Address Table, hooking certain API's related to command line arguments and exiting processes
  4. Changes PE memory protection to RWX
  5. Run's PE in it's own thread
  6. Captures output from PE and returns it to CobaltStrike
  7. Reverts PE memory protection to RW
  8. Overwrites the PE in memory with the XOR'd copy that was made during peload

peunload

peunload is called to remove the PE from Beacon memory when an Operator is done with it or wishes to load a different PE. It performs the following major actions:

  1. Closes handles and file pointers created during peload
  2. Terminates the conhost.exe process created during peload
  3. Zeroes out and then frees both copies of the PE in memory
  4. Tries to unload any DLL's loaded by the PE into the Beacon process (optional)

petable

petable is used to display information regarding all PE's currently loaded into Beacons.

Each CobaltStrike Client has their own petable; Inline-Execute-PE goes to great lengths to ensure the synchronicity of its data between all connected CobaltStrike Clients so that PE's may be used by all Operators. For more on this, see "Design Considerations and Commentary".

image

peconfig

peconfig is used to configure options pertaining to how Inline-Execute-PE functions. The two current options that may be altered are:

  1. Timeout. This dictates how long perun will wait for the PE to complete execution before terminating it. This exists as a safeguard in the event that incorrect arguments are given to a PE that cause it to never return/finish execution. This setting is 60 seconds by default but may be modified to accommodate longer-running PE's.
  2. UnloadLibraries. This option controls whether peunload will try to free DLL's from the Beacon process that were loaded by the PE. This is set to TRUE by default. Some PE's cause issues when DLL's are unloaded from the Beacon process and can cause Beacon to crash, in which case it is better to leave all DLL's loaded by the PE in the Beacon process. This has been observed when using powershell.exe (perhaps due to it loading the .Net CLR into the Beacon process).

pebroadcast

pebroadcast can be used to manually broadcast the contents of a Client's petable to all other connected CobaltStrike Clients.

Every other CobaltStrike Client will update their petable with the data broadcasted. This shouldn't ever really be necessary, but the feature exists just in case.

Usage

Use peload to load a PE into Beacon memory
image

Alternatively, if there is a PE on the target machine you would like to use without creating a new process, provide the path and the --local switch
image

Call perun, passing any arguments to the loaded PE
image

Double quotes in arguments must be escaped using backslashes
image

If you have identified that a PE causes issues when trying to free DLL's during unload, use peconfig to set unloadlibraries to false
image

Once you are done using a PE, call peunload to clean it up from Beacon
image

A different PE now may be loaded into the Beacon
image

perun timeout

You must be careful about the command line arguments you pass to the PE; some PE's will crash outright if given wrong arguments, while others will run endlessly causing Beacon to never call back even though the process is still running.

This can be seen with Mimikatz.exe when 'exit' isn't specified at the end of the list of arguments
image

...

image

Inline-Execute-PE will terminate the running PE's thread after the specified timeout value has been reached. This enables Beacon to be able to resume normal communications (Beacon does not call back until the perun BOF has completed execution). While normal CobaltStrike commands and other BOF's may still be used in this Beacon, Inline-Execute-PE is now disabled; when a running PE is terminated in this manner it seems to break stdout and stderr in the Beacon process, and PE's loaded subsequently do not function properly.

The PE may (and should) still be unloaded from Beacon memory, however looking at petable will show that this Beacon may no longer have additional PE's loaded into it. image

It is imperative that you test the PE's you wish to run using Inline-Execute-PE, and that you exercise care when giving command line arguments to perun. Some PE's are more forgiving than others.

Tips, Tricks, and Observations

The below are in no particular order some observations made during testing and development regarding certain PE's that users might want to load into Beacon.

  1. Using peunload on Powershell.exe will usually crash Beacon when UnloadLibraries is TRUE; I believe this has to do with Powershell.exe loading the CLR.
  2. Cmd.exe will crash Beacon unless '/c' is used as the first argument. E.g. 'perun /c cd' is ok, 'perun cd' is not.
  3. Mimikatz.exe will crash Beacon if it was loaded, used, unloaded, and then loaded again IF UnloadLibraries was TRUE during the first peunload.
  4. Some PE's are programmed to print their help menu's when the PE exits; these won't be displayed because calls to ExitProcess and exit() and the like are hooked and redirected to ExitThread so that the PE doesn't cause our Beacon process to exit.
  5. Some PE's aren't very good about freeing memory when they are done with it and rely on that memory being freed when the process exits; because the PE is running inside the Beacon process (and thus the process doesn't exit when PE is done), Beacon can tend to bloat as more PE's are loaded and ran inside of it. Observe this during testing using something like Process Explorer and be mindful of it during operations.
  6. Sysinternal's Psexec doesn't seem to work; while it does run, it complains about the handle to the remote machine being invalid. In practice if one were to want to use something like psexec, it would probably be better achieved using CobaltStrike's socks proxy and an attack-box version of psexec anyways.
  7. Spawning a new beacon to use with Inline-Execute-PE probably isn't a bad idea, especially as you are getting a feel for how different PE's interact and function within the framework. Two is one, one is none.
  8. If there is a LOLBIN you want to use without the telemetry of creating a new process, use the --local switch with peload and read it from disk on the target system. This can also be useful to avoid versioning issues.

IOC's and AV/EDR

IOC's associated with Inline-Execute-PE include but are not limited to:

  1. Allocating memory using VirtualAlloc
  2. Changing memory protections on allocated memory between RW and RWX
  3. Creating a child conhost.exe process
  4. Loading DLL's required by the mapped PE
  5. Any actions performed by the actual PE; for instance, Mimikatz touching LSASS

AV/EDR

I did not give this a full-battery test against an EDR during development, partly due to laziness and partly due to lack of availability of a test environment. It was however tested against latest patch Windows Defender (which is in my experience a pretty good AV product).

Mimikatz.exe is probably the most suspicious and well-known PE that comes to mind as a candidate for use with Inline-Execute-PE. I found that Windows Defender ability to detect Mimikatz running using Inline-Execute-PE was contingent on the process that Beacon was running in.

A beacon running in a standalone executable (think beacon.exe with artifact kit so that it is able to execute and run normally past Defender) will be caught when using Mimikatz.exe with Inline-Execute-PE.

A beacon running in a Windows process (injected into Explorer.exe, notepad.exe, etc or DLL sideloaded into a legitimate process) will NOT be caught when using Mimikatz.exe with Inline-Execute-PE.

In regards to EDR's that perform userland hooking, as I said I haven't tested but I have the following general thoughts:

Being that the PE is running inside of the Beacon process, which you have presumably already unhoooked/refreshed NTDLL inside of, I would think you shouldn't have too many problems with the API calls made by the PE being flagged. The same issues regarding what the PE actually does (touches processes, alters reg keys, etc) still apply.

Design Considerations and Commentary

A couple months ago I came across RunPE-In-Memory and had the thought to try my hand at converting it into a BOF for CobaltStrike. The journey that followed was much more complex and took a lot longer than anticipated. This project was particularly challenging because it isn't a standalone tool in it's own right, it is a tool used to run other tools. This requires a great deal of flexibility and effort towards compatibility with a wide range of PE's and all of the different ways those PE's might accomplish the same task (get arguments, terminate, etc).

At the outset, Inline-Execute-PE was envisioned as an all-in-one BOF, responsible for loading, executing, and freeing a PE in a Beacon. About 3 weeks into the project, by which time I had a POC ~75% completed, I found Pezor which was released ~1.5 years ago and already did almost everything I was trying to do; the major difference being that Pezor called Donut under the hood to turn the PE into shellcode, rather than manually mapping the original PE into memory.

This discovery was welcome in one regard and disappointing in another; it was phenomenal to have a mature project from which to draw inspiration and help me over some sticking points in my code, but disheartening in that I had effectively been reinventing the wheel without knowing it. After reading up on Pezor and

Core symbols most depended-on inside this repo

getNtHdrs
called by 2
Inline-Execute-PE/peload.c
peLoader
called by 2
Inline-Execute-PE/peload.c
getPeDir
called by 1
Inline-Execute-PE/peload.c
xorPE
called by 1
Inline-Execute-PE/peload.c
cleanupModules
called by 1
Inline-Execute-PE/peunload.c
KillConhost
called by 1
Inline-Execute-PE/peunload.c
peUnload
called by 1
Inline-Execute-PE/peunload.c
masqueradeCmdline
called by 1
Inline-Execute-PE/perun.c

Shape

Function 28
Class 7

Languages

C89%
C++11%

Modules by API surface

Inline-Execute-PE/perun.c20 symbols
Inline-Execute-PE/peload.c6 symbols
Inline-Execute-PE/peunload.c5 symbols
Inline-Execute-PE/bofdefs.h4 symbols

For agents

$ claude mcp add Inline-Execute-PE \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact