Custom Payloads in go-exploit
March 10, 2026 ยท View on GitHub
Custom payloads (or Bring-Your-Own-Payload (BYOP)) are supported from the go-exploit command line interface and exploit developers should aim to attempt to support the usecase whenever possible as it adds a lot of flexibility for users.
When an exploit adds support for a payload using config.AddPayload depending on the supported payloads, the file based (ELF, SO, .exe, .dll, webshell) types will add a -payload option that will automatically read a file from disk and add it to config.CustomPayload if the user uses the flag.
If the user uses one of the new payload.*Command types then -command flag will be available and config.CustomPayload will contain the value provided by that flag (or it can be accessed directly from the normal flag handling for the string type).
Details for this can be seen in the package documentation:
- Payload API documentation: https://pkg.go.dev/github.com/vulncheck-oss/go-exploit@main/payload
config.AddPayloadAPI documentation: https://pkg.go.dev/github.com/vulncheck-oss/go-exploit@main/config#Config.AddPayload
An example of how to define the code for this, the following adds support for a generic command and 2 payload types of different architectures:
supportedPayload := []payload.Supported{
{
Type: payload.GenericCommand,
Arch: payload.None,
Effects: payload.NoEffects,
Default: true,
},
{
Type: payload.LinuxELF,
Arch: payload.AMD64,
Effects: payload.NoEffects,
},
{
Type: payload.LinuxELF,
Arch: payload.ARM64,
Effects: payload.Effects{
payload.FileCreate: []string{"/var/www/html/pwnt", "/var/www/html/pwnt2"},
},
},
}
conf := config.NewRemoteExploit(
config.ImplementedFeatures{AssetDetection: true, VersionScanning: false, Exploitation: true},
config.CodeExecution, supportedC2,
"", []string{""},
[]string{""}, "CVE-2023-28324", "HTTP", 80)
conf.AddPayload(supportedPayload...)
Now when the exploit is run the following options can be seen:
...
-command string
Command to use for the exploit, an empty string will use the exploit default.
...
-payload string
Path to load custom payload from, an empty string will use the exploit default.
-payload-arch string
Payload architecture to use based on supported archs: none, amd64, arm64 (default "none")
-payload-type string
Payload type to use based on supported types: GenericCommand, LinuxELF, LinuxELF (default "GenericCommand")
...
If a payload is defined with only one architecture -payload-arch will not show up and if only one payload type is defined -payload-type will also disappear. For example:
supportedPayload := []payload.Supported{
{
Type: payload.GenericCommand,
Arch: payload.None,
Effects: payload.NoEffects,
Default: true,
},
}
conf := config.NewRemoteExploit(
config.ImplementedFeatures{AssetDetection: true, VersionScanning: false, Exploitation: true},
config.CodeExecution, supportedC2,
"", []string{""},
[]string{""}, "CVE-2023-28324", "HTTP", 80)
conf.AddPayload(supportedPayload...)
will yield the following flags with none of the others:
...
-command string
Command to use for the exploit, an empty string will use the exploit default.
...
Now the implementer will need to add support to their payload generation in order to handle the cases of custom payload use:
if conf.HasCustomPayload() {
if conf.SelectedPayload.Type.IsCommand() {
output.PrintfStatus("using '%s' in place of default", string(conf.CustomPayload))
} else {
output.PrintfStatus("using binary len %d in place of default", len(string(conf.CustomPayload)))
}
}
Or if there is a complex case where more specificity is required:
switch conf.SelectedPayload.Type {
case payload.GenericCommand:
output.PrintfStatus("adding GenericCommand")
if conf.HasCustomPayload() {
// Handle payload, ie any encoding or exploit specific bad chars
output.PrintfStatus("using '%s' in place of default", string(conf.CustomPayload))
}
// Handle the normal default case
case payload.LinuxELF:
output.PrintfStatus("adding LinuxELF")
if conf.HasCustomPayload() {
// Handle payload, ie any encoding or exploit specific bad chars
output.PrintfStatus("using binary len %d in place of default", len(string(conf.CustomPayload)))
}
// Handle the normal default case
}
Payload and Exploit Effects
You can now define payload effects. The above example adds a list of payload effects if the default payload is used. An example of how this is now available in the details listing from the above set of examples to allow for programmatically extracting support from details lists:
poptart:~/src/work/payload-test $ go run byop/byop.go -details
time=2025-10-10T10:38:43.694-06:00 level=SUCCESS msg="Implementation Details" ExploitType=CodeExecution AssetDetection=true VersionScanner=false Exploitation=true SupportedC2=[SimpleShellServer] SupportedPayloads="[GenericCommand (none): Effects - LinuxELF (amd64): Effects - LinuxELF (arm64): Effects - (FileCreate: /var/www/html/pwnt, /var/www/html/pwnt2)]" Vendor="" Products=[] CPE=[] CVE=CVE-2023-28324 Protocol=HTTP DefaultPort=80 CustomFlags="[{Name:command Type:string Default:} {Name:payload Type:string Default:} {Name:payload-type Type:string Default:GenericCommand} {Name:payload-arch Type:string Default:none}]"
Reference
- Initial Change details: https://github.com/vulncheck-oss/go-exploit/pull/459