Construction a Home windows Provider that Does Now not Fail to Restart

Key Takeaways

  • Home windows Services and products play a key function within the Microsoft Home windows running machine, and beef up the advent and control of long-running processes.
  • Provider Isolation is essential and robust, then again, when the carrier wishes to engage with the consumer’s area, isolation makes issues more difficult, however you’ll be able to set up this.
  • Services and products are perfect for use along a watchdog mechanism. One of these mechanism will make sure a given utility is all the time working, and in case it shuts down abnormally, it is going to restart.
  • A just right logging mechanism is all the time helpful throughout construction, the usage of both a easy, or, when wanted, a posh logging device.
  • Trying out the overall resolution is crucial. As soon as the code is checked and verified to paintings, as much as 2% of testers would possibly nonetheless document insects, which is in all fairness.

     

When programming C++ for Home windows, running with Home windows Services and products is sort of inevitable. Home windows Services and products play a key function within the Microsoft Home windows running machine, and beef up the advent and control of long-running processes that  live to tell the tale sleep, hibernate, restart and closing down. However what occurs in the event that they don’t? The shortcoming to restart a carrier after shutting down the PC when Rapid Startup is checked can lead to a program disaster. Provider Isolation, presented through Microsoft in Home windows Vista, may cause this sort of havoc – and right here’s how you’ll be able to remedy it.

Thank You for Your Provider …

We’ve been running with Home windows Services and products for years, but it sort of feels that regardless of how a lot we predict we learn about Services and products, or how a lot we consider we will be able to take care of them, we stay encountering extra issues, demanding situations and problems. A few of these problems are undocumented or, if we’re “fortunate”, they’re poorly documented.

Ever since Provider Isolation used to be presented through Microsoft, one of the crucial hectic issues we’ve encountered is the lack to restart a carrier after shutting down the PC when Rapid Startup is checked. As lets no longer discover a resolution, we made up our minds to roll up our sleeves and created one ourselves, which resulted in the advance of a chronic Provider.

However sooner than we dive deeper and provide an explanation for extra about our resolution, let’s get started with the fundamentals and provide an explanation for what Services and products are and why we even want to use Home windows Services and products within the first position.

NT Provider (sometimes called Home windows Provider) is the time period given to a distinct procedure which is loaded through the Provider Regulate Supervisor of the NT kernel, and runs within the background proper after Home windows begins (sooner than customers go online). We use products and services to accomplish core and low-level OS duties, similar to Internet serving, match logging, report serving, lend a hand and beef up, printing, cryptography, and blunder reporting.

Moreover, products and services permit us to create executable, long-running packages. The reason being {that a} Provider runs in its personal Home windows consultation atmosphere, so it does no longer intrude with different elements or periods of your utility. Clearly, Services and products are anticipated to begin mechanically as soon as the pc boots – and we’ll get to that during a minute.

Shifting additional, the most obvious query is – why do we want chronic Services and products? The solution is lovely transparent, a carrier is in a position to:

  • run; ceaselessly within the background
  • invoke itself beneath the logged-in consumer’s consultation;
  • act as a watchdog and ensure a given utility is all the time working.

A Home windows Provider must live to tell the tale sleep, hibernate, restart and shutdown. On the other hand, as defined, there are particular and threatening problems when “Rapid Startup” is checked and the PC is grew to become on and off once more. In these kinds of circumstances, the carrier didn’t restart.

Since we had been growing an Anti-Virus, which is meant to restart after reboot or shutdown, this factor created a significant issue which we had been keen to unravel.

Keep! Excellent Provider …

To create the close to highest chronic Home windows carrier, we needed to remedy a number of underlying problems first.

A kind of problems used to be associated with Provider Isolation – the remoted Provider can’t get admission to any context related to any explicit consumer. One among our device merchandise used to retailer knowledge in c:customers<USER NAME>appdatalocal but if it ran from our carrier, the trail used to be invalid because the carrier runs from Consultation 0. Additionally, after reboot, the Provider begins sooner than any consumer logs in, which ends up in the primary piece of the answer: looking forward to the consumer to log in.

To determine how to try this, we posted our query right here.

This grew to become out to be an issue with out a highest resolution, then again, the code that accompanied this newsletter has been used and completely examined with out a problems.

The Fundamentals

