{"id":15014783,"url":"https://github.com/doronz88/rpc-project","last_synced_at":"2026-01-19T12:01:04.934Z","repository":{"id":37003971,"uuid":"451455508","full_name":"doronz88/rpc-project","owner":"doronz88","description":"Minimalistic server (written in C) and a python3 client to allow calling native functions on a remote host for automation purposes","archived":false,"fork":false,"pushed_at":"2025-12-24T11:03:36.000Z","size":1501,"stargazers_count":66,"open_issues_count":0,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-12-26T01:49:14.710Z","etag":null,"topics":["automation","c","ios","ipython","linux","macos","python","python3","remote-control","remote-shell","shell"],"latest_commit_sha":null,"homepage":"https://rpc-project.readthedocs.io/","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/doronz88.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-01-24T12:29:17.000Z","updated_at":"2025-12-24T11:03:31.000Z","dependencies_parsed_at":"2023-01-17T12:32:33.927Z","dependency_job_id":"d82ede33-60e5-422e-a30f-6387fec50061","html_url":"https://github.com/doronz88/rpc-project","commit_stats":null,"previous_names":[],"tags_count":182,"template":false,"template_full_name":null,"purl":"pkg:github/doronz88/rpc-project","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Frpc-project","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Frpc-project/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Frpc-project/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Frpc-project/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doronz88","download_url":"https://codeload.github.com/doronz88/rpc-project/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Frpc-project/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28567861,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-19T08:53:44.001Z","status":"ssl_error","status_checked_at":"2026-01-19T08:52:40.245Z","response_time":67,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["automation","c","ios","ipython","linux","macos","python","python3","remote-control","remote-shell","shell"],"created_at":"2024-09-24T19:46:05.270Z","updated_at":"2026-01-19T12:01:04.907Z","avatar_url":"https://github.com/doronz88.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rpc-Project\n\n\u003c!-- markdownlint-disable MD013 --\u003e\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/doronz88/rpc-project)\n\u003c!-- markdownlint-enable MD013 --\u003e\n\n\n- [rpc-project](#rpc-project)\n  - [Description](#description)\n  - [Local installation](#local-installation)\n  - [Remote installation](#remote-installation)\n  - [Building](#building)\n    - [macOS/iOS](#macosios)\n    - [Linux](#linux)\n  - [Quickstart](#quickstart)\n  - [Calling native functions](#calling-native-functions)\n    - [Globalized symbols](#globalized-symbols)\n    - [ObjC support](#objc-support)\n\n# rpc-project\n\n## Description\n\nSimple RPC service providing an API for controlling every aspect of the target machine. Thie swiss army knife can be used for:\n\n- QA Automation\n- Developement (simply test your APIs through python)\n- Software research (found an interesting OS API? try it out with no compilation required!)\n\nThis project includes two components:\n\n- Server binary written in C exposing a protocol to call native C functions.\n- Client written in Python3 to communicate with the server\n\nThe python client utilizes the ability to call native functions in order to provide APIs for different aspects:\n\n- Remote system commands (`p.spawn()`)\n- Remote shell (`p.shell()`)\n- Filesystem management (`p.fs.*`)\n- Network management (WiFi scan, TCP connect, etc...) (`p.network.*`)\n- Sysctl API (`p.sysctl.*`)\n- Darwin only:\n  - Multimedia automation (recording and playing) (`p.media.*`)\n  - Preferences managemnent (remote manage CFPreference and SCPreferences) (`p.preferences.*`)\n  - Process management (kill, list, query open FDs, etc...) (`p.processes.*`)\n  - Location services (`p.location.*`)\n  - HID simulation (`p.hid.*`)\n    - Control battery properties (current percentage and temperature)\n    - Simulate touch and keyboard events\n  - IORegistry API (`p.ioregistry.*`)\n  - Reports management (Logs and Crash Reports) (`p.reports.*`)\n  - Time settings (`p.time.*`)\n  - Bluetooth management (`p.bluetooth.*`)\n  - Location Services (`p.location.*`)\n  - XPC wrappers (`p.xpc.*`)\n  - iOS Only:\n    - MobileGestalt (`p.mobile_gestalt.*`)\n    - Backlight adjusting (`p.backlight.*`)\n    - Dump decrypted applications (`p.processes.get_by_basename(process_name).dump_app('/path/to/output')`)\n\nand much more...\n\n## Local installation\n\n```shell\n# install the package\npython3 -m pip install -U rpcclient\n\n# enter shell\nrpclocal\n```\n\n## Remote installation\n\nDownload and execute the latest server artifact, according to your platform and arch from the latest [GitHub action execution](https://github.com/doronz88/rpc-project/actions/workflows/server-publish.yml).\n\nIf your specific platform and arch isn't listed, you can also [build it yourself](#building).\n\nInstall the latest client from PyPi:\n\n```shell\npython3 -m pip install -U rpcclient\n```\n\n## Building\n\n**Note:** Cross-platform support is currently not available.\n\n### macOS/iOS\nFor macOS/iOS (Ensure that Xcode is installed):\n\n```bash\nbrew install protobuf protobuf-c\npython3 -m pip install mypy-protobuf protobuf grpcio-tools\ngit clone git@github.com:doronz88/rpc-project.git\ncd rpc-project\nmake -C src/protos/ all\ncd src/rpcserver\nmkdir build\ncd build\ncmake .. -DTARGET=OSX\nmake\ncmake .. -DTARGET=IOS \nmake\n```\n\n### Linux\n\n```bash\nsudo apt-get install -y protobuf-compiler libprotobuf-dev libprotoc-dev protobuf-c-compiler\npython3 -m pip mypy-protobuf protobuf grpcio-tools\ngit clone git@github.com:doronz88/rpc-project.git --recurse-submodules\ncd rpc-project\nmake -C src/protos/ all\ncd src/rpcserver\nmkdir build\ncd build\ncmake .. -DTARGET=LINUX\nmake\n```\n\n## Quickstart\n\nTo execute the server:\n\n```\nUsage: ./rpcserver [-p port] [-o (stdout|syslog|file:filename)]\n-h  show this help message\n-o  output. can be all of the following: stdout, syslog and file:filename. can be passed multiple times\n\nExample usage:\n./rpcserver -p 5910 -o syslog -o stdout -o file:/tmp/log.txt\n```\n\nConnecting via:\n\n```shell\npython3 -m rpcclient\n```\n\nFull usage:\n\n```\nUsage: rpcclient [OPTIONS] [HOSTNAME]\n\n  Start the console. If HOSTNAME is provided, connect immediately. Otherwise,\n  start without a connection. You can connect later from the console.\n\nOptions:\n  -p, --port INTEGER        TCP port to connect to\n  -r, --rebind-symbols      reload all symbols upon connection\n  -l, --load-all-libraries  load all libraries\n  --help                    Show this message and exit.\n```\n\n\u003e **_NOTE:_** If you are attempting to connect to a **jailbroken iOS device**, you will be required to also create a TCP tunnel to your device. For example, using: [`pymobiledevice3`](https://github.com/doronz88/pymobiledevice3): ```python3 -m pymobiledevice3 usbmux forward 5910 5910 -vvv```\n\nYou should now get a nice iPython shell looking like this:\n\n```\nRpcClient has been successfully loaded! 😎\nUsage:\nmgr     Client manager: create | get | remove | clients | clear\nconsole Console controller: switch\np       Active client (e.g., p.info(), p.pid)\nF1      Show this help\nF2      Show active contexts\nF3      Previous context\nF4      Toggle Auto switch on creation\nHave a nice flight ✈️! Starting an IPython shell...\nPython 3.12.7 (main, Oct  1 2024, 02:05:46) [Clang 16.0.0 (clang-1600.0.26.3)]\nType 'copyright', 'credits' or 'license' for more information\nIPython 9.4.0 -- An enhanced Interactive Python. Type '?' for help.\n\nIPython profile: rpcclient\nTip: Use `F2` or %edit with no arguments to open an empty editor with a temporary file.\n\n[Rpc-client]:\n```\n\n### Understanding the globals: mgr, console, and p\n\n- mgr — Client manager for creating and tracking RPC clients\n    - `mgr.create(hostname=\"127.0.0.1\", port=5910)` → create and connect a new client\n    - `mgr.get(pid)` → get a client by PID\n    - `mgr.remove(pid)` → disconnect and remove a client\n    - `mgr.clients` → list current clients\n    - `mgr.clear()` → remove all clients\n\n- console — Console/session controller for switching active client contexts\n    - `console.switch(pid)` → switch the active context to a specific PID\n    - `console.switch()` → interactively pick a client to switch to\n\n- p — The active client in the current console context\n    - Use p to call APIs, e.g., `p.info()`, `p.pid`, `p.fs.listdir(\".\")`, `p.spawn([...])`\n    - When you switch contexts, p is automatically updated to the selected client\n\n\nCreate a new client:\n```\n[Rpc-client]: mgr.create(hostname=\"127.0.0.1\")\nAuto-switched to new client PID: 79669\n[osx| (79669) rpcserver_macosx]: \u003cMacosClient: 79669 | rpcserver_macosx\u003e\n\n[osx| (79669) rpcserver_macosx]:\n```\n\nChange the console context to another client:\n```\n[Rpc-client]: console.switch()\n[?] Select a client PID: \u003cMacosClient: 78536 | rpcserver_macosx\u003e\n   \u003cMacosClient: 78535 | rpcserver_macosx\u003e\n ❯ \u003cMacosClient: 78536 | rpcserver_macosx\u003e\n```\n\nNow you can try accessing the different features using the global `p` variable.\nFor example (Just a tiny sample of the many things you can now do. Feel free to explore much more!):\n\n```\n[osx| (78479) rpcserver_macosx]: p.spawn(['sleep', '1'])\n[osx| (78479) rpcserver_macosx]: SpawnResult(error=0, pid=25047, stdout=\u003c_io.TextIOWrapper name='\u003cstdout\u003e' mode='w' encoding='utf-8'\u003e)\n\n[osx| (78479) rpcserver_macosx]: p.fs.listdir('.')\n[osx| (78479) rpcserver_macosx]:\n['common.c',\n '.pytest_cache',\n 'Makefile',\n 'rpcserver_iphoneos_arm64',\n 'rpcserver.c',\n 'common.h',\n 'rpcserver_macosx_x86_64',\n 'ents.plist',\n 'build_darwin.sh']\n\n[osx| (78479) rpcserver_macosx]: p.processes.get_by_pid(p.pid).fds\n[osx| (78479) rpcserver_macosx]:\n[FileFd(fd=0, path='/dev/ttys000'),\n FileFd(fd=1, path='/dev/ttys000'),\n FileFd(fd=2, path='/dev/ttys000'),\n Ipv6TcpFd(fd=3, local_address='0.0.0.0', local_port=5910, remote_address='0.0.0.0', remote_port=0),\n Ipv6TcpFd(fd=4, local_address='127.0.0.1', local_port=5910, remote_address='127.0.0.1', remote_port=53217),\n Ipv6TcpFd(fd=5, local_address='127.0.0.1', local_port=5910, remote_address='127.0.0.1', remote_port=53229),\n Ipv6TcpFd(fd=6, local_address='127.0.0.1', local_port=5910, remote_address='127.0.0.1', remote_port=53530)]\n\n[osx| (78479) rpcserver_macosx]: p.processes.get_by_pid(p.pid).regions[:3]\n[osx| (78479) rpcserver_macosx]:\n[Region(region_type='__TEXT', start=4501995520, end=4502028288, vsize='32K', protection='r-x', protection_max='r-x', region_detail='/Users/USER/*/rpcserver_macosx_x86_64'),\n Region(region_type='__DATA_CONST', start=4502028288, end=4502044672, vsize='16K', protection='r--', protection_max='rw-', region_detail='/Users/USER/*/rpcserver_macosx_x86_64'),\n Region(region_type='__DATA', start=4502044672, end=4502061056, vsize='16K', protection='rw-', protection_max='rw-', region_detail='/Users/USER/*/rpcserver_macosx_x86_64')]\n```\n\n## Calling native functions\n\nIn `rpc-project`, almost everything is wrapped using the `Symbol` Object. Symbol is just a nicer way for referring to addresses\nencapsulated with an object allowing to deref the memory inside, or use these addresses as functions.\n\nIn order to create a symbol from a given address, please use:\n\n```python\ns = p.symbol(0x12345678)\n\n# the Symbol object extends `int`\nTrue == isinstance(s, int)\n\n# write into this memory\ns.poke(b'abc')\n\n# peek(/read) 20 bytes of memory\nprint(s.peek(20))\n\n# jump to `s` as a function, passing (1, \"string\") as its args \ns(1, \"string\")\n\n# change the size of each item_size inside `s` for derefs\ns.item_size = 1\n\n# *(char *)s = 1\ns[0] = 1\n\n# *(((char *)s)+1) = 1\ns[1] = 1\n\n# symbol inherits from int, so all int operations apply\ns += 4\n\n# change s item size back to 8 to store pointers\ns.item_size = 8\n\n# *(intptr_t *)s = 1\ns[0] = 1\n\n# storing the return value of the function executed at `0x11223344`\n# into `*s`\ns[0] = p.symbol(0x11223344)()  # calling symbols also returns symbols \n\n# query in which file 0x11223344 is loaded from\nprint(p.symbol(0x11223344).filename)\n```\n\n### Globalized symbols\n\nUsually you would want/need to use the symbols already mapped into the currently running process. To do so, you can\naccess them using `symbols.\u003csymbol-name\u003e`. The `symbols` global object is of type `SymbolsJar`, which is a wrapper\nto `dict` for accessing all exported symbols. For example, the following will generate a call to the exported\n`malloc` function with `20` as its only argument:\n\n```python\nx = symbols.malloc(20)\n```\n\nYou can also just write their name as if they already were in the global scope. The client will check if no name collision\nexists, and if so, will perform the following lazily for you:\n\n```python\nx = malloc(20)\n\n# is equivalent to:\nmalloc = symbols.malloc\nx = malloc(20)\n```\n\n### ObjC support\n\nWhen working on Darwin based systems, it can be sometimes easier to use the builtin ObjC support.\n\n```python\n# creating CF/NSObjets using the builtin cf() method\nsome_cf_string = p.cf('some string')\n\n# create a new NSMutableDictionary\na = NSMutableDictionary.new()\n\n# tell where this class is loaded from\nprint(NSMutableDictionary.bundle_path)\n\n# which is a short-hand for objc_get_class(class_name)\na = p.objc_get_class('NSMutableDictionary').new()\n\n# each darwin object is a \"DarwinSymbol\", instead of a \"simple Symbol\"\n# that mean it has the special method: objc_call(\"selector\", ...)\na.objc_call('setObject:forKey:', p.cf('value'), p.cf('key'))\n\n# we can look at the CFDescription of every DarwinSymbol using the \"cfdesc\" property\na.cfdesc\n\n# it can be easier to use further ObjC capabilities by converting the current DarwinSymbol into an ObjectiveCSymbol instead\na = a.objc_symbol\n\n# We can now examine the class/objects properties\na.show()\n\n# now we'll have a auto-complete for all of its selectors, ivars, etc.\na.setObject_forKey_(p.cf('value2'), p.cf('key2'))\n\n# and we can easily convert this object to python native using the py() method. please note that this is done behind-the-scene using plistlib, meaning only plist-serializable objects (and None) can be coverted this way.\na = a.py()\n\n# attempt to load all frameworks for auto-completions of all ObjC classes\n# (equivalent to running the client with -l -r)\np.load_all_libraries()\n\n# create autorelease pool context where the pool is drained at the end\npool_ctx = p.create_autorelease_pool_ctx()\n# ...\n# do stuff\n# ...\n# drain pool\npool_ctx.drain()\n\n# same but in `with` statement\nwith p.create_autorelease_pool_ctx() as pool_ctx:\n    # do stuff\n\n# fetch all currently existing autorelease pools and iterate over all of the objects inside\nfor pool in p.get_autorelease_pools():\n    for obj in pool:\n        # do stuff with objects\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoronz88%2Frpc-project","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoronz88%2Frpc-project","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoronz88%2Frpc-project/lists"}