Click here to Skip to main content
Click here to Skip to main content
Go to top

Service Hiding

, , 8 Dec 2009
Rate this:
Please Sign up or sign in to vote.
In this article, we continue investigation in the field of hiding application in the system. This article will tell where Windows OS stores the services and how uses them. We’ll discuss how this knowledge can be applied to finding our custom service and hiding it.

Table of Contents

  1. Introduction
  2. List of Services
    1. services.exe
    2. Signatures
    3. Structure of SERVICE_RECORD
    4. Life After Windows Vista
  3. Patching
    1. Search of Signatures
    2. Service Hiding
    3. Correct Restoration
  4. Structure of the Project Files
  5. References
  6. History

1. Introduction

In this article, we continue investigation in the field of hiding application in the system. This theme was started by Ivan Romanenko and Sergey Popenko in the article “Driver to Hide Processes and Files”. Our aim is to discover the ways of application hiding in the system for the wide audience. The approaches described can be used in the Corporate Security systems development – to hide system agents and prevent switching them off by users. Information can also be useful for those who research harmful software - to build the adequate answer for the threats.

This article will tell where Windows OS stores the services and how it uses them. We’ll discuss how this knowledge can be applied to finding our custom service and hiding it.

So let’s start our research.

2. List of Services

At the first step of my research, I thought that if there were services then somewhere their manager had to be. And it proved to be true – such a manager is in the file named services.exe.

2.1. services.exe

The process services.exe deals with all operations associated with services, service manager – which is familiar to many developers by the ::OpenSCManager function, and obviously with the service storing too.

So my first task is to find where the service database is stored.

My analysis showed that we need ScInitDatabase function and it includes such assembler instructions:

ScInitDatabase  proc near              
  xor     eax, eax
  push    esi
  mov      g_uScTotalNumServiceRecs, eax
  mov     g_ImageDatabase, eax ;  _IMAGE_RECORD ImageDatabase
  mov     g_pImageDatabase, eax ;  Pointer to first _IMAGE_RECORD
  mov      g_ServiceDatabase, eax ; _SERVICE_RECORD ServiceDatabase
  mov      g_pServiceDatabase, eax ; Pointer to first _SERVICE_RECORD
  call     ?ScInitGroupDatabase@@YGXXZ ; ScInitGroupDatabase(void)
  mov     esi,  ds:__imp__RtlInitializeResource@4 ; RtlInitializeResource(x)
  push    offset  ?ScServiceRecordLock@@3VCServiceRecordLock@@A ; 
		CServiceRecordLock  ScServiceRecordLock
  mov     ?ResumeNumber@@3KA, 1 ;  ulong ResumeNumber
  call    esi ;  RtlInitializeResource(x) ; RtlInitializeResource(x)
  push    offset  ?ScServiceListLock@@3VCServiceListLock@@A ; 
		CServiceListLock  ScServiceListLock
  call    esi ;  RtlInitializeResource(x) ; RtlInitializeResource(x)
  push    offset  ?ScGroupListLock@@3VCGroupListLock@@A ; CGroupListLock  ScGroupListLock
  call    esi ;  RtlInitializeResource(x) ; RtlInitializeResource(x)
  call     ?ScGenerateServiceDB@@YGHXZ ; ScGenerateServiceDB(void)
  neg     eax
  sbb     eax, eax
  neg     eax
  pop     esi
  retn
  ScInitDatabase  end

I marked out some lines with red, they are suspected to store the data we need – service database g_pServiceDatabase. On the basis of the places where ScCreateServiceRecord and ScGetNamedServiceRecord variables are used, we can say that it is exactly what we are looking for – service database and the pointer to its beginning.

2.2. Signatures

Without a second thought, we take this method as a basis as it is called only once at the program start, and choose signature to search pointer to the beginning of the list of services in the process memory.

Let’s consider the first variant A3 9C A0 01 01 A3 98 A0 01 01 E8 B5 08 00 00, which is just the assembler code of the three mentioned lines:

  mov      g_ServiceDatabase, eax ; _SERVICE_RECORD ServiceDatabase
  mov      g_pServiceDatabase, eax ; Pointer to first _SERVICE_RECORD
  call     ?ScInitGroupDatabase@@YGXXZ ; ScInitGroupDatabase(void)

To use this signature, we should guarantee that:

  • All versions of services.exe have the same signature in the proper place
  • All versions of services.exe have only one such signature

I developed the simple utility based on the code of FindSignature function (plServicesSiganture.cpp):

unsigned char g_ServicesDBSignature[]  =
    { 0xA3, 0x9C, 0xA0, 0x01, 0x01, 0xA3, 0x98, 0xA0, 
	0x01, 0x01, 0xE8, 0xB5, 0x08, 0x00, 0x00 };
unsigned char g_ServicesDBSignatureMask[] = 
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

  int
  FindSignature ( char* pBaseAddr, unsigned long ulSectionSize, 
			unsigned  long *pulSignatureOffset  )
  {
      char*  pFoundPos = 0;
      char*  pCheckPos = 0;
  
      MASK_BUFFER  bufMask = { (char*)g_ServicesDBSignature, 
                               (char*)g_ServicesDBSignatureMask, 
      sizeof  ( g_ServicesDBSignature ) / sizeof ( g_ServicesDBSignature[0]  ) };
  
      // find first occurrence of  signature
      BUFFER  bufMemory = { pBaseAddr,  ulSectionSize };
      if  ( PL_STATUS_SUCCESS != PlSearchSequence ( &bufMask,
                                                    &bufMemory,
                                                    &pFoundPos ) )
      {
            return  0;
      }
  
      // find second occurrence of  signature – exception situation
      bufMemory.pStart = pFoundPos  + 1;
      bufMemory.nSize = ulSectionSize  - (pFoundPos - pBaseAddr  + 1);
      if  ( PL_STATUS_SUCCESS == PlSearchSequence ( &bufMask,
                                                    &bufMemory,
                                                    &pCheckPos ) )
      {
           return  0;
      }
  
      *pulSignatureOffset  = (unsigned long)(pFoundPos - pBaseAddr);
      return 1;
  }

I started with services.exe for Windows XP and then checked all mentioned requirements for the signature for the rest of the services.exe versions for other OS versions. That gave me the final variant of the signature, which suited all versions and requirements:

68 XX XX  XX XX C7 XX XX  XX XX XX 01 00 00 00 

where XX is any value that means that we should search the signature by mask.

This signature is pointing to the execution of the following commands from ScInitDatabase function:

  push    offset  ?ScServiceRecordLock@@3VCServiceRecordLock@@A ; 
		CServiceRecordLock  ScServiceRecordLock
  mov     ?ResumeNumber@@3KA, 1 ; ulong  ResumeNumber 

2.3.Structure of SERVICE_RECORD

Service database is the list of structures SERVICE_RECORD, which contain necessary description of each service for the system. Here we won’t discuss the complete structure SERVICE_RECORD as far as it’s not necessary for us.

My further research discovered three fields:

  • Prev – pointer to the previous list element
  • Next – pointer to the next list element
  • ServiceName – pointer to the string with the service name

The offsets of these fields relative to the SERVICE_RECORD structure beginning are:

  • Prev – 0x00
  • Next – 0x04
  • ServiceName – 0x08

2.4. Life After Windows Vista

Windows Vista release “changed the world” – and it affected services.exe also.

The search for the list beginning changed and so did the structure of SERVICE_RECORD. Now dereferencing should be applied to the pointer to the service list beginning (it must be the common improved protection of the pointers in the process memory) and also I had to look for the new offsets of Next, Prev and ServiceName fields.

Here are these offsets:

  • Prev – 0x00
  • Next – 0x60
  • ServiceName – 0x04

Changes in the logic of the database beginning search are reflected in the functions SearchForServicesDbOffset_EarlierVista and SearchForServicesDbOffset_Vista (in the file plServicesSignature.cpp).

3. Patching

Below we’ll discuss the difficulties of the practical implementation.

3.1 Search of the Signatures

To search for the signatures, we should look through the memory of the “services.exe” process and detect a signature. So we examine all memory allocated by this process using VirtualQueryEx and ReadProcessMemory functions:

MEMORY_BASIC_INFORMATION mbi;
if ( ::VirtualQueryEx ( hProcess, 0, &mbi, sizeof(mbi) ) != sizeof(mbi) ) 
    throw std::exception ( "Error on VirtualQueryEx" );

void* pAddr = mbi.AllocationBase;
do
{
    MEMORY_BASIC_INFORMATION mbi1;
    if ( ::VirtualQueryEx ( hProcess, pAddr, &mbi1, sizeof(mbi1) ) != sizeof(mbi1) ) 
        throw std::exception ( "Error on VirtualQueryEx" );

    if ( mbi1.RegionSize != 0 )
    {
        std::vector< unsigned char > bufMemory ( mbi1.RegionSize );
        SIZE_T nBytesRead = 0;
        if ( ::ReadProcessMemory ( hProcess, pAddr, &bufMemory[0], 
		mbi1.RegionSize, &nBytesRead ) )
        {
		    //Here you can search for your data
        }
    }
    else
    {
        throw std::exception ( "No more process memory" );
    }

    pAddr = (PBYTE)pAddr + mbi1.RegionSize;

} while ( true );

After that, we find the beginning of the service list by the signature.

3.2. Service Hiding

Now we should only find our record in the list and redefine the Next pointer of the previous record to the record which is next to our one, and correspondingly the Prev pointer of the next to our record should be redefined to the record which is previous to our one:

Drawing1.jpg

// find sevice by ServiceName
    PUCHAR pServiceRecord = 0;
    if ( !LookupServicesDbRecordByName 
	( hProcess, pServiceDb, pServicesContext, pwstrServiceName, &pServiceRecord ) )
    {
        false;
    }

    std::vector< unsigned char > buffer 
	( pServicesContext->m_OffsetFncList.m_fncGetEstimatedSize () );
    if ( !::ReadProcessMemory ( hProcess, pServiceRecord, &buffer[0], 
	pServicesContext->m_OffsetFncList.m_fncGetEstimatedSize (), 0 ) )
    {
        throw std::exception ( "Error on ReadProcessMemory" );
    }

    // Get address of Next and Prev records
    PUCHAR pPrevServiceRecord = (unsigned char*)*(PULONG)pServicesContext->
		m_OffsetFncList.m_fncGetOffset_Prev ( &buffer[0] );
    PUCHAR pNextServiceRecord = (unsigned char*)*(PULONG)pServicesContext->
		m_OffsetFncList.m_fncGetOffset_Next ( &buffer[0] );

    if ( pPrevServiceRecord )
    {
        // PrevRecord->Next = NextRecord

        ulPatchAddr = (ULONG)pNextServiceRecord;
        PUCHAR pPrevNextServiceRecord = (PUCHAR)pServicesContext->
		m_OffsetFncList.m_fncGetOffset_Next ( pPrevServiceRecord );

        SIZE_T szWritten = 0;
        if ( !::WriteProcessMemory ( hProcess, 
                                     pPrevNextServiceRecord,
                                     &ulPatchAddr,
                                     sizeof (ulPatchAddr),
                                     &szWritten ) )
        {
            throw std::exception ( "Error on WriteProcessMemory" );
        }
    }

    if ( pNextServiceRecord )
    {
        // NextRecord->Prev = PrevRecord

        ulPatchAddr = (ULONG)pPrevServiceRecord;
        PUCHAR pNextPrevServiceRecord = (PUCHAR)pServicesContext->
		m_OffsetFncList.m_fncGetOffset_Prev ( pNextServiceRecord );
        if ( !::WriteProcessMemory ( hProcess, 
                                     pNextPrevServiceRecord,
                                     &ulPatchAddr,
                                     sizeof (ulPatchAddr),
                                     0 ) )
        {
            throw std::exception ( "Error on WriteProcessMemory" );
        }
    }

