引言
- 项目概述:对开源的C2框架sliver进行源码分析,意图学习其原理。本篇分析sliver的入口以及脚手架,和基本的配置文件
- 目标与读者:网络安全兴趣爱好者
准备工作
git clone https://github.com/BishopFox/sliver.git
入口点
由于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 | / / 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的部分源码
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) {
/ / Root command starts the server normally
appDir : = assets.GetRootAppDir() / / makedir $HOME / .sliver
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 ] / / Hide cli from grumble console
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 | / / GetRootAppDir - Get the Sliver app dir , default is : ~ / .sliver /
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 | / / 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
接着执行下面的两个函数
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) / / 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
首先便是服务端的配置文件
1 | serverConfig : = configs.GetServerConfig()
|
1 2 3 4 5 6 | func GetServerConfig() * ServerConfig {
configPath : = GetServerConfigPath()
config : = getDefaultServerConfig()
.....
/ / 后面的内容就是读取configPath的路径的json格式的配置文件解析到config进行使用和保存
}
|
1 2 3 4 5 6 7 | / / 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
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 ] / / Hide cli from grumble console
console.Start()
}
|
查看服务配置文件是否配置守护进程,也就是配成service,如果有配置成守护进程,就监听端口可以进行多人协同。
database.json
至于数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 | package db
import (
"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()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | / / newDBClient - Initialize the db client
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 | / / GetDatabaseConfig - Get config value
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"
}
|
欢迎来我的博客逛一逛!
https://suqerbrave.github.io/