mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4c6221c3c | ||
|
|
c3108c4e70 |
No files matched your search
@@ -176,6 +176,27 @@ type SliverHTTPClient struct {
|
||||
Options *HTTPOptions
|
||||
}
|
||||
|
||||
// NewSliverHTTPClient constructs a client using a provided driver.
|
||||
// Primarily intended for tests or custom transport wiring.
|
||||
func NewSliverHTTPClient(origin string, driver HTTPDriver, opts *HTTPOptions) *SliverHTTPClient {
|
||||
if opts == nil {
|
||||
opts = &HTTPOptions{
|
||||
Driver: goHTTPDriver,
|
||||
NetTimeout: 30 * time.Second,
|
||||
TlsTimeout: 30 * time.Second,
|
||||
PollTimeout: 30 * time.Second,
|
||||
MaxErrors: 10,
|
||||
}
|
||||
}
|
||||
return &SliverHTTPClient{
|
||||
Origin: origin,
|
||||
driver: driver,
|
||||
pollMutex: &sync.Mutex{},
|
||||
Closed: false,
|
||||
Options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
// SessionInit - Initialize the session
|
||||
func (s *SliverHTTPClient) SessionInit() error {
|
||||
sKey := cryptography.RandomSymmetricKey()
|
||||
@@ -621,11 +642,29 @@ func (s *SliverHTTPClient) parseSegments() *url.URL {
|
||||
extensions = append(extensions, "{{$extension}}")
|
||||
// {{end}}
|
||||
|
||||
files = filterTemplatePlaceholders(files)
|
||||
paths = filterTemplatePlaceholders(paths)
|
||||
extensions = filterTemplatePlaceholders(extensions)
|
||||
|
||||
curl.Path = s.pathJoinURL(s.randomPath(paths, files, extensions))
|
||||
|
||||
return curl
|
||||
}
|
||||
|
||||
func filterTemplatePlaceholders(values []string) []string {
|
||||
if len(values) == 0 {
|
||||
return values
|
||||
}
|
||||
filtered := values[:0]
|
||||
for _, value := range values {
|
||||
if strings.Contains(value, "{{") {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) startSessionURL() *url.URL {
|
||||
curl := s.parseSegments()
|
||||
return curl
|
||||
@@ -643,6 +682,12 @@ func (s *SliverHTTPClient) randomPath(segments []string, filenames []string, ext
|
||||
genSegments = append(genSegments, seg)
|
||||
}
|
||||
}
|
||||
if len(filenames) == 0 {
|
||||
filenames = []string{"index"}
|
||||
}
|
||||
if len(exts) == 0 {
|
||||
exts = []string{""}
|
||||
}
|
||||
filename := filenames[util.Intn(len(filenames))]
|
||||
ext := exts[util.Intn(len(exts))]
|
||||
|
||||
@@ -651,6 +696,8 @@ func (s *SliverHTTPClient) randomPath(segments []string, filenames []string, ext
|
||||
// {{end}}
|
||||
if ext != "" {
|
||||
genSegments = append(genSegments, fmt.Sprintf("%s.%s", filename, ext))
|
||||
} else {
|
||||
genSegments = append(genSegments, filename)
|
||||
}
|
||||
return genSegments
|
||||
}
|
||||
|
||||
@@ -52,6 +52,14 @@ var (
|
||||
certPEM = `{{.Build.MtlsCert}}`
|
||||
)
|
||||
|
||||
// SetTestCertificates allows tests to override the embedded mTLS materials.
|
||||
// This should only be used in test harnesses.
|
||||
func SetTestCertificates(caPEM, certPEMIn, keyPEMIn string) {
|
||||
caCertPEM = caPEM
|
||||
certPEM = certPEMIn
|
||||
keyPEM = keyPEMIn
|
||||
}
|
||||
|
||||
// WriteEnvelope - Writes a message to the TLS socket using length prefix framing
|
||||
// which is a fancy way of saying we write the length of the message then the message
|
||||
// e.g. [uint32 length|message] so the receiver can delimit messages properly
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
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 (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
)
|
||||
|
||||
var (
|
||||
serverAgeKeyPair *cryptography.AgeKeyPair
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
implantConfig := setup()
|
||||
code1 := m.Run()
|
||||
cleanup(implantConfig)
|
||||
os.Exit(code1)
|
||||
}
|
||||
|
||||
func setup() *models.ImplantConfig {
|
||||
var err error
|
||||
certs.SetupCAs()
|
||||
serverAgeKeyPair = cryptography.AgeServerKeyPair()
|
||||
peerAgeKeyPair, _ := cryptography.RandomAgeKeyPair()
|
||||
implantCrypto.SetSecrets(
|
||||
peerAgeKeyPair.Public,
|
||||
peerAgeKeyPair.Private,
|
||||
"",
|
||||
serverAgeKeyPair.Public,
|
||||
cryptography.MinisignServerPublicKey(),
|
||||
)
|
||||
|
||||
digest := sha256.New()
|
||||
digest.Write([]byte(peerAgeKeyPair.Public))
|
||||
publicKeyDigest := hex.EncodeToString(digest.Sum(nil))
|
||||
|
||||
implantBuild := &models.ImplantBuild{
|
||||
PeerPublicKey: peerAgeKeyPair.Public,
|
||||
PeerPublicKeyDigest: publicKeyDigest,
|
||||
PeerPrivateKey: peerAgeKeyPair.Private,
|
||||
|
||||
AgeServerPublicKey: serverAgeKeyPair.Public,
|
||||
}
|
||||
err = db.Session().Create(implantBuild).Error
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
implantConfig := &models.ImplantConfig{
|
||||
ImplantBuilds: []models.ImplantBuild{*implantBuild},
|
||||
}
|
||||
return implantConfig
|
||||
}
|
||||
|
||||
func cleanup(implantConfig *models.ImplantConfig) {
|
||||
db.Session().Delete(implantConfig)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !windows
|
||||
|
||||
package c2_test
|
||||
|
||||
import implantHandlers "github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
|
||||
func dispatchHandler(handler implantHandlers.RPCHandler, data []byte, resp implantHandlers.RPCResponse) {
|
||||
handler(data, resp)
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package c2_test
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2025 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 (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
)
|
||||
|
||||
var (
|
||||
serverAgeKeyPair *cryptography.AgeKeyPair
|
||||
peerAgeKeyPair *cryptography.AgeKeyPair
|
||||
|
||||
testImplantConfig *models.ImplantConfig
|
||||
testImplantBuild *models.ImplantBuild
|
||||
|
||||
mtlsCACertPEM string
|
||||
mtlsCertPEM string
|
||||
mtlsKeyPEM string
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHTTPC2ConfigName = "default"
|
||||
testImplantCertName = "test-implant"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setup()
|
||||
code := m.Run()
|
||||
cleanup()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func setup() {
|
||||
var err error
|
||||
certs.SetupCAs()
|
||||
serverAgeKeyPair = cryptography.AgeServerKeyPair()
|
||||
peerAgeKeyPair, err = cryptography.RandomAgeKeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
implantCrypto.SetSecrets(
|
||||
peerAgeKeyPair.Public,
|
||||
peerAgeKeyPair.Private,
|
||||
cryptography.MinisignServerSign([]byte(peerAgeKeyPair.Public)),
|
||||
serverAgeKeyPair.Public,
|
||||
cryptography.MinisignServerPublicKey(),
|
||||
)
|
||||
|
||||
httpConfig, err := db.LoadHTTPC2ConfigByName(defaultHTTPC2ConfigName)
|
||||
if err != nil && !errors.Is(err, db.ErrRecordNotFound) {
|
||||
panic(err)
|
||||
}
|
||||
if httpConfig == nil || httpConfig.ServerConfig == nil || len(httpConfig.ServerConfig.Cookies) == 0 {
|
||||
defaultConfig := configs.GenerateDefaultHTTPC2Config()
|
||||
if err := db.SaveHTTPC2Config(defaultConfig); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
caPEM, _, err := certs.GetCertificateAuthorityPEM(certs.MtlsServerCA)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mtlsCACertPEM = string(caPEM)
|
||||
|
||||
_, _, err = certs.GetECCCertificate(certs.MtlsServerCA, "localhost")
|
||||
if errors.Is(err, certs.ErrCertDoesNotExist) {
|
||||
_, _, err = certs.MtlsC2ServerGenerateECCCertificate("localhost")
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
certPEM, keyPEM, err := certs.GetECCCertificate(certs.MtlsImplantCA, testImplantCertName)
|
||||
if errors.Is(err, certs.ErrCertDoesNotExist) {
|
||||
certPEM, keyPEM, err = certs.MtlsC2ImplantGenerateECCCertificate(testImplantCertName)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mtlsCertPEM = string(certPEM)
|
||||
mtlsKeyPEM = string(keyPEM)
|
||||
|
||||
testImplantConfig = &models.ImplantConfig{
|
||||
GOOS: runtime.GOOS,
|
||||
GOARCH: runtime.GOARCH,
|
||||
IncludeHTTP: true,
|
||||
IncludeMTLS: true,
|
||||
HttpC2ConfigName: defaultHTTPC2ConfigName,
|
||||
ConnectionStrategy: "s",
|
||||
}
|
||||
if err := db.Session().Create(testImplantConfig).Error; err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
digest := sha256.Sum256([]byte(peerAgeKeyPair.Public))
|
||||
testImplantBuild = &models.ImplantBuild{
|
||||
Name: "e2e-test-" + time.Now().Format("20060102150405.000000000"),
|
||||
ImplantConfigID: testImplantConfig.ID,
|
||||
PeerPublicKey: peerAgeKeyPair.Public,
|
||||
PeerPublicKeyDigest: hex.EncodeToString(digest[:]),
|
||||
PeerPrivateKey: peerAgeKeyPair.Private,
|
||||
PeerPublicKeySignature: cryptography.MinisignServerSign([]byte(peerAgeKeyPair.Public)),
|
||||
AgeServerPublicKey: serverAgeKeyPair.Public,
|
||||
MinisignServerPublicKey: cryptography.MinisignServerPublicKey(),
|
||||
MtlsCACert: mtlsCACertPEM,
|
||||
MtlsCert: mtlsCertPEM,
|
||||
MtlsKey: mtlsKeyPEM,
|
||||
}
|
||||
if err := db.Session().Create(testImplantBuild).Error; err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
if testImplantBuild != nil {
|
||||
db.Session().Delete(testImplantBuild)
|
||||
}
|
||||
if testImplantConfig != nil {
|
||||
db.Session().Delete(testImplantConfig)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
package c2_test
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2025 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 (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
implantHandlers "github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
implantTransports "github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
implantHTTP "github.com/bishopfox/sliver/implant/sliver/transports/httpclient"
|
||||
implantMTLS "github.com/bishopfox/sliver/implant/sliver/transports/mtls"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
c2 "github.com/bishopfox/sliver/server/c2"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
serverTransport "github.com/bishopfox/sliver/server/transport"
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
sessionWaitTimeout = 10 * time.Second
|
||||
requestTimeout = 10 * time.Second
|
||||
|
||||
testMTLSServerHost = "localhost"
|
||||
testHTTPOrigin = "https://sliver.test"
|
||||
)
|
||||
|
||||
func TestE2EMTLSInfoAndLs(t *testing.T) {
|
||||
clearSessions()
|
||||
implantMTLS.SetTestCertificates(mtlsCACertPEM, mtlsCertPEM, mtlsKeyPEM)
|
||||
|
||||
serverTLS, clientTLS, cleanup := newMTLSConnPair(t)
|
||||
defer cleanup()
|
||||
|
||||
go c2.HandleSliverConnectionForTest(serverTLS)
|
||||
|
||||
register := newRegister(t, fmt.Sprintf("e2e-mtls-%d", time.Now().UnixNano()), "mtls://"+testMTLSServerHost)
|
||||
conn := newMTLSConnection(t, clientTLS)
|
||||
stopImplant := startImplantSession(t, conn, register)
|
||||
defer stopImplant()
|
||||
|
||||
session := waitForSession(t, register.Name)
|
||||
defer session.Connection.Cleanup()
|
||||
|
||||
rpcClient, rpcCleanup := newGRPCClient(t)
|
||||
defer rpcCleanup()
|
||||
|
||||
assertSessionInfo(t, session, register, consts.MtlsStr)
|
||||
assertLsRoundTrip(t, rpcClient, session.ID)
|
||||
}
|
||||
|
||||
func TestE2EHTTPSInfoAndLs(t *testing.T) {
|
||||
clearSessions()
|
||||
|
||||
server := newHTTPTestServer(t)
|
||||
conn := newHTTPConnection(t, server.HTTPServer.Handler, testHTTPOrigin)
|
||||
|
||||
register := newRegister(t, fmt.Sprintf("e2e-https-%d", time.Now().UnixNano()), testHTTPOrigin)
|
||||
stopImplant := startImplantSession(t, conn, register)
|
||||
defer stopImplant()
|
||||
|
||||
session := waitForSession(t, register.Name)
|
||||
defer session.Connection.Cleanup()
|
||||
|
||||
rpcClient, rpcCleanup := newGRPCClient(t)
|
||||
defer rpcCleanup()
|
||||
|
||||
assertSessionInfo(t, session, register, "http(s)")
|
||||
assertLsRoundTrip(t, rpcClient, session.ID)
|
||||
}
|
||||
|
||||
func newMTLSConnPair(t *testing.T) (*tls.Conn, *tls.Conn, func()) {
|
||||
t.Helper()
|
||||
serverConfig := c2.TestServerTLSConfig(testMTLSServerHost)
|
||||
if serverConfig == nil {
|
||||
t.Fatalf("mtls server tls config missing")
|
||||
}
|
||||
clientConfig := testMTLSClientConfig(t)
|
||||
|
||||
serverRaw, clientRaw := net.Pipe()
|
||||
serverTLS := tls.Server(serverRaw, serverConfig)
|
||||
clientTLS := tls.Client(clientRaw, clientConfig)
|
||||
|
||||
errCh := make(chan error, 2)
|
||||
go func() {
|
||||
errCh <- serverTLS.Handshake()
|
||||
}()
|
||||
go func() {
|
||||
errCh <- clientTLS.Handshake()
|
||||
}()
|
||||
for range 2 {
|
||||
if err := <-errCh; err != nil {
|
||||
serverTLS.Close()
|
||||
clientTLS.Close()
|
||||
t.Fatalf("mtls handshake failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
_ = serverTLS.Close()
|
||||
_ = clientTLS.Close()
|
||||
}
|
||||
return serverTLS, clientTLS, cleanup
|
||||
}
|
||||
|
||||
func testMTLSClientConfig(t *testing.T) *tls.Config {
|
||||
t.Helper()
|
||||
cert, err := tls.X509KeyPair([]byte(mtlsCertPEM), []byte(mtlsKeyPEM))
|
||||
if err != nil {
|
||||
t.Fatalf("mtls client cert parse: %v", err)
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: true,
|
||||
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return implantCrypto.RootOnlyVerifyCertificate(mtlsCACertPEM, rawCerts, verifiedChains)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newMTLSConnection(t *testing.T, conn *tls.Conn) *implantTransports.Connection {
|
||||
t.Helper()
|
||||
send := make(chan *sliverpb.Envelope)
|
||||
recv := make(chan *sliverpb.Envelope)
|
||||
done := make(chan struct{})
|
||||
|
||||
var once sync.Once
|
||||
var wg sync.WaitGroup
|
||||
|
||||
start := func() error {
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case envelope, ok := <-send:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := implantMTLS.WriteEnvelope(conn, envelope); err != nil {
|
||||
return
|
||||
}
|
||||
case <-time.After(implantMTLS.PingInterval):
|
||||
_ = implantMTLS.WritePing(conn)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
envelope, err := implantMTLS.ReadEnvelope(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if envelope == nil {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case recv <- envelope:
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
stop := func() error {
|
||||
once.Do(func() {
|
||||
close(done)
|
||||
close(send)
|
||||
_ = conn.Close()
|
||||
})
|
||||
wg.Wait()
|
||||
close(recv)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &implantTransports.Connection{
|
||||
Send: send,
|
||||
Recv: recv,
|
||||
Start: start,
|
||||
Stop: stop,
|
||||
}
|
||||
}
|
||||
|
||||
func newHTTPTestServer(t *testing.T) *c2.SliverHTTPC2 {
|
||||
t.Helper()
|
||||
req := &clientpb.HTTPListenerReq{
|
||||
Host: "sliver.test",
|
||||
Port: 443,
|
||||
Secure: true,
|
||||
Domain: "sliver.test",
|
||||
}
|
||||
server, err := c2.StartHTTPListener(req)
|
||||
if err != nil {
|
||||
t.Fatalf("start https listener: %v", err)
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
type inMemoryHTTPDriver struct {
|
||||
handler http.Handler
|
||||
jar http.CookieJar
|
||||
baseURL *url.URL
|
||||
remoteAddr string
|
||||
}
|
||||
|
||||
func newInMemoryHTTPDriver(t *testing.T, handler http.Handler, baseURL *url.URL) *inMemoryHTTPDriver {
|
||||
t.Helper()
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cookie jar: %v", err)
|
||||
}
|
||||
return &inMemoryHTTPDriver{
|
||||
handler: handler,
|
||||
jar: jar,
|
||||
baseURL: baseURL,
|
||||
remoteAddr: "127.0.0.1:12345",
|
||||
}
|
||||
}
|
||||
|
||||
func (d *inMemoryHTTPDriver) Do(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Scheme == "" {
|
||||
req.URL.Scheme = d.baseURL.Scheme
|
||||
}
|
||||
if req.URL.Host == "" {
|
||||
req.URL.Host = d.baseURL.Host
|
||||
}
|
||||
if req.Host == "" {
|
||||
req.Host = req.URL.Host
|
||||
}
|
||||
if req.RemoteAddr == "" {
|
||||
req.RemoteAddr = d.remoteAddr
|
||||
}
|
||||
if req.TLS == nil && req.URL.Scheme == "https" {
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
}
|
||||
if req.RequestURI == "" {
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
}
|
||||
|
||||
for _, cookie := range d.jar.Cookies(req.URL) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
d.handler.ServeHTTP(rr, req)
|
||||
resp := rr.Result()
|
||||
d.jar.SetCookies(req.URL, resp.Cookies())
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func newHTTPConnection(t *testing.T, handler http.Handler, origin string) *implantTransports.Connection {
|
||||
t.Helper()
|
||||
baseURL, err := url.Parse(origin)
|
||||
if err != nil {
|
||||
t.Fatalf("parse origin: %v", err)
|
||||
}
|
||||
|
||||
driver := newInMemoryHTTPDriver(t, handler, baseURL)
|
||||
opts := implantHTTP.ParseHTTPOptions(baseURL)
|
||||
opts.NetTimeout = 5 * time.Second
|
||||
opts.PollTimeout = 2 * time.Second
|
||||
opts.MaxErrors = 3
|
||||
|
||||
client := implantHTTP.NewSliverHTTPClient(origin, driver, opts)
|
||||
client.PathPrefix = baseURL.Path
|
||||
if err := client.SessionInit(); err != nil {
|
||||
t.Fatalf("http session init: %v", err)
|
||||
}
|
||||
|
||||
send := make(chan *sliverpb.Envelope)
|
||||
recv := make(chan *sliverpb.Envelope)
|
||||
done := make(chan struct{})
|
||||
|
||||
var once sync.Once
|
||||
var wg sync.WaitGroup
|
||||
|
||||
start := func() error {
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case envelope, ok := <-send:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := client.WriteEnvelope(envelope); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
envelope, err := client.ReadEnvelope()
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if envelope == nil {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case recv <- envelope:
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
stop := func() error {
|
||||
once.Do(func() {
|
||||
close(done)
|
||||
close(send)
|
||||
_ = client.CloseSession()
|
||||
})
|
||||
wg.Wait()
|
||||
close(recv)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &implantTransports.Connection{
|
||||
Send: send,
|
||||
Recv: recv,
|
||||
Start: start,
|
||||
Stop: stop,
|
||||
}
|
||||
}
|
||||
|
||||
func newGRPCClient(t *testing.T) (rpcpb.SliverRPCClient, func()) {
|
||||
t.Helper()
|
||||
grpcServer, ln, err := serverTransport.LocalListener()
|
||||
if err != nil {
|
||||
t.Fatalf("grpc listener: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
conn, err := grpc.DialContext(
|
||||
ctx,
|
||||
"bufnet",
|
||||
grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
|
||||
return ln.Dial()
|
||||
}),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
grpcServer.Stop()
|
||||
_ = ln.Close()
|
||||
t.Fatalf("grpc dial: %v", err)
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
_ = conn.Close()
|
||||
grpcServer.Stop()
|
||||
_ = ln.Close()
|
||||
}
|
||||
|
||||
return rpcpb.NewSliverRPCClient(conn), cleanup
|
||||
}
|
||||
|
||||
func startImplantSession(t *testing.T, conn *implantTransports.Connection, register *sliverpb.Register) func() {
|
||||
t.Helper()
|
||||
if err := conn.Start(); err != nil {
|
||||
t.Fatalf("start implant connection: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- runImplantSession(ctx, conn, register)
|
||||
}()
|
||||
|
||||
return func() {
|
||||
cancel()
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Fatalf("implant session error: %v", err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("implant session did not stop")
|
||||
}
|
||||
_ = conn.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func runImplantSession(ctx context.Context, conn *implantTransports.Connection, register *sliverpb.Register) error {
|
||||
if conn == nil {
|
||||
return fmt.Errorf("nil implant connection")
|
||||
}
|
||||
|
||||
regData, err := proto.Marshal(register)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Send <- &sliverpb.Envelope{Type: sliverpb.MsgRegister, Data: regData}
|
||||
|
||||
sysHandlers := implantHandlers.GetSystemHandlers()
|
||||
killHandlers := implantHandlers.GetKillHandlers()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case envelope, ok := <-conn.Recv:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if _, ok := killHandlers[envelope.Type]; ok {
|
||||
return nil
|
||||
}
|
||||
if handler, ok := sysHandlers[envelope.Type]; ok {
|
||||
envelopeID := envelope.ID
|
||||
dispatchHandler(handler, envelope.Data, func(data []byte, err error) {
|
||||
conn.Send <- &sliverpb.Envelope{ID: envelopeID, Data: data}
|
||||
})
|
||||
continue
|
||||
}
|
||||
conn.Send <- &sliverpb.Envelope{ID: envelope.ID, UnknownMessageType: true}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRegister(t *testing.T, name string, activeC2 string) *sliverpb.Register {
|
||||
t.Helper()
|
||||
host, _ := os.Hostname()
|
||||
id, _ := uuid.NewV4()
|
||||
return &sliverpb.Register{
|
||||
Name: name,
|
||||
Hostname: host,
|
||||
Uuid: id.String(),
|
||||
Username: "sliver-test",
|
||||
Uid: "1000",
|
||||
Gid: "1000",
|
||||
Os: runtime.GOOS,
|
||||
Version: "test",
|
||||
Arch: runtime.GOARCH,
|
||||
Pid: int32(os.Getpid()),
|
||||
Filename: "sliver-test",
|
||||
ActiveC2: activeC2,
|
||||
ReconnectInterval: int64(time.Second),
|
||||
Locale: "en-US",
|
||||
}
|
||||
}
|
||||
|
||||
func waitForSession(t *testing.T, name string) *core.Session {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(sessionWaitTimeout)
|
||||
for time.Now().Before(deadline) {
|
||||
for _, session := range core.Sessions.All() {
|
||||
if session.Name == name {
|
||||
return session
|
||||
}
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("session %q not registered", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearSessions() {
|
||||
for _, session := range core.Sessions.All() {
|
||||
core.Sessions.Remove(session.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func assertSessionInfo(t *testing.T, session *core.Session, register *sliverpb.Register, transport string) {
|
||||
t.Helper()
|
||||
if session.Name != register.Name {
|
||||
t.Fatalf("session name mismatch: got %q want %q", session.Name, register.Name)
|
||||
}
|
||||
if session.Hostname != register.Hostname {
|
||||
t.Fatalf("session hostname mismatch: got %q want %q", session.Hostname, register.Hostname)
|
||||
}
|
||||
if session.Username != register.Username {
|
||||
t.Fatalf("session username mismatch: got %q want %q", session.Username, register.Username)
|
||||
}
|
||||
if session.OS != register.Os {
|
||||
t.Fatalf("session os mismatch: got %q want %q", session.OS, register.Os)
|
||||
}
|
||||
if session.Arch != register.Arch {
|
||||
t.Fatalf("session arch mismatch: got %q want %q", session.Arch, register.Arch)
|
||||
}
|
||||
if session.ActiveC2 != register.ActiveC2 {
|
||||
t.Fatalf("session active c2 mismatch: got %q want %q", session.ActiveC2, register.ActiveC2)
|
||||
}
|
||||
if session.Connection == nil {
|
||||
t.Fatalf("session connection missing")
|
||||
}
|
||||
if session.Connection.Transport != transport {
|
||||
t.Fatalf("session transport mismatch: got %q want %q", session.Connection.Transport, transport)
|
||||
}
|
||||
if session.Connection.RemoteAddress == "" {
|
||||
t.Fatalf("session remote address not set")
|
||||
}
|
||||
}
|
||||
|
||||
func assertLsRoundTrip(t *testing.T, rpcClient rpcpb.SliverRPCClient, sessionID string) {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
fileName := "sliver-test.txt"
|
||||
filePath := filepath.Join(tmpDir, fileName)
|
||||
if err := os.WriteFile(filePath, []byte("hello"), 0600); err != nil {
|
||||
t.Fatalf("write temp file: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
defer cancel()
|
||||
|
||||
resp, err := rpcClient.Ls(ctx, &sliverpb.LsReq{
|
||||
Path: tmpDir,
|
||||
Request: &commonpb.Request{
|
||||
SessionID: sessionID,
|
||||
Timeout: int64(requestTimeout),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ls request failed: %v", err)
|
||||
}
|
||||
if !resp.Exists {
|
||||
t.Fatalf("ls response marked as non-existent: %v", resp.GetResponse())
|
||||
}
|
||||
for _, entry := range resp.Files {
|
||||
if entry.Name == fileName {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("ls response missing %q", fileName)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build windows
|
||||
|
||||
package c2_test
|
||||
|
||||
import implantHandlers "github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
|
||||
func dispatchHandler(handler implantHandlers.RPCHandler, data []byte, resp implantHandlers.RPCResponse) {
|
||||
implantHandlers.WrapperHandler(handler, data, resp)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package c2
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2025 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 (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
// TestServerTLSConfig exposes the server TLS config for tests.
|
||||
func TestServerTLSConfig(host string) *tls.Config {
|
||||
return getServerTLSConfig(host)
|
||||
}
|
||||
|
||||
// HandleSliverConnectionForTest exposes the mTLS connection handler for tests.
|
||||
func HandleSliverConnectionForTest(conn net.Conn) {
|
||||
handleSliverConnection(conn)
|
||||
}
|
||||
Reference in new issue
Block a user