After these manipulations, our service disappears.

3.3 Correct Restoration

Hiding is good but I should tell about restoration too to complete the story.

Why restore? To delete service correctly by means of corresponding DeleteService function.

My proposition is to create a callback function that returns the address of the hidden service in the memory of the “services.exe” process after the hiding. Then we will be able to easily restore the services sequence when we need it.

We should remember that the system is permanently changing and Prev and Next pointers of the hidden record cannot be the “live” records in the service list already.

That’s why while restoring, we should make such steps:

  • Search the record which corresponds to the Prev address; if there is such a record, then add element to the list after the specified element (InsertAfter).
  • If the record is not found, then search the record by the corresponding Next address. If it is found, then add element to the list before the specified element (InsertBefore).
  • If the record is not found by Next pointer, then simply add the service to the end of the service list (InsertAtTheEnd).
std::vector< unsigned char > buffer 
( pServicesContext->m_OffsetFncList.m_fncGetEstimatedSize () );
if ( !::ReadProcessMemory ( hProcess, pServiceRecord, &buffer[0], 
	pServicesContext->m_OffsetFncList.m_fncGetEstimatedSize (), 0 ) )
{
    throw std::exception ( "Error on ReadProcessMemory" );
}

PUCHAR pPrevServiceRecord = (unsigned char*)*(PULONG)pServicesContext->
	m_OffsetFncList.m_fncGetOffset_Prev ( &buffer[0] );
if ( pPrevServiceRecord )
{
    bool bFound = LookupServicesDbRecordByAddr ( hProcess, pServiceDb, 
    pServicesContext, pPrevServiceRecord, &pPrevServiceRecord );
    if ( bFound )
    {
        InsertAfterPreviousRecord ( hProcess, pServicesContext, 
        pServiceRecord, pPrevServiceRecord );
        return true;
    }
}

PUCHAR pNextServiceRecord = (unsigned char*)*(PULONG)pServicesContext->
m_OffsetFncList.m_fncGetOffset_Next ( &buffer[0] );
if ( pNextServiceRecord )
{
    bool bFound = LookupServicesDbRecordByAddr ( hProcess, pServiceDb, 
    pServicesContext, pNextServiceRecord, &pNextServiceRecord );
    if ( bFound )
    {
        InsertBeforeNextRecord ( hProcess, pServicesContext, 
        	pServiceRecord, pNextServiceRecord );
        return true;
    }
}

bool bFound = LookupServicesDbRecordByEnd ( hProcess, pServiceDb, 
	pServicesContext, &pPrevServiceRecord );
if ( !bFound )
{
    return false;
}

InsertAsLastRecord ( hProcess, pServicesContext, pServiceRecord, pPrevServiceRecord );
return true;

4. Structure of the Project Files

The source code of the usermode application which illustrates everything told here is in the archive attached.

5. References

Jeffrey Richter, Christophe Nasarre. Windows via C/C++. Especially the chapter devoted to the Virtual memory research.

6. History

  • 8th December, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Authors

Apriorit Inc
Apriorit Inc.
Ukraine Ukraine
ApriorIT is a Software Research and Development company that works in advanced knowledge-intensive scopes.
 
Company offers integrated research&development services for the software projects in such directions as Corporate Security, Remote Control, Mobile Development, Embedded Systems, Virtualization, Drivers and others.
 
Official site http://www.apriorit.com
Group type: Organisation

31 members

Follow on   LinkedIn

Eugene Wineblat
Software Developer (Senior) ApriorIT
Ukraine Ukraine
Senior Software Developer of Apriorit Inc.
My favorite tasks are multithreading, networking and WDK.
 
... But in free time : beer, meat, travelling and photo...)
 
LinkedIn Profile : Wineblat Eugene

Comments and Discussions

 
Generalcool Pinmembercute_friend707725-May-10 19:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 8 Dec 2009
Article Copyright 2009 by Apriorit Inc, Eugene Wineblat
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid