18
Hands-On Lab Background Services - .NET Lab version: 1.0.0 Last updated: 10/8/2009

Hands-On Labaz12722.vo.msecnd.net/windows7trainingcourse1-0/Labs/Background... · In this Hands-On Lab, you will learn how to: Design and implement a trigger-start service Minimize

Embed Size (px)

Citation preview

Hands-On Lab

Background Services - .NET

Lab version: 1.0.0

Last updated: 10/8/2009

Background Services

2

CONTENTS

OVERVIEW ............................................................................................................................................. 3

EXERCISE 1: TRIGGER-START SERVICE ............................................................................................ 7 Task 1 – Implement Service Configuration Changes.......................................................................... 7

Task 2 – Add Code to Register the Service as Trigger-Start ............................................................. 10

Task 3 – Enable the UsbCopyService to Trigger-Start ...................................................................... 12

SUMMARY ............................................................................................................................................ 18

Background Services

3

Overview

Windows 7 and Windows Vista have introduced many improvements in background processing. The

modern challenges of implementing efficient background processes are:

Performance – boot latency, logon latency, shutdown latency; background work interfering with

foreground processing

Power consumption

Security – increased attack surface

Windows 7 background services and scheduled tasks offer a variety of mechanisms for minimizing

power consumption, reducing the system’s attack surface and improving application and system

performance. Among these mechanisms are:

Service requested security privileges

Service SIDs

Delayed auto-start services

Trigger-start services

Scheduled tasks conditions and settings

With the abundance of services in any modern installation of Windows, background processing must be

designed to meet security, performance and power consumption requirements.

Objectives

In this Hands-On Lab, you will learn how to:

Design and implement a trigger-start service

Minimize the amount of privileges requested by a service

System Requirements

You must have the following items to complete this lab:

Microsoft Visual Studio 2008

Windows 7

Windows 7 SDK

Background Services

4

Windows Sysinternals Process Explorer

Setup

This lab requires that the Windows 7 SDK is correctly integrated with Visual Studio 2008. To do this you

can follow these steps:

1. From the Start Menu, go to All Programs | Microsoft Windows SDK v7.0 | Visual Studio

Registration and click Windows SDK Configuration Tool. On the Windows SDK Configuration

Tool dialog, verify that the v7.0 version is selected and then click Make Current.

Figure 1

Windows SDK Configuration tool

2. You also need update Visual C++ directories to point to the Windows 7 SDK headers, libraries

and tools. These paths should match your local Windows 7 SDK installation. To do this, from the

Microsoft Visual Studio 2008 menu, select Tools | Options to open the Options dialog. Expand

the Projects and Solutions node from the left panel, select VC++ Directories and follow these

steps:

a. From the right panel, select Executable files from the combo located at the upper-right

corner. Then select the button, browse to the %Windows7SDKInstallDir%\v7.0\Bin

folder and click Select Folder.

Background Services

5

Figure 2

Configuring the VC++ directories

Note: Windows 7 SDK is installed by default under the C:\Program Files\Microsoft

SDKs\Windows folder.

b. Repeat the previous step adding the %Windows7SDKInstallDir%\v7.0\Include folder for

the Include files option, and the %Windows7SDKInstallDir%\v7.0\Lib folder for the

Library Files option.

c. Finally, click OK to save the settings.

Additionally, this lab requires specific “hard coded” folders to work with that you need to create:

1. On the local C drive you need to create a C:\FromUSB folder to which all the images from the

USB key will be copied.

2. On the generic USB key you need to create a ToCopy folder from which all the images will be

copied from.

Note: The service demonstrated in this exercise is not well-designed with regard to the design goals for

background processes on Windows. It is a just sample application showing how to use the Trigger Start

Services in Windows 7. For more information on designing efficient background processes, consult the

Microsoft whitepaper “Designing Efficient Background Processes”.

Background Services

6

Background Services

7

Exercise 1: Trigger-Start Service

In this exercise, you will modify an existing service to be a better citizen of the operating system. You

will change the service’s requested security privileges to run with least privileges; and you will change

the service configuration so that it automatically starts when necessary instead of running and polling

the system in the background.

The sample service used for this purpose is a USB file copy service, which copies files from a specific

directory on any USB storage device inserted to the system. The techniques used in this exercise are

