{"id":15014885,"url":"https://github.com/doronz88/hilda","last_synced_at":"2026-02-05T19:02:27.220Z","repository":{"id":37749594,"uuid":"337048753","full_name":"doronz88/hilda","owner":"doronz88","description":"LLDB wrapped and empowered by iPython's features","archived":false,"fork":false,"pushed_at":"2025-03-10T08:00:04.000Z","size":21193,"stargazers_count":143,"open_issues_count":0,"forks_count":17,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-05-15T20:15:46.441Z","etag":null,"topics":["debug","debugger","ios","ipython","lldb","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","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}},"created_at":"2021-02-08T11:09:30.000Z","updated_at":"2025-04-29T16:49:23.000Z","dependencies_parsed_at":"2024-02-04T08:25:24.136Z","dependency_job_id":"dd8ebc25-8be6-4f1e-95c9-e5ee66d1e45b","html_url":"https://github.com/doronz88/hilda","commit_stats":{"total_commits":132,"total_committers":6,"mean_commits":22.0,"dds":"0.43939393939393945","last_synced_commit":"7169c212cbb236af590e47db7b6cd1124a4b426c"},"previous_names":[],"tags_count":45,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Fhilda","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Fhilda/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Fhilda/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doronz88%2Fhilda/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doronz88","download_url":"https://codeload.github.com/doronz88/hilda/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254509478,"owners_count":22082892,"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":["debug","debugger","ios","ipython","lldb","python"],"created_at":"2024-09-24T19:46:13.119Z","updated_at":"2026-02-05T19:02:27.213Z","avatar_url":"https://github.com/doronz88.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hilda\n\n- [Hilda](#hilda)\n  - [Overview](#overview)\n  - [Installation](#installation)\n  - [How to use](#how-to-use)\n    - [Starting a Hilda interactive shell](#starting-a-hilda-interactive-shell)\n    - [Inside a Hilda shell](#inside-a-hilda-shell)\n      - [Magic functions](#magic-functions)\n      - [Key-bindings](#key-bindings)\n      - [Configurables](#configurables)\n      - [UI Configuration](#ui-configuration)\n    - [Python API](#python-api)\n      - [Symbol objects](#symbol-objects)\n      - [Globalized symbols](#globalized-symbols)\n      - [Searching for the right symbol](#searching-for-the-right-symbol)\n      - [Objective-C Classes](#objective-c-classes)\n      - [Objective-C Objects](#objective-c-objects)\n      - [Using snippets](#using-snippets)\n  - [Contributing](#contributing)\n\n## Overview\n\nHilda is a debugger which combines both the power of LLDB and iPython for easier debugging.\n\nThe name originates from the TV show \"Hilda\", which is the best friend of\n[Frida](https://frida.re/). Both Frida and Hilda are meant for pretty much the same purpose, except Hilda takes the\nmore \"\ndebugger-y\" approach (based on LLDB).\n\nCurrently, the project is intended for iOS/OSX debugging, but in the future we will possibly add support for the\nfollowing platforms as well:\n\n- Linux\n- Android\n\nSince LLDB allows abstraction for both platform and architecture, it should be possible to make the necessary changes\nwithout too many modifications.\n\nPull requests are more than welcome 😊.\n\nIf you need help or have an amazing idea you would like to suggest, feel free\nto [start a discussion 💬](https://github.com/doronz88/hilda/discussions).\n\n## Installation\n\nRequirements for remote iOS device (not required for debugging a local OSX process):\n\n- Jailbroken iOS device\n- `debugserver` in device's PATH\n  - [You can use this tool in order to obtain the binary](https://github.com/doronz88/debugserver-deploy)\n  - After re-signing with new entitlements, you can put the binary in the following path: `/usr/bin/debugserver`\n\nIn order to install please run:\n\n```shell\nxcrun python3 -m pip install --user -U hilda\n```\n\n*⚠️ Please note that Hilda is installed on top of XCode's python so LLDB will be able to use its features.*\n\n## How to use\n\n### Starting a Hilda interactive shell\n\nYou can may start a Hilda interactive shell by invoking any of the subcommand:\n\n- `hilda launch /path/to/executable`\n  - Launch given executable on current host\n- `hilda attach [-p pid] [-n process-name]`\n  - Attach to an already running process on current host (specified by either `pid` or `process-name`)\n- `hilda remote HOSTNAME PORT`\n  - Attach to an already running process on a target host (specified by `HOSTNAME PORT`)\n- `hilda bare`\n  - Only start an LLDB shell and load Hilda as a plugin.\n  - Please refer to the following help page if you require help on the command available to you within the lldb shell:\n\n    [lldb command map](https://lldb.llvm.org/use/map.html).\n\n    As a cheatsheet, connecting to a remote platform like so:\n\n    ```shell\n    platform connect connect://ip:port\n    ```\n\n    ... and attaching to a local process:\n\n    ```shell\n    process attach -n process_name\n    process attach -p process_pid\n    ```\n\n    When you are ready, just execute `hilda` to move to Hilda's iPython shell.\n\n### Inside a Hilda shell\n\nUpon starting Hilda, you are welcomed into an IPython shell.\nYou can access following methods via the variable `p`.\n\nBasic flow control:\n\n- `stop` - Stop process\n- `cont` - Continue process\n- `finish` - Run current function until return\n- `step_into` - Step into current instruction\n- `step_over` - Step over current instruction.\n- `run_for` - Run the process for given interval\n- `force_return` - Prematurely return from a stack frame, short-circuiting execution of inner\n  frames and optionally yielding a specified value.\n- `jump` - Jump to given symbol\n- `wait_for_module` - Wait for a module to be loaded (`dlopen`) by checking if given expression is contained within its filename\n- `detach` - Detach from process (useful for exiting gracefully so the\n  process doesn't get killed when you exit)\n\nBreakpoints:\n- `bp` or `breakpoints.add` - Add a breakpoint\n- `breakpoints.show` - Show existing breakpoints\n- `breakpoints.remove` - Remove a single breakpoint\n- `breakpoints.clear` - Remove all breakpoints\n- `monitor` or `breakpoints.add_monitor` - Creates a breakpoint whose callback implements the requested features (print register values, execute commands, mock return value, etc.)\n\nBasic read/write:\n\n- `get_register` - Get register value\n- `set_register` - Set register value\n- `poke` - Write data at address\n- `peek[_str,_std_str]` - Read buffer/C-string/`std::string` at address\n- `po` - Print object using LLDB's `po` command\n  Can also run arbitrary native code:\n\n  ```python\n  p.po('NSMutableString *s = [NSMutableString string]; [s appendString:@\"abc\"]; [s description]')\n  ```\n- `disass` - Print disassembly at address\n- `show_current_source` - Print current source code (if possible)\n- `bt` - Get backtrace\n- `lsof` - Get all open FDs\n- `hd` - Hexdump a buffer\n- `proc_info` - Print information about currently running mapped process\n- `print_proc_entitlements` - Get the plist embedded inside the process' __LINKEDIT section.\n\nExecute code:\n\n- `call` - Call function at given address with given parameters\n- `objc_call` - Simulate a call to an objc selector\n- `inject` - Inject a single library into currently running process\n- `disable_jetsam_memory_checks` -\n   Disable jetsam memory checks (to prevent raising\n   `error: Execution was interrupted, reason: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=15 MB, unused=0x0).`\n   when evaluating expressions).\n\nHilda symbols:\n\n- `symbol` - Get symbol object for a given address\n- `objc_symbol` - Get objc symbol wrapper for given address\n- `file_symbol` - Calculate symbol address without ASLR\n- `save` - Save loaded symbols map (for loading later using the load() command)\n- `load` - Load an existing symbols map (previously saved by the save() command)\n- `globalize_symbols` - Make all symbols in python's global scope\n\nAdvanced:\n\n- `lldb_handle_command` - Execute an LLDB command (e.g., `p.lldb_handle_command('register read')`)\n- `evaluate_expression` - Use for quick code snippets (wrapper for LLDB's `EvaluateExpression`)\n\n  Take advantage of local variables inside the expression using format string, e.g.,\n\n  ```python\n  currentDevice = p.objc_get_class('UIDevice').currentDevice\n  p.evaluate_expression(f'[[{currentDevice} systemName] hasPrefix:@\"2\"]')\n  ```\n- `import_module` - Import \u0026 reload given python module (intended mainly for external snippets)\n- `unwind` - Unwind the stack (useful when get_evaluation_unwind() == False)\n- `set_selected_thread` - sets the currently selected thread, which is used in other parts of the program, such as displaying disassembly or\n  checking registers.\n  This ensures the application focuses on the specified thread for these operations.\n\nObjective-C related:\n\n- `objc_get_class` - Get ObjC class object\n- `CFSTR` - Create CFStringRef object from given string\n- `ns` - Create NSObject from given data\n- `from_ns` - Create python object from NS object.\n\n#### Magic functions\n\nSometimes accessing the [Python API](#python-api) can be tiring, so we added some magic functions to help you out!\n\n- `%objc \u003cclassName\u003e`\n  - Equivalent to: `className = p.objc_get_class(className)`\n- `%fbp \u003cfilename\u003e \u003caddressInHex\u003e`\n  - Equivalent to: `p.file_symbol(addressInHex, filename).bp()`\n\n#### Key-bindings\n\n- **F1**: Show banner help message\n- **F2**: Show process state UI\n- **F3**: Toggle stdout/stderr enablement\n- **F7**: Step Into\n- **F8**: Step Over\n- **F9**: Continue\n- **F10**: Stop\n\n#### Configurables\n\nThe global `cfg` used to configure various settings for evaluation and monitoring.\n\nThese settings include:\n\n- `evaluation_unwind_on_error`: Whether to unwind on error during evaluation. (Default: `False`)\n- `evaluation_ignore_breakpoints`: Whether to ignore breakpoints during evaluation. (Default: `False`)\n- `nsobject_exclusion`: Whether to exclude `NSObject` during evaluation, reducing IPython autocomplete results. (\n  Default: `False`)\n- `objc_verbose_monitor`: When set to `True`, using `monitor()` will automatically print Objective-C method arguments. (\n  Default: `False`)\n\n#### UI Configuration\n\nHilda contains a minimal UI for examining the target state.\nThe UI is divided into views:\n\n- Registers\n- Disassembly\n- Stack\n- Backtrace\n\n![img.png](gifs/ui.png)\n\nThis UI can be displayed at any time be executing:\n\n```python\nui.show()\n```\n\nBy default `step_into` and `step_over` will show this UI automatically.\nYou may disable this behavior by executing:\n\n```python\nui.active = False\n```\n\nAttentively, if you want to display UI after hitting a breakpoint, you can register `ui.show` as callback:\n\n```python\np.symbol(0x7ff7b97c21b0).bp(ui.show)\n```\n\nTry playing with the UI settings by yourself:\n\n```python\n# Disable stack view\nui.views.stack.active = False\n\n# View words from the stack\nui.views.stack.depth = 10\n\n# View last 10 frames\nui.views.backtrace.depth = 10\n\n# Disassemble 5 instructions\nui.views.disassembly.instruction_count = 5\n\n# Change disassembly syntax to AT\u0026T\nui.views.disassembly.flavor = 'att'\n\n# View floating point registers\nui.views.registers.rtype = 'float'\n\n# Change addresses print color\nui.colors.address = 'red'\n\n# Change titles color\nui.color.title = 'green'\n```\n\n### Python API\n\nHilda provides a comprehensive API wrappers to access LLDB capabilities.\nThis API may be used to access process memory, trigger functions, place breakpoints and much more!\n\nAlso, in addition to access this API using the [Hilda shell](#inside-a-hilda-shell), you may also use pure-python script using any of the `create_hilda_client_using_*` APIs.\n\nConsider the following snippet as an example of such usage:\n\n```python\nfrom hilda.launch_lldb import create_hilda_client_using_attach_by_name\n\n# attach to `sysmond`\np = create_hilda_client_using_attach_by_name('sysmond')\n\n# allocate 10 bytes and print their address\nprint(p.symbols.malloc(10))\n\n# detach\np.detach()\n```\n\nPlease note this script must be executed using `xcrun python3` in order for it to be able to access LLDB API.\n\n#### Symbol objects\n\nIn Hilda, 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# print the un-shifted file address \n# (calculating the ASLR shift for you, so you can just view it in IDA)\nprint(s.file_address)\n\n# or.. if you know the file address, but don't wanna mess\n# with ASLR calculations\ns = p.file_symbol(0x12345678)\n\n# peek(/read) 20 bytes of memory\nprint(s.peek(20))\n\n# write into this memory\ns.poke('abc')\n\n# let LLDB print-object (it should guess the type automatically\n# based on its memory layout)\nprint(s.po())\n\n# or you can help LLDB with telling it its type manually\nprint(s.po('char *'))\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# attempt to resolve symbol's name\nprint(p.symbol(0x11223344).lldb_address)\n\n# monitor each time a symbol is called into console and print its backtrace (`bt` option)\n# this will create a scripted breakpoint which prints your desired data and continue\ns.monitor(bt=True)\n\n# you can also:\n#   bt -\u003e view the backtrace\n#   regs -\u003e view registers upon each call in your desired format\n#   retval -\u003e view the return value upon each call in your desired format\n#   cmd -\u003e execute a list of LLDB commands on each hit\ns.monitor(regs={'x0': 'x'},  # print `x0` in HEX form\n          retval='po',  # use LLDB's `po` for printing the returned value\n          bt=True,  # view backtrace (will also resolve ASLR addresses for you)\n          cmd=['thread list'],  # show thread list \n          )\n\n# we can also just `force_return` with a hard-coded value to practically disable \n# a specific functionality\ns.monitor(force_return=0)  # cause the function to always return `0`\n\n# as for everything, if you need help understanding each such feature, \n# simply execute the following to view its help (many such features even contain examples) \ns.monitor?\n\n# create a scripted_breakpoint manually\ndef scripted_breakpoint(hilda, *args):\n    # like everything in hilda, registers are also\n    # just simple `Symbol` objects, so feel free to \n    # use them to your heart's content :)\n    if hilda.registers.x0.peek(4) == b'\\x11\\x22\\x33\\x44':\n        hilda.registers.x0 = hilda.symbols.malloc(200)\n        hilda.registers.x0.poke(b'\\x22' * 200)\n\n    # just continue the process\n    hilda.cont()\n\n\ns.bp(scripted_breakpoint)\n\n# Place a breakpoint at a symbol not yet loaded by it's name\np.bp('symbol_name')\n\n# In case you need to specify a specific library it's loaded from\np.bp(('symbol_name', 'ModuleName'))\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 `SymbolList`, which acts like\n`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 = p.symbols.malloc(20)\n```\n\nYou can also just write their name as if they already were in the global scope. Hilda 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 = p.symbols.malloc\nx = malloc(20)\n```\n\n#### Searching for the right symbol\n\nSometimes you don't really know where to start your research. All you have is just theories of how your desired exported\nsymbol should be called (if any).\n\n```python\n# find all symbols prefixed as `mem*` AND don't have `cpy`\n# in their name\nl = p.symbols.filter_startswith('mem') - p.symbols.filter_name_contains('cpy')\n\n# filter only symbols of type \"code\" (removing data global for example)\nl = l.filter_code_symbols()\n\n# monitor every time each one is called, print its `x0` in HEX\n# form and show the backtrace\nl.monitor(regs={'x0': 'x'}, bt=True)\n```\n\n#### Objective-C Classes\n\nThe same as symbols applies to Objective-C classes name resolution. You can either:\n\n```python\nd = NSDictionary.new()  # call its `new` selector\n\n# which is equivalent to:\nNSDictionary = p.objc_get_class('NSDictionary')\nd = NSDictionary.new()\n\n# Or you can use the IPython magic function\n%objc\nNSDictionary\n```\n\nThis is possible only since `NSDictionary` is exported. In case it is not, you must call `objc_get_class()` explicitly.\n\nAs you can see, you can directly access all the class' methods.\n\nPlease look what more stuff you can do as shown below:\n\n```python\n# show the class' ivars\nprint(NSDictionary.ivars)\n\n# show the class' methods\nprint(NSDictionary.methods)\n\n# show the class' properties\nprint(NSDictionary.properties)\n\n# view class' selectors which are prefixed with 'init'\nprint(NSDictionary.methods.filter_startswith('init'))\n\n# you can of course use any of `SymbolList` over them, for example:\n# this will `po` (print object) all those selectors returned value\nNSDictionary.methods.filter_startswith('init').monitor(retval='po')\n\n# monitor each time any selector in NSDictionary is called\nNSDictionary.monitor()\n\n# `force_return` for some specific selector with a hard-coded value (4)\nNSDictionary.methods.get('valueForKey:').address.monitor(force_return=4)\n\n# capture the `self` object at the first hit of any selector\n# `True` for busy-wait for object to be captured\ndictionary = NSDictionary.capture_self(True)\n\n# print a colored and formatted version for class layout\ndictionary.show()\n```\n\n#### Objective-C Objects\n\nIn order to work with ObjC objects, each symbol contains a property called\n`objc_symbol`. After calling, you can work better with each object:\n\n```python\ndict = NSDictionary.new().objc_symbol\ndict.show()  # print object layout\n\n# just like class, you can access its ivars, method, etc...\nprint(dict.ivars)\n\n# except now they have values you can view\nprint(dict._ivarName)\n\n# or edit\ndict._ivarName = value\n\n# and of course you can call the object's methods\n# hilda will checks if the method returned an ObjC object:\n#   - if so, call `objc_symbol` upon it for you\n#   - otherwise, leave it as a simple `Symbol` object\narr = dict.objectForKey_('keyContainingNSArray')\n\n# you can also call class-methods\n# hilda will call it using either the instance object,\n# or the class object respectively of the use\nnewDict = dict.dictionary()\n\n# print the retrieved object\nprint(arr.po())\n```\n\nAlso, working with Objective-C objects like this can be somewhat exhausting, so we created the `ns` and `from_ns`\ncommands so you are able to use complicated types when parsing values and passing as arguments:\n\n```python\nimport datetime\n\n# using the `ns` command we can just pass a python-native dictionary\nfunction_requiring_a_specfic_dictionary(p.cf({\n    'key1': 'string',  # will convert to NSString\n    'key2': True,  # will convert to NSNumber\n    'key3': b'1234',  # will convert to NSData\n    'key4': datetime.datetime(2021, 1, 1)  # will convert to NSDate\n}))\n\n# and also parse one\nnormal_python_dict = p.cf({\n    'key1': 'string',  # will convert to NSString\n    'key2': True,  # will convert to NSNumber\n    'key3': b'1234',  # will convert to NSData\n    'key4': datetime.datetime(2021, 1, 1)  # will convert to NSDate\n}).py()\n```\n\nOn last resort, if the object is not serializable for this to work, you can just run pure Objective-C code:\n\n```python\n# let LLDB compile and execute the expression\nabc_string = p.evaluate_expression('[NSString stringWithFormat:@\"abc\"]')\n\n# will print \"abc\"\nprint(abc_string.po())\n```\n\n#### Using snippets\n\nSnippets are extensions for normal functionality used as quick cookbooks for day-to-day tasks of a debugger.\n\nThey all use the following concept to use:\n\n```python\nfrom hilda.snippets import snippet_name\n\nsnippet_name.do_something()\n```\n\nFor example, XPC sniffing can be done using:\n\n```python\nfrom hilda.snippets import xpc\n\nxpc.sniff_all()\n```\n\nThis will monitor all XPC related traffic in the given process.\n\n## Contributing\n\nPlease run the tests as follows before submitting a PR:\n\n```shell\nxcrun python3 -m pytest\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoronz88%2Fhilda","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoronz88%2Fhilda","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoronz88%2Fhilda/lists"}