Skip to content

Commit 6e5ad93

Browse files
authored
feat: add output option (#8)
This commit also refactor the overall project architecture to make it more scalable. It also adds tests on encryption method and a cleaner implementation of the loader.Compile function Resolves: #3 Signed-off-by: Vasek - Tom C <[email protected]>
1 parent 19c4411 commit 6e5ad93

File tree

12 files changed

+163
-76
lines changed

12 files changed

+163
-76
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@
2121
go.work
2222

2323
221b
24+
*.sh
2425

2526
.idea

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ Usage:
3232
Flags:
3333
-h, --help help for bake
3434
-k, --key string key to use for the xor
35+
-o, --output string Output path (e.g., /home/bin.exe)
3536
-s, --shellpath string Path to the shell scrypt
3637

3738
Global Flags:
3839
--debug activate debug mode
40+
3941
```
4042

4143
## Example

cli/bake.go

+21-12
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,52 @@ import (
44
"fmt"
55
"github.com/spf13/cobra"
66
"os"
7+
"path/filepath"
8+
"strings"
79

8-
"github.com/cmepw/221b/encryption"
10+
"github.com/cmepw/221b/encryption/xor"
911
"github.com/cmepw/221b/loader"
1012
"github.com/cmepw/221b/logger"
1113
)
1214

1315
var (
14-
shellpath string
16+
shellPath string
1517
key string
18+
output string
1619
)
1720

1821
var (
19-
ErrMissingShellpath = fmt.Errorf("missing shellpath argument")
22+
ErrMissingShellPath = fmt.Errorf("missing shellPath argument")
2023
ErrMissingKey = fmt.Errorf("missing key argument")
2124
)
2225

2326
var bake = &cobra.Command{
2427
Use: "bake",
2528
Short: "Build a windows payload with the given shell encrypted in it to bypass AV",
2629
Run: func(cmd *cobra.Command, args []string) {
27-
if shellpath == "" {
28-
logger.Fatal(ErrMissingShellpath)
30+
if shellPath == "" {
31+
logger.Fatal(ErrMissingShellPath)
2932
}
3033

3134
if key == "" {
3235
logger.Fatal(ErrMissingKey)
3336
}
3437

35-
logger.Debug(fmt.Sprintf("baking %s with key %s", shellpath, key))
38+
if output == "" {
39+
output = strings.TrimSuffix(filepath.Base(shellPath), filepath.Ext(shellPath)) + loader.WindowsExt
40+
logger.Warn(fmt.Sprintf("output path set to default: %s", output))
41+
}
42+
43+
logger.Debug(fmt.Sprintf("baking %s with key %s", shellPath, key))
3644

37-
logger.Debug(fmt.Sprintf("reading %s", shellpath))
38-
file, err := os.ReadFile(shellpath)
45+
logger.Debug(fmt.Sprintf("reading %s", shellPath))
46+
file, err := os.ReadFile(shellPath)
3947
if err != nil {
4048
logger.Fatal(err)
4149
}
4250

43-
logger.Debug(fmt.Sprintf("encrypting %s", shellpath))
44-
encryptedShell := encryption.Xor.Encrypt(file, []byte(key))
51+
logger.Debug(fmt.Sprintf("encrypting %s", shellPath))
52+
encryptedShell := xor.Encrypt(file, []byte(key))
4553

4654
logger.Debug(fmt.Sprintf("injecting encrypted shell into payload"))
4755

@@ -56,13 +64,14 @@ var bake = &cobra.Command{
5664
fmt.Println(string(content))
5765
}
5866

59-
if err := xorLoader.Compile(shellpath, content); err != nil {
67+
if err := xorLoader.Compile(output, content); err != nil {
6068
logger.Fatal(err)
6169
}
6270
},
6371
}
6472

6573
func init() {
66-
bake.Flags().StringVarP(&shellpath, "shellpath", "s", "", "Path to the shell scrypt")
74+
bake.Flags().StringVarP(&shellPath, "shellPath", "s", "", "path to the shell scrypt")
6775
bake.Flags().StringVarP(&key, "key", "k", "", "key to use for the xor")
76+
bake.Flags().StringVarP(&output, "output", "o", "", "output path (e.g., /home/bin.exe)")
6877
}

encryption/xor.go

-23
This file was deleted.

encryption/xor/xor.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Package xor implements xor encryption and decryption.
2+
package xor
3+
4+
// Decrypt apply xor on the content with the given key.
5+
// Note that it will actually update the given content.
6+
func Decrypt(content, key []byte) []byte {
7+
keyLen := len(key)
8+
9+
for i := 0; i < len(content); i++ {
10+
content[i] ^= key[i%keyLen]
11+
}
12+
return content
13+
}
14+
15+
// Encrypt apply xor on the content with the given key.
16+
// Note that it will actually update the given content.
17+
func Encrypt(content, key []byte) []byte {
18+
keyLen := len(key)
19+
20+
for i := 0; i < len(content); i++ {
21+
content[i] ^= key[i%keyLen]
22+
}
23+
24+
return content
25+
}

encryption/xor/xor_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package xor
2+
3+
import (
4+
"bytes"
5+
"github.com/stretchr/testify/require"
6+
"testing"
7+
)
8+
9+
func TestXor_EncryptDecrypt(t *testing.T) {
10+
type TestCase struct {
11+
content []byte
12+
key []byte
13+
}
14+
15+
testsCases := map[string]TestCase{
16+
"encrypt a simple string": {
17+
content: []byte("hello world"),
18+
key: []byte("alwpkrMkgke"),
19+
},
20+
"encrypt a golang file": {
21+
content: []byte(`
22+
package main
23+
24+
import "fmt"
25+
26+
func main() {
27+
fmt.Println("hello Go!")
28+
}
29+
`),
30+
key: []byte("mdfptiEdd"),
31+
},
32+
}
33+
34+
for name, tt := range testsCases {
35+
t.Run(name, func(t *testing.T) {
36+
encryptedContent := Encrypt(bytes.Clone(tt.content), tt.key)
37+
38+
require.NotEqual(t, tt.content, encryptedContent)
39+
decryptedContent := Decrypt(encryptedContent, tt.key)
40+
require.Equal(t, tt.content, decryptedContent)
41+
})
42+
}
43+
}

go.mod

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ module github.com/cmepw/221b
22

33
go 1.20
44

5-
require github.com/spf13/cobra v1.7.0
5+
require (
6+
github.com/spf13/cobra v1.7.0
7+
github.com/stretchr/testify v1.8.4
8+
)
69

710
require (
11+
github.com/davecgh/go-spew v1.1.1 // indirect
812
github.com/inconshreveable/mousetrap v1.1.0 // indirect
13+
github.com/pmezard/go-difflib v1.0.0 // indirect
914
github.com/spf13/pflag v1.0.5 // indirect
15+
gopkg.in/yaml.v3 v3.0.1 // indirect
1016
)

go.sum

+8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
35
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
6+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
48
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
59
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
610
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
711
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
812
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
13+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
14+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
15+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
916
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
17+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1018
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

loader/extension.go

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package loader
2+
3+
const WindowsExt = ".exe"

loader/loader.go

+48-38
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"os"
66
"os/exec"
77
"path/filepath"
8-
"strings"
98

109
"github.com/cmepw/221b/logger"
1110
"github.com/cmepw/221b/templates"
@@ -19,69 +18,80 @@ type Loader interface {
1918
type baseLoader struct{}
2019

2120
const (
22-
windowsExt = ".exe"
23-
tmpFile = "tmp.go"
21+
tmpFile = "tmp.go"
22+
tmpDir = "/tmp/221b-compile"
2423
)
2524

26-
func (b baseLoader) Compile(path string, content []byte) error {
27-
outputPath := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + windowsExt
28-
29-
dir := "/tmp/test"
30-
if err := os.MkdirAll(dir, 0750); err != nil {
31-
logger.Error(fmt.Errorf("could not create temporary directory"))
25+
func (b baseLoader) Compile(outputPath string, content []byte) error {
26+
if err := b.setupTmpDir(content); err != nil {
3227
return err
3328
}
34-
3529
defer func() {
36-
_ = os.RemoveAll(dir)
30+
logger.Debug(fmt.Sprintf("cleanup temporary dir %s", tmpDir))
31+
_ = os.RemoveAll(tmpDir)
3732
}()
3833

39-
// Set environment
40-
logger.Debug("write content to temporary file")
41-
if err := os.WriteFile(filepath.Join(dir, tmpFile), content, 0666); err != nil {
42-
logger.Error(fmt.Errorf("could not write tmp file"))
43-
return err
44-
}
45-
46-
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(templates.GoMod), 0666); err != nil {
47-
logger.Error(fmt.Errorf("could not write tmp go.mod file"))
48-
return err
49-
}
50-
51-
initCmd := exec.Command("go", "get", "-u", "golang.org/x/sys/windows")
52-
initCmd.Dir = dir
53-
initCmd.Stderr = os.Stderr
54-
initCmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64")
55-
if err := initCmd.Run(); err != nil {
34+
err := b.execCmd("go", "get", "-u", "golang.org/x/sys/windows")
35+
if err != nil {
5636
logger.Error(fmt.Errorf("could not install dependency"))
5737
return err
5838
}
5939

6040
logger.Debug("dependency installed")
41+
logger.Debug("start compiling binary")
6142

62-
pwd, err := os.Getwd()
43+
relOutputPath, err := filepath.Abs(outputPath)
6344
if err != nil {
6445
return err
6546
}
6647

67-
buildCmd := exec.Command(
48+
err = b.execCmd(
6849
"go",
6950
"build",
7051
"-ldflags",
7152
"-s -w -H=windowsgui",
7253
"-o",
73-
filepath.Join(pwd, outputPath),
74-
filepath.Join(dir, tmpFile),
54+
relOutputPath,
55+
filepath.Join(tmpDir, tmpFile),
7556
)
76-
buildCmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64")
77-
buildCmd.Stderr = os.Stderr
78-
buildCmd.Dir = dir
79-
80-
if err := buildCmd.Run(); err != nil {
57+
if err != nil {
8158
logger.Error(fmt.Errorf("failed to compile"))
8259
return err
8360
}
8461

85-
logger.Info(fmt.Sprintf("file compiled to %s", filepath.Join(pwd, outputPath)))
62+
logger.Info(fmt.Sprintf("file compiled to %s", relOutputPath))
63+
64+
return nil
65+
}
66+
67+
func (b baseLoader) execCmd(name string, args ...string) error {
68+
logger.Debug(fmt.Sprintf("execute command %s", name))
69+
70+
cmd := exec.Command(name, args...)
71+
cmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64")
72+
cmd.Stderr = os.Stderr
73+
cmd.Dir = tmpDir
74+
75+
return cmd.Run()
76+
}
77+
78+
func (b baseLoader) setupTmpDir(goFile []byte) error {
79+
logger.Debug(fmt.Sprintf("setup temporary directory %s", tmpDir))
80+
81+
if err := os.MkdirAll(tmpDir, 0750); err != nil {
82+
logger.Error(fmt.Errorf("could not create temporary directory"))
83+
return err
84+
}
85+
86+
if err := os.WriteFile(filepath.Join(tmpDir, tmpFile), goFile, 0666); err != nil {
87+
logger.Error(fmt.Errorf("could not write tmp file"))
88+
return err
89+
}
90+
91+
if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte(templates.GoMod), 0666); err != nil {
92+
logger.Error(fmt.Errorf("could not write tmp go.mod file"))
93+
return err
94+
}
95+
8696
return nil
8797
}

logger/logger.go

-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import (
55
"os"
66
)
77

8-
var DebugMode = false
9-
108
func Debug(msg string) {
119
if DebugMode {
1210
fmt.Printf("[*] %s\n", msg)

logger/mode.go

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package logger
2+
3+
// DebugMode is a global variable that can be set to true
4+
// to enable debug mode
5+
var DebugMode = false

0 commit comments

Comments
 (0)