티스토리 뷰

서비스 디버깅의 정석은 SCM에 의해서 실행된 서비스 프로세스에 디버거를 Attach해서 디버깅해야 합니다.

개념은 단순하지만 실행 방법에 있어 문제가 있고, 그건 바로 SCM이 서비스를 실행시킨 이후에 Attach를 하면 핵심 코드(서비스 메인 함수)가 이미 실행된 이후라는 것입니다. 따라서 SCM이 서비스 프로세스를 생성하고 EP 코드를 실행시키기 직전에 디버거를 Attach해야 합니다.


1. 그냥 디버깅 - EIP 강제 세팅

main() 함수로 가서 StartServiceCtrlDispatcher() API 함수를 찾는다. 이 함수를 호출하면 SCM에게 서비스 메인 함수(SvcMain()) 주소를 알려줘야 합니다. 따라서 이 API를 찾으면 SvcMain() 주소를 알아낼 수 있습니다. 

StartServiceCtrlDispatcher() API의 파라미터 pServiceTable은 SERVICE_TABLE_ENTRY 구조체 포인터 입니다. 이 구조체를 따라가면 서비스의 이름("SvcMain") 문자열과 서비스 메인 함수(SvcMain())의 주소를 알 수 있습니다.


typedef struct _SERVICE_TABLE_ENTRY {

LPTSTR        lpServiceName; // 서비스 이름

LPSERVICE_MAIN_FUNCTION lpServiceProc;

} SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;

<SERVICE_TABLE_ENTRY 구조체 정의>


StartServiceCtrlDispatcher() 함수를 실행하고 덤프창과 스택창을 보면 (오른쪽 그림)스택 창을 보면 SvcTest 라는 문자열이 들어있는 0012FD5C 부분을 덤프창으로 찾아보면 4바이트씩 위의 구조체에서 봤듯이 서비스이름과 서비스함수를 찾을 수가 있습니다. 리틀 엔디언으로 CC A9 40 00 은 이름이고, 20 13 40 00 은 SvcMain() 함수의 주소라는 것을 알 수가 있습니다.

 


위에서 구한 00401320 (SvcMain()) 주소로 가서 New Origin here 으로 강제 EIP를 시행합니다. EIP 레지스터 외에 다른 것들(스택, EIP를 제외한 레지스터)은 모두 그대로입니다.


SvcMain()을 디버깅할 때 주의할 점이 있는데, SCM으로 부터 정상적으로 실행된 서비스 프로세스가 아니기 때문에 일부 서비스 관련 API를 호출할 때 예외(EXCEPTION)가 발생할 수 있다는 것입니다. 아래의 RegisterServiceCtrlHandler를 실행하면 EXCEPTION_ACCESS_VIOLATION(0xC0000005) 예외가 발생합니다. 


위와 같이 모든 서비스가 발생된다는 것은 아닙니다. 만약에 발생할 시에 아래와 같이 디버깅 옵션에서 Exceptions에서 Memory access violation 을 체크하면 예외 처리가 되어서 디버깅이 오류없이 진행을 할 수가 있습니다.


2. Attach 방식(서비스 디버깅의 정석)

위 작업의 핵심은 디버거를 Attach 시킬 때까지 서비스 프로세스의 중요 코드가 실행되지 못하도록 무한루프에 빠뜨리는 것입니다. 원리는 단순합니다만, 고려할 사항이 하나 더 있습니다. 그것은 바로 위 작업을 Service Start Timeout(기본 30초) 이내에 완료해야 한다는 것입니다.


SCM은 서비스를 실행시킨 후 일정 시간 동안 서비스의 상태가 STATUS_RUNNING으로 변경되길 기다리는데, 만약 그 시간 내에 서비스의 상태가 변경되지 않는다면, SCM은 ERROR_SERVICE_REQUEST_TIMEOUT 에러를 발생시키고 해당 서비스 프로세스를 종료합니다. 


