[C & C++] Check PE - PE 파일 여부 확인
지정한 핸들의 파일이 PE 파일인지 확인하는 코드이다. MZ 헤더와 PE 시그니쳐를 확인하며, 실제 파일의 크기가 PE 구조에 나타난 크기보다 작지는 않는지 확인한다. PE 구조를 파싱하는 부분은 아래와 동일하므로 조금씩 수정하면 다른 부분들도 확인할 수 있다.
2016.08.27
[C & C++] Search a file - 파일 탐색
인자로 지정한 경로에 존재하는 파일을 탐색하는 코드이다. 만약 폴더를 탐색하면 재귀를 통해 다시 그 안에 파일들까지 탐색한다. 컴파일러에 따라 "_CRT_SECURE_NO_WARNINGS"를 추가해주어야 하거나, "char * 형식의 인수가 LPCWSTR 형식의 매개 변수와 호환되지 않습니다" 라는 오류로 인해 유니코드 문자 집합 사용 설정을 해제해주어야 한다.
2016.08.24
no image
Assembly로 보는 코드, strcmp 문자열 비교
* 컴파일한 어셈블리 코드는 컴파일러나 속성 등 여러 요인에 따라 많이 상이 할 수 있습니다.우선 문자열 비교를 위한 코드는 아래와 같다. buf1 에는 "Compare" 라는 문자열을 저장해놓고 buf2 에는 사용자로부터 입력을 받도록 하였다. 그리고 strcmp 를 통해 두 값을 비교하는 코드이다. 위 코드에서 strcmp 부분의 어셈블리 코드는 아래와 같다. 여기서 buf2 에 "Comparz" 를 넣어주므로 ARG.1 에는 "Compare", ARG.2 에는 "Comparz" 가 위치하게 된다. 눈으로 한번 살펴본 뒤 이에 대한 자세한 설명을 읽어보자. 우선 strcmp(buf1, buf2) 에서 buf1 은 "Compare" 이고 buf2는 "Comparz" 라는 문자열을 입력해주었다. 스택에 ..
2016.08.08
no image
공인인증서 탈취 악성코드
개요랜섬웨어나 게임 계정, 금융 정보 탈취 등으로 인한 피해를 끊이지 않고 있다. 이런 악성코드를 제작하는 공격자의 목적은 결국 금전을 획득하는 것이다. 우리는 많은 매체들을 통해 이런 사건에 대한 피해 소식을 접할 수 있다. 랜섬웨어의 경우 악성코드에 감염되면 파일이 암호화가 되어 공격자에게 금액을 지불해야 한다. 하지만 사용자 PC 에서 금융 정보를 탈취할 때, 공격자는 사용자의 보안 카드 번호 등을 알 수 없으므로 이에 대해 사용자가 입력하도록 한다. 따라서 이번 보고서에서는 금융 정보 탈취 악성코드에 감염된 경우 어떠한 증상이 있는지 알아보자. 동작악성 프로세스가 실행되었더라도 사용자가 Internet Explorer 자체를 실행시키지 않을 수가 있다. 때문에 공격자는 사용자 PC 에서 악성코드를..
2016.08.06
no image
Windows Event Message
Windows Application일반적으로 프로그램이란 실행하면 작성한 코드의 순서대로 동작하는 것으로 생각할 수 있다. 하지만 Windows GUI 응용프로그램은 실행된 후에 단지 윈도우를 출력할 뿐이며, 일반적으로 아무것도 하지 않는다. 이런 프로그램은 사용자가 키보드 입력이나 마우스 버튼 클릭으로 ‘이벤트’가 발생되면 그때마다 대응되는 처리를 하는 방식으로 동작한다.이러한 ‘이벤트 반응형’ 프로그램이 동작하기 위해서는 우선 이벤트가 발생했음을 프로그램에 알리는 구조가 필요하다. Windows에서는 각각의 응용프로그램이 마우스나 키보드로부터 직접 입력을 받지 않는다. 대신 이러한 이벤트가 발생하는지 Windows가 확인한 뒤, 이벤트가 발생했을 때 응용프로그램에 통지한다.[그림 1] 이벤트 전달 과..
2016.07.13
no image
Windows Multi Task
개요 우리는 학교 과제를 하기 위해 HWP나 Word, Excel, PPT 등의 응용프로그램을 실행해야 한다. 하지만 단순히 이러한 동작만을 하는 것이 아니라 노래를 듣는 동시에 코드를 짜거나, 이에 더해 PC 톡으로 친구들에게 물어보기도 한다. 우리가 이러한 프로그램들을 실행하면 결과적으로 CPU에서 명령어를 처리하게 된다. 그렇다면 어떻게 CPU가 하나뿐이더라도 동시에 여러 작업이 가능해지는 것일까? 이를 위해 프로세스와 스레드가 어떻게 동작하는지에 대하여 알아보자. 프로그램은 일반적으로 하드 디스크 등에 저장되어 있는 실행코드를 뜻하고, 프로세스는 프로그램을 구동하여 프로그램 자체와 프로그램의 상태가 메모리 상에서 실행되는 작업 단위를 지칭한다. 예를 들어, 하나의 프로그램을 여러 번 구동하면 여러..
2016.07.08
Windows Service
Introdution윈도우 운영체제에 있어 우리가 흔히 직접 실행할 수 있는 프로그램 이외에 우리가 직접 실행하지 않아도 실행되는 프로세스가 있다. 이 중 서비스는 보통 시스템의 시작과 함께 시작되어 윈도우의 핵심 프로세스의 기능을 수행한다. 중요한 만큼 사용자의 개입을 최소화하기 위해 백그라운드에서 동작한다. 프로세스에 대한 제어권을 우리가 갖지 않고 윈도우의 서비스 제어 관리자가 가지고 있기 때문에 일반적인 방법으로 서비스를 분석할 수 없다. 이러한 요인들로 인해 악성코드 제작자는 서비스를 통해 백그라운드라는 점, 사용자 개입의 최소화 등의 이점을 누릴 수 있다. 따라서 이번 문서에서는 서비스가 어떻게 동작하는지, 그리고 이를 분석하기 위해서는 어떠한 방식을 사용해야 하는지에 대하여 알아보자. Ser..
2016.06.21
WFP 무력화
WFP (Windows File Protection)WFP는 중요한 Windows 시스템 파일이 대체 또는 변경되는 것을 방지하기 위해 Windows에서 기본적으로 제공하는 기능(Vista부터는 WRP로 대체)으로, 프로그램들이 Windows 시스템의 중요한 파일들을 덮어씌울 수 없게 하여 프로그램과 운영체제로부터 발생할 수 있는 문제를 사전에 방지한다. WFP는 보호하고자 하는 시스템 파일이 올바른지 확인하기 위해 코드 서명에 의해 생성된 카탈로그와 파일 시그니쳐를 사용하여 확인한다. 그렇다면 정상적인 경우라도 이러한 파일의 변경이 일어날 수 없을까? 시스템 파일에 치명적인 취약점이 발견되었을 경우 WFP에 의해 해당 파일을 대체하지 못한다면 이는 위험을 품고 있는 OS가 되어버릴 것이다. 따라서 보호..
2016.06.21

지정한 핸들의 파일이 PE 파일인지 확인하는 코드이다. MZ 헤더와 PE 시그니쳐를 확인하며, 실제 파일의 크기가 PE 구조에 나타난 크기보다 작지는 않는지 확인한다. PE 구조를 파싱하는 부분은 아래와 동일하므로 조금씩 수정하면 다른 부분들도 확인할 수 있다.


인자로 지정한 경로에 존재하는 파일을 탐색하는 코드이다. 만약 폴더를 탐색하면 재귀를 통해 다시 그 안에 파일들까지 탐색한다. 컴파일러에 따라 "_CRT_SECURE_NO_WARNINGS"를 추가해주어야 하거나, "char * 형식의 인수가 LPCWSTR 형식의 매개 변수와 호환되지 않습니다" 라는 오류로 인해 유니코드 문자 집합 사용 설정을 해제해주어야 한다.



* 컴파일한 어셈블리 코드는 컴파일러나 속성 등 여러 요인에 따라 많이 상이 할 수 있습니다.

우선 문자열 비교를 위한 코드는 아래와 같다. buf1 에는 "Compare" 라는 문자열을 저장해놓고 buf2 에는 사용자로부터 입력을 받도록 하였다. 그리고 strcmp 를 통해 두 값을 비교하는 코드이다. 

위 코드에서 strcmp 부분의 어셈블리 코드는 아래와 같다. 여기서 buf2 에 "Comparz" 를 넣어주므로 ARG.1 에는 "Compare", ARG.2 에는 "Comparz" 가 위치하게 된다. 눈으로 한번 살펴본 뒤 이에 대한 자세한 설명을 읽어보자.

우선 strcmp(buf1, buf2) 에서 buf1 은 "Compare" 이고 buf2는 "Comparz" 라는 문자열을 입력해주었다. 스택에 들어있는 각 문자열을 EDX 와 ECX 레지스터에 넣어준다.

첫 줄에서 EDX 에 있는 기준 문자열 4 Bytes 를 EAX 로 복사한다. 이를 통해 EAX 에는 0x706D6F43 이 오게 된다. 얼핏 보면 함수의 주소 같지만 "Comp" 라는 문자에 해당하는 ASCII 값이다. 두 번째 줄부터 CMP 명령어가 나타난다. CMP 명령어는 두 값을 비교할 때 사용되는 명령어이다. AL 은 EAX 의 하위 1 Byte 로 0x43("C") 가 된다. 이 "C" 와 사용자가 입력한 문자열("Comparz") 의 첫 번째 문자를 비교한다.


AL 과 ECX 의 첫 글자가 일치한다면 ZF 는 1로 설정되어 JNE 에서 점프하지 않는다. 그리고 TEST AL, AL 은 비교한 두 문자가 0 인지 확인하는 명령어로, 두 문자 모두 0 이라면 ZF 가 1로 설정되어 JZ 에서 점프하여 RETN 으로 간다.

위에서 AL, AH 를 통해 두 문자씩 비교를 하였다. 그렇다면 세 번째 문자부터는 SHR EAX, 10 을 통해 EAX 값을 정리해준다. SHR EAX, 10 는 EAX 레지스터의 값을 0x10 bit 만큼 우측으로 이동시키는 것으로 상위 2 Bytes 의 값이 하위 2 Bytes 에 자리 잡게 된다. 이 경우 "0x706D6F43" 이 "0x0000706D" 가 된다. 그리고 다시 AL 과 AH 를 통해 나머지 문자들도 비교한다.

기준 문자열이 4 Bytes 보다 크기 때문에 주소를 옮겨 주어야 한다. 따라서 ECX 와 EDX 에 있는 주소 값에 각각 4 를 더해 다섯 번째 문자를 가리키도록 한다. 그 후 다시 문자 비교를 시작하는 위치 0x62BEF950 으로 이동한다.

반복문을 돌면서 한 문자씩 비교하는 것을 확인하였다. 이번에는 반환 값에 대하여 알아보자. 아래 두 그림은 0 을 반환하는 경우와, 0 이 아닌 값을 반환하는 경우이다. 아래 첫 번째 그림은 XOR EAX, EAX 를 통해 EAX 의 값을 0 으로 만든다. strcmp 는 두 값이 같은 경우 0 이 반환되는 것으로 바로 그 부분이다.

SBB EAX, EAX 는 SUB EAX, EAX 와 유사하다. 다만 이에 Carry Flag(CF)의 값을 다시 빼준다. 따라서 EAX 에는 어떤 값이 있더라도 자기 자신을 소거한 뒤 CF 의 값에 따라 1을 더 빼주는 셈이 된다. SBB 명령어를 통해 0 또는 0xFFFFFFFF 가 된 EAX 에 OR EAX, 0x1 을 해주므로 결국 EAX 에는 1 또는 -1 이 반환된다.

좀 더 구체적으로 1 또는 -1 은 마지막 CMP 명령어에서 결정된다. 기준 문자열의 마지막 문자 'e' 와 비교 문자열의 'z' 를 비교했을 때, 각각의 ASCII 값은 0x65 와 0x7A 이다. '기준 문자 < 비교 문자' 인 경우 CF 는 borrow 가 발생하여 1로 설정된다. 이 경우 최종적으로 반환되는 값은 -1 이 된다. 반대로 비교 문자열이 "Compara" 라 할 때, 마지막 문자 'a' 는 'e' 보다 작은(기준 문자 > 비교 문자) 값이므로 borrow 가 발생하지 않아 CF 가 0으로 설정되어 최종 반환 값은 이에 OR 1 을 하여 1 이 된다.

'Reversing > Theory' 카테고리의 다른 글

암호학 기초 개념  (2) 2016.11.23
Memory Detection(메모리 진단)  (0) 2016.09.26
WFP 무력화  (0) 2016.06.21
DLL이란?  (4) 2016.05.29
PE구조의 이해  (0) 2016.05.04
개요

랜섬웨어나 게임 계정, 금융 정보 탈취 등으로 인한 피해를 끊이지 않고 있다. 이런 악성코드를 제작하는 공격자의 목적은 결국 금전을 획득하는 것이다. 우리는 많은 매체들을 통해 이런 사건에 대한 피해 소식을 접할 수 있다. 랜섬웨어의 경우 악성코드에 감염되면 파일이 암호화가 되어 공격자에게 금액을 지불해야 한다. 하지만 사용자 PC 에서 금융 정보를 탈취할 때, 공격자는 사용자의 보안 카드 번호 등을 알 수 없으므로 이에 대해 사용자가 입력하도록 한다. 따라서 이번 보고서에서는 금융 정보 탈취 악성코드에 감염된 경우 어떠한 증상이 있는지 알아보자. 


동작

악성 프로세스가 실행되었더라도 사용자가 Internet Explorer 자체를 실행시키지 않을 수가 있다. 때문에 공격자는 사용자 PC 에서 악성코드를 지속시키기 위해 자동 실행 레지스트리에 등록한다.

[그림1] 자동 실행 등록 


사용자의 PC 에 공인인증서가 존재하고 있는지 확인한다. 공인인증서를 찾은 경우 임시 폴더 하위에 이를 복제하는 것을 확인할 수가 있다. 그리고 복제한 공인인증서 파일을 압축하여 임의의 ZIP 파일로 저장해 놓는다.

[그림 2] 공인인증서 복사 및 압축


PAC (Proxy Auto Config)

실행된 악성코드는 Internet Explorer 의 시작 페이지를 국내 유명 포털 사이트로 바꾼다. 그리고 AutoConfigURL 에 값을 등록해준다. 이를 통해 자동 구성 스크립트가 사용되며, 사용자가 URL 입력 시 연결할 IP 정보를 알아 오기 위해 레지스트리 값에 등록된 주소에 질의한다.

[그림 3] 시작 페이지 변경 및 PAC 설정


등록된 주소는 "127.0.0.1:1171" 로 자신 PC 의 1171 번 포트에 질의하게 된다. 아래 그림을 보면 악성 프로세스인 b.exe 가 1171 번 포트에서 LISTENING 중인 것을 확인할 수 있다. 이를 통해 URL 에 따른 IP 정보를 b.exe 에서 받아오게 된다. 사용자는 일반 웹 사이트에 접속하더라도 b.exe 에게 질의하여 공격자가 원하는 사이트로 접속하게 된다.

[그림 4] 연결 대기


네트워크

악성코드는 공격자의 서버와 통신을 시도한다. 공격자의 서버는 QQ 사이트에 등록되어 있어, 네트워크 패킷을 보면 아래와 같이 QQ 사이트 공격자 ID(338366585)를 통해 공격자의 IP 를 받아온다.

[그림 5] QQ 에 사용자 주소 요청 (QQ : 103.7.30.86, 공격자 : 103.20.193.205)


공격자 서버의 주소를 정상적으로 받아 왔다면, 이전에 압축한 사용자 공인인증서를 탈취한다. 아래 패킷과 같이 ZIP 파일의 PK 헤더와 공인인증서 정보가 있는 것을 확인할 수 있다.

[그림 6] 공인인증서 전송


파밍 사이트

감염된 후 Internet Explorer 를 실행하면 아래와 같은 팝업 창이 나타나 다른 행동을 할 수 없게 된다. 은행 배너 중 하나를 클릭하면 공격자가 지정한 파밍 사이트로 이동하게 된다.

[그림 7] 인터넷 접속 시 팝업 창


IBK 기업은행 배너를 클릭한 결과 실제 IBK 기업은행과 같은 페이지로 이동한 것을 확인할 수 있다. 여기까지는 일반 은행 업무를 볼 때와 같은 상황이다. 하지만 은행 사이트에 접속하여 업무를 보기 위해 임의의 버튼을 클릭하면 아래와 같은 메시지 창이 나타난다. 메시지 창이 나타난 이후 가짜 본인인증 사이트로 이동된다.

[그림 8] 클릭 시 나타나는 팝업창


이동한 웹 사이트에는 한국 인터넷 진흥원 KISA 를 볼 수 있으며, 이용자 정보 입력을 유도한다. 하지만 이 페이지 역시 가짜 사이트로 이용자 정보 입력을 제외한 다른 버튼을 클릭할 경우 페이지 이동이 이루어지지 않는다. 이용자 정보 또한 임의의 정보를 기입하면 다음 페이지로 넘어가진다.

[그림 9] 개인정보 입력 유도


임의로 개인 정보를 입력하고 진행을 하면 URL 에 자신이 기입한 이름, 주민번호, 계좌번호 등이 URL 에 노출되는 것을 확인할 수 있다.

[그림 10] 입력한 임의의 개인정보

[그림 11] URL 에 노출되는 개인정보


마지막으로 이체 비밀번호와 보안카드 정보 입력을 유도한다. 정상적인 인증과 달리 보안 카드의 모든 번호를 입력하도록 한다.

[그림 12] 보안카드 정보 입력 유도


결론

사용자 PC 에 저장된 공인인증서를 탈취 후 PAC 를 통해 파밍 사이트로 연결하는 악성코드에 대하여 알아보았다. PC 에 인증서를 저장해놓은 경우 금융 정보 탈취 악성코드가 접근하기 쉬워진다. 따라서 인증서를 USB 와 같은 이동식 매체에 저장하여 필요할 때만 연결하여 사용하는 것이 하나의 예방법이 될 수 있다. 또한 파밍 사이트로 연결되는 방법은 위에서 언급한 PAC 외에도 존재하므로 사용자가 속을 수 있다. 그러므로 위와 같이 과도한 개인 및 금융 정보를 요구한다면 의심을 해보아야 한다. 의심되는 증상이 있을 경우 정보 기입을 멈추고 백신을 통해 PC 감염 여부를 확인하여야 한다.


Windows Event Message

Kail-KM
|2016. 7. 13. 09:51

 

Windows Application

일반적으로 프로그램이란 실행하면 작성한 코드의 순서대로 동작하는 것으로 생각할 수 있다. 하지만 Windows GUI 응용프로그램은 실행된 후에 단지 윈도우를 출력할 뿐이며, 일반적으로 아무것도 하지 않는다. 이런 프로그램은 사용자가 키보드 입력이나 마우스 버튼 클릭으로 이벤트가 발생되면 그때마다 대응되는 처리를 하는 방식으로 동작한다.

이러한 이벤트 반응형프로그램이 동작하기 위해서는 우선 이벤트가 발생했음을 프로그램에 알리는 구조가 필요하다. Windows에서는 각각의 응용프로그램이 마우스나 키보드로부터 직접 입력을 받지 않는다. 대신 이러한 이벤트가 발생하는지 Windows가 확인한 뒤, 이벤트가 발생했을 때 응용프로그램에 통지한다.

[그림 1] 이벤트 전달 과정

좀 더 자세히 알아보면, 이러한 각 이벤트는 하드웨어의 장치 드라이버가 처리해야 할 일이다. 장치 드라이버는 이벤트를 감지했을 경우 이를 Windows에 알린다. 하지만 Windows가 각 하드웨어의 모든 부분까지 알지는 못하므로 이벤트를 좀 더 단일화된 형태로 변환한 뒤 시스템에 이를 넣는다. 시스템 큐에 추가 된 각 이벤트는 선입선출 방식으로 Windows가 하나씩 꺼내어 해당 응용프로그램에 이벤트를 넘겨준다.

 

Windows Message

사용자로부터 발생한 이벤트에 대해 각 응용프로그램의 큐에 전달된다고 하였다. 좀더 정확하게는 응용프로그램보다도 응용프로그램의 특정 윈도우에 이벤트를 알려준다고 해야한다. 이는 아래 그림과 같이, 하나의 응용프로그램이지만 여러 윈도우를 가진 경우를 생각하면 된다. 사용자가 입력한 키보드 이벤트(WM_KEYDOWN)‘Proc.exe’이란 프로세스에 도착하더라도, 어떤 윈도우가 이를 받아야하는지 나타나지 않는다면 처리가 복잡해진다.

[그림 2] 여러 윈도우를 가진 응용프로그램

이 때문에 사용자 입력 등의 이벤트는 특정 윈도우와 연관된다. Windows 에서 각각의 윈도우에 이벤트를 알리기 위해선 “Window Message”라는 구조체를 이용한다. 구조체의 첫 번째 인자를 보면 각 메시지가 윈도우 핸들을 담고 있다. 이 값을 통해 조작 대상 윈도우를 구별할 수 있다. 그리고 두 번째 인자 Message ID를 통해 어떤 이벤트가 발생했는지 알 수 있다.

typedef struct tagMSG {

  HWND        hwnd;          // Window Handle

  UINT          message;       // Message ID

  WPARAM      wParam;        // Additional information about the message

  LPARAM       lParam;         // Additional information about the message

  DWORD       time;          

  POINT         pt;             // Cursor position

} MSG, *PMSG, *LPMSG;

[코드 1] 윈도우 메시지 구조체

그렇다면 자신에게 온 메시지를 어떻게 확인할까? 위에서 말했다시피 이벤트가 발생하면 해당 응용프로그램 큐(Application Queue)에 메시지가 추가된다고 하였다. 큐에 들어가 있는 메시지는 GetMessage API를 통해 꺼내어 온다. 여기서 주목해야할 인자는 바로 첫 번째 인자로, 인자로 넘겨준 주소에 메시지 정보들을 담아 반환해준다.

BOOL WINAPI GetMessage(

  _Out_    LPMSG  lpMsg,

  _In_opt_  HWND  hWnd,

  _In_      UINT   wMsgFilterMin,

  _In_      UINT   wMsgFilterMax

);

[코드 2] GetMessage API

OllyDBG를 통해 GetMessage API 호출 부분을 확인해보자. 아래의 코드를 보면 함수를 호출하기 위해 각 인자를 Stack에 넣어주는 것을 확인할 수 있다. 여기서 확인해야 할 것은 바로 밑에서 두 번째 줄에 위치한 ‘PUSH EAX’이다. EAX에는 메시지에 대한 정보가 저장될 주소를 담고 있다. 아래 코드에서는 0x4FF8A0인 것을 직접 확인할 수 있다.

Address       Command                             Comments

00B418A1  | PUSH 0                               ; |MsgFilterMax = 0

00B418A3  | PUSH 0                               ; |MsgFilterMin = 0

00B418A5  | PUSH 0                               ; |hWnd = NULL

00B418A7  | LEA EAX,[LOCAL.24]                    ; |

00B418AA  | PUSH EAX                            ; |pMsg : 0x4FF8A0

00B418AB  | CALL DWORD PTR DS:[GetMessage]     ; \USER32.GetMessageW

[코드 3] Disassembly GetMessage

메시지 정보가 반환될 주소를 확인하고 GetMessage API를 호출해보자. 아래의 두 덤프 중 상단의 내용이 하단의 내용으로 바뀌는 것을 확인할 수 있다. 여기서 가장 중요한 것은 바로 메시지 ID0x4FF8A40x100 (WM_KEYDOWN) 메시지가 있는 것을 확인할 수 있다.

Address   Hex dump                                         ASCII

004FF8A0  CC CC CC CC|CC CC CC CC|CC CC CC CC|CC CC CC CC| ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ

004FF8B0  CC CC CC CC|CC CC CC CC|CC CC CC CC|CC CC CC CC| ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ

Address   Hex dump                                         ASCII

004FF8A0  52 0B 35 00| 00 01 00 00 | 11 00 00 00|01 00 1D 00|   R5       

004FF8B0  68 3B 3B 0D | 7E 03 00 00 |81 01 00 00|CC CC CC CC|    h;;

~ÌÌÌÌ

[코드 4] GetMessage Dump

Hex                 Decimal           Symbolic

0000                0                    WM_NULL

0001                1                    WM_CREATE

0002                2                    WM_DESTROY

0003                3                    WM_MOVE

0100                256                 WM_KEYDOWN

0201                513                 WM_LBUTTONDOWN

020a                522                 WM_MOUSEWHEEL

[ 1] 주요 Message Numbers

결국 메시지 큐에서 꺼내온 메시지는 Window Handle이 가리키는 윈도우에 전송되어 윈도우 프로시저에서 처리한다. 윈도우 프로시저는 Windows에 의해 직접 호출되기 때문에 “CALLBACK” 이라는 타입을 지정해주어야 한다. 첫 번째 인자를 통해 어떠한 Window Handle인지 알 수 있고, 두 번째 인자는 GetMessage를 통해 받은 메시지 ID가 인자로 들어오게 된다..

LRESULT CALLBACK WindowProc(

    HWND   hWnd,        // Window Handle

    UINT     uMsg,        // Message ID

    WPARAM wParam,      // argv 1

    LPARAM  lParam       // argv 2

);

[코드 5] Window Procedure

윈도우 프로시저 처리는 비교문을 통해 프로그래머가 지정한 번호의 메시지가 오는지 확인한다. 아래의 코드를 보면 CMP 명령어를 통해 GetMessage로 받은 Message ID(Local.49)0x100 (WM_KEYDOWN)인지 확인한다. 맞을 경우 사용자가 지정해 놓은 MessageBox 출력을 수행한다.

Address        Command                                  Comments

00B44ED0  |.  CMP DWORD PTR SS:[LOCAL.49], 100        ; WM_KEYDOWN

00B44EDA  |.  JE SHORT 00B44EFF

00B44EFF  |   MOV ESI,ESP

00B44F01  |.  PUSH 40

00B44F03  |.  PUSH OFFSET 00B46C08                  ; |Caption = "Message"

00B44F08  |.  PUSH OFFSET 00B46D08                  ; |Text = "Key Down!"

00B44F0D  |.  MOV EAX,DWORD PTR SS:[ARG.1]         ; |

00B44F10  |.  PUSH EAX                               ; |hOwner => [ARG.1]