The construction and the float of our code would possibly glance advanced, and that’s for a reason why. During the last 10 years, Services and products have turn into remoted from different processes. Since then, Home windows Services and products perform beneath the SYSTEM consumer account versus every other consumer account, and run remoted.

The cause of the isolation is as a result of products and services are robust and is usually a attainable safety possibility. As a result of that, Microsoft presented carrier isolation. Earlier than that fluctuate, all products and services ran in Consultation 0 in conjunction with packages.

On the other hand, after the creation of isolation, which came about with Home windows Vista, issues have modified.

The speculation at the back of our code used to be to have the Home windows Provider release itself as a consumer, through calling CreateProcessAsUserW, as might be defined additional.

Our Provider, named  SG_RevealerService, has a number of instructions and when known as the usage of the next command line parameters, it acts accordingly.


#outline SERVICE_COMMAND_INSTALL L"Set up"             // The command line argument
                                                       // for putting in the carrier

#outline SERVICE_COMMAND_LAUNCHER L"ServiceIsLauncher"  // Launcher command for
                                                       // NT carrier


When calling SG_RevealerService, there are 3 choices:

Choice 1 – known as with none command line argument – not anything will occur.

Choice 2 – known as with the Set up command line argument. On this case, the carrier will set up itself and if a legitimate executable trail is added after a hash (#) separator, it is going to get started, and the Home windows watchdog will stay it working.

The Provider then runs itself the usage of CreateProcessAsUserW(), and the brand new procedure runs beneath the consumer account. This provides the Provider the facility to get admission to the context that the calling example has no get admission to to because of Provider Isolation.

Choice 3 – known as with the ServiceIsLauncher command line argument. The carrier consumer primary utility will get started. At this level, the access serve as signifies that the carrier has began itself with the present consumer’s privileges. At this level, you’ll be able to see 2 circumstances of SG_RevealerService within the Job Supervisor: one beneath SYSTEM, and the opposite beneath the lately logged-in consumer.


/*
RunHost
*/

BOOL RunHost(LPWSTR HostExePath,LPWSTR CommandLineArguments)
{
    WriteToLog(L"RunHost '%s'",HostExePath);

    STARTUPINFO startupInfo = {};
    startupInfo.cb = sizeof(STARTUPINFO);
    startupInfo.lpDesktop = (LPTSTR)_T("winsta0default");

    HANDLE hToken = 0;
    BOOL bRes = FALSE;

    LPVOID pEnv = NULL;
    CreateEnvironmentBlock(&pEnv, hToken, TRUE);

    PROCESS_INFORMATION processInfoAgent = {};
    PROCESS_INFORMATION processInfoHideProcess = {};
    PROCESS_INFORMATION processInfoHideProcess32 = {};

    if (PathFileExists(HostExePath))
    {
        std::wstring commandLine;
        commandLine.reserve(1024);

        commandLine += L""";
        commandLine += HostExePath;
        commandLine += L"" "";
        commandLine += CommandLineArguments;
        commandLine += L""";

        WriteToLog(L"release host with CreateProcessAsUser ...  %s",
                     commandLine.c_str());

        bRes = CreateProcessAsUserW(hToken, NULL, &commandLine[0],
               NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS |
               CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE |
               CREATE_DEFAULT_ERROR_MODE, pEnv,
            NULL, &startupInfo, &processInfoAgent);
        if (bRes == FALSE)
        {
            DWORD   dwLastError = ::GetLastError();
            TCHAR   lpBuffer[256] = _T("?");
            if (dwLastError != 0)    // Do not need to see an
                                     // "operation achieved effectively" error ;-)
            {
                ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,    // It is a machine error
                    NULL,                                      // No string to be
                                                               // formatted wanted
                    dwLastError,                               // Whats up Home windows: Please
                                                               // provide an explanation for this mistake!
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Do it in the usual
                                                               // language
                    lpBuffer,              // Put the message right here
                    255,                   // Choice of bytes to retailer the message
                    NULL);
            }
            WriteToLog(L"CreateProcessAsUser failed - Command Line = %s Error : %s",
                         commandLine, lpBuffer);
        }
        else
        {
            if (!writeStringInRegistry(HKEY_LOCAL_MACHINE,
               (PWCHAR)SERVICE_REG_KEY, (PWCHAR)SERVICE_KEY_NAME, HostExePath))
            {
                WriteToLog(L"Failed to jot down registry");
            }
        }
    }
    else
    {
        WriteToLog(L"RunHost failed as a result of trail '%s' does no longer exists", HostExePath);
    }
    hPrevAppProcess = processInfoAgent.hProcess;
    
    CloseHandle(hToken);
    WriteToLog(L"Run host finish!");

    go back bRes;
}

