예제 #1
0
    def exchange(self, uri: str, tree: Node, text_encoding: str="shift-jis", packet_encoding: str="binary") -> Node:
        headers = {}

        if self.__verbose:
            print('Outgoing request:')
            print(tree)

        # Handle encoding
        if packet_encoding == "xml":
            _packet_encoding = EAmuseProtocol.XML
        elif packet_encoding == "binary":
            _packet_encoding = EAmuseProtocol.BINARY
        else:
            raise Exception(f"Unknown packet encoding {packet_encoding}")

        # Handle encryption
        if self.__encryption:
            encryption = f'1-{hex_string(8)}-{hex_string(4)}'
            headers['X-Eamuse-Info'] = encryption
        else:
            encryption = None

        # Handle compression
        if self.__compression:
            compression = 'lz77'
        else:
            compression = None
        headers['X-Compress'] = compression

        # Convert it
        proto = EAmuseProtocol()
        req = proto.encode(
            compression,
            encryption,
            tree,
            text_encoding=text_encoding,
            packet_encoding=_packet_encoding,
        )

        # Send the request, get the response
        r = requests.post(
            f'http://{self.__address}:{self.__port}{"/" if uri[0] != "/" else ""}{uri}',
            headers=headers,
            data=req,
        )

        # Get the compression and encryption
        encryption = headers.get('X-Eamuse-Info')
        compression = headers.get('X-Compress')

        # Decode it
        packet = proto.decode(
            compression,
            encryption,
            r.content,
        )
        if self.__verbose:
            print('Incoming response:')
            print(packet)
        return packet
예제 #2
0
    def assertLoopback(self, root: Node) -> Optional[int]:
        proto = EAmuseProtocol()

        for encoding in [EAmuseProtocol.BINARY, EAmuseProtocol.XML]:
            if encoding == EAmuseProtocol.BINARY:
                loop_name = "binary"
            elif encoding == EAmuseProtocol.XML:
                loop_name = "xml"
            else:
                raise Exception("Logic error!")

            binary = proto.encode(None,
                                  None,
                                  root,
                                  text_encoding=EAmuseProtocol.SHIFT_JIS,
                                  packet_encoding=encoding)
            newroot = proto.decode(None, None, binary)
            self.assertEqual(
                newroot, root,
                "Round trip with {} and no encryption/compression doesn't match!"
                .format(loop_name))

            binary = proto.encode(None,
                                  '1-abcdef-0123',
                                  root,
                                  text_encoding=EAmuseProtocol.SHIFT_JIS,
                                  packet_encoding=encoding)
            newroot = proto.decode(None, '1-abcdef-0123', binary)
            self.assertEqual(
                newroot, root,
                "Round trip with {}, encryption and no compression doesn't match!"
                .format(loop_name))

            binary = proto.encode('none',
                                  None,
                                  root,
                                  text_encoding=EAmuseProtocol.SHIFT_JIS,
                                  packet_encoding=encoding)
            newroot = proto.decode('none', None, binary)
            self.assertEqual(
                newroot, root,
                "Round trip with {}, encryption and no compression doesn't match!"
                .format(loop_name))

            binary = proto.encode('lz77',
                                  None,
                                  root,
                                  text_encoding=EAmuseProtocol.SHIFT_JIS,
                                  packet_encoding=encoding)
            newroot = proto.decode('lz77', None, binary)
            self.assertEqual(
                newroot, root,
                "Round trip with {}, no encryption and lz77 compression doesn't match!"
                .format(loop_name))
예제 #3
0
def main() -> None:
    parser = argparse.ArgumentParser(description="A utility to replay a packet from a log or binary dump.")
    parser.add_argument("-i", "--infile", help="File containing an XML or binary node structure. Use - for stdin.", type=str, default=None, required=True)
    parser.add_argument("-e", "--encoding", help="Encoding for the packet, defaults to UTF-8.", type=str, default='utf-8')
    parser.add_argument("-p", "--port", help="Port to talk to. Defaults to 80", type=int, default=80)
    parser.add_argument("-a", "--address", help="Address to talk to. Defaults to 127.0.0.1", type=str, default="127.0.0.1")
    parser.add_argument("-u", "--path", help="URI that we should post to. Defaults to '/'", type=str, default="/")
    args = parser.parse_args()

    if args.infile == '-':
        # Load from stdin
        packet = sys.stdin.buffer.read()
    else:
        with open(args.infile, mode="rb") as myfile:
            packet = myfile.read()
            myfile.close

    # Add an XML special node to force encoding (will be overwritten if there
    # is one in the packet).
    packet = b''.join([
        f'<?xml encoding="{args.encoding}"?>'.encode(args.encoding),
        packet,
    ])

    # Attempt to decode it
    proto = EAmuseProtocol()
    tree = proto.decode(
        None,
        None,
        packet,
    )

    if tree is None:
        # Can't decode, exit
        raise Exception("Unable to decode packet!")

    model = tree.attribute('model')
    module = tree.children[0].name
    method = tree.children[0].attribute('method')

    server = Protocol(args.address, args.port, False, False, False)
    server.exchange(
        f'{args.path}?model={model}&module={module}&method={method}',
        tree,
    )
