{"id":18974241,"url":"https://github.com/vincentdary/ghidra-pipe","last_synced_at":"2025-09-12T03:18:15.175Z","repository":{"id":62592451,"uuid":"505157517","full_name":"VincentDary/ghidra-pipe","owner":"VincentDary","description":"Teleport Python code from CPython to Jython","archived":false,"fork":false,"pushed_at":"2022-06-21T12:38:28.000Z","size":38,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-03T13:46:41.253Z","etag":null,"topics":["ghidra","ghidra-plugin","json-rpc-api","jython","python3","reverse-engineering","server","zero-dependency"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VincentDary.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-06-19T16:08:39.000Z","updated_at":"2023-11-22T20:13:09.000Z","dependencies_parsed_at":"2022-11-03T22:58:36.478Z","dependency_job_id":null,"html_url":"https://github.com/VincentDary/ghidra-pipe","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VincentDary%2Fghidra-pipe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VincentDary%2Fghidra-pipe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VincentDary%2Fghidra-pipe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VincentDary%2Fghidra-pipe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VincentDary","download_url":"https://codeload.github.com/VincentDary/ghidra-pipe/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239971581,"owners_count":19727191,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ghidra","ghidra-plugin","json-rpc-api","jython","python3","reverse-engineering","server","zero-dependency"],"created_at":"2024-11-08T15:14:27.885Z","updated_at":"2025-02-21T07:20:56.296Z","avatar_url":"https://github.com/VincentDary.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ghidra Pipe: Teleport Python code from CPython to Jython\n\n\n\n* [What is Ghidra Pipe](#what-is-ghidra-pipe)\n* [Installation](#installation)\n* [Start/Stop the Pipe Server](#startstop-the-pipe-server)\n* [Setting Custom port and hostname for the Pipe](#setting-custom-port-and-hostname-for-the-pipe)\n  + [Server Side](#server-side)\n  + [Client Side](#client-side)\n* [Teleport Python Code from CPython 3 to Jython](#teleport-python-code-from-cpython-3-to-jython)\n  + [Code](#code)\n  + [Function](#function)\n  + [Class](#class)\n  + [Standard Output and Error Redirection](#standard-output-and-error-redirection)\n  + [Remote Exception](#remote-exception)\n  + [Full Usage Example](#full-usage-example)\n* [Custom Pipe Communication Routines](#custom-pipe-communication-routines)\n  + [Custom Binary Communication Example](#custom-binary-communication-example)\n  + [Custom JSON Communication Example](#custom-json-communication-example)\n* [Reach Existing Remote Object from Everywhere Through Proxy](#reach-existing-remote-object-from-everywhere-through-proxy)\n* [Proxy Remote Object Tracking Information](#proxy-remote-object-tracking-information)\n* [File Copy Through Pipe](#file-copy-through-pipe)\n* [Pipe Server JSON RPC Interface](#pipe-server-json-rpc-interface)\n* [Development](#development)\n* [FAQ](#faq)\n  + [Why an Another Tool](#why-an-another-tool)\n  + [Why the Pipe Server use Java Socket](#why-the-pipe-server-use-java-socket)\n  + [Why Ghidra-Pipe no Proxify Ghidra API in the Client Side Global Namespace](#why-ghidra-pipe-no-proxify-ghidra-api-in-the-client-side-global-namespace)\n \n \n\n## What is Ghidra Pipe\n\nGhidra-Pipe provides various ways to interface custom reverse engineering tools with Ghidra environment and its Ghidra Jython API. On one side, a Ghidra Python script run a pipe server which exposes several services though RPC methods. On the other hand, Ghidra-Pipe provides a pipe client API to access these services. The pipe client and the server communicate through classic TCP socket with the JSON RPC V2 protocol. So it is possible to implement a custom pipe client in any language to use the pipe server services. The network traffic is non encrypted (on local by default), since this tool is for research purpose no effort have been done for encrypted communication.\n\nSummary of features:\n\n- Teleport Python code from CPython 3 to Jython:\n  * Remote Python code execution;\n  * Remote Python functions declaration;\n  * Remote Python class declaration;\n  * Function call, class usage and object creation/usage through proxy.\n- Custom pipe communication:\n  * Remote custom communication routine declaration;\n  * Custom communication channel opening through proxy;\n  * Helpers for binary/JSON communication.\n- File copy from local to remote or from remote to local.\n- JSON RPC V2 interface:\n  * Python code execution;\n  * Python function execution; \n  * Python object creation;\n  * Python object attribute getter and setter;\n  * Register/call custom Python communicator;\n  * File copy from local to remote or from remote to local.\n- Lightweight code (\u003c 1000 loc), zero dependency and easy to modify to feet your need.\n\n\n## Installation\n\nInstall the Ghidra-Pipe Python package from the Python Package Index (PyPI).\n\n```text\n$ pip install ghidra-pipe\n```\n\nCopy the pipe server plugins to a custom Ghidra plugins directory.  \n\n```text\n$ ghidra-pipe --plugin-install /path/to/ghidra_plugins\n```\n\nThe plugin installer copy the following Python script in the ghidra-pipe directory.\n\n```text\n$ tree ghidra-pipe/\nghidra-pipe/\n├── pipe_default_conf.py\n├── pipe_server.py\n├── plugin_ghidra_pipe_server_start.py\n└── plugin_ghidra_pipe_server_stop.py\n\n0 directories, 4 files\n```\n\n\nThis tool has been tested on these platforms and configuration, but can probably work on variant.\n\n| OS          | Python | Jython | Java Runtime                      |\n|-------------|--------|--------|-----------------------------------|\n| GNU/linux   | 3.10.5 | 2.7.2  | OpenJDK 11.0.15+10                |\n| GNU/linux   | 3.8.0  | 2.7.2  | OpenJDK 11.0.15+10                |\n| GNU/linux   | 3.7.0  | 2.7.2  | OpenJDK 11.0.15+10                |\n| GNU/linux   | 3.6.0  | 2.7.2  | OpenJDK 11.0.15+10                |\n| GNU/linux   | 3.5.4  | 2.7.2  | OpenJDK 11.0.15+10                |\n| Windows x64 | 3.10.5 | 2.7.2  | adoptopenjdk jdk-17.0.3.7-hotspot |\n| Windows x64 | 3.8.1  | 2.7.2  | adoptopenjdk jdk-17.0.3.7-hotspot |\n| Windows x64 | 3.7.1  | 2.7.2  | adoptopenjdk jdk-17.0.3.7-hotspot |\n| Windows x64 | 3.6.0  | 2.7.2  | adoptopenjdk jdk-17.0.3.7-hotspot |\n| Windows x64 | 3.5.3  | 2.7.2  | adoptopenjdk jdk-17.0.3.7-hotspot |\n\n## Start/Stop the Pipe Server\n\nStart the pipe server via the Ghidra GUI, open the script manager window in `Window \u003e Script Manager`, and  run  the  script `plugin_ghidra_pipe_server_start.py` localised in the `ghidra_pipe` directory.\n\nStop the pipe server via the Ghidra GUI, open the script manager window in `Window \u003e Script Manager`, and  run  the  script `plugin_ghidra_pipe_server_stop.py` localised in the `ghidra_pipe` directory.\n\nIt is also possible to stop the pipe server via the pipe client API.\n\n```text\n\u003e\u003e\u003e from ghidra_pipe import PipeClient\n\u003e\u003e\u003e PipeClient().server_remote_shutdown()\n```\n\n## Setting Custom port and hostname for the Pipe\n\n### Server Side\n\nBy default, the pipe server listen for incoming connection on localhost and TCP port 5098. These parameters are configurable through the configuration file localised in the Ghidra-Pipe plugins directory in `ghidra_pipe/pipe_default_conf.py` with the variables `PIPE_IP` and `PIPE_PORT` (before Python module import).\n\n### Client Side\n\nBy default, all pipe client methods initiate connection on localhost and TCP port 5098. These parameters are configurable globally via the environment variables `PIPE_IP` and `PIPE_PORT` (before Python module import). Otherwise, the `PipeClient` class accept the optional keyword arguments `ip_address` and `port`.\n\n```text\n\u003e\u003e\u003e from ghidra_pipe import PipeClient\n\u003e\u003e\u003e pipe_client = PipeClient(ip_address='192.168.1.35', port=5090)\n```\n\n## Teleport Python Code from CPython 3 to Jython\n\nTeleport Python code from CPython 3 to Jython requires that the teleported code is compatible with CPython 3 and the remote version of Jython (2/3).\n\n### Code\n\nThe `PipeClient.exec` method allows for remote Python code execution. The method take Python source code as argument which will be executed on the remote global namespace of the pipe server via a classic exec. Note that this Python source code does not need to be compatible Python 3 since it will be never evaluate locally. Stdout and stderr of the code executed remotely is forwarded locally and can be captured and returned by the method if the `std_cap` option is set.\n\n```text\n\u003e\u003e\u003e from ghidra_pipe import PipeClient\n\u003e\u003e\u003e pipe_client = PipeClient()\n\u003e\u003e\u003e pipe_client.exec(\"\"\"\n... import sys\n... print(sys.version)\n... \"\"\")\n2.7.2 (v2.7.2:925a3cc3b49d, Mar 21 2020, 10:03:58)\n[OpenJDK 64-Bit Server VM (Oracle Corporation)]\n```\n\nSince the code is executed in the remote global namespace of the pipe server all your import and object created at runtime are available between `exec` call.\n\n```text\n\u003e\u003e\u003e pipe_client.exec('a = 78')\n\u003e\u003e\u003e output = pipe_client.exec('print(a)', std_cap=True)\n78\n\u003e\u003e\u003e output\n'78\\n'\n```\n\nThis feature can be useful to execute a third party script in Jython interpreter. \n\n```text\n\u003e\u003e\u003e with open('/tmp/test_jython_script.py', 'r') as f:\n...     output = PipeClient().exec(f.read(), std_cap=True)\n... \n```\n\n\n### Function\n\nThe `PipeClient.register_func` method allow remote function declaration. It retrieves the source code of the function pass as argument and execute it on the remote global namespace of the pipe server. Note that this feature is supported natively in IPython REPL but not in classic REPL due to source code retrieving issues.  \n\n```python\nfrom ghidra_pipe import PipeClient\n\ndef remote_func(a, b=True):\n    return a, b\n\nremote_func = PipeClient().register_func(remote_func)\n```\n\nThe method return a function proxy which can be used to invoke the remote Python function transparently. Function arguments and return values are limited to the following Python basic types : None, int, float, bool, str, dict, list, tuple, bytearray. Stdout and stderr of the function invoked remotely is forwarded locally.\n\n```python\nprint(remote_func)\na, b = remote_func(4, b=False)\nprint(a)\nprint(b)\n```\n\nOutput.\n\n```text\n\u003cfunction PipeClient.func_proxy_factory.\u003clocals\u003e.func_proxy at 0x7f1b249abac0\u003e\n4\nFalse\n```\n\n\n\n### Class\n\nThe `PipeClient.register_class` method allow remote class declaration. It retrieves the source code of the class pass as argument and execute it on the remote global namespace of the pipe server. Note that this feature is not supported in Python/IPython REPL due to source code retrieving issues.  \n\n```python\nfrom ghidra_pipe import PipeClient\n\nclass Foo:\n    CLASS_ATTR = 78\n\n    @staticmethod\n    def static_method(a):\n        return a\n\n    @classmethod\n    def class_method(cls):\n        return cls.CLASS_ATTR + 2\n\n    def instance_method(self):\n        print(self)\n\nFoo = PipeClient().register_class(Foo)\n```\n\nThe decorator return a class proxy which can be used transparently as a standalone class or to create new object.\n\n```python\nprint(Foo)\nprint(Foo.CLASS_ATTR)\nprint(Foo.static_method(5))\nprint(Foo.class_method())\n```\n\nOutput.\n```text\n\u003cclass 'ghidra_pipe.pipe_client._class_proxy_factory.\u003clocals\u003e.ClassProxy'\u003e\n78\n5\n80\n```\n\nWhen the class proxy is called for new object creation, a new object is created in the remote global namespace of the pipe server and the class proxy return an object proxy which can be used transparently.\n\n\n```python\nfoo_obj =  Foo()\nprint(foo_obj)\nfoo_obj.instance_method()\n```\n\nOutput.\n```text\n\u003cghidra_pipe.pipe_client.ObjProxy object at 0x7febb567b070\u003e\n\u003cpipe_server.Foo instance at 0x80\u003e\n```\n\n\nNote that attributes access, return values, class and object method arguments are limited to the following Python basic types : None, int, float, bool, str, dict, list, bytearray. Stdout and stderr of the class/object methods executed remotely is forwarded locally.\n\n### Standard Output and Error Redirection\n\nBy default, the standard output and the standard error of the code executed remotely are forwarded to the standard output and error of the client. This behaviour can be change with the `std_forward` flag of the `PipeClient`.\n\n```text\n\u003e\u003e\u003e from ghidra_pipe import PipeClient\n\u003e\u003e\u003e PipeClient().exec('print(\"debug\")')\ndebug\n\u003e\u003e\u003e PipeClient(std_forward=False).exec('print(\"debug\")')\n```\n\n\n### Remote Exception\n\nIf Python code executed remotely raise an exception an `PipeServerRemoteCodeExecErr` exception is raised locally. This exception contains various debug information as the code which raise the exception, the remote stacktrace, the port and the ip of the pipe server.\n\n\n```python\nfrom ghidra_pipe import PipeClient, PipeServerRemoteCodeExecErr\n\ndef this_func_raise_an_exception():\n    v = 1 + not_exist\n\nthis_func_raise_an_exception = PipeClient().register_func(this_func_raise_an_exception)\n\ntry:\n    this_func_raise_an_exception()\nexcept PipeServerRemoteCodeExecErr as ex:\n    print(ex.code)\n    print('-'*10)\n    print(ex.stacktrace)\n    print('-'*10)\n    print(ex.ip)\n    print(ex.port)\n```\n\nOutput.\n```text\n__ret__=this_func_raise_an_exception()\n----------\nTraceback (most recent call last):\n  File \"/home/pink/ghidra-pipe/src/ghidra_pipe/pipe_server.py\", line 263, in py_code_exec\n    exec(\"\"\"exec py_code in globals()\"\"\")\n  File \"\u003cstring\u003e\", line 1, in \u003cmodule\u003e\n  File \"\u003cstring\u003e\", line 1, in \u003cmodule\u003e\n  File \"\u003cstring\u003e\", line 2, in this_func_raise_an_exception\nNameError: global name 'not_exist' is not defined\n\n----------\nlocalhost\n5098\n```\n\n### Full Usage Example\n\nThis demonstration script shows how the pipe client interface can be used in a complementary way. First, the `PipeClient.exec` is used to perform Python module import in the remote global namespace of the pipe server. Next, The class `GhidraColor` and the functions `set_memory_color`, `get_current_addr` are declared in the remote global namespace though the `PipeClient.register_class` and the `PipeClient.register_func` methods. Then these class and functions are used locally to color 8 bytes of memory in blue and the next 8 bytes of memory in white. Next, the `PipeClient.exec` send code to the pipe server which use these same class and functions remotely to color the next 8 bytes of memory in blue.\n\n```python\nfrom ghidra_pipe import PipeClient\n\npipe_client = PipeClient()\n\n# Python modules import\npipe_client.exec(\"\"\"\nfrom ghidra.program.model.address import AddressSet\nfrom ghidra.app.plugin.core.colorizer import ColorizingService\nfrom java.awt import Color\n\"\"\")\n    \nclass GhidraColor:\n    def __init__(self):\n        self.colorizing_service = state.getTool().getService(ColorizingService)\n\n    def set_color(self, addr, rgb1, rgb2, rgb3):\n        self.colorizing_service.setBackgroundColor(\n            toAddr(addr), toAddr(addr), Color(rgb1, rgb2, rgb3))\n\ndef set_memory_color(addr, rgb1, rgb2, rgb3):\n    # Usage of class GhidraColor previously declared\n    g_color = GhidraColor()\n    g_color.set_color(addr, rgb1, rgb2, rgb3)\n\ndef get_current_addr():\n    return int(currentAddress.toString(), 16)\n\n# Remote object declaration\nGhidraColor = pipe_client.register_class(GhidraColor)\nset_memory_color = pipe_client.register_func(set_memory_color)\nget_current_addr = pipe_client.register_func(get_current_addr)\n\n# Local usage of remote class and function declared\ncurrent_addr = get_current_addr()\nghidra_color = GhidraColor()\nghidra_color.set_color(current_addr, 0, 0, 255)  # blue\nghidra_color.set_color(current_addr + 4, 0, 0, 255)  # blue\nset_memory_color(current_addr + 8, 255, 255, 255)  # white\nset_memory_color(current_addr + 12, 255, 255, 255)  # white\n\n# Remote usage of class and function declared remotly\npipe_client.exec(\"\"\"\ncurrent_addr = get_current_addr()\nremote_ghidra_color = GhidraColor()\nremote_ghidra_color.set_color(current_addr + 16, 255, 0, 0)  # red\nremote_ghidra_color.set_color(current_addr + 20, 255, 0, 0)  # red\n\"\"\")\n```\n\n\n## Custom Pipe Communication Routines\n\nThe `PipeClient.register_custom_communicator` method allows to create a custom communication channels between an external tools and the remote routine. It retrieves the source code of the function pass as argument and executes it on the remote global namespace of the pipe server. The remote function is registered as a custom communication routine and become available. A communicator proxy is returned which can be used to open a custom communication channel with the remote routine. The code of the routine must compatible with CPython 3 and the remote version of Jython (2/3)Python.\n\n### Custom Binary Communication Example\n\nThe following example registers the `coffee_communicator` communication routine. The routine send the value `0xc0dec0fe` to the client and enter an infinite receive loop which except the value `0xc0febab1` to close the communication. \n\n```python\nfrom ghidra_pipe import PipeClient\n\ndef coffee_communicator(tcp_net_io):\n  msg_out = jarray.zeros(0, 'b')\n  msg_out.fromstring(b'\\xC0\\xDE\\xC0\\xFE')\n  tcp_net_io.sendall(msg_out)\n  while True:\n    msg_in = tcp_net_io.recvall(4)\n    msg_out = jarray.zeros(0, 'b')\n    if msg_in.tostring() == b'\\xC0\\xFE\\xBA\\xB1':\n      msg_out.fromstring(b'\\xDE\\xAD\\xBE\\xEF')\n      tcp_net_io.sendall(msg_out)\n      tcp_net_io.sock.close()\n      return\n    else:\n      msg_out.fromstring(b'\\xFF\\xFF\\xFF\\xFF')\n      tcp_net_io.sendall(msg_out)\n\ncoffee_communicator = PipeClient().register_custom_communicator(coffee_communicator)\nprint(coffee_communicator)\n```\nOutput.\n```\n\u003cfunction PipeClient.communicator_proxy_factory.\u003clocals\u003e.communicator_proxy at 0x7fc858293880\u003e\n```\n\nWhen the communicator proxy is invoked a communication channel is open with the pipe server and the remote communication routine on the server side is called with a `pipe_server.JavaTcpNetIo` instance as argument. The communicator proxy return a `pipe_client.TcpNetIo` instance. These two objects in each side wrap a client socket connected to each other and provide communication helper methods (`sendall`, `recvall`, `recvall_to_file`, `sendall_from_file`)\n\n```python\nclient_tcp_net_io = coffee_communicator()\nprint(client_tcp_net_io)\nprint(client_tcp_net_io.sock)\n```\nOutput.\n```text\n\u003cghidra_pipe.pipe_client.TcpNetIo object at 0x7fc858392320\u003e\n\u003csocket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 60350), raddr=('127.0.0.1', 5098)\u003e\n```\n\nNext the communication with the remote routine is easy.\n\n```python\nprint( client_tcp_net_io.recvall(4) )\nclient_tcp_net_io.sendall(b'\\xC0\\xFE\\xBA\\xB1')\nprint( client_tcp_net_io.recvall(4) )\n```\nOutput.\n```text\nbytearray(b'\\xc0\\xde\\xc0\\xfe')\nbytearray(b'\\xde\\xad\\xbe\\xef')\n```\n\nUse the underlying socket to close the communication.\n\n```python\nclient_tcp_net_io.sock.close()\n```\n\n\n### Custom JSON Communication Example\n\nThe following example registers the `json_coffe_communicator` communication routine. The routine send a JSON message with the string 'C0DEC0FE' to the client and enter an infinite receive loop which except a JSON message with the string 'C0FEBAB1' to close the communication. \n\n```python\nfrom ghidra_pipe import PipeClient\n\ndef json_coffee_communicator(tcp_json_com):\n    tcp_json_com.send({'data': 'C0DEC0FE' })\n    while True:\n        msg_in = tcp_json_com.recv()\n        if msg_in['data'] == 'C0FEBAB1':\n            tcp_json_com.send({'data': 'DEADBEEF' })\n            tcp_json_com.io.sock.close()\n        else:\n            tcp_json_com.send({'data': 'retry' })\n\njson_coffee_communicator = PipeClient().register_custom_communicator(\n  json_coffee_communicator, com_type='json')\nprint(json_coffee_communicator)\n```\nOutput.\n```text\n\u003cfunction PipeClient.communicator_proxy_factory.\u003clocals\u003e.communicator_proxy at 0x7f5308aa3ac0\u003e\n```\n\nWhen the communicator proxy is invoked a communication channel is open with the pipe server and the remote communication routine on the server side is called with a `pipe_server.JavaTcpJsonCom` instance as argument and the function proxy return a `TcpJsonCom` instance. These two objects in each side wrap a client socket connected to each other and provide JSON communication helper methods (`send`, `recv`).\n\n```python\ntcp_json_com = json_coffee_communicator()\nprint(tcp_json_com)\nprint(tcp_json_com.io)\nprint(tcp_json_com.io.sock)\n```\nOutput.\n```text\n\u003cghidra_pipe.pipe_client.TcpJsonCom object at 0x7f5308977fa0\u003e\n\u003cghidra_pipe.pipe_client.TcpNetIo object at 0x7f5308977c40\u003e\n\u003csocket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 46422), raddr=('127.0.0.1', 5098)\u003e\n\n```\n\nNext the communication with the remote routine is easy.\n\n```python\nprint( tcp_json_com.recv() )\ntcp_json_com.send({'data': 'C0FEBAB1'})\nprint(tcp_json_com.recv())\n```\nOutput.\n```text\n{'data': 'C0DEC0FE'}\n{'data': 'DEADBEEF'}\n```\n\nUse the underlying socket to close the communication.\n\n```python\ntcp_json_com.io.sock.close()\n```\n\n\n## Reach Existing Remote Object from Everywhere Through Proxy\n\nRemote existing object declared in the global namespace of the pipe server as class, object and communicator can be reach from anywhere via the following pipe client proxy interface:\n- `PipeClient.func_proxy_factory`\n- `PipeClient.class_proxy_factory`\n- `PipeClient.obj_proxy_factory`\n- `PipeClient.communicator_proxy_factory`.\n\nExample to reach a function previously declared.\n\n```python\nfrom ghidra_pipe import PipeClient\n\n\ndef foo():\n  print(sys.version)\n\n\nfoo = PipeClient().register_func(foo)\nfoo()\n```\nOutput.\n```text\n2.7.2 (v2.7.2:925a3cc3b49d, Mar 21 2020, 10:03:58)\n[OpenJDK 64-Bit Server VM (Oracle Corporation)]\n```\n\nReach this object from another place.\n\n```text\n\u003e\u003e\u003e from ghidra_pipe import PipeClient\n\u003e\u003e\u003e foo = PipeClient().func_proxy_factory('foo')\n\u003e\u003e\u003e foo()\n2.7.2 (v2.7.2:925a3cc3b49d, Mar 21 2020, 10:03:58)\n[OpenJDK 64-Bit Server VM (Oracle Corporation)]\n```\n\n\n## Proxy Remote Object Tracking Information\n\nFor each remote function, class, object or custom communicator declared in the remote global namespace of the pipe server a proxy is returned by the pipe client interface. Each proxy keep track of basic information about the target pipe server and the remote object via the following attributes: `__PROXY_IP__`, `__PROXY_PORT__`, `__PROXY_OBJECT_NAME__`, `__PROXY_SRC__`. Example with a remote function.\n\n```python\nfrom ghidra_pipe import PipeClient\n\ndef foo():\n    pass\n\nfoo = PipeClient().register_func(foo)\n\nprint(foo.__PROXY_IP__)\nprint(foo.__PROXY_PORT__)\nprint(foo.__PROXY_OBJECT_NAME__)\nprint(\"-\"*80)\nprint(foo.__PROXY_SRC__)\n```\nOutput.\n```text\nlocalhost\n5098\nfoo\n--------------------------------------------------------------------------------\ndef foo():\n    pass\n```\n\nKeep in mind that if an object is redefined on a target remote pipe server, all object proxy which bind this object have the `__PROXY_SRC__` attribute de-synchronised with the remote object, because this information was not updated at runtime.\n\n\n## File Copy Through Pipe\n\nThe pipe client interface provides a way to copy file from local to remote, from remote to local, from local bytes buffer to remote file and remote file to local bytes buffer. \n\nCopy a local file on the remote pipe server filesystem.\n\n```text\n\u003e\u003e\u003e from ghidra_pipe import PipeClient\n\u003e\u003e\u003e\n\u003e\u003e\u003e with open('/tmp/local_file.bin', 'wb') as f:\n...     f.write(b'\\xDE\\xAD\\xC0\\xDE')\n... \n4\n\u003e\u003e\u003e PipeClient().file_transfer_to_server('/tmp/local_file.bin', '/tmp/remote_file.bin')\n```\n\nCopy a remote file from remote pipe server filesystem to local.\n\n```text\n\u003e\u003e\u003e PipeClient().file_transfer_to_client('/tmp/remote_file.bin', '/tmp/local_file_comeback.bin')\n4\n\u003e\u003e\u003e with open('/tmp/local_file_comeback.bin', 'rb') as f:\n...      f.read()\n... \nb'\\xde\\xad\\xc0\\xde'\n```\n\nCopy a local bytes buffer to a file on the remote pipe server filesystem.\n\n```text\n\u003e\u003e\u003e PipeClient().file_bytes_transfer_to_server(b'\\xC0\\xFE\\xBA\\xB1', '/tmp/bytes_remote_file.bin')\n```\n\nCopy a file on the remote pipe server filesystem to local bytes buffer.\n\n```text\n\u003e\u003e\u003e PipeClient().file_bytes_transfer_to_client('/tmp/bytes_remote_file.bin')\nbytearray(b'\\xc0\\xfe\\xba\\xb1')\n```\n\n## Pipe Server JSON RPC Interface\n\nThe pipe server expose a [JSON RPC V2](https://www.jsonrpc.org/specification) Interface. The batch mode is not implemented. The pipe server is mono thread and process only one client at time. One RPC method is processed by connection.  The JSON frames exchanged by the client and the server are length prefixed as following. This frame encoding scheme is very simply and can be implemented in any language.\n\n```text\n    4 bytes\n+----------------+-------------------- // --------------------+\n|  JSON LENGTH   |                JSON MESSAGE                |\n+----------------+-------------------- // --------------------+\n```\n\nAs described in the JSON RPC V2 documentation the RPC requests take the following forms:\n```text\n{'jsonrpc': '2.0', 'id': \u003cunique_request_identifier\u003e, 'method': \u003cmethod_name\u003e, 'params': {}}\n```\n\nAnd the RPC notification take the following forms:\n```text\n{'jsonrpc': '2.0', 'method': \u003cmethod_name\u003e, 'params': {}}\n```\n\n\nThe following RPC methods are available via RPC request:\n- get_server_banner\n- code_exec\n- func_exec\n- object_proxy_new\n- object_proxy_getattr\n- object_proxy_setattr\n- remote_shutdown\n- register_custom_communicator\n\nThe following RPC methods are available via RPC notification:\n- execute_custom_communicator\n- file_transfer_to_client\n- file_transfer_to_server\n\nAll the pipe server RPC methods are described in the following document [json_rpc_api_pipe_server.md](./json_rpc_api_pipe_server.md)\n\n\n## Development\n\nInstall the package in develop mode with the `DEV` identifier.\n\n```text\n$ git clone https://github.com/vincentdary/ghidra-pipe\n$ cd ghidra_pipe\n$ pip install -e .[DEV]\n```\n\nFor coverage information for both pipe client and server side install coverage in Jython and in Python2 (Required because Jython coverage do not support report generation). \n```text\n$ jython -m pip install coverage==5.6b1\n$ python2 -m pip install coverage==4.3.4\n```\n\nRun the tests.\n\n```text\n$ cd ghidra_pipe/test/  \u0026\u0026 ./run_tests.sh\n```\n\nFor coverage information run the test with the coverage flag.\n\n```text\n$ cd ghidra_pipe/test/ \u0026\u0026 ./run_tests.sh --coverage\n```\n\nSee the coverage of the pipe client code.\n\n```text\n$ firefox  pipe_client_coverage/htmlcov/index.html \u0026\n```\n\nSee the coverage of the pipe server code. \n\n```text\n$ cd pipe_server_coverage/ \u0026\u0026 coverage2 html\n$ firefox pipe_server_coverage/htmlcov/index.html \u0026\n```\n\n## FAQ\n\n### Why an Another Tool\n\nThe author was charmed by [Ghidra Bridge](https://github.com/justfoxing/ghidra_bridge), but the tool was not working as expected (very slow) and was not expose the desired interface. That's why this new tool was created, with fewer functionalities but with different technical choices and much less code.\n\n### Why the Pipe Server use Java Socket\n\nThe pipe server use Java socket based on `java.net` instead of the socket library of Jython. The reason of this choice is caused by the slowness of the Jython socket interface (based on `io.netty`) due to the conversion of Java bytes to Python bytes. In any case the conversion Java/Python bytes with tostring /fromstring must be avoided for large data length when it is possible to avoid bottlenecks. The pipe server gets around this problem for exceptional cases when it is necessary by dropping and loading the content of Java/Python byte array in temporary file. It is a bit dirty, but it allows avoiding bottlenecks.\n\n### Why Ghidra-Pipe no Proxify Ghidra API in the Client Side Global Namespace\n\nBind the Ghidra API in the global namespace of the pipe client can be more convenient for REPL purpose and can allow less boilerplate code to access Ghidra Jython API. [Ghidra Bridge](https://github.com/justfoxing/ghidra_bridge) provides this features. However, this choice has disastrous performance because for each method call or attribute access on remote object an underlying request must be sent to the server proxy which includes at least request/response serialization/deserialization and request processing. For example, to perform comparison between two remote object of type GenericAdresse this will involve several network exchanges and server/client processing before to obtain the result. This proxy mechanism slow by design is not suitable or unusable for large Ghidra script. Moreover, provide this feature correctly requires implementing a lot of mechanics. This is why Ghidra-Pipe has chosen to not implement this feature.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvincentdary%2Fghidra-pipe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvincentdary%2Fghidra-pipe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvincentdary%2Fghidra-pipe/lists"}