no image
윈도우 후킹 원리 (1) - User Mode
Intro리버싱을 하는 데 있어 흔히 "Art of Reversing is an API Hooking"이라는 말과 같이 API 후킹은 리버싱의 꽃이라 일컬어진다. 어떤 윈도우 응용프로그램을 개발하기 위해서 우리는 다양한 종류의 언어나 도구를 사용할 수가 있다. 이런 언어나 도구를 사용하여 개발하는 방법은 다르더라도 결국, 개발된 프로그램의 내부로 들어가면 윈도우 운영체제가 제공하는 API를 호출한다.이러한 API는 사용자 영역뿐만 아니라 커널 영역에서도 Native API의 형태로 존재하기 때문에 API 후킹을 이해하는 것은 윈도우의 많은 부분을 조작할 수 있음을 의미한다. 따라서 이번 문서에서는 API 후킹에 대한 이해를 도모하며, 기본적인 후킹의 방법에 대해 이해하므로 다른 후킹 방법 또한 낯설지 않..
2016.04.23
Windows Boot Process (Vista 이상ver 부팅 과정)
Intro컴퓨터가 어떠한 과정으로 부팅되는지 알고 있는 것은 이후에 어떠한 악성코드가 어떤 부팅 과정에서 실행될 수 있는지에 대해 이해할 수 있는 중요한 요소이다. 부트킷과 같은 강력한 악성코드는 MBR을 변조하여 자신을 먼저 부팅시키기도 하며, 윈도우 운영체제가 실행됨과 동시에 여러 모듈을 로드할 때 로드되기도 한다. 따라서 이러한 요소들을 이해하기 위해 컴퓨터 부팅 절차에 대하여 알아보자. Power On우선 전원이 공급되지 않는다면 고철 덩어리에 불과하기 때문에 전원이 공급되어야 하는 것이 당연히 첫 번째 순서이다. 이러한 순서가 바로 "Power On" 단계로 전원이 공급되면 Power Supply가 외부 전압을 시스템에서 사용 가능한 전압으로 변환해준다. 이 변환된 전기 흐름은 CPU로 전달되어..
2016.04.13
System Call & SSDT Hooking
System CallWindows 운영체제는 사용자 모드와 커널 모드라는 두 가지 형태의 권한이 존재하고 있다. 굳이 하나가 아닌 두 가지로 분류되는 것은 모든 프로세스가 하나의 권한으로만 동작할 경우, 각 프로세스는 하드웨어나 프로세스에 직접 접근할 수 있게 된다. 이는 어떠한 프로세스라도 운영체제의 핵심 기능을 조작할 수 있게 되는 것이므로 보안에 있어 매우 취약하게 된다. 이러한 요소를 방지하기 위해 두 개의 영역으로 분류되었고, 당연히 사용자 모드에 존재하고 있는 프로세스는 커널 영역에 접근할 수가 없다. 하지만 커널 영역에 접근할 수 없다는 것은 해당 프로세스가 디스크의 내용을 읽을 수가 없게 되고, 그 외에도 많은 작업들에 제한이 생긴다. 따라서 이러한 불편함을 보완하기 위해 "사용자 모드의 ..
2016.04.10
윈도우 메모리구조와 메모리분석 기초
물리 메모리물리 메모리라 하면 우리는 흔히 RAM만을 생각한다. 하지만 실제 4GB램을 장착하더라도 사용할 수 있는 메모리는 4GB 이하이다. 이는 시스템이 관리하는 모든 메모리란 램 하나만을 의미하는 것이 아닌 장치 메모리(Device Memory)가 존재하기 때문인데, 이로 인해 우리가 사용할 수 있는 공간은 4GB 램일 경우 [4GB-장치 메모리]가 된다. 안 그래도 부족한 4GB 메모리가 이로 인해 더욱 부족하게 되는 것이다. 그렇기에 가상 메모리라는 개념을 사용하게 되었는데, 가상 메모리라 해도 결국 이 실제 메모리에서 활동하게 되는 것이다. 하지만 가상 메모리라 해도 결국 실제 메모리에서 활동한다고 하였는데, 여러 프로세스를 실행하면 이 주소 공간이 부족하지 않을까? 이를 위해 존재하는 것이 ..
2016.03.29
CPU 레지스터
CPU 레지스터메모리는 아래의 그림과 같은 계층 구조를 갖고 있다. 이러한 계층 구조는 메모리를 필요에 따라 여러 가지 종류로 나누어 놓는 것으로, 이렇게 나누어 놓은 것은 대부분 CPU가 메모리에 더 빨리 접근하도록 하기 위함이다. 하드 디스크는 직접 CPU에 접근할 방법이 존재하지 않고, 메모리는 CPU 외부에 존재하고 있기 때문에 캐시와 레지스터보다 더욱 느리게 접근된다. 이와 같이 레지스터는 CPU가 메모리에 더 빨리 접근하기 위해 존재한다.레지스터는 CPU의 작은 저장 공간으로 CPU가 데이터에 접근하는 가장 빠른 방법을 제공한다 하였다. IA-32에서 CPU는 8개의 범용 레지스터와 명령 포인터, 5개의 세그먼트 레지스터, 그리고 부동 소수점 데이터 레지스터가 존재하고 있다. 이에 대하여 각각..
2016.03.26
CSIDL 값
CSIDL_DESKTOP = 0 CSIDL_INTERNET = 1 CSIDL_PROGRAMS = 2 CSIDL_CONTROLS = 3 CSIDL_PRINTERS = 4 CSIDL_MY_DOCUMENTS = 5 CSIDL_PERSONAL = 0x5 CSIDL_FAVORITES = 6 CSIDL_STARTUP = 7 CSIDL_RECENT = 8 CSIDL_SENDTO = 9 CSIDL_BITBUCKET = 0xA CSIDL_STARTMENU = 0xB CSIDL_MYMUSIC = 0xD CSIDL_MYVIDEO = 0xE CSIDL_DESKTOPDIRECTORY = 0x10 CSIDL_DRIVES = 0x11 CSIDL_NETWORK = 0x12 CSIDL_NETHOOD = 0x13 CSIDL_FONTS ..
2016.02.20
no image
Windows Event Log (2) – 주요 이벤트 로그
1. 개요 이벤트 로그에 대하여 학습을 하기 위해 이벤트 ID에 대하여 알아보았다. 너무 많은 이벤트들이 존재하기에 어떤 이벤트가 중요한 것인지 불필요한지 직접 확인 하기에는 번거로움이 크다. 따라서 이번 문서에서는 많은 이벤트들 중에서 유심히 보아야 할 로그에 대하여 학습하고자 한다. 이에 대하여 NSA에서는 Spotting the Adversary with Windows Event Log Monitoring에 대하여 글을 작성하였으며 이를 토대로 해당 문서를 작성하고자 한다. 위에서 언급한 해당 문서에서는 크게 16개의 카테고리에 대하여 설명을 한다. 이러한 로그에 대하여 학습해보자.표 1. NSA – 16가지 이벤트 카테고리 아래의 테이블은 각 카테고리와 이를 나타내는 이벤트 ID에 대하여 정리된 ..
2016.02.01
no image
Windows Event Log (1) – 이벤트 로그의 개념
1. 이벤트 로그 로그란 감사 설정된 시스템의 모든 기록을 담고 있는 데이터라 할 수 있다. 이러한 데이터에는 성능, 오류, 경고 및 운영 정보 등의 중요 정보가 기록되며, 특별한 형태의 기준에 따라 숫자와 기호 등으로 이루어져 있다. 이러한 로그는 운영체제가 업그레이드 됨에 따라 버전 별로 형태와 경로가 조금 상이하다. 각 운영체제 별 이벤트 로그의 특징은 아래의 그림과 같다. 그림 1. 운영체제 별 로그 특징 이러한 로그를 분석하여 필요로 하는 유용한 정보를 만들어 낼 수가 있으며 로그 데이터 분석을 통해 얻을 수 있는 정보는 다음과 같이 다양하게 활용될 수가 있다. 표 1. 로그 데이터 분석의 활용 이러한 로그 데이터는 시스템에서 발생하는 모든 문제에 대한 유일한 단서가 될 수 있으며, 시스템에서 ..
2016.02.01

  Intro


리버싱을 하는 데 있어 흔히 "Art of Reversing is an API Hooking"이라는 말과 같이 API 후킹은 리버싱의 꽃이라 일컬어진다. 어떤 윈도우 응용프로그램을 개발하기 위해서 우리는 다양한 종류의 언어나 도구를 사용할 수가 있다. 이런 언어나 도구를 사용하여 개발하는 방법은 다르더라도 결국, 개발된 프로그램의 내부로 들어가면 윈도우 운영체제가 제공하는 API를 호출한다.

이러한 API는 사용자 영역뿐만 아니라 커널 영역에서도 Native API의 형태로 존재하기 때문에 API 후킹을 이해하는 것은 윈도우의 많은 부분을 조작할 수 있음을 의미한다. 따라서 이번 문서에서는 API 후킹에 대한 이해를 도모하며, 기본적인 후킹의 방법에 대해 이해하므로 다른 후킹 방법 또한 낯설지 않도록 하는 것이 목적이다.

단, 후킹을 진행하는 자세한 코드들은 포함하지 않고, 어느 부분을 어떠한 방식으로 후킹 하는지 중점적으로 살펴볼 것이다. 코드들의 경우 다른 좋은 소스가 많으므로 그러한 요소들을 찾아보면 좋을 것이다. 이제부터 API 후킹에 대해 알아보자.

  

Prior Knowledge


이번 장에서는 구체적인 API 후킹에 대하여 알아보기 전에 우리가 알고 있는 API가 무엇인지 다시 한 번 정리해보고 API 후킹의 기본적인 개념에 대하여 알아볼 것이다.


  What is an API?

API란 Application Programming Interface의 약자로 단어 자체만으로는 무슨 뜻인지 이해하기 힘들다. 쉽게 말해 운영체제가 응용프로그램을 위해 제공하는 함수의 집합으로 응용프로그램과 디바이스를 연결해주는 역할을 한다. 좀 더 구체적으로 응용프로그램이 메모리, 파일, 네트워크, 비디오, 사운드 등 시스템 자원을 사용하고 싶더라도 이들에 대해 직접 접근할 수가 없다. 이러한 시스템 자원은 운영체제가 직접 관리하며 보안이나 효율 등 여러 면에서 사용자 응용프로그램이 접근할 수 없도록 막아놓았기 때문이다.

따라서 사용자 응용프로그램이 시스템 커널에게 이러한 시스템 자원의 사용을 요청해야 하며, 이 방법이 바로 MS 제공한 Win32 API를 이용하는 것이다. 즉 API 함수 없이는 프로세스, 스레드, 메모리, 파일, 네트워크, 레지스트리 등 시스템 자원에 접근할 수 있는 프로그램을 만들 수가 없다. 아래 그림은 32비트 Windows OS의 프로세스 메모리를 간략히 나타낸 그림이다.

그림 1. User Mode & Kernel Mode

실제 응용프로그램 코드를 실행하기 위해서는 많은 DLL이 로딩된다. 모든 프로세스는 기본적으로 kernel32.dll이 로딩되며, kernel32.dll은 ntdll.dll을 로딩한다. 바로 이 ntdll.dll의 역할이 사용자 모드에서 커널 모드로 요청하는 작업을 수행한다. 이러한 방식을 통해 시스템 자원에 접근할 수 있게 되는 것이다.


  What is an API Hooking?

API 후킹에 관해 이야기하기 전에 후킹에 대하여 먼저 알아보자. 후킹이란 이미 작성되어 있는 코드의 특정 지점을 가로채서 동작 방식에 변화를 주는 기술이라 할 수 있다. Hook이라는 단어 자체가 낚싯바늘 같은 갈고리 모양을 가지는데 이를 통해 무엇인가를 낚아채는 것과 같이 컴퓨터에서도 무엇인가를 낚아채는 형태로 사용될 수 있다.

그렇다면 API를 후킹 한다는 말은 무슨 뜻일까? 바로 Win32 API가 호출되는 중간에서 가로채어 제어권을 얻어낸다는 것이다. 그렇다면 어느 시점에 가로채는지 궁금할 수가 있다. API 후킹에는 많은 기법이 존재하는데 이러한 분류가 주로 어떤 방식을 사용하는지, 그리고 바로 어느 지점에서 가로채는지에 따라 분류되므로 이에 대해서는 각 방식에서 자세히 알아볼 것이다. 기본적인 후킹의 개념은 아래의 그림과 같다.

그림 2. 정상호출과 후킹된 호출

정상적인 경우 A 단계가 진행된 다음에 B가 진행되어야 하지만, 후킹 된 호출의 경우 A 단계 다음에 B가 진행되는 것이 아니라 C가 진행된 뒤 B가 진행된다. 이를 통해 A의 요청을 조작하거나 특정한 조건에만 B가 실행되도록 조작할 수가 있다. 위와 같은 방식으로 API를 후킹 하면 그 함수의 기능을 사용하지 못하게 할 수도 있고 어떻게 사용하는지 감시만 할 수도 있다. 심지어 전혀 다른 내용으로 바뀌게끔 할 수도 있다.

  

User Mode Hooking


Windows에서는 크게 사용자 모드(User Mode)의 후킹과 커널 모드(Kernel Mode)의 후킹으로 나누어진다. 이번 장에서는 사용자 영역에서 어떻게 API 호출이 이루어지는지, 그리고 어떻게 이들을 후킹할 수 있는지에 대하여 알아보자.


  IAT Hooking

3.1.1 IAT(Import Address Table)

IAT후킹은 IAT(Import Address Table)를 후킹 하는 것으로, IAT는 쉽게 말해 해당 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블이다. 예를 들어, 어떤 프로그램이 Kernel32.dll의 GetDriveType API를 호출한다면 해당 API를 사용하기 위한 주소를 IAT에서 참조할 수 있다.

그림 3. PE View로 본 IAT

IAT 후킹은 바로 이 IAT에서 후킹하고자 하는 함수 주소를 내가 원하는 함수(이후 후킹 함수)로 교체하고, 후킹 함수에서 파라미터나 리턴 값을 조작하고 원래 함수를 호출하는 방법이 바로 IAT 후킹이다. 가장 쉬우면서도 안정적인 방법이라 일반적으로 자주 사용되는 후킹 방식이다. 그렇다면 일반적으로 API가 호출되는 상황을 보자.

그림 4. Sleep API

Sleep()를 호출할 때 직접 호출하지 않고 0x43B168 주소에 있는 값을 가져와서 호출한다. 0x43B168 주소는 해당 프로그램의 IAT 메모리 영역으로 0x76AE7990이라는 값이 존재하고 있다. 이제 이 값이 바로 해당 프로세스 메모리에 로딩된 Kernel32.dll의 Sleep 함수의 주소이다.

그렇다면 왜 0x40104F에서는 CALL 0x76AE7990이라 하지 않고 다른 곳을 참조하는 하여 접근하는 것일까? 이는 운영체제 버전이나, 어떤 언어, 서비스 팩이냐에 따라DLL의 버전이 다르며, 해당 함수의 위치가 달라지기 때문이다. 모든 환경에서 Sleep() 함수 호출을 보장하기 위해 컴파일러는 Sleep()의 실제 주소가 저장될 위치(0x43B168)를 준비하고 CALL DWORD PTR DS:[43B168]을 적어두기만 한다. 그 후 파일이 실행되는 순간 PE Loader가 0x43B168 위치에 Sleep() 함수의 주소를 입력해준다.

 

3.2.2 Hook

그렇다면 이러한 IAT의 개념에 대해 알아보았지만, 대체 이 부분 중 어느 곳을 후킹 해야 한다는 것인가 의문을 가질 수 있다. 위의 Sleep 함수를 예로, 사용자 모드에서 Sleep API가 호출되는 과정은 아래의 그림과 같이 나타낼 수 있다.

