{"id":18933353,"url":"https://github.com/synap5e/pydetours","last_synced_at":"2025-04-15T16:34:26.925Z","repository":{"id":150507262,"uuid":"367484458","full_name":"synap5e/pydetours","owner":"synap5e","description":"Single file, 0 compilation, 0 dependancy (other than python), windows x86/x86_64 process hooking with python","archived":false,"fork":false,"pushed_at":"2021-05-19T10:59:07.000Z","size":38,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T00:11:16.838Z","etag":null,"topics":["amd64","assembly","detours","hooking","iat-hooking","python","windows","x86"],"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/synap5e.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}},"created_at":"2021-05-14T21:35:33.000Z","updated_at":"2024-10-14T00:45:34.000Z","dependencies_parsed_at":"2023-04-04T04:47:26.318Z","dependency_job_id":null,"html_url":"https://github.com/synap5e/pydetours","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synap5e%2Fpydetours","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synap5e%2Fpydetours/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synap5e%2Fpydetours/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synap5e%2Fpydetours/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/synap5e","download_url":"https://codeload.github.com/synap5e/pydetours/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249108930,"owners_count":21214089,"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":["amd64","assembly","detours","hooking","iat-hooking","python","windows","x86"],"created_at":"2024-11-08T11:54:15.456Z","updated_at":"2025-04-15T16:34:26.918Z","avatar_url":"https://github.com/synap5e.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pydetours\n\nSingle file, 0 compilation, 0 dependancy (other than python), x86/x86_64 hooking in python.\n\n## Examples\n\n\n\n### IAT hooking\n\nLets take the target process `netcat`. Lets say we want to change the port it binds on (this is a contrived example for netcat, but could be useful for other applications).\n\nTarget process:\n\n    .\\static-binaries\\binaries\\windows\\x86\\ncat.exe -4lvvp 5678\n\nHook script\n```python\n# support running with embedded python\nimport os; import sys; sys.path.append(os.path.dirname(__file__))\n\nfrom pydetours import *\nimport struct\nimport socket\n\n@hook_iat(\n\t'ncat.exe',        # module to hook the import of\n\t'ws2_32.dll!bind'  # the import: \u003cdll\u003e!\u003cfunction_name_or_ordinal\u003e\n)\ndef bind_hook(registers):\n\tsock, addr, namelen = Arguments(registers, 3)        # Parse registers into arguments (autodetect calling convention, and unpack to `bind`s arguments)\n\tmemory[addr + 2:addr + 4] = struct.pack('\u003eH', 7654)  # Replace the `port` in `addr`. Use the `memory` object to get R/W access to the address space (or use ctypes directly)\n\t# Return nothing/None and the bound function will run (e.g. this case)\n\t# Return a non-None value (usually an int) and this value will be returned instead of running the bound function\n\nif __name__ == '__main__':\n\t# Launch the process and inject into it\n\tlaunch(\n\t\tcmdline=['./static-binaries/binaries/windows/x86/ncat.exe', '-4lvvp', '5678'],\n\t\tfile_to_inject=__file__,\n\t)\n\t# Alternatively inject('ncat.exe', __file__) would inject into a running process\n\t# Pass `alloc_console=True` to launch/inject if the process is a GUI program and a console is desired\nif __name__ == '__hooks__':\n\t# This code will run inside the injected process upon injection\n\tprint(\"! I'm running inside the hooked process\")\n```\n\nRun the script (use 32bit python since the target is 32bit)\n\n    .\\python-3.7.9-embed-win32\\python.exe .\\demo1.py\n \nAnd a console will open for ncat\n```\n[@] Python stub started\n[@] Stub running C:\\Users\\simon\\workspace\\pydetours\\demo1.py\n * Hooking ncat.exe:ws2_32.dll!bind to run \u003cfunction bind_hook at 0x02E6B780\u003e\n  - Resolved ncat.exe:ws2_32.dll!bind -\u003e thunk=0x00aef82c\n  - Created landing at 0x02eb0000\n  - Writing function hook landing bytecode to run \u003cfunction bind_hook at 0x02E6B780\u003e (0x2e6b780)\n  - Patching thunk to point to landing\n! I'm running inside the hooked process\nNcat: Version 6.47 ( http://nmap.org/ncat )\nNcat: Listening on 0.0.0.0:7654\n```\n\n### Instruction hooking\n\nRather than hooking an import, we can hook an address directly\n\n```python\n@hook(\n\t'gameassembly.dll+0x88B7F0'  # can be \u003cdll\u003e+\u003coffset\u003e or \u003cabsolute\u003e\n)\ndef PlayerControl_SetColor(r):\n\tprint('PlayerControl.SetColor', Arguments(r, 2))\n\t# Similar to `hook_iat`, returning a value will force an immediate return with that value in *ax (i.e. the return value)\n\treturn 1\n  \n# To work, `hook` replaces the instructions at the provided address with a trampoline.\n# By default it detects common function prolouges as relocatable, however if a function does not begin with a recognised prolouge, \n# provide `position_independent_bytes`. pydetours will then relocate exactly that many bytes, and use that space for the trampoline.\n# `address:position_independent_bytes` must contain assembly instructions that do not depend on the instruction pointer or contain jumps\n@hook('gameassembly.dll+0x887660', position_independent_bytes=10)\ndef PlayerControl_CoStartMeeting(r):\n\tprint('PlayerControl.CoStartMeeting', Arguments(r, 2))\n  \n# Note that hooks can be inserted anywhere enough relocatable instructions exist (requires 5 bytes),\n# not just at the start of functions (return non-None values with care in this case as the stack may have moved)\n```\n\n### Registers\n\nHooked functions are passed a `Registers` object, representing the registers at the time of hooking. This can be used to read and write the registers.\nModifications to this object will modify the register state on completion of the hook (with the exception of the stack pointer).\n\n```python\n@hook('foo.exe+0x112233', position_independent_bytes=7)\ndef foo_hook(r):\n\tr.eax += 10\n```\n\n#### Arguments\n\nConstruct an `Arguments` object from the registers object, and provide the number of arguments.\nThis will autodetect 32bit or 64bit calling convention and parse the arguments from registers/the stacl.\nModifications to arguments will also be written back.\n\nn.b. floating point arguments are not yet supported.\n\n\n### Using module introspection\n\nWhen running inside a hooked process, `pydetours.modules` will be accessible containing the full import and exports of all modules.\n\nAdditionally you can inspect the modules of a running foreign process using the same API.\n\n```python\n\u003e\u003e\u003e from pydetours import *\n\u003e\u003e\u003e from pprint import pprint\n\u003e\u003e\u003e \n\u003e\u003e\u003e pid = getpid('ncat.exe')\n\u003e\u003e\u003e handle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)\n\u003e\u003e\u003e modules = Modules(handle)\n\u003e\u003e\u003e pprint(modules)\n{'advapi32.dll': Module(name='advapi32.dll', path='C:\\\\WINDOWS\\\\System32\\\\ADVAPI32.dll', \u003c33 imports\u003e, \u003c857 exports\u003e),\n# ... snip\n 'mswsock.dll': Module(name='mswsock.dll', path='C:\\\\WINDOWS\\\\system32\\\\mswsock.dll', \u003c32 imports\u003e, \u003c63 exports\u003e),\n 'ncat.exe': Module(name='ncat.exe', path='C:\\\\Users\\\\simon\\\\workspace\\\\pydetours\\\\static-binaries\\\\binaries\\\\windows\\\\x86\\\\ncat.exe', \u003c5 imports\u003e, \u003c0 exports\u003e),\n# ... snip\n 'ws2_32.dll': Module(name='ws2_32.dll', path='C:\\\\WINDOWS\\\\System32\\\\WS2_32.dll', \u003c31 imports\u003e, \u003c180 exports\u003e)}\n\u003e\u003e\u003e\n\u003e\u003e\u003e pprint(modules['ncat.exe'].imports['ws2_32.dll'].by_name)\n{'WSACloseEvent': ResolvedFunctionImport(ordinal=45, name='WSACloseEvent', by_ordinal=False, thunk=4716632, original_address=1989827904, resolved_address=1989827904),\n# ... snip\n 'accept': ResolvedFunctionImport(ordinal=1, name='accept', by_ordinal=False, thunk=4716616, original_address=1989822016, resolved_address=1989822016),\n 'bind': ResolvedFunctionImport(ordinal=2, name='bind', by_ordinal=False, thunk=4716588, original_address=1989791120, resolved_address=56164352),\n 'closesocket': ResolvedFunctionImport(ordinal=3, name='closesocket', by_ordinal=False, thunk=4716708, original_address=1989792576, resolved_address=1989792576),\n 'connect': ResolvedFunctionImport(ordinal=4, name='connect', by_ordinal=False, thunk=4716612, original_address=1989823968, resolved_address=1989823968),\n# ... snip\n 'socket': ResolvedFunctionImport(ordinal=23, name='socket', by_ordinal=False, thunk=11466808, resolved_address=1989786320)}\n ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynap5e%2Fpydetours","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsynap5e%2Fpydetours","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynap5e%2Fpydetours/lists"}