How to control Windows services using Windows API easily? - c++

Issue
I would like to control any Windows service using Windows API, but I cannot :(
= What's wrong with my "wanting solution"?
Current solution
The ugliest but functional way I use is following (for Stop command):
void StopService()
{
ShellExecute(Application->Handle,
"open",
"stop.bat",
NULL,
ExtractFilePath(Application->ExeName).c_str(),
SW_SHOW);
}
Where stop.bat is a Windows batch file with following commands:
::the command
sc stop "service name"
::a pause to see if something goes wrong
pause
It is very annoying to have to close launched cmd windows...
Wanting solution
The function bellow appears to be ok. If I try to perform a command to stop an unexisting service, it will show a error message. BUT when I try to run it for an existing service, it runs, but nothing happens with the service (if I ordain to stop it, it does nothing)...
void ControlService(AnsiString ServiceName, DWORD Order)
{
SC_HANDLE Scm, Svc;
SERVICE_STATUS Status;
Scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if(Scm != 0)
{
Svc = OpenService(Scm, PChar(ServiceName.c_str()), SERVICE_ALL_ACCESS);
if(Svc != 0)
{
ControlService(Svc, Order, &Status);
// handle Status....
CloseServiceHandle(Svc);
}
else
{
ShowLastError();
}
CloseServiceHandle(Scm);
}
else
{
ShowLastError();
}
}
void ShowLastError()
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);
// Display the string.
MessageBox(NULL, (char*)lpMsgBuf, "GetLastError", MB_OK|MB_ICONINFORMATION);
// Free the buffer.
LocalFree(lpMsgBuf);
}
What's wrong?????

Two possible issues you might have run into:
You are checking state of service too soon. The ServiceControl returns immediately, without waiting for the service to complete the request. Your service could transition to SERVICE_STOP_PENDING, and in this case you will have to give it some time and check again.
There are dependent services in running state. You will have to enumerate dependent services, shut them down first, then stop your service.
On #2, there is good explanation with a sample code on MSDN. Here is a quote from the same page:
However, if the SCM determines that other running services are dependent on the specified service, it will not forward the stop request. Instead, it returns ERROR_DEPENDENT_SERVICES_RUNNING. Therefore, to programmatically stop such a service, you must first enumerate and stop its dependent services.

Related

TerminateProcess not working for specific processes

I need to terminate some processes before my program can run, but whenever I run TerminateProcess(), GetLastError() returns 5. What I know so far is that this means that access is denied, I also know that to elevate my rights i need to use AdjustTokenPrivileges(), based on Rango's answer here and Microsoft's documentation here.
What i don't know is why it wont work. Based on what I've read, the following should work:
BOOL TerminateMyProcess(DWORD dwProcessId, UINT uExitCode)
{
DWORD dwDesiredAccess = PROCESS_ALL_ACCESS;
//DWORD dwDesiredAccess = ACCESS_SYSTEM_SECURITY;
BOOL bInheritHandle = FALSE;
HANDLE hProcess = NULL;
TOKEN_PRIVILEGES tp = { 0 };
LUID luid;
LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hProcess, FALSE, &tp, 0, NULL, NULL);
hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
BOOL result = TerminateProcess(hProcess, uExitCode);
CloseHandle(hProcess);
return result;
}
As you can tell from the above, I am not sure which dwDesiredAccess I am supposed to use. PROCESS_ALL_ACCESS lets me terminate other simple processes, such as chrome.exe or notepad.exe, but not what i need to terminate. ACCESS_SYSTEM_SECURITY is whats' said to be used by microsoft, but then i cannot terminate any process at all.
Also, is this even close to what i need to do, if not can you please point me in the right direction.
UPDATE: Added UpdatePrivilege() function from here, and called it before calling OpenProcess() function, still no bite.
UPDATE 2: The process I need to terminate is the "afwServ.exe" process, i.e. Avast's firewall. I need to do this so that my firewall is the sole firewall used by Windows. This a requirement of my project.
I think you misunderstood the difference between right and privileges, this answer explains it.
In this case, your current user may not have permission to terminate the specified process. You may first need to use LogonUser and use administrator identity to simulate the process context.
What you want to terminate is a service, you can try using ControlService with SERVICE_CONTROL_STOP to stop the services. But you also need to switch to the user with permission first.

Proper use of RegOpenCurrentUser