그림 5. 메모리에서 Sleep API

(A)는 3.2.1에서 이야기한 바와 같이 해당 함수를 사용하기 위한 주소 공간을 만들어 놓은 것이다. 이렇게 만들어 놓은 주소에 프로그램이 실행되며 PE 로더가 Sleep 함수의 주소를 채워 넣는다. 여기서 만들어진 주소 공간 43B168은 RVA 값이며, 이에 해당하는 파일 Offset은 39168이다. 39168은 바로 IAT 의 Sleep 함수가 위치한 Offset이다.

그림 6. Sleep API in IAT

만약 (A)에 존재하고 있는 값인 43B168을 다른 값으로 바꾸면 어떻게 될까? 직접 Sleep 함수를 호출하는 부분에 IsDebuggerPresent API의 IAT 주소를 넣어보자. 주소는 위 그림에서와 같이 Offset 39164이므로 이는 RVA 43B164가 된다. 이제 아래와 같이 코드를 패치해보자.

그림 7. 코드 패치

원래 Sleep()을 호출하던 부분에 IsDebuggerPresent()의 IAT 주소로 변경을 해주었다. 그렇다면 이제 위에서 언급한 바와 같이 PE 로더가 0x40104F에서 호출하는 함수의 주소를 0x43B164에서 참조하기 때문에 아래의 그림처럼 IsDebuggerPresent()가 위치하게 된다.

그림 8. 호출할 함수 주소 변경

이를 정리하자면, 프로그램이 사용하고자 하는 API를 호출할 때 해당 명령어에는 "CALL DWORD PTR DS:[IAT에 존재하는 해당 함수의 RVA]"로 나타내며, 프로그램이 실행되면서 실제 주소가 해당 DS:[RVA]에 올라오게 된다. 따라서 위와 같이 (A)를 후킹 하기 위해선 DS:[HookFunc]로 바꿔주어야 한다..

이번에는 (B)의 경우를 생각해보자. (A)는 프로그램이 실행되기 이전 파일의 형태에서 조작이 가능하였지만 (B)의 경우 파일이 실행되면서 43B168에 실제 Sleep API의 주소를 가지고 온다. 그러므로 (B) 부분은 파일이 아닌 프로세스일 경우에만 조작이 가능함을 알 수가 있다. 따라서 쉽게 DLL Injection과 같은 기법을 통해 프로세스의 메모리를 조작할 수 있지만, 이번 문서는 코드와 관련된 부분은 최대한 제외하고 원리를 이해함을 목적으로 할 것이기 때문에 필자는 디버거를 통해서 접근할 것이다.

그림 9. 함수 호출 - Debugger

위 그림을 보면 Sleep 함수를 호출할 때 43B168을 참조하며, IsDebuggerPresent 함수를 호출할 때는 43B164를 참고하는 것을 확인할 수 있다. 아래 덤프 영역을 보면 각각 해당하는 함수의 주소가 PE 로더에 의해 지정된 것을 확인할 수 있다. Sleep 함수가 가리키는 곳에는 76AE7990이 위치하며 다른 곳에는 76AEB0B0가 위치하고 있다. 해당 지점에는 각 함수의 실제 실행과 관련된 부분이 존재하고 있다. 따라서 이 위치를 변경하면 후킹을 진행할 수 있다. 만약 43B168(Sleep)에 76AEB0B0를 넣어주면 어떻게 될까?

그림 10. Sleep이 호출하는 주소 변경

위 그림처럼 분명 디버거는 Sleep()이라고 나타내지만 하단의 DS에서 가리키는 곳은 KERNEL32.IsDebuggerPresent 이다. 이를 통해 디버거 또한 IAT를 기준으로 주석에 나타내주는 것임을 알 수가 있다. 그렇다면 좀 더 심화 학습을 진행해보자. 흔히 코드 케이브(Code Cave)라 할 수 있는 방법을 여기에 적용해볼 수가 있다.

코드 케이브에 대해 간단히 설명하자면 해당 프로세스의 빈 공간에 사용자가 정의한 코드를 넣어준 뒤, 이 부분으로 프로그램이 전개되도록 하는 방식이다. 예제 프로그램에서 빈 공간은 Sleep이 호출되는 윗부분에 존재하고 있다. 따라서 Call 명령어를 통해 DS:[43B168]을 참조하게 되는데, 이때 43B168은 코드 케이브의 위치인 401023이 존재하고 있다. 401023에서 Sleep API의 인자로 사용되기 위해 스택에 저장되어 있는 값을 증가시키므로 흐름을 방해한다. ADD 명령 다음에는 원래의 기능을 수행하기 위해 조작되기 이전 값인 76AE7990로 점프를 해주면 된다.

그림 11. Code Cave를 사용한 후킹

이렇게 조작을 했는데 과연 제대로 프로그램이 동작할까? 당연히 제대로 동작한다. 이전 (A)에서는 IAT에 존재하는 다른 API로 변경하는 것만 진행했지만, 이번 과정에서는 인자로 전달되는 값을 직접 수정할 수가 있다. 이는 사용자로부터 입력 받은 값을 조작하여 특정한 함수의 흐름을 조작할 수 있는 것과 같이 다른 면에도 유용하게 활용할 수 있다. (B)에 적용한 방식은 (C)의 시작 부분을 JMP Hook_Address로 이동하여 유사하게 적용할 수 있다.

(C)의 경우 실제 함수의 명령어들이 위치하고 있다. 따라서 이 부분을 후킹 하기 위해서는 명령어를 조작하여야 하는데 어떻게 조작해야 할까? (C)의 내용은 밑에 그림과 같다. 자세히 보면 상단의 세 명령어의 OPCODE는 총 5 바이트이다. 바로 이 부분을 조작하므로 우리는 원하는 함수를 후킹할 수 있다. 아래의 그림을 보자.

그림 12. (C) 단계 원래 명령어와 조작된 명령어

Sleep() 함수의 원래 주소 7673A6B0의 명령어를 JMP 명령어로 변경한 것을 확인할 수 있다. 이렇게 변경된 Sleep()함수는 호출될 때마다 해당 부분으로 이동하게 되며 공격자가 의도한 대로 흐름이 변경될 것이다. 필자가 의도한 조작은 아래 그림과 같이 Sleep() 함수의 시간 값을 증가시킨 다음, 원래대로 코드를 복원하고 복원된 지점으로 이동하는 것이다.

그림 13. 조작 코드

이렇게 코드가 아닌 어셈블리로 조작한 것은 글을 읽는 사람들의 이해를 돕고자 진행한 것이다. 실제로 이렇게 하는 사람은 거의 없으며, 특히 42000E에서 원래대로 코드를 복원하는 부분은 VirtualProtect로 권한을 변경하여 진행하는 등 어셈블리로 진행할 경우 복잡하게 이루어지기 때문에 넣지 않았다. 하지만 실제 C/C++와 같은 언어로 후킹 함수를 제작할 때는 더욱 편리할 것이다.

마지막으로 예제를 통해 진행한 각 방식의 차이에 대하여 알아보자. (A)를 후킹 할 때 해당 주소의 코드를 직접 변경하였는데, 이로 인해 해당 주소가 아닌 지점에서 Sleep()을 호출할 경우 정상적인 Sleep()이 호출된다. 반면에 (B)의 경우 IAT가 가리키는 DS에 존재하는 값을 변경하였고, 이로 인해 Sleep() 함수를 호출하는 모든 곳이 조작된 DS를 가리킨다. 따라서 (B)의 방식의 경우 해당 프로세스에서 Sleep() 함수는 후킹 된 코드 케이브를 지나가게 된다. 마찬가지로 (C) 또한 실제 Sleep() 함수의 부분을 JMP 명령어로 조작하였기 때문에 해당 프로세스의 모든 Sleep() 함수가 후킹된 것이다.

특히 (C)의 방식은 Ntdll.dll의 함수도 조작이 가능하다. 아래의 그림을 보면 실제 Ntdll.ZwDelayExecution을 호출하는 것을 확인할 수 있으며, 해당 부분의 실제 코드의 5 바이트를 JMP 명령어로 수정하여 원하는 후킹을 진행할 수가 있다.

그림 14. Ntdll.dll의 API

단, 이러한 IAT후킹의 경우, 후킹 하고자 하는 함수가 IAT에 존재해야만 후킹할 수 있기 때문에 동적으로 API를 로드(예로, GetProcAddress API 등을 통해)하는 경우 IAT를 참조하지 않아 IAT 후킹을 사용할 없다. 또한 IAT는 프로세스마다 각각 존재하기 때문에 a.exe의 IAT를 후킹하였다고 b.exe까지 후킹 된 것이 아님을 유의해야 한다.


  Message Hooking

윈도우 운영체제는 사용자에게 GUI를 제공해주고, 사용자는 제공받은 GUI를 이용하여 원하는 동작을 할 수 있다. 동작을 수행하는데 있어 마우스를 움직이거나 클릭, 또는 키보드 버튼을 누르게 되는데 이러한 동작은 윈도우 운영체제가 Event Driven 방식으로 처리한다. 다시 말해 이러한 동작을 이벤트로 발생시켜 운영체제가 그 이벤트에 맞는 메시지를 해당 응용프로그램에게 전달하여 처리하는 방식이다.

아래 그림을 보면 메시지 후킹이 어떤 지점에서 이루어지는지 나타낸 것이다. 사용자가 어떠한 행위를 했을 때 이벤트가 발생되고, 이벤트 발생으로 인해 OS에서 응용프로그램으로 보낼 메시지들이 OS Message Queue에 존재하고 있다. 예를 들어 키보드 입력 이벤트가 발생하면 WM_KEYDOWN 메시지가 OS Message Queue에 추가된다. 운영체제는 해당 이벤트가 어느 응용프로그램에서 발생했는지 파악한 다음, 큐에서 메시지를 꺼내어 해당 응용프로그램의 메시지 큐에 전달한다. 해당 응용프로그램은 자신의 Application Message Queue에 WM_KEYDOWN 메시지가 추가된 것을 확인하고 해당 이벤트 핸들러를 호출한다. 이러한 방식으로 윈도우는 메시지를 전달한다.

그림 15. 메시지 전달 방식

재미있는 사실은 윈도우 운영체제에서 이러한 메시지 훅기능을 기본적으로 제공한다는 것이다. 바로 SetWindowsHookEx API가 그 주인공으로 훅 체인에 응용프로그램이 정의한 후크 프로시저를 설치하며 이를 통해 사용자는 특정 유형의 이벤트를 모니터링 할 수 있다.

HHOOK WINAPI SetWindowsHookEx(

_In_ int idHook // 훅 종류

_In_ HOOKPROC lpfn, // 지정한 이벤트 발생시 처리하는 프로시저 주소

_In_ HINSTANCE hMod, // lpfn이 있는 DLL의 핸들

_In_ DWORD dwThreadId

); 

그림 16. SetWindowsHookEx API

만약 해당 API를 구현하는 HookKey.dll이 존재하며 이를 실행하기 위한 HookMain.exe를 제작하였다고 가정하자. HookMain.exe를 실행하면 HookKey.dll이 해당 프로세스에 로드되며 SetWindowsHookEx()가 호출된다. 이렇게 메시지 후킹이 걸린 상태에서, 다른 프로세스가 해당 이벤트를 발생시킨다면 HookKey.dll은 그 프로세스에서도 로딩이 된다.

그림 17. DLL Injection

위 그림과 같이 나타낼 수 있으며, 다시 말해 후킹 된 이벤트가 발생하는 모든 프로세스에 DLL 인젝션이 일어나는 것이다. 이러한 방식을 통해 메시지를 후킹할 수 있으며, 이는 DLL 인젝션의 한 방법으로 사용할 수 있다.

 

 

Intro

컴퓨터가 어떠한 과정으로 부팅되는지 알고 있는 것은 이후에 어떠한 악성코드가 어떤 부팅 과정에서 실행될 수 있는지에 대해 이해할 수 있는 중요한 요소이다. 부트킷과 같은 강력한 악성코드는 MBR을 변조하여 자신을 먼저 부팅시키기도 하며, 윈도우 운영체제가 실행됨과 동시에 여러 모듈을 로드할 때 로드되기도 한다. 따라서 이러한 요소들을 이해하기 위해 컴퓨터 부팅 절차에 대하여 알아보자.


Power On

우선 전원이 공급되지 않는다면 고철 덩어리에 불과하기 때문에 전원이 공급되어야 하는 것이 당연히 첫 번째 순서이다. 이러한 순서가 바로 "Power On" 단계로 전원이 공급되면 Power Supply가 외부 전압을 시스템에서 사용 가능한 전압으로 변환해준다. 이 변환된 전기 흐름은 CPU로 전달되어 CPU에 남아 있는 불필요한 내용을 제거하고 PC(Program Counter)를 초기화시킨다. 


초기화되는 값은 보통 0xF000 값을 가지는데 이 값은 메인보드에 위치한 ROM BIOS의 부트 프로그램의 주소 값을 가리킨다. 따라서 기존 PC의 값은 사라지고 ROM BIOS의 부트 프로그램의 주소가 해당 자리에 오게 되는 것이다.


ROM BIOS

위 Power On 과정을 통해 PC(Program Counter)에 존재하고 있는 ROM BIOS로 넘어오게 된다. 여기서 BIOS(Basic Input Output System)란, 하드웨어 입출력을 제어하는 컴퓨터의 가장 기본적인 프로그램이며 하드웨어와 소프트웨어가 처음으로 만나는 지점이다. ROM BIOS는 다음 동작(POST)을 수행하는데 필요한 기본적인 테스트를 먼저 수행한다. 우선 CPU의 이상 유무를 확인하고 CPU 테스트 결과가 ROM BIOS에 저장된 값과 일치하면 다음의 본격적인 POST 작업을 수행한다.


POST

ROM BIOS에서 CPU의 이상 유무를 확인한 다음 이상이 없을 경우 본격적인 POST 과정을 진행한다. POST는 Power On Self-Test로 단계별로 해당 장치에 이상이 있는지 없는지 확인하는 과정을 진행한다. 만약 이 과정을 수행하는데 문제가 있을 경우 우리가 가끔씩 들을 수 있었던 비프음 소리를 내어 우리에게 인지할 수 있도록 한다. 총 8 단계로 나뉘며 각 항목에 대하여 알아보자.


1. 시스템 버스 테스트, 시스템 버스란 CPU와 메인 메모리 간에 데이터 전송을 컨트롤하며 컴퓨터 버스 중에서 가장 중요한 역할을 수행한다. 시스템 버스는 각기 다른 3 가지 타입의 정보를 전송하는데 아래의 그림과 같이 컨트롤, 주소, 데이터이다. 프로세서와 메모리 간의 통신, 프로세서와 I/O 장치 간 데이터 전송, I/O가 DMA를  통해 메모리와 하는 통신을 담당한다.

이와 같이 시스템 버스는 데이터가 CPU와 메모리 간을 여행하기 위한 고속도로라고 생각하면 된다. 그리고 이러한 시스템 버스가 정상적으로 동작하는지 확인하기 위해 특정 시그널을 보내 테스트하여 이상이 없다면 다음 단계로 넘어가게 된다.


2. RTC 테스트, RTC는 Real Time Clock으로 "실시간 시계"라는 말 그대로의 의미를 갖고 있다. 이는 CMOS에 위치하여 컴퓨터의 전원이 꺼지더라도 설정된 시간 값을 유지하고 있으며 이후에 다시 시스템에 시간을 제공하기 위한 모듈이다. 시간 값을 유지하고 있는 것은 생각보다 중요한 요소이기 때문에 POST 과정에서 RTC의 이상 유무를 확인하고 이상이 없을 경우 다음 단계로 넘어가게 된다.


