diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 094a4f25..9b5afb44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,6 +115,36 @@ jobs: env: COVERALLS_PARALLEL: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + manticore-server: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: 3.7 + + - name: 'Install tools' + run: | + # just command runner https://github.com/casey/just#pre-built-binaries + curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to "${HOME}/.local/bin" + sudo wget -O /usr/bin/solc https://github.com/ethereum/solidity/releases/download/v0.4.24/solc-static-linux + sudo chmod +x /usr/bin/solc + + - name: 'Lint MUI Server' + working-directory: server + run: | + just init + source venv/bin/activate + just lint + + - name: 'Test MUI Server' + working-directory: server + run: | + source venv/bin/activate + just test + # Send notification when all tests have finished to combine coverage results coverage-finish: needs: tests diff --git a/pyproject.toml b/pyproject.toml index 0721b0f6..f4bca6c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,9 @@ [tool.black] target-version = ['py36'] line-length = 100 +extend-exclude = ''' +( + venv + | server # Has its own formatting +) +''' diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 00000000..247a1830 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,4 @@ +manticore_server.egg-info/** +**/__pycache__ +dist/** +.mypy_cache/** diff --git a/server/README.md b/server/README.md new file mode 100644 index 00000000..18e36a89 --- /dev/null +++ b/server/README.md @@ -0,0 +1,20 @@ +# Manticore Server +Manticore Server is a gRPC service that acts as a wrapper around [Manticore](https://github.com/trailofbits/manticore), to support projects like the [Manticore User Interface (MUI)](https://github.com/trailofbits/ManticoreUI). Manticore Server is designed to allow developers to more easily create tools around Manticore that aren't in Python or to allow for Manticore to be run and managed in the cloud/remotely. + +❗NOTE❗: The server is not published or packaged anywhere and is intended to be installed from source in a clean Python virtual environment. The server is only tested for compatibility with the version of Manticore living in the parent directory; this version of Manticore is installed when installing the server. + +# Usage +Create a new Python virtual environment: + +```bash +python3 -m venv venv +source venv/bin/activate +``` + +Install the server with `pip install .`. This will install Manticore from the parent directory. + +The Manticore Server can be run via `manticore_server`, and it will run on port `50010`. + +Your Manticore Server client will require the relevant gRPC client code. You can find out how to generate gRPC client code in your desired language from [the gRPC website](https://grpc.io/docs/languages/). + +You may refer to the [Protobuf Specification](manticore_server/ManticoreServer.proto) for information about the RPC services provided and the message types. diff --git a/server/justfile b/server/justfile new file mode 100644 index 00000000..970e1fc4 --- /dev/null +++ b/server/justfile @@ -0,0 +1,25 @@ +init: + test -d venv || python3 -m venv venv + . venv/bin/activate; pip install -U setuptools pip wheel; pip install -e .[dev] + +format: + black . + isort . + +lint: + black --check . + isort --check . + mypy + +generate: + python3 setup.py generate + +build: generate + #!/usr/bin/env bash + set -euo + mkdir -p dist + py_ver=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') + python3 -m shiv -c manticore_server -o dist/manticore_server --reproducible --no-modify --python "/usr/bin/env python${py_ver}" --compressed . + +test: + python3 -m unittest tests/test_*.py diff --git a/server/manticore_server/ManticoreServer.proto b/server/manticore_server/ManticoreServer.proto new file mode 100644 index 00000000..1ab5f1d8 --- /dev/null +++ b/server/manticore_server/ManticoreServer.proto @@ -0,0 +1,107 @@ +syntax = "proto3"; + +package manticore_server; + +service ManticoreServer { + rpc StartNative(NativeArguments) returns (ManticoreInstance) {} + rpc StartEVM(EVMArguments) returns (ManticoreInstance) {} + rpc Terminate(ManticoreInstance) returns (TerminateResponse) {} + rpc GetStateList(ManticoreInstance) returns (ManticoreStateList) {} + rpc GetMessageList(ManticoreInstance) returns (ManticoreMessageList) {} + rpc CheckManticoreRunning(ManticoreInstance) returns (ManticoreRunningStatus) {} + rpc StopServer(StopServerRequest) returns (StopServerResponse) {} + rpc ControlState(ControlStateRequest) returns (ControlStateResponse) {} +} + +// LogMessage and StateList message types have "Manticore" in their names to distinguish them from those in mserialize + +message ManticoreLogMessage { + string content = 1; +} + +message ManticoreMessageList { + repeated ManticoreLogMessage messages = 2; +} + +message ManticoreState { + int32 state_id = 3; + uint64 pc = 10; + optional int32 parent_id = 28; + repeated int32 children_ids = 29; +} + +message ManticoreStateList { + + // state categories in Manticore - based on manticore enums StateStatus and StateList + repeated ManticoreState active_states = 4; + repeated ManticoreState waiting_states = 5; + repeated ManticoreState forked_states = 6; + repeated ManticoreState errored_states = 7; + repeated ManticoreState complete_states = 8; + repeated ManticoreState paused_states = 33; +} + +message ManticoreInstance { + string uuid = 9; +} + +message TerminateResponse {} + +message Hook { + + enum HookType { + FIND = 0; + AVOID = 1; + CUSTOM = 2; + GLOBAL = 3; + } + + optional uint64 address = 26; + HookType type = 27; + optional string func_text = 31; +} + +message NativeArguments { + string program_path = 11; + repeated string binary_args = 16; + repeated string envp = 17; + repeated string symbolic_files = 18; + optional string concrete_start = 19; + optional string stdin_size = 20; + optional string additional_mcore_args = 21; + repeated Hook hooks = 30; + optional uint64 emulate_until = 32; +} + +message EVMArguments { + string contract_path = 12; + string contract_name = 13; + string solc_bin = 14; + optional string tx_limit = 22; + optional string tx_account = 23; + repeated string detectors_to_exclude = 24; + optional string additional_flags = 25; +} + +message ManticoreRunningStatus { + bool is_running = 15; +} + +message StopServerRequest {} + +message StopServerResponse {} + +message ControlStateRequest { + + enum StateAction { + RESUME = 0; + PAUSE = 1; + KILL = 2; + } + + int32 state_id = 34; + ManticoreInstance manticore_instance = 35; + StateAction action = 36; +} + +message ControlStateResponse {} diff --git a/server/manticore_server/ManticoreServer_pb2.py b/server/manticore_server/ManticoreServer_pb2.py new file mode 100644 index 00000000..183e4575 --- /dev/null +++ b/server/manticore_server/ManticoreServer_pb2.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: manticore_server/ManticoreServer.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&manticore_server/ManticoreServer.proto\x12\x10manticore_server\"&\n\x13ManticoreLogMessage\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\"O\n\x14ManticoreMessageList\x12\x37\n\x08messages\x18\x02 \x03(\x0b\x32%.manticore_server.ManticoreLogMessage\"j\n\x0eManticoreState\x12\x10\n\x08state_id\x18\x03 \x01(\x05\x12\n\n\x02pc\x18\n \x01(\x04\x12\x16\n\tparent_id\x18\x1c \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x63hildren_ids\x18\x1d \x03(\x05\x42\x0c\n\n_parent_id\"\xee\x02\n\x12ManticoreStateList\x12\x37\n\ractive_states\x18\x04 \x03(\x0b\x32 .manticore_server.ManticoreState\x12\x38\n\x0ewaiting_states\x18\x05 \x03(\x0b\x32 .manticore_server.ManticoreState\x12\x37\n\rforked_states\x18\x06 \x03(\x0b\x32 .manticore_server.ManticoreState\x12\x38\n\x0e\x65rrored_states\x18\x07 \x03(\x0b\x32 .manticore_server.ManticoreState\x12\x39\n\x0f\x63omplete_states\x18\x08 \x03(\x0b\x32 .manticore_server.ManticoreState\x12\x37\n\rpaused_states\x18! \x03(\x0b\x32 .manticore_server.ManticoreState\"!\n\x11ManticoreInstance\x12\x0c\n\x04uuid\x18\t \x01(\t\"\x13\n\x11TerminateResponse\"\xb6\x01\n\x04Hook\x12\x14\n\x07\x61\x64\x64ress\x18\x1a \x01(\x04H\x00\x88\x01\x01\x12-\n\x04type\x18\x1b \x01(\x0e\x32\x1f.manticore_server.Hook.HookType\x12\x16\n\tfunc_text\x18\x1f \x01(\tH\x01\x88\x01\x01\"7\n\x08HookType\x12\x08\n\x04\x46IND\x10\x00\x12\t\n\x05\x41VOID\x10\x01\x12\n\n\x06\x43USTOM\x10\x02\x12\n\n\x06GLOBAL\x10\x03\x42\n\n\x08_addressB\x0c\n\n_func_text\"\xcd\x02\n\x0fNativeArguments\x12\x14\n\x0cprogram_path\x18\x0b \x01(\t\x12\x13\n\x0b\x62inary_args\x18\x10 \x03(\t\x12\x0c\n\x04\x65nvp\x18\x11 \x03(\t\x12\x16\n\x0esymbolic_files\x18\x12 \x03(\t\x12\x1b\n\x0e\x63oncrete_start\x18\x13 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nstdin_size\x18\x14 \x01(\tH\x01\x88\x01\x01\x12\"\n\x15\x61\x64\x64itional_mcore_args\x18\x15 \x01(\tH\x02\x88\x01\x01\x12%\n\x05hooks\x18\x1e \x03(\x0b\x32\x16.manticore_server.Hook\x12\x1a\n\remulate_until\x18 \x01(\x04H\x03\x88\x01\x01\x42\x11\n\x0f_concrete_startB\r\n\x0b_stdin_sizeB\x18\n\x16_additional_mcore_argsB\x10\n\x0e_emulate_until\"\xec\x01\n\x0c\x45VMArguments\x12\x15\n\rcontract_path\x18\x0c \x01(\t\x12\x15\n\rcontract_name\x18\r \x01(\t\x12\x10\n\x08solc_bin\x18\x0e \x01(\t\x12\x15\n\x08tx_limit\x18\x16 \x01(\tH\x00\x88\x01\x01\x12\x17\n\ntx_account\x18\x17 \x01(\tH\x01\x88\x01\x01\x12\x1c\n\x14\x64\x65tectors_to_exclude\x18\x18 \x03(\t\x12\x1d\n\x10\x61\x64\x64itional_flags\x18\x19 \x01(\tH\x02\x88\x01\x01\x42\x0b\n\t_tx_limitB\r\n\x0b_tx_accountB\x13\n\x11_additional_flags\",\n\x16ManticoreRunningStatus\x12\x12\n\nis_running\x18\x0f \x01(\x08\"\x13\n\x11StopServerRequest\"\x14\n\x12StopServerResponse\"\xdb\x01\n\x13\x43ontrolStateRequest\x12\x10\n\x08state_id\x18\" \x01(\x05\x12?\n\x12manticore_instance\x18# \x01(\x0b\x32#.manticore_server.ManticoreInstance\x12\x41\n\x06\x61\x63tion\x18$ \x01(\x0e\x32\x31.manticore_server.ControlStateRequest.StateAction\".\n\x0bStateAction\x12\n\n\x06RESUME\x10\x00\x12\t\n\x05PAUSE\x10\x01\x12\x08\n\x04KILL\x10\x02\"\x16\n\x14\x43ontrolStateResponse2\xfa\x05\n\x0fManticoreServer\x12W\n\x0bStartNative\x12!.manticore_server.NativeArguments\x1a#.manticore_server.ManticoreInstance\"\x00\x12Q\n\x08StartEVM\x12\x1e.manticore_server.EVMArguments\x1a#.manticore_server.ManticoreInstance\"\x00\x12W\n\tTerminate\x12#.manticore_server.ManticoreInstance\x1a#.manticore_server.TerminateResponse\"\x00\x12[\n\x0cGetStateList\x12#.manticore_server.ManticoreInstance\x1a$.manticore_server.ManticoreStateList\"\x00\x12_\n\x0eGetMessageList\x12#.manticore_server.ManticoreInstance\x1a&.manticore_server.ManticoreMessageList\"\x00\x12h\n\x15\x43heckManticoreRunning\x12#.manticore_server.ManticoreInstance\x1a(.manticore_server.ManticoreRunningStatus\"\x00\x12Y\n\nStopServer\x12#.manticore_server.StopServerRequest\x1a$.manticore_server.StopServerResponse\"\x00\x12_\n\x0c\x43ontrolState\x12%.manticore_server.ControlStateRequest\x1a&.manticore_server.ControlStateResponse\"\x00\x62\x06proto3') + + + +_MANTICORELOGMESSAGE = DESCRIPTOR.message_types_by_name['ManticoreLogMessage'] +_MANTICOREMESSAGELIST = DESCRIPTOR.message_types_by_name['ManticoreMessageList'] +_MANTICORESTATE = DESCRIPTOR.message_types_by_name['ManticoreState'] +_MANTICORESTATELIST = DESCRIPTOR.message_types_by_name['ManticoreStateList'] +_MANTICOREINSTANCE = DESCRIPTOR.message_types_by_name['ManticoreInstance'] +_TERMINATERESPONSE = DESCRIPTOR.message_types_by_name['TerminateResponse'] +_HOOK = DESCRIPTOR.message_types_by_name['Hook'] +_NATIVEARGUMENTS = DESCRIPTOR.message_types_by_name['NativeArguments'] +_EVMARGUMENTS = DESCRIPTOR.message_types_by_name['EVMArguments'] +_MANTICORERUNNINGSTATUS = DESCRIPTOR.message_types_by_name['ManticoreRunningStatus'] +_STOPSERVERREQUEST = DESCRIPTOR.message_types_by_name['StopServerRequest'] +_STOPSERVERRESPONSE = DESCRIPTOR.message_types_by_name['StopServerResponse'] +_CONTROLSTATEREQUEST = DESCRIPTOR.message_types_by_name['ControlStateRequest'] +_CONTROLSTATERESPONSE = DESCRIPTOR.message_types_by_name['ControlStateResponse'] +_HOOK_HOOKTYPE = _HOOK.enum_types_by_name['HookType'] +_CONTROLSTATEREQUEST_STATEACTION = _CONTROLSTATEREQUEST.enum_types_by_name['StateAction'] +ManticoreLogMessage = _reflection.GeneratedProtocolMessageType('ManticoreLogMessage', (_message.Message,), { + 'DESCRIPTOR' : _MANTICORELOGMESSAGE, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.ManticoreLogMessage) + }) +_sym_db.RegisterMessage(ManticoreLogMessage) + +ManticoreMessageList = _reflection.GeneratedProtocolMessageType('ManticoreMessageList', (_message.Message,), { + 'DESCRIPTOR' : _MANTICOREMESSAGELIST, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.ManticoreMessageList) + }) +_sym_db.RegisterMessage(ManticoreMessageList) + +ManticoreState = _reflection.GeneratedProtocolMessageType('ManticoreState', (_message.Message,), { + 'DESCRIPTOR' : _MANTICORESTATE, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.ManticoreState) + }) +_sym_db.RegisterMessage(ManticoreState) + +ManticoreStateList = _reflection.GeneratedProtocolMessageType('ManticoreStateList', (_message.Message,), { + 'DESCRIPTOR' : _MANTICORESTATELIST, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.ManticoreStateList) + }) +_sym_db.RegisterMessage(ManticoreStateList) + +ManticoreInstance = _reflection.GeneratedProtocolMessageType('ManticoreInstance', (_message.Message,), { + 'DESCRIPTOR' : _MANTICOREINSTANCE, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.ManticoreInstance) + }) +_sym_db.RegisterMessage(ManticoreInstance) + +TerminateResponse = _reflection.GeneratedProtocolMessageType('TerminateResponse', (_message.Message,), { + 'DESCRIPTOR' : _TERMINATERESPONSE, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.TerminateResponse) + }) +_sym_db.RegisterMessage(TerminateResponse) + +Hook = _reflection.GeneratedProtocolMessageType('Hook', (_message.Message,), { + 'DESCRIPTOR' : _HOOK, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.Hook) + }) +_sym_db.RegisterMessage(Hook) + +NativeArguments = _reflection.GeneratedProtocolMessageType('NativeArguments', (_message.Message,), { + 'DESCRIPTOR' : _NATIVEARGUMENTS, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.NativeArguments) + }) +_sym_db.RegisterMessage(NativeArguments) + +EVMArguments = _reflection.GeneratedProtocolMessageType('EVMArguments', (_message.Message,), { + 'DESCRIPTOR' : _EVMARGUMENTS, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.EVMArguments) + }) +_sym_db.RegisterMessage(EVMArguments) + +ManticoreRunningStatus = _reflection.GeneratedProtocolMessageType('ManticoreRunningStatus', (_message.Message,), { + 'DESCRIPTOR' : _MANTICORERUNNINGSTATUS, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.ManticoreRunningStatus) + }) +_sym_db.RegisterMessage(ManticoreRunningStatus) + +StopServerRequest = _reflection.GeneratedProtocolMessageType('StopServerRequest', (_message.Message,), { + 'DESCRIPTOR' : _STOPSERVERREQUEST, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.StopServerRequest) + }) +_sym_db.RegisterMessage(StopServerRequest) + +StopServerResponse = _reflection.GeneratedProtocolMessageType('StopServerResponse', (_message.Message,), { + 'DESCRIPTOR' : _STOPSERVERRESPONSE, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.StopServerResponse) + }) +_sym_db.RegisterMessage(StopServerResponse) + +ControlStateRequest = _reflection.GeneratedProtocolMessageType('ControlStateRequest', (_message.Message,), { + 'DESCRIPTOR' : _CONTROLSTATEREQUEST, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.ControlStateRequest) + }) +_sym_db.RegisterMessage(ControlStateRequest) + +ControlStateResponse = _reflection.GeneratedProtocolMessageType('ControlStateResponse', (_message.Message,), { + 'DESCRIPTOR' : _CONTROLSTATERESPONSE, + '__module__' : 'manticore_server.ManticoreServer_pb2' + # @@protoc_insertion_point(class_scope:manticore_server.ControlStateResponse) + }) +_sym_db.RegisterMessage(ControlStateResponse) + +_MANTICORESERVER = DESCRIPTOR.services_by_name['ManticoreServer'] +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _MANTICORELOGMESSAGE._serialized_start=60 + _MANTICORELOGMESSAGE._serialized_end=98 + _MANTICOREMESSAGELIST._serialized_start=100 + _MANTICOREMESSAGELIST._serialized_end=179 + _MANTICORESTATE._serialized_start=181 + _MANTICORESTATE._serialized_end=287 + _MANTICORESTATELIST._serialized_start=290 + _MANTICORESTATELIST._serialized_end=656 + _MANTICOREINSTANCE._serialized_start=658 + _MANTICOREINSTANCE._serialized_end=691 + _TERMINATERESPONSE._serialized_start=693 + _TERMINATERESPONSE._serialized_end=712 + _HOOK._serialized_start=715 + _HOOK._serialized_end=897 + _HOOK_HOOKTYPE._serialized_start=816 + _HOOK_HOOKTYPE._serialized_end=871 + _NATIVEARGUMENTS._serialized_start=900 + _NATIVEARGUMENTS._serialized_end=1233 + _EVMARGUMENTS._serialized_start=1236 + _EVMARGUMENTS._serialized_end=1472 + _MANTICORERUNNINGSTATUS._serialized_start=1474 + _MANTICORERUNNINGSTATUS._serialized_end=1518 + _STOPSERVERREQUEST._serialized_start=1520 + _STOPSERVERREQUEST._serialized_end=1539 + _STOPSERVERRESPONSE._serialized_start=1541 + _STOPSERVERRESPONSE._serialized_end=1561 + _CONTROLSTATEREQUEST._serialized_start=1564 + _CONTROLSTATEREQUEST._serialized_end=1783 + _CONTROLSTATEREQUEST_STATEACTION._serialized_start=1737 + _CONTROLSTATEREQUEST_STATEACTION._serialized_end=1783 + _CONTROLSTATERESPONSE._serialized_start=1785 + _CONTROLSTATERESPONSE._serialized_end=1807 + _MANTICORESERVER._serialized_start=1810 + _MANTICORESERVER._serialized_end=2572 +# @@protoc_insertion_point(module_scope) diff --git a/server/manticore_server/ManticoreServer_pb2.pyi b/server/manticore_server/ManticoreServer_pb2.pyi new file mode 100644 index 00000000..c9147c2f --- /dev/null +++ b/server/manticore_server/ManticoreServer_pb2.pyi @@ -0,0 +1,346 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class ManticoreLogMessage(google.protobuf.message.Message): + """LogMessage and StateList message types have "Manticore" in their names to distinguish them from those in mserialize""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + CONTENT_FIELD_NUMBER: builtins.int + content: builtins.str + def __init__( + self, + *, + content: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["content", b"content"]) -> None: ... + +global___ManticoreLogMessage = ManticoreLogMessage + +class ManticoreMessageList(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGES_FIELD_NUMBER: builtins.int + @property + def messages(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ManticoreLogMessage]: ... + def __init__( + self, + *, + messages: collections.abc.Iterable[global___ManticoreLogMessage] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["messages", b"messages"]) -> None: ... + +global___ManticoreMessageList = ManticoreMessageList + +class ManticoreState(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STATE_ID_FIELD_NUMBER: builtins.int + PC_FIELD_NUMBER: builtins.int + PARENT_ID_FIELD_NUMBER: builtins.int + CHILDREN_IDS_FIELD_NUMBER: builtins.int + state_id: builtins.int + pc: builtins.int + parent_id: builtins.int + @property + def children_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: ... + def __init__( + self, + *, + state_id: builtins.int = ..., + pc: builtins.int = ..., + parent_id: builtins.int | None = ..., + children_ids: collections.abc.Iterable[builtins.int] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_parent_id", b"_parent_id", "parent_id", b"parent_id"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_parent_id", b"_parent_id", "children_ids", b"children_ids", "parent_id", b"parent_id", "pc", b"pc", "state_id", b"state_id"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["_parent_id", b"_parent_id"]) -> typing_extensions.Literal["parent_id"] | None: ... + +global___ManticoreState = ManticoreState + +class ManticoreStateList(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ACTIVE_STATES_FIELD_NUMBER: builtins.int + WAITING_STATES_FIELD_NUMBER: builtins.int + FORKED_STATES_FIELD_NUMBER: builtins.int + ERRORED_STATES_FIELD_NUMBER: builtins.int + COMPLETE_STATES_FIELD_NUMBER: builtins.int + PAUSED_STATES_FIELD_NUMBER: builtins.int + @property + def active_states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ManticoreState]: + """state categories in Manticore - based on manticore enums StateStatus and StateList""" + @property + def waiting_states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ManticoreState]: ... + @property + def forked_states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ManticoreState]: ... + @property + def errored_states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ManticoreState]: ... + @property + def complete_states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ManticoreState]: ... + @property + def paused_states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ManticoreState]: ... + def __init__( + self, + *, + active_states: collections.abc.Iterable[global___ManticoreState] | None = ..., + waiting_states: collections.abc.Iterable[global___ManticoreState] | None = ..., + forked_states: collections.abc.Iterable[global___ManticoreState] | None = ..., + errored_states: collections.abc.Iterable[global___ManticoreState] | None = ..., + complete_states: collections.abc.Iterable[global___ManticoreState] | None = ..., + paused_states: collections.abc.Iterable[global___ManticoreState] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["active_states", b"active_states", "complete_states", b"complete_states", "errored_states", b"errored_states", "forked_states", b"forked_states", "paused_states", b"paused_states", "waiting_states", b"waiting_states"]) -> None: ... + +global___ManticoreStateList = ManticoreStateList + +class ManticoreInstance(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + UUID_FIELD_NUMBER: builtins.int + uuid: builtins.str + def __init__( + self, + *, + uuid: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["uuid", b"uuid"]) -> None: ... + +global___ManticoreInstance = ManticoreInstance + +class TerminateResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___TerminateResponse = TerminateResponse + +class Hook(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _HookType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _HookTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Hook._HookType.ValueType], builtins.type): # noqa: F821 + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + FIND: Hook._HookType.ValueType # 0 + AVOID: Hook._HookType.ValueType # 1 + CUSTOM: Hook._HookType.ValueType # 2 + GLOBAL: Hook._HookType.ValueType # 3 + + class HookType(_HookType, metaclass=_HookTypeEnumTypeWrapper): ... + FIND: Hook.HookType.ValueType # 0 + AVOID: Hook.HookType.ValueType # 1 + CUSTOM: Hook.HookType.ValueType # 2 + GLOBAL: Hook.HookType.ValueType # 3 + + ADDRESS_FIELD_NUMBER: builtins.int + TYPE_FIELD_NUMBER: builtins.int + FUNC_TEXT_FIELD_NUMBER: builtins.int + address: builtins.int + type: global___Hook.HookType.ValueType + func_text: builtins.str + def __init__( + self, + *, + address: builtins.int | None = ..., + type: global___Hook.HookType.ValueType = ..., + func_text: builtins.str | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_address", b"_address", "_func_text", b"_func_text", "address", b"address", "func_text", b"func_text"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_address", b"_address", "_func_text", b"_func_text", "address", b"address", "func_text", b"func_text", "type", b"type"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_address", b"_address"]) -> typing_extensions.Literal["address"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_func_text", b"_func_text"]) -> typing_extensions.Literal["func_text"] | None: ... + +global___Hook = Hook + +class NativeArguments(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PROGRAM_PATH_FIELD_NUMBER: builtins.int + BINARY_ARGS_FIELD_NUMBER: builtins.int + ENVP_FIELD_NUMBER: builtins.int + SYMBOLIC_FILES_FIELD_NUMBER: builtins.int + CONCRETE_START_FIELD_NUMBER: builtins.int + STDIN_SIZE_FIELD_NUMBER: builtins.int + ADDITIONAL_MCORE_ARGS_FIELD_NUMBER: builtins.int + HOOKS_FIELD_NUMBER: builtins.int + EMULATE_UNTIL_FIELD_NUMBER: builtins.int + program_path: builtins.str + @property + def binary_args(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... + @property + def envp(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... + @property + def symbolic_files(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... + concrete_start: builtins.str + stdin_size: builtins.str + additional_mcore_args: builtins.str + @property + def hooks(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Hook]: ... + emulate_until: builtins.int + def __init__( + self, + *, + program_path: builtins.str = ..., + binary_args: collections.abc.Iterable[builtins.str] | None = ..., + envp: collections.abc.Iterable[builtins.str] | None = ..., + symbolic_files: collections.abc.Iterable[builtins.str] | None = ..., + concrete_start: builtins.str | None = ..., + stdin_size: builtins.str | None = ..., + additional_mcore_args: builtins.str | None = ..., + hooks: collections.abc.Iterable[global___Hook] | None = ..., + emulate_until: builtins.int | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_additional_mcore_args", b"_additional_mcore_args", "_concrete_start", b"_concrete_start", "_emulate_until", b"_emulate_until", "_stdin_size", b"_stdin_size", "additional_mcore_args", b"additional_mcore_args", "concrete_start", b"concrete_start", "emulate_until", b"emulate_until", "stdin_size", b"stdin_size"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_additional_mcore_args", b"_additional_mcore_args", "_concrete_start", b"_concrete_start", "_emulate_until", b"_emulate_until", "_stdin_size", b"_stdin_size", "additional_mcore_args", b"additional_mcore_args", "binary_args", b"binary_args", "concrete_start", b"concrete_start", "emulate_until", b"emulate_until", "envp", b"envp", "hooks", b"hooks", "program_path", b"program_path", "stdin_size", b"stdin_size", "symbolic_files", b"symbolic_files"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_additional_mcore_args", b"_additional_mcore_args"]) -> typing_extensions.Literal["additional_mcore_args"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_concrete_start", b"_concrete_start"]) -> typing_extensions.Literal["concrete_start"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_emulate_until", b"_emulate_until"]) -> typing_extensions.Literal["emulate_until"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_stdin_size", b"_stdin_size"]) -> typing_extensions.Literal["stdin_size"] | None: ... + +global___NativeArguments = NativeArguments + +class EVMArguments(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + CONTRACT_PATH_FIELD_NUMBER: builtins.int + CONTRACT_NAME_FIELD_NUMBER: builtins.int + SOLC_BIN_FIELD_NUMBER: builtins.int + TX_LIMIT_FIELD_NUMBER: builtins.int + TX_ACCOUNT_FIELD_NUMBER: builtins.int + DETECTORS_TO_EXCLUDE_FIELD_NUMBER: builtins.int + ADDITIONAL_FLAGS_FIELD_NUMBER: builtins.int + contract_path: builtins.str + contract_name: builtins.str + solc_bin: builtins.str + tx_limit: builtins.str + tx_account: builtins.str + @property + def detectors_to_exclude(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... + additional_flags: builtins.str + def __init__( + self, + *, + contract_path: builtins.str = ..., + contract_name: builtins.str = ..., + solc_bin: builtins.str = ..., + tx_limit: builtins.str | None = ..., + tx_account: builtins.str | None = ..., + detectors_to_exclude: collections.abc.Iterable[builtins.str] | None = ..., + additional_flags: builtins.str | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_additional_flags", b"_additional_flags", "_tx_account", b"_tx_account", "_tx_limit", b"_tx_limit", "additional_flags", b"additional_flags", "tx_account", b"tx_account", "tx_limit", b"tx_limit"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_additional_flags", b"_additional_flags", "_tx_account", b"_tx_account", "_tx_limit", b"_tx_limit", "additional_flags", b"additional_flags", "contract_name", b"contract_name", "contract_path", b"contract_path", "detectors_to_exclude", b"detectors_to_exclude", "solc_bin", b"solc_bin", "tx_account", b"tx_account", "tx_limit", b"tx_limit"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_additional_flags", b"_additional_flags"]) -> typing_extensions.Literal["additional_flags"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_tx_account", b"_tx_account"]) -> typing_extensions.Literal["tx_account"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_tx_limit", b"_tx_limit"]) -> typing_extensions.Literal["tx_limit"] | None: ... + +global___EVMArguments = EVMArguments + +class ManticoreRunningStatus(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + IS_RUNNING_FIELD_NUMBER: builtins.int + is_running: builtins.bool + def __init__( + self, + *, + is_running: builtins.bool = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["is_running", b"is_running"]) -> None: ... + +global___ManticoreRunningStatus = ManticoreRunningStatus + +class StopServerRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___StopServerRequest = StopServerRequest + +class StopServerResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___StopServerResponse = StopServerResponse + +class ControlStateRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _StateAction: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _StateActionEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ControlStateRequest._StateAction.ValueType], builtins.type): # noqa: F821 + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + RESUME: ControlStateRequest._StateAction.ValueType # 0 + PAUSE: ControlStateRequest._StateAction.ValueType # 1 + KILL: ControlStateRequest._StateAction.ValueType # 2 + + class StateAction(_StateAction, metaclass=_StateActionEnumTypeWrapper): ... + RESUME: ControlStateRequest.StateAction.ValueType # 0 + PAUSE: ControlStateRequest.StateAction.ValueType # 1 + KILL: ControlStateRequest.StateAction.ValueType # 2 + + STATE_ID_FIELD_NUMBER: builtins.int + MANTICORE_INSTANCE_FIELD_NUMBER: builtins.int + ACTION_FIELD_NUMBER: builtins.int + state_id: builtins.int + @property + def manticore_instance(self) -> global___ManticoreInstance: ... + action: global___ControlStateRequest.StateAction.ValueType + def __init__( + self, + *, + state_id: builtins.int = ..., + manticore_instance: global___ManticoreInstance | None = ..., + action: global___ControlStateRequest.StateAction.ValueType = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["manticore_instance", b"manticore_instance"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["action", b"action", "manticore_instance", b"manticore_instance", "state_id", b"state_id"]) -> None: ... + +global___ControlStateRequest = ControlStateRequest + +class ControlStateResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___ControlStateResponse = ControlStateResponse diff --git a/server/manticore_server/ManticoreServer_pb2_grpc.py b/server/manticore_server/ManticoreServer_pb2_grpc.py new file mode 100644 index 00000000..84906b8c --- /dev/null +++ b/server/manticore_server/ManticoreServer_pb2_grpc.py @@ -0,0 +1,297 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from manticore_server import ManticoreServer_pb2 as manticore__server_dot_ManticoreServer__pb2 + + +class ManticoreServerStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.StartNative = channel.unary_unary( + '/manticore_server.ManticoreServer/StartNative', + request_serializer=manticore__server_dot_ManticoreServer__pb2.NativeArguments.SerializeToString, + response_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.FromString, + ) + self.StartEVM = channel.unary_unary( + '/manticore_server.ManticoreServer/StartEVM', + request_serializer=manticore__server_dot_ManticoreServer__pb2.EVMArguments.SerializeToString, + response_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.FromString, + ) + self.Terminate = channel.unary_unary( + '/manticore_server.ManticoreServer/Terminate', + request_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + response_deserializer=manticore__server_dot_ManticoreServer__pb2.TerminateResponse.FromString, + ) + self.GetStateList = channel.unary_unary( + '/manticore_server.ManticoreServer/GetStateList', + request_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + response_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreStateList.FromString, + ) + self.GetMessageList = channel.unary_unary( + '/manticore_server.ManticoreServer/GetMessageList', + request_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + response_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreMessageList.FromString, + ) + self.CheckManticoreRunning = channel.unary_unary( + '/manticore_server.ManticoreServer/CheckManticoreRunning', + request_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + response_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreRunningStatus.FromString, + ) + self.StopServer = channel.unary_unary( + '/manticore_server.ManticoreServer/StopServer', + request_serializer=manticore__server_dot_ManticoreServer__pb2.StopServerRequest.SerializeToString, + response_deserializer=manticore__server_dot_ManticoreServer__pb2.StopServerResponse.FromString, + ) + self.ControlState = channel.unary_unary( + '/manticore_server.ManticoreServer/ControlState', + request_serializer=manticore__server_dot_ManticoreServer__pb2.ControlStateRequest.SerializeToString, + response_deserializer=manticore__server_dot_ManticoreServer__pb2.ControlStateResponse.FromString, + ) + + +class ManticoreServerServicer(object): + """Missing associated documentation comment in .proto file.""" + + def StartNative(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StartEVM(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Terminate(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetStateList(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetMessageList(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CheckManticoreRunning(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StopServer(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ControlState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ManticoreServerServicer_to_server(servicer, server): + rpc_method_handlers = { + 'StartNative': grpc.unary_unary_rpc_method_handler( + servicer.StartNative, + request_deserializer=manticore__server_dot_ManticoreServer__pb2.NativeArguments.FromString, + response_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + ), + 'StartEVM': grpc.unary_unary_rpc_method_handler( + servicer.StartEVM, + request_deserializer=manticore__server_dot_ManticoreServer__pb2.EVMArguments.FromString, + response_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + ), + 'Terminate': grpc.unary_unary_rpc_method_handler( + servicer.Terminate, + request_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.FromString, + response_serializer=manticore__server_dot_ManticoreServer__pb2.TerminateResponse.SerializeToString, + ), + 'GetStateList': grpc.unary_unary_rpc_method_handler( + servicer.GetStateList, + request_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.FromString, + response_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreStateList.SerializeToString, + ), + 'GetMessageList': grpc.unary_unary_rpc_method_handler( + servicer.GetMessageList, + request_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.FromString, + response_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreMessageList.SerializeToString, + ), + 'CheckManticoreRunning': grpc.unary_unary_rpc_method_handler( + servicer.CheckManticoreRunning, + request_deserializer=manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.FromString, + response_serializer=manticore__server_dot_ManticoreServer__pb2.ManticoreRunningStatus.SerializeToString, + ), + 'StopServer': grpc.unary_unary_rpc_method_handler( + servicer.StopServer, + request_deserializer=manticore__server_dot_ManticoreServer__pb2.StopServerRequest.FromString, + response_serializer=manticore__server_dot_ManticoreServer__pb2.StopServerResponse.SerializeToString, + ), + 'ControlState': grpc.unary_unary_rpc_method_handler( + servicer.ControlState, + request_deserializer=manticore__server_dot_ManticoreServer__pb2.ControlStateRequest.FromString, + response_serializer=manticore__server_dot_ManticoreServer__pb2.ControlStateResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'manticore_server.ManticoreServer', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class ManticoreServer(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def StartNative(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/manticore_server.ManticoreServer/StartNative', + manticore__server_dot_ManticoreServer__pb2.NativeArguments.SerializeToString, + manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def StartEVM(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/manticore_server.ManticoreServer/StartEVM', + manticore__server_dot_ManticoreServer__pb2.EVMArguments.SerializeToString, + manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Terminate(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/manticore_server.ManticoreServer/Terminate', + manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + manticore__server_dot_ManticoreServer__pb2.TerminateResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetStateList(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/manticore_server.ManticoreServer/GetStateList', + manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + manticore__server_dot_ManticoreServer__pb2.ManticoreStateList.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetMessageList(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/manticore_server.ManticoreServer/GetMessageList', + manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + manticore__server_dot_ManticoreServer__pb2.ManticoreMessageList.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def CheckManticoreRunning(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/manticore_server.ManticoreServer/CheckManticoreRunning', + manticore__server_dot_ManticoreServer__pb2.ManticoreInstance.SerializeToString, + manticore__server_dot_ManticoreServer__pb2.ManticoreRunningStatus.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def StopServer(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/manticore_server.ManticoreServer/StopServer', + manticore__server_dot_ManticoreServer__pb2.StopServerRequest.SerializeToString, + manticore__server_dot_ManticoreServer__pb2.StopServerResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ControlState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/manticore_server.ManticoreServer/ControlState', + manticore__server_dot_ManticoreServer__pb2.ControlStateRequest.SerializeToString, + manticore__server_dot_ManticoreServer__pb2.ControlStateResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/server/manticore_server/__init__.py b/server/manticore_server/__init__.py new file mode 100644 index 00000000..e739a04e --- /dev/null +++ b/server/manticore_server/__init__.py @@ -0,0 +1 @@ +from . import manticore_server diff --git a/server/manticore_server/evm_utils.py b/server/manticore_server/evm_utils.py new file mode 100644 index 00000000..ca37077e --- /dev/null +++ b/server/manticore_server/evm_utils.py @@ -0,0 +1,200 @@ +import argparse +import logging +import shlex +from typing import List, Type + +from crytic_compile import cryticparser +from manticore.core.plugin import Profiler +from manticore.ethereum import ( + DetectDelegatecall, + DetectEnvInstruction, + DetectExternalCallAndLeak, + DetectIntegerOverflow, + DetectInvalid, + DetectManipulableBalance, + Detector, + DetectReentrancyAdvanced, + DetectReentrancySimple, + DetectSuicidal, + DetectUninitializedMemory, + DetectUninitializedStorage, + DetectUnusedRetVal, + ManticoreEVM, +) +from manticore.ethereum.plugins import ( + FilterFunctions, + KeepOnlyIfStorageChanges, + LoopDepthLimiter, + SkipRevertBasicBlocks, + VerboseTrace, +) +from manticore.utils import config + +logger = logging.getLogger(__name__) + + +def get_detectors_classes() -> List[Type[Detector]]: + return [ + DetectInvalid, + DetectIntegerOverflow, + DetectUninitializedStorage, + DetectUninitializedMemory, + DetectReentrancySimple, + DetectReentrancyAdvanced, + DetectUnusedRetVal, + DetectSuicidal, + DetectDelegatecall, + DetectExternalCallAndLeak, + DetectEnvInstruction, + DetectManipulableBalance, + # The RaceCondition detector has been disabled for now as it seems to collide with IntegerOverflow detector + # DetectRaceCondition + ] + + +def parse_detectors(detectors_to_exclude: List[str]) -> List[Type[Detector]]: + """returns a list of detectors that should be used""" + all_detector_classes = get_detectors_classes() + all_detector_args = map(lambda x: x.ARGUMENT, all_detector_classes) + for d in detectors_to_exclude: + if d not in all_detector_args: + raise Exception( + f"{d} is not a detector name, must be one of {list(all_detector_args)}. See also `--list-detectors`." + ) + + return [d for d in all_detector_classes if d.ARGUMENT not in detectors_to_exclude] + + +# TODO(ekilmer): This could be refactored to reduce duplication in main manticore package +def setup_detectors_flags( + detectors_to_exclude: List[str], additional_flags: str, m: ManticoreEVM +) -> argparse.Namespace: + """parse and apply additional arguments for manticore EVM execution, CLI-style""" + + parser = argparse.ArgumentParser( + description="Symbolic execution tool", + prog="manticore", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + consts = config.get_group("main") + consts.add("profile", default=False, description="Enable worker profiling mode") + consts.add( + "explore_balance", + default=False, + description="Explore states in which only the balance was changed", + ) + + consts.add( + "skip_reverts", + default=False, + description="Simply avoid exploring basic blocks that end in a REVERT", + ) + + # Add crytic compile arguments + # See https://github.com/crytic/crytic-compile/wiki/Configuration + cryticparser.init(parser) + + eth_flags = parser.add_argument_group("Ethereum flags") + + eth_flags.add_argument( + "--verbose-trace", + action="store_true", + help="Dump an extra verbose trace for each state", + ) + + eth_flags.add_argument( + "--txnocoverage", + action="store_true", + help="Do not use coverage as stopping criteria", + ) + + eth_flags.add_argument( + "--txnoether", + action="store_true", + help="Do not attempt to send ether to contract", + ) + + eth_flags.add_argument( + "--txpreconstrain", + action="store_true", + help="Constrain human transactions to avoid exceptions in the contract function dispatcher", + ) + + eth_flags.add_argument( + "--avoid-constant", + action="store_true", + help="Avoid exploring constant functions for human transactions", + ) + + eth_flags.add_argument( + "--limit-loops", + action="store_true", + help="Limit loops depth", + ) + + eth_flags.add_argument( + "--no-testcases", + action="store_true", + help="Do not generate testcases for discovered states when analysis finishes", + ) + + eth_flags.add_argument( + "--only-alive-testcases", + action="store_true", + help="Do not generate testcases for invalid/throwing states when analysis finishes", + ) + + eth_flags.add_argument( + "--thorough-mode", + action="store_true", + help="Configure Manticore for more exhaustive exploration. Evaluate gas, generate testcases for dead states, " + "explore constant functions, and run a small suite of detectors.", + ) + + config_flags = parser.add_argument_group("Constants") + config.add_config_vars_to_argparse(config_flags) + + args = parser.parse_args(shlex.split(additional_flags)) + config.process_config_values(parser, args) + + if not args.thorough_mode: + args.avoid_constant = True + args.exclude_all = True + args.only_alive_testcases = True + consts_evm = config.get_group("evm") + consts_evm.oog = "ignore" + consts.skip_reverts = True + + if consts.skip_reverts: + m.register_plugin(SkipRevertBasicBlocks()) + + if consts.explore_balance: + m.register_plugin(KeepOnlyIfStorageChanges()) + + if args.verbose_trace: + m.register_plugin(VerboseTrace()) + + if args.limit_loops: + m.register_plugin(LoopDepthLimiter()) + + for detector in parse_detectors(detectors_to_exclude): + m.register_plugin(detector()) + + if consts.profile: + profiler = Profiler() + m.register_plugin(profiler) + + if args.avoid_constant: + # avoid all human level tx that has no effect on the storage + filter_nohuman_constants = FilterFunctions( + regexp=r".*", depth="human", mutability="constant", include=False + ) + m.register_plugin(filter_nohuman_constants) + + if m.plugins: + logger.info( + f'Registered plugins: {", ".join(d.name for d in m.plugins.values())}' + ) + + return args diff --git a/server/manticore_server/introspect_plugin.py b/server/manticore_server/introspect_plugin.py new file mode 100644 index 00000000..74ae23f1 --- /dev/null +++ b/server/manticore_server/introspect_plugin.py @@ -0,0 +1,45 @@ +import logging +from datetime import datetime + +from manticore.core.plugin import IntrospectionAPIPlugin, StateDescriptor +from manticore.core.state import StateBase +from manticore.utils.enums import StateLists + +logger = logging.getLogger(__name__) + + +class ManticoreServerIntrospectionPlugin(IntrospectionAPIPlugin): + NAME = "ManticoreServerIntrospectionPlugin" + + @property + def name(self) -> str: + return "ManticoreServerIntrospectionPlugin" + + def create_state(self, state_id: int): + """Override create_state to force a state update right after creation. + This is helpful when retrieving info from a state yet to execute.""" + super().create_state(state_id) + state = self.manticore._load(state_id) + self._force_update_state_descriptor(state) + + def will_fork_state_callback(self, state: StateBase, expression, solutions, policy): + self._force_update_state_descriptor(state) + + def will_transition_state_callback( + self, state_id: int, from_list: StateLists, to_list: StateLists + ): + state = self.manticore._load(state_id) + self._force_update_state_descriptor(state) + + def _force_update_state_descriptor(self, state: StateBase): + """Force a given state to update its information, which can include the current PC, etc. + Calling _update_state_descriptor directly may become an issue if specific state implementations + start to require additional arguments for this method.""" + with self.locked_context("manticore_state", dict) as context: + state._update_state_descriptor( + context.setdefault(state.id, StateDescriptor(state_id=state.id)), + ) + context[state.id].last_intermittent_update = datetime.now() + + def did_terminate_worker_callback(self, worker_id: int): + logger.debug(f"worker exits (id: {worker_id})") diff --git a/server/manticore_server/manticore_server.py b/server/manticore_server/manticore_server.py new file mode 100644 index 00000000..fea66988 --- /dev/null +++ b/server/manticore_server/manticore_server.py @@ -0,0 +1,552 @@ +import argparse +import dataclasses +import logging +import shutil +import time +import uuid +from collections import defaultdict +from concurrent import futures +from pathlib import Path +from threading import Event, Thread +from typing import Any, Callable, Dict, Optional, Set + +import grpc +from grpc._server import _Context +from manticore.core.plugin import ( + InstructionCounter, + RecordSymbolicBranches, + StateDescriptor, + Tracer, + Visited, +) +from manticore.core.state import StateBase, TerminateState +from manticore.core.worker import WorkerProcess, WorkerThread +from manticore.ethereum import ManticoreEVM +from manticore.native import Manticore +from manticore.utils.enums import StateLists, StateStatus +from manticore.utils.helpers import deque +from manticore.utils.log import CallbackStream, ManticoreContextFilter + +from .evm_utils import setup_detectors_flags +from .introspect_plugin import ManticoreServerIntrospectionPlugin +from .ManticoreServer_pb2 import * +from .ManticoreServer_pb2_grpc import ( + ManticoreServerServicer, + add_ManticoreServerServicer_to_server, +) +from .native_plugin import UnicornEmulatePlugin +from .native_utils import parse_native_arguments + +logger = logging.getLogger(__name__) + + +class ManticoreWrapper: + def __init__( + self, mcore_object: Manticore, thread_target: Callable, *thread_args: Any + ): + self.uuid: str = uuid.uuid4().hex + self.manticore_object: Manticore = mcore_object + # mimics Manticore repository, reasoning for the queue size difference is provided in Manticore: + # https://github.com/trailofbits/manticore/blob/5a258f499098394c0af25e2e3f00b1b603c2334d/manticore/core/manticore.py#L133-L135 + self.log_queue = ( + mcore_object._manager.Queue(15000) + if mcore_object._worker_type == WorkerProcess + else deque(maxlen=5000) + ) + + # contains the state ids of all states paused by the user through the ControlState rpc + self.paused_states: Set[int] = set() + + # saves a copy of all state descriptors after analysis is complete or terminated + # but before the finalize() operation which destroys all states + self.final_states: Optional[Dict[int, StateDescriptor]] = None + + self.state_callbacks: Dict[int, Set[Callable]] = defaultdict(set) + + self.thread: Thread = Thread( + target=thread_target, + args=(self,) + thread_args, + daemon=True, + name=self.uuid, + ) + + def start(self) -> None: + self.thread.start() + + def append_log(self, msg: str) -> None: + q = self.log_queue + try: + q.append(msg) + except AttributeError: + # mimics Manticore repository, reasoning for using append and catching an AttributeError is provided in Manticore: + # https://github.com/trailofbits/manticore/blob/5a258f499098394c0af25e2e3f00b1b603c2334d/manticore/core/worker.py#L297-L303 + if q.full(): + q.get() + q.put(msg) + + +class ManticoreServicer(ManticoreServerServicer): + """Provides functionality for the methods set out in the protobuf spec""" + + def __init__(self, stop_event: Event): + """Initializes the dict that keeps track of all created manticore instances, as well as avoid/find address set""" + + self.manticore_instances: Dict[str, ManticoreWrapper] = {} + self.stop_event: Event = stop_event + + manticore_logger = logging.getLogger("manticore") + manticore_logger.parent = None + manticore_logger.setLevel(logging.WARNING) + + custom_log_handler = logging.StreamHandler(CallbackStream(self.log_callback)) + custom_log_handler.setFormatter( + logging.Formatter( + "%(threadName)s %(asctime)s: [%(process)d] %(name)s:%(levelname)s %(message)s" + ) + ) + custom_log_handler.addFilter(ManticoreContextFilter()) + + manticore_logger.addHandler(custom_log_handler) + + def log_callback(self, msg: str): + logger.debug(msg) + thread_name, msg_content = msg.split(" ", 1) + + if thread_name in self.manticore_instances: + # This will always be True if multiprocessing or single is used, since all WorkerProcess/WorkerSingle + # instances will share the same Thread name as the ManticoreWrapper's mthread which is added on Start + self.manticore_instances[thread_name].append_log(msg_content) + else: + for mwrapper in filter( + lambda x: x.manticore_object._worker_type == WorkerThread, + list(self.manticore_instances.values())[::-1], + ): # Thread name/idents can be reused, so start search from most recently-started instance + for worker in mwrapper.manticore_object._workers + list( + mwrapper.manticore_object._daemon_threads.values() + ): + if type(worker._t) == Thread and worker._t.name == thread_name: + worker._t.name = mwrapper.uuid + mwrapper.append_log(msg_content) + return + + def StartNative( + self, native_arguments: NativeArguments, context: _Context + ) -> ManticoreInstance: + """Starts a singular Manticore instance to analyze a native binary""" + try: + parsed = parse_native_arguments(native_arguments.additional_mcore_args) + except Exception as e: + logger.debug(e) + context.set_code(grpc.StatusCode.INVALID_ARGUMENT) + context.set_details("Additional arguments could not be parsed!") + return ManticoreInstance() + try: + m = Manticore.linux( + native_arguments.program_path, + argv=None + if not native_arguments.binary_args + else list(native_arguments.binary_args), + envp=None + if not native_arguments.envp + else { + key: val + for key, val in [e.split("=") for e in native_arguments.envp] + }, + symbolic_files=None + if not native_arguments.symbolic_files + else list(native_arguments.symbolic_files), + concrete_start="" + if not native_arguments.concrete_start + else native_arguments.concrete_start, + stdin_size=265 + if not native_arguments.stdin_size + else int(native_arguments.stdin_size), + workspace_url=parsed.workspace, + introspection_plugin_type=ManticoreServerIntrospectionPlugin, + ) + except Exception as e: + logger.warning(e) + context.set_code(grpc.StatusCode.INVALID_ARGUMENT) + context.set_details("Basic arguments are invalid!") + return ManticoreInstance() + + try: + m.register_plugin(InstructionCounter()) + m.register_plugin(Visited(parsed.coverage)) + m.register_plugin(Tracer()) + m.register_plugin(RecordSymbolicBranches()) + + if native_arguments.emulate_until: + m.register_plugin(UnicornEmulatePlugin(native_arguments.emulate_until)) + + if parsed.names is not None: + m.apply_model_hooks(parsed.names) + + if parsed.assertions: + m.load_assertions(parsed.assertions) + + def find_f(state: StateBase): + bufs = state.solve_one_n_batched(state.input_symbols) + for symbol, buf in zip(state.input_symbols, bufs): + logger.info(f"{symbol.name}: {buf!r}\n") + with m.locked_context() as context: + m.kill() + state.abandon() + + def avoid_f(state: StateBase): + state.abandon() + + for hook in native_arguments.hooks: + if hook.type == Hook.HookType.FIND: + m.add_hook(hook.address, find_f) + elif hook.type == Hook.HookType.AVOID: + m.add_hook(hook.address, avoid_f) + elif hook.type == Hook.HookType.CUSTOM: + exec(hook.func_text, {"addr": hook.address, "m": m}) + elif hook.type == Hook.HookType.GLOBAL: + exec(hook.func_text, {"m": m}) + + except Exception as e: + logger.warning(e) + context.set_code(grpc.StatusCode.INVALID_ARGUMENT) + context.set_details("Hooks set are invalid!") + return ManticoreInstance() + + try: + + def manticore_native_runner(mcore_wrapper: ManticoreWrapper): + mcore_wrapper.manticore_object.run() + mcore_wrapper.final_states = { + k: dataclasses.replace(v) + for k, v in mcore_wrapper.manticore_object.introspect().items() + } + mcore_wrapper.manticore_object.finalize() + + manticore_wrapper = ManticoreWrapper(m, manticore_native_runner) + self.manticore_instances[manticore_wrapper.uuid] = manticore_wrapper + + def state_callback_hook(state: StateBase): + callbacks = manticore_wrapper.state_callbacks[state.id] + for callback in callbacks: + callback(state) + + m.hook(None)(state_callback_hook) + + manticore_wrapper.start() + + except Exception as e: + logger.warning(e) + context.set_code(grpc.StatusCode.INTERNAL) + context.set_details( + "Manticore failed to start or crashed during execution!" + ) + return ManticoreInstance() + + return ManticoreInstance(uuid=manticore_wrapper.uuid) + + def StartEVM( + self, evm_arguments: EVMArguments, context: _Context + ) -> ManticoreInstance: + """Starts a singular Manticore instance to analyze a solidity contract""" + if evm_arguments.contract_path == "": + context.set_code(grpc.StatusCode.INVALID_ARGUMENT) + context.set_details("Contract path not specified!") + return ManticoreInstance() + if not Path(evm_arguments.contract_path).is_file(): + context.set_code(grpc.StatusCode.INVALID_ARGUMENT) + context.set_details( + f"Contract path invalid: '{evm_arguments.contract_path}'" + ) + return ManticoreInstance() + + if evm_arguments.solc_bin: + solc_bin_path = evm_arguments.solc_bin + elif shutil.which("solc"): + solc_bin_path = str(shutil.which("solc")) + else: + context.set_code(grpc.StatusCode.INVALID_ARGUMENT) + context.set_details( + f"solc binary neither specified in EVMArguments nor found in PATH!" + ) + return ManticoreInstance() + + try: + m = ManticoreEVM() + + args = setup_detectors_flags( + list(evm_arguments.detectors_to_exclude), + evm_arguments.additional_flags, + m, + ) + + def manticore_evm_runner( + mcore_wrapper: ManticoreWrapper, args: argparse.Namespace + ): + + mcore_wrapper.manticore_object.multi_tx_analysis( + evm_arguments.contract_path, + contract_name=evm_arguments.contract_name, + tx_limit=-1 + if not evm_arguments.tx_limit + else evm_arguments.tx_limit, + tx_use_coverage=True + if args.txnocoverage == None + else args.txnocoverage, + tx_send_ether=True if args.txnoether == None else args.txnoether, + tx_account="attacker" + if not evm_arguments.tx_account + else evm_arguments.tx_account, + tx_preconstrain=False + if args.txpreconstrain == None + else args.txpreconstrain, + compile_args={"solc_solcs_bin": solc_bin_path}, + ) + + mcore_wrapper.final_states = { + k: dataclasses.replace(v) + for k, v in mcore_wrapper.manticore_object.introspect().items() + } + + if not args.no_testcases: + mcore_wrapper.manticore_object.finalize( + only_alive_states=args.only_alive_testcases + ) + else: + mcore_wrapper.manticore_object.kill() + + manticore_wrapper = ManticoreWrapper(m, manticore_evm_runner, args) + self.manticore_instances[manticore_wrapper.uuid] = manticore_wrapper + manticore_wrapper.start() + + except Exception as e: + logger.warning(e) + context.set_code(grpc.StatusCode.INTERNAL) + context.set_details( + "Manticore failed to start or crashed during execution!" + ) + return ManticoreInstance() + + return ManticoreInstance(uuid=manticore_wrapper.uuid) + + def Terminate( + self, mcore_instance: ManticoreInstance, context: _Context + ) -> TerminateResponse: + """Terminates the specified Manticore instance.""" + if mcore_instance.uuid not in self.manticore_instances: + context.set_code(grpc.StatusCode.FAILED_PRECONDITION) + context.set_details("Specified Manticore instance not found!") + return TerminateResponse() + + m_wrapper = self.manticore_instances[mcore_instance.uuid] + + if not ( + m_wrapper.manticore_object.is_running() and m_wrapper.thread.is_alive() + ): + return TerminateResponse() + m_wrapper.manticore_object.kill() + return TerminateResponse() + + def GetStateList( + self, mcore_instance: ManticoreInstance, context: _Context + ) -> ManticoreStateList: + """Returns full list of states for given ManticoreInstance.""" + active_states = [] + waiting_states = [] + paused_states = [] + forked_states = [] + errored_states = [] + complete_states = [] + + if mcore_instance.uuid not in self.manticore_instances: + context.set_code(grpc.StatusCode.FAILED_PRECONDITION) + context.set_details("Specified Manticore instance not found!") + return ManticoreStateList() + + mcore_wrapper = self.manticore_instances[mcore_instance.uuid] + states = ( + mcore_wrapper.final_states + if mcore_wrapper.final_states is not None + else mcore_wrapper.manticore_object.introspect() + ) + + for state_id, state_desc in states.items(): + + state_args = {"state_id": state_id} + + if isinstance(state_desc.pc, int): + state_args["pc"] = state_desc.pc + elif isinstance(state_desc.last_pc, int): + state_args["pc"] = state_desc.last_pc + + if isinstance(state_desc.parent, int): + state_args["parent_id"] = state_desc.parent + + state_args["children_ids"] = list(state_desc.children) + + s = ManticoreState(**state_args) + + if state_desc.status == StateStatus.running: + active_states.append(s) + elif state_desc.status in ( + StateStatus.waiting_for_solver, + StateStatus.waiting_for_worker, + ): + waiting_states.append(s) + elif state_desc.status == StateStatus.destroyed: + forked_states.append(s) + elif state_desc.status == StateStatus.stopped: + if state_id in mcore_wrapper.paused_states: + paused_states.append(s) + elif state_desc.state_list == StateLists.killed: + errored_states.append(s) + else: + complete_states.append(s) + else: + raise ValueError(f"Unknown status {state_desc.status}") + + return ManticoreStateList( + active_states=active_states, + waiting_states=waiting_states, + paused_states=paused_states, + forked_states=forked_states, + errored_states=errored_states, + complete_states=complete_states, + ) + + def GetMessageList( + self, mcore_instance: ManticoreInstance, context: _Context + ) -> ManticoreMessageList: + """Returns any new log messages for given ManticoreInstance since the previous call. + Currently, implementation is based on TUI.""" + if mcore_instance.uuid not in self.manticore_instances: + context.set_code(grpc.StatusCode.FAILED_PRECONDITION) + context.set_details("Specified Manticore instance not found!") + return ManticoreMessageList() + + q = self.manticore_instances[mcore_instance.uuid].log_queue + i = 0 + messages = [] + while not q.empty(): + msg = ManticoreLogMessage(content=q.get()) + messages.append(msg) + i += 1 + return ManticoreMessageList(messages=messages) + + def CheckManticoreRunning( + self, mcore_instance: ManticoreInstance, context: _Context + ) -> ManticoreRunningStatus: + + if mcore_instance.uuid not in self.manticore_instances: + context.set_code(grpc.StatusCode.FAILED_PRECONDITION) + context.set_details("Specified Manticore instance not found!") + return ManticoreRunningStatus() + + m_wrapper = self.manticore_instances[mcore_instance.uuid] + + return ManticoreRunningStatus(is_running=(m_wrapper.thread.is_alive())) + + def StopServer( + self, request: StopServerRequest, context: _Context + ) -> StopServerResponse: + to_warn = False + for mwrapper in self.manticore_instances.values(): + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.thread.is_alive(): + time.sleep(1) + if (time.time() - stime) > 10: + to_warn = True + break + + if to_warn: + warning_message = "WARNING: Not all Manticore processes were shut down successfully before timeout. There may be extra processes running even after the server has stopped." + context.set_code(grpc.StatusCode.INTERNAL) + context.set_details(warning_message) + logger.warning(warning_message) + + self.stop_event.set() + return StopServerResponse() + + def ControlState( + self, request: ControlStateRequest, context: _Context + ) -> ControlStateResponse: + if request.manticore_instance.uuid not in self.manticore_instances: + context.set_code(grpc.StatusCode.FAILED_PRECONDITION) + context.set_details("Specified Manticore instance not found!") + return ControlStateResponse() + + mcore_wrapper = self.manticore_instances[request.manticore_instance.uuid] + mcore_object = mcore_wrapper.manticore_object + states = ( + mcore_wrapper.final_states + if mcore_wrapper.final_states is not None + else mcore_object.introspect() + ) + + if request.state_id not in states: + context.set_code(grpc.StatusCode.FAILED_PRECONDITION) + context.set_details("Specified state not found!") + return ControlStateResponse() + + # Pause / Resume / Kill should have no effect if Manticore is no longer running, + # but we do not raise an error to account for latency between CheckStatus() calls + if not mcore_object.is_running(): + return ControlStateResponse() + + # helper callbacks for state control + def state_pause_hook(state: StateBase) -> None: + mcore_wrapper.state_callbacks[state.id].discard(state_pause_hook) + raise TerminateState("Pausing state") + + def state_kill_hook(state: StateBase) -> None: + mcore_wrapper.state_callbacks[state.id].discard(state_kill_hook) + state.abandon() + + try: + if request.action == ControlStateRequest.StateAction.PAUSE: + if len(mcore_wrapper.paused_states) == 0: + with mcore_object._lock: + mcore_object._busy_states.append(-1) + mcore_object._lock.notify_all() + mcore_wrapper.state_callbacks[request.state_id].add(state_pause_hook) + mcore_wrapper.paused_states.add(request.state_id) + + elif request.action == ControlStateRequest.StateAction.RESUME: + mcore_wrapper.paused_states.discard(request.state_id) + with mcore_object._lock: + mcore_object._revive_state(request.state_id) + if len(mcore_wrapper.paused_states) == 0: + mcore_object._busy_states.remove(-1) + mcore_object._lock.notify_all() + + elif request.action == ControlStateRequest.StateAction.KILL: + if request.state_id in mcore_wrapper.paused_states: + mcore_wrapper.paused_states.remove(request.state_id) + if len(mcore_wrapper.paused_states) == 0: + with mcore_object._lock: + mcore_object._busy_states.remove(-1) + mcore_object._lock.notify_all() + else: + mcore_wrapper.state_callbacks[request.state_id].add(state_kill_hook) + except: + context.set_code(grpc.StatusCode.INTERNAL) + context.set_details( + f"Failed apply action {request.action} to state {request.state_id}" + ) + + return ControlStateResponse() + + +def main(): + logger.setLevel(logging.DEBUG) + port = "[::]:50010" + logger.info(f"server starting at '{port}' ...") + stop_event = Event() + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + add_ManticoreServerServicer_to_server(ManticoreServicer(stop_event), server) + server.add_insecure_port(port) + server.start() + logger.info(f"server started.") + stop_event.wait() + server.stop(None) + logger.info("shutdown gracefully!") diff --git a/server/manticore_server/native_plugin.py b/server/manticore_server/native_plugin.py new file mode 100644 index 00000000..ae353013 --- /dev/null +++ b/server/manticore_server/native_plugin.py @@ -0,0 +1,13 @@ +from manticore.core.plugin import Plugin + + +class UnicornEmulatePlugin(Plugin): + """Manticore plugin to speed up emulation using unicorn until `start`""" + + def __init__(self, start: int): + super().__init__() + self.start = start + + def will_run_callback(self, ready_states): + for state in ready_states: + state.cpu.emulate_until(self.start) diff --git a/server/manticore_server/native_utils.py b/server/manticore_server/native_utils.py new file mode 100644 index 00000000..74c2d8e4 --- /dev/null +++ b/server/manticore_server/native_utils.py @@ -0,0 +1,122 @@ +import argparse +import shlex + +import pkg_resources +from manticore.utils import config +from manticore.utils.log import set_verbosity + + +# TODO(ekilmer): This should be refactored to reduce duplication in main +# manticore package +def parse_native_arguments(additional_args: str) -> argparse.Namespace: + """parse additional arguments for manticore native execution, CLI-style""" + + def positive(value): + ivalue = int(value) + if ivalue <= 0: + raise argparse.ArgumentTypeError("Argument must be positive") + return ivalue + + parser = argparse.ArgumentParser( + description="Symbolic execution tool", + prog="manticore", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + parser.add_argument("--context", type=str, default=None, help=argparse.SUPPRESS) + parser.add_argument( + "--coverage", + type=str, + default="visited.txt", + help="Where to write the coverage data", + ) + parser.add_argument("--names", type=str, default=None, help=argparse.SUPPRESS) + parser.add_argument( + "--no-colors", + action="store_true", + help="Disable ANSI color escape sequences in output", + ) + parser.add_argument("--offset", type=int, default=16, help=argparse.SUPPRESS) + parser.add_argument( + "--policy", + type=str, + default="random", + help=( + "Search policy. random|adhoc|uncovered|dicount" + "|icount|syscount|depth. (use + (max) or - (min)" + " to specify order. e.g. +random)" + ), + ) + parser.add_argument( + "-v", action="count", default=1, help="Specify verbosity level from -v to -vvvv" + ) + parser.add_argument( + "--workspace", + type=str, + default=None, + help=("A folder name for temporaries and results." "(default mcore_?????)"), + ) + + current_version = pkg_resources.get_distribution("manticore").version + parser.add_argument( + "--version", + action="version", + version=f"Manticore {current_version}", + help="Show program version information", + ) + parser.add_argument( + "--config", + type=str, + help="Manticore config file (.yml) to use. (default config file pattern is: ./[.]m[anti]core.yml)", + ) + + bin_flags = parser.add_argument_group("Binary flags") + bin_flags.add_argument( + "--entrysymbol", type=str, default=None, help="Symbol as entry point" + ) + bin_flags.add_argument( + "--assertions", type=str, default=None, help=argparse.SUPPRESS + ) + bin_flags.add_argument("--buffer", type=str, help=argparse.SUPPRESS) + bin_flags.add_argument( + "--data", + type=str, + default="", + help="Initial concrete concrete_data for the input symbolic buffer", + ) + bin_flags.add_argument( + "--file", + type=str, + default=[], + action="append", + dest="files", + help="Specify symbolic input file, '+' marks symbolic bytes", + ) + bin_flags.add_argument( + "--env", + type=str, + nargs=1, + default=[], + action="append", + help='Add an environment variable. Use "+" for symbolic bytes. (VARNAME=++++)', + ) + bin_flags.add_argument( + "--pure-symbolic", + action="store_true", + help="Treat all writable memory as symbolic", + ) + + config_flags = parser.add_argument_group("Constants") + config.add_config_vars_to_argparse(config_flags) + + parsed = parser.parse_args(shlex.split(additional_args)) + config.process_config_values(parser, parsed) + + if parsed.policy.startswith("min"): + parsed.policy = "-" + parsed.policy[3:] + elif parsed.policy.startswith("max"): + parsed.policy = "+" + parsed.policy[3:] + + set_verbosity(parsed.v) + + return parsed diff --git a/server/pyproject.toml b/server/pyproject.toml new file mode 100644 index 00000000..9d8ed117 --- /dev/null +++ b/server/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +# Minimum requirements for the build system to execute. +requires = ["setuptools>=49.0.0", "wheel"] # PEP 518 specifications. + +[tool.mypy] +files = ["manticore_server", "tests"] +ignore_missing_imports = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_unreachable = true + +[tool.isort] +profile = "black" +skip = ["venv", "build", "manticore_server/ManticoreServer_pb2.py", "manticore_server/ManticoreServer_pb2_grpc.py"] + +[tool.black] +line-length = 88 +extend-exclude = ''' + ManticoreServer_pb2_grpc\.py + | ManticoreServer_pb2\.py + | ManticoreServer_pb2\.pyi + | venv +''' diff --git a/server/setup.py b/server/setup.py new file mode 100644 index 00000000..0d229960 --- /dev/null +++ b/server/setup.py @@ -0,0 +1,62 @@ +from pathlib import Path + +from setuptools import Command, find_packages, setup + + +class GenerateCommand(Command): + description = "generates manticore_server server protobuf + grpc code from protobuf specification file" + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + from grpc.tools import protoc + + protoc.main( + [ + "grpc_tools.protoc", + "-I.", + "--python_out=.", + "--grpc_python_out=.", + "--mypy_out=.", + "./manticore_server/ManticoreServer.proto", + ] + ) + + +setup( + name="manticore_server", + version="0.0.1", + packages=find_packages(exclude=["tests", "tests.*"]), + python_requires=">=3.7", + install_requires=[ + f"manticore[native] @ file://{Path(__file__).parent.resolve()}/..", + "protobuf~=3.20", + "grpcio~=1.46", + "crytic-compile>=0.2.2", + ], + extras_require={ + "dev": [ + "grpcio-tools", + "mypy-protobuf", + "shiv~=1.0.1", + "types-setuptools", + "black~=22.0", + "isort==5.10.1", + "mypy==0.942", + ] + }, + entry_points={ + "console_scripts": [ + "manticore_server=manticore_server.manticore_server:main", + ], + "distutils.commands": ["generate = GenerateCommand"], + }, + cmdclass={ + "generate": GenerateCommand, + }, +) diff --git a/server/tests/__init__.py b/server/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/tests/contracts/adder.sol b/server/tests/contracts/adder.sol new file mode 100644 index 00000000..b08e74b3 --- /dev/null +++ b/server/tests/contracts/adder.sol @@ -0,0 +1,7 @@ +contract Adder { + function incremented(uint value) public returns (uint){ + if (value == 1) + revert(); + return value + 1; + } +} diff --git a/server/tests/mock_classes.py b/server/tests/mock_classes.py new file mode 100644 index 00000000..c7f98d33 --- /dev/null +++ b/server/tests/mock_classes.py @@ -0,0 +1,18 @@ +import grpc +from grpc._server import _Context + + +class MockContext(_Context): + def __init__(self) -> None: + self.code: grpc.StatusCode = grpc.StatusCode.OK + self.details: str = "" + + def set_details(self, details: str) -> None: + self.details = details + + def set_code(self, code: grpc.StatusCode) -> None: + self.code = code + + def reset(self): + self.code: grpc.StatusCode = grpc.StatusCode.OK + self.details: str = "" diff --git a/server/tests/test_ethereum.py b/server/tests/test_ethereum.py new file mode 100644 index 00000000..cafcda26 --- /dev/null +++ b/server/tests/test_ethereum.py @@ -0,0 +1,343 @@ +import glob +import os +import threading +import time +import unittest +import unittest.mock +from inspect import currentframe, getframeinfo +from pathlib import Path +from shutil import rmtree, which +from uuid import UUID, uuid4 + +import grpc + +from manticore_server import manticore_server +from manticore_server.ManticoreServer_pb2 import * +from tests.mock_classes import MockContext + + +class ManticoreServerEVMTest(unittest.TestCase): + def setUp(self): + self.dirname = str(Path(getframeinfo(currentframe()).filename).resolve().parent) + self.contract_path = str(self.dirname / Path("contracts") / Path("adder.sol")) + self.test_event = threading.Event() + self.servicer = manticore_server.ManticoreServicer(self.test_event) + self.solc_path = which("solc") + self.context = MockContext() + + def tearDown(self): + for mwrapper in self.servicer.manticore_instances.values(): + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.thread.is_alive(): + time.sleep(1) + if (time.time() - stime) > 15: + break + + @classmethod + def tearDownClass(cls): + for f in glob.glob("mcore_*"): + if Path(f).is_dir(): + rmtree(f, ignore_errors=True) + + def test_start_with_no_or_invalid_contract_path(self): + self.servicer.StartEVM(EVMArguments(solc_bin=self.solc_path), self.context) + self.assertEqual(self.context.code, grpc.StatusCode.INVALID_ARGUMENT) + self.assertEqual(self.context.details, "Contract path not specified!") + + self.context.reset() + + invalid_contract_path = str( + self.dirname / Path("contracts") / Path("invalid_contract") + ) + self.servicer.StartEVM( + EVMArguments(contract_path=invalid_contract_path, solc_bin=self.solc_path), + self.context, + ) + + self.assertEqual(self.context.code, grpc.StatusCode.INVALID_ARGUMENT) + self.assertEqual( + self.context.details, f"Contract path invalid: '{invalid_contract_path}'" + ) + + def test_start_with_no_solc_specified_or_in_path(self): + path_to_use = os.environ["PATH"] + solc_on_path = which("solc") + + if solc_on_path: + solc_dir = str(Path(solc_on_path).parent) + cur_paths = os.environ["PATH"].split(os.pathsep) + + path_to_use = str(os.pathsep).join( + [dir for dir in cur_paths if dir != solc_dir] + ) + + with unittest.mock.patch.dict(os.environ, {"PATH": ""}): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path), + self.context, + ) + + self.assertEqual(self.context.code, grpc.StatusCode.INVALID_ARGUMENT) + self.assertEqual( + self.context.details, + "solc binary neither specified in EVMArguments nor found in PATH!", + ) + + def test_start(self): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + + try: + UUID(mcore_instance.uuid) + except ValueError: + self.fail( + "Start() returned ManticoreInstance with missing or malformed UUID" + ) + + self.assertTrue(mcore_instance.uuid in self.servicer.manticore_instances) + + mcore = self.servicer.manticore_instances[mcore_instance.uuid].manticore_object + self.assertTrue(Path(mcore.workspace).is_dir()) + + def test_terminate_running_manticore(self): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + stime = time.time() + while not mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 5: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to start running before timeout" + ) + time.sleep(1) + + self.context.reset() + + self.servicer.Terminate(mcore_instance, self.context) + self.assertEqual(self.context.code, grpc.StatusCode.OK) + self.assertTrue(mwrapper.manticore_object.is_killed()) + + stime = time.time() + while mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running before timeout" + ) + time.sleep(1) + + def test_terminate_killed_manticore(self): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + + self.context.reset() + + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running before timeout" + ) + time.sleep(1) + + self.servicer.Terminate(mcore_instance, self.context) + self.assertEqual(self.context.code, grpc.StatusCode.OK) + + def test_terminate_invalid_manticore(self): + t_status = self.servicer.Terminate( + ManticoreInstance(uuid=uuid4().hex), self.context + ) + self.assertEqual(self.context.code, grpc.StatusCode.FAILED_PRECONDITION) + + def test_get_message_list_running_manticore(self): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + stime = time.time() + while mwrapper.manticore_object._log_queue.empty() and time.time() - stime < 5: + time.sleep(1) + if not mwrapper.manticore_object._log_queue.empty(): + deque_messages = list(mwrapper.manticore_object._log_queue) + messages = self.servicer.GetMessageList( + mcore_instance, self.context + ).messages + for i in range(len(messages)): + self.assertEqual(messages[i].content, deque_messages[i]) + break + + def test_get_message_list_stopped_manticore(self): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running before timeout" + ) + time.sleep(1) + + stime = time.time() + while mwrapper.manticore_object._log_queue.empty() and time.time() - stime < 5: + time.sleep(1) + if not mwrapper.manticore_object._log_queue.empty(): + deque_messages = list(mwrapper.manticore_object._log_queue) + messages = self.servicer.GetMessageList( + mcore_instance, self.context + ).messages + for i in range(len(messages)): + self.assertEqual(messages[i].content, deque_messages[i]) + break + + def test_get_message_list_invalid_manticore(self): + message_list = self.servicer.GetMessageList( + ManticoreInstance(uuid=uuid4().hex), self.context + ) + self.assertEqual(self.context.code, grpc.StatusCode.FAILED_PRECONDITION) + self.assertEqual( + self.context.details, "Specified Manticore instance not found!" + ) + self.assertEqual(len(message_list.messages), 0) + + def test_get_state_list_running_manticore(self): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + for i in range(5): + time.sleep(1) + state_list = self.servicer.GetStateList(mcore_instance, self.context) + all_states = list( + map( + lambda x: x.state_id, + list(state_list.active_states) + + list(state_list.waiting_states) + + list(state_list.forked_states) + + list(state_list.errored_states) + + list(state_list.complete_states), + ) + ) + state_ids = mwrapper.manticore_object.introspect().keys() + + for sid in state_ids: + self.assertIn(sid, all_states) + + def test_get_state_list_stopped_manticore(self): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running before timeout" + ) + time.sleep(1) + + stime = time.time() + for i in range(5): + time.sleep(1) + state_list = self.servicer.GetStateList(mcore_instance, self.context) + all_states = list( + map( + lambda x: x.state_id, + list(state_list.active_states) + + list(state_list.waiting_states) + + list(state_list.forked_states) + + list(state_list.errored_states) + + list(state_list.complete_states), + ) + ) + state_ids = mwrapper.manticore_object.introspect().keys() + + for sid in state_ids: + self.assertIn(sid, all_states) + + def test_get_state_list_invalid_manticore(self): + state_list = self.servicer.GetStateList( + ManticoreInstance(uuid=uuid4().hex), self.context + ) + + self.assertFalse(state_list.active_states) + self.assertFalse(state_list.waiting_states) + self.assertFalse(state_list.forked_states) + self.assertFalse(state_list.errored_states) + self.assertFalse(state_list.complete_states) + + def test_check_manticore_running(self): + mcore_instance = self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + stime = time.time() + while not mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to start running before timeout" + ) + time.sleep(1) + + self.assertTrue( + self.servicer.CheckManticoreRunning(mcore_instance, self.context).is_running + ) + + mwrapper.manticore_object.kill() + + stime = time.time() + while mwrapper.thread.is_alive(): + if (time.time() - stime) > 45: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running and finish generating reports before timeout" + ) + time.sleep(1) + + self.assertFalse( + self.servicer.CheckManticoreRunning(mcore_instance, self.context).is_running + ) + + def test_check_manticore_running_invalid_manticore(self): + self.assertFalse( + self.servicer.CheckManticoreRunning( + ManticoreInstance(uuid=uuid4().hex), self.context + ).is_running + ) + + def test_stop_server(self): + self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + self.servicer.StartEVM( + EVMArguments(contract_path=self.contract_path, solc_bin=self.solc_path), + self.context, + ) + + self.servicer.StopServer(StopServerRequest(), self.context) + + self.assertTrue(self.test_event.is_set()) + for mwrapper in self.servicer.manticore_instances.values(): + self.assertFalse(mwrapper.manticore_object.is_running()) diff --git a/server/tests/test_native.py b/server/tests/test_native.py new file mode 100644 index 00000000..77f112e8 --- /dev/null +++ b/server/tests/test_native.py @@ -0,0 +1,618 @@ +import glob +import threading +import time +import unittest +from inspect import currentframe, getframeinfo +from pathlib import Path +from shutil import rmtree +from uuid import UUID, uuid4 + +import grpc + +from manticore_server import manticore_server +from manticore_server.ManticoreServer_pb2 import * +from tests.mock_classes import MockContext + + +class ManticoreServerCoreNativeTest(unittest.TestCase): + def setUp(self): + this_dir = Path(getframeinfo(currentframe()).filename).resolve().parent + self.dirname = str(this_dir) + self.binary_path = str( + this_dir.parent.parent + / "tests" + / "native" + / "binaries" + / "arguments_linux_amd64" + ) + print(self.binary_path) + self.test_event = threading.Event() + self.servicer = manticore_server.ManticoreServicer(self.test_event) + self.context = MockContext() + + def tearDown(self): + for mwrapper in self.servicer.manticore_instances.values(): + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.thread.is_alive(): + time.sleep(1) + if (time.time() - stime) > 15: + break + + @classmethod + def tearDownClass(cls): + for f in glob.glob("mcore_*"): + if Path(f).is_dir(): + rmtree(f, ignore_errors=True) + + def test_start_with_no_or_invalid_binary_path(self): + self.servicer.StartNative(NativeArguments(), self.context) + + self.assertEquals(self.context.code, grpc.StatusCode.INVALID_ARGUMENT) + self.assertEquals(self.context.details, "Basic arguments are invalid!") + + self.context.reset() + + invalid_binary_path = str( + self.dirname / Path("binaries") / Path("invalid_binary") + ) + + self.servicer.StartNative( + NativeArguments(program_path=invalid_binary_path), self.context + ) + + self.assertEquals(self.context.code, grpc.StatusCode.INVALID_ARGUMENT) + self.assertEquals(self.context.details, "Basic arguments are invalid!") + + def test_start(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + + try: + UUID(mcore_instance.uuid) + except ValueError: + self.fail( + "Start() returned ManticoreInstance with missing or malformed UUID" + ) + + self.assertTrue(mcore_instance.uuid in self.servicer.manticore_instances) + + mcore = self.servicer.manticore_instances[mcore_instance.uuid].manticore_object + self.assertTrue(Path(mcore.workspace).is_dir()) + + def test_start_with_find_avoid_hooks(self): + mcore_instance = self.servicer.StartNative( + NativeArguments( + program_path=str(self.binary_path), + hooks=[ + Hook(type=Hook.HookType.FIND, address=0x40100C), + Hook(type=Hook.HookType.AVOID, address=0x401018), + ], + ), + self.context, + ) + self.assertTrue(mcore_instance.uuid in self.servicer.manticore_instances) + # TODO: Once logging is improved, check that find_f outputs successful stdin. Might require different test binary. + + def test_start_with_global_hook(self): + mcore_instance = self.servicer.StartNative( + NativeArguments( + program_path=str(self.binary_path), + hooks=[ + Hook( + type=Hook.HookType.GLOBAL, + func_text="\n".join( + ( + "global m", + "def hook(state):", + " m.test_attribute = True", + " m.kill()", + "m.hook(None)(hook)", + ) + ), + ), + ], + ), + self.context, + ) + + self.assertTrue(mcore_instance.uuid in self.servicer.manticore_instances) + m = self.servicer.manticore_instances[mcore_instance.uuid].manticore_object + + stime = time.time() + while not hasattr(m, "test_attribute"): + if (time.time() - stime) > 10: + self.fail( + f"Global hook failed to trigger on {mcore_instance.uuid} before timeout" + ) + time.sleep(1) + + self.assertTrue(m.test_attribute) + + def test_start_with_custom_hook(self): + mcore_instance = self.servicer.StartNative( + NativeArguments( + program_path=str(self.binary_path), + hooks=[ + Hook( + type=Hook.HookType.CUSTOM, + func_text="\n".join( + ( + "global m, addr", + "def hook(state):", + " m.test_attribute = addr", + " m.kill()", + "m.hook(addr)(hook)", + ) + ), + address=0x400FDC, + ), + ], + ), + self.context, + ) + + self.assertTrue(mcore_instance.uuid in self.servicer.manticore_instances) + m = self.servicer.manticore_instances[mcore_instance.uuid].manticore_object + + stime = time.time() + while not hasattr(m, "test_attribute"): + if (time.time() - stime) > 10: + self.fail( + f"Custom hook failed to trigger on {mcore_instance.uuid} before timeout" + ) + time.sleep(1) + + self.assertTrue(m.test_attribute == 0x400FDC) + + def test_start_with_invalid_custom_and_global_hook(self): + + self.servicer.StartNative( + NativeArguments( + program_path=str(self.binary_path), + hooks=[ + Hook( + type=Hook.HookType.CUSTOM, + func_text="this is an invalid hook", + address=0x400FDC, + ), + ], + ), + self.context, + ) + + self.assertEquals(self.context.code, grpc.StatusCode.INVALID_ARGUMENT) + self.assertEquals(self.context.details, "Hooks set are invalid!") + + self.context.reset() + + self.servicer.StartNative( + NativeArguments( + program_path=str(self.binary_path), + hooks=[ + Hook( + type=Hook.HookType.GLOBAL, + func_text="this is another invalid hook", + ), + ], + ), + self.context, + ) + + self.assertEquals(self.context.code, grpc.StatusCode.INVALID_ARGUMENT) + self.assertEquals(self.context.details, "Hooks set are invalid!") + + def test_terminate_running_manticore(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + stime = time.time() + while not mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 5: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to start running before timeout" + ) + time.sleep(1) + + self.context.reset() + + self.servicer.Terminate(mcore_instance, self.context) + self.assertEqual(self.context.code, grpc.StatusCode.OK) + self.assertTrue(mwrapper.manticore_object.is_killed()) + + stime = time.time() + while mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running before timeout" + ) + time.sleep(1) + + def test_terminate_killed_manticore(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running before timeout" + ) + time.sleep(1) + + t_status = self.servicer.Terminate(mcore_instance, self.context) + + self.assertEquals(self.context.code, grpc.StatusCode.OK) + + def test_terminate_invalid_manticore(self): + t_status = self.servicer.Terminate( + ManticoreInstance(uuid=uuid4().hex), self.context + ) + self.assertEqual(self.context.code, grpc.StatusCode.FAILED_PRECONDITION) + + def test_get_message_list_running_manticore(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + stime = time.time() + while mwrapper.manticore_object._log_queue.empty() and time.time() - stime < 5: + time.sleep(1) + if not mwrapper.manticore_object._log_queue.empty(): + deque_messages = list(mwrapper.manticore_object._log_queue) + messages = self.servicer.GetMessageList( + mcore_instance, self.context + ).messages + for i in range(len(messages)): + self.assertEqual(messages[i].content, deque_messages[i]) + break + + def test_get_message_list_stopped_manticore(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running before timeout" + ) + time.sleep(1) + + stime = time.time() + while mwrapper.manticore_object._log_queue.empty() and time.time() - stime < 5: + time.sleep(1) + if not mwrapper.manticore_object._log_queue.empty(): + deque_messages = list(mwrapper.manticore_object._log_queue) + messages = self.servicer.GetMessageList( + mcore_instance, self.context + ).messages + for i in range(len(messages)): + self.assertEqual(messages[i].content, deque_messages[i]) + break + + def test_get_message_list_invalid_manticore(self): + message_list = self.servicer.GetMessageList( + ManticoreInstance(uuid=uuid4().hex), self.context + ) + self.assertEqual(self.context.code, grpc.StatusCode.FAILED_PRECONDITION) + self.assertEqual( + self.context.details, "Specified Manticore instance not found!" + ) + self.assertEqual(len(message_list.messages), 0) + + def test_get_state_list_running_manticore(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + for i in range(5): + time.sleep(1) + state_list = self.servicer.GetStateList(mcore_instance, self.context) + all_states = list( + map( + lambda x: x.state_id, + list(state_list.active_states) + + list(state_list.waiting_states) + + list(state_list.forked_states) + + list(state_list.errored_states) + + list(state_list.complete_states), + ) + ) + state_ids = mwrapper.manticore_object.introspect().keys() + + for sid in state_ids: + self.assertIn(sid, all_states) + + def test_get_state_list_stopped_manticore(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + mwrapper.manticore_object.kill() + stime = time.time() + while mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running before timeout" + ) + time.sleep(1) + + stime = time.time() + for i in range(5): + time.sleep(1) + state_list = self.servicer.GetStateList(mcore_instance, self.context) + all_states = list( + map( + lambda x: x.state_id, + list(state_list.active_states) + + list(state_list.waiting_states) + + list(state_list.forked_states) + + list(state_list.errored_states) + + list(state_list.complete_states), + ) + ) + state_ids = mwrapper.manticore_object.introspect().keys() + + for sid in state_ids: + self.assertIn(sid, all_states) + + def test_get_state_list_invalid_manticore(self): + state_list = self.servicer.GetStateList( + ManticoreInstance(uuid=uuid4().hex), self.context + ) + + self.assertFalse(state_list.active_states) + self.assertFalse(state_list.waiting_states) + self.assertFalse(state_list.forked_states) + self.assertFalse(state_list.errored_states) + self.assertFalse(state_list.complete_states) + + def test_check_manticore_running(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + stime = time.time() + while not mwrapper.manticore_object.is_running(): + if (time.time() - stime) > 10: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to start running before timeout" + ) + time.sleep(1) + + self.assertTrue( + self.servicer.CheckManticoreRunning(mcore_instance, self.context).is_running + ) + + mwrapper.manticore_object.kill() + + stime = time.time() + while mwrapper.thread.is_alive(): + if (time.time() - stime) > 45: + self.fail( + f"Manticore instance {mcore_instance.uuid} failed to stop running and finish generating reports before timeout" + ) + time.sleep(1) + + self.assertFalse( + self.servicer.CheckManticoreRunning(mcore_instance, self.context).is_running + ) + + def test_check_manticore_running_invalid_manticore(self): + self.assertFalse( + self.servicer.CheckManticoreRunning( + ManticoreInstance(uuid=uuid4().hex), self.context + ).is_running + ) + + def test_control_state_invalid_manticore(self): + self.servicer.ControlState( + ControlStateRequest( + manticore_instance=ManticoreInstance(uuid=uuid4().hex), + state_id=1, + action=ControlStateRequest.StateAction.PAUSE, + ), + self.context, + ) + self.assertEqual(self.context.code, grpc.StatusCode.FAILED_PRECONDITION) + self.assertEqual( + self.context.details, "Specified Manticore instance not found!" + ) + + def test_control_state_invalid_state(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + + self.servicer.ControlState( + ControlStateRequest( + manticore_instance=mcore_instance, + state_id=-1, + action=ControlStateRequest.StateAction.PAUSE, + ), + self.context, + ) + self.assertEqual(self.context.code, grpc.StatusCode.FAILED_PRECONDITION) + self.assertEqual(self.context.details, "Specified state not found!") + + def test_control_state_pause_state(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + stime = time.time() + active_states = self.servicer.GetStateList( + mcore_instance, self.context + ).active_states + while len(active_states) == 0: + time.sleep(1) + if (time.time() - stime) > 30: + self.fail("Manticore did not create an active state before timeout") + active_states = self.servicer.GetStateList( + mcore_instance, self.context + ).active_states + candidate_state_id = list(active_states)[0].state_id + + self.servicer.ControlState( + ControlStateRequest( + manticore_instance=mcore_instance, + state_id=candidate_state_id, + action=ControlStateRequest.StateAction.PAUSE, + ), + self.context, + ) + self.assertEqual(self.context.code, grpc.StatusCode.OK) + self.assertEqual(self.context.details, "") + + self.assertTrue(candidate_state_id in mwrapper.paused_states) + self.assertTrue(-1 in mwrapper.manticore_object._busy_states) + stime = time.time() + paused_states = self.servicer.GetStateList( + mcore_instance, self.context + ).paused_states + while len(paused_states) == 0: + time.sleep(1) + if (time.time() - stime) > 30: + self.fail( + f"Manticore did not pause the state {candidate_state_id} before timeout" + ) + paused_states = self.servicer.GetStateList( + mcore_instance, self.context + ).paused_states + self.assertTrue(candidate_state_id == list(paused_states)[0].state_id) + + def test_control_state_resume_paused_state(self): + self.test_control_state_pause_state() + self.assertTrue(len(self.servicer.manticore_instances) == 1) + + mwrapper = list(self.servicer.manticore_instances.values())[0] + paused_state_id = list(mwrapper.paused_states)[0] + + self.servicer.ControlState( + ControlStateRequest( + manticore_instance=ManticoreInstance(uuid=mwrapper.uuid), + state_id=paused_state_id, + action=ControlStateRequest.StateAction.RESUME, + ), + self.context, + ) + + self.assertEqual(self.context.code, grpc.StatusCode.OK) + self.assertEqual(self.context.details, "") + + self.assertTrue(len(mwrapper.paused_states) == 0) + self.assertTrue(-1 not in mwrapper.manticore_object._busy_states) + + self.assertTrue( + paused_state_id + not in map( + lambda x: x.state_id, + self.servicer.GetStateList( + ManticoreInstance(uuid=mwrapper.uuid), self.context + ).paused_states, + ) + ) + + def test_control_state_kill_active_state(self): + mcore_instance = self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + mwrapper = self.servicer.manticore_instances[mcore_instance.uuid] + + stime = time.time() + active_states = self.servicer.GetStateList( + mcore_instance, self.context + ).active_states + while len(active_states) == 0: + time.sleep(1) + if (time.time() - stime) > 30: + self.fail("Manticore did not create an active state before timeout") + active_states = self.servicer.GetStateList( + mcore_instance, self.context + ).active_states + candidate_state_id = list(active_states)[0].state_id + + self.servicer.ControlState( + ControlStateRequest( + manticore_instance=mcore_instance, + state_id=candidate_state_id, + action=ControlStateRequest.StateAction.KILL, + ), + self.context, + ) + self.assertEqual(self.context.code, grpc.StatusCode.OK) + self.assertEqual(self.context.details, "") + + stime = time.time() + active_states = self.servicer.GetStateList( + mcore_instance, self.context + ).active_states + while len(active_states) != 0: + time.sleep(1) + if (time.time() - stime) > 30: + self.fail( + f"Manticore did not kill the state {candidate_state_id} before timeout" + ) + active_states = self.servicer.GetStateList( + mcore_instance, self.context + ).active_states + + def test_control_state_kill_paused_state(self): + self.test_control_state_pause_state() + self.assertTrue(len(self.servicer.manticore_instances) == 1) + + mwrapper = list(self.servicer.manticore_instances.values())[0] + paused_state_id = list(mwrapper.paused_states)[0] + + self.servicer.ControlState( + ControlStateRequest( + manticore_instance=ManticoreInstance(uuid=mwrapper.uuid), + state_id=paused_state_id, + action=ControlStateRequest.StateAction.KILL, + ), + self.context, + ) + + self.assertEqual(self.context.code, grpc.StatusCode.OK) + self.assertEqual(self.context.details, "") + + self.assertTrue(len(mwrapper.paused_states) == 0) + self.assertTrue(-1 not in mwrapper.manticore_object._busy_states) + + self.assertTrue( + paused_state_id + not in map( + lambda x: x.state_id, + self.servicer.GetStateList( + ManticoreInstance(uuid=mwrapper.uuid), self.context + ).paused_states, + ) + ) + + def test_stop_server(self): + self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + self.servicer.StartNative( + NativeArguments(program_path=self.binary_path), self.context + ) + + self.servicer.StopServer(StopServerRequest(), self.context) + + self.assertTrue(self.test_event.is_set()) + for mwrapper in self.servicer.manticore_instances.values(): + self.assertFalse(mwrapper.manticore_object.is_running())