일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- DebugBlocker
- 분석 보고서
- 리버싱
- Python
- Self-Creation
- CP949
- Dynamic안티디버깅
- 안티디버깅
- UTF-16
- 서비스디버깅
- IL코드
- CodeEngn
- 디버깅
- OllyDbg
- JIT
- UTF-32
- Advanced안티디버깅
- RedLine Stealer
- ImageSwitching
- Visual Studio
- hxd
- 서비스프로세스
- PEImageSwitching
- Static안티디버깅
- Exe2Aut
- x32dbg
- dotPeek
- PEImage
- 리버싱핵심원리
- .net
- Today
- Total
-榮-
디버깅 실습1 - 서비스 (서비스 프로세스 디버깅) 본문
1. 서비스 프로세스 동작 원리
- 서비스 시스템 개요
서비스(Service) 프로그램은 SCM(Service Control Manager)에 의해서 관리된다. 서비스 애플리케이션이 실행되기 위해서는 시작 명령을 내려줄 수 있는 서비스 제어기(Service Contro-ller)가 필요하다. 서비스 제어기는 SCM에게 서비스 제어를 요청하면 SCM이 서비스 프로그램에게 제어 명령을 전달하고 리턴 값을 돌려받는 구조로 되어있다.
서비스 제어기에서 서비스 프로그램에 직접 명령을 내릴 수 없고, SCM을 통해서 명령을 내려야 된다.
- 서비스 시작 과정
서비스 프로그램의 시작 과정을 간략히 정리해 보면 아래 그림 2와 같다.
모든 서비스 프로그램은 외부(서비스 제어기)에서 StartService() API를 호출하면서 시작되며, ‘자동 실행’ 서비스의 경우 SCM에서 StartSevice()를 호출해 준다.
서비스 프로세스 시작 과정
1. 서비스 제어기에서 StartService() 호출
서비스 제어기에서 StartService()를 호출하면 SCM은 해당 서비스 프로세스를 생성한다. 그리고 서비스 프로세스의 EP 코드가 실행된다.
2. 서비스 프로세스에서 StartServiceCtrlDispatcher() 호출
서비스 프로세스 내부에서 StartServiceCtrlDispatcher()가 호출되면 서비스 제어기의 StartService() 함수가 리턴된다. 그리고 SCM은 서비스 프로세스의 서비스 메인 함수 SvcMain()을 호출하여 주소를 등록한다.
(StartService()는 SCM이 StartServiceCtrlDispatcher()로부터 SvcMain()가 성공적으로 생성되었다는 알림을 받으면 반환)
3. 서비스 프로세스에서 SetServiceStatus(SERVICE_RUNNING) 호출
현재는 SERVICE_START_PENFING 상태이다. SvcMain() 내부에서 SetServiceStatus(SERVICE_RUNNING) API를 호출해야 정상적인 서비스 프로세스로서 동작할 수 있다.
서비스 프로그램은 결국 SCM에 의해서 프로세스가 생성되고 SvcMain() 함수로 제어가 넘어오면서 SetServiceStatus(SERVICE_RUNNING) API를 호출해야 정상적인 서비스 프로세스로 동작할 수 있다.
2. 서비스 프로세스의 디버깅 이슈
서비스 프로그램을 정확히 디버깅하려면 SCM에 의해 실행되는 서비스 프로세스에 디버거를 Attach 해서 디버깅을 해야 한다.
- 문제는 SCM
서비스 프로세스 디버깅의 핵심은 서비스 프로세스가 SCM에 의해 실행된다는 것이다.
- 서비스 프로세스는 SCM에 의해서 실행됨
- 서비스 핵심 코드는 주로 서비스 메인 함수(SvcMain())에 존재
- 서비스 메인 함수(SvcMain())는 SCM에 의해서 정상 호출됨
서비스 메인 함수(SvcMain())를 디버깅해야 한다. 디버거로 서비스 프로그램의 실행 파일을 열고 디버깅을 시작하면 서비스 메인 함수가 실행되지 않는다. 따라서 SCM을 통해서 정상 실행된 서비스 프로세스에 Attach해서 디버깅을 해야 한다.
- 디버거로 안 되는 것은 없다.
디버거로 서비스 실행 파일을 열어서 디버깅을 하면 SCM이 서비스 메인 함수를 호출하지 않아 디버깅할 수 없다(SCM에 의한 실행이 아니므로 실행을 못 시킴).
디버거는 디버기 프로세스에 대한 막강한 권한을 가지므로 강제로 디버깅 위치를 서비스 메인 함수로 지정한 후 디버깅을 할 수 있다(예, OllyDbg의 ‘New origin here’). 이 방법으로 서비스 메인 함수를 디버깅해도 큰 무리가 없으며, 간단해서 추천하는 방법이다.
하지만 편법이므로 복잡하게 동작하는 서비스 프로세스의 경우 완벽하게 디버깅할 수 없는 경우도 있다.
- 정석대로
정석은 SCM에 의해 실행된 서비스 프로세스에 디버거를 Attach하여 디버깅하는 방법이다.
문제는 SCM이 서비스를 실행시킨 이후에 Attach를 하면 핵심 코드(서비스 메인 함수)가 이미 실행된 이후라는 점이다. 따라서 SCM이 서비스 프로세스를 생성하고 EP 코드를 실행시키기 직전에 디버거를 Attach해야 한다.
3. 서비스 디버깅 실습
리버싱 핵심 원리의 간단한 실습 파일(DebugMe1.exe)을 설치
(예제 서비스(SvcTest)의 기능은 OutputDebugString() API를 사용한 디버그 문자열 출력)
- EP 강제 세팅
디버거로 서비스 파일을 직접 열고 디버깅하는 방법
보통 서비스 프로그램의 주요 코드는 서비스 메인(SvcMain()) 함수와 서비스 핸들러(SvcHandler()) 함수에 존재한다. SCM에 의해 실행되지 않고 디버거에 의해서 실행된 서비스 프로세스는 SvcMain()과 SvcHandler()가 호출되지 않는다. 따라서 그 함수들의 주소를 알아내서 그곳으로 디버깅 위치를 옮겨야 된다.
40106C주소의 StartServiceCtrlDispatcher()를 확인할 수 있다.
EXE 파일 형태의 Windows 서비스 프로그램은 반드시 EP 코드에서 StartServiceCtrlDispatcher() API를 호출하여 서비스 메인 함수 SvcMain()주소를 등록한다. 따라서 이 API를 찾으면 SvcMain()의 주소를 알아낼 수 있다.
DLL 파일 형태의 Windows 서비스의 경우 서비스 메인 함수(기본으로 ServiceMain)를 Export하고, SCM은 그 Export 함수를 실행한다. 따라서 DLL의 경우에는 StartServiceCtrlDispatcher() API를 따로 호출할 필요가 없다.
- 서비스 메인 함수의 이름을 ServiceMain이 아닌 다른 이름으로 지정하고 싶으면 관련 레지스트리에 등록하면 된다.
StartServiceCtrlDispatcher() API의 파라미터 pServiceTable은 SERVICE_TABLE_ENTRY 구조체 포인터이다. 이 구조체에서 서비스의 이름 문자열과 서비스 메인 함수의 주소를 알 수 있다.
SERVICE_TABLE_ENTRY 구조체 정의 |
typedef struct _SERVICE_TABLE_ENTRYA { LPSTR lpServiceName; // 서비스 이름 LPSERVICE_MAIN_FUNCTIONA lpServiceProc; // 서비스 메인 함수 주소 } SERVICE_TABLE_ENTRYA, *LPSERVICE_TABLE_ENTRYA; |
출처 : MSDN
pServiceTable(18FD24)의 첫 번째 멤버(40A9CC)는 “SvcTest” 문자열이고, 두 번째 멤버(401320)가 SvcMain()함수의 주소이다.
SvcMain()으로 이동하면 OutputDebugString() API를 사용하여 디버그 문자열을 출력하는 코드를 확인할 수 있다.
이제 디버기 프로세스의 EIP를 SvcMain()으로 변경하여 디버깅하면 된다.
- SvcMain()으로 이동하여 디버깅하기 위해서는 ‘New origin here’로 디버기 프로세스의 EIP 값을 SvcMain()의 시작 주소로 변경해야 된다. 이때 EIP 레지스터 외에 다른 것들(스택, EIP를 제외한 레지스터)은 모두 그대로이다.
- SCM으로부터 정상적으로 실행된 서비스 프로세스가 아니기 때문에 일부 서비스 관련 API룰 호출할 때 EXCEPTION이 발생할 수 있다. (예외처리 옵션이나 Plugin 사용)
- 간혹 EP 강제 세팅으로 정확한 디버깅을 할 수 없는 경우가 있다. 이때는 정석(Attach) 방법으로 디버깅해야 된다.
- Attach 방식
SCM을 통해서 정식으로 실행되는 서비스 프로세스를 디버거로 Attach해서 디버깅해야 하는 경우 간단한 디버깅 기법이 사용된다. 디버깅 기법의 작업 흐름을 EP 코드 디버깅 기준으로 간단히 표현하면 아래와 같다.
[서비스 설치] | ||
↓ | ||
[파일 패치] EP코드에 무한루프 설치 |
||
↓ | ||
[서비스 실행] 무한루프에 걸림 |
← | SCM은 대기 상태 (SERVICE_RUNNING을 기다림) |
↓ | ||
[Attach] 디버기를 Attach 시킴 |
||
↓ | ||
[프로세스 패치] EP 코드를 원래대로 복구 |
||
↓ | ||
[프로세스 패치] EP 코드부터 디버깅 |
← | 서비스 프로세스의 EP 코드부터 디버깅 가능 |
핵심은 디버거를 Attach시킬 때까지 서비스 프로세스의 중요 코드가 실행되지 못하도록 무한루프에 빠트리는 것이며, 고려할 사항은 Service Start Timeout(기본 30초) 이내에 완료하는 것이다.
SCM은 서비스를 실행시킨 후 일정 시간(Service Start Timeout) 동안 서비스의 상태가 STATUS_RUNNING으로 변경되길 기다린다.
만약 그 시간 내에 서비스의 상태가 변경되지 않는다면, SCM은 ERROR_SERVICE_REQUEST_TIMEOUT 에러를 발생시키고 해당 서비스 프로세스를 종료한다.
1) 서비스 설치
Windows 서비스 파일을 설치한다.
2) Service Start Timeout 시간 증가
서비스 상태를 변경하려면, 서비스 메인 함수 내에 존재하는 SetServiceStatus() API를 호출해야 된다. 하지만 30초 내의 작업은 매우 어려움으로 서비스 상태 변경 전 Service Start Timeout의 시간을 늘려야 된다.
Service Start Timeout 시간 증가
레지스트리 에디터(regedit.exe)를 실행하여 DWORD 값 생성
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control] ServicesPipeTimeout
ServicePipeTimeout은 기본값이 아니므로 생성해야 되며, 값은 밀리세컨드(millisecond)를 의미하며 시스템을 재부팅하면 적용된다.
Service Start Timeout을 늘리는 것은 모든 서비스에 영향을 미치므로 중요한 업무용 컴퓨터가 아닌 디버깅용 테스트 컴퓨터에만 적용하는 걸 권장
3) 파일 패치: 무한루프 설치
서비스 실행 파일(EXE 혹은 DLL)의 EP(Entry Point) 주소에 무한루프(Infinite Loop) 코드를 덮어씌운다.
Stud_PE 같은 PE 관련 유틸리티를 이용하여 파일의 EP 주소(RVA/RAW)를 확인한다.
Address of Entry Point 가 00001824이다. EP이므로 첫 번째 섹션(.text)이다. 따라서 RAW는 1824 – 1000 + 400 = C24이다.
EP(Entry Point)의 파일 옵셋(File Offset = RAW)이 C24인 것을 확인할 수 있다.
HxD 유틸리티를 이용하여 해당 위치로 이동하면 원본 EP 코드의 첫 바이트를 찾을 수 있다. 이때, 첫 2바이트를 따로 적어두고, 해당 두 바이트를 0xEB, 0xFE로 패치한다.
OpCode 0xEB는 근거리(Short Distance) JMP 명령어이고 1바이트 크기의 값을 가진다. 이 값은 Signed Value(부호 있는 수)이며 ‘Next EIP와의 상대적인 거리’를 의미한다. Jump Address = Next EIP(401826) + 0xFE(-2) = 401824 |
패치된 코드를 OllyDbg로 확인하면 무한루프 명령어(자신의 주소 401824로 점프)를 확인할 수 있다.
4) 서비스 시작
서비스를 시작하면 서비스 프로세스는 무한루프에 빠져서 CPU 점유율이 100%에 가깝게 상승한다.
오류 1053이 발생하면 그것이 ERROR_SERVICE_REQUEST_TIMEOUT 에러이다. 앞에서 Service Start Timeout의 시간을 늘려서 서비스 프로세스가 종료된 것은 아니므로 계속 진행하면 된다.
5) Attach 디버거
디버거를 Attach 시키면 시스템 라이브러리(ntdll.DbgBreakPoint) 영역에서 멈춘다.
6) 프로세스 패치: 무한루프 제거
Ctrl+G 명령으로 프로세스의 EP 주소(VA: 401824)로 이동하여 BP 설치 후 실행한다.
401824 주소에서 ‘Edit’기능을 이용하여 (앞에서 기록해 둔) 원본 코드(0xE8, 0xC0)으로 복원한다.
7) 디버깅 시작
이제 서비스 메인 함수에서 SetServiceStatus() API를 호출하여 SERVICE_RUNNING 상태로 변경되어야 서비스 프로세스가 정삭적으로 실행된다. (프로세스 시작 과정 3)
SetServiceStatus() API 두 번째 파라미터 구조체의 dwCurrentState가 40134D에서 4(SERVICE_RUNNING)로 세팅되는 것을 확인할 수 있다.
SetServiceStatus() API |
BOOL SetServiceStatus( [in] SERVICE_STATUS_HANDLE hServiceStatus, [in] LPSERVICE_STATUS lpServiceStatus ); |
출처 : MSDN
[in] hServiceStatus - 현재 서비스의 상태 정보 구조에 대한 핸들입니다.
[in] lpServiceStatus - SERVICE_STATUS 구조에 대한 포인터는 호출 서비스에 대한 최신 상태 정보를 포함합니다.
출처 : MSDN
출처 : MSDN
SetServiceStatus(SERVICE_RUNNING) API가 실행되면 해당 서비스가 시작됨으로 변경된 것을 확인할 수 있다.
따라서 서비스 프로세스 Attach 방식 디버깅을 정리하면 아래와 같다.
[서비스 설치] |
↓ |
[Service Start Timeout 시간 증가] |
↓ |
[파일 패치] EP코드에 무한루프 설치 |
↓ |
[서비스 실행] 무한루프에 걸림 |
↓ |
[Attach] 디버기를 Attach 시킴 |
↓ |
[프로세스 패치] EP 코드를 원래대로 복구 (무한루프 제거) |
↓ |
[프로세스 패치] EP 코드부터 디버깅 |
4. 마무리
서비스 프로세스의 동작 원리와 디버깅 방법을 정리하였는데, 서비스 동작 원리를 이해하지 못하면 서비스 메인 함수를 정확히 디버깅할 수 없다.
따라서 동작 원리를 이해하는데 중점을 두어야 된다.
'리버싱 핵심원리' 카테고리의 다른 글
디버깅 실습3 - PE Image Switching 개념 및 동작 원리 (0) | 2024.01.31 |
---|---|
디버깅 실습2 - Self-Creation (JIT 디버깅) (0) | 2024.01.16 |
Advanced 안티 디버깅 (0) | 2024.01.10 |
Dynamic 안티 디버깅 (0) | 2024.01.10 |
Static 안티 디버깅 (1) | 2024.01.10 |