applicable to any service that can trigger-start when certain criteria are met.

Task 1 – Implement Service Configuration Changes

To modify service configuration programmatically, the Win32 API function ChangeServiceConfig2 must

be called with the appropriate parameters.

1. Open the starting solution Begin.sln located under the

%TrainingKitInstallDir%\BackgroundServices\Ex1-TriggerStartService\Begin folder, choosing

the language of your preference (C# or VB).

Note: Visual Studio 2008 must be run in elevated mode. To do this, right-click the Visual

Studio 2008 icon and select Run as Administrator.

2. In the ServiceControlInterop project, locate the ServiceControlInterop.cpp file, under the

Source Files folder, and add code in the RemoveAllPrivilegesFromService method to specify

that the service requires no privileges:

a. Allocate a SERVICE_REQUIRED_PRIVILEGES_INFO structure and set its

pmszRequiredPrivileges member to SE_CHANGE_NOTIFY_NAME L"\0".

b. Call the ChangeServiceConfig2 method with the

SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO information level and pass the address

of the structure allocated in the previous step.

Use the following code as a complete example (shown in bold):

C++

void ServiceControl::RemoveAllPrivilegesFromService(System::String^

serviceName)

{

....

if (hService == NULL)

{

Background Services

8

DWORD dwLastError = GetLastError();

CloseServiceHandle(hSCManager);

throw Marshal::GetExceptionForHR(dwLastError);

}

//This effectively strips out all other privileges.

SERVICE_REQUIRED_PRIVILEGES_INFO requiredPrivileges = {0};

requiredPrivileges.pmszRequiredPrivileges = SE_CHANGE_NOTIFY_NAME L"\0";

if (ChangeServiceConfig2(hService,

SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO, &requiredPrivileges) == FALSE)

{

DWORD dwLastError = GetLastError();

CloseServiceHandle(hService);

CloseServiceHandle(hSCManager);

throw Marshal::GetExceptionForHR(dwLastError);

}

CloseServiceHandle(hService);

CloseServiceHandle(hSCManager);

}

3. In the SetServiceTriggerStartOnUSBArrival method, add code to set the service to trigger-start

when a generic USB disk becomes available:

a. Allocate a SERVICE_TRIGGER_SPECIFIC_DATA_ITEM structure. To do this:

i. Set its dwDataType member to SERVICE_TRIGGER_DATA_TYPE_STRING.

ii. Set its cbData member to the length of the string L"USBSTOR\\GenDisk" in

bytes.

iii. Set its pData member to that string.

b. Allocate a SERVICE_TRIGGER structure. To do this:

i. Set its dwTriggerType member to

SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL.

ii. Set its dwAction member to SERVICE_TRIGGER_ACTION_SERVICE_START.

iii. Set its pTriggerSubtype member to the address of the GUID_USBDevice GUID.

iv. Set its cDataItems member to 1 and its pDataItems member to the address of

the structure allocated in the previous step.

c. Allocate a SERVICE_TRIGGER_INFO structure. To do this:

Background Services

9

i. Set set its cTriggers member to 1 and its pTriggers member to the address of the

structure allocated in the previous step.

d. Call the ChangeServiceConfig2 function with the SERVICE_CONFIG_TRIGGER_INFO

information level and pass to it the address of the structure allocated in the previous

step.

Use the following code as a complete example (shown in bold):

C++

void ServiceControl::SetServiceTriggerStartOnUSBArrival(System::String^

serviceName)

{

...

if (hService == NULL)

{

DWORD dwLastError = GetLastError();

CloseServiceHandle(hSCManager);

throw Marshal::GetExceptionForHR(dwLastError);

}

LPCWSTR lpszDeviceString = L"USBSTOR\\GenDisk";

SERVICE_TRIGGER_SPECIFIC_DATA_ITEM deviceData = {0};

deviceData.dwDataType = SERVICE_TRIGGER_DATA_TYPE_STRING;

deviceData.cbData = (wcslen(lpszDeviceString)+1) * sizeof(WCHAR);

deviceData.pData = (PBYTE)lpszDeviceString;

SERVICE_TRIGGER serviceTrigger = {0};

serviceTrigger.dwTriggerType =

SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL;

serviceTrigger.dwAction = SERVICE_TRIGGER_ACTION_SERVICE_START;

serviceTrigger.pTriggerSubtype = (GUID*)&GUID_USBDevice;

serviceTrigger.cDataItems = 1;

serviceTrigger.pDataItems = &deviceData;

SERVICE_TRIGGER_INFO serviceTriggerInfo = {0};

serviceTriggerInfo.cTriggers = 1;

serviceTriggerInfo.pTriggers = &serviceTrigger;

if (ChangeServiceConfig2(hService, SERVICE_CONFIG_TRIGGER_INFO,

&serviceTriggerInfo) == FALSE)

{

DWORD dwLastError = GetLastError();

CloseServiceHandle(hService);

CloseServiceHandle(hSCManager);

throw Marshal::GetExceptionForHR(dwLastError);

}

Background Services

10

CloseServiceHandle(hService);

CloseServiceHandle(hSCManager);

}

This completes the C++/CLI wrapper necessary for .NET interaction with the service trigger-start

APIs.

Task 2 – Add Code to Register the Service as Trigger-Start

Services can be registered as trigger-start from the sc.exe command line utility (using the sc triggerinfo

command from an elevated Command Shell), or using the ChangeServiceConfig2 API programmatically,

which we wrapped as part of the previous task.

1. In the RegisterService project, locate the RegisterServiceForm.cs (C#) or

RegisterServiceForm.vb (Visual Basic) code file and within it add code to the

btnRegisterTriggerStart_Click method to change the service configuration to trigger-start using

the C++/CLI wrapper, as follows (shown in bold):

C#

private void btnRegisterTriggerStart_Click(object sender, EventArgs e)

{

AddService();

ServiceControl.SetServiceTriggerStartOnUSBArrival(ServiceName);

StopLogReaderTimer();

StartLogReaderTimer();

}

Visual Basic

Private Sub btnRegisterTriggerStart_Click() Handles

btnRegisterTriggerStart.Click

AddService()

ServiceControl.SetServiceTriggerStartOnUSBArrival(ServiceName)

StopLogReaderTimer()

StartLogReaderTimer()

End Sub

2. In the btnRemovePrivileges_Click method, add code to change the service configuration to

request the minimum possible privileges using the C++/CLI wrapper, as follows:

C#

Background Services

11

private void btnRemovePrivileges_Click(object sender, EventArgs e)

{

ServiceControl.RemoveAllPrivilegesFromService(ServiceName);

}

Visual Basic

Private Sub btnRemovePrivileges_Click() Handles btnRemovePrivileges.Click

ServiceControl.RemoveAllPrivilegesFromService(ServiceName)

End Sub

You have now completed the changes to the service registration UI application so that the

service can be registered as a trigger-start service with the minimum amount of necessary

privileges.

Background Services

12

Task 3 – Enable the UsbCopyService to Trigger-Start

Unlike auto-start services which typically poll for interesting events using periodic timers, trigger-start

services act when a trigger starts them and then deactivate (stop) themselves when there is no more

work to perform.

1. In the UsbCopyService project, locate and open the USBService.cs (C#) or USBService.vb (Visual

Basic) code file.

2. (For Visual Basic users only) Add the ProcessTriggerStart method, which will be called when the

service is configured to trigger-start.

Visual Basic

Private Sub ProcessTriggerStart()

DoWork()

End Sub

3. Modify the OnStart method to check whether the service is configured to trigger-start using the

ServiceControl.IsServiceTriggerStart method (pass the ServiceName property inherited from

the ServiceBase class as the parameter). To do this, replace the method implementation with

the following code:

C#

protected override void OnStart(string[] args)

{

if (ServiceControl.IsServiceTriggerStart(ServiceName))

{

}

else

{

_timer = new Timer(delegate

{

DoWork();

});

_timer.Change(0, 5000);

}

}

Visual Basic

Protected Overrides Sub OnStart(ByVal args() As String)

If ServiceControl.IsServiceTriggerStart(ServiceName) Then

Else

_timer = New Timer(AddressOf ProcessTimerEvent)

_timer.Change(0, 5000)

End If

Background Services

13

End Sub

4. Now if the service is configured to trigger-start, instead of creating the periodic timer the

method will enqueue the work to be performed only once. To do this, add the following code

(shown in bold):

C#

protected override void OnStart(string[] args)

{

if (ServiceControl.IsServiceTriggerStart(ServiceName))

{

ThreadPool.QueueUserWorkItem(delegate

{

DoWork();

});

}

else

{

_timer = new Timer(delegate

{

DoWork();

});

_timer.Change(0, 5000);

}

}

Visual Basic

Protected Overrides Sub OnStart(ByVal args() As String)

If ServiceControl.IsServiceTriggerStart(ServiceName) Then

ThreadPool.QueueUserWorkItem(AddressOf ProcessTriggerStart)

Else

_timer = New Timer(AddressOf ProcessTimerEvent)

_timer.Change(0, 5000)

End If

End Sub

5. After completing the work, the service should stop using the Stop method inherited from the

ServiceBase class. To do this, add the following code (shown in bold) to the OnStart method

(C#) or to the ProcessTriggerStart method (Visual Basic):

C#

protected override void OnStart(string[] args)

{

if (ServiceControl.IsServiceTriggerStart(ServiceName))

{

Background Services

14

ThreadPool.QueueUserWorkItem(delegate

{

DoWork();

Stop();

});

}

else

{

...

}

}

Visual Basic

Private Sub ProcessTriggerStart()

DoWork()

Me.Stop()

End Sub

6. You are now ready to test the service. Run the RegisterService project.

Note: The project should be run as administrator. You can do this either from the elevated

Visual Studio instance or by running the executable elevated from the shell or command

prompt.

7. Register the service as demand-start by clicking the Register Demand-Start button and start it

by clicking the Start button. It will continuously poll for a USB device every 5 seconds. When a

USB device with the ToCopy directory is detected, it will copy that directory’s contents to the

C:\FromUSB directory.

Figure 3

Registering the service as demand-start

Background Services

15

8. Inspect the service’s access token by launching Sysinternals Process Explorer. Right-click on the

UsbCopyService.exe process in the process tree, select Properties and switch to the Security

tab. Under the service’s privilege list, many privileges are either enabled or just present

(disabled).

Figure 4

Inspecting service privileges

Note: Find the download link for Sysinternals Process Explorer (procexp.exe) in the

Prerequisites section of this hands-on lab.

9. Stop the service using the Stop button.

Background Services

16

10. Remove privileges from the service by clicking the Remove Privileges button.

11. Start the service by clicking the Start button and inspect its access token again using

Sysinternals Process Explorer. The service’s privilege list should now contain only the

SeChangeNotifyPrivilege (which is always retained for application compatibility purposes).

Figure 5

Service privileges (after removing privileges)

12. Stop the service using the Stop button and delete it using the Delete button.

13. Remove the USB Device from the computer.

14. Register the service as trigger-start by clicking the Register Trigger-Start button. Do not start

the service manually.

Background Services

17

15. Inspect the service’s trigger-start configuration by running the following command from an

elevated command prompt.

Command

sc qtriggerinfo UsbCopyService

Figure 6

Inspecting service's trigger-start configuration

16. Insert a USB device with a ToCopy directory. The service should automatically start, copy the

files and then shut itself down.

17. You can verify that the service is down by running the following command from an elevated

command prompt.

Command

sc queryex UsbCopyService

Figure 7

Verifying service is down

Background Services

18

18. When done, delete the service by clicking the Delete button.

19. You can ensure that the service has been deleted by running the same previous command from

an elevated command prompt.

Command

sc queryex UsbCopyService

Figure 8

Verifying service has been deleted

In this exercise, you have modified an existing service to trigger-start when a USB device is detected in

the system. You have also modified the service’s requested privilege list to the minimum necessary

amount of privileges, thus reducing the system’s attack surface. The full exercise solution can be found

in the %TrainingKitInstallDir%\BackgroundServices\Ex1-TriggerStartService\End directory, depending

on the language of your preference.

Summary

In this lab you have improved the performance and security of a Windows service to become a better

citizen of the operating system. You used the trigger-start service mechanism to launch the service only

when there is actual work for it to perform, and you have minimized the service’s attack surface by

removing unnecessary privileges from its process’ token.

You can find a full demo of the USB copy service and the Weather Updater service as part of this

Windows 7 course’s code samples.