引言
- 项目概述:对开源的C2框架sliver进行源码分析,意图学习其原理。本篇分析sliver的入口以及脚手架,和基本的配置文件
- 目标与读者:网络安全兴趣爱好者
准备工作
- 源码路径BishopFox/sliver: Adversary Emulation Framework (github.com)
git clone https://github.com/BishopFox/sliver.git
- go语言环境
Download and install - The Go Programming Language - vscode
Download Visual Studio Code - Mac, Linux, Windows
入口点
由于sliver是CS架构的系统,而且主要功能在服务端所以分析目标是sliver-server
这里查看到入口点的内容只有运行cli.Execute()
server/main.go
import ("github.com/bishopfox/sliver/server/cli"
)func main() {cli.Execute()
}
server/cli/cli.go
// Execute - Execute root command
func Execute() {if err := rootCmd.Execute(); err != nil {fmt.Println(err)os.Exit(1)}
}
这里的cli.Execute()运行的就是rootCmd.Execute(),所以要重点关注rootCmd
跳转到github.com/bishopfox/sliver/server/cli,发现其使用的脚手架框架是github.com/spf13/cobra
如果对cobra不太熟悉,可以看看这个UP做的视频
https://www.bilibili.com/video/BV1ka4y177iK
Cobra 是由 Go 团队成员 spf13 为 Hugo 项目创建的,并已被许多流行的 Go 项目所采用,如 Kubernetes、Helm、Docker (distribution)、Etcd 等。
简而言之就是可以方便的编写带有参数的命令行程序。
rootCmd
这里摆上server/cli/cli.go的部分源码
var rootCmd = &cobra.Command{Use: "sliver-server",Short: "",Long: ``,Run: func(cmd *cobra.Command, args []string) {// Root command starts the server normallyappDir := assets.GetRootAppDir() //makedir $HOME/.sliverlogFile := initConsoleLogging(appDir)defer logFile.Close()defer func() {if r := recover(); r != nil {log.Printf("panic:\n%s", debug.Stack())fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))os.Exit(99)}}()assets.Setup(false, true)certs.SetupCAs()certs.SetupWGKeys()cryptography.AgeServerKeyPair()cryptography.MinisignServerPrivateKey()c2.SetupDefaultC2Profiles()serverConfig := configs.GetServerConfig()listenerJobs, err := db.ListenerJobs()if err != nil {fmt.Println(err)}err = StartPersistentJobs(listenerJobs)if err != nil {fmt.Println(err)}if serverConfig.DaemonMode {daemon.Start(daemon.BlankHost, daemon.BlankPort, serverConfig.DaemonConfig.Tailscale)} else {os.Args = os.Args[:1] // Hide cli from grumble consoleconsole.Start()}},
}
由于这个是rootCmd,所以其中Use: “sliver-server"表示这个命令本身。cobra在-h等参数中会告诉这个命令是什么命令,这里就是指的是"sliver-server”。
└─$ sliver-server -h
Usage://Use: "sliver-server" 指的就这下面的例子,用于提示这个命令是什么sliver-server [flags] sliver-server [command]Available Commands:builder Start the process as an external buildercompletion Generate the autocompletion script for the specified shelldaemon Force start server in daemon modeexport-ca Export certificate authorityhelp Help about any commandimport-ca Import certificate authorityoperator Generate operator configuration filesunpack Unpack assets and exitversion Print version and exitFlags:-h, --help help for sliver-serverUse "sliver-server [command] --help" for more information about a command.
Run: func(cmd *cobra.Command, args []string)
是这个命令(sliver-server)需要运行的内容
首先执行appDir := assets.GetRootAppDir()
// GetRootAppDir - Get the Sliver app dir, default is: ~/.sliver/
func GetRootAppDir() string {value := os.Getenv(envVarName)var dir stringif len(value) == 0 {user, _ := user.Current()dir = filepath.Join(user.HomeDir, ".sliver")} else {dir = value}if _, err := os.Stat(dir); os.IsNotExist(err) {err = os.MkdirAll(dir, 0700)if err != nil {setupLog.Fatalf("Cannot write to sliver root dir %s", err)}}return dir
}
可以从上面的注释看到这个就是在当前用户的home目录下创建.sliver
目录
然后再执行logFile := initConsoleLogging(appDir)
// Initialize logging
func initConsoleLogging(appDir string) *os.File {log.SetFlags(log.LstdFlags | log.Lshortfile)logFile, err := os.OpenFile(filepath.Join(appDir, "logs", logFileName), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600)if err != nil {log.Fatalf("Error opening file: %v", err)}log.SetOutput(logFile)return logFile
}
其中logFileName = "console.log" 所以该函数就是创建
~/.sliver/logs/console.log`, 并返回这个文件到变量logFile
接着执行下面的两个函数
defer logFile.Close()defer func() {if r := recover(); r != nil {log.Printf("panic:\n%s", debug.Stack())fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))os.Exit(99)}
}()
前者defer logFile.Close()
表示在当前函数生存期最后把logFile关闭
后者是使用recover()
函数确认是否出现panic,如果没有产生panic,r的值就是nil,如果产生了panic,就用后面的语句对panic进行处理
这里要注意的是 先defer后调用,有点类似于压栈操作
后面接着一系列初始化操作
assets.Setup(false, true) //assets init
certs.SetupCAs() //ca init
certs.SetupWGKeys() //wireguard key init
cryptography.AgeServerKeyPair() //Get teh server's ECC key pair
cryptography.MinisignServerPrivateKey() //Get the server's minisign key pair
c2.SetupDefaultC2Profiles()
第一个assets.Setup(false, true)
,对各种资源进行初始化,例如开头的banner。
certs.SetupCAs()
初始化了CA证书
certs.SetupWGKeys()
初始化了wireguard key
cryptography.AgeServerKeyPair()
初始化了ECC秘钥对
cryptography.MinisignServerPrivateKey()
初始化minisign秘钥对
c2.SetupDefaultC2Profiles()
初始化默认的C2Profiles
配置文件
server.json
首先便是服务端的配置文件
serverConfig := configs.GetServerConfig()
func GetServerConfig() *ServerConfig {configPath := GetServerConfigPath()config := getDefaultServerConfig().....
//后面的内容就是读取configPath的路径的json格式的配置文件解析到config进行使用和保存
}
// GetServerConfigPath - File path to config.json
func GetServerConfigPath() string {appDir := assets.GetRootAppDir()serverConfigPath := filepath.Join(appDir, "configs", serverConfigFileName)serverConfigLog.Debugf("Loading config from %s", serverConfigPath)return serverConfigPath
}
GetServerConfigPath函数就是读取~/.sliver/config/server.json
func getDefaultServerConfig() *ServerConfig {return &ServerConfig{DaemonMode: false,DaemonConfig: &DaemonConfig{Host: "",Port: 31337,},Logs: &LogConfig{Level: int(logrus.InfoLevel),GRPCUnaryPayloads: false,GRPCStreamPayloads: false,},CC: map[string]string{},CXX: map[string]string{},}
}
getDefaultServerConfig函数是返回一个默认的config内容
serverConfig := configs.GetServerConfig()
最后执行的结果就是获取~/.sliver/config/server.json
的内容给到变量serverConfig
这里可以看下默认的config内容的样子
└─$ cat ~/.sliver/configs/server.json
{"daemon_mode": false,"daemon": {"host": "","port": 31337,"tailscale": false},"logs": {"level": 4,"grpc_unary_payloads": false,"grpc_stream_payloads": false,"tls_key_logger": false},"watch_tower": null,"go_proxy": "","cc": {},"cxx": {}
}
接着代码是
listenerJobs, err := db.ListenerJobs()
if err != nil {fmt.Println(err)
}err = StartPersistentJobs(listenerJobs)
if err != nil {fmt.Println(err)
}
意思是获取数据库中保存的监听任务,也就是说,就算服务down了,重启自动就从数据库读取任务继续运行,或者说如果忘记结束job,这个job就一直跑着
然后的代码是
if serverConfig.DaemonMode {daemon.Start(daemon.BlankHost, daemon.BlankPort, serverConfig.DaemonConfig.Tailscale)
} else {os.Args = os.Args[:1] // Hide cli from grumble consoleconsole.Start()
}
查看服务配置文件是否配置守护进程,也就是配成service,如果有配置成守护进程,就监听端口可以进行多人协同。
database.json
至于数据库
package dbimport ("gorm.io/gorm"
)
// Client - Database Client
var Client = newDBClient()
// Session - Database session
func Session() *gorm.DB {return Client.Session(&gorm.Session{FullSaveAssociations: true,})
}
可以看到有一个导出的Client和Session()
看看生成这个Client的newDBClient()
// newDBClient - Initialize the db client
func newDBClient() *gorm.DB {dbConfig := configs.GetDatabaseConfig()var dbClient *gorm.DBswitch dbConfig.Dialect {case configs.Sqlite:dbClient = sqliteClient(dbConfig)case configs.Postgres:dbClient = postgresClient(dbConfig)case configs.MySQL:dbClient = mySQLClient(dbConfig)default:panic(fmt.Sprintf("Unknown DB Dialect: '%s'", dbConfig.Dialect))}.....
}
首先从dbConfig := configs.GetDatabaseConfig()获取配置文件
然后根据配置文件去连接数据库
// GetDatabaseConfig - Get config value
func GetDatabaseConfig() *DatabaseConfig {configPath := GetDatabaseConfigPath()config := getDefaultDatabaseConfig()......
}
和前面server.json的函数相似
不过GetDatabaseConfigPath()
读取的是`~/.sliver/config/database.json
看一下默认的内容
└─$ cat ~/.sliver/configs/database.json
{"dialect": "sqlite3","database": "","username": "","password": "","host": "","port": 0,"params": null,"max_idle_conns": 10,"max_open_conns": 100,"log_level": "warn"
}
欢迎来关注我的公众号 GEEK-DREAM