引言
项目概述 :对开源的C2框架sliver进行源码分析,意图学习其原理。本篇分析sliver的入口以及脚手架,和基本的配置文件
目标与读者 :网络安全兴趣爱好者
准备工作
入口点 由于sliver是CS架构的系统,而且主要功能在服务端所以分析目标是sliver-server 这里查看到入口点的内容只有运行cli.Execute() server/main.go
1 2 3 4 5 6 7 import ( "github.com/bishopfox/sliver/server/cli" ) func main () { cli.Execute() }
server/cli/cli.go
1 2 3 4 5 6 7 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的部分源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 var rootCmd = &cobra.Command{ Use: "sliver-server" , Short: "" , Long: `` , Run: func (cmd *cobra.Command, args []string ) { appDir := assets.GetRootAppDir() logFile := 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 ] console.Start() } }, }
由于这个是rootCmd,所以其中Use: “sliver-server”表示这个命令本身。cobra在-h等参数中会告诉这个命令是什么命令,这里就是指的是”sliver-server”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 └─$ sliver-server -h Usage://Use: "sliver-server" 指的就这下面的例子,用于提示这个命令是什么 sliver-server [flags] sliver-server [command ] Available Commands: builder Start the process as an external builder completion Generate the autocompletion script for the specified shell daemon Force start server in daemon mode export-ca Export certificate authority help Help about any command import-ca Import certificate authority operator Generate operator configuration files unpack Unpack assets and exit version Print version and exit Flags: -h, --help help for sliver-server Use "sliver-server [command] --help" for more information about a command .
Run: func(cmd *cobra.Command, args []string)
是这个命令(sliver-server)需要运行的内容 首先执行appDir := assets.GetRootAppDir()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func GetRootAppDir () string { value := os.Getenv(envVarName) var dir string if 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)
1 2 3 4 5 6 7 8 9 10 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, 0 o600) if err != nil { log.Fatalf("Error opening file: %v" , err) } log.SetOutput(logFile) return logFile }
其中logFileName = "console.log" 所以该函数就是创建
~/.sliver/logs/console.log`, 并返回这个文件到变量logFile 接着执行下面的两个函数
1 2 3 4 5 6 7 8 9 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后调用,有点类似于压栈操作
后面接着一系列初始化操作
1 2 3 4 5 6 assets.Setup(false , true ) certs.SetupCAs() certs.SetupWGKeys() cryptography.AgeServerKeyPair() cryptography.MinisignServerPrivateKey() c2.SetupDefaultC2Profiles()
第一个assets.Setup(false, true)
,对各种资源进行初始化,例如开头的banner。certs.SetupCAs()
初始化了CA证书certs.SetupWGKeys()
初始化了wireguard keycryptography.AgeServerKeyPair()
初始化了ECC秘钥对cryptography.MinisignServerPrivateKey()
初始化minisign秘钥对c2.SetupDefaultC2Profiles()
初始化默认的C2Profiles
配置文件 server.json 首先便是服务端的配置文件
1 serverConfig := configs.GetServerConfig()
1 2 3 4 5 6 func GetServerConfig () *ServerConfig { configPath := GetServerConfigPath() config := getDefaultServerConfig() ..... }
1 2 3 4 5 6 7 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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内容的样子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 └─$ 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" : {} }
接着代码是
1 2 3 4 5 6 7 8 9 listenerJobs, err := db.ListenerJobs() if err != nil { fmt.Println(err) } err = StartPersistentJobs(listenerJobs) if err != nil { fmt.Println(err) }
意思是获取数据库中保存的监听任务,也就是说,就算服务down了,重启自动就从数据库读取任务继续运行,或者说如果忘记结束job,这个job就一直跑着
然后的代码是
1 2 3 4 5 6 if serverConfig.DaemonMode { daemon.Start(daemon.BlankHost, daemon.BlankPort, serverConfig.DaemonConfig.Tailscale) } else { os.Args = os.Args[:1 ] console.Start() }
查看服务配置文件是否配置守护进程,也就是配成service,如果有配置成守护进程,就监听端口可以进行多人协同。
database.json 至于数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 package dbimport ( "gorm.io/gorm" ) var Client = newDBClient()func Session () *gorm.DB { return Client.Session(&gorm.Session{ FullSaveAssociations: true , }) }
可以看到有一个导出的Client和Session()
看看生成这个Client的newDBClient()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func newDBClient () *gorm.DB { dbConfig := configs.GetDatabaseConfig() var dbClient *gorm.DB switch 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()获取配置文件 然后根据配置文件去连接数据库
1 2 3 4 5 6 func GetDatabaseConfig () *DatabaseConfig { configPath := GetDatabaseConfigPath() config := getDefaultDatabaseConfig() ...... }
和前面server.json的函数相似 不过GetDatabaseConfigPath()
读取的是`~/.sliver/config/database.json
看一下默认的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 └─$ 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