{"id":19199569,"url":"https://github.com/subconsciouscompute/winhook-rs","last_synced_at":"2025-07-15T01:03:58.950Z","repository":{"id":103465337,"uuid":"582125720","full_name":"SubconsciousCompute/winhook-rs","owner":"SubconsciousCompute","description":"Windows API Hooking in Rust","archived":false,"fork":false,"pushed_at":"2023-02-28T06:04:40.000Z","size":25,"stargazers_count":49,"open_issues_count":1,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-10T03:48:32.764Z","etag":null,"topics":["api","hooking","rust","win32","windows"],"latest_commit_sha":null,"homepage":"","language":"Rust","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/SubconsciousCompute.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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":"2022-12-25T19:32:04.000Z","updated_at":"2025-06-04T14:51:48.000Z","dependencies_parsed_at":null,"dependency_job_id":"ce460ebc-8578-4a94-b1ed-95375c8f132e","html_url":"https://github.com/SubconsciousCompute/winhook-rs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/SubconsciousCompute/winhook-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SubconsciousCompute%2Fwinhook-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SubconsciousCompute%2Fwinhook-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SubconsciousCompute%2Fwinhook-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SubconsciousCompute%2Fwinhook-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SubconsciousCompute","download_url":"https://codeload.github.com/SubconsciousCompute/winhook-rs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SubconsciousCompute%2Fwinhook-rs/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265382600,"owners_count":23756392,"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":["api","hooking","rust","win32","windows"],"created_at":"2024-11-09T12:27:52.521Z","updated_at":"2025-07-15T01:03:58.604Z","avatar_url":"https://github.com/SubconsciousCompute.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# winhook-rs\n\n[![i686-pc-windows-msvc build](https://github.com/SubconsciousCompute/winhook-rs/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/SubconsciousCompute/winhook-rs/actions/workflows/main.yml)\n\nAlso\ncheckout [fsfilter-rs:  A rust library to monitor filesystem 🪛 and more in windows using filesystem driver](https://github.com/SubconsciousCompute/fsfilter-rs)\n\n\u003e Windows API hooking is one of the techniques used by AV/EDR solutions to determine if code is malicious.\n\u003e A `MessageBoxA` function will be hooked in this instance, but it could be any.\n\u003e\n\u003e A simple program that will work follows:\n\u003e\n\u003e 1. Get memory address of the `MessageBoxA` function\n\u003e 2. Read the first 6 bytes of the `MessageBoxA` - will need these bytes for unhooking the function\n\u003e 3. Create a `HookedMessageBox` function that will be executed when the original `MessageBoxA` is called\n\u003e 4. Get memory address of the `HookedMessageBox`\n\u003e 5. Patch/redirect `MessageBoxA` to `HookedMessageBox`\n\u003e 6. Call `MessageBoxA`. Code gets redirected to `HookedMessageBox`\n\u003e 7. `HookedMessageBox` executes its code, prints the supplied arguments, unhooks the `MessageBoxA` and transfers the\n     code control to the actual `MessageBoxA`\n\n[Read code for more details in comments](src/main.rs) or [jump to bottom](#code).\n\n## Running\n\nWe need to build for `32bit` systems:\n\n1. `rustup default stable-i686-pc-windows-msvc` or `cargo +stable-i686-pc-windows-msvc run`\n2. `cargo run --release`\n\n## Output\n\n![MessageBox popup](resources/original_popup.png)\n\n```\n================\nHooked!!!\noriginal-lptext: Hello World\noriginal-lpcation: Rust\n================\n\n================\nHooked!!!\nhooked-lptext: Hooked Hello World\nhooked-lpcation: Hooked Rust\n================\n```\n\n![MessageBox popup](resources/hooked_popup.png)\n\n## LICENSE\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details\n\n## Acknowledgments\n\n* [ired.team notes](https://www.ired.team/)\n\n## Code\n\n```rust\n// cargo +stable-i686-pc-windows-msvc run\n\nuse std::ffi::c_void;\n\nuse windows::{\n    core::PCSTR,\n    s,\n    Win32::{\n        Foundation::HWND,\n        System::{\n            Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory},\n            LibraryLoader::{GetProcAddress, LoadLibraryA},\n            Threading::GetCurrentProcess,\n        },\n        UI::WindowsAndMessaging::{MessageBoxA, MESSAGEBOX_RESULT, MESSAGEBOX_STYLE},\n    },\n};\n\nstatic mut MESSAGE_BOX_ORIGINAL_BYTES: [u8; 6] = [0; 6];\nstatic mut BYTES_WRITTEN: usize = 0;\nstatic mut MESSAGE_BOX_ADDRESS: Option\u003cunsafe extern \"system\" fn() -\u003e isize\u003e = None;\n\nfn main() {\n    unsafe {\n        // Show messagebox before hooking\n        //\n        // Pop the message box before the function is hooked - just to make sure it works and to\n        // prove that no functions are hooked so far -  it's the first instruction of the program\n        MessageBoxA(HWND(0), s!(\"Hello World\"), s!(\"Rust\"), Default::default());\n\n        let dll_handle = LoadLibraryA(s!(\"user32.dll\")).unwrap();\n        let bytes_read: usize = 0;\n\n        // Get address of the `MessageBox` function in memory\n        //\n        // If we disassemble the bytes at that address, we will definitely see that there is code for\n        // `MessageBoxA`\n        MESSAGE_BOX_ADDRESS = GetProcAddress(dll_handle, s!(\"MessageBoxA\"));\n\n        // save the first 6 bytes of the original MessageBoxA function - will need for unhooking\n        //\n        // Note the first 6 bytes \u003cxx xx xx xx xx xx\u003e(mind the endian-ness, where `xx` is some hex address).\n        // We need to save these bytes for future when we want to unhook MessageBoxA\n        ReadProcessMemory(\n            GetCurrentProcess(),\n            MESSAGE_BOX_ADDRESS.unwrap() as *const c_void,\n            MESSAGE_BOX_ORIGINAL_BYTES.as_ptr() as *mut c_void,\n            6,\n            Some(bytes_read as *mut usize),\n        );\n\n        // Create a patch `push \u003caddress of new MessageBoxA); ret`\n        //\n        // Let's now build the patch (hook) bytes:\n        let hooked_message_box_address = (HookedMessageBox as *mut ()).cast::\u003cc_void\u003e();\n        let offset = hooked_message_box_address as isize;\n        let mut patch = [0; 6];\n        patch[0] = 0x68;\n        let temp = offset.to_ne_bytes();\n        patch[1..5].copy_from_slice(\u0026temp[..4]);\n        patch[5] = 0xC3;\n        // ...that will translate into the following assembly instructions:\n        //\n        // ```asm\n        // // push HookedMessageBox memory address onto the stack\n        // push HookedMessageBox\n        // // jump to HookedMessageBox\n        // ret\n        // ```\n\n        // Patch the `MessageBoxA`\n        //\n        // We can now patch the `MessageBoxA` - memory pane in the bottom right shows the patch being\n        // written to the beginning of `MessageBoxA` function and the top right shows the beginning of\n        // the same function is re-written with a `push \u003caddress that jumps to our hooked function\u003e;\n        // ret` instructions\n        WriteProcessMemory(\n            GetCurrentProcess(),\n            MESSAGE_BOX_ADDRESS.unwrap() as *const c_void,\n            patch.as_ptr().cast::\u003cc_void\u003e(),\n            6,\n            Some(BYTES_WRITTEN as *mut usize),\n        );\n\n        // show messagebox after hooking\n        MessageBoxA(HWND(0), s!(\"Hello World\"), s!(\"Rust\"), Default::default());\n    }\n}\n\n// The `HookedMessageBox` intercepts and prints out the arguments supplied to `MessageBoxA`, then\n// unhooks `MessageBoxA` by swapping back the first 6 bytes to the original bytes of the `MessageBoxA`\n// function and then calls the `MessageBoxA` with the supplied arguments:\n#[no_mangle]\npub extern \"stdcall\" fn HookedMessageBox(\n    hwnd: HWND,\n    mut lptext: PCSTR,\n    mut lpcaption: PCSTR,\n    utype: MESSAGEBOX_STYLE,\n) -\u003e MESSAGEBOX_RESULT {\n    unsafe {\n        // Print intercepted values from the MessageBoxA function\n        println!(\n            \"================\\nHooked!!!\\noriginal-lptext: {}\\noriginal-lpcation: {}\\n================\",\n            lptext.to_string().unwrap(),\n            lpcaption.to_string().unwrap(),\n        );\n\n        // Change intercepted values from the MessageBoxA function\n        lptext = s!(\"Hooked Hello World\");\n        lpcaption = s!(\"Hooked Rust\");\n\n        // Print new values from the MessageBoxA function\n        println!(\n            \"\\n================\\nHooked!!!\\nhooked-lptext: {}\\nhooked-lpcation: {}\\n================\",\n            lptext.to_string().unwrap(),\n            lpcaption.to_string().unwrap(),\n        );\n\n        // Unpatch MessageBoxA\n        WriteProcessMemory(\n            GetCurrentProcess(),\n            MESSAGE_BOX_ADDRESS.unwrap() as *const c_void,\n            MESSAGE_BOX_ORIGINAL_BYTES.as_ptr().cast::\u003cc_void\u003e(),\n            6,\n            Some(BYTES_WRITTEN as *mut usize),\n        );\n\n        // call the original MessageBoxA\n        MessageBoxA(hwnd, lptext, lpcaption, utype)\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubconsciouscompute%2Fwinhook-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsubconsciouscompute%2Fwinhook-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubconsciouscompute%2Fwinhook-rs/lists"}