3. 시스템 비디오 구성요소 테스트, 일반적으로 그래픽 카드를 테스트하며 과정이 완료되면 모니터와 같은 표준 출력을 통해 부팅 과정을 출력한다. 부팅 시 모니터를 보면 제일 먼저 나오는 정보가 바로 그래픽 정보라는 것을 알 수가 있다. 이러한 항목들의 이상 여부를 확인한 뒤, 다음 단계로 넘어간다.


4. RAM 테스트, RAM을 테스트하는 단계로 메모리 용량만큼 숫자를 카운트하며 이와 같은 테스트를 통해 현재 RAM이 정상적인지 확인한다. 


5. 키보드 테스트, 키보드가 정상적으로 연결되었는지와 눌러진 Key가 없는지를 테스트하며, 이때 에러가 발생하면 도중에 중단되고 에러 메시지를 나타낸다. 만약 특정 키보드의 키가 눌려 있는 경우 비프음을 출력될 것이다.


6. 드라이브 테스트, 다음 단계로 시스템에 연결된 플로피나 CD, HDD와 같은 모든 드라이브에 신호를 보내 정상적인지 확인하며, 이상이 있을 경우 에러 메시지를 나타낸다. 그 후, 기타 PnP(Plug and Play) 장치나 메인보드에 연결된 장치를 검색하며 검사한다.


7~8. POST 결과 테스트 및 추가적인 BIOS Load, 앞서 수행한 POST의 결과가 BIOS에 저장된 값과 일치하는지 검사한다. 그 후 추가적인 BIOS(자체적으로 BIOS를 가진 하드웨어)가 있을 경우 해당 BIOS를 RAM으로 올린다. 


MBR

POST 과정을 아무 이상 없이 완료하게 되면 부트 프로그램이 운영체제를 로드하기 위해, 인식한 드라이브 내에서 첫 번째 섹터를 읽는다. 드라이브의 첫 번째 섹터에는 MBR(Master Boot Record)이 위치하고 있다. MBR의 첫 번째 명령어인 JMP 0x63을 통해 해당 MBR의 코드를 진행하는 동안 오류가 발생하는지 확인한다.  

MBR의 코드 내용이 정상적으로 실행이 되었다면 이제 파티션 테이블에서 부팅 가능한 파티션을 찾는다. 파티션 테이블은 위 그림에서 붉게 표시한 부분으로 부팅 가능한 파티션은 각 16바이트 중 첫 번째의 값이 0x80이 위치하고 있다. 위 예에서는 세 번째 파티션이 부팅 가능함을 알 수가 있으며 해당 VBR로 JMP 하게 된다.


VBR

VBR은 부팅 가능한 파티션의 첫 번째 섹터로, 해당 파티션에 대한 정보가 들어있다. 또한 운영체제를 로드하는 파일들이 저장되어 있는데 이후 BIOS는 파일에 부팅 권한을 넘겨주게 되고, VBR은 해당 운영체제의 커널을 메모리에 로드하는 작업을 수행하게 된다.


윈도우 운영체제의 VBR에는 클러스터 크기, MFT의 위치, 전체 섹터 등 해당 볼륨의 추가적인 정보 외에 부팅에 필요한 시스템 파일의 위치와 실행할 수 있는 코드가 포함되어 있다. 이러한 코드는 Windows Vista 이전엔 NT Loader(NTLDR)를 실행하였지만, Windows Vista부터는 BOOTMGE.EXE를 실행하게 된다. 아래의 그림을 보자.


BOOTMGR.EXE


VBR에는 BOOTMGR.EXE를 실행시키는 코드가 포함되어 있으므로 이에 따라 실행된다. BOOTMGR.EXE는 NT 부트 섹터의 system32/boot 위치를 기반으로 로드되며 자신의 체크섬을 계산한 후 0x4000000에 맵핑된다. 32비트 BmMain() 함수를 수행한 뒤 부수적인 두 개의 과정을 수행하는데 우선 흔히 노트북의 절전모드와 같이 하이버네이션 상태의 경우 WINRESUME.EXE를 로드한다. 다른 하나의 과정은 위 그림과 같이 BCD(Boot Configuration Data)를 통해 부팅을 준비한다. BCD는 NT 계열의 Boot.ini와 같은 역할로, 기본적인 부팅 정보를 획득하며 부팅을 준비한다.


WINLOAD.EXE


윈도우 비스타 이전의 NTLDR과 비슷한 기능을 수행하며 Boot Loader라고 부른다. 부트 관리자가 BCD를 참조하여 해당 위치에 있는 윈도우 로드 파일인 WINLOAD.EXE를 실행하며 제어권을 이 파일에 넘긴다. 이 파일이 실행되면 드라이버 및 기타 필요 파일과 함께 커널을 읽는다. WINLOAD.EXE가 드라이버 및 기타 필요 파일을 읽은 후 윈도우를 시작하기 위해 NTOSKRNL.EXE를 읽어 NT 커널을 로드한다.


NTOSKRNL.EXE


NTOSKRNL.EXE는 커널과 HAL(Hardware Abstraction Layer), 시스템 레지스트리 정보들을 가지고 핵심 파일들을 실행하며 OslArchTransferToKernel을 사용하여 커널로 제어를 전환시킨다. NTOSKRNL.EXE을 로드하는 단계에서 시스템 프로세스가 생성되는데 시스템 프로세스는 커널에서만 실행되는 시스템 스레드들을 호스팅 하는 프로세스로 커널과 연결되는 서브시스템을 구동하거나 관리하는 역할을 한다. 추가적으로 실행되는 파일의 목록은 아래와 같다.

이후 두 단계의 시스템 초기화 과정을 거친다. 첫 번째 과정(Phase 0)은 커널 자체를 초기화한 다음, HallInitializeBios를 호출한다. 그리고 Display Driver를 초기화하며 디버거를 시작한 뒤, 마지막으로 KillInitializeKernel을 호출한다. 두 번째 과정(Phase 1)은 InitializationDiscard, HallInitSystem, ObInitSystem, ASLR set이 진행되며 그다음으로  PsInitialisystemProcess를 호출한 다음 StartFirstUserProcess의 순서로 진행된다. 이 단계에서 그래픽 모드로 전환하게 되며, 윈도우를 시작하는 화면이 출력된다.


SMSS.EXE


기본적인 초기화가 완료되면 사용자의 세션을 만들기 위한 환경을 구성하는 SMSS.EXE(Session Manager SubSystem)를 실행한다. 이는 시스템 스레드에서 시작되며 WINLOGON 및 WIN32(CSRSS.EXE) 프로세스의 시작과 시스템 변수 설정을 비롯한 다양한 작업을 수행한다. 커널 생성 이후 최초로 생성되는 시스템 프로세스로 이후 WINLOG.EXE를 실행한다.


WINLOGON.EXE


드라이브가 모두 로드되면 WINLOGON.EXE를 로드하고 LSASS.EXE(Local Security Authority)를 실행하여 로그온 화면을 표시한다. 이후 로그온 화면에 정보를 입력하면 userinit.exe가 실행되어 사용자 정보를 읽어 승인처리를 진행한다. 성공적으로 로딩되면 HKLM\SYSTEM\LastKnownGoodRecovery에 갱신된다. 


로그온이 되면서 동시에 장치 감지 과정이 일어나는데 새로운 장치가 감지되면 Plug n Play 기능은 드라이브 파일인 CAB을 설치하고 시스템 자원을 할당한 후 장치를 마운팅 하고 설정을 완료한다. 이 과정이 모두 끝나면 사용자는 소프트웨어와 하드웨어 사이의 특별한 환경과 시스템의 상호 작용을 연결하는 GUI(Graphic User Interface)를 가지게 된다.


Reference


http://cafe.naver.com/boanproject/book1293232/16953

http://proneer.tistory.com/entry/Windows-윈도우-부팅-순서

http://forensic-proof.com/archives/178

https://neosmart.net/wiki/mbr-boot-process/

http://muhan56.tistory.com/archive/20130426

http://schoolofweb.net/컴퓨터-버스란-시스템-버스-편

https://ko.wikipedia.org/wiki/실시간_시계


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

Windows Multi Task  (0) 2016.07.08
Windows Service  (0) 2016.06.21
CSIDL 값  (0) 2016.02.20
Write Protection - Registry Setting  (0) 2016.01.17
Windows USB Autorn 설정  (0) 2016.01.04
System Call

Windows 운영체제는 사용자 모드와 커널 모드라는 두 가지 형태의 권한이 존재하고 있다. 굳이 하나가 아닌 두 가지로 분류되는 것은 모든 프로세스가 하나의 권한으로만 동작할 경우, 각 프로세스는 하드웨어나 프로세스에 직접 접근할 수 있게 된다. 이는 어떠한 프로세스라도 운영체제의 핵심 기능을 조작할 수 있게 되는 것이므로 보안에 있어 매우 취약하게 된다. 이러한 요소를 방지하기 위해 두 개의 영역으로 분류되었고, 당연히 사용자 모드에 존재하고 있는 프로세스는 커널 영역에 접근할 수가 없다.


하지만 커널 영역에 접근할 수 없다는 것은 해당 프로세스가 디스크의 내용을 읽을 수가 없게 되고, 그 외에도 많은 작업들에 제한이 생긴다. 따라서 이러한 불편함을 보완하기 위해 "사용자 모드의 프로세스가 커널 영역에 접근할 수 있는 방법을 만들자."라는 의도에 부합되는 것이 바로 시스템 호출(System Call)이다. 즉, Windows는 커널에 대한 직접적인 접근을 제한하는 대신 사용자 영역에서 API를 호출하면 NTDLL.DLL을 거쳐 커널 모드로 진입하게 된다.

시스템 콜은 크게 2가지 방법으로 진행될 수 있는데, 바로 "INT 0x2E"와 "SYSENTER"이다. INT 0x2E의 경우 소프트웨어 인터럽트로 Windows XP 이전에는 이를 통해 시스템 콜을 진행하였다면, XP부터는 SYSENTER를 통해 시스템 콜을 진행하였다. 이 둘의 차이점이 몇 가지 존재하고 있지만 가장 큰 차이점은 바로, SYSENTER는 수행 시간에 따른 부하가 적다는 것이다. 또한 각 명령어가 호출된 후 과정에 차이가 있는데 이는 아래의 그림에서와 같이 SYSENTER 명령어가 진행될 경우 KiFastCallEntry()를 호출한 후, 이를 통해 다시 KiSystemService()를 호출한다. 이에 반해 INT 0x2E의 경우 KiFastCallEntry()를 거치지 않고 바로 호출된다는 것이다.


INT 0x2E나 SYSENTER 명령어의 경우 우리가 직접 확인할 수도 있다. 흔히 자주 접할 수 있는 OllyDBG와 같은 유저 모드 디버거를 통해 특정한 함수를 호출하는 부분을 계속 따라가다 보면 ntdll.dll의 함수를 볼 수가 있다. 그리고 이러한 함수를 계속 트레이싱하다 보면 INT 0x2E나 SYSENTER 명령어를 확인할 수 있다. 단, 유저 모드 디버거이기 때문에 해당 명령어가 수행되는 자세한 과정은 확인할 수가 없으므로 이를 확인하기 위해선 WinDBG와 같은 커널 디버거를 이용해야 한다. 진입하는 과정은 아래의 그림과 같다.

CALL EDX를 통해 지정된 주소를 호출하게 되는데, 해당 부분은 아래의 그림과 같다. 여기서 FS:[0x30]은 TEB의 0x30번째에 있는 값을 나타낸다. 이는 PEB를 나타내는 것으로 이러한 방법을 통해 PEB에 접근할 수가 있다. 그리고 PEB의 0x464번째 바이트 값을 통해 해당 값이 2인지 비교한 다음 만약 2라면 INT 0x2E를 통해 커널 모드에 진입하게 된다.

만약 해당 값이 2가 아니라면 JMP 명령어를 통해서 KiFastSystemCall 부분으로 넘어오게 되는데, 해당 함수의 부분은 SYSENTER 명령어를 통해 KiFastCallEntry로 이동하게 된다.

이렇게 커널 모드에 접근하는 과정을 확인할 수가 있었다. 그렇다면 어떻게 다시 사용자 영역으로 복귀할까? 이 역시 두 명령어가 차이를 보인다. SYSENTER 명령어의 경우 커널 영역에서 "SYSEXIT" 명령어를 통해 사용자 영역으로 돌아오게 되며, INT 0x2E의 경우 "IRET" 명령어를 통해 원래의 코드로 복귀가 가능하다.


IDT Hooking

IDT는 256개의 Enyrt로 이루어진 배열이며 엔트리 하나당 하나의 인터럽트에 대응되며 이러한 각 인터럽트는 IDT로부터 처리할 함수의 주소(ISR)를 전달받는다. 다시 말해, IDT에서는 각 인터럽트를 처리할 함수의 주소를 갖고 있다. 인터럽트 테이블의 메모리 주소를 얻으려면 IDTR 레지스터 값을 읽어야 하는데 이는 "sidt" 명령을 통해 알 수가 있으며, 반대로 'lidt' 명령을 통해 IDTR 레지스터의 값을 변경할 수 있다. sidt 명령에 의해 반환되는 idt 데이터 구조는 아래와 같다.

typedef struct
{
    unsigned short IDTLimit;
    unsigned short LowIDTbase;
    unsigned short HiIDTbase;
}

IDT의 주소는 위 구조체에서 확인할 수 있듯이, 하위 주소와 상위 주소가 나누어져 있다. 이를 통해 IDT의 주소를 확인할 수가 있는데, 그렇다면 IDT에는 어떠한 내용이 포함되어 있는지 아래의 그림을 보자. 크게 중요한 내용은 바로 위 시스템 콜에서 볼 수 있었던 0x2E와 키보드 인터럽트인 0x93이 존재하고 있다. 0x93의 경우 키보드 메시지 후킹과 관련된 내용이므로 자세히 다루지 않고 0x2E를 위주로 살펴보자.


INT 0x2E


응용 프로그램이 운영체제가 제공하는 API를 호출하면 NTDLL.DLL은 EAX 레지스터에 해당 시스템 함수의 번호를 로드하고 EDX 레지스터에는 그 시스템 함수로 전달되는 인자가 저장된 사용자 영역의 스택 주소를 로드한다. 그리고 INT 0x2E 명령을 수행하여 인터럽트를 발생시킨다. 이 인터럽트에 의해 유저 모드에서 커널 모드로 전환된다. 따라서 INT 0x2E 명령을 수행하여 인터럽트가 발생한 다음, IDT로부터 ISR의 주소를 얻어 실행하고자 할 때 바로 ISR의 주소를 바꾸어 IDT를 후킹 할 수가 있는 것이다. 이를 표현하면 아래와 같다.

IDT를 후킹 하는 데 있어 동작하고 있는 중 그 값이 변경되면 오류를 일으킬 수 있기 때문에 'cli' 명령을 통해 인터럽트 처리를 정지시킨 후, 해당 값이나 주소를 변경해야 한다. 모든 수정이 완료된 이후에는 다시 인터럽트 처리를 활성화시켜야 하기 때문에 'sti' 명령을 사용하여야만 한다. 이를 위한 코드는 아래와 같다.

_asm {
     sidt idt_info;
}

idt_entries = (*IDTENTRY) MAKELONG (idt_info.LowIDTbase, idt_info.HiIDTbase);
int2e_entry = &(idt_entries[0x2E])    //IDT에서 0x2E번째 엔트리 주소