즉, 디버거를 Attach한 후 30초 이내에 서비스 프로세스의 상태를 STATUS_RUNNING으로 변경해야 합니다. 서비스 상태를 변경하려면, 서비스 메인 함수 내에 존재하는 SetServiceStatus() API를 호출해야 합니다. 30초 내에 작업을 하기는 어려우므로 Service Start Timeout 시간을 충분히 크게 눌려줄 필요가 있습니다.


* Service Start Timeout 시간 증가

regedit.exe 를 실행시켜서 아래의 경로에 들어가서 ServicePipeTimeout을 DWORD 값으로 생성을 한 후에 이 값은 밀리세컨드(Millisecond)를 의미하므로 60 x 60 x 24 x 1000 = 86400000(24hour) 정도로 충분히 크게 입력하고 재부팅하면 됩니다. 이 작업은 시스템의 모든 서비스에 영향을 미치기 때문에 가급적으로 가상망에만 적용할 것을 권장합니다.


[파일 패치: 무한루프 설치]

서비스 실행 파일의 EP 주소에 무한루프 코드를 덮어쓰겠습니다. EP 주소를 확인해 보면 C24(1824)인 것을 기억합니다.


WinHex로 C24 주소로 가보면 E8 C0이 되어 있습니다. 


올리디버거로 확인하면 E8 C0 으로 CALL 명령어인 것을 확인 가능합니다. 


WinHex로 돌아가서 E8 C0 을 EB FE로 변경합니다.

* Jump Address = Next EIP(401826) + 0xFE(-2) = 401824

많은 JMP/CALL 명령어가 위와 같이 '상대적인 거리"를 이용하기 때문에 위 계산 방법을 잘 숙지해야 합니다.


변경된 것을 올리디버거로 보면 CALL 에서 JMP로 EP 코드를 반복해서 점프하기 때문에 무한루프에 빠지게 됩니다.


[서비스 시작]

서비스를 다시 실행해 보겠습니다.

 


오류 1053은 ERROR_SERVICE_REQUEST_TIMEOUT 에러입니다. 서비스 프로세스가 종료된 것은 아니므로 계속 진행하시기 바랍니다.


DebugMe1.exe 의 점유율이 100이 다되면서 CPU가 미칠려고 하네요.



[Attach 디버거]

올리디버거에서 Attach를 통해서 디버깅을 진행하겠습니다.


현재 실행되는 프로세스들인데 DebugMe1 을 Attach 시켜보겠습니다.


아래와 같이 시스템 라이브러리(ntdll.DbgBreakPoint) 영역에서 멈춥니다.


EP(00401824) 코드로 BP를 걸고 실행을 하고 Hex를 E8 C0으로 원래 코드로 다시 복구합니다. 


변경한 부분입니다. 이제 편안하게 코드를 디버깅하면 됩니다.



서비스는 아직 실행이 아닌, 시작하는 중으로 시작하지 않았습니다.



SetServiceStatus() 함수를 찾아서 BP를 걸고 실행을 합니다. 오류가 나시는 분들은 메모리 에러 예외처리를 해주시면 됩니다. 인자 값으로는 40CDA8 주소안에 4를 저장합니다. 4는 SERVICE_RUNNING 으로 실행으로 바꿔줍니다.



40CDA8을 덤프를 통해 본 모습인데, "04"가 들어있는 것을 확인 가능합니다.



SetServiceStatus를 실행하고 서비스 상태를 보면 시작됨으로 변경이 되었습니다.



참고 : 리버싱핵심원리




'리버싱 > 정리' 카테고리의 다른 글

[PE Image Switching 디버깅]  (0) 2015.08.14
[Self Creation 디버깅 방법]  (0) 2015.08.13
서비스 프로세스 동작 원리  (0) 2015.08.12
IDA pro 사용법  (0) 2015.08.12
[코드인젝션]기본 뼈대  (0) 2015.08.11
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함