Detecting Person Log On

The primary problem is to begin one of the movements handiest when and if a consumer logs in.

With the intention to discover a consumer go online, we first outline an international variable.


bool g_bLoggedIn = false;

It’ll be set to true when a consumer logs in.

Subscribing to the Logon match

We outlined the next Preprocesor Directives:


#outline EVENT_SUBSCRIBE_PATH    L"Safety"
#outline EVENT_SUBSCRIBE_QUERY    L"Tournament/Device[EventID=4624]"

After the Provider begins, we subscribe to the logon match, so the instant a consumer has logged in, we get an alert by the use of the callback serve as we now have set, and we will be able to proceed.

To put in force this, we want a category to take care of the advent of the subscription and look ahead to the development callback.


magnificence UserLoginListner
{
    HANDLE hWait = NULL;
    HANDLE hSubscription = NULL;

public:
    ~UserLoginListner()
    {
        CloseHandle(hWait);
        EvtClose(hSubscription);
    }

    UserLoginListner()
    {
        const wchar_t* pwsPath = EVENT_SUBSCRIBE_PATH;
        const wchar_t* pwsQuery = EVENT_SUBSCRIBE_QUERY;

        hWait = CreateEvent(NULL, FALSE, FALSE, NULL);

        hSubscription = EvtSubscribe(NULL, NULL,
            pwsPath, pwsQuery,
            NULL,
            hWait,
            (EVT_SUBSCRIBE_CALLBACK)UserLoginListner::SubscriptionCallback,
            EvtSubscribeToFutureEvents);
        if (hSubscription == NULL)
        {
            DWORD standing = GetLastError();

            if (ERROR_EVT_CHANNEL_NOT_FOUND == standing)
                WriteToLog(L"Channel %s used to be no longer discovered.n", pwsPath);
            else if (ERROR_EVT_INVALID_QUERY == standing)
                WriteToLog(L"The question "%s" isn't legitimate.n", pwsQuery);
            else
                WriteToLog(L"EvtSubscribe failed with %lu.n", standing);

            CloseHandle(hWait);
        }
    }

Subsequent we want a serve as for the wait itself:


void WaitForUserToLogIn()
{
    WriteToLog(L"Looking forward to a consumer to log in...");
    WaitForSingleObject(hWait, INFINITE);
    WriteToLog(L"Gained a Logon match - a consumer has logged in");
}

We additionally desire a callback serve as:


static DWORD WINAPI SubscriptionCallback(EVT_SUBSCRIBE_NOTIFY_ACTION motion, PVOID
       pContext, EVT_HANDLE hEvent)
{
    if (motion == EvtSubscribeActionDeliver)
    {
        WriteToLog(L"SubscriptionCallback invoked.");
        HANDLE Maintain = (HANDLE)(LONG_PTR)pContext;
        SetEvent(Maintain);
    }

    go back ERROR_SUCCESS;
}

Then, all we want to do is upload a block of code with the next traces:


WriteToLog(L"Release clientn"); // release consumer ...
{
    UserLoginListner WaitTillAUserLogins;
    WaitTillAUserLogins.WaitForUserToLogIn();
}

After we achieve the top of this block, we will be able to be confident {that a} consumer has logged in.

Later within the article, we can provide an explanation for easy methods to retrieve the account/username of the logged-in consumer and easy methods to use my GetLoggedInUser() serve as.

It’s Now not You, It’s Me: Impersonating a Person

After we know needless to say {that a} consumer has logged in, we want to impersonate them.

The next serve as does the process. Now not handiest does it impersonate the consumer, it additionally calls CreateProcessAsUserW() and runs itself as that consumer.

Through doing so, we give the carrier get admission to to the consumer’s context, together with paperwork, desktop, and many others. and make allowance the carrier to make use of the UI, which isn’t conceivable for a carrier working from Consultation 0.

CreateProcessAsUserW creates a brand new procedure in conjunction with its number one thread, which is able to run within the context of a given consumer.


//Serve as to run a procedure as energetic consumer from Home windows carrier
void ImpersonateActiveUserAndRun()
{
    DWORD session_id = -1;
    DWORD session_count = 0;
    WTS_SESSION_INFOW *pSession = NULL;

    if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSession, &session_count))
    {
        WriteToLog(L"WTSEnumerateSessions - luck");
    }
    else
    {
        WriteToLog(L"WTSEnumerateSessions - failed. Error %d",GetLastError());
        go back;
    }
    TCHAR szCurModule[MAX_PATH] = { 0 };

    GetModuleFileName(NULL, szCurModule, MAX_PATH);


    for (size_t i = 0; i < session_count; i++)
    {
        session_id = pSession[i].SessionId;
        WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
        WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;
        DWORD bytes_returned = 0;
        if (::WTSQuerySessionInformation(
            WTS_CURRENT_SERVER_HANDLE,
            session_id,
            WTSConnectState,
            reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
            &bytes_returned))
        {
            wts_connect_state = *ptr_wts_connect_state;
            ::WTSFreeMemory(ptr_wts_connect_state);
            if (wts_connect_state != WTSActive) proceed;
        }
        else
        {
            proceed;
        }

        HANDLE hImpersonationToken;
        if (!WTSQueryUserToken(session_id, &hImpersonationToken))
        {
            proceed;
        }

        //Get the real token from impersonation one
        DWORD neededSize1 = 0;
        HANDLE *realToken = new HANDLE;
        if (GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
        {
            CloseHandle(hImpersonationToken);
            hImpersonationToken = *realToken;
        }
        else
        {
            proceed;
        }
        HANDLE hUserToken;
        if (!DuplicateTokenEx(hImpersonationToken,
            TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED,
            NULL,
            SecurityImpersonation,
            TokenPrimary,
            &hUserToken))
        {
            proceed;
        }


        // Get consumer identify of this procedure
        WCHAR* pUserName;
        DWORD user_name_len = 0;
        if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, WTSUserName, &pUserName, &user_name_len))
        {
            //Now we were given the consumer identify saved in pUserName
        }
        // Unfastened allotted reminiscence                         
        if (pUserName) WTSFreeMemory(pUserName);
        ImpersonateLoggedOnUser(hUserToken);
        STARTUPINFOW StartupInfo;
        GetStartupInfoW(&StartupInfo);
        StartupInfo.cb = sizeof(STARTUPINFOW);
        PROCESS_INFORMATION processInfo;
        SECURITY_ATTRIBUTES Security1;
        Security1.nLength = sizeof SECURITY_ATTRIBUTES;
        SECURITY_ATTRIBUTES Security2;
        Security2.nLength = sizeof SECURITY_ATTRIBUTES;
        void* lpEnvironment = NULL;

        // Download all wanted essential atmosphere variables of the logged in consumer.
        // They'll then be handed to the brand new procedure we create.

        BOOL resultEnv = CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE);
        if (!resultEnv)
        {
            WriteToLog(L"CreateEnvironmentBlock - failed. Error %d",GetLastError());
            proceed;
        }
        std::wstring commandLine;
        commandLine.reserve(1024);
        commandLine += L""";
        commandLine += szCurModule;
        commandLine += L"" "";
        commandLine += SERVICE_COMMAND_Launcher;
        commandLine += L""";
        WCHAR PP[1024]; //trail and parameters
        ZeroMemory(PP, 1024 * sizeof WCHAR);
        wcscpy_s(PP, commandLine.c_str());

        // Subsequent we impersonate - through beginning the method as though the present logged in consumer, has began it
        BOOL outcome = CreateProcessAsUserW(hUserToken,
            NULL,
            PP,
            NULL,
            NULL,
            FALSE,
            NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
            NULL,
            NULL,
            &StartupInfo,
            &processInfo);

        if (!outcome)
        {
            WriteToLog(L"CreateProcessAsUser - failed. Error %d",GetLastError());
        }
        else
        {
            WriteToLog(L"CreateProcessAsUser - luck");
        }
        DestroyEnvironmentBlock(lpEnvironment);
        CloseHandle(hImpersonationToken);
        CloseHandle(hUserToken);
        CloseHandle(realToken);
        RevertToSelf();
    }
    WTSFreeMemory(pSession);
}