_asm {
     cli;                                                                // 인터럽트 Disable
     lea eax, HookKiSystemService;             // EAX에 후킹 함수의 주소를 저장
     mov ebx, int2e_entry;                             // IDT에서 0x2E번째 엔트리의 주소 전달
     mov [ebx], ax;                                            // 후킹 함수 하위 16비트 주소를 IDT엔트리에 기록
     shr eax, 16;                                                 
     mov [ebx+6], ax;                                        // 후킹 함수 상위 16비트 주소 또한 기록
     sti;                                                                // 인터럽트 Enable
}


MSR Hooking

그렇다면 SYSENTER의 경우는 어떻게 될까? 윈도우 XP 이상에서는 INT 0x2E가 아닌 SYSENTER를 사용하는데, 시스템 콜이 요청되면 NTDLL은 EAX에 해당 시스템 콜의 번호를 로드하고 EDX 레지스터에서는 포인터 레지스터인 ESP를 로드한다. 그다음 NTDLL은 SYSENTER 명령을 실행한다. SYSENTER 명령이 실행되면 커널 영역에 들어가게 된다. 이때 그냥 커널로 들어가는 것이 아니라 실행되어질 커널의 주소인 MSR 0x176 레지스터(KiFastCallEntry) 내부에 있는 값으로 이동하게 된다. 그 후 KiFastCallEntry는 EAX에 저장되어 있는 시스템 콜 번호를 가지고 SSDT에서 Nt함수 주소로 가져오고 해당 함수를 호출한다. SSDT에 대해선 뒤에서 더 자세히 알아볼 것이다.

위 그림과 같이 나타낼 수 있으며, 바로 MSR 0x176의 값을 변경하므로 이를 수정하는 것이다. 이를 변경하기 위해서는 rdmsr 명령어와 wrmsr 명령어를 사용하여 msr의 값을 읽고 조작하여야 한다. 이를 위한 코드는 아래와 같다.

_asm {
    mov ecx, 0x176            // MSR 0x176 지정
    rdmsr                             // 어떠한 주소가 있지 읽음
    mov  OrigFunc, eax    // 기존에 있던 값을 저장
    mov eax, HookFunc  
    wrmsr                            // MSR 0x176에 HookFunc의 주소를 기록함
}

이렇게 후킹 작업을 완료한 다음, SYSENTER 명령으로 인해 MSR 0x176에 존재하고 있는 KiFastCallEntry의 주소가 아닌 훅 함수의 주소가 호출되므로 인해 KiFastCallEntry가 아닌 HookFunc()이 먼저 호출된 다음 KiFastCallEntry가 호출된다. 실제로 이러한 후킹 작업이 성공적으로 이루어졌다면 훅 함수에서는 EAX 레지스터에 있는 값을 통해 어떠한 시스템 콜을 요청했는지 확인할 수가 있다. 이는 다시 말해 특정한 시스템 콜을 지정하여 해당 시스템 콜은 거부되도록 할 수 있다.


단, 시스템 콜은 빈번하게 이루어지는 작업이기 때문에 과도한 조건문을 걸어놓는 것은 시스템의 성능을 크게 하락시키므로 사용자가 속도의 변화를 체감할 수도 있다. 따라서 빈번하게 호출되는 만큼 최적화된 내용만을 후킹 함수에 넣어야 한다.


SSDT Hooking

시스템 서비스 디스패치 테이블은 시스템 콜을 처리하기 위한 함수를 찾을 때 사용된다. 위에서 프로그램이 시스템 콜을 호출하는 두 가지 방법(INT 0x2E, SYSENTER)에 대하여 알아보았다. 이 두 명령어를 통해 결국 KiSystemService 함수(시스템 서비스 디스패처)를 호출하게 되는데, 이 함수는 EAX 레지스터에서 시스템 콜 번호를 읽어 SSDT에서 해당 시스템 콜의 루틴을 찾는다. 


또한 KiSystemService는 시스템 콜의 인자를 유저 모드 스택에서 커널 모드 스택으로 복사하는데, 이때 EDX 레지스터가 시스템 콜에 사용할 인자를 가리키고 있다(몇몇 루트킷은 이런 시스템 콜 처리 과정에서 시스템 콜의 인자를 변경하거나 시스템 콜의 수행 루틴을 변경시키기도 한다). 시스템 콜 번호에 맞게 KeServiceDescriptorTable을 참조하여 Native API를 호출한다. 그 후 시스템 콜을 종료하고 각각 IRET나 SYSEXIT 명령어를 사용하여 유저 모드로 복귀한다. 아래의 그림은 이를 종합적으로 표현한 내용이다.

결국 SSDT에서 서비스 호출 번호에 맞는 주소를 얻은 다음 이를 호출하는 형태로 진행되는 것이다. 그렇다면 SSDT Hooking은 어느 부분에서 후킹 해야 하는 것일까? 바로 GetFuncAddress 과정에서 인덱스 번호에 맞는 함수의 주소를 SSDT에서 가지고 올 때이다. 예를 들어, 0xAD 서비스 함수(EAX=0xAD) 시스템 콜이 발생하면 SSDT에서 0xAD번째에 위치한 함수의 주소를 가지고 오게 된다. 그리고 해당 함수를 호출하므로 진행되는 방식인데, 만약 SSDT를 변조하므로 0xAD번째에 위치한 함수의 주소를 HookFunc의 주소로 대체하면 해당 시스템 콜이 발생할 때마다 HookFunc가 호출된다.

위 예를 좀 더 구체적으로 제시하자면 0xAD 시스템 콜의 루틴이 SSDT에서 0xCCCCCCCC로 존재하고 있는 상황에 이 주소를 Hook함수의 주소인 0xDDDDDDDD로 대체하게 되면 0xAD 시스템 콜이 발생할 때마다 0xDDDDDDDD의 함수가 호출되게 된다. 대개 이러한 후킹 함수는 정상적인 루틴 0xCCCCCCCC를 조작하는데, 어떠한 인자가 오느냐에 따라 이를 진행하지 않거나 원래 함수의 결과가 특정 값일 경우 이를 변조하여 반환하는 등의 조작을 가할 수가 있다.


하지만 SSDT는 Read Only로 설정되어 있기 때문에 SSDT Hooking을 진행하기 위해서는 쓰기 권한이 필요하다. 이를 우회하기 위한 방법 중 하나인 CR0 Register에 대하여 알아보자. CR0 레지스터는 WP(Write Protect) Bit를 포함하고 있는데 이 값이 0 이면 메모리 보호 기능이 해제되고 1 이면 메모리 보호가 활성화된다. 따라서 메모리 보호 기능을 비활성화하므로 쓰기 권한을 얻을 수가 있다. 해당 코드는 다음과 같다.

__asm {    // 메모리 보호 기능 비활성화
    push eax
    mov eax, CR0
    and eax, 0xFFFEFFFF
    mov CR0, eax
    pop eax}

__asm {    // 메모리 보호 기능 활성화
    push eax
    mov eax, CR0
    or eax, NOT 0xFFFEFFFF
    mov CR0, eax
    pop eax}

이러한 후킹은 목적은 대개 흔적을 남기지 않는 것이 중요하므로, SSDT를 후킹 한 다음에는 다시 CR0를 조작하여 메모리 보호 기능을 활성화해주어야 한다. 이는 비활성화와는 반대로 or eax, NOT 0xFFFEFFFF 연산을 해주어야 한다. 이제 SSDT에 값을 기록할 수 있으므로 어떻게 변조할 것인가에 대하여 알아보자. SSDT Hooking에는 유용하게 사용되는 몇 가지 매크로가 존재한다. 


"SYSTEMSERVICE" 매크로는 ntoskrnl.exe에서 제공하는 Zw* 함수의 주소를 입력받아 그에 상응하는 Nt* 함수의 주소를 SSDT에서 구할 때 사용한다. "SYSCALL_INDEX" 매크로는 Zw* 함수의 주소를 입력받아서 SSDT에서 해당 함수의 인덱스 번호를 구하는 데 사용할 수 있다. 이렇게 "SYSTEMSERVICE"와 "SYSCALL_INDEX" 매크로가 해당 함수의 시작 부분(_func)에 +1을 하는 이유는 opcode에 따라 [mov eax, 인덱스 값] 이므로 해당 인덱스 값은 함수의 시작점에서 mov eax의 opcode 값 다음에 오므로 +1을 더해 해당 값을 알 수 있기 때문이다. 이 두 매크로를 통해 각각 Nt* 함수의 주소와 인덱스 번호를 구할 수가 있다.

#define SYSTEMSERVICE(_func)  KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)                                                                                                              ((PUCHAR)_func+1)]
#define SYSCALL_INDEX (_func) *(PULONG) ((PUCHAR) _func+1)


HOOK_SYSCALL과 UNHOOK_SYSCALL 매크로는 후킹을 수행할 Zw* 함수의 주소와 SSDT에서의 인덱스, 그리고 새로운 HookFunc 함수의 주소를 이용해 SSDT 안에서의 주소를 변경해준다. 밑의 코드 중에서 "InterlockedExchange"가 있는데 이는 두 개의 인자 값이 일치하지 않을 경우 첫 번째 인자가 지정하는 메모리의 값을 두 번째 인자로 바꾸는 함수이다.

#define HOOK_SYSCALL (_func, _Hook, _Orig)\
               _Orig=(PVOID) InterlockedExchange((PLONG)\                &MappedSystemCallTable[SYSCALL_INDEX(_func)],(LONG) _Hook)
#define UNHOOK_SYSCALL(_func, _Hook, _Orig)\
               InterlockedExchange((PLONG)\
               &MappedSystemCallTable[SYSCALL_INDEX(_func)], (LONG) _Orig)

이러한 과정을 통해 SSDT에 존재하고 있는 서비스 루틴을 훅 함수로 대체할 수가 있고, 다시 원래대로 되돌릴 수가 있다. 


Reference


http://luckyyowu.tistory.com/133

http://hackerspace.tistory.com/entry/시스템-호출-처리순서

http://bbolmin.tistory.com/158

http://luckey.tistory.com/86

http://securityfactory.tistory.com/158

https://en.wikipedia.org/wiki/Interrupt_descriptor_table

http://blog.naver.com/wwwkasa/80167132015

http://ezbeat.tistory.com/279

SSDT(System Service Descriptor Table) Hooking.pdf - Written by 백구

물리 메모리

물리 메모리라 하면 우리는 흔히 RAM만을 생각한다. 하지만 실제 4GB램을 장착하더라도 사용할 수 있는 메모리는 4GB 이하이다. 이는 시스템이 관리하는 모든 메모리란 램 하나만을 의미하는 것이 아닌 장치 메모리(Device Memory)가 존재하기 때문인데, 이로 인해 우리가 사용할 수 있는 공간은 4GB 램일 경우 [4GB-장치 메모리]가 된다. 안 그래도 부족한 4GB 메모리가 이로 인해 더욱 부족하게 되는 것이다. 그렇기에 가상 메모리라는 개념을 사용하게 되었는데, 가상 메모리라 해도 결국 이 실제 메모리에서 활동하게 되는 것이다. 

하지만 가상 메모리라 해도 결국 실제 메모리에서 활동한다고 하였는데, 여러 프로세스를 실행하면 이 주소 공간이 부족하지 않을까? 이를 위해 존재하는 것이 바로 페이징 기법이다. 페이징 기법은 메모리 관리자가 현재 사용하는 프로세스를 메모리에 올리고 만약 메모리에서 어떠한 프로세스가 놀거나 쉬고 있다면 이를 페이지 파일로 내리는 것이다.

이를 통해 컴퓨터는 더 넓은 메모리 공간을 갖고 있는 것처럼 사용할 수 있다. 단, 페이지 스왑이 일어날 때 디스크를 읽고 쓰는 작업이 발생하기 때문에 메모리만으로 프로세스가 동작할 때보다 많은 성능 저하가 발생한다는 것 또한 알아야 한다. 그렇다면 이제 각 프로세스마다 갖는 가상 메모리에 대하여 알아보자.


가상 메모리

가상 메로리란 단어 그 자체와 같이 물리적으로 존재하지 않는 가상적인 메모리 공간을 뜻한다. 메모리 공간 외에 하드 디스크에 파일 형태로 따로 준비하는 가상의 메모리 공간으로, 부족한 시스템 메모리를 보조해주는 역할을 한다. 보조해준다는 것은 실제로 존재하는 것이 아님과 같이 위 물리 메모리에서 언급한 바와 같이 실제 메모리가 부족할 경우 페이징을 통해 실제 메모리에 공간을 확보하여 실행함을 뜻한다. 만약 페이징 되어 있는 요소가 다시 메모리에서 활동해야 한다면 이를 다시 페이징 시켜 메모리에 올려 활동할 수 있도록 메모리 관리자가 이를 조정할 수 있도록 한다.


가상 메모리의 구조는 크게 나누어 아래 그림(좌측)과 같이 두 부분으로 나눌 수가 있다. 4GB의 가상 메모리가 할당되면 2GB의 사용자 영역과 2GB의 커널 영역으로 나누어지며 커널 영역은 윈도가 사용하는 공간이며, 사용자 영역은 우리가 보통 사용하는 일반 응용 프로그램들이 사용하는 공간이다. 이러한 가상 메모리는 하나의 프로세스마다 할당이 되어 독립적으로 제공된 자신만의 가상공간에서 작업할 수 있게 된다. 단, 여기서 커널 공간은 단일 공간으로 커널 모드를 사용하는 모든 프로세스에서 공유되며, 커널 영역은 공유되면서 시스템 운영에 필수적이기 때문에 주로 페이지 파일보다는 RAM에 존재하고 있다.


프로세스가 사용하는 가상 주소 공간의 내용도 결국 실행되기 위해서는 실제 메모리에 올라가야 한다. 이러한 조정을 메모리 관리자가 진행하게 되며, 위 물리 메모리의 그림에서와 같이 스왑이 일어나 메모리를 조정하는 것이다. 쉽게 말해, 사용하는 프로세스만 메모리에 올리고, 사용하지 않는 것은 디스크에 저장하는 방식이다.

이제 좀 더 세분화된 메모리 구조에 대하여 알아보자. 위 그림(우측)을 보면 code영역부터 시작하여 kernel 영역까지 나타나 있다. 이 또한 대략적인 형태이며, 이외에도 더 많은 요소들이 존재하고 있지만 너무 많은 내용들을 다루어야 하기 때문에 위에 나타난 항목에 대해서만 알아보자.


code 영역은 코드 자체를 구성하는 메모리 영역으로 메모리에서 실행하고자 하는 명령어들이 위치해 있으며 data 영역에는 초기화된 변수들이 존재하는 곳으로, 전역 변수, 정적 변수, 베열, 구조체 등이 저장된다. 그 외에 초기화되지 않은 변수들은 bss영역에 저장되며, 이러한 변수들은 프로그램이 실행될 때 생성되고 프로그램이 종료되면 시스템에 반환된다. 


위의 변수들은 프로그램의 실행과 함께 생성된다 하였다. 그렇다면 동적으로 할당되는 변수들은 어디에 존재할까? 바로 Heap 영역이다. 동적으로 할당된 변수들은 낮은 주소의 힙 영역부터 생성되어 높은 주소로 쌓이는 형태로 존재하고 있으며 만약 동적으로 할당된 변수를 해제할 경우 이는 힙 영역에서 사라지게 된다.


마지막으로 Stack 영역의 경우 프로그램이 자동으로 사용하는 임시 메모리 영역으로 지역변수, 매개변수, 반환 값 등을 위해 필요할 때마다 생성하고 지우는 등의 작업이 이루어진다. 다른 영역들과는 다르게 함수가 호출될 시 생성되며, 함수가 끝나면 제거된다. 스택 영역은 높은 주소에서부터 낮은 주소로 쌓이는 형태로 진행되며 우리가 흔히 알고 있는 LIFO(Last Input First Out)로 동작한다.


흔히 메모리 구조라 하면 위와 같이 나타나지만, 이보다 한 단계 나아가 좀 더 상세한 메모리 구조에 대하여 한 번 알아보자. DLL이 어떻게 어느 주소에 위치하는지, 어떠한 프로세스인지, 다음 프로세스는 어떠한 것이 있는지 등을 확인하기 위한 구조라 생각하면 된다. 아래의 그림이 이를 표현한 것으로 메모리 포렌식과 관련하여 공부를 해본 사람이라면 어떠한 요소들이 어디에 사용되는지 대략적인 감이 올 것이다.

커널 영역의 EPROCESS를 먼저 확인하. 아마 리버싱에 입문한지 얼마 안 된 사람이라면 이것이 어떠한 것인지 모를 수가 있다. EPROCESS 구조체는 프로세스에 관한 많은 정보를 담고 있는 구조체로 KPROCESS (PCB)를 가리키거나 프로세스의 생성 시간, 다른 프로세스가 가진 EPROCESS 구조, 토큰, 그리고 PEB의 주소 등 많은 정보를 가지고 있다. 프로세스는 이러한 EPROCESS 구조로부터 많은 정보를 얻을 수가 있는데 우선 PCB에 대하여 알아보자.


PCB는 Process Contorl Block으로 이들을 통해 ETHREAD와 KTHREAD 구조체를 참고하여 TEB(FS:0)을 찾을 수가 있다. TEB에는 스레드와 관련된 정보들을 포함하고 있으며, 이중에는 해당 스레드가 어떠한 프로세스에 속해있는지 알 수 있는 PEB(Process EnvironmentBlock)를 찾아갈 수가 있다. PEB에는 현재 프로세스의 Image Base Address에 대한 정보, LDR에 관한 정보 등에 대하여 알 수 있다. 이렇게 LDR을 확인하므로 어떠한 DLL이 어느 주소에 위치하였는지 확인할 수가 있다.


간략하게나마 메모리 구조로부터 얻을 수 있는 정보들에 대하여 확인해보았다. 간략하게나마 이러한 구조에 대해 이야기한 것은, 프로세스가 존재하고 있는 가상 메모리 하나로부터 많은 정보들을 확인할 수가 있기 때문이며, 이를 토대로 발전한 것이 바로 메모리 포렌식 분석 기법이다. 따라서 메모리에 대해 이해하는 것은 악성코드를 분석할 때 좀 더 주도면밀하게 분석할 수 있는 여건을 형성해 줄 것이다.


메모리 분석

메모리 구조에 대하여 간략히 알아보았으니 메모리 분석 도구를 통해 이에 대하여 한 번 실습을 진행해보자. 메모리 분석도구로 Volatility와 Rekall 등 다양한 도구가 존재하고 있지만, 필자는 Rekall을 통해 실습을 진행할 것이다. 우선 pslist 명령에 대하여 알아보자. pslist는 현재 메모리에서 실행되고 있거나 종료된 프로세스의 목록을 나열해준다. 그렇다면 어떠한 프로세스가 존재하고 있는지 어떻게 알 수 있을까? pslist 명령어는 EPROCESS 구조에서 ActiveProcessLinks를 통해 현재 실행 중인 프로세스의 목록을 가지고 온다. 해당 구조는 링크형태로 다음 프로세스와 이전 프로세스를 가리키고 있다. 

가장 우측의 "Start" 항목은 프로세스가 시작된 시간으로 이 또한 EPROCESS 구조체가 가지고 있는 CreateTime의 값을 통해 알 수가 있다. 그리고 만약 프로세스가 종료되어있지만 메모리에 아직 남아있는 경우 ExitTime을 통해 프로세스가 종료된 시간까지 확인할 수 있다. 


이번에는 프로세스가 가지고 있는 스레드에 대하여 알아보자. 스레드 목록 또한 EPROCESS 구조로부터 확인할 수가 있는데, 바로 ThreadListHead를 확인하면 된다. 해당 값은 위와 유사하게 어떠한 스레드가 존재하고 있는지 링크 형태로 존재하고 있다. 아래 결과를 확인하면 현재 해당 프로세스 DarkSeoul.exe에는 하나의 스레드가 존재하고 있는 것을 확인할 수 있다.


프로세스 목록과 스레드 목록에 대해 알아보았으니 이제 DLL의 목록에 대하여 알아보자. DLL 목록의 경우 EPROCESS 구조에서 PEB 구조의 위치를 확인한 다음, PEB 구조에서 LDR( _LDR_DATA_TABLE_ENTRY)의 "InMemoryOrderModuleList"를 통해 확인할 수가 있다. 해당 값에는 링크의 형태로 DLL의 목록을 가지고 있으며 이를 통해 어떠한 DLL들이 각 프로세스에 사용되고 있는지 확인할 수가 있다.


메모리로부터 프로세스가 가진 핸들 또한 확인할 수 있는데, 이는 쉽게 handles 명령을 사용하면 된다. 이러한 핸들의 목록은 위 큰 그림에서는 포함되지 않았지만, 마찬가지로 EPROCESS 구조로부터 ObjectTable (_HANDLE_TABLE)에 포함되어 있는 HandleTableList를 통해 핸들의 목록을 확인할 수가 있다.


이렇게 메모리의 구조에 대하여 알아보았고, 메모리 분석도구를 통해 실제 메모리를 분석해보며 어떠한 구조를 참고하는지에 대하여 알아보았다. 사실 이러한 메모리 구조를 모르더라도 Volatility나 Rekall을 사용할 수가 있다. 하지만 알고 쓰는 것과 모르고 쓰는 것은 엄연히 다르기 때문에 많이 알고 있어서 나쁠 건 없다고 생각한다. 여기서 다루지 않은 요소 중, 어떠한 정보를 어떻게 찾는지에 대해선 https://github.com/volatilityfoundation/volatility/wiki/Command-Reference를 참고하면 된다.


Reference


http://cappleblog.co.kr/554

http://www.codemachine.com/article_x64kvas.html

https://msdn.microsoft.com/en-us/library/ee483001(v=winembedded.60).aspx

http://computervirus.uw.hu/ch12lev1sec3.html


http://www.openrce.org/reference_library/files/reference/Windows%20Memory%20Layout,%20User-Kernel%20Address%20Spaces.pdf


https://github.com/volatilityfoundation/volatility/wiki/Command-Reference



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

System Call & SSDT Hooking  (0) 2016.04.10
BOF에 취약한 함수  (1) 2016.03.30
CPU 레지스터  (0) 2016.03.26
C기본 문법 어셈블리 변환  (5) 2016.03.20
Visual Studio 메인함수 찾기  (1) 2016.03.16

CPU 레지스터

Kail-KM
|2016. 3. 26. 02:28
CPU 레지스터

메모리는 아래의 그림과 같은 계층 구조를 갖고 있다. 이러한 계층 구조는 메모리를 필요에 따라 여러 가지 종류로 나누어 놓는 것으로, 이렇게 나누어 놓은 것은 대부분 CPU가 메모리에 더 빨리 접근하도록 하기 위함이다. 하드 디스크는 직접 CPU에 접근할 방법이 존재하지 않고, 메모리는 CPU 외부에 존재하고 있기 때문에 캐시와 레지스터보다 더욱 느리게 접근된다. 이와 같이 레지스터는 CPU가 메모리에 더 빨리 접근하기 위해 존재한다.

레지스터는 CPU의 작은 저장 공간으로 CPU가 데이터에 접근하는 가장 빠른 방법을 제공한다 하였다. IA-32에서 CPU는 8개의 범용 레지스터와 명령 포인터, 5개의 세그먼트 레지스터, 그리고 부동 소수점 데이터 레지스터가 존재하고 있다. 이에 대하여 각각 알아보자.


범용 레지스터 & 명령 포인터


범용 레지스터에는 8개의 레지스터가 존재하고 있다. 리버싱을 한 번이라도 해본 사람이라면 OllyDBG의 우측 상단에 존재하고 있는 레지스터 창을 보았을 것이다. 이러한 범용 레지스터를 설명하기 전에 레지스터의 크기에 대하여 먼저 알아보자.


일반적으로 EAX, ECX, EDX와 같이 'Extened'가 붙어 있는 경우를 많이 볼 수 있다. 하지만 유연한 진행을 위하여 하나의 EAX에서도 일부만 사용해야 할 상황이 필요하다. 그렇기에 비트가 나누어지는 것에 대하여 알아야 하는데, EAX는 32비트(DWORD)로 우리가 흔히 알고 있는 4바이트의 크기를 같는다. AX의 경우 16비트(WORD)의 크기를 갖는데, 이는 다시 상위 8비트(BYTE) AH와 하위 8비트(BYTE) AL로 구분할 수 있다.

EAX 레지스터(Accumulator Register)

주로 산술 연산과 리턴 값을 전달하기 위해 사용되며 상대적으로 사용되어 값이 자주 변하기 때문에 값을 보존하는 용도로 사용하지는 않는다. 산술 연산으로는 곱셈이나 나눗셈, 덧셈, 뺄셈 등 대부분의 경우 EAX에 해당 값이 들어 있는 경우가 많다. EAX가 리턴 값을 사용되는 경우란, C언어를 예로 Main() 함수에서 다른 함수 Sub()를 호출하였을 때 Sub()에서 "return 0;"을 사용하였다면 어셈블리에서는 Sub()가 RETN 명령어를 동작하기 전에 EAX 레지스터에 "MOV EAX, 0"과 같은 명령어를 사용하여 저장한다. 이렇게 값을 저장하기 때문에 해당 값을 다시 Main()에서 사용될 수 있게 된다.


ECX 레지스터(Counter Register)

반복문으로 인해 나타난 루프(Loop)에서 반복의 횟수를 제어할 때 주로 사용되며, EAX와 같이 많은 연산에 사용될 수도 있다.


EDX 레지스터(Data Register)

EAX와 함께 연산 작업에 주로 사용되며, 특히 나눗셈의 경우 피제수(소수)를 EDX에 넣어서 연산하며 연산 결과 몫은 EAX에 나머지는 EDX에 입력된다.


EBX(Base Index Register)

베이스 인덱스를 지정하는 용도로 사용되지만, 다른 레지스터 또한 베이스 인덱스를 지정하는데 자주 사용된다. 따라서 EBX는 특정한 역할을 갖기보다는 주로 변하지 않는 값을 저장할 때 사용된다.


ESI(Source Index Register) & EDI(Destination Index)

ESI와 ESI의 경우 반복문 등을 통하여 ESI에 있는 값을 EDI에 복사하고자 할 때 사용된다. 따라서 복사할 데이터는 ESI에 존재하고 있으며, 복사되어 저장될 새로운 공간을 EDI가 가리키고 있다.


ESP(Stack Pointer Register) & EBP(Base Pointer Register)

하나의 스택 프레임에 있어서 ESP는 스택의 끝 위치를 나타내며 EBP의 경우 스택의 시작 위치를 나타낸다. EBP의 경우 스택 프레임의 시작 위치 값을 갖고 있기 때문에 값이 잘 변하지 않지만, ESP의 경우 스택에 PUSH와 POP 명령어 등을 사용하기 때문에 유동적으로 값이 자주 변화한다.


EIP(Instruction Pointer)

EIP에는 다음에 실행해야 할 명령어가 존재하는 메모리 주소가 저장된다. 현재 명령어를 실행 완료한 후 EIP레지스터에 저장되어 있는 주소에 위치한 명령어를 실행하게 된다. 



세그먼트 레지스터


세그먼트는 프로그램에 정의된 메모리 상의 특정 영역으로 코드, 데이터, 스택 등을 포함하며 메모리의 대부분에 위치할 수 있다. 각 세그먼트 레지스터는 자신에게 지정된 주소를 가리키고 있으며, 기본적으로 CS, DS, SS 3개의 레지스터가 사용되며 이외에 ES, FS, GS 레지스터가 필요에 따라 사용될 수 있다.


CS (Code Register)

CS 레지스터는 코드 세그먼트의 시작 주소를 가리키며, 해당 세그먼트 주소에 EIP 레지스터의 오프셋 값을 더하면, 실행하기 위해 메모리로부터 가져와야 할 명령어의 주소가 된다. 일반적인 프로그래밍에서는 이 레지스터를 직접 참조할 필요가 없다.


DS (Data Register)

DS 레지스터는 프로그램의 데이터 세그먼트의 시작 주소를 포함한다. 명령어는 이 주소를 사용하여 데이터의 위치를 알아내며, 이 주소에 EIP 값을 더하면 데이터 세그먼트에 속한 특정 바이트 위치에 대한 참조가 생성된다.


SS (Stack Register)

SS 레지스터는 메모리 상에 스택의 구현을 가능하게 한다. 프로그램은 주소와 데이터의 임시 저장 목적으로 스택을 사용한다. 시스템은 프로그램의 스택 세그먼트의 시작 주소를 SS레지스터에 저장하며, 이 세그먼트 주소에 ESP 레지스터의 오프셋 값을 더하면 참조되고 있는 스택의 현재 워드를 가리킨다.


ES & FS & GS

이 3개의 레지스터는 Extra Segment로 여분의 데이터 세그먼트이다. ES의 경우 주로 문자열과 관련된 명령어를 위해 사용되는 세그먼트며, FS 세그먼트의 경우 TIB를 가리키는 세그먼트로 주로 안티 디버깅에 의해 참조될 수 있다. 특히 FS:[0x30]의 경우 PEB(Process Environment Block)를 가리키고 있기 때문에 디버깅 여부를 확인할 수가 있다. 이와 같이 FS의 경우 특정한 용도로 사용된다. 마지막으로 GS의 경우도 여분의 데이터 세그먼트로 주로 스택 스매싱이 일어났는지 확인할 때 사용할 수 있다.



플래그 레지스터


32비트 플래그는 다양한 컴퓨터 행동의 상태를 나타내는 비트를 포함하고 있다. 많은 플래그가 존재하고 있지만, 여기서는 OllyDBG와 같이 디버거에서 자주 볼 수 있는 플래그들에 대해서만 알아보자.


CF (Carry flag), 연산을 수행하면서 carry 혹은 borrow가 발생하면 1이 된다. Carry와 Borrow는 덧셈 연산 시 bit bound를 넘어가거나 뺄셈을 하는데 빌려오는 경우를 말한다.


PF (Parity flag), 연산 결과 최하위 바이트의 값이 짝수일 경우에 1이 되며 홀수일 경우 0이 된다. 이는 패리티 체크를 하기 위해 사용된다.


AF (Adjust flag), 8(16) 비트 연산에서, 하위 4(8) 비트로부터 상위 4(8) 비트로 자리올림이나 내림이 발생한 경우에 1로 셋 되고 그 외의 경우 0으로 셋 된다. 


ZF (Zero flag), 산술 및 논리 연산의 결과가 0 일 때 설정된다. 만약 연산의 결과가 0이 아닌 경우 해당 플래그는 0으로 나타나며, 연산 결과가 0일 경우 플래그가 셋 되어 1이 된다.


SF (Sign flag), 부호 비트가 1인 경우에는 1로 설정되고, 0인 경우 0으로 설정된다. 이는 다시 말해 음수인 경우에 1이 되는 것이며, 양수인 경우 0 임을 뜻한다.


TF (Trap flag), 디버깅에 사용되는 플래그로 설정된 경우 CPU는 명령 하나를 실행할 때마다 자동적으로 내부 인터럽트 1(INT1)이 발생한다. 해당 플래그가 설정된 경우 디버깅 시 Sing-Step이 가능해진다.


DF (Direction flag), 문자열을 처리할 때 해당 플래그가 0일 경우 문자열의 번지를 나타내는 레지스터 값이 자동으로 증가하고, 해당 플래그가 1일 경우 이러한 번지를 나타내는 값이 자동으로 감소한다.


OF (Overflow flag), 정수형 결과 값이 너무 큰 양수이거나 너무 작은 음수여서 피연산자의 데이터 타입에 모두 들어가지 않을 경우 1이 된다.



Reference


http://mafa.tistory.com/entry/3장-CPU-레지스터

http://carpedm20.blogspot.kr/2012/08/blog-post_13.html

https://en.wikipedia.org/wiki/Win32_Thread_Information_Block#Accessing_the_TIB

https://en.wikipedia.org/wiki/Process_Environment_Block

http://egloos.zum.com/voals/v/1669866

http://karfn84.tistory.com/entry/어셈블리-레지스터의-기능


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

BOF에 취약한 함수  (1) 2016.03.30
윈도우 메모리구조와 메모리분석 기초  (3) 2016.03.29
C기본 문법 어셈블리 변환  (5) 2016.03.20
Visual Studio 메인함수 찾기  (1) 2016.03.16
ClamAV & PEiD to Yara Rules  (1) 2016.03.11

CSIDL 값

Kail-KM
|2016. 2. 20. 20:40
CSIDL_DESKTOP = 0
CSIDL_INTERNET = 1
CSIDL_PROGRAMS = 2
CSIDL_CONTROLS = 3
CSIDL_PRINTERS = 4
CSIDL_MY_DOCUMENTS = 5
CSIDL_PERSONAL 	 = 0x5

CSIDL_FAVORITES = 6
CSIDL_STARTUP = 7
CSIDL_RECENT = 8
CSIDL_SENDTO = 9
CSIDL_BITBUCKET = 0xA
CSIDL_STARTMENU = 0xB
CSIDL_MYMUSIC = 0xD
CSIDL_MYVIDEO = 0xE
CSIDL_DESKTOPDIRECTORY = 0x10
CSIDL_DRIVES = 0x11
CSIDL_NETWORK = 0x12
CSIDL_NETHOOD = 0x13
CSIDL_FONTS = 0x14
CSIDL_TEMPLATES = 0x15
CSIDL_COMMON_STARTMENU = 0x16
CSIDL_COMMON_PROGRAMS = 0x17
CSIDL_COMMON_STARTUP = 0x18
CSIDL_COMMON_DESKTOPDIRECTORY = 0x19
CSIDL_APPDATA = 0x1A
CSIDL_PRINTHOOD = 0x1B
CSIDL_LOCAL_APPDATA = 0x1C
CSIDL_ALTSTARTUP = 0x1D
CSIDL_COMMON_ALTSTARTUP = 0x1E
CSIDL_COMMON_FAVORITES = 0x1F
CSIDL_INTERNET_CACHE = 0x20
CSIDL_COOKIES = 0x21
CSIDL_HISTORY = 0x22
CSIDL_COMMON_APPDATA = 0x23
CSIDL_WINDOWS = 0x24
CSIDL_SYSTEM = 0x25
CSIDL_PROGRAM_FILES = 0x26
CSIDL_MYPICTURES = 0x27
CSIDL_PROFILE = 0x28
CSIDL_SYSTEMX86 = 0x29
CSIDL_PROGRAM_FILESX86 = 0x2A
CSIDL_PROGRAM_FILES_COMMON = 0x2B
CSIDL_PROGRAM_FILES_COMMONX86 = 0x2C
CSIDL_COMMON_TEMPLATES = 0x2D
CSIDL_COMMON_DOCUMENTS = 0x2E
CSIDL_COMMON_ADMINTOOLS = 0x2F
CSIDL_ADMINTOOLS = 0x30
CSIDL_CONNECTIONS = 0x31
CSIDL_COMMON_MUSIC = 0x35
CSIDL_COMMON_PICTURES = 0x36
CSIDL_COMMON_VIDEO = 0x37
CSIDL_RESOURCES = 0x38
CSIDL_RESOURCES_LOCALIZED = 0x39
CSIDL_COMMON_OEM_LINKS = 0x3A
CSIDL_CDBURN_AREA = 0x3B
CSIDL_COMPUTERSNEARME = 0x3D

CSIDL_FLAG_CREATE 	 = 0x8000

# CSIDL_DESKTOP = 0x0
# CSIDL_INTERNET = 0x1
# CSIDL_PROGRAMS 	 = 0x2
# CSIDL_COMMON_PROGRAMS 	 = 0x17
# CSIDL_PRINTERS 	 = 0x4
# CSIDL_PERSONAL 	 = 0x5
# CSIDL_COMMON_DOCUMENTS 	 = 0x2E
# CSIDL_FAVORITES 	 = 0x6
# CSIDL_COMMON_FAVORITES 	 = 0x1F
# CSIDL_STARTUP 	 = 0x7
# CSIDL_COMMON_STARTUP 	 = 0x18
# CSIDL_RECENT 	 = 0x8
# CSIDL_SENDTO 	 = 0x9
# CSIDL_STARTMENU 	 = 0xB
# CSIDL_COMMON_STARTMENU 	 = 0x16
# CSIDL_DESKTOPDIRECTORY 	 = 0x10
# CSIDL_COMMON_DESKTOPDIRECTORY 	 = 0x19
# CSIDL_NETHOOD 	 = 0x13
# CSIDL_FONTS 	 = 0x14
# CSIDL_TEMPLATES 	 = 0x15
# CSIDL_COMMON_TEMPLATES 	 = 0x2D
# CSIDL_APPDATA 	 = 0x1A
# CSIDL_COMMON_APPDATA 	 = 0x23
# CSIDL_LOCAL_APPDATA 	 = 0x1C
# CSIDL_PRINTHOOD 	 = 0x1B
# CSIDL_ALTSTARTUP 	 = 0x1D
# CSIDL_COMMON_ALTSTARTUP 	 = 0x1E
# CSIDL_INTERNET_CACHE 	 = 0x20
# CSIDL_COOKIES 	 = 0x21
# CSIDL_HISTORY 	 = 0x22
# CSIDL_WINDOWS 	 = 0x24
# CSIDL_SYSTEM 	 = 0x25
# CSIDL_PROGRAM_FILES 	 = 0x26
# CSIDL_PROGRAM_FILES_COMMON 	 = 0x2B
# CSIDL_MYPICTURES 	 = 0x27
# CSIDL_COMMON_PICTURES 	 = 0x36
# CSIDL_PROFILE 	 = 0x28
# CSIDL_ADMINTOOLS 	 = 0x30
# CSIDL_COMMON_ADMINTOOLS 	 = 0x2F
# CSIDL_MYMUSIC 	 = 0xD
# CSIDL_COMMON_MUSIC 	 = 0x35
# CSIDL_MYVIDEO 	 = 0xE
# CSIDL_COMMON_VIDEO 	 = 0x37
# CSIDL_RESOURCES 	 = 0x38
# CSIDL_RESOURCES_LOCALIZED 	 = 0x39
# CSIDL_CDBURN_AREA 	 = 0x3B

csidl_names = {
    CSIDL_DESKTOP: "CSIDL_DESKTOP",
    CSIDL_INTERNET: "CSIDL_INTERNET",
    CSIDL_PROGRAMS: "CSIDL_PROGRAMS",
    CSIDL_CONTROLS: "CSIDL_CONTROLS",
    CSIDL_PRINTERS: "CSIDL_PRINTERS",
    CSIDL_MY_DOCUMENTS: "CSIDL_MY_DOCUMENTS",
    CSIDL_FAVORITES: "CSIDL_FAVORITES",
    CSIDL_STARTUP: "CSIDL_STARTUP",
    CSIDL_RECENT: "CSIDL_RECENT",
    CSIDL_SENDTO: "CSIDL_SENDTO",
    CSIDL_BITBUCKET: "CSIDL_BITBUCKET",
    CSIDL_STARTMENU: "CSIDL_STARTMENU",
    CSIDL_MYMUSIC: "CSIDL_MYMUSIC",
    CSIDL_MYVIDEO: "CSIDL_MYVIDEO",
    CSIDL_DESKTOPDIRECTORY: "CSIDL_DESKTOPDIRECTORY",
    CSIDL_DRIVES: "CSIDL_DRIVES",
    CSIDL_NETWORK: "CSIDL_NETWORK",
    CSIDL_NETHOOD: "CSIDL_NETHOOD",
    CSIDL_FONTS: "CSIDL_FONTS",
    CSIDL_TEMPLATES: "CSIDL_TEMPLATES",
    CSIDL_COMMON_STARTMENU: "CSIDL_COMMON_STARTMENU",
    CSIDL_COMMON_PROGRAMS: "CSIDL_COMMON_PROGRAMS",
    CSIDL_COMMON_STARTUP: "CSIDL_COMMON_STARTUP",
    CSIDL_COMMON_DESKTOPDIRECTORY: "CSIDL_COMMON_DESKTOPDIRECTORY",
    CSIDL_APPDATA: "CSIDL_APPDATA",
    CSIDL_PRINTHOOD: "CSIDL_PRINTHOOD",
    CSIDL_LOCAL_APPDATA: "CSIDL_LOCAL_APPDATA",
    CSIDL_ALTSTARTUP: "CSIDL_ALTSTARTUP",
    CSIDL_COMMON_ALTSTARTUP: "CSIDL_COMMON_ALTSTARTUP",
    CSIDL_COMMON_FAVORITES: "CSIDL_COMMON_FAVORITES",
    CSIDL_INTERNET_CACHE: "CSIDL_INTERNET_CACHE",
    CSIDL_COOKIES: "CSIDL_COOKIES",
    CSIDL_HISTORY: "CSIDL_HISTORY",
    CSIDL_COMMON_APPDATA: "CSIDL_COMMON_APPDATA",
    CSIDL_WINDOWS: "CSIDL_WINDOWS",
    CSIDL_SYSTEM: "CSIDL_SYSTEM",
    CSIDL_PROGRAM_FILES: "CSIDL_PROGRAM_FILES",
    CSIDL_MYPICTURES: "CSIDL_MYPICTURES",
    CSIDL_PROFILE: "CSIDL_PROFILE",
    CSIDL_SYSTEMX86: "CSIDL_SYSTEMX86",
    CSIDL_PROGRAM_FILESX86: "CSIDL_PROGRAM_FILESX86",
    CSIDL_PROGRAM_FILES_COMMON: "CSIDL_PROGRAM_FILES_COMMON",
    CSIDL_PROGRAM_FILES_COMMONX86: "CSIDL_PROGRAM_FILES_COMMONX86",
    CSIDL_COMMON_TEMPLATES: "CSIDL_COMMON_TEMPLATES",
    CSIDL_COMMON_DOCUMENTS: "CSIDL_COMMON_DOCUMENTS",
    CSIDL_COMMON_ADMINTOOLS: "CSIDL_COMMON_ADMINTOOLS",
    CSIDL_ADMINTOOLS: "CSIDL_ADMINTOOLS",
    CSIDL_CONNECTIONS: "CSIDL_CONNECTIONS",
    CSIDL_COMMON_MUSIC: "CSIDL_COMMON_MUSIC",
    CSIDL_COMMON_PICTURES: "CSIDL_COMMON_PICTURES",
    CSIDL_COMMON_VIDEO: "CSIDL_COMMON_VIDEO",
    CSIDL_RESOURCES: "CSIDL_RESOURCES",
    CSIDL_RESOURCES_LOCALIZED: "CSIDL_RESOURCES_LOCALIZED",
    CSIDL_COMMON_OEM_LINKS: "CSIDL_COMMON_OEM_LINKS",
    CSIDL_CDBURN_AREA: "CSIDL_CDBURN_AREA",
    CSIDL_COMPUTERSNEARME: "CSIDL_COMPUTERSNEARME"
}

csidl_display_names = {
    CSIDL_ADMINTOOLS: "Administrative Tools",
    CSIDL_CDBURN_AREA: "CD Burning",
    CSIDL_COMMON_ADMINTOOLS: "Administrative Tools",
    CSIDL_COMMON_OEM_LINKS: "OEM Links",
    CSIDL_COMMON_PROGRAMS: "Programs",
    CSIDL_COMMON_STARTMENU: "Start Menu",
    CSIDL_COMMON_STARTUP: "Startup",
    CSIDL_COMMON_ALTSTARTUP: "Startup",
    CSIDL_COMMON_TEMPLATES: "Templates",
    CSIDL_DRIVES: "My Computer",
    CSIDL_CONNECTIONS: "Network Connections",
    CSIDL_CONTROLS: "Control Panel",
    CSIDL_COOKIES: "Cookies",
    CSIDL_DESKTOP: "Desktop",
    CSIDL_DESKTOPDIRECTORY: "Desktop",
    CSIDL_FAVORITES: "Favorites",
    CSIDL_COMMON_FAVORITES: "Favorites",
    CSIDL_FONTS: "Fonts",
    CSIDL_HISTORY: "History",
    CSIDL_INTERNET_CACHE: "Temporary Internet Files",
    CSIDL_INTERNET: "Internet Explorer",
    CSIDL_LOCAL_APPDATA: "Application Data",
    CSIDL_MYMUSIC: "My Music",
    CSIDL_NETHOOD: "NetHood",
    CSIDL_NETWORK: "My Network Places",
    CSIDL_COMPUTERSNEARME: "My Network Places",
    CSIDL_MYPICTURES: "My Pictures",
    CSIDL_PRINTHOOD: "PrintHood",
    CSIDL_PRINTERS: "Printers and Faxes",
    CSIDL_PROFILE: "The user's username (%USERNAME%)",
    CSIDL_COMMON_APPDATA: "Application Data",
    CSIDL_PROGRAM_FILES: "Program Files",
    CSIDL_PROGRAM_FILES_COMMON: "Common Files",
    CSIDL_PROGRAM_FILES_COMMONX86: "Common Files",
    CSIDL_PROGRAM_FILESX86: "Program Files",
    CSIDL_PROGRAMS: "Programs",
    CSIDL_COMMON_DESKTOPDIRECTORY: "Desktop",
    CSIDL_COMMON_DOCUMENTS: "Shared Documents",
    CSIDL_COMMON_MUSIC: "Shared Music",
    CSIDL_COMMON_PICTURES: "Shared Pictures",
    CSIDL_COMMON_VIDEO: "Shared Video",
    CSIDL_RECENT: "My Recent Documents",
    CSIDL_BITBUCKET: "Recycle Bin",
    CSIDL_RESOURCES: "Resources",
    CSIDL_APPDATA: "Application Data",
    CSIDL_SENDTO: "SendTo",
    CSIDL_STARTMENU: "Start Menu",
    CSIDL_STARTUP: "Startup",
    CSIDL_ALTSTARTUP: "Startup",
    CSIDL_SYSTEM: "system32",
    CSIDL_SYSTEMX86: "system32",
    CSIDL_TEMPLATES: "Templates",
    CSIDL_WINDOWS: "WINDOWS"
}


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

Windows Service  (0) 2016.06.21
Windows Boot Process (Vista 이상ver 부팅 과정)  (0) 2016.04.13
Write Protection - Registry Setting  (0) 2016.01.17
Windows USB Autorn 설정  (0) 2016.01.04
Windows 10 _HANDLES_TABLE, _HADNLES_TABLE_ENTRY  (0) 2015.11.09

1. 개요


  이벤트 로그에 대하여 학습을 하기 위해 이벤트 ID에 대하여 알아보았다. 너무 많은 이벤트들이 존재하기에 어떤 이벤트가 중요한 것인지 불필요한지 직접 확인 하기에는 번거로움이 크다. 따라서 이번 문서에서는 많은 이벤트들 중에서 유심히 보아야 할 로그에 대하여 학습하고자 한다.

  이에 대하여 NSA에서는 Spotting the Adversary with Windows Event Log Monitoring에 대하여 글을 작성하였으며 이를 토대로 해당 문서를 작성하고자 한다. 위에서 언급한 해당 문서에서는 크게 16개의 카테고리에 대하여 설명을 한다. 이러한 로그에 대하여 학습해보자.

표 1. NSA – 16가지 이벤트 카테고리

  아래의 테이블은 각 카테고리와 이를 나타내는 이벤트 ID에 대하여 정리된 것이다. 이러한 표에서 몇 가지에 대하여 상세하게 알아보자.


2. 이벤트 카테고리


2.1 Application Whitelisting

  응용프로그램 화이트 리스트 이벤트는 차단된 응용 프로그램을 찾기 위해 수집되어야 한다. 차단된 응용프로그램들은 악의적일 수가 있으며, 승인되지 않은 소프트웨어의 실행을 시도하고자 한다. 응용프로그램 화이트 리스트 이벤트는 SRP(소프트웨어 제한 정책)이나 AppLocker가 네트워크 상에서 사용될 때 수집 될 수 있다.

그림 1. Whitelisting Events

2.2 Application Crashes

  응용 프로그램 충돌에 관한 이벤트들은 충돌이 발생했을 때, 이것이 악의적인 것에 의한 것인지 아닌지 판단하는데 도움을 준다. BSOD, Windows Error Reporting(WER), 응용 프로그램 충돌들은 이벤트를 발생시킨다. 만약 EMET(Microsoft Enhanced Mitigation Experience Toolkit)을 사용하고 있다면, EMET 로그들도 수집될 것이다.

그림 2. Application Events

2.3 System or Service Failures

  시스템과 서비스 실패는 조사하는데 있어 흥미로운 이벤트들이다. 서비스 동작은 보통 실패하지 않는다. 만약 서비스가 실패했을 경우, 관리자로서 이를 검토하여야 한다. 만약 윈도우 서비스가 지속적으로 실패할 경우, 이는 공격자가 해당 서비스를 대상으로 하고 있음을 나타내는 것이다.

그림 3. System Events

2.4 Windows Update Errors

  컴퓨터는 알려진 취약점을 대비하기 위하여 최신 상태를 유지해야 한다. 그럴 가능성은 거의 없지만, 이러한 패치가 때로는 적용하는데 실패하기도 한다. 업데이트를 실패하는 것은 운영체제나 응용프로그램의 취약점을 피하기 위하여 해결해야만 한다.

그림 4. Windows Update Failed Event

2.5 Windows Firewall

  만약 클라이언트 환경이 호스트 기반의 윈도우 방화벽의 사용하고 있다면, 방화벽의 상태를 나타내는 이벤트들이 수집된다. 예를 들어, 만약 방화벽이 활성화에서 비활성화로 전환된다면 이에 대한 로그가 남는 것이다. 일반적인 사용자는 방화벽의 상태나 규칙을 변경하지 않는다.

그림 5. Firewall Events

2.6 Clearing Event Logs

  정상적인 동작을 하는 동안 이벤트 로그 데이터가 지워질 가능성은 거의 없으며, 악의적인 공격자들이 그들의 행동을 감추기 위해 이벤트 로그를 지웠을 가능성은 높다. 이벤트 로그가 지워져을 때 이는 분명히 의심할 만한 사항이다. 이벤트 로그를 수집하는 것은 공격자가 자신의 행동을 감추기 더 어렵게 하는 이점이 있다.

  하지만 공격자가 이벤트를 지웠을 경우 이것이 새로운 이벤트로 기록이 된다. 따라서 로그를 지웠다는 새로운 이벤트가 발견된다면, 이에 맞게 추가적인 행동을 취해야 한다. 가령 VSS를 확인한다거나 삭제된 로그를 복구하는 조치를 취하면 좋을 것이다.

그림 6. Log Activity Events

2.7 Software and Service Installation

  일반적인 네트워크 동작의 일부로써, 새로운 소프트웨어나 서비스가 설치되며 이러한 활동에 대해서 이벤트가 생성 된다. 관리자들은 새롭게 설치된 소프트웨어나 시스템 서비스에 대한 로그를 통해 확인할 수가 있으며, 네트워크에 위험하지 않은지 추가적인 확인할 수가 있다.

그림 7. Software and Service Events

  여기서 이벤트 ID 800에 대하여 알아야 더 언급하자면, 윈도우 7에서 해당 이벤트는 매일 오전 12:30에 응용프로그램 활동에 대하여 요약을 제공하기 위하여 생성된다. 이 이벤트는 설치되거나 제거된 응용 프로그램의 수를 찾는 관리자들에게 편의를 제공할 것이다.

2.8 Account Usage

  사용자 계정 정보는 수집과 감사 될 수가 있다. 로컬 계정 사용을 추적하는 것은 Pass the Hash 활동과 인가되지 않은 다른 계정의 사용을 탐지할 수 있게 도와준다. 원격 데스크톱 로그인 이나 권한 그룹에 사용자를 추가하거나, 계정 잠금과 같은 추가적인 정보들은 추적될 수가 있다.

  권한 그룹에 추가된 사용자 계정은 실제로 권한 그룹에 속한 사용자임을 보증하기 위하여 더 면밀히 감시되어야 한다. 권한 그룹의 인가되지 않은 멤버는 악의적인 활동이 발생할 수 있음을 나타낸다.

그림 8. Account Activity Events

  도메인 계정 잠금 이벤트는 로컬 계정 잠금 이벤트가 로컬 컴퓨터에 생성되는 것에 반하여 도메인 컨트롤러에 생성된다. 또 위에서 성공적으로 사용자 계정에 로그인한 이벤트 (ID: 4624)에서는 로그온 유형에 대하여 반드시 확인해주어야 한다. 이에 대한 설명은 2.15의 로그온 유형 표를 참고하자.

2.9 Kernel Driver Signing

  윈도우 비스타 64비트에서 커널 드라이브 서명은 커널에 악의적인 드라이버들이나 행동이 삽입되는 것에 대해 막도록 하는 효과가 있다. 변경되어 보호되고 있는 드라이버의 표시는 악의적인 활동을 나타낼 수가 있거나 디스크 에러와 조사를 보장한다.

그림 9. Kernel Driver Signing Events

2.10 Group Policy Errors

  도메인 컴퓨터의 관리는 관리자가 보안과 그룹 정책의 규제를 높이는 것을 허가한다. 그룹 정책 에러로 인해 정책을 적용하지 못하는 것은 보안의 안전함을 감소시킨다. 따라서 관리자는 즉시 이러한 이벤트들에 대해 조사하여야 한다.

그림 10. Group Policy Errors Events

2.11 Windows Defender Activities

  Spyware나 Malware는 심각한 문제들을 남기기에 마이크로소프트는 Windows Defender라는 Anti-Spyware와 Anti-Virus를 개발하였다. 이러한 악의적인 프로그램을 감지, 제거, 예방한다는 알림은 조사되어야 한다. Windows Defender가 도작하는데 실패했다는 알림은, 관리자가 추가적인 감염이나 이러한 가능성을 방지하기 위해 즉시 올바르게 만들어야 한다. 만약 서드파티 안티바이러스나 안티스파이웨어 제품이 올바르게 사용되고 있다면, 이러한 로그는 필요하지 않다.

그림 11. Windows Defender Activities Event

2.12 Mobile Device Activities

  무선 장치들은 많은 곳에 존재하며 기업의 무선 장치 활동을 기록하는 것은 중요하다. 프로토콜에 관계 없이 통신을 위해 사용되는 무선장치들은 다른 네트워크를 오가며 손상될 수가 있다. 그러므로 어떤 휴대용 네트워크 장치를 추적하는 것은 손상이 일어나는 것을 방지하는데 유용하다. 아래의 이벤트들의 생성은 얼마나 자주 장치를 연결해제 하거나, 재연결 하는 가에 따라 빈번히 생성된다.

그림 12. Mobility related Events

2.13 External Media Detection

  USB 장치를 감지하는 것은 많은 환경에서 중요하다. 아래의 나타나는 이벤트와 이벤트 로그들은 오직 윈도우 8 이상에서만 사용이 가능하다.

그림 13. External Media Detection Events

  만약 윈도우 7이하의 경우에는 Microsoft-Windows-Driverframeworks-Usermode를 확인하면 된다. 해당 부분에서 이벤트 ID 1003, 2000, 2001, 1004를 통하여 USB가 연결되었다는 것을 확인할 수가 있으며 반대로 해당 USB가 해제된 것은 1006, 1008, 2900, 2901 등을 통해 확인할 수가 있다.

2.14 Printing Services

  문서를 인쇄하는 것은 많은 환경에서 일상적인 작업을 위해 필수적이다. 매우 많은 양의 인쇄는 어떤 문서가 인쇄되었는지, 누구의 것인지에 대하여 추적하거나 확인하는데 있어 어렵게 한다. 처리를 위해 프린터로 전달되는 문서들은 다양한 방식으로 로깅하기 위해 기록될 수 있다.

  각 인쇄 작업은 프린터 서버나 프린터 그 자체 또는 요청된 기계에 기록될 수가 있다. 이러한 동작들에 대한 로그를 기록하는 것은 인쇄된 특정 문서들을 발견할 수가 있다. 아래의 이벤트들은 문서를 인쇄하기 위해 요청되는 클라이언트 장비에서 생성된 것이다.

그림 14. Printing Services Events

2.15 Pass the Hash Detection

  사용자 계정에 Pass the Hash(PTH)를 검출하는 것은 XML로 고급 필터링 옵션을 구성하는 커스텀 뷰를 생성하는 것이 요구된다. 이벤트 쿼리 언어는 XPath에 기반한다. 아래의 추천된 쿼리 리스트는 PTH 공격을 감지하는데 제한된다. 이러한 쿼리들은 도메인의 일부가 아닌 로컬 계정을 사용하는 공격자의 행동을 밝히는데 초점이 맞추어져 있다.

  아래의 쿼리 리스트는 도메인의 일부가 아닌 다른 장치로 원격 연결을 시도하는 로컬 계정이 보여주는 이벤트들을 포착한다. 아래의 XPath 쿼리들은 이벤트 뷰어의 Custom Views를 통해 사용된다.

  성공적인 PTH은 Securiy Log의 이벤트 ID 4624를 야기한다. 도메인 로그온이 아닌 익명의 NTLM 인증은 로그온 유형이 3으로 나타난다. 아래에는 해당 이벤트에 대한 설명이 나와있으며 아래의 표에서 로그온 유형에 따른 설명들을 확인할 수가 있다.

그림 15. Logon Success – PTH Event

표 2. Logon Type

  로그온 유형에 따라 해당 로그인의 의미가 많이 중요해질 수가 있으므로 위의 표를 상기하여야 한다. 아래에 있는 쿼리 리스트에서 <DOMAIN NAME>에 실제 도메인 이름을 넣어주면 해당 쿼리가 올바르게 동작한다.

<QueryList>

<Query Id="0" Path="ForwardedEvents">

<Select Path="ForwardedEvents">

*[System[(Level=4 or Level=0) and (EventID=4624)]]

and

*[EventData[Data[@Name='LogonType'] and (Data='3')]]

and

*[EventData[Data[@Name='AuthenticationPackageName'] = 'NTLM']]

and

*[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]

and

*[EventData[Data[@Name='TargetDomainName'] != '<DOMAIN NAME>']]

</Select>

</Query>

</QueryList> 

  PTH를 통한 로그온 시도에 실패했을 경우 이는 이벤트 ID 4625를 야기한다. 이 역시 로그온 타입이 3으로 도메인 로그온이 아닌 익명의 로그온 계정이 NTLM 인증을 하고자 할 때 생성된다.

그림 16. Logon Fail – PTH Events

<QueryList>

<Query Id="0" Path="ForwardedEvents">

<Select Path="ForwardedEvents">

*[System[(Level=4 or Level=0) and (EventID=4625)]]

and

*[EventData[Data[@Name='LogonType'] and (Data='3')]]

and

*[EventData[Data[@Name='AuthenticationPackageName'] = 'NTLM']]

and

*[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]

and

*[EventData[Data[@Name='TargetDomainName'] != '<DOMAIN NAME>']]

</Select>

</Query>

</QueryList>

2.16 Remote Desktop Logon Detection

  원격 데스크탑 계정 활동 이벤트는 GUI 이벤트 뷰어를 통해 확인하기 쉽지 않다. 계정이 원격으로 클라이언트에 연결될 때, 일반적인 로그온 성공 이벤트가 생성된다. 커스텀 쿼리 필터는 행해진 로그온 유형을 분류하는데 도움을 줄 수 있다. 아래의 쿼리는 원격 데스크톱을 사용한 로그인을 보여준다. 원격 데스크톱 활동은 특정 관리자가 이를 사용하며 이는 제한된 설정에서 진행되어야 한다. 또한 이러한 활동은 모니터링 되어야만 한다. 만약 예상치 못한 로그인 활동이 있을 경우 이에 대해선 반드시 조사하여야 한다.

  아래의 XPath 쿼리는 이벤트 뷰어의 커스텀 뷰를 통해 사용된다. 이벤트 ID 4624와 4634는 각 각 언제 사용자가 로그온 했는지, 로그오프 했는지를 나타낸다. 로그온 타입 10은 이러한 원격 로그인에 대해 나타낸다.

그림 17. Remote Desktop Login Events

<QueryList>

<Query Id="0" Path="ForwardedEvents">

<Select Path="ForwardedEvents">

<!-- Collects Logon and Logoffs of RDP -->

<!-- Remote Desktop Protocol Connections -->

*[System[(Level=4 or Level=0) and (EventID=4624 or EventID=4634)]]

and

*[EventData[Data[@Name='LogonType']='10')]]

and

(*[EventData[Data[5]='10')]]

or

*[EventData[Data[@Name='AuthenticationPackageName'] = 'Negotiate']])

</Select>

</Query>

</QueryList>

 

3. 결론


2장의 내용을 통하여 어떠한 로그들이 어떠한 용도로 활용되어야 하는지에 대하여 알아보았다. 위의 내용이 옳다는 것만은 아니다. 당연히 이외에도 유심히 보아야 할 이벤트로그는 많이 존재할 것이다. 그러한 추가적인 내용을 공부하기 전에 이번 문서를 통해 어떠한 식으로 접근하여야 하는가에 대하여 알아보았다고 할 수 있다.

이벤트 로그를 공부하면서 많은 이벤트를 접하게 될 때 www.eventid.net/을 통해 찾아볼 수 있으며, 대부분의 경우 구글에 검색하면 많은 정보들을 볼 수가 있다.

  

출처


* 블로그, Spotting the Adversary with Windows Event Log Monitoring

http://www.redblue.team/2015/09/spotting-adversary-with-windows-event.html

* 블로그, 윈도우 이벤트 로그 분석 #기초

http://elven.kr/archives/361

* 문서, Spotting the Adversary with Windows Event Log Monitoring.pdf

    https://www.nsa.gov/ia/_files/app/spotting_the_adversary_with_windows_event_log_monitoring.pdf

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

Pulling the Plug  (0) 2017.03.22
Unicode 확장자 변조(RLO)  (3) 2016.05.15
Windows Event Log (1) – 이벤트 로그의 개념  (2) 2016.02.01
Volume Shadow Copy 분석  (1) 2016.01.18
NTFS FIle System (9) $UsnJrnl  (0) 2016.01.16

1. 이벤트 로그


  로그란 감사 설정된 시스템의 모든 기록을 담고 있는 데이터라 할 수 있다. 이러한 데이터에는 성능, 오류, 경고 및 운영 정보 등의 중요 정보가 기록되며, 특별한 형태의 기준에 따라 숫자와 기호 등으로 이루어져 있다. 이러한 로그는 운영체제가 업그레이드 됨에 따라 버전 별로 형태와 경로가 조금 상이하다. 각 운영체제 별 이벤트 로그의 특징은 아래의 그림과 같다.


그림 1. 운영체제 별 로그 특징


  이러한 로그를 분석하여 필요로 하는 유용한 정보를 만들어 낼 수가 있으며 로그 데이터 분석을 통해 얻을 수 있는 정보는 다음과 같이 다양하게 활용될 수가 있다.


표 1. 로그 데이터 분석의 활용


  이러한 로그 데이터는 시스템에서 발생하는 모든 문제에 대한 유일한 단서가 될 수 있으며, 시스템에서 발생한 오류 및 보안 결함에 대하여 검색이 가능하다. 또한 잠재적인 시스템 문제를 예측하는데 사용할 수가 있다.

  장애 발생시 복구에 필요한 정보로 활용하거나, 침해 사고가 발생된 경우 로그에 남아 있는 근거들을 자료로 활용할 수가 있다. 이렇게 살펴본 바와 같이 로그 데이터는 중요한 의미를 가지고 있으며, 그렇기에 올바른 분석이 더욱 요구 된다.



2. 로그 관리 및 분석


  로그 분석에 필요한 정보로는 로그 설정 방법, 파일의 저장 위치, 로그에서 나타내는 정보를 말할 수가 있다. 그리고 로그를 관리하기 위해서는 로그의 실시간 저장 및 무결성을 확보해야 하는데, 이를 위해선 시스템의 로컬에 로그를 저장하기 보다는 원격 로그서버를 구축하거나 DB서버와 연동하는 방식도 사용된다. 기초적인 Windows의 로그에 대하여 알아보자.


2.1 이벤트 로그의 이해

  Windows 시스템에서는 시스템의 로그가 이벤트 로그형식으로 관리되며, 이벤트 로그를 확인하기 위해서는 Windows의 Event Viewer를 이용하여야 한다. Event Viewer를 실행시키면 아래의 그림과 같은 모습을 나타내는 것을 확인할 수가 있다.


그림 2. 윈도우 이벤트 뷰어


  Windows 시스템은 응용 프로그램 로그, 보안 로그, 시스템 로그와 같이 세 가지 로그를 이벤트에 기록하며, OS 구성에 따라 디렉터리 서비스 로그, 파일 복제 서비스 로그, DNS 서버 로그가 추가될 수가 있다. 주요 이벤트 별 특징은 다음과 같다.


표 2. Windows 시스템 이벤트 로그 종류


이러한 로그들은 이벤트 뷰어를 통해 쉽게 확인할 수가 있다. 그렇다면 이벤트 뷰어를 통해 어떻게 나타나는지 아래의 그림을 통해 확인해보자.


그림 3. 이벤트 헤더와 설명


  각 이벤트를 확인하면 위와 같은 정보들이 나타난다. 위의 상자와 같은 부분에서 어떠한 경로에 있는지 등에 대한 정보가 있으며, 아래에는 해당 이벤트에 대한 정보들이 나타나 있다. 상단의 탭에서 Details를 누르면 좀 더 많은 정보들을 확인할 수가 있다.

  위 그림에서 하단의 Level이라 되어 있는 곳을 보자. Level에는 Information이라 되어 있다. 이처럼 이벤트 뷰어에서는 이벤트의 형태를 다섯 가지의 유형으로 구분한다. 이러한 다섯 가지 유형에 대해 아래의 그림을 통해 확인하자.


표 3. 이벤트 유형


2.2 이벤트 로그 설정 확인

  이벤트 로그에 대하여 개략적으로 알아보았다. 그렇다면 이러한 이벤트 로그의 설정을 확인하는 방법에 대하여 알아보자. 이벤트 로그를 설정하기 위해선 크게 두 가지 방법이 있으며 바로 레지스트리를 이용하는 방법과 이벤트 뷰어를 이용하는 방법이다. 이에 대해 알아보자.


레지스트리를 통한 확인

  레지스트리를 통해 확인하는 방법에 대하여 먼저 알아보자. 우선 Win+R을 통해 실행 창이 나타나면 regedit를 입력하여 레지스트리 편집기를 띄우자. 그리고 아래의 경로로 이동하면 이벤트 로그에 관한 설정이 나타나는 것을 확인할 수가 있다.


그림 4. 레지스트리를 통한 이벤트 로그 설정

위와 같이 나타나는 것을 확인할 수가 있다. 좌측의 키에는 응용프로그램, 보안, 시스템 등이 존재하는 것을 확인할 수가 있다. 우측은 각 항목들을 통해 로그에 대한 설정이 기록되어 있는 것을 확인할 수가 있다. 몇 가지 항목이 의미하는 것에 대하여 알아보자.

표 4. 레지스트리 - 항목 설명


이벤트 뷰어를 통한 확인

이벤트 뷰어에서도 각 이벤트 로그에 대한 속성을 변경할 수가 있다. 해당 속성을 변경하고자 한다면, 해당 이벤트의 속성에 들어가야 한다. 아래의 그림을 보자.


그림 5. 이벤트 뷰어 - Disabled


  현재 위의 그림에서 붉게 표시한 바와 같이, Driverframeworks-UserMode는 모니터링 설정이 되어 있지 않은 것을 확인할 수가 없다. 만약 모니터링 되고 있지 않은 항목에 대하여 모니터링이 필요하다고 생각되면 우측을 클릭하면 된다.


그림 6. 이벤트 뷰어 – 설정


우클릭을 하여 나타나는 목록 중에서 Properties에 들어가서 설정을 할 수가 있으며, Properties 밑에 있는 Enable Log를 통해서도 모니터링 하도록 설정할 수가 있다. 이렇게 설정을 바꾼 뒤, 해당 이벤트가 발생하게 되면 로그가 남는 것을 확인할 수가 있다.


그림 7. 이벤트 뷰어 - Enable

 

  

3. 이벤트 로그 감사 정책


  감사정책이란 개체 액세스, 로그온/로그오프, 감사 정책 설정 변경 등의 보안 관련 로그를 기록하며, 지정한 이벤트 범주의 사용자나 시스템 동작을 기록하도록 정책 설정이 가능하다. 감사 정책의 자세한 설명 및 로그 관리를 위한 권장 값은 다음과 같다.


표 5. 감사 정책 권장 값


3.1 로컬 보안 정책 설정

  이러한 감사 정책을 확인하기 위해 로컬 보안 정책을 확인해보자. 제어판에서 관리도구 중 로컬 보안 정책을 선택하거나 Local Security Policy를 찾아서 실행한 다음 Local Policies의 하위에 있는 Audit Policy를 확인하면 된다. 아래의 그림을 보자.


그림 8. Local Security Policy


  위와 같이 설정되어 있는 것을 확인할 수가 있다. 현재 감사하지 않음으로 되어 있는 것을 확인할 수가 있다. 이를 감사로 변경하면 감사가 시작된다. 이러한 감사정책을 구성하기 전에 유의해야 할 사항들에 대하여 알아보자.

  첫 째, 지나친 감사 범위 설정은 중요 로그 검색에 어려움을 동반한다. 불필요한 로그까지 저장되기 때문에, 검색의 어려움뿐만 아니라 시스템의 성능 저하 및 로그 관리의 어려움을 발생 시킬 수 있으므로 시스템 감사 구성에 필요한 항목을 사전에 선정하여야 한다.

  둘 째, 잘못된 이벤트 로그 크기 설정은 용량 한계로 시스템이 중지될 수가 있다. 시스템 장애가 발생한다는 것은 서비스를 위해 서버를 운영하는 기업에서는 많은 손실을 야기할 수 있다. Windows는 이러한 문제를 방지하기 위해 기본 설정으로 오래된 항목을 덮어씌우는 옵션이 설정되어 있다.


3.2 보안 템플릿을 통한 감사 정책 구성

  보안 템플릿이란 보안 구성을 표시하는 파일로 로컬 컴퓨터 및 그룹 정책 개체를 적용하거나 보안 분석에 사용된다. 이러한 보안 템플릿을 이용하면 손쉽게 감사 정책 구성을 공유할 수가 있고, 반대로 공유된 감사 정책을 구성할 수가 있다.


그림 9. 보안 템플릿 구성


  템플릿을 구성하는 방법은 위의 그림과 같다. 좌측의 키에서 Security Settings에 놓고 상단 탭의 Action을 선택하면 위와 같이 나타나는 것을 확인할 수가 있다. Import Policy를 선택하면 다른 템플릿을 통해 감사 정책을 구성할 수가 있다.

  반대로 현재의 감사 정책 구성을 다른 PC에 공유하거나 백업해놓고자 할 경우 Export Policy를 선택하여야 한다. 선택 후 저장 경로를 설정해주면 *.inf와 같은 형식으로 저장이 되는 것을 확인할 수가 있을 것이다.



4. 보안 감사 이벤트 설명


그렇다면 어떤 이벤트를 보아야 할까? 우선 이벤트 로그는 기존의 Evt에서 비스타 이후 부터 Evtx로 사용되고 있다. 실제 로그 파일의 헤더 및 구성의 변경이 있지만 대부분의 이벤트가 Evt ID 값에 4096 값을 더해주면Evtx 형태의 이벤트 ID 값이 된다. 각 감사에 따른 주요한 이벤트들의 목록의 아래의 표들과 같다. [표 출처 - http://elven.kr/archives/361 ]


개체 액세스 검사
파일, 폴더, 레지스트리 등 개체에 액세스 하는 사용자의 이벤트를 감시할지 여부를 결정합니다.
개체에 대한 접근 성공 여부를 감사하며, 개체 액세스 감사를 실행하면 보안 로그에 관련 로그가 기록됩니다.

 Type 1 – 이벤트 ID Type 2 – 이벤트 ID내용
 5604656개체에 대한 접근 허가
 5624658개체에 대한 핸들 닫힘 – 이벤트 종료
 5634659삭제할 목적으로 개체에 접근
 5644660보호된 개체의 삭제

계정 관리 감사
컴퓨터의 각 계정 관리 이벤트를 감사할지 여부를 결정합니다.
해당 이벤트의 예로는 사용자 계정 및 그룹 생성/수정/삭제, 암호 설정 및 변경 등이 해당됩니다.

 Type 1 – 이벤트 ID Type 2 – 이벤트 ID내용
6244720사용자 계정 생성
625 사용자 계정 유형 변경
6264722사용할 수 있는 사용자 계정
6274723암호 변경 시도
6284724사용자 계정 암호 설정
6294725사용하지 않는 사용자 계정
6304726삭제된 사용자 계정
6364732보안 사용 로컬 그룹 구성원 추가
6374733보안 사용 로컬 그룹 구성원 제거
6424738변경된 사용자 계정
643 변경된 도메인 정책
6444740사용자 계정 잠김

계정 로그온 이벤트 감사
계정 유효성을 검사하는 컴퓨터가 아닌 다른 컴퓨터에서의 로그온/로그오프 감사 여부를 결정합니다.
해당 감사 항목은 침입 감지에 유용하게 사용할 수 있습니다.

 Type 1 – 이벤트 ID Type 2 – 이벤트 ID내용
6804776로그인 성공 정보
6814777로그인 실패 정보

로그인 이벤트 감사
감사 이벤트를 기록하는 컴퓨터에 대해 사용자 로그온/로그오프 또는 네트워크 연결의 각 인스턴스 감사 여부를 결정합니다.
성공 감사는 로그온 시도가 성공할 때 생성되며, 이 항목은 침입 감지에 유용하게 사용할 수 있습니다.
해당 감사는 계정 로그온 이벤트 감사 보다 상제 정보를 로깅합니다.

 Type 1 – 이벤트 ID Type 2 – 이벤트 ID내용
5284624성공적인 로그인
5294625알 수 없는 계정이나 잘못된 암호를 이용한 로그인 시도
530 로그인 시 허용 시간 이내에 로그인 실패
531 사용지 금지된 계정을 통한 로그인 시도
532 사용 기간이 만료된 계정을 통한 로그인 시도
533 로그인이 허용되지 않은 계정을 통한 로그인 시도
534 허용되지 않은 로그인 유형을 통한 로그인 시도
535 암호 사용 기간 만료
537 위의 사항에 해당되지 않으나 로그인에 실패한 경우
5384634로그오프
539 로그인하려는 계정이 잠겨 있음
540 로그인 성공

프로세스 추적 감사
프로세스 활성화/종료 및 간접적 개체 액세스 등의 이벤트에 대한 자세한 추적 정보를 감사할지 여부를 결정합니다.
윈도우 XP SP2 및 서버 2003 SP1 이상에서 프로세스 추적 감사를 사용하면 윈도우 방화벽 구성 요소의 작동 모드 및 상태에 대한 정보도 기록합니다.

 Type 1 – 이벤트 ID Type 2 – 이벤트 ID내용
5924688새 프로세스 생성
5934689프로세스 종료
5944690개체에 대한 힌트의 중복
5954691개체에 대한 간접적인 접근

시스템 이벤트 감사
컴퓨터를 다시 시작하거나 종료할 경우 또는 컴퓨터 보안이나 보안 로그에 영향을 주는 이벤트가 발생할 경우 이를 감사할지 여부를 결정합니다. 이러한 이벤트는 매우 중요하기 때문에 조직의 모든 컴퓨터에 대하여 이 정책 설정을 [사용] 설정할 필요가 있습니다.

 Type 1 – 이벤트 ID Type 2 – 이벤트 ID내용
5124608윈도우 시동
5134609윈도우 종료
5144610LSA (Local Security Authority) 인증 패키지 로드
5154611신뢰할 수 있는 로그인 프로세스가 LSA로 등록
5164612저장 공간의 부족으로 인해 보안 이벤트 메세지 손실
517 보안 로그 삭제

로그온 유형에 따라 아래와 같이 분류할 수 있습니다.
이 중에서 “로그온 유형 10” 의 경우 원격 접속 이벤트로 사고 분석 시 가장 빈번히 활용됩니다.

로그온 유형 2대화식콘솔에서 키보드로 로그인 (KVM 포함)
로그온 유형 3네트워크네트워크를 통한 원격 로그인 (파일 공유, IIS 접속 등)
로그온 유형 4스케쥴스케쥴에 등록된 배치 작업 실행 시 미리 설정된 계정 정보로 로그인
로그온 유형 5서비스서비스가 실행될 때 미리 설정된 계정 정보로 로그인
로그온 유형 7잠금해제화면보호기 잠금 해제시
로그온 유형 8네트워크유형 3과 비슷하나 계정 정보를 평문으로 전송할 때 발생
로그온 유형 9새자격실행(RunAS)에서 프로그램 실행 시 /netonly 옵션을 줄 때
로그온 유형 10원격 대화식터미널 서비스, 원격 접속, 원격지원으로 로그인
로그온 유형 11캐시된 캐화식PC에 캐시로 저장된 암호로 자동 입력 로그인

  위의 표는 보안 감사 정책에 따라 구분된 것으로 실제 이벤트 뷰어를 통해서 확인할 때는 해당 항목을 찾는데 번거로움이 있을 수 있다. 따라서 아래의 그림과 같이 주요한 몇 개의 이벤트만 따로 정리할 수가 있다.



출처


* 블로그, 윈도우 이벤트 로그 분석 #기초

* 안랩, 전문가 칼럼, 윈도우 로그 관리 및 분석 방법

* MSDN, 보안 템플릿 정의


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

Unicode 확장자 변조(RLO)  (3) 2016.05.15
Windows Event Log (2) – 주요 이벤트 로그  (0) 2016.02.01
Volume Shadow Copy 분석  (1) 2016.01.18
NTFS FIle System (9) $UsnJrnl  (0) 2016.01.16
NTFS File System (8) $LogFile  (0) 2016.01.13