此篇文章结合学到的知识做个总结
微软有个api为AddSecurityPackage,可以添加ssp,这里就看下其的实现逻辑,这里从将从公开这个 API 的 secur32.dll 这个 DLL 文件开始,主要为本人进行了修改以及自己去复现的总结
在 Ghidra 打开这个 DLL 文件,这实际上只是一个调用 sspcli.dll 的包装器
我们这边主要是看sspicli.dll的实现逻辑是调用了NdrClientCall3
函数,注意这个函数就是我们接下来要调用rpc调用进行模拟的需要的函数
在调用 NdrClientCall3 时,我们发现传递了以下参数:
这里给出了一个值是3 的 nProcNum 参数,如果我们深入研究 sspirpc_ProxyInfo 结构体,就会发现 RPC 接口的 UUID 为 4f32adc8-6052-4a04-8701-293ccf2096f0:
下面就是18001e2c0指向的是18001e2c0
然后我们再看是哪个函数对这个接口做了调用,看到一个很可疑的程序
下面可以看到我们将Proc3_SspirCallrpc的接口的参数给拿出来了
想要知道函数调用的参数,那么其实就是需要去动态调试了,这里就不一一叙说,此处借鉴网图
接下来我们来看下xpn老师的rpc源码
前期主要是根据上面的那张图去构造rpc的packet,然后下面主要就是去绑定rpc,然后模拟AddSecurityPackage函数的调用
#define SECURITY_WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <subauth.h>
#include <sspi.h>
#include <Dbghelp.h>
#include "sspi_h.h"
int main(int argc, char **argv) {
RPC_STATUS status;
UNICODE_STRING packageName;
UWORD packetLen = 0;
unsigned char* pszStringBinding = NULL;
unsigned long ulCode;
unsigned long long unk1;
unsigned char rpcPacket[0x2000];
long out1 = 0, out2 = 0;
void* out3 = (void*)0;
struct Struct_144_t out4;
printf("\nAddSecurityPackage Raw RPC Example... by @_xpn_\n\n");
if (argc != 2) {
printf("Usage: %s PACKAGE_PATH\n");
return 1;
}
printf("[*] Building RPC packet\n");
// Init RPC packet
memset(&packageName, 0, sizeof(packageName));
memset(rpcPacket, 0, sizeof(rpcPacket));
// Build DLL to be loaded by lsass
packageName.Length = strlen(argv[1]) * 2;
packageName.MaximumLength = (strlen(argv[1]) * 2) + 2;
mbstowcs((wchar_t*)(rpcPacket + 0xd8), argv[1], (sizeof(rpcPacket) - 0xd8) / 2);
packetLen = 0xd8 + packageName.MaximumLength;
// Complete RPC packet fields
*(unsigned long long*)rpcPacket = 0xc4; // ??
*(unsigned short*)(rpcPacket + 2) = packetLen; // Length of packet
*(unsigned long long*)((char*)rpcPacket + 8) = GetCurrentProcessId(); // Process ID
*(unsigned long long*)((char*)rpcPacket + 16) = GetCurrentThreadId(); //Thread ID
*(unsigned long long*)((char*)rpcPacket + 0x28) = 0x0b; // RPC call ID
*(void**)((char*)rpcPacket + 0xd0) = &unk1; // ??
// Copy package name into RPC packet
memcpy(rpcPacket + 0x40, &packageName, 8);
*(unsigned long long*)((char*)rpcPacket + 0x48) = 0xd8; // Offset to unicode ssp name
// Create the RPC connection string
//主要为绑定rpc通道
status = RpcStringBindingCompose(NULL,
(unsigned char*)"ncalrpc",
NULL,
(unsigned char*)"lsasspirpc",
NULL,
&pszStringBinding);
if (status) {
return 1;
}
// Create RPC handle
printf("[*] Connecting to lsasspirpc RPC service\n");
//该 RpcBindingFromStringBinding函数从绑定句柄的字符串表示返回绑定句柄
status = RpcBindingFromStringBinding(pszStringBinding, &default_IfHandle);
if (status) {
return 1;
}
memset(&out4, 0, sizeof(out4));
RpcTryExcept
{
// Create our RPC context handle
printf("[*] Sending SspirConnectRpc call\n");
long ret = Proc0_SspirConnectRpc((unsigned char *)NULL, 2, &out1, &out2, &out3);
// Make the "AddSecurityPackage" call directly via RPC
printf("[*] Sending SspirCallRpc call\n");
//此处函数就是对NdrClientCall3函数的封装
ret = Proc3_SspirCallRpc(out3, packetLen, rpcPacket, &out2, (unsigned char **)&out3, &out4);
}
RpcExcept(1)
{
ulCode = RpcExceptionCode();
if (ulCode == 0x6c6) {
printf("[*] Error code 0x6c6 returned, which is expected if DLL load returns FALSE\n");
}
else {
printf("[!] Error code %x received\n", ulCode);
}
}
RpcEndExcept
return 0;
}
//下面的函数是为了满足链接需要而写的,没有的话会出现链接错误
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);
}
如下图所示就是在构造rpc的包
为了找出SSPI RPC服务器在LSASS过程中使用的具体端点,我们可以对sspisrv.dll进行反向工程。在导出的函数SspiSrvInitialize()中,我们看到如下调用:
RpcServerUseProtseqEpW(L"ncalrpc", 0xAu, L"lsasspirpc", 0);
所述 RpcServerUseProtseqEp函数告诉RPC运行时库使用具有指定端点组合指定的协议序列,用于接收远程过程调用
Protseq
指向要在RPC运行时库中注册的协议序列的字符串标识符的指针。
MaxCalls
ncacn_ip_tcp协议序列的积压队列长度。所有其他协议序列将忽略此参数。使用RPC_C_PROTSEQ_MAX_REQS_DEFAULT指定默认值。请参阅备注。
Endpoint
指向用于为Protseq参数中指定的协议序列创建绑定的端点地址信息的指针。
SecurityDescriptor
指向为安全子系统提供的可选参数的指针。仅用于ncacn_np和ncalrpc协议序列。所有其他协议序列将忽略此参数。不建议在端点上使用安全描述符以使服务器安全。此参数未出现在此API的DCE规范中
sspisrv.dll的导出函数SspiSrvInitialize如下所示,可以看到下面调用了RpcServerUseProtseqEpW,RpcServerUseProtseqEp函数告诉RPC运行时库使用具有指定端点组合指定的协议序列,用于接收远程过程调用
,下面就可以看到lsasspirpcSSPI RPC服务器在LSASS过程中使用的具体端点就是lsasspirpc,所以上面源码中调用RpcStringBindingCompose使用的就是lsasspirpc,sspisrv.dll是LSA SSPI RPC interface DLL
然后我们可以查看Proc0_SspirConnectRpc和Proc3_SspirCallRpc就是对NdrClientCall3函数的封装
long Proc0_SspirConnectRpc(
/* [string][unique][in] */ unsigned char *arg_1,
/* [in] */ long arg_2,
/* [out] */ long *arg_3,
/* [out] */ long *arg_4,
/* [context_handle][out] */ void **arg_5)
{
CLIENT_CALL_RETURN _RetVal;
_RetVal = NdrClientCall3(
( PMIDL_STUBLESS_PROXY_INFO )&DefaultIfName_ProxyInfo,
0,
0,
arg_1,
arg_2,
arg_3,
arg_4,
arg_5);
return ( long )_RetVal.Simple;
}
这里再来看下DefaultIfName_ProxyInfo结构体,该结构包含有关远程接口描述的信息
static const MIDL_STUBLESS_PROXY_INFO DefaultIfName_ProxyInfo =
{
&DefaultIfName_StubDesc,
sspi__MIDL_ProcFormatString.Format,
DefaultIfName_FormatStringOffsetTable,
(RPC_SYNTAX_IDENTIFIER*)&_RpcTransferSyntax,
2,
(MIDL_SYNTAX_INFO*)DefaultIfName_SyntaxInfo
};
其实就是不断的包含,最后我们定位到下面的位置,第一个就是我们上面找到的的uuid的接口