-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 NEFARIOUSPLAN-CANONICAL-V1 {"body_md":"## The bug Microsoft patched\n\n`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:\n\n```c\ntotalLength = (domain->Length + password->Length + username->Length + 93) & 0xFFFFFFF8;\n__totalLength = totalLength + certLen;\n```\n\n`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 `memcpy`s 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`.\n\nThe 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.\n\n## What the PoC actually demonstrates\n\nThe 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:\n\n```c\nstatus = AcquireCredentialsHandleW(\n NULL,\n (LPWSTR)L\"TSSSP\",\n SECPKG_CRED_OUTBOUND,\n NULL,\n buf,\n NULL, NULL,\n &credHandle, &expiry);\n```\n\nThe script's success condition is one SSPI error code:\n\n```c\nelse if (status == SEC_E_INTERNAL_ERROR) {\n printf(\"\\n[+] PoC succeded! Enjoy the crash >:D\\n\");\n}\n```\n\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.\n\nThat 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.\n\n## The second PoC is the first PoC\n\n`kaleth4/CVE-2025-47987` was created on May 4, 2026. Its `Exploit.c` is the kryptoenix exploit script with cosmetic edits.\n\nThe buffer layout is identical. kryptoenix's offsets are written by hand:\n\n```c\nsize_t offsetUser = 0x48; // header is fixed 0x48 bytes\n*(uint64_t*)(buf + 0x00) = 13ULL; // MessageType\n*(uint64_t*)(buf + 0x08) = usernameLen;\n*(uint64_t*)(buf + 0x10) = (uint64_t)offsetUser;\n// ...\n*(uint32_t*)(buf + 0x38) = (uint32_t)0x1337; // :)\n*(uint32_t*)(buf + 0x3C) = (uint32_t)certBlobSize;\n*(uint32_t*)(buf + 0x40) = (uint32_t)offsetCert;\n```\n\nkaleth4's same offsets are wrapped in a `typedef struct`:\n\n```c\n#pragma pack(push, 1)\ntypedef struct _KERB_CERT_LOGON_HEADER {\n uint64_t MessageType;\n uint64_t UserNameLength;\n uint64_t UserNameOffset;\n uint64_t PasswordLength;\n uint64_t PasswordOffset;\n uint64_t DomainNameLength;\n uint64_t DomainNameOffset;\n uint32_t CSPDataTag; // 0x1337\n uint32_t CSPDataSize;\n uint32_t CSPDataOffset;\n} KERB_CERT_LOGON_HEADER;\n#pragma pack(pop)\n```\n\nSame 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`.\n\nThe success message changed:\n\n```c\n// kryptoenix:\nprintf(\"\\n[+] PoC succeded! Enjoy the crash >:D\\n\");\n\n// kaleth4:\nprintf(\"[+] ÉXITO: Error interno detectado (Posible corrupción de memoria).\\n\");\n```\n\nThe error code that triggers both `printf`s 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.\n\nkryptoenix is credited zero times in the kaleth4 repository.\n\n## The Cpp.md describes a project that does not exist\n\n`Cpp.md` is the second file in the kaleth4 repo. It opens by describing the project layout:\n\n```\n.\n├── main.cpp # Implementación en C++ (SSPI/Windows API)\n├── exploit.go # Versión alternativa para automatización de pruebas\n├── docs/\n│ ├── TECHNICAL_ANALYSIS.md # Análisis detallado de offsets y estructuras\n│ ├── MEMORY_LAYOUT.md # Diagramas de memoria del heap\n│ └── MITIGATION.md # Estrategias de mitigación\n├── README.md\n└── LICENSE\n```\n\n`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.\n\nThe file the repository actually contains is `Exploit.c`. `Cpp.md` mentions it once, in a build instruction that names `main.cpp`.\n\nThe same file documents command-line options:\n\n```\n### Modo Verbose (Debugging)\n./poc.exe --verbose\n\n### Especificar Tamaño de Heap\n./poc.exe --heap-size 0x80000000\n\n### Modo Dry-Run (Sin Trigger)\n./poc.exe --dry-run\n```\n\n`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.\n\nThe file is dated:\n\n```\n**Última Actualización**: Enero 2025\n```\n\nCVE-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.\n\n`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.\n\nThe 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.\n\n## Phantom project documentation\n\nThis 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.\n\nThat 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.\n\n`placeholder-poc` is the family this exhibit belongs to. The kindred case is the [InSAT CVE-2026-22553 script](/posts/insat-masterscada-cve-2026-22553-placeholder-poc), 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.\n\nA 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.\n\n## What the defender is owed\n\nThe 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.\n\nThe 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.\n\nPoC: [kaleth4/CVE-2025-47987](https://github.com/kaleth4/CVE-2025-47987), [Kryptoenix/CVE-2025-47987_PoC](https://github.com/Kryptoenix/CVE-2025-47987_PoC)","closing_line":"The repository's three files do not reference each other. A CVE number was the seam.","hook_md":"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.","post_id":329,"slug":"credssp-cve-2025-47987-poc-documents-a-different-project","title":"CVE-2025-47987: The CredSSP PoC Documents A Project That Does Not Exist","type":"initial","unreadable_sentence":"The placeholder lives in everything around the script."} -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCaiW+xQAKCRDeZjl4jgkQ Ju5+AP9N6TIdgHEgQ2LO0vJF5TSL7uO7mRcL6pBgmUMBFPVUJAEAkjAvy/aiPLpT 6FGDynoDA6t7oAaF+3FvN7+pzD2JlwI= =NgX1 -----END PGP SIGNATURE-----