mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-10 01:41:29 -10:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16b54c8f5b | ||
|
|
8317678143 | ||
|
|
d3a6e82214 | ||
|
|
4f899c97f0 | ||
|
|
1a388c6e1e | ||
|
|
335797e661 | ||
|
|
d767bd019c | ||
|
|
590f9c3f96 | ||
|
|
cff899aa49 | ||
|
|
ca2dc9d267 | ||
|
|
afdd77c642 | ||
|
|
8a5970c3da | ||
|
|
2eb2ac8121 | ||
|
|
daf7d404b1 | ||
|
|
3b308920f4 | ||
|
|
c73b183f74 | ||
|
|
7564312f5e | ||
|
|
6134b78afb | ||
|
|
3db9946608 | ||
|
|
ef09058498 | ||
|
|
331faeb8ef | ||
|
|
4ae195e3eb | ||
|
|
f8f8ba5864 | ||
|
|
216e3b9759 | ||
|
|
f1bc5074f9 | ||
|
|
1b5d3436eb | ||
|
|
9397fcb4ae | ||
|
|
04c6c3f800 | ||
|
|
7842a6c4c6 | ||
|
|
6c815a4432 | ||
|
|
81358e31bb | ||
|
|
7bea772dbd | ||
|
|
7fe0b295f5 |
No files matched your search
@@ -57,7 +57,7 @@ var (
|
||||
|
||||
defaultHostProc = map[string]string{
|
||||
"windows": windowsDefaultHostProc,
|
||||
"linux": windowsDefaultHostProc,
|
||||
"linux": linuxDefaultHostProc,
|
||||
"darwin": macosDefaultHostProc,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -99,6 +99,7 @@ type ExtCommand struct {
|
||||
Arguments []*extensionArgument `json:"arguments"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
DependsOn string `json:"depends_on"`
|
||||
Init string `json:"init"`
|
||||
|
||||
Manifest *ExtensionManifest
|
||||
}
|
||||
@@ -371,38 +372,50 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
}
|
||||
extensionList = extList.Names
|
||||
}
|
||||
depLoaded := false
|
||||
//extensionList contains all *implant* loaded extensions. Is a sha256 sum of the relevant file.
|
||||
|
||||
//get the file hash to compare against implant extensions later
|
||||
toberunfilepath, err := ext.getFileForTarget(goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//we need to check the dependent if it's a bof
|
||||
if ext.DependsOn != "" {
|
||||
//verify extension is in loaded list
|
||||
if extension, found := loadedExtensions[ext.DependsOn]; found {
|
||||
toberunfilepath, err = extension.getFileForTarget(goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// handle error
|
||||
return fmt.Errorf("attempted to load non-existing extension: %s", ext.DependsOn)
|
||||
}
|
||||
}
|
||||
|
||||
//todo, maybe cache these values somewhere
|
||||
toberunfiledata, err := os.ReadFile(toberunfilepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(toberunfiledata) == 0 {
|
||||
//read an empty file, bail out
|
||||
return errors.New("read empty extension file content")
|
||||
}
|
||||
bd := sha256.Sum256(toberunfiledata)
|
||||
toberunhash := hex.EncodeToString(bd[:])
|
||||
|
||||
for _, extName := range extensionList {
|
||||
if !depLoaded {
|
||||
if extN, ok := loadedExtensions[cmd.Name()]; ok && extName == extN.DependsOn {
|
||||
depLoaded = true
|
||||
}
|
||||
}
|
||||
//check if the file suffixes are the same (should only be relevant to the same extension package), and don't load the file if it's already there
|
||||
extN := loadedExtensions[cmd.Name()]
|
||||
if extN != nil {
|
||||
//confirm we haven't already loaded the file
|
||||
for _, extf := range extN.Files {
|
||||
if strings.HasSuffix(binPath, extf.Path) {
|
||||
//file already exists, no need to load it
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if extN, ok := loadedExtensions[cmd.Name()]; ok && extN.CommandName == extName {
|
||||
//check if extension we are trying to run (ext.CommandName or ext.DependsOn, for bofs) is already loaded
|
||||
if extName == toberunhash {
|
||||
//exists on the other side, exit early
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Extension not found, let's load it
|
||||
if filepath.Ext(binPath) == ".o" {
|
||||
// BOFs are not loaded by the DLL loader, but we make sure the loader itself is loaded
|
||||
// Auto load the coff loader if we have it
|
||||
if !depLoaded {
|
||||
if errLoad := loadDep(goos, goarch, ext.DependsOn, cmd, con); errLoad != nil {
|
||||
return errLoad
|
||||
}
|
||||
}
|
||||
return nil
|
||||
// BOFs are not loaded by the DLL loader, but we need to load the loader (more load more good)
|
||||
if ext.DependsOn != "" {
|
||||
return loadDep(goos, goarch, ext.DependsOn, cmd, con)
|
||||
}
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
@@ -414,17 +427,45 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerExtension(goos string, _ *ExtCommand, binData []byte, cmd *cobra.Command, con *console.SliverClient) error {
|
||||
func registerExtension(goos string, ext *ExtCommand, binData []byte, cmd *cobra.Command, con *console.SliverClient) error {
|
||||
//set extension name to a hash of the data to avoid loading more than one instance
|
||||
bd := sha256.Sum256(binData)
|
||||
name := hex.EncodeToString(bd[:])
|
||||
sess, beac := con.ActiveTarget.GetInteractive()
|
||||
ctrl := make(chan bool)
|
||||
//first time run of an extension will require some waiting depending on the size
|
||||
if sess != nil {
|
||||
msg := fmt.Sprintf("Sending %s to implant ...", ext.CommandName)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
}
|
||||
//don't block if we are in beacon mode
|
||||
if beac != nil && sess == nil {
|
||||
go func() {
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: name,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
Init: ext.Init,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error registering extension: %s\n", err)
|
||||
}
|
||||
if registerResp.Response != nil && registerResp.Response.Err != "" {
|
||||
con.PrintErrorf("Error registering extension: %s\n", errors.New(registerResp.Response.Err))
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
//session mode (hopefully)
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: name,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
//Init: ext.Init, ?
|
||||
Name: name,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -525,6 +566,33 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
bd := sha256.Sum256(extdata)
|
||||
name := hex.EncodeToString(bd[:])
|
||||
if isBOF {
|
||||
//if we are using a bof, we are actually calling the coffloader extension - so get the file from dep ref and use that shasum
|
||||
|
||||
if extension, found := loadedExtensions[ext.DependsOn]; found {
|
||||
dep, err := extension.getFileForTarget(goos, goarch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not get file for extension %s", ext.DependsOn)
|
||||
return
|
||||
}
|
||||
depdata, err := os.ReadFile(dep)
|
||||
if err != nil {
|
||||
con.PrintErrorf("dep read file error: %s\n", err)
|
||||
return
|
||||
}
|
||||
if len(depdata) == 0 {
|
||||
con.PrintErrorf("read empty file: %s\n", dep)
|
||||
return
|
||||
}
|
||||
bd = sha256.Sum256(depdata)
|
||||
name = hex.EncodeToString(bd[:])
|
||||
} else {
|
||||
// handle error
|
||||
con.PrintErrorf("attempted to load non-existing extension: %s", ext.DependsOn)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
callExtResp, err := con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
Name: name,
|
||||
Export: entryPoint,
|
||||
@@ -579,7 +647,7 @@ func getExtArgs(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]b
|
||||
missingRequiredArgs := make([]string, 0)
|
||||
|
||||
// If we have an extension that expects a single string, but more than one has been parsed, combine them
|
||||
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") {
|
||||
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") && len(args) > 0 {
|
||||
// The loop below will only read the first element of args because ext.Arguments is 1
|
||||
args[0] = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
@@ -184,6 +184,28 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
profilesCmd.AddCommand(profilesNewCmd)
|
||||
|
||||
profilesStageCmd := &cobra.Command{
|
||||
Use: consts.StageStr,
|
||||
Short: "Generate implant from a profile and encode or encrypt it",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Long: help.GetHelpFor([]string{consts.ProfilesStr, consts.StageStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ProfilesStageCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("profiles", false, profilesStageCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.StringP("name", "n", "", "Implant name")
|
||||
f.StringP("aes-encrypt-key", "k", "", "AES Encryption Key")
|
||||
f.StringP("aes-encrypt-iv", "i", "", "AES Encryption IV")
|
||||
f.StringP("rc4-encrypt-key", "r", "", "RC4 encryption key")
|
||||
f.BoolP("prepend-size", "p", false, "Prepend stage size")
|
||||
f.StringP("compress", "c", "", "Compress stage (zlib, gzip, deflate9 or deflate)")
|
||||
})
|
||||
|
||||
carapace.Gen(profilesStageCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesStageCmd)
|
||||
|
||||
// Session flags and completions.
|
||||
coreImplantFlags("session", profilesNewCmd)
|
||||
compileImplantFlags("session", profilesNewCmd)
|
||||
|
||||
@@ -108,6 +108,7 @@ var (
|
||||
// Profiles
|
||||
consts.ProfilesStr + sep + consts.NewStr: newProfileHelp,
|
||||
consts.ProfilesStr + sep + consts.GenerateStr: generateProfileHelp,
|
||||
consts.ProfilesStr + sep + consts.StageStr: generateProfileStageHelp,
|
||||
|
||||
// Reactions
|
||||
consts.ReactionStr: reactionHelp,
|
||||
@@ -249,6 +250,9 @@ settings. Generated implants will still have per-binary certificates/obfuscation
|
||||
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate [name] <options>
|
||||
[[.Bold]]About:[[.Normal]] Generate an implant from a saved profile (see 'profiles new --help').`
|
||||
|
||||
generateProfileStageHelp = `[[.Bold]]Command:[[.Normal]] stage [name] <options>
|
||||
[[.Bold]]About:[[.Normal]] Generate and encrypt or encode an implant from a saved profile (see 'profiles stage --help').`
|
||||
|
||||
msfHelp = `[[.Bold]]Command:[[.Normal]] msf [--lhost] <options>
|
||||
[[.Bold]]About:[[.Normal]] Execute a metasploit payload in the current process.`
|
||||
|
||||
|
||||
@@ -169,5 +169,5 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["compress"] = carapace.ActionValues([]string{"zlib", "gzip", "deflate9", "none"}...).Tag("compression formats")
|
||||
})
|
||||
|
||||
return []*cobra.Command{jobsCmd, mtlsCmd, wgCmd, httpCmd, httpsCmd, stageCmd}
|
||||
return []*cobra.Command{jobsCmd, mtlsCmd, wgCmd, dnsCmd, httpCmd, httpsCmd, stageCmd}
|
||||
}
|
||||
@@ -264,11 +264,13 @@ func downloadAsset(client *http.Client, asset *version.Asset, saveTo string) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bar := pb.Full.Start64(limit)
|
||||
barReader := bar.NewProxyReader(resp.Body)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useRouter } from "next/router";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import AsciinemaPlayer from "./asciinema";
|
||||
import Youtube from "./youtube";
|
||||
|
||||
export type MarkdownProps = {
|
||||
key?: string;
|
||||
@@ -131,6 +132,11 @@ const MarkdownViewer = (props: MarkdownProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (lang === "youtube") {
|
||||
const embedId = (children as string) || "";
|
||||
return <Youtube embedId={embedId.trim()} />;
|
||||
}
|
||||
|
||||
if (lang === "asciinema") {
|
||||
const asciiCast: MarkdownAsciiCast = JSON.parse(
|
||||
children as string
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
export type YoutubeProps = {
|
||||
className?: string;
|
||||
|
||||
width?: number;
|
||||
height?: number;
|
||||
|
||||
embedId: string;
|
||||
};
|
||||
|
||||
export default function Youtube(props: YoutubeProps) {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<iframe
|
||||
width={`${props.width ? props.width : 640}`}
|
||||
height={`${props.height ? props.height : 360}`}
|
||||
src={`https://youtu.be/embed/${props.embedId}`}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
import React from "react";
|
||||
|
||||
// This generates the https: and wss: "connect-src" directives based on the above backends list so its a little easier to edit.
|
||||
const CSP = `default-src 'none'; script-src 'self' 'unsafe-eval'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self'`;
|
||||
const CSP = `default-src 'none'; script-src 'self' 'unsafe-eval'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; frame-src https://youtu.be; connect-src 'self'`;
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx: any) {
|
||||
@@ -17,7 +17,6 @@ class MyDocument extends Document {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<title>Sliver Docs</title>
|
||||
<meta httpEquiv="Content-Security-Policy" content={CSP} />
|
||||
</Head>
|
||||
<body>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
### Videos
|
||||
|
||||
- https://youtu.be/3R6WKUgN0K4?t=456
|
||||
- https://www.youtube.com/watch?v=QO_1UMaiWHk
|
||||
- https://www.youtube.com/watch?v=izMMmOaLn9g
|
||||
- https://www.youtube.com/watch?v=qIbrozlf2wM
|
||||
- https://www.youtube.com/watch?v=CKfjLnEMfvI
|
||||
- https://youtu.be/watch?v=QO_1UMaiWHk
|
||||
- https://youtu.be/watch?v=izMMmOaLn9g
|
||||
- https://youtu.be/watch?v=qIbrozlf2wM
|
||||
- https://youtu.be/watch?v=CKfjLnEMfvI
|
||||
|
||||
### Blogs
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
path, filter := determineDirPathFilter(targetPath)
|
||||
|
||||
dir, files, err := getDirList(path)
|
||||
dir, rootDirEntry, files, err := getDirList(path)
|
||||
|
||||
// Convert directory listing to protobuf
|
||||
timezone, offset := time.Now().Zone()
|
||||
@@ -166,6 +166,19 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
dirList.Exists = false
|
||||
}
|
||||
dirList.Files = []*sliverpb.FileInfo{}
|
||||
rootDirInfo, err := rootDirEntry.Info()
|
||||
if err == nil && filter == "" {
|
||||
// We should not get an error because we created the DirEntry object from the FileInfo object
|
||||
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
|
||||
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
|
||||
Size: rootDirInfo.Size(),
|
||||
ModTime: rootDirInfo.ModTime().Unix(),
|
||||
Mode: rootDirInfo.Mode().String(),
|
||||
Uid: getUid(rootDirInfo),
|
||||
Gid: getGid(rootDirInfo),
|
||||
IsDir: rootDirInfo.IsDir(),
|
||||
})
|
||||
}
|
||||
|
||||
var match bool = false
|
||||
var linkPath string = ""
|
||||
@@ -226,19 +239,30 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func getDirList(target string) (string, []fs.DirEntry, error) {
|
||||
func getDirList(target string) (string, fs.DirEntry, []fs.DirEntry, error) {
|
||||
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
|
||||
return "", nil, nil, err
|
||||
}
|
||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
if rootInfo, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
/*
|
||||
We could place the entry for the directory itself
|
||||
at the beginning of the returned slice of DirEntry
|
||||
objects, but then it is not clear if that is the
|
||||
root directory or a directory / file in the root
|
||||
directory with the same name as the root directory.
|
||||
|
||||
Using WalkDir is not great here because you cannot
|
||||
tell it to not be recursive, so we will be wasting
|
||||
cycles telling it to skip directories and files
|
||||
*/
|
||||
files, err := os.ReadDir(dir)
|
||||
return dir, files, err
|
||||
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
|
||||
}
|
||||
return dir, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
}
|
||||
|
||||
func rmHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
@@ -31,8 +31,8 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/procdump"
|
||||
"github.com/bishopfox/sliver/implant/sliver/mount"
|
||||
"github.com/bishopfox/sliver/implant/sliver/procdump"
|
||||
"github.com/bishopfox/sliver/implant/sliver/taskrunner"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -190,7 +190,7 @@ func memfilesListHandler(_ []byte, resp RPCResponse) {
|
||||
|
||||
pid := os.Getpid()
|
||||
path := fmt.Sprintf("/proc/%d/fd/", pid)
|
||||
dir, files, err := getDirList(path)
|
||||
dir, rootDirEntry, files, err := getDirList(path)
|
||||
|
||||
// Convert directory listing to protobuf
|
||||
timezone, offset := time.Now().Zone()
|
||||
@@ -201,6 +201,19 @@ func memfilesListHandler(_ []byte, resp RPCResponse) {
|
||||
dirList.Exists = false
|
||||
}
|
||||
dirList.Files = []*sliverpb.FileInfo{}
|
||||
rootDirInfo, err := rootDirEntry.Info()
|
||||
if err == nil {
|
||||
// We should not get an error because we created the DirEntry object from the FileInfo object
|
||||
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
|
||||
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
|
||||
Size: rootDirInfo.Size(),
|
||||
ModTime: rootDirInfo.ModTime().Unix(),
|
||||
Mode: rootDirInfo.Mode().String(),
|
||||
Uid: getUid(rootDirInfo),
|
||||
Gid: getGid(rootDirInfo),
|
||||
IsDir: rootDirInfo.IsDir(),
|
||||
})
|
||||
}
|
||||
|
||||
for _, dirEntry := range files {
|
||||
//log.Printf("File: %s\n", dirEntry.Name())
|
||||
|
||||
@@ -162,6 +162,60 @@ func DNSStartSession(parent string, opts *DNSOptions) (*SliverDNSClient, error)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
type DNSBase32Encoder struct{}
|
||||
|
||||
const alpha = "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
// Encode - Base32 Encode
|
||||
func (e DNSBase32Encoder) Encode(data []byte) ([]byte, error) {
|
||||
encodedData, err := encoders.Base32.Encode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
index := insecureRand.Intn(len(alpha))
|
||||
if index&1 == 1 {
|
||||
index = index - 1
|
||||
}
|
||||
prefix := alpha[index]
|
||||
return append([]byte{prefix}, encodedData...), nil
|
||||
}
|
||||
|
||||
// Decode - Base32 Decode
|
||||
func (e DNSBase32Encoder) Decode(data []byte) ([]byte, error) {
|
||||
if len(data) < 1 {
|
||||
return nil, errors.New("invalid data")
|
||||
}
|
||||
return encoders.Base32.Decode(data[1:])
|
||||
}
|
||||
|
||||
type DNSBase58Encoder struct{}
|
||||
|
||||
// Encode - Base58 Encode
|
||||
func (e DNSBase58Encoder) Encode(data []byte) ([]byte, error) {
|
||||
encodedData, err := encoders.Base58.Encode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := insecureRand.Intn(len(alpha))
|
||||
if index&1 != 1 {
|
||||
index = index + 1
|
||||
if len(alpha) <= index {
|
||||
index = index - 2
|
||||
}
|
||||
}
|
||||
prefix := alpha[index]
|
||||
return append([]byte{prefix}, encodedData...), nil
|
||||
}
|
||||
|
||||
// Decode - Base58 Decode
|
||||
func (e DNSBase58Encoder) Decode(data []byte) ([]byte, error) {
|
||||
if len(data) < 1 {
|
||||
return nil, errors.New("invalid data")
|
||||
}
|
||||
return encoders.Base58.Decode(data[1:])
|
||||
}
|
||||
|
||||
// NewDNSClient - Initialize a new DNS client, generally you should use DNSStartSession
|
||||
// instead of this function, this is exported mostly for unit testing
|
||||
func NewDNSClient(parent string, opts *DNSOptions) *SliverDNSClient {
|
||||
@@ -179,9 +233,10 @@ func NewDNSClient(parent string, opts *DNSOptions) *SliverDNSClient {
|
||||
closed: true,
|
||||
|
||||
WorkersPerResolver: opts.WorkersPerResolver,
|
||||
subdataSpace: 254 - len(parent) - (1 + (254-len(parent))/64),
|
||||
base32: encoders.Base32Encoder{},
|
||||
base58: encoders.Base58Encoder{},
|
||||
// parent domain data encoder
|
||||
subdataSpace: 254 - len(parent) - (1 + (254-len(parent))/64) - 1,
|
||||
base32: DNSBase32Encoder{},
|
||||
base58: DNSBase58Encoder{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,8 +265,8 @@ type SliverDNSClient struct {
|
||||
workerPool []*DNSWorker
|
||||
WorkersPerResolver int
|
||||
|
||||
base32 encoders.Base32Encoder
|
||||
base58 encoders.Base58Encoder
|
||||
base32 DNSBase32Encoder
|
||||
base58 DNSBase58Encoder
|
||||
|
||||
enableCaseSensitiveEncoder bool
|
||||
}
|
||||
@@ -460,8 +515,8 @@ func (s *SliverDNSClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
log.Printf("[dns] read envelope ...")
|
||||
// {{end}}
|
||||
|
||||
resolver, meta := s.randomResolver()
|
||||
pollMsg, err := s.pollMsg(meta)
|
||||
resolver, _ := s.randomResolver()
|
||||
pollMsg, err := s.pollMsg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -536,7 +591,7 @@ func (s *SliverDNSClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
log.Printf("[dns] clear msg domain: %v", domain)
|
||||
// {{end}}
|
||||
|
||||
respData, _, err = resolver.A(domain)
|
||||
_, _, err = resolver.A(domain)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] clear msg error: %s", err)
|
||||
@@ -740,13 +795,13 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
stop = len(data) - 1 // make sure the loop is executed at least once
|
||||
}
|
||||
|
||||
// Sometimes adding a byte will result in +2 chars so we -1 the subdata space
|
||||
// Sometimes adding a byte will result in +2 chars so we -2 the subdata space
|
||||
encoded = ""
|
||||
// {{if .Config.Debug}}
|
||||
//log.Printf("[dns] encoded: %d, subdata space: %d | stop: %d, len: %d",
|
||||
// len(encoded), (s.subdataSpace - 1), stop, len(data))
|
||||
// len(encoded), (s.subdataSpace - 2), stop, len(data))
|
||||
// {{end}}
|
||||
for len(encoded) < (s.subdataSpace-1) && stop < len(data) {
|
||||
for len(encoded) < (s.subdataSpace-2) && stop < len(data) {
|
||||
stop++
|
||||
// {{if .Config.Debug}}
|
||||
// log.Printf("[dns] shave data [%d:%d] of %d", start, stop, len(data))
|
||||
@@ -889,7 +944,7 @@ func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) pollMsg(meta *ResolverMetadata) (string, error) {
|
||||
func (s *SliverDNSClient) pollMsg() (string, error) {
|
||||
nonceBuf := make([]byte, 8)
|
||||
rand.Read(nonceBuf)
|
||||
pollMsg, _ := proto.Marshal(&dnspb.DNSMessage{
|
||||
|
||||
@@ -75,28 +75,28 @@ func TestSplitBufferBase58(t *testing.T) {
|
||||
t.Logf("Testing with client1")
|
||||
client1 := NewDNSClient(parent1, opts)
|
||||
testData := randomData(2048)
|
||||
clientSplitBuffer(t, client1, encoders.Base58{}, testData)
|
||||
clientSplitBuffer(t, client1, DNSBase58Encoder{}, testData)
|
||||
|
||||
t.Logf("Testing with client2")
|
||||
client2 := NewDNSClient(parent2, opts)
|
||||
testData2 := randomData(2048)
|
||||
clientSplitBuffer(t, client2, encoders.Base58{}, testData2)
|
||||
clientSplitBuffer(t, client2, DNSBase58Encoder{}, testData2)
|
||||
|
||||
t.Logf("Testing with client3")
|
||||
client3 := NewDNSClient(parent3, opts)
|
||||
testData3 := randomData(2048)
|
||||
clientSplitBuffer(t, client3, encoders.Base58{}, testData3)
|
||||
clientSplitBuffer(t, client3, DNSBase58Encoder{}, testData3)
|
||||
|
||||
t.Logf("Testing with client max")
|
||||
clientMax := NewDNSClient(parentMax, opts)
|
||||
testDataMax := randomData(2048)
|
||||
clientSplitBuffer(t, clientMax, encoders.Base58{}, testDataMax)
|
||||
clientSplitBuffer(t, clientMax, DNSBase58Encoder{}, testDataMax)
|
||||
|
||||
t.Logf("Testing all clients with randomly sized data")
|
||||
for _, client := range []*SliverDNSClient{client1, client2, client3} {
|
||||
for count := 0; count < 10; count++ {
|
||||
testData := randomDataRandomSize(2 * 1024 * 1024)
|
||||
clientSplitBuffer(t, client, encoders.Base58{}, testData)
|
||||
clientSplitBuffer(t, client, DNSBase58Encoder{}, testData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,28 +106,28 @@ func TestSplitBufferBase32(t *testing.T) {
|
||||
t.Logf("Testing with client1")
|
||||
client1 := NewDNSClient(parent1, opts)
|
||||
testData := randomData(2048)
|
||||
clientSplitBuffer(t, client1, encoders.Base32{}, testData)
|
||||
clientSplitBuffer(t, client1, DNSBase32Encoder{}, testData)
|
||||
|
||||
t.Logf("Testing with client2")
|
||||
client2 := NewDNSClient(parent2, opts)
|
||||
testData2 := randomData(2048)
|
||||
clientSplitBuffer(t, client2, encoders.Base32{}, testData2)
|
||||
clientSplitBuffer(t, client2, DNSBase32Encoder{}, testData2)
|
||||
|
||||
t.Logf("Testing with client3")
|
||||
client3 := NewDNSClient(parent3, opts)
|
||||
testData3 := randomData(2048)
|
||||
clientSplitBuffer(t, client3, encoders.Base32{}, testData3)
|
||||
clientSplitBuffer(t, client3, DNSBase32Encoder{}, testData3)
|
||||
|
||||
t.Logf("Testing with client max")
|
||||
clientMax := NewDNSClient(parentMax, opts)
|
||||
testDataMax := randomData(2048)
|
||||
clientSplitBuffer(t, clientMax, encoders.Base32{}, testDataMax)
|
||||
clientSplitBuffer(t, clientMax, DNSBase32Encoder{}, testDataMax)
|
||||
|
||||
t.Logf("Testing all clients with randomly sized data")
|
||||
for _, client := range []*SliverDNSClient{client1, client2, client3} {
|
||||
for count := 0; count < 10; count++ {
|
||||
testData := randomDataRandomSize(2 * 1024 * 1024)
|
||||
clientSplitBuffer(t, client, encoders.Base32{}, testData)
|
||||
clientSplitBuffer(t, client, DNSBase32Encoder{}, testData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,41 +186,41 @@ func TestSubdataSpace(t *testing.T) {
|
||||
|
||||
// 1.example.com. (14 chars parent, 240 chars subdata)
|
||||
// Grand Total: 254 chars
|
||||
// parent | subdata with '.' | subdata without '.'
|
||||
// 254 - 15 - [64 - 64 - 64 - 47] = 63 + 63 + 63 + 46 (235)
|
||||
// parent | subdata with '.' | subdata without '.'
|
||||
// 254 - 15 - [64 - 64 - 64 - 47] - 1 = 63 + 63 + 63 + 46 (234)
|
||||
// expected value is thus 235 (max chars without '.'), rounded down if applicable
|
||||
client1 := NewDNSClient(parent1, opts)
|
||||
if client1.subdataSpace != 235 {
|
||||
if client1.subdataSpace != 234 {
|
||||
t.Fatalf("Unexpected subdata space for parent %s: %d", parent1, client1.subdataSpace)
|
||||
}
|
||||
|
||||
// .something-longer.example.com. (30 chars parent, 224 chars subdata)
|
||||
// Grand Total: 254 chars
|
||||
// parent | subdata with '.' | subdata without '.'
|
||||
// 254 - 30 - [64 - 64 - 64 - 32] = 63 + 63 + 63 + 31 (220)
|
||||
// parent | subdata with '.' | subdata without '.'
|
||||
// 254 - 30 - [64 - 64 - 64 - 32] - 1 = 63 + 63 + 63 + 31 (219)
|
||||
// expected value is thus 235 (max chars without '.'), rounded down if applicable
|
||||
client2 := NewDNSClient(parent2, opts)
|
||||
if client2.subdataSpace != 220 {
|
||||
if client2.subdataSpace != 219 {
|
||||
t.Fatalf("Unexpected subdata space for parent %s: %d", parent2, client2.subdataSpace)
|
||||
}
|
||||
|
||||
// .something-even-longer.example.computer. (40 chars parent, 214 chars subdata)
|
||||
// Grand Total: 254 chars
|
||||
// parent | subdata with '.' | subdata without '.'
|
||||
// 254 - 40 - [64 - 64 - 64 - 22] = 63 + 63 + 63 + 21 (210)
|
||||
// parent | subdata with '.' | subdata without '.'
|
||||
// 254 - 40 - [64 - 64 - 64 - 22] - 1 = 63 + 63 + 63 + 21 (209)
|
||||
// expected value is thus 235 (max chars without '.'), rounded down if applicable
|
||||
client3 := NewDNSClient(parent3, opts)
|
||||
if client3.subdataSpace != 210 {
|
||||
if client3.subdataSpace != 209 {
|
||||
t.Fatalf("Unexpected subdata space for parent %s: %d", parent3, client3.subdataSpace)
|
||||
}
|
||||
|
||||
// "maxParent" (154 chars parent, 100 chars subdata)
|
||||
// Grand Total: 254 chars
|
||||
// parent | subdata with '.' | subdata without '.'
|
||||
// 254 - 154 - [64 - 36] = 63 + 35 (98)
|
||||
// parent | subdata with '.' | subdata without '.'
|
||||
// 254 - 154 - [64 - 36] - 1 = 63 + 35 (97)
|
||||
// expected value is thus 98 (max chars without '.'), rounded down if applicable
|
||||
clientMax := NewDNSClient(parentMax, opts)
|
||||
if clientMax.subdataSpace != 98 {
|
||||
if clientMax.subdataSpace != 97 {
|
||||
t.Fatalf("Unexpected subdata space for parent %s: %d", parentMax, clientMax.subdataSpace)
|
||||
}
|
||||
}
|
||||
|
||||
+39
-37
@@ -19,12 +19,12 @@ package c2
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
We've put a little effort to making the server at least not super easily fingerprintable,
|
||||
We've put a little effort to making the server at least not super easily finger printable,
|
||||
though I'm guessing it's also still not super hard to do. The server must receive a valid
|
||||
TOTP code before we start returning any non-error records. All requests must be formatted
|
||||
as valid protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a
|
||||
8 bit "message ID." The server only responds to non-TOTP queries with valid dns session IDs
|
||||
16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
16,777,216 can probably be brute-forced but it'll at least be slow.
|
||||
|
||||
DNS command and control outline:
|
||||
1. Implant sends TOTP encoded message to DNS server, server checks validity
|
||||
@@ -45,7 +45,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/dnspb"
|
||||
@@ -87,7 +86,6 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
|
||||
messages: &sync.Map{}, // In progress message streams
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: enforceOTP,
|
||||
}
|
||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||
@@ -317,7 +315,6 @@ type SliverDNSServer struct {
|
||||
messages *sync.Map
|
||||
TTL uint32
|
||||
MaxTXTLength int
|
||||
EnforceOTP bool
|
||||
}
|
||||
|
||||
// Shutdown - Shutdown the DNS server
|
||||
@@ -387,7 +384,7 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
|
||||
// TOTP Handler can be called without dns session ID
|
||||
if msg.Type == dnspb.DNSMessageType_TOTP {
|
||||
return s.handleHello(domain, msg, req)
|
||||
return s.handleHello(msg, req)
|
||||
}
|
||||
|
||||
// All other handlers require a valid dns session ID
|
||||
@@ -402,15 +399,15 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
case dnspb.DNSMessageType_NOP:
|
||||
return s.handleNOP(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_INIT:
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
return s.handleDNSSessionInit(msg, req)
|
||||
case dnspb.DNSMessageType_POLL:
|
||||
return s.handlePoll(domain, msg, checksum, req)
|
||||
return s.handlePoll(msg, req)
|
||||
case dnspb.DNSMessageType_DATA_FROM_IMPLANT:
|
||||
return s.handleDataFromImplant(domain, msg, checksum, req)
|
||||
return s.handleDataFromImplant(msg, checksum, req)
|
||||
case dnspb.DNSMessageType_DATA_TO_IMPLANT:
|
||||
return s.handleDataToImplant(domain, msg, checksum, req)
|
||||
return s.handleDataToImplant(msg, req)
|
||||
case dnspb.DNSMessageType_CLEAR:
|
||||
return s.handleClear(domain, msg, checksum, req)
|
||||
return s.handleClear(msg, checksum, req)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -420,31 +417,36 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
// of the plaintext data (that would be very bad).
|
||||
func (s *SliverDNSServer) decodeSubdata(subdomain string) (*dnspb.DNSMessage, uint32, error) {
|
||||
subdata := strings.Join(strings.Split(subdomain, "."), "")
|
||||
dnsLog.Debugf("subdata = %s", subdata)
|
||||
encoders := s.determineLikelyEncoders(subdata)
|
||||
for _, encoder := range encoders {
|
||||
data, err := encoder.Decode([]byte(subdata))
|
||||
if err == nil {
|
||||
msg := &dnspb.DNSMessage{}
|
||||
err = proto.Unmarshal(data, msg)
|
||||
if err == nil {
|
||||
return msg, crc32.ChecksumIEEE(data), nil
|
||||
}
|
||||
}
|
||||
dnsLog.Debugf("failed to decode subdata with %#v (%s)", encoder, err)
|
||||
if len(subdata) < 2 {
|
||||
return nil, 0, ErrInvalidMsg
|
||||
}
|
||||
dnsLog.Debugf("subdata = %s", subdata)
|
||||
encoder := determineEncoder(subdata[0])
|
||||
data, err := encoder.Decode([]byte(subdata[1:]))
|
||||
if err == nil {
|
||||
msg := &dnspb.DNSMessage{}
|
||||
err = proto.Unmarshal(data, msg)
|
||||
if err == nil {
|
||||
return msg, crc32.ChecksumIEEE(data), nil
|
||||
}
|
||||
}
|
||||
dnsLog.Debugf("failed to decode subdata with %#v (%s)", encoder, err)
|
||||
|
||||
return nil, 0, ErrInvalidMsg
|
||||
}
|
||||
|
||||
// Returns the most likely -> least likely encoders, if decoding fails fallback to
|
||||
// the next encoder until we run out of options.
|
||||
func (s *SliverDNSServer) determineLikelyEncoders(subdata string) []encoders.Encoder {
|
||||
for _, char := range subdata {
|
||||
if unicode.IsUpper(char) {
|
||||
return []encoders.Encoder{encoders.Base58{}, encoders.Base32{}}
|
||||
}
|
||||
func determineEncoder(firstChar byte) encoders.Encoder {
|
||||
index := strings.Index("abcdefghijklmnopqrstuvwxyz", strings.ToLower(string(firstChar)))
|
||||
if index == -1 {
|
||||
return encoders.Base32{}
|
||||
}
|
||||
return []encoders.Encoder{encoders.Base32{}, encoders.Base58{}}
|
||||
// if the index is odd, use base58, otherwise use base32
|
||||
if index&1 == 1 {
|
||||
return encoders.Base58{}
|
||||
}
|
||||
return encoders.Base32{}
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) nameErrorResp(req *dns.Msg) *dns.Msg {
|
||||
@@ -464,7 +466,7 @@ func (s *SliverDNSServer) refusedErrorResp(req *dns.Msg) *dns.Msg {
|
||||
// ---------------------------
|
||||
// DNS Message Handlers
|
||||
// ---------------------------
|
||||
func (s *SliverDNSServer) handleHello(domain string, msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
func (s *SliverDNSServer) handleHello(msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[dns] totp request: %v", msg)
|
||||
|
||||
dnsSessionID := dnsSessionID()
|
||||
@@ -498,7 +500,7 @@ func (s *SliverDNSServer) handleHello(domain string, msg *dnspb.DNSMessage, req
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
func (s *SliverDNSServer) handleDNSSessionInit(msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[session init] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
@@ -591,7 +593,7 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
func (s *SliverDNSServer) handlePoll(msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[poll] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
@@ -621,7 +623,7 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
|
||||
}
|
||||
}
|
||||
|
||||
respData := []byte{}
|
||||
var respData []byte
|
||||
dnsLog.Debugf("[poll] manifest %d (%d bytes)", msgID, msgLen)
|
||||
respData, _ = proto.Marshal(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_MANIFEST,
|
||||
@@ -672,7 +674,7 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
func (s *SliverDNSServer) handleDataFromImplant(msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[from implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
@@ -702,7 +704,7 @@ func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMes
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleDataToImplant(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
func (s *SliverDNSServer) handleDataToImplant(msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[to implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
@@ -761,7 +763,7 @@ func (s *SliverDNSServer) handleDataToImplant(domain string, msg *dnspb.DNSMessa
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleClear(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
func (s *SliverDNSServer) handleClear(msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[clear] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
@@ -802,7 +804,7 @@ func (s *SliverDNSServer) handleClear(domain string, msg *dnspb.DNSMessage, chec
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleNOP(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
func (s *SliverDNSServer) handleNOP(_ string, _ *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[nop] request checksum: %d", checksum)
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
|
||||
+76
-10
@@ -103,7 +103,7 @@ func TestPendingEnvelopes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func reassemble(t *testing.T, parent string, size int, encoder encoders.Encoder) {
|
||||
func reassemble(t *testing.T, _ string, size int, encoder encoders.Encoder) {
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
dnsMsgs, original := randomDNSMsgs(t, example1, size, encoder, client)
|
||||
dnsSession := DNSSession{
|
||||
@@ -162,14 +162,80 @@ func TestIsC2Domain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineLikelyEncoders(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
sample := randomDataRandomSize(2048)
|
||||
encodedSample, _ := encoders.Base58{}.Encode(sample)
|
||||
b58Sample := string(encodedSample)
|
||||
likelyEncoders := listener.determineLikelyEncoders(b58Sample)
|
||||
_, err := likelyEncoders[0].Decode([]byte(b58Sample))
|
||||
if err != nil {
|
||||
t.Error("DetermineLikelyEncoders failed to decode sample")
|
||||
func TestDetermineEncoder1(t *testing.T) {
|
||||
encoder := determineEncoder("a"[0])
|
||||
|
||||
// check which encoder type the encoder is
|
||||
if encoder == nil {
|
||||
t.Fatal("Expected encoder to not be nil")
|
||||
}
|
||||
|
||||
// check if the encoder is the correct type
|
||||
if _, ok := encoder.(encoders.Base32); !ok {
|
||||
t.Fatalf("Expected encoder to be of type Base32 got %T", encoder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineEncoder2(t *testing.T) {
|
||||
encoder := determineEncoder("b"[0])
|
||||
|
||||
// check which encoder type the encoder is
|
||||
if encoder == nil {
|
||||
t.Fatal("Expected encoder to not be nil")
|
||||
}
|
||||
|
||||
// check if the encoder is the correct type
|
||||
if _, ok := encoder.(encoders.Base58); !ok {
|
||||
t.Fatalf("Expected encoder to be of type Base58 got %T", encoder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineEncoder3(t *testing.T) {
|
||||
encoder := determineEncoder("1"[0])
|
||||
|
||||
// check which encoder type the encoder is
|
||||
if encoder == nil {
|
||||
t.Fatal("Expected encoder to not be nil")
|
||||
}
|
||||
|
||||
// check if the encoder is the correct type
|
||||
if _, ok := encoder.(encoders.Base32); !ok {
|
||||
t.Fatalf("Expected encoder to be of type Base32 got %T", encoder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineEncoder4(t *testing.T) {
|
||||
alpha := "abcdefghijklmnopqrstuvwxyz"
|
||||
for i := 0; i < 100; i++ {
|
||||
index := insecureRand.Intn(len(alpha))
|
||||
if index&1 == 1 {
|
||||
index = index - 1
|
||||
}
|
||||
encoder := determineEncoder(alpha[index])
|
||||
|
||||
// check if the encoder is the correct type
|
||||
if _, ok := encoder.(encoders.Base32); !ok {
|
||||
t.Fatalf("Expected encoder to be of type Base32 got %T", encoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineEncoder5(t *testing.T) {
|
||||
alpha := "abcdefghijklmnopqrstuvwxyz"
|
||||
for i := 0; i < 100; i++ {
|
||||
index := insecureRand.Intn(len(alpha))
|
||||
if index&1 != 1 {
|
||||
index = index + 1
|
||||
if len(alpha) <= index {
|
||||
index = index - 2
|
||||
}
|
||||
}
|
||||
|
||||
encoder := determineEncoder(alpha[index])
|
||||
|
||||
// check if the encoder is the correct type
|
||||
if _, ok := encoder.(encoders.Base58); !ok {
|
||||
t.Fatalf("Expected encoder to be of type Base58 got %T", encoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,7 +525,6 @@ func (s *SliverHTTPC2) mainHandler(resp http.ResponseWriter, req *http.Request)
|
||||
// redirect to default page
|
||||
httpLog.Debugf("No pattern matches for request uri")
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
|
||||
+9
-10
@@ -29,7 +29,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
@@ -52,9 +51,9 @@ func StartMTLSListenerJob(mtlsListener *clientpb.MTLSListenerReq) (*core.Job, er
|
||||
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
Name: constants.MtlsStr,
|
||||
Name: consts.MtlsStr,
|
||||
Description: fmt.Sprintf("mutual tls listener %s", bind),
|
||||
Protocol: constants.TCPListenerStr,
|
||||
Protocol: consts.TCPListenerStr,
|
||||
Port: uint16(mtlsListener.Port),
|
||||
JobCtrl: make(chan bool),
|
||||
}
|
||||
@@ -79,9 +78,9 @@ func StartWGListenerJob(wgListener *clientpb.WGListenerReq) (*core.Job, error) {
|
||||
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
Name: constants.WGStr,
|
||||
Name: consts.WGStr,
|
||||
Description: fmt.Sprintf("wg listener port: %d", wgListener.Port),
|
||||
Protocol: constants.UDPListenerStr,
|
||||
Protocol: consts.UDPListenerStr,
|
||||
Port: uint16(wgListener.Port),
|
||||
JobCtrl: make(chan bool),
|
||||
}
|
||||
@@ -132,9 +131,9 @@ func StartDNSListenerJob(dnsListener *clientpb.DNSListenerReq) (*core.Job, error
|
||||
description := fmt.Sprintf("%s (canaries %v)", strings.Join(dnsListener.Domains, " "), dnsListener.Canaries)
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
Name: constants.DnsStr,
|
||||
Name: consts.DnsStr,
|
||||
Description: description,
|
||||
Protocol: constants.UDPListenerStr,
|
||||
Protocol: consts.UDPListenerStr,
|
||||
Port: uint16(dnsListener.Port),
|
||||
JobCtrl: make(chan bool),
|
||||
Domains: dnsListener.Domains,
|
||||
@@ -175,16 +174,16 @@ func StartHTTPListenerJob(req *clientpb.HTTPListenerReq) (*core.Job, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := constants.HttpStr
|
||||
name := consts.HttpStr
|
||||
if req.Secure {
|
||||
name = constants.HttpsStr
|
||||
name = consts.HttpsStr
|
||||
}
|
||||
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
Name: name,
|
||||
Description: fmt.Sprintf("%s for domain %s", name, req.Domain),
|
||||
Protocol: constants.TCPListenerStr,
|
||||
Protocol: consts.TCPListenerStr,
|
||||
Port: uint16(req.Port),
|
||||
JobCtrl: make(chan bool),
|
||||
Domains: []string{req.Domain},
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ func socketReadEnvelope(connection net.Conn) (*sliverpb.Envelope, error) {
|
||||
}
|
||||
|
||||
// getServerTLSConfig - Generate the TLS configuration, we do now allow the end user
|
||||
// to specify any TLS paramters, we choose sensible defaults instead
|
||||
// to specify any TLS parameters, we choose sensible defaults instead
|
||||
func getServerTLSConfig(host string) *tls.Config {
|
||||
|
||||
mtlsCACert, _, err := certs.GetCertificateAuthority(certs.MtlsImplantCA)
|
||||
|
||||
+58
-4
@@ -436,20 +436,74 @@ func SaveHTTPC2Config(httpC2Config *clientpb.HTTPC2Config) error {
|
||||
func HTTPC2ConfigUpdate(newConf *clientpb.HTTPC2Config, oldConf *clientpb.HTTPC2Config) error {
|
||||
clientID, _ := uuid.FromString(oldConf.ImplantConfig.ID)
|
||||
c2Config := models.HTTPC2ConfigFromProtobuf(newConf)
|
||||
err := Session().Where(&models.ImplantConfig{
|
||||
|
||||
err := Session().Where(&models.HttpC2PathSegment{
|
||||
HttpC2ImplantConfigID: clientID,
|
||||
}).Delete(&models.HttpC2PathSegment{})
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
err = Session().Where(&models.ImplantConfig{
|
||||
ID: clientID,
|
||||
}).Updates(c2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
for _, segment := range c2Config.ImplantConfig.PathSegments {
|
||||
segment.HttpC2ImplantConfigID = clientID
|
||||
err = Session().Clauses(clause.OnConflict{
|
||||
UpdateAll: true,
|
||||
}).Create(&segment)
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
}
|
||||
|
||||
serverID, _ := uuid.FromString(oldConf.ServerConfig.ID)
|
||||
|
||||
err = Session().Where(&models.HttpC2Cookie{
|
||||
HttpC2ServerConfigID: serverID,
|
||||
}).Delete(&models.HttpC2Cookie{})
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
err = Session().Where(&models.HttpC2Header{
|
||||
HttpC2ServerConfigID: serverID,
|
||||
}).Delete(&models.HttpC2Header{})
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
serverID, _ := uuid.FromString(oldConf.ImplantConfig.ID)
|
||||
err = Session().Where(&models.HttpC2ServerConfig{
|
||||
ID: serverID,
|
||||
}).Updates(c2Config.ServerConfig)
|
||||
if err != nil {
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
for _, cookie := range c2Config.ServerConfig.Cookies {
|
||||
cookie.HttpC2ServerConfigID = serverID
|
||||
err = Session().Clauses(clause.OnConflict{
|
||||
UpdateAll: true,
|
||||
}).Create(&cookie)
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
}
|
||||
|
||||
for _, header := range c2Config.ServerConfig.Headers {
|
||||
header.HttpC2ServerConfigID = serverID
|
||||
err = Session().Clauses(clause.OnConflict{
|
||||
UpdateAll: true,
|
||||
}).Create(&header)
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package encoders
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Base32 Encoder
|
||||
@@ -35,5 +36,5 @@ func (e Base32) Encode(data []byte) ([]byte, error) {
|
||||
|
||||
// Decode - Base32 Decode
|
||||
func (e Base32) Decode(data []byte) ([]byte, error) {
|
||||
return sliverBase32.DecodeString(string(data))
|
||||
return sliverBase32.DecodeString(strings.ToLower(string(data)))
|
||||
}
|
||||
Reference in new issue
Block a user