예제 #4
0
def generate_code(infile: str, outfile: str, encoding: str) -> None:
    if infile == '-':
        # Load from stdin
        packet = sys.stdin.buffer.read()
    else:
        with open(infile, mode="rb") as infp:
            packet = infp.read()
            infp.close

    # Add an XML special node to force encoding (will be overwritten if there
    # is one in the packet).
    packet = b''.join([
        f'<?xml encoding="{encoding}"?>'.encode(encoding),
        packet,
    ])

    # Attempt to decode it
    proto = EAmuseProtocol()
    req = proto.decode(
        None,
        None,
        packet,
    )

    if req is None:
        # Can't decode, exit
        raise Exception("Unable to decode packet!")

    # Walk through, outputting each node and attaching it to its parent
    code = '\n'.join(generate_lines(req, {}))

    if outfile == '-':
        print(code)
    else:
        with open(outfile, mode="a") as outfp:
            outfp.write(code)
            outfp.close
예제 #5
0
def mainloop(address: Optional[str] = None,
             port: int = 80,
             verbose: bool = False) -> None:
    """
    Main loop of BEMANIShark. Starts an instance of Sniffer and EAmuseProtocol and does a
    lazy job of banging them together with the above HTTP.parse. Will loop trying to decode
    packets forever.

    Arguments:
        address - A string representing an IP of interest
        port - An integer representing a port of interest
    """
    sniffer = Sniffer(address=address, port=port)
    parser = EAmuseProtocol()

    while True:
        packets = sniffer.recv_stream()

        inbound = HTTP.parse(packets['inbound'], request=True)
        outbound = HTTP.parse(packets['outbound'], response=True)

        if inbound is not None:
            if inbound['data'] is None:
                in_req = None
            else:
                try:
                    in_req = parser.decode(
                        inbound['headers'].get('x-compress'),
                        inbound['headers'].get('x-eamuse-info'),
                        inbound['data'])
                except EAmuseException:
                    in_req = None

            print("Inbound request (from {}:{} to {}:{}):".format(
                packets['source_address'],
                packets['source_port'],
                packets['destination_address'],
                packets['destination_port'],
            ))
            if verbose:
                print("HTTP {} request for URI {}".format(
                    inbound['method'], inbound['uri']))
                print("Compression is {}".format(inbound['headers'].get(
                    'x-compress', 'none')))
                print("Encryption key is {}".format(inbound['headers'].get(
                    'x-eamuse-info', 'none')))
            if in_req is None:
                print("Inbound request was not parseable")
            else:
                print(in_req)

        if outbound is not None:
            if outbound['data'] is None:
                out_req = None
            else:
                try:
                    out_req = parser.decode(
                        outbound['headers'].get('x-compress'),
                        outbound['headers'].get('x-eamuse-info'),
                        outbound['data'])
                except EAmuseException:
                    out_req = None

            print("Outbound response (from {}:{} to {}:{}):".format(
                packets['destination_address'],
                packets['destination_port'],
                packets['source_address'],
                packets['source_port'],
            ))
            if verbose:
                print("Compression is {}".format(outbound['headers'].get(
                    'x-compress', 'none')))
                print("Encryption key is {}".format(outbound['headers'].get(
                    'x-eamuse-info', 'none')))
            if out_req is None:
                print("Outbound response was not parseable")
            else:
                print(out_req)
