CUCKOO沙箱源码分析 中篇
前言
这是讲述cuckoo源码分析的第二篇文章, 主要讲述host端的分析模块.
回顾
上一篇文章,主要将cuckoo执行分析模块前的准备工作介绍了一番, 准备工作如下:
设置工作目录(CWD) --> 工作目录存放包括样本信息, 分析结果, yara规则, 配置文件等在内的重要内容.
设置cuckoo服务端 --> 设置一个tcp 服务端, 但也可以作为http服务端来使用. 后面会提到通过http服务,将主要分析代码和样本传入虚拟机中.
加载配置 --> 如: 虚拟机的类型, ip, 端口, 数据库, yara规则
检查虚拟机的相关信息(如: 软件类型, 虚拟镜像名称, 快照)
分析模块之AnalysisManger
AnalysisManager类
AnalysisManager
继承于threading.Thread
类, 上篇结尾执行了start
函数, 那么在AnalysisManager
类中就应该查看对应的run
函数.
构造函数
# 构造函数
def __init__(self, task_id, error_queue):
"""@param task: task object containing the details for the analysis."""
threading.Thread.__init__(self)
self.errors = error_queue
self.cfg = Config()
self.storage = ""
self.binary = ""
self.storage_binary = ""
self.machine = None
self.db = Database() # 需要获取样本的信息,而样本的信息存储在数据库中, 自然要连接数据库了.
# 这里说明一下: 上传样本可以通过web界面或submit命令
self.task = self.db.view_task(task_id) # 查看任务的信息
# timeout --> 指明分析时间 platform --> 系统类型Windows, Linux, Android
# started_on --> 开始时间 completed_on --> 结束时间(分析结束之后进行填写)
# status --> 指明分析状态, 如pending, running, completed, reported, recovered, failed等
self.guest_manager = None
self.route = None
self.interface = None
self.rt_table = None
self.unrouted_network = False
self.stopped_aux = False
self.rs_port = config("cuckoo:resultserver:port") # 读取配置文件中的服务端ip和端口, 我这里设置的是192.168.56.1和2042
run函数
# 主要代码
self.launch_analysis() # 重要, 展开介绍
if self.cfg.cuckoo.process_results:
self.store_task_info() # 存储任务结果, 存储在task.json文件中.
self.db.set_status(self.task.id, TASK_COMPLETED) # 设置task对应的状态为completed
self.process_results() # 处理虚拟机运行样本的结果, 这个是流程的最后一步, 放在最后面介绍(第二个坑)
# 在CWD/storage/analyses文件夹下创建lastest符号链接, 指向最新的分析结果文件夹
latest = cwd("storage", "analyses", "latest")
latest_symlink_lock.acquire()
if os.path.lexists(latest):
os.remove(latest)
os.symlink(self.storage, latest)
launch_analysis函数
# 初始化分析, 包括:
# 1. 创建文件夹, 用于存放分析结果和样本文件
# 2. 将target指向的文件存放到storage/binaries下
self.init():
# 获取虚拟机资源
self.acquire_machine()
# 添加host的ip与任务id的映射
ResultServer().add_task(self.task, self.machine)
# 初始化client端, 参数为(虚拟机名称, ip, 平台, task.id)
# self.guest_manaer函数会调用self.guest_manager.start_analysis函数(详解见下文)开始分析.
self.guest_manager = GuestManager(
self.machine.name, self.machine.ip,
self.machine.platform, self.task.id, self
)
# 其他辅助性配置加载
# mitm.py --> https代理配置, mitmdump是mitmproxy的命令行配置
# reboot.py --> 重启, 不知道有啥用.
# replay.py --> 流量重放
# service.py --> 部署诱使样本攻击的服务, 例如: 部署蜜罐环境.
# sniffer.py --> 流量抓取, 用于流量分析
RunAuxiliary(self.task, self.machine, self.guest_manager)
self.aux.start()
# 将当前配置存入options中, options是一个字典类型
options = self.build_options()
# 远程桌面. 这个需要在cuckoo.conf中进行设置, 还需要安装其他软件, 比较麻烦.
machinery.enable_remote_control(self.machine.label)
# 设置guests表中的信息,
guest_log = self.db.guest_start(self.task.id,
self.machine.name,
self.machine.label,
machinery.__class__.__name__)
# id = 2行是 self.db.guest_start执行后的添加的, 设置client的初始信息.
+----+---------+----------------+----------------+------------+---------------------+---------------------+---------+
| id | status | name | label | manager | started_on | shutdown_on | task_id |
+----+---------+----------------+----------------+------------+---------------------+---------------------+---------+
| 1 | stopped | cuckoo_win_x64 | cuckoo_win_x64 | VirtualBox | 2020-06-14 14:40:26 | 2020-06-14 14:40:32 | 1 |
| 2 | init | cuckoo_win_x64 | cuckoo_win_x64 | VirtualBox | 2020-06-14 15:31:05 | NULL | 4 | --> 新增的行
+----+---------+----------------+----------------+------------+---------------------+---------------------+---------+
# 开启虚拟机分, 如: machinery/virtualbox.py 中 VirtualBox.start
# machinery对象阿赋值可以在cuckoo源码分析的上篇中找到, 我本地使用的是VirtualBox类的对象, 还可以是VMware, Qemu的
machinery.start(self.machine.label, self.task)
# 路由配置 vpn, tor,
self.route_network()
if "noagent" not in self.machine.options:
self.guest_manage(options) # 重要函数,这个函数的主要是调用self.guest_manager.start_analysis.
else:
self.wait_finish()
# finally 部分做一些清理工作:
# 如: self.aux.stop() --> 辅助模块的清理工作
# 如: dump_path = os.path.join(self.storage, "memory.dmp")
# machinery.dump_memory(self.machine.label, dump_path) --> 内存镜像的dump, 估计只有内存取证的时候需要, 平常不怎么需要.
# 如: machinery.stop --> 停止虚拟机
# 如: ResultServer().del_task(self.task, self.machine) --> 删除ip与任务id的映射.
分析模块之GuestManager
start_analysis
参数: options --> 分析的配置文件, monitor --> 'lastest'字符串.
# client 端也开启了http server, 获取agent(配置的时候,需要在虚拟机中放置agent.py)的信息.
r = self.get("/", do_raise=False)
# 老版的cuckoo
if r.status_code == 501:
self.is_old = True
self.aux.callback("legacy_agent")
self.old.start_analysis(options, monitor)
return
# 获取agent的version, features
status = r.json()
version = status.get("version")
features = status.get("features", [])
# 获取环境变量
# {"message": "Environment variables", "environ": {"TMP": "C:\\Users\\bill\\AppData\\Local\\Temp", "COMPUTERNAME": "BILL-PC", "USERDOMAIN": "bill-PC", ....}}
self.query_environ()
# 通过http协议,上传分析模块, 可以抓包来进行验证.
self.upload_analyzer(monitor) # 重点展开介绍
# 将options中的内容传入client中, 写入到self.analyzer_path的analysis.conf中.
self.add_config(options)
# 将mitm, reboot, replay, service, sniffer等, 这些额外的分析或功能初始化, 与任务对接.
self.aux.callback("prepare_guest")
# 如果分析的内容为文件, 传输文件(样本)
if options["category"] == "file" or options["category"] == "archive":
data = {
"filepath": os.path.join(
self.determine_temp_path(), options["file_name"]
),
}
# target中存放的是样本的路径
files = {
"file": ("sample.bin", open(options["target"], "rb")),
}
self.post("/store", files=files, data=data)
if "execpy" in features:
data = {
"filepath": "%s/analyzer.py" % self.analyzer_path,
"async": "yes",
"cwd": self.analyzer_path,
}
# 执行execpy命令 --> 在系统中执行python analyzer.py
self.post("/execpy", data=data)
else:
# Execute the analyzer that we just uploaded.
data = {
"command": "C:\\Python27\\pythonw.exe %s\\analyzer.py" % self.analyzer_path,
"async": "yes",
"cwd": self.analyzer_path,
}
# 执行execute命令, execute(command)
self.post("/execute", data=data)
upload_analyzer
根据操作系统类型(Windows, Linux), 上传分析模块.
def upload_analyzer(self, monitor):
# 根据平台, 将对应分析模块进行压缩.
# 分析模块的文件位于cuckoo/cuckoo/data/analyzer/(android, darwin, linux, windows)
# 上篇文章中讲到了,init_yara函数compile yara规则为dumpmem.yarac, analyzer_zipfile也会将
# dumpmem.yarac写入到压缩文件流中.
zip_data = analyzer_zipfile(self.platform, monitor)
log.debug(
"Uploading analyzer to guest (id=%s, ip=%s, monitor=%s, size=%d)",
self.vmid, self.ipaddr, monitor, len(zip_data)
)
# 填充self.analyzer_path内容, 这个路径存放分析模块的内容, 一般是tmp目录
self.determine_analyzer_path()
data = {
"dirpath": self.analyzer_path,
}
# 发送extract命令, 令client提取其中的文件
self.post("/extract", files={"zipfile": zip_data}, data=data)
待续
由于analyzer.py是运行在guest端的, 与客户端联系更紧密, 故放到下篇文章中介绍. 下篇文章将介绍cuckoo在客户端的操作.包括如下内容:
- 如何将结果传输给host.
- Client如何运行样本
- 如何获取样本的行为.(如: 注册表操作, 文件操作, 网络行为)
- 等其他问题(可能分为两篇:Python方面的讲解和驱动方面的讲解)
[培训]内核驱动高级班,冲击BAT一流互联网大厂工
作,每周日13:00-18:00直播授课