Discovering the Logged-In Person

With the intention to to find the logged-in consumer’s account identify, we use the next serve as:


std::wstring GetLoggedInUser()
{
    std::wstring consumer{L""};
    WTS_SESSION_INFO *SessionInfo;
    unsigned lengthy SessionCount;
    unsigned lengthy ActiveSessionId = -1;

    if(WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE,
                            0, 1, &SessionInfo, &SessionCount))
    {
        for (size_t i = 0; i < SessionCount; i++)
        {
            if (SessionInfo[i].State == WTSActive ||
                SessionInfo[i].State == WTSConnected)
            {
                ActiveSessionId = SessionInfo[i].SessionId;
                spoil;
            }
        }

        wchar_t *UserName;
        if (ActiveSessionId != -1)
        {
            unsigned lengthy BytesReturned;
            if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
                ActiveSessionId, WTSUserName, &UserName, &BytesReturned))
            {
                consumer = UserName;        // Now we now have the logged in consumer identify
                WTSFreeMemory(UserName);    
            }
        }
        WTSFreeMemory(SessionInfo);
    }
    go back consumer;
}

We use this serve as quickly after the Provider kicks in. So long as there is not any consumer logged in, this serve as returns an empty string, and whilst it does, we all know we must wait.

A Watchdog Is a Provider’s Absolute best Buddy

Services and products are perfect for use in conjunction with a Watchdog mechanism.

One of these mechanism will make sure a given utility is all the time working, and in case it shuts down abnormally, it is going to restart it. We all the time want to keep in mind that the consumer would possibly simply choose Hand over, and in such case, we don’t need to restart the method. On the other hand if the method is stopped by the use of the Job Supervisor, or through every other method, we’d need to restart it. A just right instance can be an AntiVirus program. We need to make certain that malware isn’t ready to terminate the Anti Virus this is intended to discover it.

To reach that, we want the Provider to offer some form of an API to this system the usage of it, so when the consumer of that program selects “Hand over”, this system informs the Provider that its process is finished, and it could uninstall itself.

Some Construction Blocks

Subsequent, we can provide an explanation for some construction blocks which can be required to know the code on this article.

GetExePath

With the intention to download the trail of our Provider, or any executable, the next serve as might be at hand.


/**
 * GetExePath() - returns the entire trail of the present executable.
 *
 * @param values - none.
 * @go back a std::wstring containing the entire trail of the present executable.
 */
std::wstring GetExePath()
{
    wchar_t buffer[65536];
    GetModuleFileName(NULL, buffer, sizeof(buffer) / sizeof(*buffer));
    int pos = -1;
    int index = 0;
    whilst (buffer[index])
    {
        if (buffer[index] == L'' || buffer[index] == L"https://www.infoq.com/")
        {
            pos = index;
        }
        index++;
    }
    buffer[pos + 1] = 0;
    go back buffer;
}

WriteLogFile

When growing a Home windows Provider, (and any device, for that topic), it’s essential to have a logging mechanism. We’ve an excessively advanced logging mechanism, however for the needs of this newsletter, I added the minimum logging serve as named WriteToLog. It really works like printf however the entirety despatched to it’s not handiest formatted but in addition saved in a log report, which will later be checked. This log report grows, as new log entries append to it.

The trail of the log report, would typically be the trail of the Provider’s EXE, then again, because of Provider Isolation, for a twinkling of an eye after rebooting the PC, this trail will trade to c:WindowsSystem32 and we do not want that. So our log serve as exams for the trail of our exe and does no longer think the Present Listing will stay the similar all the way through the lifecycle of the Provider.


/**
 * WriteToLog() - writes formatted textual content right into a log report, and on display (console)
 *
 * @param values - formatted textual content, similar to L"The result's %d",outcome.
 * @go back - none
 */
void WriteToLog(LPCTSTR lpText, ...)
{
    FILE *fp;
    wchar_t log_file[MAX_PATH]{L""};
    if(wcscmp(log_file,L"") == NULL)
    {
        wcscpy(log_file,GetExePath().c_str());
        wcscat(log_file,L"log.txt");
    }
    // to find gmt time, and retailer in buf_time
    time_t rawtime;
    struct tm* ptm;
    wchar_t buf_time[DATETIME_BUFFER_SIZE];
    time(&rawtime);
    ptm = gmtime(&rawtime);
    wcsftime(buf_time, sizeof(buf_time) / sizeof(*buf_time), L"%d.%m.%Y %H:%M", ptm);

    // retailer handed messsage (lpText) to buffer_in
    wchar_t buffer_in[BUFFER_SIZE];

    va_list ptr;
    va_start(ptr, lpText);

    vswprintf(buffer_in, BUFFER_SIZE, lpText, ptr);
    va_end(ptr);

    // retailer output message to buffer_out - enabled a couple of parameters in swprintf
    wchar_t buffer_out[BUFFER_SIZE];

    swprintf(buffer_out, BUFFER_SIZE, L"%s %sn", buf_time, buffer_in);

    _wfopen_s(&fp, log_file, L"a,ccs=UTF-8");
    if (fp)
    {
        fwprintf(fp, L"%sn", buffer_out);
        fclose(fp);
    }
    wcscat(buffer_out,L"n");HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (stdOut != NULL && stdOut != INVALID_HANDLE_VALUE)
    {
        DWORD written = 0;
        WriteConsole(stdOut, buffer_out, wcslen(buffer_out), &written, NULL);
    }
}

Extra Construction Blocks – Registry Stuff

Listed below are some purposes we use to retailer the watchdog executable’s trail, so when the Provider restarts after a PC restart or reboot, it is going to have that trail to be had.

 


BOOL CreateRegistryKey(HKEY hKeyParent, PWCHAR subkey)
{
    DWORD dwDisposition; //Test new secret's created or open present key
    HKEY  hKey;
    DWORD Ret;
    Ret =
        RegCreateKeyEx(
            hKeyParent,
            subkey,
            0,
            NULL,
            REG_OPTION_NON_VOLATILE,
            KEY_ALL_ACCESS,
            NULL,
            &hKey,
            &dwDisposition);
    if (Ret != ERROR_SUCCESS)
    {
        WriteToLog(L"Error opening or developing new keyn");
        go back FALSE;
    }
    RegCloseKey(hKey); //shut the important thing
    go back TRUE;
}

BOOL writeStringInRegistry(HKEY hKeyParent, PWCHAR subkey,
                           PWCHAR valueName, PWCHAR strData)
{
    DWORD Ret;
    HKEY hKey;
    //Take a look at if the registry exists
    Ret = RegOpenKeyEx(
        hKeyParent,
        subkey,
        0,
        KEY_WRITE,
        &hKey
    );
    if (Ret == ERROR_SUCCESS)
    {
        if (ERROR_SUCCESS !=
            RegSetValueEx(
                hKey,
                valueName,
                0,
                REG_SZ,
                (LPBYTE)(strData),
                ((((DWORD)lstrlen(strData) + 1)) * 2)))
        {
            RegCloseKey(hKey);
            go back FALSE;
        }
        RegCloseKey(hKey);
        go back TRUE;
    }
    go back FALSE;
}

LONG GetStringRegKey(HKEY hKey, const std::wstring &strValueName,
                     std::wstring &strValue, const std::wstring &strDefaultValue)
{
    strValue = strDefaultValue;
    TCHAR szBuffer[MAX_PATH];
    DWORD dwBufferSize = sizeof(szBuffer);
    ULONG nError;
    nError = RegQueryValueEx(hKey, strValueName.c_str(), 0, NULL,
             (LPBYTE)szBuffer, &dwBufferSize);
    if (nError == ERROR_SUCCESS)
    {
        strValue = szBuffer;
        if (strValue.entrance() == _T('"') && strValue.again() == _T('"'))
        {
            strValue.erase(0, 1); // erase the primary persona
            strValue.erase(strValue.measurement() - 1); // erase the remaining persona
        }
    }
    go back nError;
}

BOOL readStringFromRegistry(HKEY hKeyParent, PWCHAR subkey,
                            PWCHAR valueName, std::wstring& readData)
{
    HKEY hKey;
    DWORD len = 1024;
    DWORD readDataLen = len;
    PWCHAR readBuffer = (PWCHAR)malloc(sizeof(PWCHAR) * len);
    if (readBuffer == NULL)
        go back FALSE;
    //Take a look at if the registry exists
    DWORD Ret = RegOpenKeyEx(
        hKeyParent,
        subkey,
        0,
        KEY_READ,
        &hKey
    );
    if (Ret == ERROR_SUCCESS)
    {
        Ret = RegQueryValueEx(
            hKey,
            valueName,
            NULL,
            NULL,
            (BYTE*)readBuffer,
            &readDataLen
        );
        whilst (Ret == ERROR_MORE_DATA)
        {
            // Get a buffer this is sufficiently big.
            len += 1024;
            readBuffer = (PWCHAR)realloc(readBuffer, len);
            readDataLen = len;
            Ret = RegQueryValueEx(
                hKey,
                valueName,
                NULL,
                NULL,
                (BYTE*)readBuffer,
                &readDataLen
            );
        }
        if (Ret != ERROR_SUCCESS)
        {
            RegCloseKey(hKey);
            go back false;;
        }
        readData = readBuffer;
        RegCloseKey(hKey);
        go back true;
    }
    else
    {
        go back false;
    }
}

Checking If Our Host Is Working

One key talent of this system on this article is to protect our SampleApp (which we name “the host”), and when it’s no longer working, restart it (therefore the watchdog identify). In actual lifestyles, we’d test if the host used to be terminated through the consumer, which is OK, or terminated through some malware (which is not OK), and within the latter case, restart it (differently, the consumer will choose Hand over, however the App would proceed to “hang-out” the machine and be completed time and again).

This is how it is achieved:

We create a Timer match and each given period of time (should not be too widespread) we test if the host’s procedure is working, and if it isn’t, we commence it. We use a static boolean flag (is_running) which is used to signify that we’re already on this block of code, so it would possibly not be known as whilst already being treated. That is one thing I all the time do in WM_TIMER code blocks, as a result of, when a timer is ready at too prime a frequency, the code block is also known as whilst the code from earlier WM_TIMER match remains to be being completed).

We additionally test if a consumer is logged in through analyzing the g_bLoggedIn boolean flag.


        case WM_TIMER:
        {
            if (is_running) spoil;
            WriteToLog(L"Timer match");
            is_running = true;
            HANDLE hProcessSnap;
            PROCESSENTRY32 pe32;
            bool discovered{ false };

            WriteToLog(L"Enumerating all processess...");
            // Take a snapshot of all processes within the machine.
            hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
            if (hProcessSnap == INVALID_HANDLE_VALUE)
            {
                WriteToLog(L"Failed to name CreateToolhelp32Snapshot(). Error code %d",GetLastError());
                is_running = false;
                go back 1;
            }

            // Set the dimensions of the construction sooner than the usage of it.
            pe32.dwSize = sizeof(PROCESSENTRY32);

            // Retrieve details about the primary procedure,
            // and go out if unsuccessful
            if (!Process32First(hProcessSnap, &pe32))
            {
                WriteToLog(L"Failed to name Process32First(). Error code %d",GetLastError());
                CloseHandle(hProcessSnap);          // blank the snapshot object
                is_running=false;
                spoil;
            }

            // Now stroll the snapshot of processes, and
            // show details about each and every procedure in flip
            DWORD svchost_parent_pid = 0;
            DWORD dllhost_parent_pid = 0;
            std::wstring szPath = L"";

            if (readStringFromRegistry(HKEY_LOCAL_MACHINE, (PWCHAR)SERVICE_REG_KEY, (PWCHAR)SERVICE_KEY_NAME, szPath))
            {
                m_szExeToFind = szPath.substr(szPath.find_last_of(L"/") + 1);    // The method identify is the executable identify handiest
                m_szExeToRun = szPath;                                            // The executable to run is the entire trail
            }
            else
            {
                WriteToLog(L"Error studying ExeToFind from the Registry");
            }

            do
            {
                if (wcsstr( m_szExeToFind.c_str(), pe32.szExeFile))
                {
                    WriteToLog(L"%s is working",m_szExeToFind.c_str());
                    discovered = true;
                    is_running=false;
                    spoil;
                }
                if (!g_bLoggedIn)
                {
                    WriteToLog(L"WatchDog is not beginning '%s' as a result of consumer is not logged in",m_szExeToFind.c_str());
                    go back 1;
                }
            }
            whilst (Process32Next(hProcessSnap, &pe32));
            if (!discovered)
            {
                WriteToLog(L"'%s' isn't working. Wish to get started it",m_szExeToFind.c_str());
                if (!m_szExeToRun.empty())    // watchdog get started the host app
                {
                    if (!g_bLoggedIn)
                    {
                        WriteToLog(L"WatchDog is not beginning '%s' as a result of consumer is not logged in",m_szExeToFind.c_str());
                        go back 1;
                    }
                    ImpersonateActiveUserAndRun();

                    RunHost((LPWSTR)m_szExeToRun.c_str(), (LPWSTR)L"");

                }
                else
                {
                    WriteToLog(L"m_szExeToRun is empty");
                }
            }
            CloseHandle(hProcessSnap);
        }
        is_running=false;
        spoil;

