中篇文章分析了host端如何开始分析(在数据库), 包括: 开启虚拟机、上传样本、上传分析模块和分析配置文件、在数据库中记录虚拟机的状态等. 下面用一张图来说明host和client端的数据传输,agent.py代码就按照这个图来进行分析.
# 继承自SimpleHTTPServer.SimpleHTTPRequestHandler, 用作对不同路径的不同函数处理(响应) class MiniHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): server_version = "Cuckoo Agent" # 响应GET请求 def do_GET(self): #self.client_address -> 基类中的(host, port) request.client_ip, request.client_port = self.client_address request.form = {} request.files = {} request.method = "GET" self.httpd.handle(self) # 响应POST请求 def do_POST(self): environ = { "REQUEST_METHOD": "POST", "CONTENT_TYPE": self.headers.get("Content-Type"), } # host与client之间的数据传输, 格式为multipart/form-data # 你可以理解为key:value形式 form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ=environ) request.client_ip, request.client_port = self.client_address request.form = {} request.files = {} request.method = "POST" # 遍历传输的数据格式(key:value), 将带有filename字段的内容挑选出来, 以便后面写文件. # 如: analysis.conf, 分析模块, 样本等,这些数据在传输的时候, 都要带有filename字段 # 但不同的filename字段对应的值不一样. if form.list: for key in form.keys(): value = form[key] if value.filename: request.files[key] = value.file else: request.form[key] = value.value.decode("utf8") self.httpd.handle(self) class MiniHTTPServer(object): def __init__(self): self.handler = MiniHTTPRequestHandler # Reference back to the server. self.handler.httpd = self self.routes = { "GET": [], "POST": [], } # 运行tcp服务器 def run(self, host="0.0.0.0", port=8000): self.s = SocketServer.TCPServer((host, port), self.handler) self.s.allow_reuse_address = True self.s.serve_forever() # route作为一个装饰器, 修饰下面步骤中函数(@app.route(...)) # self.routes: # ["GET"] --> [(path, function), (path, function)] # ["POST"] --> [(path, function),(path, function)] def route(self, path, methods=["GET"]): def register(fn): for method in methods: self.routes[method].append((re.compile(path + "$"), fn)) return fn return register # 执行相应的处理函数 def handle(self, obj): if "client_ip" in state and request.client_ip != state["client_ip"]: if request.client_ip != "127.0.0.1": return if obj.path != "/status" or request.method != "POST": return # obj.command --> post or get # obj.path -> GET /path or POST /path for route, fn in self.routes[obj.command]: if route.match(obj.path): ret = fn() break else: ret = json_error(404, message="Route not found") ret.init() obj.send_response(ret.status_code) ret.headers(obj) obj.end_headers() if isinstance(ret, jsonify): obj.wfile.write(ret.json()) elif isinstance(ret, send_file): ret.write(obj.wfile) def shutdown(self): # BaseServer also features a .shutdown() method, but you can't use # that from the same thread as that will deadlock the whole thing. self.s._BaseServer__shutdown_request = True
在进行cuckoo沙箱安装的时候, 需要将ageng.py拷贝至Windows的Startup文件夹, 并且修改后缀py为pyw, 实现开机自启动.
浏览器输入
192.168.56.2:8000
, 返回, 获取agent的一些基本信息.{"message": "Cuckoo Agent!", "version": "0.10", "features": ["execpy", "pinning", "logs", "largefile", "unicodepath"]}
AGENT_VERSION = "0.10" AGENT_FEATURES = [ "execpy", "pinning", "logs", "largefile", "unicodepath", ] # app = MiniHTTPServer() # 以json格式返回基本信息 @app.route("/") def get_index(): return json_success( "Cuckoo Agent!", version=AGENT_VERSION, features=AGENT_FEATURES )
个人感觉没啥用, 既然已经能获取基本信息, 说明ip和端口都是正确的, 已经被固定使用了.
@app.route("/pinning") def do_pinning(): if "client_ip" in state: return json_error(500, "Agent has already been pinned to an IP!") state["client_ip"] = request.client_ip return json_success("Successfully pinned Agent", client_ip=request.client_ip)
获取client端环境变量,以便后面后续的一些命令执行.
- mkdtemp
- extract
- store
- execpy
@app.route("/environ") def get_environ(): return json_success("Environment variables", environ=dict(os.environ))
其实ageng.py中存在两个创建临时文件夹的命令: mktemp和mkdtemp. 但二者创建的位置不一样:
- mkdtemp --> 在%SYSTEMDRIVE%(C:\)下创建一个随机文件夹
- mktemp --> 在%TEMP%(C:\Users\bill\AppData\Local\Temp)下创建一个随机文件夹
@app.route("/mktemp", methods=["GET", "POST"]) def do_mktemp(): # 我抓的包中, 没有发现suffix和prefix这两个字段. # %TEMP%(C:\\Users\\bill\\AppData\\Local\\Temp) suffix = request.form.get("suffix", "") prefix = request.form.get("prefix", "tmp") dirpath = request.form.get("dirpath") try: fd, filepath = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dirpath) except: return json_exception("Error creating temporary file") os.close(fd) return json_success("Successfully created temporary file", filepath=filepath) @app.route("/mkdtemp", methods=["GET", "POST"]) def do_mkdtemp(): # 我抓的包中, 没有发现suffix和prefix这两个字段. # %SYSTEMDRIVE%(C:\\) suffix = request.form.get("suffix", "") prefix = request.form.get("prefix", "tmp") dirpath = request.form.get("dirpath") try: dirpath = tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=dirpath) except: return json_exception("Error creating temporary directory") return json_success("Successfully created temporary directory", dirpath=dirpath)
中篇中提到,将分析模块以zip格式压缩,发送给client端. 发送extrac命令, 将分析模块解压到上一步创建的文件夹中.
@app.route("/extract", methods=["POST"]) def do_extract(): # 上一步创建的随机文件夹C:\\tmppx7scxC:tmppx7scx if "dirpath" not in request.form: return json_error(400, "No dirpath has been provided") if "zipfile" not in request.files: return json_error(400, "No zip file has been provided") try: with zipfile.ZipFile(request.files["zipfile"], "r") as archive: archive.extractall(request.form["dirpath"]) except: return json_exception("Error extracting zip file") return json_success("Successfully extracted zip file")
执行store命令, 写入analysis.conf
@app.route("/store", methods=["POST"]) def do_store(): # filepath: C:/tmppx7scx/analysis.conf if "filepath" not in request.form: return json_error(400, "No filepath has been provided") # file: analysis.conf if "file" not in request.files: return json_error(400, "No file has been provided") try: with open(request.form["filepath"], "wb") as f: shutil.copyfileobj(request.files["file"], f, 10*1024*1024) except: return json_exception("Error storing file") return json_success("Successfully stored file")
analysis.conf内容
[analysis] category = file target = /tmp/cuckoo-tmp-pwnmelife/tmpZ3SA0v/maze.exe (host端的样本地址) package = exe file_type = PE32 executable (GUI) Intel 80386, for MS Windows file_name = maze.exe clock = 20200620T09:28:00 id = 1 terminate_processes = False options = apk_entry=:,procmemdump=yes,route=none enforce_timeout = False timeout = 120 ip = 192.168.56.1 pe_exports = port = 2042
执行store命令, 写入maze.exe
@app.route("/store", methods=["POST"]) def do_store(): # filepath: C:\Users\bill\AppData\Local\Temp\maze.exe if "filepath" not in request.form: return json_error(400, "No filepath has been provided") # file: sample.bin if "file" not in request.files: return json_error(400, "No file has been provided") try: with open(request.form["filepath"], "wb") as f: shutil.copyfileobj(request.files["file"], f, 10*1024*1024) except: return json_exception("Error storing file") return json_success("Successfully stored file")
def do_execpy(): # filepath: C:/tmppx7scx/analyzer.py if "filepath" not in request.form: return json_error(400, "No Python file has been provided") # Execute the command asynchronously? As a shell command? # async: yes async = "async" in request.form # cwd : C:/tmppx7scx cwd = request.form.get("cwd") stdout = stderr = None args = [ sys.executable, request.form["filepath"], ] # async = yes, 不返回执行结果 # async = false, 返回执行结果 try: if async: subprocess.Popen(args, cwd=cwd) else: p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() except: return json_exception("Error executing command") return json_success("Successfully executed command", stdout=stdout, stderr=stderr)
当返回complete状态时, host端要关闭虚拟机.
@app.route("/status") def get_status(): return json_success("Analysis status", status=state.get("status"), description=state.get("description"))
[培训]《安卓高级研修班(网课)》9月班开始招生!挑战极限、工资翻倍!
最后于 1小时前 被baolongshou编辑 ,原因: