//nefariousplan

CVE-2025-47987: The CredSSP PoC Documents A Project That Does Not Exist

patterns

cve

proof of concept

The vuln scanner watching new GitHub repositories for CVE-named PoCs fired on May 4, 2026, on kaleth4/CVE-2025-47987. The repo holds three files: a README.md that is the NVD entry pasted in Spanish, a Cpp.md that documents a project layout with files the repository does not contain, and an Exploit.c that is a refactor of an August 2025 PoC by another author, with no attribution. The three artifacts do not reference each other. No human assembled them.

The bug Microsoft patched

CVE-2025-47987 is a heap-based buffer overflow in tspkg.dll, the Terminal Services security package that backs the TSSSP provider used by CredSSP for RDP authentication. The function at fault is TSCreateKerbCertLogonBuffer. The researcher whose name is on the only working analysis, who publishes as kryptoenix, distills the bug into two lines:

totalLength = (domain->Length + password->Length + username->Length + 93) & 0xFFFFFFF8;
__totalLength = totalLength + certLen;

certLen is a caller-supplied 32-bit value pulled from a KERB_CERTIFICATE_LOGON-shaped buffer the client hands to AcquireCredentialsHandleW. The sum on the first line is bounded by a few small string lengths plus a fixed constant; the addition on the second line is unconstrained. A certLen near 0xFFFFFFE0 wraps __totalLength down into the single-byte range. The function then heap-allocates __totalLength bytes and memcpys the certificate data into it. The certificate is the size that was added; the allocation is the size that wrapped. The write past the end happens inside lsass.exe.

The patch ships in the July 2025 update cycle for every supported Windows from Server 2008 forward. CVSS 7.8 with C:H/I:H/A:H, local authentication required, no user interaction. Microsoft's advisory is one sentence and adds no further detail.

What the PoC actually demonstrates

The kryptoenix repository, Kryptoenix/CVE-2025-47987_PoC, was created on August 22, 2025, six weeks after the patch. The script builds the malformed Kerberos certificate-logon buffer at the offsets the function expects, fills a 0xFFFFFF00-byte certificate body with copies of the address of WinExec as a heap spray, wraps the spray in a 44-byte CSP header, and triggers the bug:

status = AcquireCredentialsHandleW(
    NULL,
    (LPWSTR)L"TSSSP",
    SECPKG_CRED_OUTBOUND,
    NULL,
    buf,
    NULL, NULL,
    &credHandle, &expiry);

The script's success condition is one SSPI error code:

else if (status == SEC_E_INTERNAL_ERROR) {
    printf("\n[+] PoC succeded! Enjoy the crash >:D\n");
}

Enjoy the crash is the author saying explicitly what the exploit achieves. The accompanying blog post is more explicit: the author observes that "the exploitation stage requires better expertise in Windows heap internals," and signs off with the line "crash lsass.exe!" The PoC crashes lsass.exe. It does not elevate.

That is the public state of art for CVE-2025-47987. Ten months after the patch, the demonstrated impact is an lsass crash on the local machine, which is the A:H in the CVSS vector. The C:H and the I:H are advisory. Nobody has published a working privilege escalation.

The second PoC is the first PoC

kaleth4/CVE-2025-47987 was created on May 4, 2026. Its Exploit.c is the kryptoenix exploit script with cosmetic edits.

The buffer layout is identical. kryptoenix's offsets are written by hand:

size_t offsetUser = 0x48; // header is fixed 0x48 bytes
*(uint64_t*)(buf + 0x00) = 13ULL;        // MessageType
*(uint64_t*)(buf + 0x08) = usernameLen;
*(uint64_t*)(buf + 0x10) = (uint64_t)offsetUser;
// ...
*(uint32_t*)(buf + 0x38) = (uint32_t)0x1337; // :)
*(uint32_t*)(buf + 0x3C) = (uint32_t)certBlobSize;
*(uint32_t*)(buf + 0x40) = (uint32_t)offsetCert;

kaleth4's same offsets are wrapped in a typedef struct:

#pragma pack(push, 1)
typedef struct _KERB_CERT_LOGON_HEADER {
    uint64_t MessageType;
    uint64_t UserNameLength;
    uint64_t UserNameOffset;
    uint64_t PasswordLength;
    uint64_t PasswordOffset;
    uint64_t DomainNameLength;
    uint64_t DomainNameOffset;
    uint32_t CSPDataTag;     // 0x1337
    uint32_t CSPDataSize;
    uint32_t CSPDataOffset;
} KERB_CERT_LOGON_HEADER;
#pragma pack(pop)

Same fields, same widths, same order, same 0x1337 magic at offset 0x38. Same MessageType = 13. Same TSSSP security package name on the call to AcquireCredentialsHandleW. Same 0xFFFFFF00-byte certificate spray filled with WinExec pointers. Same SEC_E_INTERNAL_ERROR success check. kaleth4 swapped malloc for VirtualAlloc and removed the intermediate BuildFakeCspData wrapper, both cosmetic. The function name BuildKerbCertLogonBuffer became BuildExploitPayload.

The success message changed:

// kryptoenix:
printf("\n[+] PoC succeded! Enjoy the crash >:D\n");

// kaleth4:
printf("[+] ÉXITO: Error interno detectado (Posible corrupción de memoria).\n");

The error code that triggers both printfs is SEC_E_INTERNAL_ERROR. kaleth4 translated Enjoy the crash into Posible corrupción de memoria. The underlying outcome is identical. The only edit was tonal.

kryptoenix is credited zero times in the kaleth4 repository.

The Cpp.md describes a project that does not exist

Cpp.md is the second file in the kaleth4 repo. It opens by describing the project layout:

.
├── main.cpp              # Implementación en C++ (SSPI/Windows API)
├── exploit.go            # Versión alternativa para automatización de pruebas
├── docs/
│   ├── TECHNICAL_ANALYSIS.md    # Análisis detallado de offsets y estructuras
│   ├── MEMORY_LAYOUT.md         # Diagramas de memoria del heap
│   └── MITIGATION.md            # Estrategias de mitigación
├── README.md
└── LICENSE

main.cpp is not in the repository. exploit.go is not in the repository. docs/TECHNICAL_ANALYSIS.md is not in the repository, and neither is the docs/ directory. MEMORY_LAYOUT.md is not in the repository. MITIGATION.md is not in the repository. LICENSE is not in the repository.

The file the repository actually contains is Exploit.c. Cpp.md mentions it once, in a build instruction that names main.cpp.

The same file documents command-line options:

### Modo Verbose (Debugging)
./poc.exe --verbose

### Especificar Tamaño de Heap
./poc.exe --heap-size 0x80000000

### Modo Dry-Run (Sin Trigger)
./poc.exe --dry-run

Exploit.c has no argc parameter and no argument parsing. None of the three flags exist in the code that ships in the repository. --heap-size 0x80000000 is half the size the code actually allocates, which is hardcoded at 0xFFFFFF00. --dry-run describes a behavior the code does not implement.

The file is dated:

**Última Actualización**: Enero 2025

CVE-2025-47987 was published on July 8, 2025. The repository was created on May 4, 2026. The documentation's "last updated" claim is six months older than the CVE it documents and sixteen months older than the repository it lives in.

README.md, the third file, is the NVD entry restated in Spanish. It carries the date 2025-04-05, three months before the CVE was published. It does not reference Exploit.c. It does not reference Cpp.md. It does not reference the project layout. It does not exist as part of a project; it exists as a standalone CVE summary that happens to share a directory with two other artifacts.

The three files in this repository describe three different things. None of them describes the others. The README is about a CVE. The Cpp.md is about a project. The Exploit.c is somebody else's code. The seam between the three is the directory listing.

Phantom project documentation

This is the operational signature: a repository whose documentation describes file names, command-line flags, and a release history that do not match what the repository actually contains. The README accurately summarizes the CVE because the model was prompted to summarize a CVE. The Cpp.md describes a plausible C++ research project because the model was prompted to describe a C++ research project. The Exploit.c works because the model was prompted to refactor a script someone else wrote. Each artifact, taken alone, is internally consistent. No artifact references any other artifact because no prompt asked the model to make them consistent with each other.

That is the form of a research repository assembled by an actor who never reviewed the assembled result. The dates pre-date the CVE because the model invented the dates without checking. The CLI flags do not exist because the model invented the flags without looking at the code. The main.cpp filename in the project layout does not match the Exploit.c filename on disk because the model named the canonical file in the project layout from its training distribution, not from ls. None of these tells require code reading; they require reading.

placeholder-poc is the family this exhibit belongs to. The kindred case is the InSAT CVE-2026-22553 script, where the vulnerable form field is marked with two adjacent comments reading # Common vulnerable field name (adjust if needed) and # placeholder field. In that case the placeholder lives in the script. In the kaleth4 case, the script is fine; it crashes lsass because kryptoenix's script crashes lsass. The placeholder lives in everything around the script. The README places it in time, dating itself before the CVE existed. The Cpp.md places it in space, describing a directory that does not exist next to a file the actual directory contains. The repository's existence as a CVE-named asset is the only fact about it that is not a placeholder.

A scanner watching GitHub for new CVE-numbered repositories does not read documentation for coherence. It reads file names and CVE-shaped strings. The kaleth4 repository scores as a PoC because it has a .c file, a CVE- in the path, and a README that mentions the CVE identifier. The repository scores as new because it was created twenty days ago. By the scanner's grammar, it is a fresh exploit drop for a high-severity Microsoft EoP. By the genre's grammar, it is a fragment from a prompt session.

What the defender is owed

The SOC analyst who watches PoC feeds for CVE-2025-47987 is owed three statements. The first is that the public state of exploitation for this bug is, ten months on, the same as it was on August 22, 2025: a crash of lsass.exe, achieved by passing a four-gigabyte certificate through AcquireCredentialsHandleW("TSSSP"), with no demonstrated path to code execution. The second is that the newest PoC in the wild is the oldest PoC in the wild, refactored. The third is that the scanner-level signal "new PoC dropped for CVE-2025-47987" is, in this case, the signal "a CVE-shaped directory listing dropped on GitHub" and nothing further.

The patch is not optional. CVSS 7.8 with C:H/I:H/A:H is an allegation Microsoft has not retracted, and lsass corruption in a context where the attacker controls the input bytes is a foundation other researchers will eventually build on. The post-patch builds are listed in MSRC's advisory and have been since July 8, 2025. What the defender is owed is the distinction between "this CVE has a working exploit in the wild" and "this CVE has a directory listing in the wild." For CVE-2025-47987, ten months in, only the second is true.

PoC: kaleth4/CVE-2025-47987, Kryptoenix/CVE-2025-47987_PoC

The repository's three files do not reference each other. A CVE number was the seam.