I went here and made a test program to see if it actually disables the task manager. Basically a simple bool switch on then switch off to see if the task manager was actually disabled. It works as intended when i compiled and ran it.
Edit: the code now looks like this
#include <iostream>
#include <Windows.h>
using namespace std;
void LockTaskManager(bool Lock);
void main(void) {
LockTaskManager(true);
cout << "Testing task manager disable." << endl;
getchar();
LockTaskManager(false);
cout << "Testing task manager enabled." << endl;
getchar();
}
void LockTaskManager(bool Lock)
{
HKEY currKey;
DWORD dwDisposition;
DWORD dwType, dwSize;
DWORD value;
if (Lock)
value = 1;
else
value = 0;
LRESULT lResult = RegOpenCurrentUser(KEY_WRITE, &currKey);
if (RegCreateKeyEx(currKey,
TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\system"),
0,
NULL,
0,
KEY_SET_VALUE,
NULL,
&currKey,
&dwDisposition) == ERROR_SUCCESS)
{
dwType = REG_DWORD;
dwSize = sizeof(DWORD);
RegSetValueEx(currKey, TEXT("DisableTaskMgr"), 0, dwType, (PBYTE)&value, dwSize);
RegCloseKey(currKey);
}
}
However, after i moved the .exe to a guest user on the same computer, it does not disable the task manager. So i went to look into how and why it worked, from microsoft's MSDN here i found that HKEY_CURRENT_USER does not change and requires the use of RegOpenCurrentUser to set the current key to the user that is running that program. i know about this post but the answer is not conclusive.
So with that said, I wish to know the correct way to approach this. The goal here is to make whoever that runs the .exe of this code be unable to run task manager.
FYI, Just so whoever reads this knows, i intend to use this as a defense mechanism in the event a flag is triggered, i want to stop malicious entities from killing this process through task manager.
what is HKEY_CURRENT_USER ? this is really \REGISTRY\USER\<UserSid> where <UserSid> some sid. when process first time use HKEY_CURRENT_USER (root of the current user key is yet not opened) system query current user sid ( TokenUser ), convert sid to string, append \REGISTRY\USER\ prefix, open and cache opened key. when process next time use, HKEY_CURRENT_USER - used already opened and cached key. even if thread is impersonating - this change nothing. however some time we need access different user key, after impersonating. exactly for this situation and RegOpenCurrentUser and used. this api query current thread (or process) token for TokenUser, format path based on current user sid, open \REGISTRY\USER\<UserSid1> and return handle to you. it not cache this handle, instead you must close it, when you no longer need the returned handle.
so at first senseless use RegOpenCurrentUser if you not impersonating current thread.
at second, this code always senseless:
LRESULT lResult = RegOpenCurrentUser(KEY_READ, &hkey);
if (RegCreateKeyEx(HKEY_CURRENT_USER,..
you not use returned hKey anyway. what sense open it in this case ?
need use it in place HKEY_CURRENT_USER !
LRESULT lResult = RegOpenCurrentUser(KEY_READ, &hkey);
if (RegCreateKeyEx(hKey,..
why code not worked under guest ? when you call RegCreateKeyEx and resulting key (system in your case) yet not exist - you need have KEY_CREATE_SUB_KEY access to the parent (Policies key). however by default guest have not any write access to key. you simply have not KEY_CREATE_SUB_KEY access. and KEY_SET_VALUE you also have not. sure that under guest call RegCreateKeyEx return ERROR_ACCESS_DENIED to you.

Problems with some system default .lnk-files launching from under an impersonated user

I'm writing the 32bit service app where I want to be able to launch Start menu items for the logged users. I did manage to accomplish this task by impersonating user and launching selected .lnk-file using CreateProcessAsUser with command-line: %windir%\system32\cmd /c " start /b /i "" "<path-to-lnk-file>" ". And it works for almost every shortcut except bunch of the system shortcuts from Accessories folder (e.g. Sticky Notes.lnk, Snipping Tool.lnk). During the launch of the Snipping Tool I'm receiving the message box with this error from cmd:
Windows cannot find 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Snipping Tool.lnk'. Make sure you typed the name correctly, and then try again.
But the .lnk-file exists in this very directory!
Summary:
service is 32 bit
Windows 8 Pro x64
launching shortcuts by user impersonation and CreateProcessAsUser with command-line %windir%\system32\cmd /c " start /b /i "" "<path-to-lnk-file>" "
approach works for almost every shortcut in Start menu except some in Start/Accessories folder (not all of them, e.g. Paint.lnk opens fine)
example code:
int launchAppForCurrentLoggedUser()
{
HANDLE userToken = WTSApiHelper::currentLoggedUserToken();
if (userToken == INVALID_HANDLE_VALUE) {
return -1;
}
//Duplicating token with access TOKEN_DUPLICATE | TOKEN_ALL_ACCESS,
//impersonation level SecurityImpersonation and token type TokenPrimary.
//Also closing original userToken
HANDLE dup = WTSApiHelper::duplicateToken(userToken);
if (dup == INVALID_HANDLE_VALUE) {
return -1;
}
int res = -1;
uint8 *env = NULL;
BOOL succeeded = CreateEnvironmentBlock((LPVOID *)&env, dup, FALSE);
if (!succeeded) {
Log("failed to get environment variables for user (error 0x%x).", GetLastError());
}
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(PROCESS_INFORMATION));
STARTUPINFOW si;
memset(&si, 0, sizeof(STARTUPINFOW));
si.cb = sizeof(STARTUPINFOW);
si.lpDesktop = L"winsta0\\Default";
WCHAR params[] = L"/c \" start /b /i \"\" \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories\\Snipping Tool.lnk\" \" ";
WCHAR cmd[] = L"C:\\Windows\\system32\\cmd.exe";
DWORD flags = env ? CREATE_UNICODE_ENVIRONMENT : 0;
succeeded = CreateProcessAsUserW(dup, cmd, params, NULL, NULL, FALSE, flags | CREATE_NO_WINDOW, env, NULL, &si, &pi);
if (!succeeded) {
Log("cannot launch process for user with error 0x%x.", GetLastError());
} else {
nres = 0;
}
DestroyEnvironmentBlock(env);
CloseHandle(dup);
return nres;
}
What do I miss here?
It's not the LNK file that is missing, but its target.
Seems like a WOW64 issue -- for your 32-bit service, %WINDIR%\System32 actually redirects to SysWOW64 and these executable files do not exist there.
Well, actually your 32-bit service is finding the 32-bit cmd.exe which does exist in SysWOW64, and then 32-bit cmd.exe has the above problem, when looking up the path %windir%\system32\SnippingTool.exe found in the shortcut .
I can reproduce the problem using a 32-bit Command Prompt. 32-bit processes attempting to use these shortcuts simply fail.
Try spawning the native version of cmd.exe (64-bit on your system), using %WINDIR%\SysNative\cmd.exe
In addition, you have quoting problems. You're trying to nest quotes, but what actually happens is that the second quote matches the first quote and exits quoting, rather than nesting.
In the future, when things fail in a service it is helpful to run the same call from a normal console application. In this case you would have immediately discovered that the issue is completely unrelated to impersonation. Second step, if it worked from a console application running in-profile would be using "Run As" with the console application, to test impersonation logic, still without the additional complexity of the service environment.
CreateProcessAsUser does not load the specified user's profile into the HKEY_USERS registry key. Therefore, to access the information in the HKEY_CURRENT_USER registry key, you must load the user's profile information into HKEY_USERS with the LoadUserProfile function before calling CreateProcessAsUser. Be sure to call UnloadUserProfile after the new process exits.
According to the msdn page
MSDN suggests to use CreateProcessWithLogonW or CreateProcessWithTokenW, or to manually load the user's profile information.
And also:
CreateProcessAsUser allows you to access the specified directory and executable image in the security context of the caller or the target user. By default, CreateProcessAsUser accesses the directory and executable image in the security context of the caller. In this case, if the caller does not have access to the directory and executable image, the function fails. To access the directory and executable image using the security context of the target user, specify hToken in a call to the ImpersonateLoggedOnUser function before calling CreateProcessAsUser.

Genearting screenshot (BMP) with Windows Service and CreateProcessWithLogonW

I'm trying to create a screenshot application using windows service, that can capture complete screen at regular time intervals. To get started with, I took basic window service example (http://code.msdn.microsoft.com/windowsdesktop/CppWindowsService-cacf4948) and included screenshot utility class in the project and called the capture function in the onStart method of sample windows service. However, I got blank screen shots. Later I realized that windows service runs in different session and hence the screenshots are blank.
S, I decoupled the screenshot utility class and made it as a seperate project and generated a .exe out of it (which is capable of writing logs to a file where ever needed). This time, I used createProcessWithLogonW function to call the executable, so that it will run the exe in specified user domain than the default service session. The following is the code snippet I have been using.
void CSampleService::StartProcess()
{
DWORD dwSize; HANDLE hToken=NULL;
LPVOID lpvEnv; PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
WCHAR szUserProfile[256] = L"";
si.cb = sizeof(STARTUPINFO);
if (!CreateEnvironmentBlock(&lpvEnv, hToken, TRUE))
{
logger::Wlog(logger::fileName,"CreateEnvironmentBlock Error");
}
dwSize = sizeof(szUserProfile)/sizeof(WCHAR);
GetUserProfileDirectory(hToken, szUserProfile, &dwSize);
CreateProcessWithLogonW(L"admin", L"MyDomain",L"mypassword",
LOGON_WITH_PROFILE, NULL, L"C:\\Temp\\application.exe",
CREATE_UNICODE_ENVIRONMENT, lpvEnv, NULL, &si, &pi);
DestroyEnvironmentBlock(lpvEnv);
CloseHandle(hToken);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
I have no errors from the logs i got. when the services get initiated, it gives a warning of interactive service trying to show a message. When I click that, I received a black screenshot again. I know that there are many things involved here, I just gave a high level view of what I'm doing. Also, i'm pretty new to windows programming. Any kind of help is appreciated.
yes, i got that working. we have to acquire the active session-ID (perhaps from opened desktop) and run the process as interactive service in that session context. This should probably solve the capturing screenshots issue. If someone is looking for GUI and service interactions perhaps IPC will be the most convenient way.

How can I assign the current process to a newly created job object?

I can't seem to use the AssignProcessToJobObject function to assign the current process to a job object handle given by CreateJobObject. This has been asked a few times already on StackOverflow, but so far none of the solutions (which usually boil down to embedding an UAC manifest) seem to work for me.
I'm using MSVC9 on Windows 7 for this. Here's the source code for my sample application and a small manifest I'm embedding (which supposedly fixes the problem - but not for me):
My sample application (main.cpp):
#include <windows.h>
static void dumpLastError()
{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
OutputDebugStringA( (LPTSTR)lpMsgBuf );
LocalFree(lpMsgBuf);
}
int main()
{
HANDLE job = CreateJobObjectA( NULL, "demo job 123" );
if ( !job ) {
OutputDebugStringA( "CreateJobObject failed" );
dumpLastError();
return 1;
}
if ( !AssignProcessToJobObject( job, GetCurrentProcess() ) ) {
OutputDebugStringA( "AssignProcessToJobObject failed" );
dumpLastError();
return 1;
}
return 0;
}
The UAC manifest (main.exe.manifest):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
<ms_asmv3:security>
<ms_asmv3:requestedPrivileges>
<ms_asmv3:requestedExecutionLevel level="requireAdministrator"/>
</ms_asmv3:requestedPrivileges>
</ms_asmv3:security>
</ms_asmv3:trustInfo>
</assembly>
I build this sample by running
cl main.cpp
mt -manifest main.exe.manifest -outputresource:main.exe;1
Unfortunately, running my main.exe sample after these steps still yields an 'Access denied' error in debug output when attempting the AssignProcessToJobObject call. Does anybody know why that is?
I know this is an old question, but I was recently having the exact same problem.
As suggested I was using the command-line workaround until a couple of minutes ago I found this post.
Since I was creating the process, I just followed the instructions on the article adding CREATE_BREAKAWAY_FROM_JOB to the process creation flags:
CreateProcess(szPath, NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)
with
CreateProcess(szPath, NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi)
I tested and it works as expected, the process gets assigned to the job, no UAC manifest, no command-line.
Hope it helps you or anyone else having this problem.
I followed a number of discussions at one point relating to job objects and UAC manifests. The only piece of information that helped me around the same problem that you're having was that this security feature (introduced in Vista) is apparently not enforced when run from a cmd.
I assume you're launching PyCmd from the start menu. Try launching it from cmd, and I'll bet the problem will go away there too.
What I ended up doing (to run mintty with cygwin) was to make a mintty.bat which said
start mintty.exe
and then a shortcut to mintty.bat which had a property set to run 'minimized' (I forget the exact wording). This allows the shell I wanted to be launched from the start menu and still work as if it had been launched from cmd.exe.
As a side-note, I'd love it if someone would come along and actually explain the problem at a build-level, and how to fix it.