00B44F11  |.  CALL DWORD PTR DS:[<MessageBox>     ; \USER32.MessageBox

[코드 6] Disassembly Window Procedure

 

'O / S > Window' 카테고리의 다른 글

네트워크 패킷 캡처 (Windows)  (0) 2020.11.25
Windows Multi Task  (0) 2016.07.08
Windows Service  (0) 2016.06.21
Windows Boot Process (Vista 이상ver 부팅 과정)  (0) 2016.04.13
CSIDL 값  (0) 2016.02.20

Windows Multi Task

Kail-KM
|2016. 7. 8. 18:53

 

개요

우리는 학교 과제를 하기 위해 HWP나 Word, Excel, PPT 등의 응용프로그램을 실행해야 한다. 하지만 단순히 이러한 동작만을 하는 것이 아니라 노래를 듣는 동시에 코드를 짜거나, 이에 더해 PC 톡으로 친구들에게 물어보기도 한다. 우리가 이러한 프로그램들을 실행하면 결과적으로 CPU에서 명령어를 처리하게 된다. 그렇다면 어떻게 CPU가 하나뿐이더라도 동시에 여러 작업이 가능해지는 것일까? 이를 위해 프로세스와 스레드가 어떻게 동작하는지에 대하여 알아보자.

프로그램은 일반적으로 하드 디스크 등에 저장되어 있는 실행코드를 뜻하고, 프로세스는 프로그램을 구동하여 프로그램 자체와 프로그램의 상태가 메모리 상에서 실행되는 작업 단위를 지칭한다. 예를 들어, 하나의 프로그램을 여러 번 구동하면 여러 개의 프로세스가 메모리 상에서 실행된다. 프로세스란 단순히 사용자가 실행시킨 실행 파일만을 필요로 하는 것이 아니라 프로그램을 읽어 들일 메모리와 데이터를 보관할 메모리, CPU의 레지스터 그리고 Windows의 경우 윈도우와 파일 등의 리소스도 사용하여 프로그램을 작동시킨다. 이처럼 프로그램을 관리하기 위한 환경을 묶어서 'Context'라고 한다.

 

멀티 태스크

어떻게 여러 작업을 동시에 처리할 수 있을까? 위에서 말했다시피 모든 명령어는 CPU에서 처리해주므로 동작하게 된다. CPU가 두 개일 경우 동시에 두 가지의 작업을 각각의 CPU에서 처리할 수 있겠지만, 하나의 CPU만 쓰는 PC에서는 어떻게 이러한 동시 작업이 가능해지는가에 대답은 바로 우리가 그렇게 느끼도록 하는 것이다. 사실 동시에 작업을 한다고는 하지만 정확히 말하자면 아주 짧은 시간마다 작업을 전환하여 모든 작업이 동시에 처리되는 것마냥 느끼도록 한다.

예를 들어 위의 그림과 같이 몇 개의 프로세스가 메모리에 올라와 있다고 하자. 이때 CPU가 처리할 수 있는 것은 하나의 작업뿐이다. 그렇기에 먼저 프로세스 A를 처리하고, 처리가 끝나면 이를 다시 중지하고 프로세스 B를 처리한다. 마찬가지로 B를 처리하다가 다음엔 C를 처리하는 방식으로 동작하게 된다. 이렇게만 이야기하면 당연히 멀티 태스크라는 말이 안 어울리며 단일 태스크인 것 같지만, 아주 빠른 시간 내에 이런 작업이 이루어진다면 사용자가 느끼기에는 충분히 멀티 태스크가 되는 것이다.

그렇다면 태스크의 전환은 어떻게 이루어지는지에 대하여 프로세스를 예로 알아보자. 우선 응용프로그램이 자발적으로 운영체제에게 제어를 넘기는 것이다. 이를 '비선점형 멀티 태스크'라고 하며 응용프로그램의 처리가 끝나서 함수에서 빠져나오면 Windows로 제어권이 반환되고 이 시점에서 다른 응용프로그램으로 전환하는 경우가 예이다. 하지만 이러한 협조 방식에는 문제가 있다. 바로 하나의 응용프로그램에서 제어를 반환하지 않는다면 다른 모든 응용프로그램은 동작할 기회를 갖지 못하게 된다. 운영체제는 제어권을 응용프로그램으로부터 기다려야 하기 때문에 이는 결국 운영체제가 프로세스의 실행을 제어한다고 하기 어렵다고 할 수 있다.

이러한 단점으로 인해 나온 것이 바로 '선점형 멀티 태스크' 구조로 운영체제는 응용프로그램이 제어를 넘겨줄 때까지 기다릴 필요 없이 강제적으로 실행을 중단시켜 다른 응용프로그램으로 전환 시킬 수 있다. 이는 프로세스 실행의 제어권을 운영체제가 가지고 있다고 할 수 있으며, 강제적으로 실행을 중단시키는 방법에 사용되는 것이 바로 'Interrupt'로 이는 하드웨어로부터 제어신호와 CPU 명령 처리 중에 일어나는 여러 가지 이벤트를 계기로 강제로 특정 코드를 실행하도록 하는 CPU 기능의 일종이다. 인터럽트 처리에도 여러 종류가 있지만 그 중 하드웨어 타이머(클럭)에서 정기적으로 인터럽트를 거는 것이 있다. 운영체제는 이를 설정해서 정기적으로 인터럽트를 받고, 필요에 따라 프로세스 전환에 사용한다.

선점형 태스크 전환은 태스크의 작업이 완료되지 않아도 CPU를 선점하므로 이를 위해선 태스크 스위칭이 일어날 때 기존에 실행중인 태스크의 상태를 보존하고 있다가, 이후 재선점 시에 이를 가지고 올 필요가 있다. 이러한 작업 내용 저장 위치는 작업 단위에 따라 달라지게 된다. 프로세스를 하나의 작업 단위로 볼 경우 PCB(Process Control Block)에 대하여 알아야 한다. PCB에는 프로세스에 대한 다양한 정보들이 저장되어 있는데 스위칭이 일어날 때 바로 이 PCB와 관련된 동작을 수행한다.

만약 현재 Process #A가 CPU에서 실행 중인 상태에서 Process #B로 전환이 일어나면 Process #A가 Ready 상태가 되면서 해당 프로세스의 상태나 레지스터 값 등이 Process #A의 PCB에 저장된다. 반대로 Process #B는 Running 상태가 되면서 Process #B의 PCB에 저장된 내용들을 CPU로 적재 시킨다. 이와 같은 작업을 통해 Context Switching을 진행한다.

하지만 이러한 Process의 Context Switching은 상대적으로 많은 내용을 메모리에서 CPU로 옮기고 다시 CPU에서 메모리로 옮기는 작업을 수행해야 한다. 이에 반해 Thread를 작업 단위로 볼 경우 훨씬 용이한 Context Switching가 가능해진다. Thread는 Process 보다 작은 작업 단위임을 위에서 언급하였다. 이는 Process 마다 하나의 PCB와 주소 공간을 가지는 반면에 Thread의 경우 하나의 Process 안에서 많은 내용을 공유한다. 아래의 그림을 보자.

Case #1의 경우 Process A가 Process B를 생성한 것으로 두 프로세스 간에 전혀 공유되는 요소가 없이 각각의 요소를 서로 갖고 있는 것을 확인할 수 있다. 이에 반해 Case #2의 경우 Process C가 Thread #1과 Thread #2를 생성한 경우로 두 Thread 간에 Code, Data, Heap을 Process 안에서 공유하고 있다. 다시 말해 각각의 Thread는 Stack 영역만을 개별적으로 갖는다. 아래는 실제 프로그램의 메모리를 나타낸 것으로 네 개의 Thread가 각각의 Stack 공간을 갖고 있는 것을 확인할 수 있다.

Memory map

Address Size Owner Section Contains Type Access Initial >Mapped as

006FE000 00002000 > > Stack of main thread Priv >RW RW

00A8C000 00002000 > >

00A8E000 00002000 > > Stack of thread 2. >Priv >RW RW

00B8D000 00002000 > >

00B8F000 00001000 > > Stack of thread 3. >Priv >RW RW

00C8D000 00002000 > >

00C8F000 00001000 > > Stack of thread 4. >Priv >RW RW

 

이에 관련하여 Thread를 생성하는 API인 CreateThread()에도 해당 Thread에게 할당하고자 하는 Stack의 크기를 지정해줄 수가 있다. 이와 같이 Thread릁 통한 태스크 전환은 프로세스 기준 태스크 전환보다 훨씬 교환해야 할 요소가 적어 리소스를 덜 사용하게 된다. Context Switching이 일어나는 동안 CPU는 아무런 일을 하지 못한다는 점을 고려했을 때, 이러한 시간을 줄이는 것은 당연히 중요한 요소가 된다. 따라서 유사한 동작을 하는 두 개의 Task를 만들 경우 굳이 새로운 Process로 하는 것보단 데이터를 공유하는 Thread로 하는 것이 효율적이다.


HANDLE WINAPI CreateThread(

_In_opt_   LPSECURITY_ATTRIBUTES   lpThreadAttributes,

_In_       SIZE_                  dwStackSize,

_In_        LPTHREAD_START_ROUTINE lpStartAddress,

_In_opt_   LPVOID                  lpParameter,

_In_       DWORD                   dwCreationFlags,

_Out_opt_ LPDWORD                 lpThreadId

);

 

우선 순위

마지막으로 스케줄링 우선 순위에 대하여 알아보자. 여러 개의 응용프로그램을 좋은 효율로 동작하게 하려면 어떤 것을 먼저 어느 만큼 실행할 지가 매우 중요하다. 이러한 조절을 '스케줄링'이라고 하며 이는 운영체제에 따라 조금씩 방식이 다르다. Windows의 경우 Thread 우선 순위에 기반한 방식과 라운드-로빈 방식을 조합한 것이다. 우선 순위는 어떤 Thread를 우선해서 실행할 것인가를 나타내는 정수 값으로 0~31까지의 우선 순위가 있으며, Windows 에서는 Process 마다 기본적인 우선순위를 나타내는 Priority Class를 지정하고 있다. 물론 이러한 Thread 우선순위는 Process와 Thread를 생성할 때 지정되며, 실행 도중 SetThreadPriority API를 호출하여 우선순위를 변경할 수 있다. 

 

상수

 

기본 우선 순위

 

REALTIME_PRIORITY_CLASS

 

24

 

HIGH_PRIRITY_CLASS

 

13

 

ABOVE_NORMAL_PRIORITY_CLASS

 

10

 

NORMAL_PRIORITY_CLASS

 

8

 

BELOW_NORMAL_PRIORITY_CLASS

 

6

 

IDLE_PRIORITY_CLASS

 

4

 

Windows는 실행 큐를 우선 순위가 높은 것부터 조사해서 실행 큐의 앞쪽으로 옮긴다. 그리고 CPU에 있는 Thread가 대기 상태로 넘어가게 되면 새로운 우선순위가 높은 실행 큐를 CPU로 가지고 와서 실행한다. 그리고 대기 중인 Thread에 있는 Thread가 실행 준비가 되면 실행 Queue로 옮겨져 CPU에 의해 동작되길 다시 기다린다. 이와 같은 전환은 Thread가 주어진 시간 간격을 사용한 경우 외에도 디스크 접근이나 동기화 등을 위해 자발적인 대가 상태에 들어간 경우와 실행 도중에 우선순위가 더 높은 Thread의 실행 준비가 끝났을 경우 발생한다.

하지만 이처럼 규칙에 충실한 스케줄링이 안 좋을 때가 있다. 예를 들어 우선순위가 높은 Thread가 하나일 경우에는 남겨진 Thread가 영원히 실행되지 않는다. 그래서 실행 준비를 마친 Thread가 일정시간 동안 실행 기회를 얻지 못했을 경우 Windows에서는 그 Thread의 우선순위를 일시적으로 올려주도록 되어 있다. 또한 디스크 접근이 완료되어 실행준비가 되었을 때나 GUI Thread가 동작을 시작했을 때도 그 Thread의 우선순위를 일시적으로 올려준다. 이런 조작을 'Priority Boosts'라고 한다.

 

Reference

+ [API로 배우는 Windows 구조와 원리] p.33 ~ p.38

+ "컨텍스트 스위칭", http://blog.eairship.kr/257

+ "스레드 다루기 (기초편)", http://www.jiniya.net/wp/archives/7194    

+ "x86에서 TSS", http://onestep.tistory.com/31

+ MSDN, " https://msdn.microsoft.com/"

+ "프로세스가 뭐지?", http://bowbowbow.tistory.com/16

'O / S > Window' 카테고리의 다른 글

네트워크 패킷 캡처 (Windows)  (0) 2020.11.25
Windows Event Message  (0) 2016.07.13
Windows Service  (0) 2016.06.21
Windows Boot Process (Vista 이상ver 부팅 과정)  (0) 2016.04.13
CSIDL 값  (0) 2016.02.20

Windows Service

Kail-KM
|2016. 6. 21. 08:59
Introdution

윈도우 운영체제에 있어 우리가 흔히 직접 실행할 수 있는 프로그램 이외에 우리가 직접 실행하지 않아도 실행되는 프로세스가 있다. 이 중 서비스는 보통 시스템의 시작과 함께 시작되어 윈도우의 핵심 프로세스의 기능을 수행한다. 중요한 만큼 사용자의 개입을 최소화하기 위해 백그라운드에서 동작한다. 프로세스에 대한 제어권을 우리가 갖지 않고 윈도우의 서비스 제어 관리자가 가지고 있기 때문에 일반적인 방법으로 서비스를 분석할 수 없다. 이러한 요인들로 인해 악성코드 제작자는 서비스를 통해 백그라운드라는 점, 사용자 개입의 최소화 등의 이점을 누릴 수 있다. 따라서 이번 문서에서는 서비스가 어떻게 동작하는지, 그리고 이를 분석하기 위해서는 어떠한 방식을 사용해야 하는지에 대하여 알아보자.


Service Control Manager(SCM)

Windows에서 서비스를 실행할 때 중요한 역할을 수행하는 것이 SCM이다. SCM은 Boot시에 시작되는 시스템 프로세스의 일종으로, 서비스를 제공하는 프로그램의 등록, 시작, 종료, 삭제 등과 같은 모든 관리를 담당한다.  일반적으로 아래의 그림과 같이 우리는 Controller를 통해 SCM에게 서비스의 실행, 중지 등을 요청하여 서비스 프로그램을 시작하고 종료할 수 있다. 이렇듯 사용자나 다른 프로그램이 Service를 조작할 경우에는 반드시 SCM을 통해야만 한다. 이를 반대로 말하면 서비스 프로그램은 SCM에서 제어할 수 있는 장치를 넣어둔 프로그램이라 할 수 있다.

서비스 프로그램은 SCM에서 제어할 수 있는 장치를 가지고 있다는 것은 서비스 프로그램의 내부에 SCM에 제어를 요청하거나 연결을 요청하는 API가 존재하고 있다는 것이다. 만약 해당 API들이 없다면 서비스 프로그램은 단순히 일반 프로그램과 다를 바가 없다. 아래는 서비스 프로그램의 간략화된 main()으로 일반적인 프로그램의 main()과는 약간 다른 코드를 갖고 있다. 일반적인 프로그램의 경우 Main()에서부터 메인 스레드를 통해 독자적으로 처리하면 되지만, 이와는 다르게 서비스 프로그램의 경우에는  Main 함수에서 프로그램과 SCM을 연결해주는 StartServiceCtrlDispatcher API를 호출해주어야 한다. 여기서 연결이란 서비스의 진입점을 SCM에게 등록해주는 것으로, 해당 API를 통해 SCM은 서비스의 제어를 가지게 되고 서비스가 정지할 때까지 제어를 돌려주지 않는다.


해당 API의 인자로 지정된 services는 배열의 형태를 가지고 있다. 아래의 코드에서 "TestSvc"는 서비스의 이름을 지정해주는 것이고, TestSvcMain은 해당 서비스 프로그램이 SCM에게 실행을 요청할 내용의 코드이다. 이는 다시 말해 서비스 전용 메인 함수로 SCM은 새롭게 스레드를 생성해서 서비스 메인의 함수를 호출한다. 따라서 우리가 이후에 분석을 할 때에 중점적으로 확인해야 할 부분이 바로 TestSvcMain이 위치한 곳이 된다.

#include <Windows.h>
#include <stdio.h>

int main()
{
    SERVICE_TABLE_ENTRY services[] = {{"TestSvc", TestSvcMain}, {NULL, NULL}};
    if(!StartServiceCtrlDispatcher(services))
    {
        printf("서비스 연결 실패");
        return -1;
    }
    return 0;
}


ServiceMain & Control Handler

서비스 프로그램의 전용 메인 함수(위 예의 TestSvcMain)가 SCM과 연결되어 프로세스가 생성되었지만, SERVICE_START_PENDING 상태가 된다. 이는 정식 서비스로 동작하는 것은 아니며 이를 동작시키기 위해선 자신의 서비스 상태를 지정해주어야 한다. 서비스의 상태를 제어하기 위해 서비스의 메인 함수에서는 RegisterServiceCtrlHandler API를 호출해야 한다. 이는 SCM에 제어 핸들러를 등록하기 위한 API로, SCM은 서비스를 제어할 필요가 있을 때 이 핸들러를 호출하는 방법으로 서비스에 통지한다. 이것이 등록되어야 SCM이 서비스 프로그램을 제어할 수 있다. 

SERVICE_STATUS_HANDLE WINAPI RegisterServiceCtrlHandler( 
    _In_ LPCTSTR            lpServiceName,  
    _In_ LPHANDLER_FUNCTION lpHandlerProc
);

제어 핸들러를 등록하고 SetServiceStatus API를 통해 서비스가 시작 상태가 된 것을 SCM에 통지해야 한다. 시작된 것을 알리고자 할 때는 SERVICE_RUNNING을 인자로 해당 API를 호출하면 된다. 이러한 과정은 서비스 프로세스가 생성되고 1초 이내에 완료될 것을 기대학 때문에 그 이상 시간이 걸릴 경우 미리 설정해주어야 한다. 만약 지정한 시간이 지나도 상태가 갱신되지 않을 경우 SCM은 초기화에 실패했다고 판단하고 서비스를 종료한다. 시간 내에 SetServiceStatus를 통해 SERVICE_RUNNIGN을 지정해주었다면 비로소 정상적인 서비스 프로세스로 동작할 수 있게 된다.

BOOL WINAPI SetServiceStatus(  
    In_ SERVICE_STATUS_HANDLE hServiceStatus,  
    In_ LPSERVICE_STATUS      lpServiceStatus
);



Reference


https://ko.wikipedia.org/wiki/윈도우_서비스

[API로 배우는 Windows 구조와 원리]

[리버싱 핵심원리]



'O / S > Window' 카테고리의 다른 글

Windows Event Message  (0) 2016.07.13
Windows Multi Task  (0) 2016.07.08
Windows Boot Process (Vista 이상ver 부팅 과정)  (0) 2016.04.13
CSIDL 값  (0) 2016.02.20
Write Protection - Registry Setting  (0) 2016.01.17

WFP 무력화

Kail-KM
|2016. 6. 21. 08:59
WFP (Windows File Protection)

WFP는 중요한 Windows 시스템 파일이 대체 또는 변경되는 것을 방지하기 위해 Windows에서 기본적으로 제공하는 기능(Vista부터는 WRP로 대체)으로, 프로그램들이 Windows 시스템의 중요한 파일들을 덮어씌울 수 없게 하여 프로그램과 운영체제로부터 발생할 수 있는 문제를 사전에 방지한다. WFP는 보호하고자 하는 시스템 파일이 올바른지 확인하기 위해 코드 서명에 의해 생성된 카탈로그와 파일 시그니쳐를 사용하여 확인한다. 그렇다면 정상적인 경우라도 이러한 파일의 변경이 일어날 수 없을까? 시스템 파일에 치명적인 취약점이 발견되었을 경우 WFP에 의해 해당 파일을 대체하지 못한다면 이는 위험을 품고 있는 OS가 되어버릴 것이다. 따라서 보호되고 있는 파일을 대체하기 위한 방법들이 존재하고 있으며, 오직 아래의 방법들을 통해서만 대체가 가능하다.

- Update.exs를 통한 Windows 서비스 팩 설치
- Update.exe나 Hotfix.exe를 통한 Hotfixes 설치
- Winnt32.exe를 통한 운영체제 업그레이드
- Windows 업데이트

만약 프로그램이 다른 방법으로 보호되고 있는 파일을 대체하고자 한다면 WFP는 원래의 파일로 복구하고자 한다. Windows Installer는 중요한 시스템 파일을 설치할 때 WFP를 준수하고, 보호된 파일 자체를 설치하거나 교체하는 대신 보호된 파일을 교체하라는 요청과 함께 WFP를 호출하게 된다.


How the WFP feature works

WFP는 두 가지 메커니즘을 통해 시스템 파일 보호 기능을 제공한다. 첫 번째 방법은 백그라운드에서 동작하는 것으로 보호되고 있는 디렉터리에서 변경이 일어난다면 변경에 대한 알림을 받은 후 동작하게 된다. 이러한 알림을 받은 WFP는 어떤 파일이 변경되었는지 결정하며 만약 그 파일이 보호되고 있다면 WFP는 파일 시그니쳐를 통해 해당 파일이 올바른 파일인지 확인하는 작업을 거치게 된다. 만약 파일이 올바르지 않다면 WFP는 새로운 파일을 Cache 폴더나 원본 설치 파일에 존재하고 있는 정상적인 파일로 바꾼다. 

1. Cache 폴더(Default : %SystemRoot%\system32\dllcache)
2. 네트워크 설치 경로(네트워크 설치를 사용하여 설치한 경우)
3. Windows CD-ROM(시스템이 CD-ROM으로부터 설치된 경우)

위의 표는 WFP가 정상 파일을 탐색하는 경로로 파일이 변조된 경우 해당 위치로부터 정상 파일을 찾아 복원한 뒤 다음과 같은 시스템 로그를 기록한다. 해당 로그에선 보호되고 있는 파일을 대체하고자 시도했다는 기록을 볼 수가 있다.

Event ID: 64001 
Source: Windows File Protection 
Description: File replacement was attempted on the protected system file c:\winnt\system32\file_name. This file was restored to the original version to maintain system stability. The file version of the system file is x.x:x.x.


How to bypass the WFP

WFP는 두 개의 DLL(SFC.DLL과 SFC_OS.DLL)을 통해 구현되며 ReadDirectoryChacnge API를 사용하여  주요 폴더의 변경 여부를 검사한다. Windows 시스템의 중요 프로세스인 winlogon.exe는 시스템 부팅시 SFC_OS.DLL을 로드시키고, 해당 라이브러리의 Ordinal#1(SfcInitProt)를 호출한다. 해당 함수는 새로운 스레드('SFC Watcher Thread)를 하나 생성하며 이 스레드는 보호 대상 파일이 존재하고 있는 폴더에 대한 Directory Change Notification 이벤트를 생성한다. 이러한 보호폴더 이벤트는 WaitForMultipleObjects 함수에 의해 동기화되며, 만약 보호 대상 파일이 변경 또는 삭제된 경우 Cache 폴더에서 해당 파일을 원상 복구하도록 한다. 만약 백업 폴더에 대상 파일이 존재하지 않다면 사용자에게 윈도우 CD를 삽입하라는 메시지를 출력하게 된다.


SFC.DLL : Windows 2000에서는 WFP의 핵심기능을 담당하지만 XP부터는 SFC_OS.DL의 보조 역할

SFC_OS.DLL : XP부터 WFP의 핵심 기능을 담당

SFCFILES.DLL : 현재 보호되고 있는 파일의 리스트를 관리

SFC.EXE : System File Checker Utility


이러한 WFP는 주요 Windows 파일을 보호하는 메커니즘이니 만큼 악성코드에 의한 타깃이 되고 있다. 최근에도 주요 시스템 파일을 교체 또는 패치하는 형태의 악성코드가 다수 등장하고 있으며, 이로 인한 시스템 불안정, BSOD 등이 발생하고 있다. 그렇다면 이제 WFP를 무력화하는 방법에 대하여 알아보자.


Method #1 


WFP를 무력화시키는 첫 번째 방법은 winlogon이 갖고 있는 Direcotry change notification handle을 종료시키는 방법으로 이 방법을 사용하면 시스템이 재부팅하기 전까지 특정 폴더에 대한 파일 보호가 이루어지지 않는다. 해당 핸들을 종료하기 위해서는 ntdll.NtDuplicateHandle이나 kernel32.DuplicateHandle을 통해 해당 핸들을 복제한 다음 CloseHandle을 통해 핸들을 닫으면 된다.

BOOL WINAPI DuplicateHandle(        // Duplicates an onject handle
  _In_  HANDLE   hSourceProcessHandle,
  _In_  HANDLE   hSourceHandle,
  _In_  HANDLE   hTargetProcessHandle,
  _Out_ LPHANDLE lpTargetHandle,
  _In_  DWORD    dwDesiredAccess,
  _In_  BOOL     bInheritHandle,
  _In_  DWORD    dwOptions
);


Method #2


WFP를 무력화하는 두 번째 방법은 Winlogin.exe가 SFC_OS.DLL을 로드한 뒤 생성하였던 'SFC Watcher Thread'를 종료시키는 것이다. 해당 스레드를 종료시키기 위해서는 SFC_OS.DLL이 export 하고 있는 #2(SfcTermintaeWatcherThread)를 이용하는 것으로 해당 API는 파라미터를 필요로 하지 않는다. winlogon.exe에서 해당 API를 호출하면 해당 스레드는 종료되고, 이로 인해 재부팅 전까지 WFP 기능은 무력화된다.


SfcTerminateWatcherThread를 호출하기 위해서는 SeDebugPrivilege 권한이 필요하며 해당 스레드를 생성한 프로세스인 winlogon.exe에서 실행되어야 한다. 그러므로 이를 위해 Injection의 기법을 사용하는 경우가 많다는 것을 알 수 있다.


Method #3


세 번째 방법은 SFC API를 이용하여 특정 파일에 대한 WFP를 1분 동안 무력화하는 방법으로, 실제로 악성코드가 주로 사용하는 방법이다. sfc_os.dll의 ordinal #5 : SfcFileException을 이용하는 방법으로, 해당 API는 특정 파일에 대하여 1분 동안 WFP 기능을 무력화시킨다. 이 방법을 사용하기 위해서는 LoadLibrary를 통해 sfc_os.dll을 로드한 뒤 GetProcAddress에 "#5"를 넘겨주어 호출하는 코드를 확인할 수 있을 것이다.

DWORD WINAPI SfcFileException(DWORD ?, PWCHAR pwszFileName, DWORD ?);

위 구조와 같이 두 번째 인자에 해당 파일의 이름을 넣어주면 되고 첫 번째 인자와 세 번째인자는 알 수 없는 인자지만 첫 번째에는 0을, 세 번째 인자에는 -1을 넣어주어야 한다. 해당 API가 성공할 경우 return value는 0이며 만약 성공하지 못한 경우에는 1이 반환된다.


Method #4


네 번째 방법은 Undocumented 레지스트리 값을 이용하는 방법으로 Windows 2000 SP1 이전 버전까지만 가능한 방법이다. 해당 레지스트리를 설정하면 WFP는 영구적으로 무력화된다. 

KEY : HKLM\Software\Policies\Microsoft\Windows NT\Windows File Protection 
Value Name : SFCDisable
Value : 0xFFFFFF9D


Method #5


마지막 방법은 Protected File List를 패치하여 특정 파일에 대한 WFP를 영구적으로 무력화하는 방법이다. WFP가 보호하고자 하는 대상은 SFCFILES.DLL에 정의되어 있음을 언급했다시피 해당 대상이 되는 파일의 내용을 패치하여 특정 파일에 대한 WFP를 영구적으로 무력화할 수 있다.


이를 위해선 SFCFILES.DLL을 복사하여 무력화하고자 하는 파일을 찾은 다음 해당 이름의 첫 바이트를 \x00으로 바꾸어 준다. 이렇게 내용을 수정한 뒤 'PEChkSum'를 이용하여 체크섬 문제를 해결하고, 'MoveLatr'를 통해 부팅 시에 원본 파일을 대신해 수정한 파일을 로드하도록 설정해주면 된다. 이러한 준비를 끝냈다면 프로세스를 완료하기 위해 재부팅해주어야 한다.



Reference


https://support.microsoft.com/en-us/kb/222193

https://bitsum.com/aboutwfp.asp

http://sinun.tistory.com/144


'Reversing > Theory' 카테고리의 다른 글

Memory Detection(메모리 진단)  (0) 2016.09.26
Assembly로 보는 코드, strcmp 문자열 비교  (0) 2016.08.08
DLL이란?  (4) 2016.05.29
PE구조의 이해  (0) 2016.05.04
윈도우 후킹 원리 [PDF]  (1) 2016.04.23