diff --git a/mitmaddon/networkthread.py b/mitmaddon/networkthread.py index b8a79c7..154259d 100644 --- a/mitmaddon/networkthread.py +++ b/mitmaddon/networkthread.py @@ -71,11 +71,13 @@ class bRequest: self.timestamp_end = flow["request"]["timestamp_end"] self.headers = [] - for k, v in flow["request"]["headers"]: + for k, v in flow["request"].get("headers", {}): self.headers.append(bHeader(str(k), str(v))) def json(self) -> dict: - return {} + state = vars(self).copy() + state["headers"] = {h.key: h.value for h in self.headers} + return state @dataclass @@ -99,11 +101,13 @@ class bResponse: self.timestamp_end = flow["response"]["timestamp_end"] self.headers = [] - for k, v in flow["response"]["headers"]: + for k, v in flow["response"].get("headers", {}): self.headers.append(bHeader(k, v)) def json(self) -> dict: - return {} + state = vars(self).copy() + state["headers"] = [{h.key: h.value} for h in self.headers] + return state @dataclass @@ -133,18 +137,13 @@ class bPacket: flowid: str data: str - def __init__(self, json: Dict): - self.ptype = json["type"] - self.flowid = str(json["id"]) - self.data = json["data"] - @dataclass class FlowItem: state: bFlowState flow: Flow time: float = 0 - retries_left: int = 5 + retries: int = 5 """ @@ -187,7 +186,7 @@ class NetworkThread(threading.Thread): # get new self.flows that may occured i, flowitem = self.q.get(block=False) if self.flows.get(i, None): - print(f"flow {i} doubled? ignoring...") + raise ValueError(f"flow {i} doubled? ignoring...") continue else: self.flows[i] = flowitem @@ -199,40 +198,63 @@ class NetworkThread(threading.Thread): msg = {"type": pkg.ptype, "id": pkg.flowid, "data": pkg.data} self.send(msg) - def send_http_request(self, id: int, request: bRequest): - pkg = bPacket(bPacketType.HTTP_REQUEST, request.json()) - self.send_packet(pkg) - self.flows[id].state = bFlowState.SENT_HTTP_REQUEST - self.flows[id].time = time.monotonic() - - def send_http_response(self, id: int, response: bResponse): - pkg = bPacket(bPacketType.HTTP_RESPONSE, response.json()) - self.send_packet(pkg) - self.flows[id].state = bFlowState.SENT_HTTP_RESPONSE - self.flows[id].time = time.monotonic() - # update all current self.flows # handles the state machine for each flow def update_flows(self): - for id, flow in self.flows.items(): + # force copy of item list, so we can remove dict items in the loop + for id, flow in list(self.flows.items()): if self.flows[id].retries <= 0: - self.flows[id].flow.kill() + if self.flows[id].flow: + self.flows[id].flow.kill() print(f"http flow {id} timed out! flow killed.") + del self.flows[id] delta = time.monotonic() - self.flows[id].time if flow.state == bFlowState.UNSENT_HTTP_REQUEST or \ flow.state == bFlowState.SENT_HTTP_REQUEST and delta > 5: - self.send_http_request(id, bRequest(flow.flow)) + pkg = bPacket(bPacketType.HTTP_REQUEST, id, bRequest(flow.flow.get_state()).json()) + self.send_packet(pkg) + + self.flows[id].time = time.monotonic() + self.flows[id].state = bFlowState.SENT_HTTP_REQUEST self.flows[id].retries -= 1 - if flow.state == bFlowState.UNSENT_HTTP_RESPONSE or \ + elif flow.state == bFlowState.UNSENT_HTTP_RESPONSE or \ flow.state == bFlowState.SENT_HTTP_RESPONSE and delta > 5: - self.send_http_response(id, bResponse(flow.flow)) + pkg = bPacket(bPacketType.HTTP_RESPONSE, id, bResponse(flow.flow.get_state()).json()) + self.send_packet(pkg) + + self.flows[id].time = time.monotonic() + self.flows[id].state = bFlowState.SENT_HTTP_RESPONSE self.flows[id].retries -= 1 - elif flow.state == bFlowState.ERROR: + if flow.state == bFlowState.ERROR: print(f"error in flow {id}!") + del self.flows[id] + + def handle_packet(self, pkg): + flow = self.flows.get(pkg.flowid, None) + + # flow ACKed + if pkg.ptype == bPacketType.ACK: + if flow and flow.flow: + flow.flow.resume() + else: + raise ValueError("unknown flow") + + # flow killed + elif pkg.ptype == bPacketType.KILL: + if flow and flow.flow: + flow.flow.kill() + else: + raise ValueError("unknown flow") + + if flow: + del self.flows[pkg.flowid] + + else: + print(f"got unexpected message {pkg.ptype}") # handle incoming packets / update the statemachine def handle_packets(self): @@ -242,14 +264,7 @@ class NetworkThread(threading.Thread): if msg: result = json.loads(str(msg)) pkg = bPacket(json=result) - # flow ACKed - if pkg.ptype == bPacketType.ACK: - continue - # flow killed - elif pkg.ptype == bPacketType.KILL: - continue - else: - print(f"got unexpected message {pkg.ptype}") + self.handle_packet(pkg) except json.JSONDecodeError: print(f"malformed message received {msg}") diff --git a/mitmaddon/test_bigsnitch.py b/mitmaddon/test_bigsnitch.py index 9083fde..11ac2bc 100644 --- a/mitmaddon/test_bigsnitch.py +++ b/mitmaddon/test_bigsnitch.py @@ -2,8 +2,10 @@ import pdb import queue - +import uuid import pytest +from mitmproxy.http import HTTPFlow, HTTPRequest, HTTPResponse +from mitmproxy.connections import ClientConnection, ServerConnection from networkthread import bPacket, bRequest, bResponse, bHeader, NetworkThread, FlowItem, bFlowState import os import tempfile @@ -13,22 +15,64 @@ import zmq from deepdiff import DeepDiff +@pytest.fixture +def flowstate_clientconn(): + return dict( + id=str(uuid.uuid4()), + address=("address", 22), + source_address=("address", 22), + ip_address=("192.168.0.1", 22), + timestamp_start=946681202, + timestamp_tcp_setup=946681203, + timestamp_tls_setup=946681204, + timestamp_end=946681205, + tls_established=True, + sni="address", + alpn=None, + tls_version="TLSv1.2", + via=None, + state=0, + error=None, + tls=False, + certificate_list=[], + alpn_offers=[], + cipher_name=None, + cipher_list=[], + via2=None, + ) + + +@pytest.fixture +def flowstate_serverconn(): + return dict( + id=str(uuid.uuid4()), + address=("address", 22), + source_address=("address", 22), + ip_address=("192.168.0.1", 22), + timestamp_start=946681202, + timestamp_tcp_setup=946681203, + timestamp_tls_setup=946681204, + timestamp_end=946681205, + tls_established=True, + sni="address", + alpn=None, + tls_version="TLSv1.2", + via=None, + state=0, + error=None, + tls=False, + certificate_list=[], + alpn_offers=[], + cipher_name=None, + cipher_list=[], + via2=None, + ) + + # usual flow state of the request with some big parts removed @pytest.fixture -def flowstate_request(): - return {'client_conn': {'address': ('::ffff:127.0.0.1', 60630, 0, 0), - 'alpn_proto_negotiated': b'http/1.1', - 'cipher_name': 'TLS_AES_256_GCM_SHA384', - 'clientcert': None, - 'id': '5dde7ef8-9b1a-4b60-9d15-d308442a27ea', - 'mitmcert': '', - 'sni': 'yolo.jetzt', - 'timestamp_end': None, - 'timestamp_start': 1619390481.8003347, - 'timestamp_tls_setup': 1619390482.6879823, - 'tls_established': True, - 'tls_extensions': [], - 'tls_version': 'TLSv1.3'}, +def flowstate_request(flowstate_clientconn, flowstate_serverconn): + return {'client_conn': flowstate_clientconn, 'error': None, 'id': '51215b69-c76f-4ac2-afcb-da3b823d9f88', 'intercepted': False, @@ -36,8 +80,7 @@ def flowstate_request(): 'marked': False, 'metadata': {}, 'mode': 'transparent', - 'request': {'authority': b'', - 'content': b'', + 'request': {'content': b'', 'headers': ((b'Host', b'yolo.jetzt'), (b'User-Agent', b'curl/7.75.0'), (b'Accept', b'*/*')), @@ -49,156 +92,138 @@ def flowstate_request(): 'scheme': b'https', 'timestamp_end': 1619390482.69, 'timestamp_start': 1619390482.6886377, - 'trailers': None}, + 'authority': '', + 'trailers': '', + }, 'response': None, - 'server_conn': {'address': ('yolo.jetzt', 443), - 'alpn_proto_negotiated': b'http/1.1', - 'cert': '', - 'id': 'ecc4cd3b-7e35-4815-b618-5931fe64729b', - 'ip_address': ('95.156.226.69', 443), - 'sni': 'yolo.jetzt', - 'source_address': ('192.168.42.182', 51514), - 'timestamp_end': None, - 'timestamp_start': 1619390481.8154442, - 'timestamp_tcp_setup': 1619390481.994565, - 'timestamp_tls_setup': 1619390482.6819758, - 'tls_established': True, - 'tls_version': 'TLSv1.2', - 'via': None}, + "server_conn": flowstate_serverconn, 'type': 'http', 'version': 9} +@pytest.fixture +def flow_request(flowstate_request): + c = ClientConnection.make_dummy("192.168.0.2") + s = ServerConnection.make_dummy("192.168.0.1") + flow = HTTPFlow(c, s) + flow.request = HTTPRequest.from_state(flowstate_request["request"]) + return flow + @pytest.fixture() -def flowstate_response(): - return {'client_conn': {'address': ('::ffff:127.0.0.1', 30190, 0, 0), - 'alpn_proto_negotiated': b'http/1.1', - 'cipher_name': 'TLS_AES_256_GCM_SHA384', - 'clientcert': None, - 'id': '2507e6ce-3132-4394-9432-f55fb5f55b05', - 'mitmcert': '', - 'sni': 'yolo.jetzt', - 'timestamp_end': None, - 'timestamp_start': 1619461916.6160116, - 'timestamp_tls_setup': 1619461916.7581937, - 'tls_established': True, - 'tls_extensions': [], - 'tls_version': 'TLSv1.3'}, - 'error': None, - 'id': '449d1a87-744f-4a18-9a5d-f085f99a5c62', - 'intercepted': True, - 'is_replay': None, - 'marked': False, - 'metadata': {}, - 'mode': 'transparent', - 'request': {'authority': b'', - 'content': b'', - 'headers': ((b'Host', b'yolo.jetzt'), - (b'User-Agent', b'curl/7.75.0'), - (b'Accept', b'*/*')), - 'host': 'yolo.jetzt', - 'http_version': b'HTTP/1.1', - 'method': b'GET', - 'path': b'/', - 'port': 443, - 'scheme': b'https', - 'timestamp_end': 1619461916.7603076, - 'timestamp_start': 1619461916.7588415, - 'trailers': None}, - 'response': {'content': b'\n\n
\n\n