Impacket Developer Guide. Part 2. Finding RPC on the system and some words about (in)security
Hello everybody, my name is Michael Zhmailo and I am a penetration testing expert in the CICADA8 team.
This is a continuation of the series of articles about development using Impacket. In the previous part, we looked at how RPC works, learned the basic concepts, and also created a client and server.
In this part I will introduce you to tools that allow you to find running RPC servers on a system, and we will also touch on some RPC server security issues.
If u want to learn RPC basics check first part here
Exploring RPC servers on the system
So, globally, there are several options for finding RPC servers and their methods:
- Finding and exploring IDL files;
- Getting information from epmapper service;
- Analysis of PE files;
- Passive exploring.
Let’s start with the last option.
Passive exploring
The method is based on the fact that the RPC methods that a particular server provides have long been known and studied. Moreover, the necessary wrappers are provided to call these methods. For example, to study potentially interesting functionality, you can refer to the WindowsRpcClients repository. And then select the file whose RPC methods are interesting to you.
For example, in this case we see that inside lsaiso.exe there is an RPC server, and inside the interface with UUID 57cce375–4430–47a6-bb96–2cad0d2fd140 there is a method BCryptIumSignHash().
This method is useful for investigating well-known RPC interfaces. There is even a list of well-known interfaces:
- At the endpoint
\\pipe\lsarpcare: LSARPC (12345778–1234-abcd-ef00–0123456789ab), LSA Directory Services (3919286a-b10c-11d0–9ba8–00c04fd92ef5); \\pipe\samr— SAMR (12345778–1234-abcd-ef00–0123456789ac);\\pipe\atsvc— Task Scheduler (1ff70682–0a51–30e8–076d-740be8cee98b);\\pipe\winreg— Windows Remote Registry (338cd001–2244–31f1-aaaa-900038001003);\\pipe\svcctl— Service Control Manager (367abb81–9844–35f1-ad32–98f038001003);\\pipe\srvsvc— Service Control Manager (4b324fc8–1670–01d3–1278–5a47bf6ee188);- and others…..
Additionally, you can find documentation for a specific protocol on msdn. For example, we can examine the MS-EVEN protocol and find that it operates via the 82273FDC-E32A-18C3–3F78–827929DC23EA interface, and that we need to connect to this RPC server via \pipe\eventlog.
The documentation will also contain a description of the methods, structures and classes that can be used with this RPC server.
If the server uses dynamic endpoints, this will also be documented. For example, as is done in MS-DRSR.
Analysis of PE Files
This method is a bit more complicated, but it is what most of the tools we will talk about later use. In short, it is all based on detecting a running RPC server inside a specific file. The main indicator of this is the import of rpcrt4.dll, or the use of special RPC functions, such as RpcStringBindingCompose(). If we look at it in a little more detail, then inside the PE file of a running RPC server in the .data section there is a symbol GlobalRpcServer, which is a complex structure _RPC_SERVER_T describing this RPC server.
Once you have managed to find RPC_SERVER_T, you can move on to studying the RPC server interfaces. The interfaces are presented as the RPC_INTERFACE_T structure.
Then all that remains is to correctly parse this structure, study all the flags, some parameters responsible for security, and, finally, explore the available RPC methods. It is worth noting that the names of RPC methods are most often extracted through PDB files. You can find a detailed analysis and study of these structures in this blog.
There are quite a few tools that can analyze PE files in this way, I will give a list of them a little later. Now let’s move on to the next method of finding RPC servers.
Gathering information from epmapper service
As you may recall, if an RPC server uses dynamic endpoints, it must register itself with the Endpoint Mapper service, otherwise clients will not be able to connect to it. We can query the EpMapper service and find out the endpoints on which specific RPC interfaces are running. This is a fairly simple method, since it involves using ready-made WINAPI calls, namely RpcMgmtEpEltInqBegin() to begin examining the list of endpoints from EpMapper and RpcMgmtEpEltInqNext() to get the next element of the list.
We could write our own tool for this, but other people have already done the work for us. So I can recommend three tools:
- PortQry — developed by Microsoft engineers;
PortQry.exe -n <HostName> -e 135- RPCDump — dump via C++;
- rpcdump.py — dump via python.
Please note that this will only allow you to get information about RPC servers that use EpMapper. If an RPC server does not use dynamic endpoints, there will be no information about it in EpMapper.
Finding and exploring IDL files
As I said, IDL files are a kind of documentation of the RPC server. IDL files describe the methods provided by the RPC server, they contain information about the arguments received and the values returned. You can easily find IDL files on the web. For example, using the following Github request.
IDL files for specific RPC interfaces are also often distributed by Windows developers themselves. Therefore, once you have determined which RPC protocol you are interested in investigating, try searching for IDL files for it. I also want to recommend you the idl_scraper tool, it allows you to analyze a lot of IDL files in bulk, generating all the information in a CSV file: RPC interface names, UUID values, names of available methods and accepted arguments. This is useful, for example, for fuzzing.
And more…
You can investigate and detect unusual behavior of RPC servers if you pay attention to the ETW event, WireShark traffic, ProcMon logs. For example, there are tools such as RpcInvestigator , rpc_visibility and RPCMon that can help investigate RPC using ETW.
Universal Tools
So, we have studied the basic methods of finding and investigating RPC on our system. As you can see, everything is quite simple. Therefore, there are quite a lot of different tools that make it easier to research RPC. So, let’s start with tools that provide a nice GUI interface and can give a fairly complete picture of the RPC servers on the system.
So, let’s start with tools that provide a nice GUI interface and can give a fairly complete picture of the RPC servers on the system.
- RPCView — works via GlobalRpcServer analysis, supports decompilation of RPC interfaces, can parse special RPC Registration Flags, which can affect the behavior of the RPC server when a client connects. These flags are very important and we will consider them a little later. For example, there is a flag RPC_IF_ALLOW_LOCAL_ONLY, due to which remote clients will not be able to connect to the RPC server. Only local connection.
- rpv + rpv-web — has a nice graphical interface, shows RPC and DCOM servers separately, shows RPC methods OpNum (Operation Number. Each RPC method has its own Opnum and this method can be called not by name, but by Opnum) and their names if you attach the symbol file.
- RpcInvestigator — I find this tool to be one of the most convenient. It supports the ability to dynamically create clients to various RPC servers, and also has a consumer that extracts and analyzes ETW events related to RPC.
- RpcEnum — This tool has integration with the Hydra decompiler, as well as the Neo4j database, which allows you to track the functions that a particular RPC method calls.
However, if you are looking to automate your research, then take a look at these tools.
- NtObjectManager — supports many Powershell cmdlets for working with RPC. Let’s look at the most basic ones
# Get information about RPC in the PE File
C:\> 'C:\Windows\System32\efssvc.dll' | Get-RpcServer
Name UUID Ver Procs EPs Service Running
---- ---- --- ----- --- ------- -------
efssvc.dll df1941c5-fe89-4e79-bf10-463657acf44d 1.0 53 0 EFS False
efssvc.dll 04eeb297-cbf4-466b-8a2a-bfd6a2f10bba 1.0 7 0 EFS False# Get more information about a specific RPC procedure
C:\> $rpcinterfaces ='C:\Windows\System32\efssvc.dll' | Get-RpcServer
C:\> $rpcinterfaces[0].Procedures[0]
Name : EfsRpcOpenFileRaw
Params : {_hProcHandle, FC_BIND_CONTEXT - NdrContextHandleTypeReference - IsOut, IsSimpleRef, FC_C_WSTRING - NdrConformantStringTypeReference - MustSize, MustFree, IsIn, IsSimpleRef, FC_LONG
- NdrSimpleTypeReference - IsIn, IsBasetype}
ReturnValue : FC_LONG - NdrSimpleTypeReference - IsOut, IsReturn, IsBasetype
Handle : FC_BIND_PRIMITIVE - NdrSimpleTypeReference - IsIn
RpcFlags : 0
ProcNum : 0
StackSize : 40
HasAsyncHandle : False
DispatchFunction : 140713075568496
DispatchOffset : 28528
InterpreterFlags : ClientMustSize, HasReturn, HasExtensions
ConstantClientBufferSize : 8
ConstantServerBufferSize : 64# We can also output the parameters of the function
C:\> $rpcinterfaces[0].Procedures[0].Params
Explicit : True
Generic : False
Attributes : IsIn
Type : FC_BIND_PRIMITIVE - NdrSimpleTypeReference
ServerAllocSize : 0
Offset : 0
Name : _hProcHandle
IsIn : True
IsOut : False
IsInOut : False
IsSimpleRef : False
IsPipe : False
Attributes : IsOut, IsSimpleRef
Type : FC_BIND_CONTEXT - NdrContextHandleTypeReference
ServerAllocSize : 0
Offset : 8
Name : p0
IsIn : False
IsOut : True
IsInOut : False
IsSimpleRef : True
....strip....# String binding info
$rpcInterfaces[0].Endpoints.BindingStringYou can also dynamically generate RPC clients code on the fly using Format-RpcClient
PS C:\WINDOWS\system32> $rpcinterfaces ='C:\Windows\System32\efssvc.dll' | Get-RpcServer
PS C:\WINDOWS\system32> $rpcinterfaces
Name UUID Ver Procs EPs Service Running
---- ---- --- ----- --- ------- -------
efssvc.dll df1941c5-fe89-4e79-bf10-463657acf44d 1.0 53 0 EFS False
efssvc.dll 04eeb297-cbf4-466b-8a2a-bfd6a2f10bba 1.0 7 0 EFS False
PS C:\WINDOWS\system32> $rpcinterfaces[1] | Format-RpcClient
...strip...
public int EfsServiceMain_1(int p0, sbyte[] p1, Struct_0 p2, out sbyte p3, out int p4, out sbyte[] p5, out int p6, out sbyte[] p7)
{
_Marshal_Helper @__m = new _Marshal_Helper(CreateMarshalBuffer());
@__m.WriteInt32(p0);
@__m.Write_4(NtCoreLib.Ndr.Marshal.NdrMarshalUtils.CheckNull(p1, "p1"), p0);
@__m.Write_0(p2);
_Unmarshal_Helper @__u = SendReceive(1, @__m);
try
{
p3 = @__u.ReadSByte();
p4 = @__u.ReadInt32();
p5 = @__u.ReadReferent<sbyte[]>(new System.Func<sbyte[]>(@__u.Read_5), false);
p6 = @__u.ReadInt32();
p7 = @__u.ReadReferent<sbyte[]>(new System.Func<sbyte[]>(@__u.Read_6), false);
return @__u.ReadInt32();
}
finally
{
@__u.Dispose();
}
}
...strip...However, you can get the client object directly via Get-RpcClient.
PS C:\WINDOWS\system32> $rpcinterfaces ='C:\Windows\System32\efssvc.dll' | Get-RpcServer
PS C:\WINDOWS\system32> $rpcinterfaces
Name UUID Ver Procs EPs Service Running
---- ---- --- ----- --- ------- -------
efssvc.dll df1941c5-fe89-4e79-bf10-463657acf44d 1.0 53 0 EFS False
efssvc.dll 04eeb297-cbf4-466b-8a2a-bfd6a2f10bba 1.0 7 0 EFS False
PS C:\WINDOWS\system32> $client = $rpcinterfaces[0] | Get-RpcClient
PS C:\WINDOWS\system32> $client
New : _Constructors
NewArray : _Array_Constructors
Connected : False
Endpoint :
ProtocolSequence :
ObjectUuid :
InterfaceId : df1941c5-fe89-4e79-bf10-463657acf44d:1.0
Transport :
DefaultTraceFlags : NoneIt is worth noting that the client will be created in an unconnected state, so it must be connected to the RPC server using Connect-RpcClient .
C:\> $rpcinterfaces[0].Endpoints
UUID Version Protocol Endpoint Annotation
---- ------- -------- -------- ----------
df1941c5-fe89-4e79-bf10-463657acf44d 1.0 ncacn_np \pipe\efsrpc EFS RPC Interface
df1941c5-fe89-4e79-bf10-463657acf44d 1.0 ncalrpc LRPC-a15516611b501688e1 EFS RPC Interface
C:\> Connect-RpcClient $client -stringbinding "ncacn_np:127.0.0.1[\\pipe\\efsrpc]" -AuthenticationLevel PacketPrivacy -AuthenticationType WinNT
C:\> $client
New : _Constructors
NewArray : _Array_Constructors
Connected : True
Endpoint : \Device\Mup\127.0.0.1\pipe\efsrpc
ProtocolSequence : ncacn_np
ObjectUuid :
InterfaceId : df1941c5-fe89-4e79-bf10-463657acf44d:1.0
Transport : NtCoreLib.Win32.Rpc.Transport.RpcNamedPipeClientTransport
DefaultTraceFlags : NoneAll that remains is to make the RPC call
C:\> $client.EfsRpcOpenFileRaw("\\127.0.0.1\test",0)
p0 retval
-- ------
Handle: 00000000-0000-0000-0000-000000000000 - Attributes: 0 53- pe_rpc_if_scraper — With this tool you can parse PE files and extract all the necessary information about the RPC server, including data about the RPC Security Callback. Output example.
"dab.dll": {
"7419cf08-91a7-4afd-8f5e-1dd76de094fd": {
"number_of_functions": 3,
"functions_pointers": [
"@x180081809",
"@x18082eb8",
"@x18083a50"
],
"function_names": [
"s_DabRpcRegisterTriggerConsumer",
"s_DabRpcUnregisterTriggerConsumer",
"s_DabRpcGetLastScheduledRunTime"
],
"role": "server",
"flags": "0x6000000",
"interface_address": "0x180815000"
},
"interface_registration_info": {
"0x180008039": {
"interface_address": "0x180015000",
"flags": "0x21",
"security_callback_addr": "0x180008819",
"has_security_description": false,
"security_callback_info": {
"security_callback_name": "DabApiRpcInterfaceSecuritycb",
"use_call_attributes": true,
"security_callback_checks": [
{
"struct_attribute_name": "RPC_CALL_ATTRIBUTES_V3_M.ProtocolSequence",
"compared_to": 3,
"condition_type": "==",
"signness": "-"
},
{
"struct_attribute_name": "RPC_CALL_ATTRIBUTES_V3_M.NullSession",
"compared_to": "True",
"condition_type": "==",
"signness": "-"
},
{
"struct_attribute_name": "RPC_CALL_ATTRIBUTES_V3_M.AuthenticationLevel",
"compared_to": 6,
"condition_type": "==",
"signness": "-"
},
{
"struct_attribute_name": "RPC_CALL_ATTRIBUTES_V3_M.AuthenticationService",
"compared_to": 10,
"condition_type": "==",
"signness": "-"
}
],
"global_caching_enabled": true
}
}
}If you want to probe RPC remotely, the data set we can get is dramatically reduced. There are literally two options: query the Endpoint Mapper service or brute force UUIDs of known interfaces and then their methods by OpNum. In the case of EpMapper, these tools will help you:
- Nessus
- RpcDump, PortQry, rpcdump.py — I talked about them above
If you want to brute force UUID and OpNum methods, then pay attention to these tools.
# Get a list of UUIDs of interfaces that are accessible without authentication
patator.py dcom_login host=10.0.0.15 port=49152 uuid=FILE0 0=/tmp/uuids.txt ver=FILE1 1=/tmp/vers.txt -t 1 -x ignore:code=-1
# Having determined the UUID of the interface, we can look at the port on which it works
patator.py dcom_login host=10.0.0.15 port=FILE0 0=/tmp/ports.txt uuid=367abb81-9844-35f1-ad32-98f038001003 ver=2.0 -t 1 -x ignore:code=-1
# Finally, you can call methods by OpNum
patator.py dcom_login host=10.0.0.15 port=49158 uuid=12345778-1234-ABCD-EF00-0123456789AB ver=0.0 opnum=FILE0 0=/tmp/opnums.txt -t 1 -x ignore:mesg=rpc_s_access_denied
# If the response is rpc_x_bad_stub_data, then it is possible to call this RPC method anonymously
# Example call of IObjectExporter::ServerAlive2()
patator.py dcom_login host=10.0.0.15 port=135 uuid=99fcfec4-5260-101b-bbcb-00aa0021347a ver=0.0 opnum=5U can read more about IObjectExporter::ServerAlive2() here.
RPC Security
Now you know how to detect running RPC servers on the system and we are about to start writing clients and actively using impacket. However, you should pay attention to one more important thing, this is RPC Security. After all, it is because of the security settings that you will receive the rpc_s_access_denied error, so you need to understand why this happens.
It is worth highlighting endpoint protection and RPC interface protection separately, since several interfaces can operate simultaneously on the same endpoint.
RPC Endpoint Security
RpcServerUseProtseqEp()
There are various methods that can be used to secure the endpoint. The most common is to explicitly specify the handle in the RpcServerUseProtseqEp() function call.
RPC_WSTR pszProtSeq = (RPC_WSTR)L"ncacn_np";
RPC_WSTR pszTCPPort = (RPC_WSTR)L"\\pipe\\hi";
RpcServerUseProtseqEp(
pszProtSeq,
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
pszTCPPort,
NULL
);Note the last parameter. It is NULL, but it is what will be responsible for the Security Descriptor, which determines who can connect to this endpoint. Please note that it makes sense to specify such a parameter only if the RPC server operates via pipes or ALPC, since authentication is not provided for other protocols (for example, with ncacn_ip_tcp).
If the descriptor is NULL, then by default the following users will have write permissions:
SELF— SID of the user on whose behalf the RPC server is running;NT AUTHORITY\ANONYMOUS LOGON;BUILTIN\Administrators;EVERYONE.
In addition, the security of transmitted messages and their integrity are configured at the endpoint level. U can read more about it here.
RPC Filters
Finally, there is a mechanism called RPC Filters that can be used to control and restrict the ability to create RPC endpoints. It works through the Windows Filtering Platform and is available starting with Windows Vista and Server 2008.
This is quite a large mechanism, which is a combination of various settings. For example, we can specify the FWPM_LAYER_RPC_UM flag to filter traffic on the RPC endpoint. There is also the FWPM_LAYER_RPC_EP_ADD flag, which limits the creation of new RPC endpoints. There are many different settings, you can read more about them here.
You can view the currently configured RPC filters like this.
C:\Users\Michael> netsh
netsh> rpc filter
netsh rpc filter> show filterAnd you can create an RPC Filter using the CLI:
rpc filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=f6beaff7-1e19-4fbb-9f8f-b89e2018337c
add filterAnd also with the help of WINAPI, as shown here. For example, we can limit the UUID of the interface (or the entire \pipe\eventlog endpoint) responsible for remote log transmission. We will block MS-EVEN. And when trying to access this functionality, we will get the following error.
C:\Users> wevutil /r:10.10.10.10 qe System /c:1
Failed to open event query.
Access is denied.RPC Filters block PetitPotam, PrinterBug and other Coerce techniques, as well as Lateral movement techniques via RPC/DCOM. We can also test the filter’s operation using rpcmap.py.
# Filter disabled
rpcmap.py 'ncacn_ip_tcp:10.10.10.10[49731]' -uuid 12345678-1234-ABCD-EF00-0123456789AB -brute-opnums -auth-rpc ./user
Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation
Password for MSRPC communication:
[*] Target MGMT interface not available
[*] Bruteforcing UUIDs. The result may not be complete.
Protocol: [MS-RPRN]: Print System Remote Protocol
Provider: spoolsv.exe
UUID: 12345678-1234-ABCD-EF00-0123456789AB v1.0
Opnums 0-64: rpc_x_bad_stub_data
# Filter enabled
rpcmap.py 'ncacn_ip_tcp:10.10.10.10[49731]' -uuid 12345678-1234-ABCD-EF00-0123456789AB -brute-opnums -auth-rpc ./user
Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation
Password for MSRPC communication:
[*] Target MGMT interface not available
[*] Bruteforcing UUIDs. The result may not be complete.
Protocol: [MS-RPRN]: Print System Remote Protocol
Provider: spoolsv.exe
UUID: 12345678-1234-ABCD-EF00-0123456789AB v1.0
Opnums 0-64: rpc_s_access_deniedRPC Interface Security
However, endpoint security is not the only way to secure RPC. RPC provides methods that allow you to secure the interface itself.
RpcServerRegisterIf*()
There are a variety of functions that allow you to register an interface with the Rpc Runtime Library. Some of them accept the following parameters:
SecurityDescriptor— Just like for RPC Endpoint Security, this setting allows you to specify a security descriptor according to which only certain clients will be able to connect to the RPC server. Function example
RPC_STATUS RpcServerRegisterIf3(
[in] RPC_IF_HANDLE IfSpec,
[in, optional] UUID *MgrTypeUuid,
[in, optional] RPC_MGR_EPV *MgrEpv,
[in] unsigned int Flags,
[in] unsigned int MaxCalls,
[in] unsigned int MaxRpcSize,
[in, optional] RPC_IF_CALLBACK_FN *IfCallback,
[in, optional] void *SecurityDescriptor
);ifCallbackFn— This is a special callback called RPC Security Callback. It allows you to register a function that the RPC Runtime Library will call after the client connects. Inside the RPC Security Callback, developers can implement their own client verification logic. For example, checking the authentication level or the service that was used for authentication. U can get information about security callback using pe_rpc_if_scraper. Function example
RPC_STATUS RpcServerRegisterIf2(
RPC_IF_HANDLE IfSpec,
UUID *MgrTypeUuid,
RPC_MGR_EPV *MgrEpv,
unsigned int Flags,
unsigned int MaxCalls,
unsigned int MaxRpcSize,
RPC_IF_CALLBACK_FN *IfCallbackFn
);
//Registration
RPC_STATUS CALLBACK SecurityCallback(RPC_IF_HANDLE hInterface, void* pBindingHandle)
{
// some custom logic
// ...
// ...
return RPC_S_OK; // Yeah! Everything is ok
}
RPC_STATUS rpcStatus = RpcServerRegisterIf2(
MyIFace_v1_0_s_ifspec,
NULL,
NULL,
RPC_IF_ALLOW_LOCAL_ONLY,
RPC_C_LISTEN_MAX_CALLS_DEFAULT,
(unsigned)-1,
SecurityCallback
);Flags— These are special RPC Registration Flags that are responsible for access of RPC clients. For example, you can specifyRPC_IF_ALLOW_CALLBACK_WITH_NO_AUTH. This will result in the RPC Security Callback being called when any client connects, even if it has not been authenticated. Or setRPC_IF_ALLOW_LOCAL_ONLY. In this case, no remote client will be able to connect to the RPC server, only a local one. U can read more here.
Authenticated Bindings
You can configure the RPC interface to accept only authenticated clients, i.e., clients whose credentials can be verified using an Authentication Provider. To do this, you need to call the RpcServerRegisterAuthInfo() function on the server.
RPC_STATUS rpcStatus = RpcServerRegisterAuthInfo(
pszSpn, // Server SPN
RPC_C_AUTHN_WINNT, // using NTLM
NULL,
NULL
);And on the client, specify authentication support by calling the RpcBindingSetAuthInfoEx() function.
RPC_STATUS status = RpcBindingSetAuthInfoEx(
hExplicitBinding,
pszHostSPN, // server spn
RPC_C_AUTHN_LEVEL_PKT,
RPC_C_AUTHN_WINNT, // using NTLM
NULL,
RPC_C_AUTHZ_NAME,
&secQos
);Pay attention to the last parameter — secQos. This is the so-called QUALITY OF SERVICE. With its help, the client can configure some important security parameters. For example, the maximum level of impersonation that is ready to give to the server.
typedef struct _SECURITY_QUALITY_OF_SERVICE {
DWORD Length;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
SECURITY_CONTEXT_TRACKING_MODE ContextTrackingMode;
BOOLEAN EffectiveOnly;
} SECURITY_QUALITY_OF_SERVICE, *PSECURITY_QUALITY_OF_SERVICE;You can find the RPC server code that sets up both the RPC Security Callback and Authenticated Bindings here.
RPC Impersonation
If the RPC server configures Authenticated Bindings and the client supports this authenticated binding, then it becomes possible to use the impersonation mechanism on the server-side. To get the client token we need to call RpcImpersonateClient(). However, we should not forget about the token impersonation levels. Let me remind you that the client can set the level of impersonation through the QOS structure in the RpcBindingSetAuthInfoEx() function. You can get the token level using this code.
HANDLE GetRpcClientToken(RPC_BINDING_HANDLE BindingHandle)
{
RPC_STATUS status = RpcImpersonateClient(BindingHandle);
if (RPC_S_OK != status)
{
cout << "RpcImpersonateClient failed " << TranslateCode(status) << endl;
return nullptr;
}
HANDLE threadToken = nullptr;
BOOL success = OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &threadToken);
RpcRevertToSelfEx(BindingHandle);
if (!success)
{
cout << "OpenThreadToken failed " << TranslateCode(GetLastError()) << endl;
return nullptr;
}
return threadToken;
}
wstring GetImpersonationLevel(HANDLE TokenHandle)
{
wstring impersonationLevel = L"not found";
SECURITY_IMPERSONATION_LEVEL levelEnum;
DWORD returnLength = 0;
if (!GetTokenInformation(TokenHandle, TokenImpersonationLevel, &levelEnum, sizeof(SECURITY_IMPERSONATION_LEVEL), &returnLength))
{
// Avoid throwing exception inside security callback and log error instead
cout << "GetTokenInformation for TokenImpersonationLevel failed " << TranslateCode(GetLastError()) << endl;
return impersonationLevel;
}
switch (levelEnum)
{
case SecurityAnonymous:
impersonationLevel = L"SecurityAnonymous";
break;
case SecurityIdentification:
impersonationLevel = L"SecurityIdentification";
break;
case SecurityImpersonation:
impersonationLevel = L"SecurityImpersonation";
break;
case SecurityDelegation:
impersonationLevel = L"SecurityDelegation";
break;
}
return impersonationLevel;
}
void LogTokenInfo(RPC_BINDING_HANDLE BindingHandle)
{
HANDLE threadToken = GetRpcClientToken(BindingHandle);
if (nullptr == threadToken)
return;
wstring impersonationLevel = GetImpersonationLevel(threadToken);
wcout << L"impersonation level: " << impersonationLevel << endl;
CloseHandle(threadToken);
}You can see the implementation of the server and client and successful impersonation here.
Conclusion
Thanks for reading my article! In it I looked at how to probe RPC servers on a system, and we also looked at some RPC security issues. I encourage you to dive deeper and read the csandker.io blog, which has covered security issues in detail and has a lot of interesting code!
I have some good news for you. In the next part, we’ll start programming with impacket, write our own clients for various protocols, and learn a lot of new interesting things! Follow us on X to stay up to date with the new article!
