Windows Service 를 이용하는 PE 파일 분석

일부 악성 PE 파일은 Windows OS 의 Service 기능을 이용하여 실행된다. 이런 파일들은 단순히 더블 클릭하는 것만으로는 서비스 루틴 코드를 실행할 수조차 없기 때문에 분석에 애를 먹을 수 있다. 이 글에서는 Windows Service 를 이용하는 PE 파일을 실행하기 위해 서비스를 관리하고, 디버깅하는 방법에 대해 다룬다.
Windows Service
MSDN 에 따르면, Windows Service 는 사용자의 상호 작용 없이 작동하도록 디자인된 user-mode 프로세스이다. Windows Service 는 시스템 부팅과 함께, 또는 다른 애플리케이션에서 Win32 API 를 이용하여 서비스 함수를 호출하여 실행된다. 또한 사용자가 직접 서비스 컨트롤 패널을 이용하여 수동으로 실행할 수도 있다. 모든 서비스는 Service Control Manager(SCM) 의 인터페이스 규칙을 따라야 한다.
Windows Vista 이전의 서비스는 일반적인 프로세스와 마찬가지로 동일한 세션에서 실행되었다. 하지만 Windows Vista 이상의 버전에서는 서비스를 제외한 모든 프로세스가 Session 0에서 제외되었다.
서비스 실행 흐름도

서비스 실행 파일은 일반적인 exe 파일의 시작 지점인 EntryPoint 를 실행할 Main Thread 이외에도, Service Thread 가 필요하다. Main Thread 는 StartServiceCtrlDispatcher()를 호출하여 실행할 서비스의 이름과 EntryPoint 리스트를 전달한다. 해당 함수에 전달되는 첫 번째 인자 lpServiceStartTable
의 구조체는 다음과 같다.
typedef struct _SERVICE_TABLE_ENTRYA {
LPSTR lpServiceName;
LPSERVICE_MAIN_FUNCTIONA lpServiceProc;
} SERVICE_TABLE_ENTRYA, *LPSERVICE_TABLE_ENTRYA;
StartServiceCtrlDispatcher()
는 lpServiceStartTable
에 등록된 각 Service EntryPoint 를 실행하는 스레드를 생성한다.
생성된 서비스 스레드는 SCM 으로부터 전달받은 control 을 처리할 프로시저를 등록하기 위해 RegisterServiceCtrlHandler()
함수를 호출한다. 이 함수는 2 번째 인자로 lpHandlerProc
주소 값을 받으며, 해당 주소의 프로시저에서 SetServiceStatus()
를 구현하여 SCM 으로부터 전달받은 제어를 처리해야 한다. 또한 첫 번재 인자인 lpServiceName
는 서비스의 이름을 지정하며, 만약 이 값이 실제 서비스의 이름과 일치하지 않으면 핸들러가 SCM 가 정상적으로 소통할 수 없다.
DLL 파일의 경우는 StartServiceCtrlDispatcher()
을 호출할 필요 없이 ServiceMain()
함수에 RegisterServiceCtrlHandler()
함수와 SCM 과 커뮤니케이션할 핸들러를 구현하면 된다.
서비스 관리
sc.exe
프로그램은 서비스 생성, 삭제, 실행, 정지,등의 기능을 수행할 수 있다.
> sc create {서비스명} binpath= {서비스 실행 파일 경로}
> sc delete {서비스명}
> sc start {서비스명}
> sc stop {서비스명}

services.msc
는 등록된 서비스 리스트 출력 및 컨트롤할 수 있다.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services
레지스트리에는 등록된 서비스와 관련된 정보가 저장된다.

레지스트리에 포함되는 정보에는 서비스 실행 파일의 경로, 실행 시 전달되어야 할 인자 값, 서비스 타입, 등이 존재한다. 다음은 서비스 Key 에 생성되는 주요 Value 와 데이터의 의미를 보여준다.
ImagePath
: 서비스가 실행할 실행 파일, 또는 드라이버 파일 경로.Type
: 서비스 타입.
Type | Description | |
---|---|---|
SERVICE_KERNEL_DRIVER (1) | 디바이스 드라이버 | |
SERVICE_FILE_SYSTEM_DRIVER (2) | 커널 모드 파일 시스템 드라이버 | |
SERVICE_ADAPTER (4) | 더 이상 쓰이지 않음. | |
SERVICE_RECOGNIZER_DRIVER (8) | 파일 시스템 recognizer 드라이버. | |
SERVICE_WIN32_OWN_PROCESS (16) | 단일 서비스만 호스트하는 서비스 프로세스. | |
SERVICE_WIN32_SHARE_PROCESS (32) | 다수의 서비스를 호스트하는 서비스 프로세스. | |
SERVICE_INTERACTIVE_PROCESS (256) | 사용자 입력을 받을 수 있는 콘솔을 출력하는 서비스 프로세스. |
Start
: 서비스 실행 타입.
Type | Description |
---|---|
SERVICE_BOOT_START (0) | Ntldr, 또는 OsLoader 가 부팅 중 미리 로딩하여 SERVICE_ SYSTEM_START 타입의 서비스가 실행되기 전 초기화. |
SERVICE_SYSTEM_START (1) | 커널 초기화 중, SERVICE_BOOT_START 타입 서비스 이후에 초기화되는 서비스. |
SERVICE_AUTO_START (2) | SCM 프로세스, Services.exe 가 시작된 이후 초기화되는 서비스. |
SERVICE_DEMAND_START (3) | 사용자의 필요에 따라 SCM 에 의해 실행되는 서비스. |
SERVICE_DISABLED (4) | 비활성화된 서비스. |
Service 분석
EXE 파일 분석
ServicesPipeTimeout 레지스트리 설정
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control
레지스트리의 ServicesPipeTimeout
Key 값을 충분히 큰 값으로 설정해야 한다. 만약 값이 설정되어 있지 않을 경우, 디폴트 값으로 30000 밀리세컨드(30초) 로 지정된다.
해당 시간이 지나기 전에 StartServiceCtrlDispatcher()
API 가 호출되어야 한다.
> reg add HKLM\SYSTEM\CurrentControlSet\Control /v ServicesPipeTimeout /t REG_DWORD /d 86400000 /f

EntryPoint 에 무한 루프 설치
정상적으로 서비스 생성 후, 실행하면 우리가 디버깅할 틈도 없이 코드가 일사천리로 실행되어버릴 것이다. 따라서 분석할 파일의 EntryPoint 의 시작 코드를 무한 루프 jmp (0xEB FE)코드로 패치하여 분석가가 디버거를 Attach 할 때까지 더 이상 코드가 진행되지 않도록 할 것이다.
먼저 분석 파일의 EntryPoint 의 파일 offset 를 계산한다. 테스트 샘플의 EntryPoint RVA 는 0x2E0B 이며, 파일 오프셋은 0x220B 이다.

HxD 프로그램으로 해당 위치의 명령어를 0xEB FE 로 변경했다. 해당 위치의 원래 코드는 나중에 다시 원상복구해야 하기 때문에 기억해야 한다.

서비스 설치, 실행
실행 창(Windows + R) 에 services.msc
를 입력하여 SCM 에서 등록한 서비스를 실행할 수 있다.

혹은 sc
명령어로 실행 파일을 실행할 서비스를 등록하고, 실행한다.
> sc create {서비스명} binpath= {서비스 실행 파일 경로}
> sc start {서비스명}
디버거 Attach & 프로세스 패치
ServicesPipeTimeout
레지스트리 값을 적절하게 설정했다면 SCM 이 서비스가 Running 상태가 되기를 기다리고 있을 것이다. 설정한 시간 내에 EntryPoint 에 설치한 무한 루프를 본래 코드로 복구해야 한다.

이후 RegisterServiceCtrlHandler()
의 두 번째 인자 주소가 가리키는 프로시저에서 SetServiceStatus()
가 호출되어 서비스가 Running 상태가 되어야 비로소 서비스가 실행된 것으로 인식된다. 서비스가 Running 상태가 된 이후는 일반적인 실행 파일처럼 분석하면 된다.
DLL 파일 분석
Windows 서비스 기능을 제공하는 DLL 을 등록, 실행하기 위해서는 몇 가지 레지스트리를 추가로 설정해야 한다.
> sc create %SERVICENAME% binPath= "%SYSTEMROOT%\system32\svchost.exe -k %SERVICENAME%" type= share start= demand
또한 다음과 같은 레지스트리를 설정하여 서비스가 실행할 DLL 의 경로와, Export 함수 이름을 지정해야 한다.
Key | Value | Data Type | Data |
---|---|---|---|
HKLM\System\CurrentControlSet\Services\%SERVICENAME%\Parameters |
ServiceDLL |
REG_EXPAND_SZ |
등록할 DLL 경로 |
HKLM\System\CurrentControlSet\Services\%SERVICENAME%\Parameters |
ServiceMain |
REG_SZ |
서비스가 실행할 메인 export 함수 이름. 해당 Value 가 존재하지 않으면 기본적으로 ServiceMain 호출. |
HKLM\Software\Microsoft\Windows NT\CurrentVersion\SvcHost |
%SERVICEGROUP% |
REG_MULTI_SZ |
%SERVICENAME%\0 |
아래 명령어는 MALSERVICE
라는 이름의 서비스를 생성하여, svchost.exe
를 복사한 파일 malsvchost.exe
로 악성 DLL 파일 sample.dll
의 ServiceMain
export 함수를 실행하도록 설정한다. 굳이 svchost.exe
가 아니라 malsvchost.exe
파일을 생성한 이유는 이미 실행되고 있는 정상 svchost.exe
와 이름을 달리하여 진단 로그를 구분하기 쉽게 하기 위함이다.
다음은 32bit OS 에서 Service DLL 을 설정하는 bat 명령어이다. SERVICENAME
, SERVICEGROUP
, BINPATH
변수 값을 적절하게 설정하자.
@echo off
set SERVICENAME=MALSERVICE
set SERVICEGROUP=MALSERVICEGROUP
set BINPATH="C:\Windows\System32\sample.dll"
sc create %SERVICENAME% binPath= "%SYSTEMROOT%\system32\malsvchost.exe -k %SERVICEGROUP%" type= share start= demand
reg add "HKLM\System\CurrentControlSet\Services\%SERVICENAME%\Parameters" /v ServiceDLL /t REG_EXPAND_SZ /d %BINPATH% /f
reg add "HKLM\System\CurrentControlSet\Services\%SERVICENAME%\Parameters" /v ServiceMain /t REG_SZ /d ServiceMain /f
reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\SvcHost" /v %SERVICEGROUP% /t REG_MULTI_SZ /d "%SERVICENAME%\0" /f
reg add "HKLM\SYSTEM\CurrentControlSet\Control" /v ServicesPipeTimeout /t REG_DWORD /d 86400000 /f
만약 64bit Windows OS 에서 32비트 DLL 을 등록한다면 WOW64 경로를 고려해야 한다.
@echo off
set SERVICENAME=MALSERVICE
set SERVICEGROUP=MALSERVICEGROUP
set BINPATH="C:\Windows\SysWOW64\sample.dll"
sc create %SERVICENAME% binPath= "%SYSTEMROOT%\SysWOW64\malsvchost.exe -k %SERVICEGROUP%" type= share start= demand
reg add "HKLM\System\CurrentControlSet\Services\%SERVICENAME%\Parameters" /v ServiceDLL /t REG_EXPAND_SZ /d %BINPATH% /f
reg add "HKLM\System\CurrentControlSet\Services\%SERVICENAME%\Parameters" /v ServiceMain /t REG_SZ /d ServiceMain /f
reg add "HKLM\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\SvcHost" /v %SERVICEGROUP% /t REG_MULTI_SZ /d "%SERVICENAME%\0" /f
reg add "HKLM\SYSTEM\CurrentControlSet\Control" /v ServicesPipeTimeout /t REG_DWORD /d 86400000 /f