System Hacking/Buffer Overflow

[Buffer Overflow] 8086 CPU 레지스터 구조

jir4vvit 2020. 3. 18. 14:32

참고문헌

제목 : 해커 지망생들이 알아야할 Buffer Overflow 기초

작성자 : 달고나

출처 : www.wowhacker.com

 

* 사진과 글은 위 문서를 참고하였습니다.

 

이전 글에서 하나의 segment의 구조를 알아 보았다.

https://jiravvit.tistory.com/entry/Buffer-Overflow-8086-Memory-Architecture

 

[Buffer Overflow] 8086 Memory Architecture

참고문헌 제목 : 해커 지망생들이 알아야할 Buffer Overflow 기초 작성자 : 달고나 출처 : www.wowhacker.com * 사진과 글은 위 문서를 참고하였습니다. <8086 Memory Architecture> 8086? : 인텔사에서 개발한 16..

jiravvit.tistory.com

 

<8086 CPU 레지스터 구조>

이제 CPU가 프로세스를 실행하기 위해서는 프로세스를 CPU에 적재시켜야 할 것이다.

그리고 RAM(=메인메모리)에 흩어져 있는 명령어 집합(instruction set)과 같이 데이터들을 적절하게 집어내고 읽고 저장하기 위해서는 여러 가지 저장 공간이 필요하다. 

또한 이것은 CPU가 재빨리 읽고 쓰기를 해야하는 데이터들이므로 CPU 내부에 존재하는 메모리(레지스터(register))를 사용한다.

 

  • 범용 레지스터 : 논리 연산, 수리 연산에 사용되는 피연산자, 주소를 계산하는데 사용되는 피연산자, 그리고 메모리 포인터가 저장되는 레지스터다.
  • 세그먼트 레지스터 : code segment, data segment, stack segment를 가리키는 주소가 들어 있는 레지스터다.
  • 플래그 레지스터 : 프로그램의 현재 상태나 조건 등을 검사하는데 사용되는 플래그들이 있는 레지스터다.
  • 인스트럭션 포인터 : 다음 수행해야 하는 명령(instruction)이 있는 메모리 상의 주소가 들어 있는 레지스터다.

일반적 시스템의 프로그램 레지스터 구성(32bit 시스템)


범용 레지스터(General-Purpose Registers)

: 프로그래머가 임의로 조작할 수 있게 허용되어 있는 레지스터 

4개의 32bit 변수라고 생각하자. (32bit 시스템 기준으로)

범용 레지스터

예전의 16bit 시절에는 각 레지스터를 AX, BX, CX, DX 등으로 불렀지만

32bit 시스템으로 전환되면서 E(Extended)가 앞에 붙어 EAX, EBX, ECX, EDX 등으로 불린다.

비슷하게, 64비트 버전에서는 'E' 대신 'R'을 사용한다. 즉, 'EAX'의 64비트 버전은 'RAX'가 된다.

 

[16bit 시스템 레지스터]

처음 네 개의 16bit 레지스터 (AX, CX, DX, BX)는 그림에서도 확인할 수 있다시피 두 개의 8비트 레지스터로 각각 접근할 수 있다. 

-> AX 레지스터의 상위 부분을 AH라고 하고 하위 부분을 AL라고 한다.

 

  • AX (Accumulator register). 산술 연산에 사용.
  • CX (Counter register). 시프트/회전 연산과 루프에서 사용.
  • DX (Data register). 산술 연산과 I/O 명령에서 사용.
  • BX (Base register). 데이터의 주소를 가리키는 포인터로 사용. (세그멘티드 모드에서는 세그멘트 레지스터 DS로 존재)
  • SP (Stack Pointer register). 스택의 최상단을 가리키는 포인터로 사용.
  • BP (Stack Base Pointer register). 스택의 베이스를 가리키는 포인터로 사용.
  • SI (Source Index register). 스트림 명령에서 소스를 가리키는 포인터로 사용.
  • DI (Destination Index register). 스트림 명령에서 도착점을 가리키는 포인터로 사용.

 

[32bit 시스템 레지스터]

EAX, EBX, ECX, EDX 레지스터들은 프로그래머의 필요에 따라 아무렇게나 사용해도 된다. 하지만 나중에 기계어 코드를 읽고 이해하기 편하게 하기 위해서 그 목적대로 사용해 주는 것이 좋다.

또한 컴파일러도 이러한 목적에 맞게 사용하고 있다.

 

  • EAX: 피연산자와 연산 결과의 저장소
  • EBX: DS segment  안의 데이터를 가리키는 포인터
  • ECX: 문자열 처리나 루프를 위한 counter
  • EDX: I/O 포인터
  • ESI: DS 레지스터가 가리키는 data segment  내의 어느 데이터를 가리키고 있는 포인터. 문자열 처리에서 source를 가리킴
  • EDI: ES 레지스터가 가리키는 data segment  내의 어느 데이터를 가리키고 있는 포인터. 문자열 처리에서 destination을 가리킴
  • ESP: SS 레지스터가 가리키는 stack segment 의 맨 꼭대기를 가리키는 포인터
  • EBP: SS 레지스터가 가리키는 스택 상의 한 데이터를 가리키는 포인터

세그먼트 레지스터

: 프로세스의 특정 세그먼트를 가리키는 포인터 역할을 한다.

세그먼트 레지스터가 가리키는 세그먼트들

CS 레지스터는 code segment를,

DS, ES, FS, GS 레지스터는 data segment를

SS 레지스터는 stack segment를 가리킨다.

 

많은 최신 운영체제에서 대부분의 애플리케이션들은 (FreeBSD, Linux 또는 Microsoft Windos와 같이) 거의 모든 세그먼트 레지스터가 동일한 위치를 가리키는 메모리 모델을 사용하는 대신, 페이징을 사용하여 세그먼트 레지스터의 사용을 비활성화 한다. 하지만 일반적으로 FS와 GS는 이 규칙에서 예외이며, 대신 스레드 특정 데이터를 가리키는데 사용된다.

 

페이징(paging)? : 쉽게 말해서 커다란 크기의 작업을 고정된 크기로 나누는 것

 

이렇게 세그먼트 레지스터가 가리키는 위치를 바탕으로 우리는 원하는 segment안의 특정 데이터, 명령어를 정확하게 끄집어 낼 수가 있게 된다.


플래그 레지스터

: 상태 플래그, 컨트롤 플래그, 시스템 플래그들의 집합이다.

시스템이 리셋되어 초기화되면 이 레지스터는 0x00000002의 값을 가진다.

1,3,5,15,22~31번 비트는 예약되어 있어 소프트웨어에 의해 조작할 수 없게 되어 있다.

 

플래그 레지스터의 구성

  • Status flags
    • CF(Carry flag): 연산 수행 중 carry나 borrow가 발생하면 1이 됨
      • carry, borrow: 덧셈에서 bit bound를 넘거나 뻴셈 시 빌려오는 경우
    • PF(Parity flag): 연산 결과 최하위 바이트 값이 짝수일 경우 1이 됨
      • 데이터 전달 중 오류가 있었는지 검사하는 parity  check에 이용
    • AF(Adjust flag): 연산 결과 carry나 borrow가 3bit 이상 발생하면 1이 됨
    • ZF(Zero flag): 결과가 zero임을 가리킴. if등 조건문이 만족되면 set됨
    • SF(Sign flag): 연산 결과 최상위 비트의 값과 같음. signed 변수의 경우 양수이면 0, 음수이면 1이 됨
    • OF(Overflow flag): 정수형 결과값이 너무 크거나 작아서 피연산자의 데이터 타입에 모두 들어가지 않을 경우 1이 됨

 

  • DF(Direction flag): 1일 경우 문자열 처리 instruction이 자동으로 감소, 0일 경우 증가

 

  • System flags
    • IF(Interrupt enable flag): 프로세서에게 mask한 interrupt에 응답하게 하려면 1
    • TF(Trap flag): 디버깅 시 single-step을 가능하게 하려면 1
    • IOPL(I/O privilege level field): 현재 수행 중인 프로세스나 task의 권한 레벨. 현재 수행 중인 프로세스의 권한을 가리키는 CPL이 I/O address 영역에 접근하려면 I/O privilege level 보다 작거나 같아야 한다.
    • NT(Nested task flag): interrupt의 chain을 제어함. 1이 되면 이전 실행 task와 현재 task가 연결되어 있음
    • RF(Resume flag): Exeption debug를 하기 위해 프로세서의 응답을 제어
    • VM(Virtual-8086 mode flag): Virtual-8086 모드를 사용하려면 1
    • AC(Alignment check flag): 이 비트와 CR0 레지스터의 AM 비트가 set되어 있으면 메모리 레퍼런스의 Alignment checking이 가능하다.
    • VIF(Virtual interrupt flag): If flag의 가상 이미지. VIP flag와 결합해 사용
    • VIP(Virtual interrupt pending flag): interrupt가 pending 되었음을 가리킴
    • ID(Identification flag): CPUID instruction을 지원하는 CPU인지를 나타냄

Instruction Pointer

: 다음 실행할 명령어가 있는 현재 code segment의 offset 값을 가진다.

이것은 하나의 명령어 범위에서 선형 명령 집합의 다음 위치를 가리킬 수 있다.

뿐만 아니라 JMP, Jcc, CALL, RET와 IRET instruction이 있는 주소값을 가진다.

 

컴퓨터는 이게 명령이든지 아니면 일반 데이터든지 동일하게 처리를 하며, 또한 이들을 구분할 능력을 가지고 있지 않다. 그렇다면 컴퓨터는 어떻게 이러한 일련의 데이터에서 명령을 찾아내어서 그걸 실행을 시키게 되는 것일까?

해답은 instruction pointer에 있다. 이 instruction pointer라는 이름에서 우리는 이 포인터가 무언가를 가르킨다는 것을 알 수 있다. instruction pointer은 다음 명령(instruction)을 가르킨다. 컴퓨터는 instruction pointer를 살펴봄으로써, 다음에 어떤 명령을 실행해야 될지를 알 수 있게 된다.

 

이런 Instruction Pointer를 다루는 레지스터가 EIP 레지스터이다.

 

EIP 레지스터는 소프트웨어에 의해 바로 엑세스 할 수 없고 contral-transfer instruction(JMP, Jcc, CALL, RET)이나 interrupt와 exception에 의해서 제어된다.

 

EIP 레지스터는 오직 CALL instruction을 수행하고 나서 프로시저 스택(procedure stack)으로부터 리턴하는 instruction의 address를 읽는 것이다.

 

프로시저 스택의 return instruction pointer의 값을 수정하고 return instruction(RET, IRET)을 수행함으로 해서 EIP 레지스터의 값을 간접적으로 지정해 줄 수 있다.

 

 


8086시스템의 메모리 및 CPU 레지스터의 구조를 알아본 이유?

우리가 buffer overflow 공격을 하는데 있어 적절한 padding 사용과 return address의 정확한 위치를 찾고 필요한 assembly 코드를 추출하고 이해하는데 필요하다.

 

 

 

참고

더보기

참고로 레지스터는 CPU 안에서 일정 부분을 처음부터 차지하고 있다.

프로세스가 아무것도 실행되지 않아도 레지스터는 CPU안에서 빈 공간으로 일정 부분을 차지하고 있다는 이야기이다.