Windows RPC调用(一)
前言:最近在学习打印机漏洞,但是其中涉及到了RPC相关的知识,这篇作为学习Windows的RPC的笔记(一)
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/rpc-start-page
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/general-build-procedure
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/using-midl
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/the-acf-file
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/binding-handles
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/tutorial
参考文章:https://xz.aliyun.com/t/11313
参考文章:https://itm4n.github.io/from-rpcview-to-petitpotam/
RPC远程过程调用
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/rpc-start-page
Microsoft微软开发并在Windows上使用的RPC实现是DCE/RPC,它是"分布式计算环境/远程过程调用"的缩写。
DCE/RPC只是Windows中使用的众多 IPC(进程间通信)机制之一。例如,它用于允许本地进程甚至网络上的远程客户端与本地或远程机器上的另一个进程或服务交互。
RPC也是一种进程间通信方式,采用的是客户机/服务器模式,它允许本地程序调用另一个地址空间的过程或者函数,而不用程序员去管理调用的细节,发生在同一台主机上就是LPC,发生在不同主机上就是RPC。
而在本地过程调用(LPC)中实现的方式是压栈直接调函数,远程过程调用也是调函数,但是在调用另一个进程的函数,而为了区分调用哪个函数设置了一些标识,这些表示则对应两个进程的对应的函数,所以客户端传给服务端不仅仅需要传递函数的参数还需要给那些标识表示调用哪个函数。
结构部分
客户端(client):服务的调用方。
客户端存根(client stub):存放服务端的地址信息,再将客户端的请求参数打包成网络数据,然后通过网络远程发送给服务方。
服务端存根(server stub):接受客户端发送过来的消息,将消息解包,并调用本地的方法。
服务端(server):真正的服务提供者。
RPC调用协议
另外RPC技术发送Local请求时使用ncalrpc协议,发送Remote请求时使用ncacn_ip_tcp或者ncacn_np协议,前者微软更推荐。
Tutorial实战编写
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/tutorial
MIDL文件编写
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/general-build-procedure
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/using-midl
由于客户端和服务器程序都依赖于接口,因此必须在开发客户端和服务器之前开发它们之间的接口,而这边定义接口的方式就是通过编写MIDL来进行实现。
为了统一客户端与服务端不同平台处理不同的实现,于是有了IDL语言。IDL文件由一个或多个接口定义组成,每一个接口定义都有一个接口头和一个接口体,接口头包含了使用此接口的信息(UUID和接口版本),接口体包含了接口函数的原型。
安装了Visual Studio都会带上Windows Kits,其中就包含了midl.exe,如下图所示
这边关于hello.idl测试文件可以通过Visual Studio来帮助编译,对着红框中文件右键编译即可,如下图所示
hello.idl
[ uuid(7a98c250-6808-11cf-b73b-00aa00b677a7), version(1.0) ] interface hello { void HelloProc([in, string] unsigned char * pszString); void Shutdown(void); }
上面的图中可以看到生成了三个文件,分别是如下所示
-
hello_h.h 客户端和服务端共用的头文件
-
hello_c.c 客户端
-
hello_s.c 服务端
ACF文件的编写
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/the-acf-file
RPC应用程序使用 ACF 文件来描述特定于硬件和操作系统的接口的特性,和IDL文件一起由MIDL编译,所以MIDL编译器(vs自带)可以为不同的平台和系统版本生成代码,这并非是必须的。
由它们(ACF/IDL)编译生成后的文件用于描述调用方和被调用过程之间的数据交换和函数原型和参数传递机制。
例如,如果客户端应用程序包含一个仅在本地计算机上有意义的复杂数据结构,则可以在ACF文件中指定如何以独立于计算机的形式表示该结构中的数据,以便进行远程过程调用。
这边将hello.acf放置到和hello.idl同级的项目中重新编译即可,在新的编译生成的hello_x.h/c文件中查找关键词IfHandle
,如果存在的话那么acf文件就应用上了
hello.acf
//file: hello.acf [ implicit_handle (handle_t hello_IfHandle) ] interface hello { }
这里有一个知识点关于[implicit_handle]
的标识,这个implicit_handle
标识指定用于不包含显式句柄作为过程参数的函数的句柄。
RPC的句柄
参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/binding-handles
关于在RPC编程中,RPC的句柄分为三种类型,分别是如下:
-
Automatic Binding Handles
-
Implicit Binding Handles
-
Explicit Binding Handles
客户端编写
客户端中编写实现通过ncacn_np协议连接管道hello调用HelloProc进行传值操作
- 创建StringBinding对象用于描述连接服务端的信息
- StringBinding对象转换为BindingHandle对象
- 调用远程服务HelloProc/Shutdown
- 释放内存
main.c
#include <stdlib.h> #include <stdio.h> #include <ctype.h> #include "../RPCTest/hello_h.h" #include <windows.h> #pragma comment(lib,"RpcRT4.lib") // 参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/the-client-application int main() { RPC_STATUS status; unsigned char* pszUuid = NULL; unsigned char* pszProtocolSequence = L"ncacn_np"; unsigned char* pszNetworkAddress = NULL; unsigned char* pszEndpoint = L"\\pipe\\hello"; unsigned char* pszOptions = NULL; unsigned char* pszStringBinding = NULL; unsigned char* pszString = "hello, world"; unsigned long ulCode; // 创建StringBinding对象用于描述连接服务端的信息 status = RpcStringBindingCompose(pszUuid, pszProtocolSequence, pszNetworkAddress, pszEndpoint, pszOptions, &pszStringBinding); if (status) { printf("Invoke RpcStringBindingCompose Error\n"); exit(status); } // StringBinding对象转换为BindingHandle对象 status = RpcBindingFromStringBinding(pszStringBinding, &hello_IfHandle); if (status) { printf("Invoke RpcBindingFromStringBinding Error\n"); exit(status); } // 调用远程服务 RpcTryExcept { HelloProc(pszString); Shutdown(); } RpcExcept(1) { ulCode = RpcExceptionCode(); printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode); } RpcEndExcept; // 释放StringBinding指针 status = RpcStringFree(&pszStringBinding); if (status) { printf("Invoke RpcStringFree Error\n"); exit(status); } // 释放hello_IfHandle句柄 status = RpcBindingFree(&hello_IfHandle); if (status){ printf("Invoke RpcBindingFree Error\n"); exit(status); } return 0; } /******************************************************/ /* MIDL allocate and free */ /******************************************************/ void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len) { return(malloc(len)); } void __RPC_USER midl_user_free(void __RPC_FAR* ptr) { free(ptr); }
服务端RPC编写
总共分为如下几个步骤:
- 指定在RPC运行时使用的协议序列
- 注册接口,使其指定接口能够被用于RPC远程调用
- 开启RPC服务器监听
#include <stdlib.h> #include <stdio.h> #include <ctype.h> #include "../RPCTest/hello_h.h" #include <windows.h> #pragma comment(lib,"RpcRT4.lib") // 参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/the-server-application int main() { RPC_STATUS status; unsigned char* pszProtocolSequence = L"ncacn_np"; unsigned char* pszSecurity = NULL; unsigned char* pszEndpoint = L"\\pipe\\hello"; unsigned int cMinCalls = 1; unsigned int fDontWait = FALSE; // 指定在RPC运行时使用的协议序列 // https://learn.microsoft.com/en-us/windows/win32/rpc/making-the-server-available-on-the-network status = RpcServerUseProtseqEp( pszProtocolSequence, // 选择 ncacn_ip_tcp 协议序列 RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Protseq-dependent parameter pszEndpoint, // 端点 NULL); // Always specify NULL here. if (status) { printf("Invoke RpcServerUseProtseqEp Error\n"); exit(status); } printf("Invoke RpcServerUseProtseqEp\n"); // 注册接口,使其指定接口能够被用于RPC远程调用 status = RpcServerRegisterIf(hello_v1_0_s_ifspec, NULL, NULL); if (status) { printf("Invoke RpcServerRegisterIf Error\n"); exit(status); } printf("Invoke RpcServerRegisterIf\n"); // 开启监听服务在服务器上面 status = RpcServerListen( cMinCalls, RPC_C_LISTEN_MAX_CALLS_DEFAULT, fDontWait); if (status) { printf("Invoke RpcServerListen Error\n"); exit(status); } printf("Invoke RpcServerListen\n"); return 0; } /******************************************************/ /* MIDL allocate and free */ /******************************************************/ void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len) { return(malloc(len)); } void __RPC_USER midl_user_free(void __RPC_FAR* ptr) { free(ptr); }
整体调用如下图所示
修改远程RPC调用
上面的代码演示中可以看到调用的过程之间是本地进行LPC调用,那么这边再修改为远程调用进行演示
main_c.c
#include <stdlib.h> #include <stdio.h> #include <ctype.h> #include "../RPCTest/hello_h.h" #include <windows.h> #pragma comment(lib,"RpcRT4.lib") // 参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/the-client-application int main() { RPC_STATUS status; unsigned char* pszUuid = NULL; // LPC本地调用 // unsigned char* pszProtocolSequence = L"ncacn_np"; // unsigned char* pszNetworkAddress = NULL; //unsigned char* pszEndpoint = L"\\pipe\\hello"; // RPC远程调用 unsigned char* pszProtocolSequence = (unsigned char*)L"ncacn_ip_tcp"; unsigned char* pszNetworkAddress = (unsigned char*)L"192.168.75.22"; unsigned char* pszEndpoint = (unsigned char*)L"5000"; unsigned char* pszOptions = NULL; unsigned char* pszStringBinding = NULL; unsigned char* pszString = "hello, world"; unsigned long ulCode; // 创建StringBinding对象用于描述连接服务端的信息 status = RpcStringBindingCompose(pszUuid, pszProtocolSequence, pszNetworkAddress, pszEndpoint, pszOptions, &pszStringBinding); if (status) { printf("Invoke RpcStringBindingCompose, The Error Is %d\n", status); exit(status); } // StringBinding对象转换为BindingHandle对象 status = RpcBindingFromStringBinding(pszStringBinding, &hello_IfHandle); if (status) { printf("Invoke RpcBindingFromStringBinding, The Error Is %d\n", status); exit(status); } // 调用远程服务 RpcTryExcept { HelloProc(pszString); Shutdown(); } RpcExcept(1) { ulCode = RpcExceptionCode(); printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode); } RpcEndExcept; // 释放StringBinding指针 status = RpcStringFree(&pszStringBinding); if (status) { printf("Invoke RpcStringFree, The Error Is %d\n", status); exit(status); } // 释放hello_IfHandle句柄 status = RpcBindingFree(&hello_IfHandle); if (status){ printf("Invoke RpcBindingFree, The Error Is %d\n", status); exit(status); } return 0; } /******************************************************/ /* MIDL allocate and free */ /******************************************************/ void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len) { return(malloc(len)); } void __RPC_USER midl_user_free(void __RPC_FAR* ptr) { free(ptr); }
main_s.c
#include <stdlib.h> #include <stdio.h> #include <ctype.h> #include "../RPCTest/hello_h.h" #include <windows.h> #pragma comment(lib,"RpcRT4.lib") // 参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/the-server-application int main() { RPC_STATUS status; // LPC本地调用 //unsigned char* pszProtocolSequence = L"ncacn_np"; //unsigned char* pszEndpoint = L"\\pipe\\hello"; // RPC远程调用 unsigned char* pszProtocolSequence = L"ncacn_ip_tcp"; unsigned char* pszEndpoint = (unsigned char*)L"5000"; unsigned char* pszSecurity = NULL; unsigned int cMinCalls = 1; unsigned int fDontWait = FALSE; // 指定在RPC运行时使用的协议序列 // https://learn.microsoft.com/en-us/windows/win32/rpc/making-the-server-available-on-the-network status = RpcServerUseProtseqEp(pszProtocolSequence, RPC_C_PROTSEQ_MAX_REQS_DEFAULT, pszEndpoint, pszSecurity); if (status) { printf("Invoke RpcServerUseProtseqEp Error, The Error Is %d\n", status); exit(status); } printf("Invoke RpcServerUseProtseqEp...\n"); // 注册接口,使其指定接口能够被用于RPC远程调用 status = RpcServerRegisterIf(hello_v1_0_s_ifspec, NULL, NULL); if (status) { printf("Invoke RpcServerRegisterIf Error, The Error Is %d\n", status); exit(status); } printf("Invoke RpcServerRegisterIf...\n"); // 开启监听服务在服务器上面 status = RpcServerListen( cMinCalls, RPC_C_LISTEN_MAX_CALLS_DEFAULT, fDontWait); if (status) { printf("Invoke RpcServerListen Error, The Error Is %d\n", GetLastError()); exit(status); } printf("Invoke RpcServerListen...\n"); return 0; } /******************************************************/ /* MIDL allocate and free */ /******************************************************/ void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len) { return(malloc(len)); } void __RPC_USER midl_user_free(void __RPC_FAR* ptr) { free(ptr); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY