{"id":21525654,"url":"https://github.com/0xlane/com-process-inject","last_synced_at":"2025-04-09T23:22:50.926Z","repository":{"id":188108028,"uuid":"589903339","full_name":"0xlane/com-process-inject","owner":"0xlane","description":"Process Injection via Component Object Model (COM) IRundown::DoCallback().","archived":false,"fork":false,"pushed_at":"2023-01-17T09:06:58.000Z","size":1094,"stargazers_count":57,"open_issues_count":0,"forks_count":12,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-24T01:12:13.151Z","etag":null,"topics":["process-injection","process-injector","redteam-tools","rust","windows"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0xlane.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-01-17T08:05:00.000Z","updated_at":"2025-02-21T02:50:49.000Z","dependencies_parsed_at":"2023-08-13T20:11:29.347Z","dependency_job_id":null,"html_url":"https://github.com/0xlane/com-process-inject","commit_stats":null,"previous_names":["0xlane/com-process-inject"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xlane%2Fcom-process-inject","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xlane%2Fcom-process-inject/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xlane%2Fcom-process-inject/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xlane%2Fcom-process-inject/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0xlane","download_url":"https://codeload.github.com/0xlane/com-process-inject/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248126699,"owners_count":21052004,"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":["process-injection","process-injector","redteam-tools","rust","windows"],"created_at":"2024-11-24T01:37:57.019Z","updated_at":"2025-04-09T23:22:50.884Z","avatar_url":"https://github.com/0xlane.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# COM PROCESS INJECTION for RUST\n\nProcess Injection via Component Object Model (COM) IRundown::DoCallback().\n\n该技术由 [@modexpblog](https://twitter.com/modexpblog) 挖掘发现，在我对该技术进行深入研究过程中，将原项目 [mdsecactivebreach/com_inject](https://github.com/mdsecactivebreach/com_inject) 使用了 Rust 重写，希望对使用 Rust 的安全人员在 COM 接口调用、进程注入等方面有所帮助。\n\n对于此项注入技术原理的更保真的解释，还请参考 [https://www.mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/](https://www.mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/)，这里只记录一下工具用法和过程中遇到的一些问题和想法。\n\n## 使用方法\n\n```powershell\nPS D:\\rust\\com-inject\u003e .\\target\\release\\com-inject.exe -h\ncom-inject (1.0) - REInject\nA process injection tool via COM\nCommands:\n  inject  Inject special dll or shellcode to target process\n  list    List interface instance in special or all process\n  help    Print this message or the help of the given subcommand(s)\n\nOptions:\n  -h, --help     Print help\n  -V, --version  Print version\n\n#############################################################\nPS D:\\rust\\com-inject\u003e .\\target\\release\\com-inject.exe inject -h\nInject special dll or shellcode to target process\n\nUsage: com-inject.exe inject [OPTIONS] \u003cPID\u003e\n\nArguments:\n  \u003cPID\u003e  Target process id\n\nOptions:\n  -m, --method            Use CoGetObject instead of CoUnmarshalInterface to establish channel\n  -d, --dll \u003cPATH\u003e        Inject DLL into target, specify full path\n  -s, --shellcode \u003cPATH\u003e  Inject shellcode into target process\n  -h, --help              Print help\n\n#############################################################\nPS D:\\rust\\com-inject\u003e .\\target\\release\\com-inject.exe list -h\nList interface instance in special or all process\n\nUsage: com-inject.exe list [OPTIONS] [PID]\n\nArguments:\n  [PID]  Target process id\n\nOptions:\n  -v, --verbose  Dispaly all interface, default only IRundown\n  -h, --help     Print help\n```\n\n\u003e Tips:\n\u003e\n\u003e - DLL 和 Shellcode 文件路径使用绝对路径\n\u003e - 不论是 list 操作还是 inject 操作，都会尝试开启 DEBUG 权限\n\u003e - 避免对同一进程交替进行 DLL 注入和 shellcode 注入或者重复进行 DLL 注入，可能会报错 “被调用的对象已与其客户端断开连接。 (0x80010108)”，貌似是多次调用后远程接口会被释放掉\n\u003e - 如果报错 “不支持此接口 (0x80004002)”，就多试几遍\n\u003e - 并不是任何进程都能注入，只能对 list 动作显示出来的进程进行注入\n\n## 技术原理\n\n先说一下如何使用 Rust 对 COM 接口调用，调用过程可以分这几个步骤：\n\n1. 接口定义\n2. 调用 CoInitializeEx 初始化\n3. 调用 CoGetObject 或其他类似 API 获取接口指针\n4. 使用接口指针调用接口方法\n5. 调用 CoUninitialize 结束\n\n重点在接口定义，后面几步都是 API 调用，对于一些有文档记录的接口一般都有对应的头文件或 IDL，直接用就行，但是对于其他 COM 接口，调用之前先要定义一个包含方法虚表的结构体/接口，这个虚表的内存偏移、方法顺序需要保证和接口实现一致，后面拿到接口指针才能正确调用对应的方法，c++ 里的接口定义示例：\n\n```cpp\nconst IID \nIID_IRundown = {\n     0x00000134,\n     0x0000,\n     0x0000,\n    {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};\n    \nMIDL_INTERFACE(\"00000134-0000-0000-C000-000000000046\")\nIRundown : public IUnknown {\n    STDMETHOD(RemQueryInterface)         ( REFIPID         ripid,\n                                           ULONG           cRefs,\n                                           USHORT          cIids,\n                                           IID            *iids,\n                                           REMQIRESULT   **ppQIResults);\n                               \n    STDMETHOD(RemAddRef)                 ( unsigned short  cInterfaceRefs,\n                                           REMINTERFACEREF InterfaceRefs[],\n                                           HRESULT        *pResults);\n                               \n    STDMETHOD(RemRelease)                ( USHORT          cInterfaceRefs,\n                                           REMINTERFACEREF InterfaceRefs[]);\n};\n```\n\n所有 COM 接口的祖先就是 IUnknown，大部分接口直接继承自 IUnknown，还有部分通过继承 IDispatch 或其他接口间接的继承自 IUnknown。继承在内存布局上实际上就是在父类的内存结构基础上进行新增，所以不继承直接将 IUnknown 中的方法搬过来也行。\n\n由于 Rust 里面接口、类全部都以 struct 的形式表达，并且和 C++ 中的 struct 内存布局是有区别的，所以在定义接口虚表时，全部需要加上 `#[repr(C)]`，代表该结构体内存布局和 C 完全一致。C 里面有 `IUnknown`，Rust 里也不需要我们从 `IUnknown` 开始实现，实际上在 windows-rs 和 winapi 这两个 crate 中都有实现，但是实现方式上有所不同。主要体现在对 “接口指针” 的定义上，下面是使用 C、winapi、windows-rs 各自如何声明一个接口指针变量：\n\n||声明方式|\n|:--|:--|\n|C|IUnknown *p = NULL;|\n|winapi|let p: *mut IUnknown = ptr::null_mut();|\n|windows-rs|let p: IUnknown;|\n\n可以看出来 winapi 的接口定义方式更符合 c 的接口调用风格，而 windows-rs 从声明上则看不出来是一个指针，指针被隐藏在了内部：\n\n```rust\n#[repr(transparent)]\npub struct IUnknown(std::ptr::NonNull\u003cstd::ffi::c_void\u003e);\n```\n\n`transparent` 可以理解为透传，相当于：`pub type IUnknown = std::ptr::NonNull\u003cstd::ffi::c_void\u003e`，所以 `let p: IUnknown` 等价于 `let p: std::ptr::NonNull\u003cstd::ffi::c_void\u003e`，这样才能看出来是个指针了。\n\n对于这块暂时解释到这里，想更进一步理解具体怎么用 Rust 定义一个接口的话，可以借鉴我代码里对 IRundown 接口的实现方式。\n\n接下来理解 COM 接口方法的调用过程，COM 实际上可以理解为 RPC 的一种上层实现，所以还是 RPC，调用接口的程序称为客户端，真正处理执行调用请求的称为服务端。之前列出的调用过程步骤中的第 3 步，使用 `CoGetObject`、`CoCreateInstance`、`CoGetObjectContext` 这些 API 获取接口指针，如果获取成功就相当于和服务端连接成功，当通过指针调用方法后，相当于发起一个请求到服务端了。\n\n所以回到该技术中，该技术使用了一个名为 `IRundown` 的接口，此接口中包含一个可以执行回调的方法 `DoCallback`，定义如下：\n\n```rust\npub DoCallback: unsafe extern \"system\" fn(this: *mut ::core::ffi::c_void, pParam: *mut XAptCallback) -\u003e windows::core::HRESULT,\n```\n\n`XAptCallback` 参数设置回调地址和参数地址：\n\n```rust\n#[repr(C)]\n#[derive(Clone, Copy)]\npub struct tagXAptCallback {\n    pub pfnCallback: PTRMEM,                    // what to execute. e.g. LoadLibraryA, EtwpCreateEtwThread\n    pub pParam: PTRMEM,                         // parameter to callback.\n    pub pServerCtx: PTRMEM,                     // combase!g_pMTAEmptyCtx\n    pub pUnk: PTRMEM,                           // Not required\n    pub iid: windows::core::GUID,               // Not required\n    pub iMethod: i32,                           // Not required\n    pub guidProcessSecret: windows::core::GUID  // combase!CProcessSecret::s_guidOle32Secret\n}\npub type XAptCallback = tagXAptCallback;\n```\n\n`pfnCallback` 为回调函数指针，`pParam` 为参数指针。加上之前说的 C/S 架构，接口调用请求实际上是在服务端处理的，所以当服务端进程接收到执行回调的请求后，触发回调执行完成代码注入。\n\n大致的技术利用原理就这些，其他的都是一些细节问题，比如如何获取到该接口指针、如何注入到任意进程中去，这两个实际上是一个问题，前面说过成功获取接口指针即是连接到目标进程，所以对于此类问题的根本是 “哪些进程属于这个接口的服务进程”。\n\n好像目前唯一好用的查看 COM 进程信息的工具就是 [OleViewDotNet](https://github.com/tyranid/oleviewdotnet) 了，需要提前使用 windbg 或者其他调试器把 combase.dll 的符号下载到本地，然后配置到 OleViewDotNet 里，否则是查不到任何结果的：\n\n![x](assets/Snipaste_2023-01-17_15-14-29.png)\n\n然后在 Processes -\u003e All Proccess -\u003e By Name 打开 COM 进程列表，搜索 `IRundown`:\n\n![x](assets/Snipaste_2023-01-17_15-11-08.png)\n\n相当于执行 `com-inject.exe list -v`。\n\n这些进程中存在 `IRundown` 接口指针，由于 `IRundown` 接口的实现者是 combase.dll，所以加载 combase.dll 的进程都有可能。\n\nwindbg 里面可以直接按下面的方式找 `IRundown` 接口虚表：\n\n```windbg\n0:004\u003e x /D /d combase!*CRemoteUnknown*\n A B C D E F G H I J K L M N O P Q R S T U V W X Y Z\n\n00007fff`637008c8 combase!CRemoteUnknown::`vftable' = \u003cfunction\u003e *[13]\n0:004\u003e dx -r1 (*((combase!void (__cdecl*(*)[13])())0x7fff637008c8))\n(*((combase!void (__cdecl*(*)[13])())0x7fff637008c8))                 [Type: void (__cdecl* [13])()]\n    [0]              : 0x7fff6353e790 : combase!CRemoteUnknown::QueryInterface+0x0 [Type: void (__cdecl*)()]\n    [1]              : 0x7fff635ae3b0 :  [Type: void (__cdecl*)()]\n    [2]              : 0x7fff635ae3b0 :  [Type: void (__cdecl*)()]\n    [3]              : 0x7fff63520600 : combase!CRemoteUnknown::RemQueryInterface+0x0 [Type: void (__cdecl*)()]\n    [4]              : 0x7fff6351a390 : combase!CRemoteUnknown::RemAddRef+0x0 [Type: void (__cdecl*)()]\n    [5]              : 0x7fff6352f2b0 : combase!CRemoteUnknown::RemRelease+0x0 [Type: void (__cdecl*)()]\n    [6]              : 0x7fff6355ad50 : combase!CRemoteUnknown::RemQueryInterface2+0x0 [Type: void (__cdecl*)()]\n    [7]              : 0x7fff6355afa0 : combase!CRemoteUnknown::AcknowledgeMarshalingSets+0x0 [Type: void (__cdecl*)()]\n    [8]              : 0x7fff636765a0 : combase!CRemoteUnknown::RemChangeRef+0x0 [Type: void (__cdecl*)()]\n    [9]              : 0x7fff6358ee90 : combase!CRemoteUnknown::DoCallback+0x0 [Type: void (__cdecl*)()]\n    [10]             : 0x7fff6358ee80 : combase!CRemoteUnknown::DoNonreentrantCallback+0x0 [Type: void (__cdecl*)()]\n    [11]             : 0x7fff634d29b0 : combase!CRemoteUnknown::GetInterfaceNameFromIPID+0x0 [Type: void (__cdecl*)()]\n    [12]             : 0x7fff6355b140 : combase!CRemoteUnknown::RundownOid+0x0 [Type: void (__cdecl*)()]\n0:004\u003e u 7fff6358ee90\ncombase!CRemoteUnknown::DoCallback [onecore\\com\\combase\\dcomrem\\remoteu.cxx @ 1843]:\n00007fff`6358ee90 48895c2408      mov     qword ptr [rsp+8],rbx\n00007fff`6358ee95 57              push    rdi\n00007fff`6358ee96 4883ec40        sub     rsp,40h\n00007fff`6358ee9a 0f104234        movups  xmm0,xmmword ptr [rdx+34h]\n00007fff`6358ee9e 488bda          mov     rbx,rdx\n00007fff`6358eea1 488d542430      lea     rdx,[rsp+30h]\n00007fff`6358eea6 f30f7f442430    movdqu  xmmword ptr [rsp+30h],xmm0\n00007fff`6358eeac e83b000000      call    combase!CProcessSecret::VerifyMatchingSecret (00007fff`6358eeec)\n0:004\u003e bp 7fff6358ee90\n```\n\n其他细节或者挖掘思路直接看一下大佬的文章解惑吧 [https://www.mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/](https://www.mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/)。\n\n## 已经解决的问题\n\n原项目运行后可能会遇到一些问题，在重写时简单处理了一下，问题如下：\n\n### A、在获取目标进程 IPID 列表偏移时，判断条件不够导致后续步骤访问到错误地址导致崩溃\n\n我在 `find_ipid_table` 中加了些条件，然后就没遇到过着个问题了：\n\n```rust\nif (*cpage)._pgalloc._cPages \u003c= 0 || (*cpage)._pgalloc._cEntries \u003c= 0 {\n    continue;\n}\n```\n\n### B、原项目默认取第一个 IPID 进行交互，但是第一个 IPID 对应的 TID 可能是 `0x0000` 或 `0xFFFF`，导致回调失败\n\n`IPID` 是一个 GUID，是对接口指针的标识，具有一定的格式：`xxxxxxxx-yyyy-zzzz-xxxx-xxxxxxxxxxxx`，`yyyy` 的位置代表进程 PID，`zzzz` 的位置代表线程 TID，如果线程 ID 无效会导致获取的 server context 不正确，最后虽然这个接口指针的状态虽然不是 `IPIDF_DISCONNECTED`，但是最终调用 `DoCallback` 时依然返回错误：“被调用的对象已与其客户端断开连接。 (0x80010108)”。\n\n所以我在获取接口指针时，加了些过滤，优先使用 TID 有效的 IPID：\n\n```rust\nlet x: Vec\u003c_\u003e = entries.iter().filter(|x| x.ipid.tid \u003e 0x0 \u0026\u0026 x.ipid.tid \u003c 0xffff).collect();\nlet y: Vec\u003c_\u003e = entries.iter().filter(|x| x.ipid.tid == 0x0).collect();\nif x.len() \u003e 0 {\n    (*rc).ipid = x[0].ipid;\n    (*rc).oxid = x[0].oxid;\n    (*rc).oid = x[0].oid;\n} else if y.len() \u003e 0 {\n    (*rc).ipid = y[0].ipid;\n    (*rc).oxid = y[0].oxid;\n    (*rc).oid = y[0].oid;\n} else {\n    (*rc).ipid = entries[0].ipid;\n    (*rc).oxid = entries[0].oxid;\n    (*rc).oid = entries[0].oid;\n}\n```\n\n## 未解决的问题\n\n1. 每次注入都会消耗掉目标进程中的一个接口指针，不确定为什么会自动释放掉，当用完之后就会一直注入失败了\n2. 对于 TID 为 `0x0000` 或 `0xFFFF` 时总是注入失败，怎么解决\n3. 通用于 `x86` 和 `x86_64` 的 COM 进程\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xlane%2Fcom-process-inject","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0xlane%2Fcom-process-inject","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xlane%2Fcom-process-inject/lists"}