예제 #6
0
def receive_request(path: str) -> Response:
    # First, parse the packet itself
    client_proto = EAmuseProtocol()
    server_proto = EAmuseProtocol()
    remote_address = request.headers.get('X-Remote-Address', None)
    request_compression = request.headers.get('X-Compress', None)
    request_encryption = request.headers.get('X-Eamuse-Info', None)

    actual_path = f'/{path}'
    if request.query_string is not None and len(request.query_string) > 0:
        actual_path = actual_path + f'?{request.query_string.decode("ascii")}'

    if config['verbose']:
        print(f"HTTP request for URI {actual_path}")
        print(f"Compression is {request_compression}")
        print(f"Encryption key is {request_encryption}")

    req = client_proto.decode(
        request_compression,
        request_encryption,
        request.data,
    )

    if req is None:
        # Nothing to do here
        return Response("Unrecognized packet!", 500)

    if config['verbose']:
        print("Original request to server:")
        print(req)

    # Grab PCBID for directing to mulitple servers
    pcbid = req.attribute('srcid')
    if pcbid in config['remote']:
        remote_host = config['remote'][pcbid]['host']
        remote_port = config['remote'][pcbid]['port']
    elif '*' in config['remote']:
        remote_host = config['remote']['*']['host']
        remote_port = config['remote']['*']['port']
    else:
        return Response(f"No route for PCBID {pcbid}", 500)

    modified_request = modify_request(config, req)
    if modified_request is None:
        # Return the original binary data instead of re-encoding it
        # to the exact same thing.
        req_binary = request.data
    else:
        if config['verbose']:
            print("Modified request to server:")
            print(modified_request)

        # Re-encode the modified packet
        req_binary = server_proto.encode(
            request_compression,
            request_encryption,
            modified_request,
            client_proto.last_text_encoding,
            client_proto.last_packet_encoding,
        )

    # Set up custom headers for remote request
    headers = {}
    if request_compression is not None:
        headers['X-Compress'] = request_compression
    if request_encryption is not None:
        headers['X-Eamuse-Info'] = request_encryption

    # For lobby functionality, make sure the request receives
    # the original IP address
    headers['X-Remote-Address'] = remote_address or request.remote_addr

    # Make request to foreign service, using the same parameters
    r = requests.post(
        f'http://{remote_host}:{remote_port}{actual_path}',
        headers=headers,
        data=req_binary,
        timeout=config['timeout'],
    )

    if r.status_code != 200:
        # Failed on remote side
        return Response("Failed to get response!", 500)

    # Decode response, for modification if necessary
    response_compression = r.headers.get('X-Compress', None)
    response_encryption = r.headers.get('X-Eamuse-Info', None)
    resp = server_proto.decode(
        response_compression,
        response_encryption,
        r.content,
    )

    if resp is None:
        # Nothing to do here
        return Response("Unrecognized packet!", 500)

    if config['verbose']:
        print("Original response from server:")
        print(resp)

    modified_response = modify_response(config, resp)
    if modified_response is None:
        # Return the original response data instead of re-encoding it
        # to the exact same thing.
        resp_binary = r.content
    else:
        if config['verbose']:
            print("Modified response from server:")
            print(modified_response)

        # Re-encode the modified packet
        resp_binary = client_proto.encode(
            response_compression,
            response_encryption,
            modified_response,
        )

    # Some old clients are case sensitive, so be careful to capitalize
    # these responses here.
    flask_resp = Response(resp_binary)
    if response_compression is not None:
        flask_resp.headers['X-Compress'] = response_compression
    if response_encryption is not None:
        flask_resp.headers['X-Eamuse-Info'] = response_encryption
    return flask_resp
예제 #7
0
def receive_request(path: str) -> Response:
    proto = EAmuseProtocol()
    remote_address = request.headers.get('x-remote-address', None)
    compression = request.headers.get('x-compress', None)
    encryption = request.headers.get('x-eamuse-info', None)
    req = proto.decode(
        compression,
        encryption,
        request.data,
    )

    if req is None:
        # Nothing to do here
        return Response("Unrecognized packet!", 500)
    if req.name in {'soapenv:Envelope', 'soap:Envelope', 'methodCall'}:
        # We get lots of spam from random bots trying to SOAP
        # us up, so ignore this shit.
        return Response("Unrecognized packet!", 500)

    # Create and format config
    global config
    requestconfig = copy.copy(config)
    requestconfig['client'] = {
        'address': remote_address or request.remote_addr,
    }

    dataprovider = Data(requestconfig)
    try:
        dispatch = Dispatch(requestconfig, dataprovider, True)
        resp = dispatch.handle(req)

        if resp is None:
            # Nothing to do here
            dataprovider.local.network.put_event(
                'unhandled_packet',
                {
                    'request': str(req),
                },
            )
            return Response("No response generated", 404)

        compression = None

        data = proto.encode(
            compression,
            encryption,
            resp,
        )

        response = make_response(data)

        # Some old clients are case-sensitive, even though http spec says these
        # shouldn't matter, so capitalize correctly.
        if compression:
            response.headers['X-Compress'] = compression
        else:
            response.headers['X-Compress'] = 'none'
        if encryption:
            response.headers['X-Eamuse-Info'] = encryption

        return response
    except UnrecognizedPCBIDException as e:
        dataprovider.local.network.put_event(
            'unauthorized_pcbid',
            {
                'pcbid': e.pcbid,
                'model': e.model,
                'ip': e.ip,
            },
        )
        return Response("Unauthorized client", 403)
    except Exception:
        stack = traceback.format_exc()
        print(stack)
        dataprovider.local.network.put_event(
            'exception',
            {
                'service': 'xrpc',
                'request': str(req),
                'traceback': stack,
            },
        )
        return Response("Crash when handling packet!", 500)
    finally:
        dataprovider.close()