Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 75 additions & 46 deletions agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.

import re
import argparse
import base64
import cgi
import email.parser
import email.policy
import enum
import http.server
import ipaddress
Expand All @@ -23,18 +25,16 @@
import tempfile
import time
import traceback
from io import StringIO
import urllib.parse
from io import BytesIO, StringIO
from threading import Lock
from typing import Iterable
from zipfile import ZipFile
import logging

try:
import re2 as re # type: ignore
except ImportError:
import re

if sys.version_info[:2] < (3, 6):
sys.exit("You are running an incompatible version of Python, please use >= 3.6")
if sys.version_info[:2] < (3, 10):
sys.exit("You are running an incompatible version of Python, please use >= 3.10")

# You must run x86 version not x64
# The analysis process interacts with low-level Windows libraries that need a
Expand All @@ -43,7 +43,7 @@
if sys.maxsize > 2**32 and sys.platform == "win32":
sys.exit("You should install python3 x86! not x64")

AGENT_VERSION = "0.20"
AGENT_VERSION = "0.21"
AGENT_FEATURES = [
"execpy",
"execute",
Expand All @@ -68,6 +68,8 @@
WAIT_TIMEOUT = 0x102
WAIT_FAILED = 0xFFFFFFFF

log = logging.getLogger(__name__)


class Status(enum.IntEnum):
INIT = 1
Expand Down Expand Up @@ -110,56 +112,83 @@ def _missing_(cls, value):
class MiniHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
server_version = "CAPE Agent"

def do_GET(self):
def _init_request(self, method):
request.client_ip, request.client_port = self.client_address
request.form = {}
request.files = {}
request.method = "GET"
request.method = method

self.httpd.handle(self)
def _decode_bytes(self, b):
try:
return b.decode("utf-8")
except UnicodeDecodeError:
return b.decode("latin-1")

def _add_to_dict(self, d, key, value):
if key in d:
if isinstance(d[key], list):
d[key].append(value)
else:
d[key] = [d[key], value]
else:
d[key] = value

def do_POST(self):
environ = {
"REQUEST_METHOD": "POST",
"CONTENT_TYPE": self.headers.get("Content-Type"),
}
def _parse_form(self):
content_type = self.headers.get("Content-Type", "")
try:
content_length = int(self.headers.get("Content-Length", 0))
except ValueError:
log.warning("Malformed Content-Length header received, defaulting to 0: %s", self.headers.get("Content-Length"))
content_length = 0

form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ=environ)
if not content_type or not content_length:
return

request.client_ip, request.client_port = self.client_address
request.form = {}
request.files = {}
request.method = "POST"
body = self.rfile.read(content_length)

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
self.httpd.handle(self)
if "multipart/form-data" in content_type:
# Prepare a valid MIME message with headers
# We prefix the body with the Content-Type header so BytesParser can recognize the boundary
headers = f"Content-Type: {content_type}\r\n".encode("latin-1")

def do_DELETE(self):
environ = {
"REQUEST_METHOD": "DELETE",
"CONTENT_TYPE": self.headers.get("Content-Type"),
}
msg = email.parser.BytesParser(policy=email.policy.default).parsebytes(headers + b"\r\n" + body)

form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ=environ)
if msg.is_multipart():
for part in msg.iter_parts():
name = part.get_param("name", header="content-disposition")
filename = part.get_filename()

request.client_ip, request.client_port = self.client_address
request.form = {}
request.files = {}
request.method = "DELETE"
if not name:
continue

if form.list:
for key in form.keys():
value = form[key]
if value.filename:
request.files[key] = value.file
payload = part.get_payload(decode=True)

if filename:
self._add_to_dict(request.files, name, BytesIO(payload))
else:
self._add_to_dict(request.form, name, self._decode_bytes(payload))

elif "application/x-www-form-urlencoded" in content_type:
data = urllib.parse.parse_qs(self._decode_bytes(body))

for key, val in data.items():
if len(val) == 1:
request.form[key] = val[0]
else:
request.form[key] = value.value
request.form[key] = val

def do_GET(self):
self._init_request("GET")
self.httpd.handle(self)

def do_POST(self):
self._init_request("POST")
self._parse_form()
self.httpd.handle(self)

def do_DELETE(self):
self._init_request("DELETE")
self._parse_form()
self.httpd.handle(self)


Expand Down
Loading