Skip to content

Instantly share code, notes, and snippets.

@valinet
Last active May 17, 2026 15:40
Show Gist options
  • Select an option

  • Save valinet/5e380b857a3800309248c54897316c95 to your computer and use it in GitHub Desktop.

Select an option

Save valinet/5e380b857a3800309248c54897316c95 to your computer and use it in GitHub Desktop.
Launch processes from SSH into the logged in user session
// cl /nologo main.c kernel32.lib user32.lib advapi32.lib wtsapi32.lib userenv.lib /O1 /GS- /Gs9999999 /GF /kernel /link /ENTRY:main /NODEFAULTLIB /SUBSYSTEM:windows /NOCOFFGRPINFO /ALIGN:16 /MERGE:.rdata=.text /MERGE:.pdata=.text /OUT:local_spawn.exe
#include <Windows.h>
#include <strsafe.h>
#include <TlHelp32.h>
#include <wtsapi32.h>
#include <UserEnv.h>
#include <Lmcons.h>
#ifndef _DEBUG
#pragma comment(linker, "/NODEFAULTLIB")
#pragma comment(linker, "/ENTRY:main")
#endif
#pragma comment(lib, "Kernel32.lib")
#pragma comment(lib, "User32.lib")
#pragma comment(lib, "Advapi32.lib")
#pragma comment(lib, "Wtsapi32.lib")
#pragma comment(lib, "Userenv.lib")
#ifndef _DEBUG
inline BOOL CheckWin32Impl(BOOL result, const char* expr, const char* file, int line) {
DWORD err = GetLastError();
if (err != ERROR_SUCCESS) {
ExitProcess(err);
}
if (!result)
ExitProcess(result);
return result;
}
#else
inline INT64 CheckWin32Impl(INT64 result, const char* expr, const char* file, int line) {
DWORD err = GetLastError();
if (err != ERROR_SUCCESS) {
LPSTR msg = NULL;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&msg, 0, NULL);
char buf[1024];
wsprintfA(buf, "[%s:%d] %s failed (err=%lu): %s\n",
file, line, expr, err,
msg ? msg : "<no message>");
OutputDebugStringA(buf);
printf(buf);
if (msg) LocalFree(msg);
ExitProcess(err);
}
if (!result)
ExitProcess(result);
return result;
}
#endif
#define CHECK(expr) (SetLastError(ERROR_SUCCESS), CheckWin32Impl((expr), #expr, __FILE__, __LINE__))
#define IsEqualToLiteralW(s, literal) \
(RtlCompareMemory((s), L"" literal, sizeof(L"" literal)) == sizeof(L"" literal))
inline BOOL VnPatchIAT(HMODULE hMod, PSTR libName, PSTR funcName, uintptr_t hookAddr)
{
// Increment module reference count to prevent other threads from unloading it while we're working with it
HMODULE module;
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)hMod, &module)) return FALSE;
// Get a reference to the import table to locate the kernel32 entry
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)module;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((uintptr_t)module + dos->e_lfanew);
PIMAGE_IMPORT_DESCRIPTOR importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((uintptr_t)module +
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
// In the import table find the entry that corresponds to kernel32
BOOL found = FALSE;
while (importDescriptor->Characteristics && importDescriptor->Name) {
PSTR importName = (PSTR)((PBYTE)module + importDescriptor->Name);
found = TRUE;
char* pA = importName, * pB = libName;
while (*pB) {
if (*pA++ != *pB++ && *(pA - 1) != (*(pB - 1) - 32) && (*(pA - 1) - 32) != *(pB - 1)) {
found = FALSE;
break;
}
}
if (found)
break;
importDescriptor++;
}
if (!found) {
FreeLibrary(module);
return FALSE;
}
// From the kernel32 import descriptor, go over its IAT thunks to
// find the one used by the rest of the code to call GetProcAddress
PIMAGE_THUNK_DATA oldthunk = (PIMAGE_THUNK_DATA)((PBYTE)module + importDescriptor->OriginalFirstThunk);
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((PBYTE)module + importDescriptor->FirstThunk);
while (thunk->u1.Function) {
PROC* funcStorage = (PROC*)&thunk->u1.Function;
BOOL bFound = FALSE;
if (oldthunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)
{
bFound = (!(*((WORD*)&(funcName)+1)) && IMAGE_ORDINAL32(oldthunk->u1.Ordinal) == (LONG64)funcName);
}
else
{
PIMAGE_IMPORT_BY_NAME byName = (PIMAGE_IMPORT_BY_NAME)((uintptr_t)module + oldthunk->u1.AddressOfData);
if ((*((WORD*)&(funcName)+1))) {
bFound = TRUE;
char* pA = (char*)byName->Name, * pB = funcName;
while (*pB) {
if (*pA++ != *pB++ && *(pA - 1) != (*(pB - 1) - 32) && (*(pA - 1) - 32) != *(pB - 1)) {
bFound = FALSE;
break;
}
}
}
}
// Found it, now let's patch it
if (bFound) {
// Get the memory page where the info is stored
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(funcStorage, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
// Try to change the page to be writable if it's not already
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &mbi.Protect)) {
FreeLibrary(module);
return FALSE;
}
// Store our hook
*funcStorage = (PROC)hookAddr;
// Restore the old flag on the page
DWORD dwOldProtect;
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect);
// Profit
FreeLibrary(module);
return TRUE;
}
thunk++;
oldthunk++;
}
FreeLibrary(module);
return FALSE;
}
BOOL WINAPI GetComputerNameExW_Hook(COMPUTER_NAME_FORMAT NameType, LPWSTR lpBuffer, LPDWORD nSize) {
SetLastError(ERROR_SUCCESS);
BOOL rv = GetComputerNameExW(NameType, lpBuffer, nSize);
if (rv && GetLastError() != ERROR_SUCCESS)
SetLastError(ERROR_SUCCESS);
return rv;
}
int main(int argc, char** argv) {
PROCESS_INFORMATION pi;
STARTUPINFOW si;
for (int i = 0; i < sizeof(si); i = i + 2)
((char*)(&si))[i] = 0;
for (int i = 1; i < sizeof(si); i = i + 2)
((char*)(&si))[i] = 0;
si.cb = sizeof(si);
DWORD dwSessionId = 0;
CHECK(ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId));
if (dwSessionId) {
wchar_t* pCommandLine = GetCommandLineW();
while (*pCommandLine && *pCommandLine++ != L' ');
pCommandLine++;
INPUT i = { .type = INPUT_MOUSE };
SendInput(1, &i, sizeof(i));
CHECK(CreateProcessW(NULL, pCommandLine, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi));
ExitProcess(GetLastError());
}
// This fixes CreateEnvironmentBlock returning 203
HMODULE hProfApi = LoadLibraryW(L"profapi.dll");
if (hProfApi)
CHECK(VnPatchIAT(hProfApi, "api-ms-win-core-sysinfo-l1-1-0.dll", "GetComputerNameExW", (uintptr_t)GetComputerNameExW_Hook));
PROCESSENTRY32W entry;
entry.dwSize = sizeof(PROCESSENTRY32W);
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
CHECK((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) != INVALID_HANDLE_VALUE);
CHECK(Process32FirstW(hSnapshot, &entry));
HANDLE hProcess = INVALID_HANDLE_VALUE;
do {
if (IsEqualToLiteralW(entry.szExeFile, L"winlogon.exe")) {
CHECK((hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID)) != INVALID_HANDLE_VALUE);
break;
}
} while (CHECK(Process32NextW(hSnapshot, &entry)));
HANDLE hToken = INVALID_HANDLE_VALUE;
CHECK(OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &hToken));
CHECK(ImpersonateLoggedOnUser(hToken));
WTS_SESSION_INFOW* wsi = NULL;
DWORD kWsi = 0;
CHECK(WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &wsi, &kWsi));
DWORD dwActiveSessionId = -1;
for (unsigned i = 0; i < kWsi; ++i) {
if (wsi[i].State == WTSActive) {
dwActiveSessionId = wsi[i].SessionId;
break;
}
}
if (dwActiveSessionId == -1)
dwActiveSessionId = WTSGetActiveConsoleSessionId();
CHECK(dwActiveSessionId != -1);
HANDLE hImpersonationToken = INVALID_HANDLE_VALUE;
CHECK(WTSQueryUserToken(dwActiveSessionId, &hImpersonationToken));
LPVOID lpEnvironment = NULL;
CHECK(CreateEnvironmentBlock(&lpEnvironment, hImpersonationToken, FALSE));
HANDLE hOwnToken = INVALID_HANDLE_VALUE;
CHECK(OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, FALSE, &hOwnToken));
TOKEN_PRIVILEGES tokPrivs;
tokPrivs.PrivilegeCount = 1;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
CHECK(LookupPrivilegeValueW(NULL, L"SeAssignPrimaryTokenPrivilege", &tokPrivs.Privileges[0].Luid));
CHECK(AdjustTokenPrivileges(hOwnToken, FALSE, &tokPrivs, sizeof(tokPrivs), NULL, NULL));
CHECK(LookupPrivilegeValueW(NULL, L"SeIncreaseQuotaPrivilege", &tokPrivs.Privileges[0].Luid));
CHECK(AdjustTokenPrivileges(hOwnToken, FALSE, &tokPrivs, sizeof(tokPrivs), NULL, NULL));
//wchar_t* pCommandLine = GetCommandLineW();
//while (*pCommandLine && *pCommandLine++ != L' ');
//pCommandLine++;
// wchar_t* p = (wchar_t*)lpEnvironment;
// int i = 0;
// while (*p) {
// wprintf(L"[%d] %s\n", i++, p);
// p += wcslen(p) + 1;
// }
// wprintf(L"(total: %d vars)\n", i);
// CREATE_BREAKAWAY_FROM_JOB is essential => https://www.sysadmins.lv/retired-msft-blogs/alejacma/createprocessasuser-fails-with-error-5-access-denied-when-using-jobs.aspx
CHECK(CreateProcessAsUserW(hImpersonationToken, NULL, GetCommandLineW(), NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_BREAKAWAY_FROM_JOB, lpEnvironment, NULL, &si, &pi));
ExitProcess(GetLastError());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment