본문 바로가기
WebBook/윈도우 구조

MBR에서 로그온까지

by 올엠 2022. 2. 9.
반응형

윈도우 구조를 이해하는 것은 연구자가 되는 것과 같다. 농작물을 연구하는 사람들은 그 농작물의 특성과 속성을 알기 위해 세포 하나 하나를 분석한다. 그리고 이를 통해 작물이 잘 죽지 않도록, 혹은 더 많은 열매가 맺을 수 있도록 작물을 개선하게 된다. 두번째는 바로 탐험가와 같다고 할 수 있다. 미지를 탐험하는 사람들은 굉장한 보물, 혹은 남들이 알지 못했던 진실을 발견할 수 있다.

하지만 이와 같은 일을 아무런 준비 없이 시작할 수는 없다. 사전에 여러 장비들과 훈련을 거쳐 실제 탐험과 연구를 진행해야 한다. 이 책은 바로 운영체제를 탐험하여 남들이 알지 못했던 진실을 알 수 있도록 본인의 기술을 높이기 위해 필요한 연장이 되어 주고, 운영체제를 장애로부터 더욱 강하고 더 높은 성능을 끌어낼 수 있는 훈련을 도와주려고 한다. 이렇게 시작하다보면 어느새 윈도우의 장애 혹은 침해 사고의 에러 코드 1의 의미와 값이 0xF인 이유를 알아 낼 수 있게 되는 것이다. 그리고 문제점의 원천적 해결 방법과 성능적으로 개선할 수 있는 부분들도 찾아낼 수 있게 된다. 이러한 응용이 가능한 이유는 바로 윈도우의 원리 이해가 원인과 경로를 알려주는 내비게이션과 같은 역할을 해주기 때문이다.

그렇다면 우리는 무엇부터 진행을 해야 할까? 윈도우 구조를 이해하기 위해서는 먼저 부팅 과정부터 이해해야 한다. 그래서 1부에서는 부팅의 시작이라 할 수 있는 MBR부터 우리가 사용할 수 있는 바탕화면이 나오기까지의 과정을 알아 보고자 한다.

 

 

MBR에서 로그온까지

위 그림은 윈도우 부팅 과정중 중요 부분을 순차적으로 표현한것으로 각 단계들을 자세히 살펴 볼것이다. 부팅 단계에 유저 모드(사용자들이 응용프로그램을 실행하는 작업 공간)와 밀접한 윈도우 핵심 부분들이 많이 들어가 있다. 이를 통해, 시스템 프로세스들을 이해하고 상호 연관성을 이해하는 데 도움이 될 것이다.

그리고 중간 중간 이해하는 데 필요하다 싶은 내용들을 추가로 삽입하여, 내용 이해를 더욱 쉽게 할 수 있도록 하였다. 그 외 시스템 분석에 필요한 도구 Windbg, Livekd, 성능 분석 도구에 대해 그림과 사용 방법도 첨가하여, 차후 해당 도구에 접근을 쉽게 할 수 있도록 하였다.

 

그러면 왜 윈도우는 커널 모드, 유저 모드로 구분하여 구성하였는지 먼저 생각해 보자.

 

운영체제는 사용자가 서로 다른 제조사의 하드웨어이지만 동일한 동작을 할 수 있도록 공통된 환경을 제공해 주는 것이 큰 목적이라 할 수 있다. 조금 더 자세히 보면 사용자들이 신경쓰지 않아도 되도록 하드웨어 자원에 대한 분배와 관리를 커널 모드로 분리하여 사용자가 실행하거나 요구하는 자원들을 알맞게 분배해 주고, 다양한 개발 환경에서 개발된 응용프로그램들을 유저 모드와커널 모드가 연결되는 통일된 인터페이스(NTDLL.DLL과 같은 Subsystem)를 제공하여, 커널 모드에서 응용프로그램의 요청을 정확히 이해할 수 있도록 만든 것이라 이해하면 된다.

즉 운영체제의 목적인 확장성이식성, 안정성 그리고 호환성을 만족하면서 성능적으로 부족하지 않는, 이 조건을 충족하는 구조를 설계한 것이 바로 커널과 유저 모드이다(이는 유닉스 시스템 역시 같은 구조이다).

그럼 윈도우는 어떻게 이 조건이 만족하는지에 대해 본격적으로 알아 보도록 하자.

 

운영체제 기초 - 유저 모드와 커널 모드

윈도우 구조를 얘기하면, 제일 먼저 떠오르는 건 앞에서 말했듯 유저 모드와 커널 모드이다. 이를 간단히 구조화하면 [그림 1-1]과 같다.

아래 그림은 앞서 얘기했던 운영체제의 목적이라 할 수 있는 사용자 지원에 대한 편리성을 고려한 유저 모드와 커널 모드의 각 영역 구분이라 할 수 있다. 유저모드와 커널모드의 특성을 요약하면 다음과 같다.

윈도우 아키텍처

 

이를 방지하기 위해 [그림 1-1]과 같이 커널과 유저 모드는 권한과 함께 구분된다. 이는 CPU, 프로세서 처리시 함께 사용되는데, 인텔 프로세서는 4개의 특권 레벨(Privilege Level)을 사용할 수 있다. 이를 Ring이라는 용어로로 구분하고, 0~3까지 사용한다. 이중 Ring 0는 최고 권한으로 커널모드라 하며, 시스템 자원을 통제 없이 마음대로 사용할 수 있다. 유저 모드에는 Ring 3 권한을 주어, 하드웨어를 바로 제어할 수 없도록 설계한 것이다. 이러한 권한 분리를 통해, 여러 응용프로그램들이 동작하는 환경에서 안정성을 확보할 수 있다. 그리고 위 [그림 1-1]과 같이 유저모드에서 크게 4가지 서브시스템을 제공하며, 이는 시스템 서비스, 서비스, 어플리케이션, 환경 서브시스템등으로 개발된 응용프로그램이 커널도 소통하는 것을 지원하므로써, 공통된 인터페이스을 이용하여 응용프로그램들은 자신이 원하는 요청을 운영체제에게 처리하도록 한다. 이를 통해 운영체제에서 필요로하는 확장성 호환성도 확보할 수 있다. 그리고 이러한 모드의 구분은 실제적으로 사용되는 메모리 영역에서 나누어 통제하게 된다. 아래 그림은 32비트(Bit) 머신의 커널과 유저모드에서 사용하는 메모리 공간이다.

기본 메모리 할당 영역

 

[그림 1-2]에서 각 영역의 구분은 앞서 권한의 구분과 같이, 안정성을 위해 커널에서 관리되는 자원들을 보호하는 목적을 함께 가지고 있다. 기존 운영체제가 도스인 시절 메모리 관리는 각 프로그램들이 직접 관리하여 진행하였다. 그 시절은 한 번에 하나의 프로그램만 실행이 가능한 구조이다. 만약 윈도우와 같은 멀티 프로세스 처리 환경에서 각 응용프로그램이 메모리를 직접 관리한다면, 서로 같은 메모리를 사용하거나, 메모리 침범 오류로 인해 운영체제는 파란 화면(블루스크린, 1부 마지막에 다룬다)이 수시로 나타나게 될 것이고, 이는 안정성과 성능적으로 많은 문제가 나타날 것이다. 이를 해결하기 위해 운영체제에서 모든 메모리 영역을 관리하며, 응용프로그램은 단지 자신에게 필요한 메모리를 요청하면, 운영체제가 그 요청에 대한 처리를 해주는 것이다. 또 안정성 확보를 위하여, 커널 모드에서만 사용 가능한 메모리를 구분하여, 시스템 처리를 위해 할당한 메모리 영역의 시스템 자원에 대해 보호를 하게 된다. 유저 모드 영역과 커널 모드 메모리 영역의 메모리 주소는 위 그림과 같이 유저모드에서 0x00000000~0x7FFFFFFF(마지막에 h를 붙이기도 하며, HEX의 약자로 16진수임을 나타냄)까지 사용하고, 이후 0x80000000~0xFFFFFFFF까지 커널 영역이 사용하는데, 커널은 시스템 부팅시 시스템에서 필요로 하는 공간을 미리 영역별로 계산하여 공간을 할당하게 된다. 이 특성으로 몇몇 커널의 특정 시스템 영역은 매번 같은 위치를 할당 받기도 한다. 차후 역분석을 많이 하다 보면, 메모리 번지수만으로도 많은 예측이 가능하게 될 것이다. 다만 이는 32비트 시스템에 일반적인 것이며, 64비트의 경우 전혀 다른 레이아웃을 가지게 된다. 그리고 32비트 메모리 시스템에서 4GB의 메모리 제한 때문에 몇가지 메모리 관련 기술을 추가로 제공한다(2장 커널 생성에서 Boot.ini를 참고).

그럼 이제부터 본격적으로 부팅 과정을 차근차근 알아보도록 하자.

시작전 윈도우 부팅 순서를 요약하여 정리해 보았다.

no 프로세스 모드 타입 설명
1 MBR 16비트 리얼 모드 Boot Sector를 로드
2 Boot Sector 16비트 리얼 모드 Root directory를 읽어 Ntldr 로드
3 NTLDR 32/64비트 보호 모드 Boot.ini를 읽어 운영체제를 선택할수 있게 하며, Ntoskrnl.exe Hal.dll을 로드
4 Winload.exe 커널 모드 커널 초기화에 필요한 파일을 부팅 볼륨에서 로드
5 Ntoskrnl.exe 커널 모드 Hal.dll Boot & sys, Dev dirver 초기화 후 Smss.exe 실행
6 Hal.dll 커널 모드 NtoskrnlDriverH/w을 연결하는 dll
7 Smss.exe 커널 모드 프로그램 실행 기본 값 : Autochk
페이지 파일 초기화
서브시스템 로드
8 Csrss.exe 커널 모드 세션 초기화, Winlogon 실행
9 Wininit.exe 커널 모드 세션0 초기화, Lsass.exe, Lsm.exe 실행(Windows Vista 이후)
10 winlogon.exe 유저 모드  
11 Lsass.exe 유저 모드  
12 Services.exe 유저 모드  
13 Userinit.exe 유저 모드  

윈도우 부팅 순서

위 표는 시스템에 전원이 들어와 윈도우 운영체제로 부팅을 진행하여, 사용자가 로그인 할 수 있는 GINA(Graphical Identification and Authentication) 화면을 표시하기까지의 과정을 순차적으로 표시한 것이다. 그리고 어려운 용어가 많이 나오는데, 이해가 되지 않는 용어들이 많다고 지금부터 걱정하지 말기 바란다. 이는 1부를 다 읽었을 때 표를 다시보면 이해가 더욱 쉬울 것이다.

 

부팅의 시작: MBR (실습)

MBR(Master Boot Record)은 운영체제가 부팅할 때 POST(Power on Self-Test, 자기 진단) 과정을 마친 후 저장매체의 첫 번째 섹터를 호출을 시도하는데, 이 때 MBR이 존재한다면, MBR의 부트 코드가 수행되게 된다.

MBR이 존재하지 않으면, 부팅 에러가 발생한다

부트 코드는 파티션 테이블에서 부팅 가능한 파티션을 찾아, 해당 파티션의 부트 섹터를 호출해 주는 역할을 한다. 윈도우 구조의 연구 중 MBR 영역이 필요한 이유는 보안적 측면에서 악성코드가 쉽게 침투할 수 있으며, 운영적 측면에서 해당 영역의 장애로 시스템이 부팅이 되지 않는 경우가 발생할 수 있기 때문이다. 따라서 이 MBR의 영역를 이해한다면 역분석은 물론, 차후 부팅 관련 문제점 분석 및 해결에 큰 도움이 될 것이다. 
자, 그럼 계속해서 부팅 단계를 설명해 보겠다. 아래 그림와 같은 MBR을 통해 부팅 가능 파티션에서 운영체제의 부트 섹터를 실행하여 커널을 실행을 통해 윈도우가 실행되게 된다.
이 흐름을 간단히 그림으로 표현하면 아래와 같다.

MBR과 운영체제의 연결 관계

그럼 이제 실습을 위해 필요한 준비물들에 대해 확인해 보자.

 

실습 준비

여기서는 이후에 만드는 가상 머신을 이용할 것이다(앞서 얘기하였지만 대부분의 분석 및 테스트는 가상 머신에서 이루어진다). 따라서 IDA 어셈블리 분석 도구와 Bochs라는 가상 머신을 가지고 분석을 진행하도록 하겠다(커널 디버깅시에는 다른 도구를 이용할 것이다).

Windows 운영체제

Windows 2003, Windows 7 등 전 운영체제 가능

Bochs

http://bochs.sourceforge.net

실시간으로 MBR 영역 분석을 IDA를 통해 진행할 수 있는 가상 머신

IDA

http://www.datarescue.com.

데모 버전: http://www.brothersoft.com/ida-pro-download-69919-s1.html

어셈블리 분석 도구로 유용하다.

HxD

http://mh-nexus.de/en/hxd

헥사(Hex) 코드 분석 도구로 자주 사용된다.

DiskProbe

http://www.microsoft.com/download/en/details.aspx?id=18546

HxD와 함께 사용되는데, 장애 복구 용도로 개발되었다.

 

IDA 디어셈블리 분석 도구로 유명한 도구이다. 해당 도구에 대해서만 다룬 전문 서적도 있을 정도로 많은 전문가들이 애용한다. 많은 편리한 기능들 중에 IDA에서 지원하는 강력한 플러그인은 빼놓을 수 없는데, 어셈블리 코드를 C 언어로 디컴파일해주는 헥슬레이(Hexray) 플러그인은 IDA의 여러 기능 중 막강한 역분석 도구임을 증명해 주기 충분하다(, 유료 버전이다). 기본 기능만 제공하는 무료 버전으로는 IDA Pro 5.0 Freeware을 제공하고 있지만 제한 사항이 많아, 되도록 유료 버전을 구해 사용하기 바란다(안타깝게도 IDA를 이용한 MBR 실시간 디버깅은 유료 버전에서 가능하고, 유료 버전을 구하기 어려운 독자는 6.0 데모 버전을 이용해 사용하기 바란다. 데모 버전도 MBR 실시간 디버깅이 가능하다).

IDA를 이용하여 파일을 분석할 수 있다.

HxD는 무료 헥사(Hex) 편집도구로서, 가벼우면서 설치가 필요 없는 도구임과 동시에 메모리, 디스크 영역도 선택하여 분석할 수 있는 유용한 헥사 편집 도구이다.

HxD에서 MBR을 로드한 화면

DiskProbe는 HxD와 비슷한 도구이지만, 본래 목적은 디스크 복구를 위해 제공하는 도구로 사용된다. 
Bochs는 MBR 영역 분석을 위해 사용하는 또 다른 가상 머신 도구로서, IDA와 궁합이 잘 맞는다. 단 성능이 다른 가상 머신에 비해 느려서, 많이 이용되지는 않는다. 하지만 Bochs는 IDA에서 기본적으로 연결 가능한 디버거인 만큼 역분석 용도로는 활용가치가 높으므로 역분석을 공부하는 입장에서는 알아두어야 한다.

그럼 MBR 실시간 분석에 필요한 Bochs 설치는 잠시 후로 미루고, MBR 영역의 구조부터 살펴보자. MBR은 각 물리 디스크의 섹터 0에 저장되며, 각 영역은 아래와 같이 구분된다.

MBR 구조

위 그림과 같이 MBR 영역은 부트 코드(Boot code)와 파티션 테이블(Partition Table), 그리고 시그니처(Signature)로 구성되어 있다.

먼저 부트 코드는 파티션 테이블에서 부팅 가능한 파티션을 찾아 해당 파티션의 부트 섹터를 호출해 주는 역할을 하게 된다. 부팅이 불가능할 경우 부트 코드에 저장된 에러 메시지를 출력하게 된다.

그리고 파티션 테이블에는 파티션 관련 정보 정보들이 16바이트(Byte) 4개로 구성된다(디스크 관리자에서 기본 볼륨을 구성시, 최대 4개의 볼륨까지만 구성이 가능한 이유이기도 하다).

그럼 HxD 도구를 이용해, 현재 컴퓨터에 설정된 MBR 영역을 직접 확인해 보자(Bochs 가상 머신은 MBR 영역 실시간 디버깅에서 이용할 예정이다.).

현재 컴퓨터에서 HxD 도구를 실행한 이후 기타 설정 -> 디스크 열기를 선택하거나, 단축 아이콘바의 디스크 아이콘을 선택한 후, 하드디스크 1을 선택하자.

 

MBR은 물리디스크 섹터 0에 저장된다

하드디스크를 열면 아래 그림과 같이 섹터 0x000000000에 위치한 값을 확인할 수 있으며, 이 섹터의 값이 바로 MBR 코드이다.

섹터 0에 위치한 MBR 영역

위 0x00000012C부터 0x00000017A까지는 MBR 관련 에러 메시지가 저장되는 부분이며 그 위 부분들이 부트 코드(16비트 어셈블리)이다.
그리고 아래 0x0000001BE부터 0x0000001CD까지 첫 번째 파티션 영역에 대한 내용으로, 이 부분을 조금더 자세히 확인해 보자.
0x0000001BE: 부팅 지시자로 부팅 가능 여부를 확인할 수 있다. 00인 경우 부팅 불가, 0x80인 경우 부팅이 가능한 파티션이다.
0x0000001BF~0x0000001C1: CHS 시작 주소(Cylinder, Head, Sector 약자로 물리적 디스크 접근 방식중 하나이지만, 528MB라는 용량한계로 현재는 거의 사용되지 않는다)
0x0000001C2: 파티션 타입으로, 0x07이면 NTFS, 0x05이면 DOS형을 의미한다.
0x0000001C3~0x0000001C5: CHS 마지막 주소
0x0000001C6~0x0000001C9: LBA 시작 주소(Logical Block Area의 약자로 CHS의 용량한계를 극복하기 위해 사용)
0x0000001CA~0x0000001CF: 전체 디스크 섹터수
이렇게 제공된 MBR 정보를 토대로 부팅 가능한 파티션 정보를 확인한 후 해당 볼륨의 첫 섹터로 넘어가게 된다.

 

 

MBR 영역 코드 분석

그럼 이제 MBR 영역을 바이너리 파일로 추출하여, 이를 IDA에 연결한 후 코드 분석을 진행해 보자.
MBR 영역 및 부트 섹터 영역의 어셈블리 코드를 확인하기 위해서는 먼저 DiskProbe 프로그램을 이용하여, MBR 영역을 선택한 후 디스크에 저장한다(HxD 도구를 사용하여도 무방하다).
DiskProbe는 윈도우 CD에서 Support\Tools\Support.cab에서 압축을 해제하여 사용하거나, http://www.microsoft.com/download/en/details.aspx?id=18546에서 다운로드 할 수 있다(설치시 Complete, 즉 전체 설치로 진행하여야 한다).
현재 컴퓨터에서 DiskProbe를 실행한 후, Drive -> Open Physical Drive 메뉴를 선택하고, MBR 영역이 존재하는 디스크를 마우스 더블 클릭으로 선택한 이후 Set Acitve 메뉴를 눌려 디스크를 활성화한다.

DiskProbe를 이용하여, MBR 영역이 존재하는 디스크를 활성화

이후 Sector -> Read 메뉴를 통해서 첫 번째 섹터를 읽어들인 후 해당 섹터를 디스크에 저장하도록 하자.

DiskProbe에서 첫번째 섹터를 읽어들인 화면

이 방법 이외에도 이후 3부 역분석에서 진행할 예정인 디스크 데이터 수집도구인 Dd.exe를 이용해서도 쉽게 MBR 영역을 가져올 수 있다

이렇게 추출한 바이너리 파일을 IDA와 연결해 분석을 진행하면 된다.

IDA에서 16비트 코드로 MBR.bin을 로드

연결하면 [그림 1-12]와 같이 알수 없는 데이터 구조로 나오는데, 이를 코드 타입 시작 위치에서 C 키를 누르거나 메뉴 Edit에서 Code를 선택해주면 코드 형식으로 화면을 재구성하여, 정상적으로 어셈블리 할 수 있다.

그럼 이제 각 코드 내용을 분석해보자.

아래 [그림 1-13]를 요약하자면, 메모리 0x7C00 위치에 로드된 후 0x7C1B부터 485바이트를 0x061B 위치로 복사하는 과정이다(MBR의 메모리 로드 위치는 0x7C00으로 일정하다).

 

seg000:0000 seg000          segment byte public 'CODE' use16
seg000:0000                 assume cs:seg000
seg000:0000                 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:0000                 xor     ax, ax
seg000:0002                 mov     ss, ax
seg000:0004                 mov     sp, 7C00h
seg000:0007                 sti
seg000:0008                 push    ax
seg000:0009                 pop     es
seg000:000A                 push    ax
seg000:000B                 pop     ds
seg000:000C                 cld
seg000:000D                 mov     si, 7C1Bh
seg000:0010                 mov     di, 61Bh              ;0x7C1B부터 0x061B로 복사
seg000:0013                 push    ax
seg000:0014                 push    di
seg000:0015                 mov     cx, 1E5h              ;485바이트가 될 때까지 반복
seg000:0018                 rep movsb
seg000:001A                 retf

MBR 부트 코드를 메모리에 로드

 

즉 위 내용은 부팅 작업을 진행하기 위해 메모리에 부팅 코드를 로드하는 코드이다. 그리고 이후 현재 디스크에서 부팅 가능한 파티션 테이블 엔트리를 찾는 과정이 진행되는데, 파티션 테이블의 첫번째 값이 0x80을 가지면 부팅이 가능한 파티션으로 판단하게 된다.

seg000:001B                 mov     bp, 7BEh
seg000:001E                 mov     cl, 4
seg000:0020
seg000:0020 loc_20:                                 ; 
seg000:0020                 cmp     [bp+0], ch
seg000:0023                 jl      short loc_2E    ;부팅 가능시 loc_2E로 이동
seg000:0025                 jnz     short loc_3A   ;부팅이 불가능한 경우 loc_3A로 이동한다.
seg000:0027                 add     bp, 10h
seg000:002A                 loop    loc_20
seg000:002C                 int     18h             ; TRANSFER TO ROM BASIC
seg000:002C                                         ; causes transfer to ROM-based BASIC (IBM-PC)
seg000:002C                                         ; often reboots a compatible; often has no effect at all

파티션 테이블 엔트리가 있는지 확인

 

이후 부팅이 불가능할 경우 각 상황에 맞는 메시지를 위한 조건과 부팅이 가능한 경우 진행하는 코드들이 아래와 같이 존재한다. loc_4F를 통해 부팅 가능한 경우 sub_9B을 호출하게 된다.

 

seg000:0030 loc_30:                                 ; 
seg000:0030                 add     si, 10h
seg000:0033                 dec     cx
seg000:0034                 jz      short loc_4F   ; 부팅 가능한 엔트리가 1개인 경우 loc_4F로 이동
seg000:0036                 cmp     [si], ch
seg000:0038                 jz      short loc_30
seg000:003A
seg000:003A loc_3A:                                 ; 부팅이 불가능한 경우 실행되는 코드
seg000:003A                 mov     al, ds:7B5h     ; ds:7B5h에는 “Invalid partition table”값이 위치한다.
seg000:003D
seg000:003D loc_3D:                                 ; 오류에 맞는 메시지를 찾는 코드
seg000:003D                                         ; 
seg000:003D                 mov     ah, 7
seg000:003F                 mov     si, ax          ; 
seg000:0041
seg000:0041 loc_41:                                 ; 
seg000:0041                 lodsb
seg000:0042
seg000:0042 loc_42:                                 ; 
seg000:0042                 cmp     al, 0
seg000:0044                 jz      short loc_42
seg000:0046                 mov     bx, 7
seg000:0049                 mov     ah, 0Eh
seg000:004B                 int     10h             ; DATA XREF: sub_9B+8
seg000:004B                                         ; 
seg000:004B                                         ; - VIDEO - WRITE CHARACTER AND ADVANCE CURSOR (TTY WRITE)
seg000:004B                                         ; AL = character, BH = Display page (alpha modes)
seg000:004B                                         ; BL = foreground color (graphics modes)
seg000:004D                 jmp     short loc_41
seg000:004F
seg000:004F loc_4F:                                 ;
seg000:004F                 mov     [bp+10h], cl
seg000:0052                 call    sub_9B         ; 해당 파티션의 시작 위치를 찾는 코드를 실행한다.
seg000:0055                 jnb     short loc_81   ; cf가 0인지 확인
seg000:0057
seg000:0057 loc_57:                                 ; 
seg000:0057                 inc     byte ptr [bp+10h]
seg000:005A                 cmp     byte ptr [bp+4], 0Bh
seg000:005E                 jz      short loc_6B
seg000:0060
seg000:0060 loc_60:                                 ; 
seg000:0060                 cmp     byte ptr [bp+4], 0Ch
seg000:0064                 jz      short loc_6B
seg000:0066                 mov     al, ds:7B6h
seg000:0069                 jnz     short loc_3D   ;
seg000:006B
seg000:006B loc_6B:                                 ; 
seg000:006B                                         ; 
seg000:006B                 add     byte ptr [bp+2], 6
seg000:006F                 add     word ptr [bp+8], 6
seg000:0073                 adc     word ptr [bp+0Ah], 0
seg000:0077                 call    sub_9B
seg000:007A                 jnb     short loc_81   ; cf가 0인지 확인
seg000:007C                 mov     al, ds:7B6h
seg000:007F                 jmp     short loc_3D

부팅 가능 파티션이 있는지 확인

 

sub_9B은 부팅에 의해 실행되는 코드로 부팅 가능한 파티션 테이블 엔트리를 읽은 후, 해당 파티션의 시작 위치를 찾는다. 그리고 각 파티션 타입을 확인하여 CHS 주소를 사용할 경우 loc_CA를 실행하고, LBA 주소를 사용할 경우 loc_E6를 실행하게 된다.
이를 통해 부팅 가능한 파티션의 첫 섹터(512 Bytes)를 읽어서 메모리 0x7C00에 덮어쓰게 된다.

seg000:009B sub_9B          proc near               ; 
seg000:009B                                         ; 
seg000:009B                 mov     di, 5
seg000:009E                 mov     dl, [bp+0]
seg000:00A1                 mov     ah, 8
seg000:00A3                 int     13h             ; DISK - DISK - GET CURRENT DRIVE PARAMETERS (XT,AT,XT286,CONV,PS)
seg000:00A3                                         ; DL = drive number
seg000:00A3                                         ; Return: CF set on error, AH = status code, BL = drive type
seg000:00A3                                         ; DL = number of consecutive drives
seg000:00A3                                         ; DH = maximum value for head number, ES:DI -> drive parameter
seg000:00A5                 jb      short loc_CA
seg000:00A7                 mov     al, cl
seg000:00A9                 and     al, 3Fh
seg000:00AB                 cbw
seg000:00AC                 mov     bl, dh
seg000:00AE                 mov     bh, ah
seg000:00B0                 inc     bx
seg000:00B1                 mul     bx
seg000:00B3                 mov     dx, cx
seg000:00B5                 xchg    dl, dh
seg000:00B7                 mov     cl, 6
seg000:00B9                 shr     dh, cl
seg000:00BB                 inc     dx
seg000:00BC                 mul     dx
seg000:00BE                 cmp     [bp+0Ah], dx
seg000:00C1                 ja      short loc_E6
seg000:00C3                 jb      short loc_CA
seg000:00C5                 cmp     [bp+8], ax
seg000:00C8                 jnb     short loc_E6
seg000:00CA
seg000:00CA loc_CA:                                 ; CHS 주소를 사용할 경우 주소 값을 읽음
seg000:00CA                                         ; 
seg000:00CA                 mov     ax, 201h
seg000:00CD                 mov     bx, 7C00h
seg000:00D0                 mov     cx, [bp+2]
seg000:00D3                 mov     dx, [bp+0]
seg000:00D6                 int     13h             ; DISK - READ SECTORS INTO MEMORY
seg000:00D6                                         ; AL = number of sectors to read, CH = track, CL = sector
seg000:00D6                                         ; DH = head, DL = drive, ES:BX -> buffer to fill
seg000:00D6                                         ; Return: CF set on error, AH = status, AL = number of sectors read
seg000:00D8                 jnb     short locret_12B
seg000:00DA                 dec     di
seg000:00DB                 jz      short locret_12B
seg000:00DD                 xor     ah, ah
seg000:00DF                 mov     dl, [bp+0]
seg000:00E2                 int     13h             ; DISK - RESET DISK SYSTEM
seg000:00E2                                         ; DL = drive (if bit 7 is set both hard disks and floppy disks reset)
seg000:00E4                 jmp     short loc_CA
seg000:00E6
seg000:00E6 loc_E6:                                 ; 확장된 INT 13 명령을 지원여부를 검사하는 코드
seg000:00E6                                         ; sub_9B+2D j
seg000:00E6                 mov     dl, [bp+0]
seg000:00E9                 pusha
seg000:00EA                 mov     bx, 55AAh
seg000:00ED                 mov     ah, 41h ; 'A'
seg000:00EF                 int     13h             ; DISK -
seg000:00F1                 jb      short loc_129
seg000:00F3                 cmp     bx, 0AA55h
seg000:00F7                 jnz     short loc_129
seg000:00F9                 test    cl, 1
seg000:00FC                 jz      short loc_129
seg000:00FE                 popa
seg000:00FF
seg000:00FF loc_FF:                                 ; 확장된 INT 13 명령을 지원한 경우 실행, LBA 주소값을 읽음 
seg000:00FF                 pusha
seg000:0100                 push    0
seg000:0102                 push    0
seg000:0104                 push    word ptr [bp+0Ah]
seg000:0107                 push    word ptr [bp+8]
seg000:010A                 push    0
seg000:010C                 push    7C00h
seg000:010F                 push    1
seg000:0111                 push    10h
seg000:0113                 mov     ah, 42h ; 'B'
seg000:0115                 mov     si, sp
seg000:0117                 int     13h             ; DISK -
seg000:0119                 popa
seg000:011A                 popa
seg000:011B                 jnb     short locret_12B
seg000:011D                 dec     di
seg000:011E                 jz      short locret_12B
seg000:0120                 xor     ah, ah
seg000:0122                 mov     dl, [bp+0]
seg000:0125                 int     13h             ; DISK - RESET DISK SYSTEM
seg000:0125                                         ; DL = drive (if bit 7 is set both hard disks and floppy disks reset)
seg000:0127                 jmp     short loc_FF
seg000:0129
seg000:0129 loc_129:                                ; 
seg000:0129                                         ; 
seg000:0129                 popa
seg000:012A                 stc
seg000:012B
seg000:012B locret_12B:                             ; 
seg000:012B                                         ; 
seg000:012B                 retn
seg000:012B sub_9B          endp

시작 위치 확인과 첫 섹터를 메모리에 덮어 씀

 

call sub_9B를 통해 정상적으로 부팅 가능한 파티션의 첫 섹터(512 Bytes)를 읽어서 메모리 0x7C00에 덮어쓰게 되고, 앞서본 MBR 구조에서 시그니처와 비교 후 정상이라면 loc_94를 실행해 메모리에 로드된 부트 섹터를 통해 부팅을 계속 진행하게 된다.

seg000:0081 loc_81:                                 ; 
seg000:0081                                         ; 
seg000:0081                 cmp     word ptr ds:7DFEh, 0AA55h   ; 시그니쳐를 체크한다.
seg000:0087                 jz      short loc_94                  ;정상인 경우 loc_94를 호출
seg000:0089                 cmp     byte ptr [bp+10h], 0
seg000:008D                 jz      short loc_57
seg000:008F                 mov     al, ds:7B7h
seg000:0092                 jmp     short loc_3D
seg000:0094
seg000:0094 loc_94:                                 ; 부트 섹터를 통해 부팅 작업을 계속한다.
seg000:0094                 mov     di, sp
seg000:0096                 push    ds
seg000:0097                 push    di
seg000:0098                 mov     si, bp
seg000:009A                 retf

정상적으로 부트 섹터를 호출하는 코드

 

MBR 영역 실시간 분석

지금까지는 코드를 눈으로 보면서 확인하였다.
이제 MBR 영역을 IDA와 Bochs를 이용하여 MBR 영역에 대해 실시간 디버깅을 진행해 보도록 하자. 바로 다음 장에서 Windbg를 이용한 커널 디버깅을 진행하는데, 왜 IDA와 Bochs 가상 머신을 이용하지? 라는 의문이 생길 수 있다. 이유는 간단하다. Windbg를 이용해서는 MBR 영역을 실시간으로 분석할 수 없기 때문이다.
MBR 영역을 실시간으로 디버깅하기 위해서는 운영체제 부팅 전단계에서부터 디버거가 제어할 수 있어야 한다. 하지만 안타깝게도 Windbg는 MBR 처리 이후인 부트 로더가 운영체제를 선택하는 단계에서 디버거를 연결할 수 있기 때문에 이미 MBR 영역에 대한 처리가 완료된 상태로 Windbg를 이용한 커널 디버깅 방법으로는 MBR 영역을 분석할 수 없다. 감사합니다.
다행히 Bochs라는 가상 머신을 IDA와 함께 이용하면, MBR 영역을 제어할 수 있어 분석이 가능하게 된다.
MBR 영역은 MBR 루트킷(Rootkit, 관리자 권한을 대신 행사할 수 있는 프로그램) 분석 작업에 유용하게 사용될 수 있고, 차후 악성 코드 등을 분석할 때에도 IDA와 잘 맞는 또 하나의 가상 머신인 Bochs를 이용하여 당신의 분석 실력을 한 단계 더 올려줄 것이다(MBR 영역뿐만이 아니라 일반 응용프로그램 분석에도 Bochs를 이용할 수 있다).

그럼 Bochs를 홈페이지 http://boshc.sourceforge.net에 방문하여 설치하도록 하자.

현재 Bochs2.7 버전까지 나와 있으나, IDA에서는 지원하는 버전을 다운로드하여 설치하기 바란다(필자가 확인했을 때는 2.5.1 버전을 설치시 정상적으로 디버깅할 수 없어 Bochs 2.3.7을 사용했다).

 

Bochs 2.3.7 버전 설치

이후 부팅할 수 있는 가상머신을 만들기 위해, 가상 머신에 사용할 가상 디스크 이미지를 생성하여야 한다. Bochs를 설치한 디렉토리나 프로그램 메뉴를 열어보면 Bximage.exe(Disk Image Creation Tool)이 존재한다. 이를 실행하여, 적당한 크기의 가상 디스크 이미지를 생성하도록 하자
예제에서는 hd(디스크타입), flat(이미지타입), 1000(용량, MB단위), mbr.img(이미지 이름)로 생성하였다.

Bochs 디스크이미지 생성

이렇게 옵션을 주면 가상 디스크 이미지를 생성하는데, 생성하면서 Bochs에서 디스크를 추가할 때 주어야 하는 설정값을 같이 알려준다. 이 정보를 아래와 같이 메모장안에 복사한 후, 좀전에 만든 디스크 이미지와 운영체제의 설치 시디를 ata0-master와 운영체제 설치 이미지를 ata0-slave로 설정한다.
그리고 메모리를 megs 옵션을 사용해 256MB으로 설정을 아래 텍스트와 같이 입력 후, 확장자를. bxrc로 지정하면 Bochs 가상머신을 실행할 수 있는 준비가 완료된다.

Bochs 가상머신 설정 파일

이 상태로 그대로 사용할 수도 있지만, 좀더 자세히 정보를 수정하고 싶다면, Bochs에서 제공하는 편집 기능을 이용하는 것이 편리하다. Bochs를 저자와 같이 인스톨 버전으로 설치하였다면 bxrc 확장자가 자동 등록되며 마우스 오른쪽 메뉴를 사용할 수 있는데, Configure 메뉴를 통해 세부적인 가상 머신의 설정을 변경할 수 있다.

 

Bochs의 다양한 설정을 GUI를 이용하여 활용할 수 있다.

이를 통해 수정후 저장하게 되면, 조금 전 짧게 만들었던 환경설정이 아래와 같이 각 옵션값을 생성하여 새로 저장된다. 다시 메모장으로 파일을 열면 아래와 같이 많은 내용이 추가된 것을 볼수 있으며, 수정없이 사용하여도 되고, 각 부분별 설명을 추가로 달아 놓았으니 필요에 따라 수정하여 사용하면 된다.

 

# configuration file generated by bochs

# Bochs의 가상 디바이스를 활성화하는 옵션으로 1을 설정하면 해당 디바이스가 활성화 된다.
plugin_ctrl: unmapped=1, biosdev=1, speaker=1, extfpuirq=1, gameport=1, pci_ide=1, acpi=1, ioapic=1
config_interface: win32config
display_library: win32
# 사용할 메모리를 지정한다 MB단위
memory: host=256, guest=256
# 사용할 BIOS 이미지를 선택한다.
romimage: file="C:\Program Files (x86)\Bochs-2.4.5/BIOS-Bochs-latest"
# 사용할 VGA BIOS 이미지를 선택한다.
vgaromimage: file="C:\Program Files (x86)\Bochs-2.4.5/VGABIOS-lgpl-latest"
# 부팅 순서를 지정한다.
boot: disk, cdrom
# 플로피 디스크를 부팅시 체크할 지 지정한다.
floppy_bootsig_check: disabled=0
# no floppya
# no floppy
# 디스크관련드라이버 설정, 디스크이미지 연결 및 시디롬 설정등을 지정한다.
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, mode=flat, translation=auto, path="mbr.img", cylinders=2031, heads=16, spt=63, biosdetect=auto, model="Generic 1234"
ata0-slave: type=cdrom, path="ko_WinXP_sp2.iso", status=inserted, biosdetect=auto, model="Generic 1234"
ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15
ata2: enabled=0
ata3: enabled=0
# 페러럴포트 활성화 유무
parport1: enabled=1, file=""
parport2: enabled=0
# COM포트 활성화 유무
com1: enabled=1, mode=null, dev=""
com2: enabled=0
com3: enabled=0
com4: enabled=0
# USB활성화 유무
usb_uhci: enabled=0
usb_ohci: enabled=0
i440fxsupport: enabled=1
vga_update_interval: 50000
vga: extension=vbe
# CPU 코어 개수와 IPS(Instructions per second)를 지정할 수 있다.
cpu: count=1, ips=4000000, reset_on_triple_fault=1, ignore_bad_msrs=1
# CPU내 이용할 기능을 활성화 하거나 비활성화 지정
cpuid: cpuid_limit_winnt=0, mmx=1, sse=sse2, xapic=1, sep=1, aes=0, xsave=0, movbe=0, 1g_pages=0
# 그 외 기타 설정들
print_timestamps: enabled=0
port_e9_hack: enabled=0
text_snapshot_check: enabled=0
private_colormap: enabled=0
clock: sync=none, time0=local
# no cmosimage
ne2k: enabled=0
pnic: enabled=0
sb16: enabled=0
# no loader
# 로그경로와 로그 표시 정도를 지정한다.
log: -
logprefix: %t%e%d
panic: action=ask
error: action=report
info: action=report
debug: action=ignore
pass: action=fatal
# 키보드 설정
keyboard_type: mf
keyboard_serial_delay: 250
keyboard_paste_delay: 100000
keyboard_mapping: enabled=0, map=
user_shortcut: keys=none
# 마우스설정
mouse: enabled=0, type=ps2

이렇게 만들어진 가상 머신을 실행하여, 운영체제를 설치하도록 하자. 물론 MBR 영역이 필요하므로, 플로피 디스크 타입으로 만들어 사용하여도 되며, 인터넷에 공개된 디스크 이미지를 사용하여도 된다. 이유인 즉 Bochs는 일반적인 가상 머신보다 실행 및 설치 속도가 상당히 느리고, 타 가상화 솔루션에 비해 호환성이 부족하여 보편적으로 많이 사용되지 않는다. 필자 역시 테스트를 위해 윈도우 XP를 설치하는데 셋업 파일 복사에만 20-30분가량 소요되니, 만약 Bochs를 이용하여 운영체제를 설치한다면 상당한 인내심이 필요할 것이다.

시간이 된다면, IDA Pro를 제공하는 Hex-rays에서 제공하는 블로그의 글 http://www.hexblog.com/?p=103을 통해 MBR 영역 이미지와 어셈블리 코드를 얻을 수 있으며, IDA 활용 팁도 확인할 수 있으므로 꼭 방문해 보기 바란다.

Bochs를 이용한 윈도우XP 설치

설치가 잘 마무리 되었다면(혹 이미지를 구했다면) 정상적으로 실행가능한지 Bochs에서 제공하는 디버거를 이용하여 테스트를 해보자. 테스트는 아래와 같은 명령을 이용하여 진행할 수 있다.

 

bochs dbg –f “확인하고자하는가상머신” -q

-q 옵션을 통해 만약 실행중 잘못된 오류가 있다면 문구를 표시하여 해당 부분을 조치 할 수 있다.

가상머신에 오류가 있는 경우 손쉽게 확인할 수 있다

이제 막바지 작업으로, IDA 설정 내용을 확인 할 차례이다.

아래와 같이 Debugger -> Run -> Local bochs debugger를 선택하거나, File -> Open을 이용하여 MBR 분석을 위해 만들어둔 가상 머신을 열면 된다.

아래 그림과 같이 Open 메뉴를 이용해 Bochs 가상머신을 실행할 수 있다.

 

IDC의 Bochs 환경 설정 파일 연결 설정

MBR을 컴퓨터 부팅중 초기 작업에 시작되므로, 디버깅 시작과 동시에 일시 정지를 걸어두면 편리하기 때문에 아래와 같이 Debugger setup 메뉴의 Stop on debugging start을 체크하도록 하자.

디버깅 시작시 바로 일시정지하도록 설정

모든 설정을 완료하였다면 이제 Bochsdbg.exe의 경로를 지정하면 끝이다. IDA Pro 6.0 이하 버전이라면 Bochsdbg 실행 파일의 위치를 지정이 올바른지 Debugger setup 메뉴의 Set specific options을 통해 확인해 보도록 하자.

IDA 6.0이전 버전의 Bochsdbg.exe가 설치된 디렉토리 지정

IDA 6.0 이후 버전부터는 위와 같은 Bochsdbg 설정 항목을 제공하지 않는다. 운영체제의 환경변수로 선언해 주어야 해당 변수의 값을 참조하여 Bochsdbg 위치를 확인한다.

만약 BXSHARE 변수로 Bochsdbg.exe의 경로를 확인할 수 없을 경우 아래와 같은 오류가 나타나며, 디버깅 진행이 중지된다.

Bochsdbg.exe를 찾을 수 없다는 오류 메세지

이는 아래와 같이 내 컴퓨터 에서 마우스 오른쪽 메뉴인 속성을 통해 시스템 변수로 등록하거나 앞서 진행한 Set 명령을 통해 등록하자.

운영체제 환경변수에 Bochs 경로 설정

Set 명령을 이용한 환경 변수는 다음과 같이 명령 프롬프트 창에서 설정 가능하다.

 

set BXSHARE=”Bochs설치경로”

여기까지 구성하였다면, IDA에서 코드 유형이 16비트인지 확인한 후, 아래와 같이 IDA를 통한 MBR 실시간 디버깅 가능하다.

이와 같은 구성은 악성코드 및 응용프로그램을 Bochs가 제공하는 가상화에서 분석을 진행할 때에도 용이하므로 사전에 Bochs와 연동이 가능하도록 IDA를 구성해 놓으면 편리하다.

MBR 영역에서 실시간 디버깅할 수 있다.

 

부트 섹터

MBR 영역 코드 확인부터 Windbg IDA를 이용한 분석까지 진행하였다. 위와 같이 MBR 영역은 코드는 부팅 가능 파티션이 있는지 확인하고, 있는 경우 이를 0x7C00에 덮어쓰는 구조이다.

이후 NTFS가 부팅 가능한 파티션인 경우 부트 섹터로 점프하면 정프 명령어가 실행되고, 점프 명령어는 BPB를 지나 오프셋(Offset) 0x0054로 점프한 후 BPB 항목을 참조해 해당 볼륨의 운영체제를 로드하는 부트 코드가 실행되는데 이 역시 MBR과 같이 16비트 어셈블리어로 구성되어 있다.

아래 그림은 부트 섹터 구조를 간략화한 것이다.

NTFS 부트 섹터 구조

부트 섹터는 부트 코드가 포함된 부트 섹터가 디스크 섹터0에 위치하게 된다.

각 영역에 대해 살펴보자.

CPU jump instruction: 점프 명령어

OEM ID: NTFS, FAT32등 파티션 관련 정보가 들어감

BIOS parameter block: 섹터의 크기, 전체 섹터 수 등, 디스크 관련 정보들이 들어감

Bootstrap Code: 운영체제 시작을 위해 실행되는 코드(16비트 어셈블리)

 

그럼 실제 데이터를 확인해 보자.

부트 섹터의 HEX코드

위 그림을 처음부터 설명하자면 0x00000000A까지 jump instruction OEM ID 대한 부분이며, 0x00000000B부터 0x000000023까지 BIOS parameter block, 0x000000024부터 0x000000053까지 BIOS parameter block 확장 영역으로 사용된다.

그리고 0x000000054부터 0x0000001FD까지는 Bootstrap Code이다(부트 섹터에 대한 세부적인 분석은 MBR과 유사하기 때문에, 진행하지 않겠다).

이렇게 부트 섹터까지 진행이 완료되고, NTLDR 로드가 성공적으로 완료 되면, 이후 NTLDR 로드가 진행되는데, NTLDR은 운영체제가 부팅할 수 있게 BCD(Windows Boot Manager)를 로드한다.

 

이렇게 MBR부터 윈도우 부팅 과정을 마무리 한다.

반응형