这是讲述cuckoo源码分析的第二篇文章, 主要讲述host端的分析模块.
上一篇文章,主要将cuckoo执行分析模块前的准备工作介绍了一番, 准备工作如下:
设置工作目录(CWD) --> 工作目录存放包括样本信息, 分析结果, yara规则, 配置文件等在内的重要内容. 设置cuckoo服务端 --> 设置一个tcp 服务端, 但也可以作为http服务端来使用. 后面会提到通过http服务,将主要分析代码和样本传入虚拟机中. 加载配置 --> 如: 虚拟机的类型, ip, 端口, 数据库, yara规则 检查虚拟机的相关信息(如: 软件类型, 虚拟镜像名称, 快照)
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的映射.
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在客户端的操作.包括如下内容: