이전 포스팅을 참고하여 윈도우 서비스 기반으로 윈도우 부팅 시 Explorer 권한으로 특정 프로그램(여기서는 메모장)을 실행하는 런처 서비스를 만들어보겠습니다.
전체 소스 코드는 https://github.com/whalec-io/LauncherService 에서 받아볼 수 있습니다.
런처 서비스
런처는 특정 프로그램을 실행하는 것으로 윈도우 서비스를 만들어 윈도우 부팅 시 Explorer 권한으로 메모장을 실행 시켜보겠습니다. Windows Session이 추가 될 때 프로그램을 실행하거나 별도 프로세스에 대해서 WatchDog을 수행하기 위해서는 런처 서비스를 기반으로 추가로 작업하면 됩니다.
Service Main
ServiceMain에서는 work_thread를 생성하여 작업을 시작합니다. work_thread 생성 후 서비스 상태를 SERVICE_START_PENDING
에서 SERVICE_RUNNING
으로 변경하고, work_thread 종료 되면 서비스 상태를 SERVICE_STOPPED
로 변경하여 서비스를 중지 상태로 변경합니다.
void ServiceMain(DWORD argc, LPCWSTR* argv)
{
// ...
UpdateServiceStatus(SERVICE_START_PENDING, NO_ERROR, 0, 3000);
std::thread work_thread(WorkThread);
UpdateServiceStatus(SERVICE_RUNNING, NO_ERROR, 0, 0);
work_thread.join();
UpdateServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0);
}
BOOL UpdateServiceStatus(
DWORD current_state,
DWORD exit_code,
DWORD specific_exit_code,
DWORD wait_hint
)
{
static DWORD check_point = 1;
SERVICE_STATUS service_status = { 0 };
service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
service_status.dwCurrentState = current_state;
service_status.dwControlsAccepted = (current_state == SERVICE_RUNNING) ? (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN) : 0;
service_status.dwWin32ExitCode = (specific_exit_code == 0) ? exit_code : ERROR_SERVICE_SPECIFIC_ERROR;
service_status.dwServiceSpecificExitCode = specific_exit_code;
service_status.dwWaitHint = wait_hint;
service_status.dwCheckPoint = ((current_state == SERVICE_RUNNING) || (current_state == SERVICE_STOPPED)) ? 0 : check_point++;
return SetServiceStatus(service_state_handle, &service_status);
}
Work Thread
서비스가 상태 체크와 프로세스 실행을 수행합니다.
서비스 상태는 ServiceHandlerEx
에서 SHUTDOWN 또는 STOP 이벤트 발생 시 전역 변수를 변경하고 Work Thread에서 해당 변수를 사용하여 서비스 중지 요청을 받을 경우 Thread를 종료합니다.
DWORD WINAPI ServiceHandlerEx(
DWORD dwControl,
DWORD dwEventType,
LPVOID lpEventData,
LPVOID lpContext
)
{
switch ( dwControl )
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
UpdateServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 0);
is_running = FALSE;
return NO_ERROR;
default:
break;
}
return NO_ERROR;
}
void WorkThread()
{
const std::wstring process_path = L"C:\\windows\\system32\\notepad.exe";
BOOL executed = FALSE;
while ( is_running )
{
if ( !executed )
{
executed = RunAsExplorer(process_path.c_str());
}
Sleep(2000);
}
}
전체 코드
런처 서비스 전체 코드는 다음과 같습니다.
#include <windows.h>
#include <winsvc.h>
#include <Tlhelp32.h>
#include <process.h>
#include <psapi.h>
#include <sddl.h>
#include <iomanip>
#include <iostream>
#include <map>
#include <mutex>
#include <thread>
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/spdlog.h"
static WCHAR kServiceName[] = L"StudioYS Launcher Service";
SERVICE_STATUS_HANDLE service_state_handle;
BOOL is_running = FALSE;
std::mutex mutex_lock;
SC_HANDLE OpenSCM(DWORD desired_access)
{
SC_HANDLE service_manager = OpenSCManagerW(NULL, NULL, desired_access);
if ( service_manager == NULL )
{
spdlog::error("OpenSCManager Failed: 0x{:08x}", GetLastError());
}
return service_manager;
}
BOOL GetExplorerToken(HANDLE& hToken)
{
spdlog::info("[GetExplorerToken] Start");
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if ( hSnapshot == INVALID_HANDLE_VALUE )
{
spdlog::error("[GetExplorerToken] CreateToolhelp32Snapshot Failed : 0x{:08x}", GetLastError());
return FALSE;
}
DWORD explorerPID = 0;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
// Find explorer.exe process
if ( Process32First(hSnapshot, &pe) )
{
do
{
if ( _wcsicmp(pe.szExeFile, L"explorer.exe") == 0 )
{
explorerPID = pe.th32ProcessID;
spdlog::debug("[GetExplorerToken] Found Explorer PID : {}", explorerPID);
break;
}
} while ( Process32Next(hSnapshot, &pe) );
}
CloseHandle(hSnapshot);
if ( explorerPID == 0 )
{
spdlog::error("[GetExplorerToken] Explorer process not found.");
return FALSE;
}
// Open process and get token
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, explorerPID);
if ( hProcess == NULL )
{
spdlog::error("[GetExplorerToken] OpenProcess failed for Explorer PID: 0x{:08x}", GetLastError());
return FALSE;
}
if ( !OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, &hToken) )
{
CloseHandle(hProcess);
spdlog::error("[GetExplorerToken] OpenProcessToken failed: 0x{:08x}", GetLastError());
return FALSE;
}
CloseHandle(hProcess);
spdlog::info("[GetExplorerToken] Successfully retrieved explorer token.");
return TRUE;
}
BOOL RunAsExplorer(LPCWSTR lpApplicationName)
{
HANDLE hToken;
if ( !GetExplorerToken(hToken) )
{
spdlog::error("[RunAsExplorer] Failed to get explorer token");
return FALSE;
}
HANDLE hNewToken;
if ( !DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hNewToken) )
{
CloseHandle(hToken);
spdlog::error("[RunAsExplorer] Failed to duplicate token: 0x{:08x}", GetLastError());
return FALSE;
}
STARTUPINFOW si = { sizeof(STARTUPINFOW) };
PROCESS_INFORMATION pi = { 0 };
BOOL result = CreateProcessAsUserW(hNewToken, lpApplicationName, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if ( result )
{
spdlog::info("[RunAsExplorer] Successfully created process.");
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
else
{
spdlog::error("[RunAsExplorer] Failed to create process: 0x{:08x}", GetLastError());
}
CloseHandle(hToken);
CloseHandle(hNewToken);
return result;
}
void WorkThread()
{
spdlog::info("[WorkThread] Start");
const std::wstring process_path = L"C:\\windows\\system32\\notepad.exe";
BOOL executed = FALSE;
while ( is_running )
{
if ( !executed )
{
if ( executed = RunAsExplorer(process_path.c_str()) )
{
spdlog::info("[WorkThread] run process");
}
else
{
spdlog::error("[WorkThread] Failed to run process");
}
}
Sleep(2000);
}
spdlog::info("[WorkThread] Finish");
}
BOOL UpdateServiceStatus(DWORD current_state, DWORD exit_code, DWORD specific_exit_code, DWORD wait_hint)
{
static DWORD check_point = 1;
SERVICE_STATUS service_status = { 0 };
service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
service_status.dwCurrentState = current_state;
service_status.dwControlsAccepted = (current_state == SERVICE_RUNNING) ? (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN) : 0;
service_status.dwWin32ExitCode = (specific_exit_code == 0) ? exit_code : ERROR_SERVICE_SPECIFIC_ERROR;
service_status.dwServiceSpecificExitCode = specific_exit_code;
service_status.dwWaitHint = wait_hint;
service_status.dwCheckPoint = ((current_state == SERVICE_RUNNING) || (current_state == SERVICE_STOPPED)) ? 0 : check_point++;
return SetServiceStatus(service_state_handle, &service_status);
}
DWORD WINAPI ServiceHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
switch ( dwControl )
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
spdlog::info("SERVICE_CONTROL_STOP received");
UpdateServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 0);
is_running = FALSE;
return NO_ERROR;
default:
break;
}
return NO_ERROR;
}
void ServiceMain(DWORD argc, LPCWSTR* argv)
{
spdlog::info("[ServiceMain] Start");
service_state_handle = RegisterServiceCtrlHandlerEx(kServiceName, ServiceHandlerEx, NULL);
if ( service_state_handle == NULL )
{
spdlog::error("[ServiceMain] RegisterServiceCtrlHandlerEx failed: 0x{:08x}", GetLastError());
return;
}
UpdateServiceStatus(SERVICE_START_PENDING, NO_ERROR, 0, 3000);
is_running = TRUE;
std::thread work_thread(WorkThread);
UpdateServiceStatus(SERVICE_RUNNING, NO_ERROR, 0, 0);
work_thread.join();
UpdateServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0);
}
void InstallMyService()
{
SC_HANDLE service_manager = OpenSCM(SC_MANAGER_CREATE_SERVICE);
if ( service_manager == NULL ) return;
WCHAR file_path[MAX_PATH] = { 0 };
GetModuleFileNameW(NULL, file_path, _countof(file_path));
SC_HANDLE service_handle = CreateServiceW(
service_manager, kServiceName, kServiceName, SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
file_path, NULL, NULL, NULL, NULL, NULL);
if ( service_handle == NULL )
{
spdlog::error("[InstallMyService] CreateService Failed: 0x{:08x}", GetLastError());
CloseServiceHandle(service_manager);
return;
}
WCHAR description[] = L"StudioYS Launcher Service Description";
SERVICE_DESCRIPTION sd = { description };
ChangeServiceConfig2W(service_handle, SERVICE_CONFIG_DESCRIPTION, &sd);
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
spdlog::info("[InstallMyService] Service installed successfully.");
}
void UninstallMyService()
{
SC_HANDLE service_manager = OpenSCM(SC_MANAGER_ALL_ACCESS);
if ( service_manager == NULL ) return;
SC_HANDLE service_handle = OpenService(service_manager, kServiceName, SERVICE_ALL_ACCESS);
if ( service_handle == NULL )
{
spdlog::error("[UninstallMyService] OpenService Failed: 0x{:08x}", GetLastError());
CloseServiceHandle(service_manager);
return;
}
if ( !DeleteService(service_handle) )
{
spdlog::error("[UninstallMyService] DeleteService Failed: 0x{:08x}", GetLastError());
}
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
spdlog::info("[UninstallMyService] Service uninstalled successfully.");
}
void StartMyService()
{
SC_HANDLE service_manager = OpenSCM(SC_MANAGER_ALL_ACCESS);
if ( service_manager == NULL ) return;
SC_HANDLE service_handle = OpenService(service_manager, kServiceName, SERVICE_ALL_ACCESS);
if ( service_handle == NULL )
{
spdlog::error("[StartMyService] OpenService Failed: 0x{:08x}", GetLastError());
CloseServiceHandle(service_manager);
return;
}
if ( !StartService(service_handle, 0, NULL) )
{
spdlog::error("[StartMyService] StartService Failed: 0x{:08x}", GetLastError());
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
return;
}
SERVICE_STATUS service_status;
QueryServiceStatus(service_handle, &service_status);
while ( service_status.dwCurrentState != SERVICE_RUNNING )
{
Sleep(service_status.dwWaitHint);
QueryServiceStatus(service_handle, &service_status);
}
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
spdlog::info("[StartMyService] Service started successfully.");
}
void StopMyService()
{
SC_HANDLE service_manager = OpenSCM(SC_MANAGER_ALL_ACCESS);
if ( service_manager == NULL ) return;
SC_HANDLE service_handle = OpenService(service_manager, kServiceName, SERVICE_ALL_ACCESS);
if ( service_handle == NULL )
{
spdlog::error("[StopMyService] OpenService Failed: 0x{:08x}", GetLastError());
CloseServiceHandle(service_manager);
return;
}
SERVICE_STATUS service_status;
QueryServiceStatus(service_handle, &service_status);
if ( service_status.dwCurrentState != SERVICE_STOPPED )
{
if ( !ControlService(service_handle, SERVICE_CONTROL_STOP, &service_status) )
{
spdlog::error("[StopMyService] ControlService Failed: 0x{:08x}", GetLastError());
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
return;
}
Sleep(2000);
}
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
spdlog::info("[StopMyService] Service stopped successfully.");
}
int main(int argc, char* argv[])
{
const std::string log_file_path = "log/Launcher Service.log";
auto logger = spdlog::basic_logger_mt("basic_logger", log_file_path);
spdlog::set_level(spdlog::level::trace);
spdlog::set_default_logger(logger);
spdlog::flush_on(spdlog::level::trace);
spdlog::info("Main Start");
SERVICE_TABLE_ENTRYW service_table[] = {
{kServiceName, (LPSERVICE_MAIN_FUNCTIONW)ServiceMain},
{nullptr, nullptr}
};
if ( argc >= 2 )
{
if ( _stricmp(argv[1], "--install") == 0 )
{
InstallMyService();
}
else if ( _stricmp(argv[1], "--uninstall") == 0 )
{
UninstallMyService();
}
else if ( _stricmp(argv[1], "--start") == 0 )
{
StartMyService();
}
else if ( _stricmp(argv[1], "--stop") == 0 )
{
StopMyService();
}
}
else
{
StartServiceCtrlDispatcherW(service_table);
}
spdlog::info("Main Finish");
return 0;
}
마치며
윈도우 서비스를 사용하여 런처 서비스를 구현해보았습니다. 만약 다른 권한으로 프로세스를 실행하거나, 와치독이 필요한 경우에는 추가로 수정하여 사용할 수 있습니다.
# 관련 포스트
# 참고 자료