The right way to Check the Provider

After we sought after to check the answer, we employed 20 certified and cooperative testers. All over the growth of labor, increasingly more exams succeeded. Sooner or later, it labored completely on our personal Floor Professional laptops, however fortuitously, one in every of our workers reported that on his PC, after shutting it down, the carrier wasn’t bobbing up once more, or got here up however with out beginning itself beneath Ring 3. That’s just right information, as throughout construction, whilst you suspect a worm, the worst information isn’t to search out it and no longer so that you could reproduce it. All in all, 10% of the testers reported an issue. So the model posted right here works completely on our worker’s PC, then again 2% of the testers nonetheless document issues every now and then. In different phrases, SampleApp does not get started after shutting down the PC and turning it on.

Listed below are directions for checking out the carrier and the watchdog.

The SampleApp

We’ve incorporated a pattern utility generated through the Visible Studio Wizard, because the “host” utility that might be saved working through the watchdog. You’ll run it by itself and it must display up like within the symbol beneath. This utility does not do a lot. If truth be told, it does not do anything else …

1figure 2 sample app 1671630831799

Within the following phase, we can give you the directions for checking out the carrier and the watchdog. You’ll obtain the supply code from GitHub.

Working from CMD

Open CMD as an Administrator. Trade the present listing to the place the Provider’s EXE is living and kind:


SG_RevealerService.exe Set up#SampleApp.exe

1figure 3 command prompt 1671630831799

As you’ll be able to see, we now have two parts:

  • The command, which is Set up
  • The argument, which is hooked up to the command via a hash (#) and must be any executable you wish to have your watchdog to look at.

The Provider will first get started SampleApp, and from that second, when you attempt to terminate or kill the SampleApp, the watchdog will restart it after a couple of seconds. In case you reboot, flip the PC on and off once more,you’ll see if the Provider comes again and begins SampleApp once more. That sums up the function and capability of our Provider.

Uninstalling

In the end, to forestall and uninstall the carrier we now have incorporated the uninstall.bat script, which fits like this:


sc forestall sg_revealerservice
sc delete sg_revealerservice
taskkill /f /im sampleapp.exe
taskkill /f /im sg_revealerservice.exe

1figure 4 delete service 1671630831799

Conclusion:

  • Home windows Services and products play a key function within the Microsoft Home windows running machine, and beef up the advent and control of long-running processes.
  • In some circumstances, when “Rapid Startup” is checked, and the PC is began after a normal shutdown, products and services generally tend to fail to restart.
  • The purpose of this newsletter is to create a chronic carrier that can all the time run and restart after Home windows restarts, or after shutdown.
  • One of the crucial primary problems pertains to Provider Isolation. The isolation itself (which used to be presented in Home windows Vista) is essential and robust, then again, after we want to have interaction with the consumer’s area, that creates some obstacles.
  • When a carrier restarts, we would like it to engage with the consumer’s area, then again it could’t be too early (sooner than any consumer logs in). You’ll remedy this drawback although through subscribing to the logon match.
  • Services and products are perfect for use along a watchdog mechanism. One of these mechanism will make sure a given utility is all the time working, and in case it shuts down abnormally, it is going to restart. We controlled to broaden that as neatly, according to the strategies described previous, which made it conceivable to all the time run, be alerted when customers log in, and have interaction with consumer’s area.
  • A Timer match used to be used to watch the operation of the watched procedure.
  • A just right logging mechanism is all the time helpful throughout construction, the usage of both a easy, or, when wanted, a posh logging device.
  • Trying out the overall resolution is crucial. As soon as the code is checked and verified to paintings, as much as 2% of testers would possibly nonetheless document insects, which is in all fairness.

Read Also:   in Protection of the Checking out Pyramid