freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Sliver源码分析 | 初始化以及脚手架
2024-08-12 22:30:27

引言

  • 项目概述:对开源的C2框架Sliver进行源码分析,意图学习其原理。本篇分析Sliver的入口以及脚手架,和基本的配置文件。

  • 目标与读者:网络安全兴趣爱好者。

准备工作

git clone https://github.com/BishopFox/sliver.git

入口点

由于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 团队成员 spf13Hugo项目创建的,并已被许多流行的 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 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"。

└─$ 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()

// 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)

// 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 console
	console.Start()
}

查看服务配置文件是否配置守护进程,也就是配成service,如果有配置成守护进程,就监听端口可以进行多人协同。

database.json

至于数据库

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()

// 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()获取配置文件
然后根据配置文件去连接数据库

// 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"
}

欢迎来我的博客光顾!
https://suqerbrave.github.io/

# 源码 # 源码分析 # C2框架
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录