Skip to content

Commit

Permalink
Implement initial integration of traffic encoders into generate pkg
Browse files Browse the repository at this point in the history
moloch-- committed Jan 27, 2023
1 parent 6d2030e commit 8f8b714
Showing 10 changed files with 1,411 additions and 1,233 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ UNAME_P := $(shell uname -p)
# If the target is Windows from Linux/Darwin, check for mingw
CROSS_COMPILERS = x86_64-w64-mingw32-gcc x86_64-w64-mingw32-g++
ifneq (,$(findstring cgosqlite,$(TAGS)))
ENV +=CGO_ENABLED=1
ENV += CGO_ENABLED=1
ifeq ($(MAKECMDGOALS), windows)
K := $(foreach exec,$(CROSS_COMPILERS),\
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
2,452 changes: 1,230 additions & 1,222 deletions protobuf/clientpb/client.pb.go

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions protobuf/clientpb/client.proto
Original file line number Diff line number Diff line change
@@ -173,11 +173,12 @@ message ImplantConfig {
bool RunAtLoad = 105;
bool TrafficEncodersEnabled = 106;

EncoderAssets Assets = 200;
repeated commonpb.File Assets = 200;
}

message EncoderAssets {
repeated commonpb.File Assets = 1;
message TrafficEncoder {
uint64 ID = 1;
commonpb.File WASM = 2;
}

message ExternalImplantConfig {
18 changes: 18 additions & 0 deletions protobuf/protobufs.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
package protobufs

/*
Sliver Implant Framework
Copyright (C) 2023 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import (
"embed"
)
21 changes: 21 additions & 0 deletions server/db/models/implant.go
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ import (
"time"

"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/gofrs/uuid"
"gorm.io/gorm"
)
@@ -132,6 +133,7 @@ type ImplantConfig struct {
FileName string

TrafficEncodersEnabled bool
Assets []EncoderAsset
}

// BeforeCreate - GORM hook
@@ -196,6 +198,12 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
config.CanaryDomains = append(config.CanaryDomains, canaryDomain.Domain)
}

// Copy Assets
config.Assets = []*commonpb.File{}
for _, asset := range ic.Assets {
config.Assets = append(config.Assets, asset.ToProtobuf())
}

// Copy C2
config.C2 = []*clientpb.ImplantC2{}
for _, c2 := range ic.C2 {
@@ -288,3 +296,16 @@ func (ip *ImplantProfile) BeforeCreate(tx *gorm.DB) (err error) {
ip.CreatedAt = time.Now()
return nil
}

// EncoderAsset - Tracks which assets were embedded into the implant
// but we currently don't keep a copy of the actual data
type EncoderAsset struct {
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
CreatedAt time.Time `gorm:"->;<-:create;"`

Name string
}

func (t *EncoderAsset) ToProtobuf() *commonpb.File {
return &commonpb.File{Name: t.Name}
}
15 changes: 14 additions & 1 deletion server/encoders/encoders.go
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@ func init() {
})
}

// EncoderMap - Maps EncoderIDs to Encoders
// EncoderMap - A map of all available encoders (native and traffic/wasm)
var EncoderMap = map[uint64]util.Encoder{
util.Base64EncoderID: Base64,
util.HexEncoderID: Hex,
@@ -62,6 +62,18 @@ var EncoderMap = map[uint64]util.Encoder{
util.PNGEncoderID: PNG,
}

// TrafficEncoderMap - Keeps track of the loaded traffic encoders (i.e., wasm-based encoder functions)
var TrafficEncoderMap = map[uint64]*traffic.TrafficEncoder{}

// NativeEncoderMap - Keeps track of the native encoders (i.e., native Go encoder functions)
var NativeEncoderMap = map[uint64]util.Encoder{
util.Base64EncoderID: Base64,
util.HexEncoderID: Hex,
util.EnglishEncoderID: English,
util.GzipEncoderID: Gzip,
util.PNGEncoderID: PNG,
}

// LoadTrafficEncodersFromFS - Loads the wasm traffic encoders from the filesystem, for the
// server these will be loaded from: <app root>/traffic-encoders/*.wasm
func LoadTrafficEncodersFromFS(encodersFS util.EncoderFS, logger func(string)) error {
@@ -93,6 +105,7 @@ func LoadTrafficEncodersFromFS(encodersFS util.EncoderFS, logger func(string)) e
return err
}
EncoderMap[uint64(wasmEncoderID)] = trafficEncoder
TrafficEncoderMap[uint64(wasmEncoderID)] = trafficEncoder
encodersLog.Info(fmt.Sprintf("loading %s (id: %d, bytes: %d)", wasmEncoderModuleName, wasmEncoderID, len(wasmEncoderData)))
}
encodersLog.Info(fmt.Sprintf("loaded %d traffic encoders", len(wasmEncoderFiles)))
113 changes: 110 additions & 3 deletions server/generate/binaries.go
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ import (
"encoding/hex"
"fmt"
"io/fs"
insecureRand "math/rand"
"net/url"
"os"
"path"
@@ -39,9 +40,13 @@ import (
"github.com/bishopfox/sliver/server/configs"
"github.com/bishopfox/sliver/server/cryptography"
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/encoders"
"github.com/bishopfox/sliver/server/gogo"
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/util"
utilEncoders "github.com/bishopfox/sliver/util/encoders"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

var (
@@ -177,6 +182,14 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) (string, *model
cfg.IsShellcode = pbConfig.IsShellcode

cfg.RunAtLoad = pbConfig.RunAtLoad
cfg.TrafficEncodersEnabled = pbConfig.TrafficEncodersEnabled

cfg.Assets = []models.EncoderAsset{}
for _, pbAsset := range pbConfig.Assets {
cfg.Assets = append(cfg.Assets, models.EncoderAsset{
Name: pbAsset.Name,
})
}

cfg.CanaryDomains = []models.CanaryDomain{}
for _, pbCanary := range pbConfig.CanaryDomains {
@@ -599,6 +612,12 @@ func renderSliverGoCode(name string, otpSecret string, config *models.ImplantCon
return "", err
}

// Render encoder assets
renderNativeEncoderAssets(config, sliverPkgDir)
if config.TrafficEncodersEnabled {
renderTrafficEncoderAssets(config, sliverPkgDir)
}

// Render GoMod
buildLog.Info("Rendering go.mod file ...")
goModPath := filepath.Join(sliverPkgDir, "go.mod")
@@ -637,6 +656,93 @@ func renderSliverGoCode(name string, otpSecret string, config *models.ImplantCon
return sliverPkgDir, nil
}

// renderTrafficEncoderAssets - Copies and compresses any enabled WASM traffic encoders
func renderTrafficEncoderAssets(config *models.ImplantConfig, sliverPkgDir string) {
buildLog.Infof("Rendering traffic encoder assets ...")
encoderAssetsPath := filepath.Join(sliverPkgDir, "implant", "sliver", "encoders", "assets")
for _, asset := range config.Assets {
if !strings.HasSuffix(asset.Name, ".wasm") {
continue
}
wasm, err := assets.TrafficEncoderFS.ReadFile(asset.Name)
if err != nil {
buildLog.Errorf("Failed to read %s: %v", asset.Name, err)
continue
}
saveAssetPath := filepath.Join(encoderAssetsPath, filepath.Base(asset.Name))
compressedWasm, _ := encoders.Gzip.Encode(wasm)
buildLog.Infof("Embed traffic encoder %s (%d, %d compressed)",
asset.Name, util.ByteCountBinary(int64(len(wasm))), util.ByteCountBinary(int64(len(compressedWasm))))
err = os.WriteFile(saveAssetPath, compressedWasm, 0600)
if err != nil {
buildLog.Errorf("Failed to write %s: %v", saveAssetPath, err)
continue
}
}
}

// renderNativeEncoderAssets - Render native encoder assets such as the english dictionary file
func renderNativeEncoderAssets(config *models.ImplantConfig, sliverPkgDir string) {
buildLog.Infof("Rendering native encoder assets ...")
encoderAssetsPath := filepath.Join(sliverPkgDir, "implant", "sliver", "encoders", "assets")

// English assets
dictionary := renderImplantEnglish()
dictionaryPath := filepath.Join(encoderAssetsPath, "english.gz")
data := []byte(strings.Join(dictionary, "\n"))
compressedData, _ := encoders.Gzip.Encode(data)
buildLog.Infof("Embed english dictionary (%d, %d compressed)",
util.ByteCountBinary(int64(len(data))), util.ByteCountBinary(int64(len(compressedData))))
err := os.WriteFile(dictionaryPath, compressedData, 0600)
if err != nil {
buildLog.Errorf("Failed to write %s: %v", dictionaryPath, err)
}
}

// renderImplantEnglish - Render the english dictionary file, ensures that the returned dictionary
// contains at least one word that will encode to a given byte value (0-255). There is also a default
// dictionary that we'll try to overwrite (the default one is in the git repo).
func renderImplantEnglish() []string {
allWords := assets.English() // 178,543 words -> server/assets/fs/english.txt
meRiCaN := cases.Title(language.AmericanEnglish)
for i := 0; i < len(allWords); i++ {
switch insecureRand.Intn(3) {
case 0:
allWords[i] = strings.ToUpper(allWords[i])
case 1:
allWords[i] = strings.ToLower(allWords[i])
case 2:
allWords[i] = meRiCaN.String(allWords[i])
}
}

// Calculate the sum for each word
allWordsDictionary := map[int][]string{}
for _, word := range allWords {
word = strings.TrimSpace(word)
sum := utilEncoders.SumWord(word)
allWordsDictionary[sum] = append(allWordsDictionary[sum], word)
}

// Shuffle the words for each byte value
for byteValue := 0; byteValue < 256; byteValue++ {
insecureRand.Shuffle(len(allWordsDictionary[byteValue]), func(i, j int) {
allWordsDictionary[byteValue][i], allWordsDictionary[byteValue][j] = allWordsDictionary[byteValue][j], allWordsDictionary[byteValue][i]
})
}

// Build the implant's dictionary, two words per-byte value
implantDictionary := []string{}
for byteValue := 0; byteValue < 256; byteValue++ {
wordsForByteValue := allWordsDictionary[byteValue]
implantDictionary = append(implantDictionary, wordsForByteValue[0])
if 1 < len(wordsForByteValue) {
implantDictionary = append(implantDictionary, wordsForByteValue[1])
}
}
return implantDictionary
}

// GenerateConfig - Generate the keys/etc for the implant
func GenerateConfig(name string, config *models.ImplantConfig, save bool) error {
var err error
@@ -929,12 +1035,13 @@ func getGoHttpsProxy() string {
}

const (
// The wireguard garble bug appears to have been fixed.
// Updated the wgGoPrivate to "*"
// wgGoPrivate = "*"
allGoPrivate = "*"
)

// goGarble - Can be used to conditionally modify the GOGARBLE env variable
// this is currently set to '*' (all packages) however in the past we've had
// to carve out specific packages, so we left this here just in case we need
// it in the future.
func goGarble(config *models.ImplantConfig) string {
// for _, c2 := range config.C2 {
// uri, err := url.Parse(c2.URL)
5 changes: 5 additions & 0 deletions util/encoders/encoders.go
Original file line number Diff line number Diff line change
@@ -21,16 +21,21 @@ package encoders
import "io/fs"

const (

// EncoderModulus - The modulus used to calculate the encoder ID from a C2 request nonce
// *** IMPORTANT *** ENCODER IDs MUST BE LESS THAN THE MODULUS
EncoderModulus = uint64(65537)
MaxN = uint64(9999999)

// These were chosen at random other than the "No Encoder" ID (0)
Base32EncoderID = uint64(65)
Base58EncoderID = uint64(43)
Base64EncoderID = uint64(131)
EnglishEncoderID = uint64(31)
GzipEncoderID = uint64(49)
HexEncoderID = uint64(92)
PNGEncoderID = uint64(22)
NoEncoderID = uint64(0)
)

// Encoder - Can losslessly encode arbitrary binary data
7 changes: 4 additions & 3 deletions util/encoders/english.go
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ func (e English) Decode(words []byte) ([]byte, error) {
if len(word) == 0 {
continue
}
byteValue := sumWord(word)
byteValue := SumWord(word)
data = append(data, byte(byteValue))
}
return data, nil
@@ -71,12 +71,13 @@ func buildDictionary() {
dictionary = map[int][]string{}
for _, word := range getEnglishDictionary() {
word = strings.TrimSpace(word)
sum := sumWord(word)
sum := SumWord(word)
dictionary[sum] = append(dictionary[sum], word)
}
}

func sumWord(word string) int {
// SumWord - Sum the ASCII values of a word
func SumWord(word string) int {
sum := 0
for _, char := range word {
sum += int(char)
4 changes: 4 additions & 0 deletions util/encoders/traffic/traffic-encoder.go
Original file line number Diff line number Diff line change
@@ -41,6 +41,8 @@ func CalculateWasmEncoderID(wasmEncoderData []byte) uint64 {

// TrafficEncoder - Implements the `Encoder` interface using a wasm backend
type TrafficEncoder struct {
ID uint64

ctx context.Context
runtime wazero.Runtime
mod api.Module
@@ -172,6 +174,8 @@ func CreateTrafficEncoder(name string, wasm []byte, logger TrafficEncoderLogCall
}

return &TrafficEncoder{
ID: CalculateWasmEncoderID(wasm),

ctx: ctx,
runtime: wasmRuntime,
mod: mod,

0 comments on commit 8f8b714

Please sign in to comment.