COM PROCESS INJECTION for RUST

项目地址:https://github.com/0xlane/com-process-inject

Process Injection via Component Object Model (COM) IRundown::DoCallback().

该技术由 @modexpblog 挖掘发现,在我对该技术进行深入研究过程中,将原项目 mdsecactivebreach/com_inject 使用了 Rust 重写,希望对使用 Rust 的安全人员在 COM 接口调用、进程注入等方面有所帮助。

对于此项注入技术原理的更保真的解释,还请参考 https://www.mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/,这里只记录一下工具用法和过程中遇到的一些问题和想法。

使用方法

PS D:\rust\com-inject> .\target\release\com-inject.exe -h
com-inject (1.0) - REInject
A process injection tool via COM
Commands:
inject Inject special dll or shellcode to target process
list List interface instance in special or all process
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
#############################################################
PS D:\rust\com-inject> .\target\release\com-inject.exe inject -h
Inject special dll or shellcode to target process
Usage: com-inject.exe inject [OPTIONS] <PID>
Arguments:
<PID> Target process id
Options:
-m, --method Use CoGetObject instead of CoUnmarshalInterface to establish channel
-d, --dll <PATH> Inject DLL into target, specify full path
-s, --shellcode <PATH> Inject shellcode into target process
-h, --help Print help
#############################################################
PS D:\rust\com-inject> .\target\release\com-inject.exe list -h
List interface instance in special or all process
Usage: com-inject.exe list [OPTIONS] [PID]
Arguments:
[PID] Target process id
Options:
-v, --verbose Dispaly all interface, default only IRundown
-h, --help Print help

Tips:

  • DLL 和 Shellcode 文件路径使用绝对路径
  • 不论是 list 操作还是 inject 操作,都会尝试开启 DEBUG 权限
  • 避免对同一进程交替进行 DLL 注入和 shellcode 注入或者重复进行 DLL 注入,可能会报错 “被调用的对象已与其客户端断开连接。 (0x80010108)”,貌似是多次调用后远程接口会被释放掉
  • 如果报错 “不支持此接口 (0x80004002)”,就多试几遍
  • 并不是任何进程都能注入,只能对 list 动作显示出来的进程进行注入

技术原理

先说一下如何使用 Rust 对 COM 接口调用,调用过程可以分这几个步骤:

  1. 接口定义
  2. 调用 CoInitializeEx 初始化
  3. 调用 CoGetObject 或其他类似 API 获取接口指针
  4. 使用接口指针调用接口方法
  5. 调用 CoUninitialize 结束

重点在接口定义,后面几步都是 API 调用,对于一些有文档记录的接口一般都有对应的头文件或 IDL,直接用就行,但是对于其他 COM 接口,调用之前先要定义一个包含方法虚表的结构体/接口,这个虚表的内存偏移、方法顺序需要保证和接口实现一致,后面拿到接口指针才能正确调用对应的方法,c++ 里的接口定义示例:

const IID
IID_IRundown = {
0x00000134,
0x0000,
0x0000,
{0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; MIDL_INTERFACE("00000134-0000-0000-C000-000000000046")
IRundown : public IUnknown {
STDMETHOD(RemQueryInterface) ( REFIPID ripid,
ULONG cRefs,
USHORT cIids,
IID *iids,
REMQIRESULT **ppQIResults); STDMETHOD(RemAddRef) ( unsigned short cInterfaceRefs,
REMINTERFACEREF InterfaceRefs[],
HRESULT *pResults); STDMETHOD(RemRelease) ( USHORT cInterfaceRefs,
REMINTERFACEREF InterfaceRefs[]);
};

所有 COM 接口的祖先就是 IUnknown,大部分接口直接继承自 IUnknown,还有部分通过继承 IDispatch 或其他接口间接的继承自 IUnknown。继承在内存布局上实际上就是在父类的内存结构基础上进行新增,所以不继承直接将 IUnknown 中的方法搬过来也行。

由于 Rust 里面接口、类全部都以 struct 的形式表达,并且和 C++ 中的 struct 内存布局是有区别的,所以在定义接口虚表时,全部需要加上 #[repr(C)],代表该结构体内存布局和 C 完全一致。C 里面有 IUnknown,Rust 里也不需要我们从 IUnknown 开始实现,实际上在 windows-rs 和 winapi 这两个 crate 中都有实现,但是实现方式上有所不同。主要体现在对 “接口指针” 的定义上,下面是使用 C、winapi、windows-rs 各自如何声明一个接口指针变量:

声明方式
C IUnknown *p = NULL;
winapi let p: *mut IUnknown = ptr::null_mut();
windows-rs let p: IUnknown;

可以看出来 winapi 的接口定义方式更符合 c 的接口调用风格,而 windows-rs 从声明上则看不出来是一个指针,指针被隐藏在了内部:

#[repr(transparent)]
pub struct IUnknown(std::ptr::NonNull<std::ffi::c_void>);

transparent 可以理解为透传,相当于:pub type IUnknown = std::ptr::NonNull<std::ffi::c_void>,所以 let p: IUnknown 等价于 let p: std::ptr::NonNull<std::ffi::c_void>,这样才能看出来是个指针了。

对于这块暂时解释到这里,想更进一步理解具体怎么用 Rust 定义一个接口的话,可以借鉴我代码里对 IRundown 接口的实现方式。

接下来理解 COM 接口方法的调用过程,COM 实际上可以理解为 RPC 的一种上层实现,所以还是 RPC,调用接口的程序称为客户端,真正处理执行调用请求的称为服务端。之前列出的调用过程步骤中的第 3 步,使用 CoGetObjectCoCreateInstanceCoGetObjectContext 这些 API 获取接口指针,如果获取成功就相当于和服务端连接成功,当通过指针调用方法后,相当于发起一个请求到服务端了。

所以回到该技术中,该技术使用了一个名为 IRundown 的接口,此接口中包含一个可以执行回调的方法 DoCallback,定义如下:

pub DoCallback: unsafe extern "system" fn(this: *mut ::core::ffi::c_void, pParam: *mut XAptCallback) -> windows::core::HRESULT,

XAptCallback 参数设置回调地址和参数地址:

#[repr(C)]
#[derive(Clone, Copy)]
pub struct tagXAptCallback {
pub pfnCallback: PTRMEM, // what to execute. e.g. LoadLibraryA, EtwpCreateEtwThread
pub pParam: PTRMEM, // parameter to callback.
pub pServerCtx: PTRMEM, // combase!g_pMTAEmptyCtx
pub pUnk: PTRMEM, // Not required
pub iid: windows::core::GUID, // Not required
pub iMethod: i32, // Not required
pub guidProcessSecret: windows::core::GUID // combase!CProcessSecret::s_guidOle32Secret
}
pub type XAptCallback = tagXAptCallback;

pfnCallback 为回调函数指针,pParam 为参数指针。加上之前说的 C/S 架构,接口调用请求实际上是在服务端处理的,所以当服务端进程接收到执行回调的请求后,触发回调执行完成代码注入。

大致的技术利用原理就这些,其他的都是一些细节问题,比如如何获取到该接口指针、如何注入到任意进程中去,这两个实际上是一个问题,前面说过成功获取接口指针即是连接到目标进程,所以对于此类问题的根本是 “哪些进程属于这个接口的服务进程”。

好像目前唯一好用的查看 COM 进程信息的工具就是 OleViewDotNet 了,需要提前使用 windbg 或者其他调试器把 combase.dll 的符号下载到本地,然后配置到 OleViewDotNet 里,否则是查不到任何结果的:

然后在 Processes -> All Proccess -> By Name 打开 COM 进程列表,搜索 IRundown:

相当于执行 com-inject.exe list -v

这些进程中存在 IRundown 接口指针,由于 IRundown 接口的实现者是 combase.dll,所以加载 combase.dll 的进程都有可能。

windbg 里面可以直接按下面的方式找 IRundown 接口虚表:

0:004> x /D /d combase!*CRemoteUnknown*
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
00007fff`637008c8 combase!CRemoteUnknown::`vftable' = <function> *[13]
0:004> dx -r1 (*((combase!void (__cdecl*(*)[13])())0x7fff637008c8))
(*((combase!void (__cdecl*(*)[13])())0x7fff637008c8)) [Type: void (__cdecl* [13])()]
[0] : 0x7fff6353e790 : combase!CRemoteUnknown::QueryInterface+0x0 [Type: void (__cdecl*)()]
[1] : 0x7fff635ae3b0 : [Type: void (__cdecl*)()]
[2] : 0x7fff635ae3b0 : [Type: void (__cdecl*)()]
[3] : 0x7fff63520600 : combase!CRemoteUnknown::RemQueryInterface+0x0 [Type: void (__cdecl*)()]
[4] : 0x7fff6351a390 : combase!CRemoteUnknown::RemAddRef+0x0 [Type: void (__cdecl*)()]
[5] : 0x7fff6352f2b0 : combase!CRemoteUnknown::RemRelease+0x0 [Type: void (__cdecl*)()]
[6] : 0x7fff6355ad50 : combase!CRemoteUnknown::RemQueryInterface2+0x0 [Type: void (__cdecl*)()]
[7] : 0x7fff6355afa0 : combase!CRemoteUnknown::AcknowledgeMarshalingSets+0x0 [Type: void (__cdecl*)()]
[8] : 0x7fff636765a0 : combase!CRemoteUnknown::RemChangeRef+0x0 [Type: void (__cdecl*)()]
[9] : 0x7fff6358ee90 : combase!CRemoteUnknown::DoCallback+0x0 [Type: void (__cdecl*)()]
[10] : 0x7fff6358ee80 : combase!CRemoteUnknown::DoNonreentrantCallback+0x0 [Type: void (__cdecl*)()]
[11] : 0x7fff634d29b0 : combase!CRemoteUnknown::GetInterfaceNameFromIPID+0x0 [Type: void (__cdecl*)()]
[12] : 0x7fff6355b140 : combase!CRemoteUnknown::RundownOid+0x0 [Type: void (__cdecl*)()]
0:004> u 7fff6358ee90
combase!CRemoteUnknown::DoCallback [onecore\com\combase\dcomrem\remoteu.cxx @ 1843]:
00007fff`6358ee90 48895c2408 mov qword ptr [rsp+8],rbx
00007fff`6358ee95 57 push rdi
00007fff`6358ee96 4883ec40 sub rsp,40h
00007fff`6358ee9a 0f104234 movups xmm0,xmmword ptr [rdx+34h]
00007fff`6358ee9e 488bda mov rbx,rdx
00007fff`6358eea1 488d542430 lea rdx,[rsp+30h]
00007fff`6358eea6 f30f7f442430 movdqu xmmword ptr [rsp+30h],xmm0
00007fff`6358eeac e83b000000 call combase!CProcessSecret::VerifyMatchingSecret (00007fff`6358eeec)
0:004> bp 7fff6358ee90

其他细节或者挖掘思路直接看一下大佬的文章解惑吧 https://www.mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/

已经解决的问题

原项目运行后可能会遇到一些问题,在重写时简单处理了一下,问题如下:

A、在获取目标进程 IPID 列表偏移时,判断条件不够导致后续步骤访问到错误地址导致崩溃

我在 find_ipid_table 中加了些条件,然后就没遇到过着个问题了:

if (*cpage)._pgalloc._cPages <= 0 || (*cpage)._pgalloc._cEntries <= 0 {
continue;
}

B、原项目默认取第一个 IPID 进行交互,但是第一个 IPID 对应的 TID 可能是 0x00000xFFFF,导致回调失败

IPID 是一个 GUID,是对接口指针的标识,具有一定的格式:xxxxxxxx-yyyy-zzzz-xxxx-xxxxxxxxxxxxyyyy 的位置代表进程 PID,zzzz 的位置代表线程 TID,如果线程 ID 无效会导致获取的 server context 不正确,最后虽然这个接口指针的状态虽然不是 IPIDF_DISCONNECTED,但是最终调用 DoCallback 时依然返回错误:“被调用的对象已与其客户端断开连接。 (0x80010108)”。

所以我在获取接口指针时,加了些过滤,优先使用 TID 有效的 IPID:

let x: Vec<_> = entries.iter().filter(|x| x.ipid.tid > 0x0 && x.ipid.tid < 0xffff).collect();
let y: Vec<_> = entries.iter().filter(|x| x.ipid.tid == 0x0).collect();
if x.len() > 0 {
(*rc).ipid = x[0].ipid;
(*rc).oxid = x[0].oxid;
(*rc).oid = x[0].oid;
} else if y.len() > 0 {
(*rc).ipid = y[0].ipid;
(*rc).oxid = y[0].oxid;
(*rc).oid = y[0].oid;
} else {
(*rc).ipid = entries[0].ipid;
(*rc).oxid = entries[0].oxid;
(*rc).oid = entries[0].oid;
}

未解决的问题

  1. 每次注入都会消耗掉目标进程中的一个接口指针,不确定为什么会自动释放掉,当用完之后就会一直注入失败了
  2. 对于 TID 为 0x00000xFFFF 时总是注入失败,怎么解决
  3. 通用于 x86x86_64 的 COM 进程

COM 进程注入技术的更多相关文章

  1. Android中通过进程注入技术改动广播接收器的优先级

    前言 这个周末又没有吊事,在家研究了怎样通过进程的注入技术改动广播接收器的优先级.关于这个应用场景是非常多的.并且也非常重要.所以就非常急的去fixed了. Android中的四大组件中有一个广播:B ...

  2. Android中通过进程注入技术修改广播接收器的优先级

    前言 这个周末又没有吊事,在家研究了如何通过进程的注入技术修改广播接收器的优先级,关于这个应用场景是很多的,而且也很重要,所以就很急的去fixed了. Android中的四大组件中有一个广播:Broa ...

  3. Android中通过进程注入技术修改系统返回的Mac地址

    致谢 感谢看雪论坛中的这位大神,分享了这个技术:http://bbs.pediy.com/showthread.php?t=186054,从这篇文章中学习到了很多内容,如果没有这篇好文章,我在研究的过 ...

  4. HOOK -- DLL的远程注入技术详解(1)

    DLL的远程注入技术是目前Win32病毒广泛使用的一种技术.使用这种技术的病毒体通常位于一个DLL中,在系统启动的时候,一个EXE程序会将这个DLL加载至某些系统进程(如Explorer.exe)中运 ...

  5. 利用“进程注入”实现无文件复活 WebShell

    引子 上周末,一个好兄弟找我说一个很重要的目标shell丢了,这个shell之前是通过一个S2代码执行的漏洞拿到的,现在漏洞还在,不过web目录全部不可写,问我有没有办法搞个webshell继续做内网 ...

  6. <ReversingEngineering>关于windows32位系统下的dll注入技术经验汇

    上个学期把自己闷在图书馆一直在看关于逆向工程技术方面的书,从入门到初级,现在也敢说自己一条腿已经迈进了这片知识的大门里,因为该博客刚开通先将一些经验记录下来,也是留给自己一方面做个参照. <逆向 ...

  7. DLL的远程注入技术

    DLL的远程注入技术是目前Win32病毒广泛使用的一种技术.使用这种技术的病毒体通常位于一个DLL中,在系统启动的时候,一个EXE程序会将这个DLL加载至某些系统进程(如Explorer.exe)中运 ...

  8. Dll注入技术之消息钩子

    转自:黑客反病毒 DLL注入技术之消息钩子注入 消息钩子注入原理是利用Windows 系统中SetWindowsHookEx()这个API,他可以拦截目标进程的消息到指定的DLL中导出的函数,利用这个 ...

  9. Dll注入技术之注册表注入

    DLL注入技术之REG注入 DLL注入技术指的是将一个DLL文件强行加载到EXE文件中,并成为EXE文件中的一部分,这样做的目的在于方便我们通过这个DLL读写EXE文件内存数据,(例如 HOOK EX ...

  10. Dll注入技术之远程线程注入

    DLL注入技术之远线程注入 DLL注入技术指的是将一个DLL文件强行加载到EXE文件中,并成为EXE文件中的一部分,这样做的目的在于方便我们通过这个DLL读写EXE文件内存数据,(例如 HOOK EX ...

随机推荐

  1. Codeforces Round #829 (Div. 1/Div. 2) 1753 A B C D 题解

    Div1A / 2C. Make Nonzero Sum 令最后每个\(a_i\)的系数为\(c_i\)(\(c_i=1/-1\)),发现只要满足\(c_1=1\)(下标从1开始),且c中没有两个-1 ...

  2. Codeforces Global Round 23 A-D

    比赛链接 A 题解 知识点:贪心,构造. 注意到有 \(1\) 就一定能构造. 时间复杂度 \(O(n)\) 空间复杂度 \(O(1)\) 代码 #include <bits/stdc++.h& ...

  3. 论文笔记 - Fantastically Ordered Prompts and Where to Find Them: Overcoming Few-Shot Prompt Order Sensitivity

    prompt 的影响因素 Motivation Prompt 中 Example 的排列顺序对模型性能有较大影响(即使已经校准参见好的情况下,选取不同的排列顺序依然会有很大的方差): 校准可以大幅度提 ...

  4. prefetch和preload

    前面的话 基于VUE的前端小站改造成SSR服务器端渲染后,HTML文档会自动使用preload和prefetch来预加载所需资源,本文将详细介绍preload和prefetch的使用 资源优先级 在介 ...

  5. 洛谷P5309 Ynoi 2011 初始化 题解

    题面. 我也想过根号分治,但是题目刷得少,数组不敢开,所以还是看题解做的. 这道题目要用到根号分治的思想,可以看看这道题目和我的题解. 题目要求处理一个数组a,支持如下操作. 对一个整数x,对数组长度 ...

  6. 【RPC和Protobuf】之RPC入门

    一,概念 RPC:Remote procedure call(远程过程调用),分布式系统中不同节点之间流行的通信方式 服务端: 注: 1.执行下面的代码之后,会相应的启动一个tcp进程 C:\User ...

  7. conky配置(附配置项作用解释)

    alignment top_right #是否嵌入桌面 background yes #是否绘制窗口边框 draw_borders no #窗口边框 border_width 10 #cpu_avg_ ...

  8. ArcGIS 添加Excel数据 报错 ArcGIS Failed to connect to database 外部数据库驱动程序(1)中的意外错误

    原因是因为 操作系统安装了一些补丁,卸载即可. 把以下补丁卸载掉即可. win7 <-- KB4041678 , KB4041681  --> SERVER 2008 R2 <-- ...

  9. 【collection】1.java容器之HashMap&LinkedHashMap&Hashtable

    Map源码剖析 HashMap&LinkedHashMap&Hashtable hashMap默认的阈值是0.75 HashMap put操作 put操作涉及3种结构,普通node节点 ...

  10. EasyCode全自动单表增删改查!

    需要IDEA下载EasyCode插件 准备好三个基础Base类 分页封装基础 package com.gton.io; import lombok.AllArgsConstructor; import ...