rpc_demo,密码是1
搜索rpcserver注册位置,我们拿赛门铁克epp来做例子
主服务进程ccsvchst.exe
根据我们的rpc实例程序我们可以知道,rpc服务段肯定要调用RpcServerRegisterIf2函数进行注册,注册的时候要传入一个字符串来表示rpc服务的类型,示例程序是ncacn_ip_tcp

使用rpcview可以看到主服务进程ccsvchst的rpc类型是ncalrpc

那么我们搜索ccsvchst进程的所有二进制文件,去找这个字符串即可
我们使用windbg内核调试器获取到该进程的所有dll路径,然后全部拷贝到一个目录中,之后使用下面这个python脚本去搜索这个字符串
import os
def utf16le_search_in_file(file_path, utf16le_bytes):
try:
with open(file_path, 'rb') as f:
data = f.read()
return utf16le_bytes in data
except Exception as e:
# Skip files we can't read
return False
def search_folder_for_utf16le_string(folder_path, target_string):
utf16le_bytes = target_string.encode('utf-16le')
for root, dirs, files in os.walk(folder_path):
for filename in files:
file_path = os.path.join(root, filename)
if utf16le_search_in_file(file_path, utf16le_bytes):
print(f"[FOUND] {file_path}")
if __name__ == "__main__":
folder = r"C:\users\x\downloads\sep" # 🔧 Change this
search_string = "ncalrpc" # 🔧 Change this
search_folder_for_utf16le_string(folder, search_string)
搜索出来下面这3个不在system32下的dll包含这个字符串
CsrssDrvLoadDll.dll
ccIPC.dll
symamsi.dll
IPC很可能是进程间通信的意思,那我们就先来看这个dll
查看导入表,果不其然,存在RpcServerRegisterIfEx函数,查看其引用,找到注册代码

对照我们的示例代码,那么第一个参数就是RemotePrivilegeCall_v1_0_s_ifspec
status = RpcServerRegisterIf2(
RemotePrivilegeCall_v1_0_s_ifspec, // Name of the interface defined in RemotePrivilegeCall.h
NULL, // UUID to bind to (NULL means the one from the MIDL file)
NULL, // Interface to use (NULL means the one from the MIDL file)
RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH, // Invoke the security callback function
RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Numbers of simultaneous connections
(unsigned)-1, // Maximum size of data block received
SecurityCallback // Name of the function that acts as the security callback
);
这个参数的值实际上就是RemotePrivilegeCall___RpcServerInterface的地址
RPC_IF_HANDLE RemotePrivilegeCall_v1_0_s_ifspec = (RPC_IF_HANDLE)& RemotePrivilegeCall___RpcServerInterface;
那么我们就知道unk_180031F80就是RemotePrivilegeCall___RpcServerInterface,他的结构如下:
static const RPC_SERVER_INTERFACE RemotePrivilegeCall___RpcServerInterface =
{
sizeof(RPC_SERVER_INTERFACE),
{ { 0xAB4ED934,0x1293,0x10DE,{ 0xBC,0x12,0xAE,0x18,0xC4,0x8D,0xEF,0x33 } },{ 1,0 } },
{ { 0x8A885D04,0x1CEB,0x11C9,{ 0x9F,0xE8,0x08,0x00,0x2B,0x10,0x48,0x60 } },{ 2,0 } },
(RPC_DISPATCH_TABLE*)&RemotePrivilegeCall_v1_0_DispatchTable,
0,
0,
0,
&RemotePrivilegeCall_ServerInfo,
0x04000000
};
RemotePrivilegeCall_ServerInfo里面包含有rpc服务端函数的信息,现在我们去windbg里面看一下
RemotePrivilegeCall_ServerInfo是RPC_SERVER_INTERFACE结构体的InterpreterInfo字段,他的偏移量是80


那这个地方存的是RemotePrivilegeCall_ServerInfo的地址
static const MIDL_SERVER_INFO RemotePrivilegeCall_ServerInfo =
{
&RemotePrivilegeCall_StubDesc,
RemotePrivilegeCall_ServerRoutineTable,
RemotePrivilegeCall__MIDL_ProcFormatString.Format,
RemotePrivilegeCall_FormatStringOffsetTable,
0,
0,
0,
0 };
我们在8偏移处即可得到rpc服务端函数的表
dqs poi(ccipc!getfactory+2eb84+8)

然后我们要找到client,看看他是怎么调用rpc接口的,首先我们要获取到服务端rpc的接口标识符,就是一个GUID
这个GUID就在RemotePrivilegeCall___RpcServerInterface的4偏移量,长度为0x10

这一串字节序列同样也会出现在client的二进制文件中

那么使用这种方法,我们就可以在赛门铁克的安装包中找到client
我靠赛门铁克他这个二进制里面的guid和实际跑起来的时候的guid竟然是不一样的,太逆天了,我自己的示例程序就是一样的,不知道他这个是怎么回事,但是好在第二个语法的GUID是正确的,我们可以先搜索这个来确定大致的范围

那我们就是用下面的脚本搜索语法GUID字节序列04 5d 88 8a eb 1c c9 11-9f e8 08 00 2b 10 48 60
import os
def search_bytes_in_file(file_path, target_bytes):
try:
with open(file_path, 'rb') as f:
data = f.read()
return target_bytes in data
except Exception:
return False
def search_folder_for_bytes(folder_path, target_hex_string):
# Convert hex string like "DEADBEEF" to bytes
target_bytes = bytes.fromhex(target_hex_string)
for root, dirs, files in os.walk(folder_path):
for filename in files:
file_path = os.path.join(root, filename)
if search_bytes_in_file(file_path, target_bytes):
print(f"[FOUND] {file_path}")
if __name__ == "__main__":
folder = r"C:\Program Files\Symantec\Symantec Endpoint Protection" # 🔧 Change this
hex_string = "045d888aeb1cc9119fe808002b104860" # 🔧 Change this to your target bytes (no 0x, no spaces)
search_folder_for_bytes(folder, hex_string)
关键问题是这样找出来的也不一定是客户端,因为服务端也会有这个语法GUID
那这样的话,我们就只能用rpcview来逆向这个接口,获取到定义文件了
[
uuid(a3e7e0e5-615c-4355-964f-a58e97cfc695),
version(1.0),
]
interface DefaultIfName
{
long Proc0(
[in]long arg_1,
[in][size_is(arg_1)]byte *arg_2,
[out]long *arg_3,
[out][ref][size_is(, *arg_3)]byte **arg_4,
[out][context_handle] void** arg_5);
long Proc1(
[in][out][context_handle] void** arg_1);
long Proc2(
[in][context_handle] void* arg_1,
[in]long arg_2,
[in][size_is(arg_2)]byte *arg_3);
long Proc3(
[in][context_handle] void* arg_1,
[in]long arg_2,
[in][size_is(arg_2)]byte *arg_3);
long Proc4(
[in][context_handle] void* arg_1,
[in]long arg_2,
[in][size_is(arg_2)]byte *arg_3,
[out]long *arg_4,
[out][ref][size_is(, *arg_4)]byte **arg_5);
long Proc5(
[in][context_handle] void* arg_1,
[in]long arg_2,
[in][size_is(arg_2)]byte *arg_3);
long Proc6(
[in][context_handle] void* arg_1,
[in]long arg_2,
[in][size_is(arg_2)]byte *arg_3,
[in]long arg_4,
[in][size_is(arg_4)]byte *arg_5,
[out]long *arg_6,
[out][ref][size_is(, *arg_6)]byte **arg_7);
}
正好7个函数,和我们前面分析出来的函数数组也是能够对应得上的
但是为什么这里逆向出来的没有binding_handle啊,这对吗这?
感觉还是要找到客户端才行
recatos源码
从网上把recatos的源码下载下来然后把rpcrt4.dll编译出来,我们的示例程序使用动态加载的方式调用我们自己编译出来的rpcrt4.dll,就可以在调试的时候查看源代码了
RpcServerUseProtseqEpW
对于tcp_ip形式的RPC,rpcrt其实就是监听了一个指定端口的socket而已

> rpcrt4.dll!rpcrt4_protseq_ncacn_ip_tcp_open_endpoint(_RpcServerProtseq * protseq, const char * endpoint) Line 1647 C
rpcrt4.dll!RPCRT4_use_protseq(_RpcServerProtseq * ps, const char * endpoint) Line 841 C
rpcrt4.dll!RpcServerUseProtseqEpExW(wchar_t * Protseq, unsigned int MaxCalls, wchar_t * Endpoint, void * SecurityDescriptor, _RPC_POLICY * lpPolicy) Line 1053 C
rpcrt4.dll!RpcServerUseProtseqEpW(wchar_t * Protseq, unsigned int MaxCalls, wchar_t * Endpoint, void * SecurityDescriptor) Line 939 C
RPCServer.exe!main() Line 70 C++
[External Code]
也就是说在调用RpcServerUseProtseqEpW之后,tcp端口就已经开始监听了
RpcServerRegisterIf2 注册接口
所谓的注册,就是把我们添加到一个全局的list中

RpcEpRegisterW 注册接口到end point mapper
这个其实是和\\pipe\\epmapper RPC接口进行通信来进行注册的,最后会看到他调用ndrclientcall2和这个接口进行通信

ncalrpc internal
这个通信机制的内部就是使用命名管道

管道名称就是RpcServerUseProtseqEpW的第三个参数,即endpoint
\\.\pipe\lrpc\MyLocalRpcEndpoint
赛门铁克
000001e4`2778dab8 "{3334ED3B-03FC-43B2-A709-D20C266"
000001e4`2778daf8 "5E1D7}"
他这个nclrpc的管道名称好像是动态生成的,就是一个GUID