본문 바로가기

Reverse Engineering/Reversing 이론 설명

[System] 기계어의 구성(명령어 형식 : Instruction Format)

  이전에 마이크로프로세서가 읽어들이는 숫자화된 명령어의 집합을 기계어라 하였으며, 이 기계어는 실제 시스템 프로그래밍을 하면서 직접 다루어야할 경우가 많다. 대표적인 예는 디스어셈블러나 디버거를 작성하는 경우이지만, 이외에 프로그래밍 언어에 의하여 컴파일되어진 실행 이미지의 내용을 직접 조작해야할 때나 프로그램의 함수 호출을 중간에 가로채야 하는 API-Hooking과 같은 테크닉에서 사용될 수 있다.

  그럼 이제부터 실제 기계어 코드가 어떻게 구성되어 있는지 본격저으로 알아보도록 할 것이며, 여기에서 다루는 기계어 코드는 인텔 마이크로프로세서 또는 그 호환 기종에 기반한 것이다.

  인텔 마이크로프로세서는 아래와 같은 내용들이 묶여 하나의 명령어를 구성하게 되는데, 이들 요소는 그 명령어에 따라 존재하는 요소도 있고 그렇지 않은 요소도 있을 수 있으며, 그 변화에 많은 영향을 미치는 것은 명령어의 번지 지정 방식(Addressing Mode)이다.

Instruction
Prefixes 
Opcode  ModR/M  SIB  Displacement  Immediate 
(optional)         1,2,3 byte       1 byte            1 byte            0,1,2,4 byte     0,1,2,4byte

Instruction Prefixes
- 기계어에서 가장 처음 Byte에 존재할 수 있는 Prefix는 그 명령어의 쓰임에 따라 존재할 수도 있고 그렇지 않을 수도 있으며, 이 Prefix가 존재할 경우는 4가지 Group의 경우로 나눌 수 있다.

● Group 1(Lock and Repeat Prefix) : Lock Prefix는 멀티프로세서 환경에서 동시에 접근하는 메모리에 대하여 충돌하는 것을 막아준다. Repeat PrefixString 명령어들(MOVS, CMPS, SCAB, LODS, STOS, INS 그리고 OUTS)과 함께 사용되어 해당 명렁어에 대하여 특정한 상황에 반복적인 일을 처리하게끔 할 때 사용된다.
● Group 2(Segment Override Prefix) : 메모리 참조 시 프로세서는 세그먼테이션 과정에서 항상 어떠한 세그먼트 값과 함께 작동하게 되어 있으며, 프로그램에서 세그먼트를 명시적으로 지정하지 않았다면 디폴트 세그먼트가 사용되지만 그렇지 않을 경우 명시적으로 지시되어진 세그먼트 값은 이 Prefix에 그 정보가 저장된다.
● Group 3(Operand Override Prefix) : 16비트 프로그램에서 32비트의 오퍼랜드를 지정하거나 32비트 프로그램에서 16비트의 오퍼랜드를 지정할 경우에 사용되어지는 것으로, 16비트 프로그램에서는 디폴트로 오퍼랜드 크기를 8비트 또는 16비트를 사용하고, 32비트 프로그램에서는 오퍼랜드 크기 또한 32비트를 사용하게 된다.
  만약 32비트 프로그램에서 16비트의 오퍼랜드 값을 취하고 싶거나 16비트 프로그램에서 32비트의 오퍼랜드 값을 취해야 할 경우 Prefix값 66이 붙게 되어 이러한 명령어로 해석하게 한다.
● Group 4(Address Override Prefix) : 메모리 참조 명령에서는 오퍼랜드에 주소를 지정하게 되는데, 이때 16비트 프로그램에서는 디폴트로 16비트가, 32비트 프로그램에서는 32비트의 주소 사이즈가 사용된다. 하지만, 32비트 프로그램에서 [mov word ptr[cx], 1] 또는 16비트 프로그램에서 [mov word ptr[ecx], 100]과 같이 디폴트와 다른 주소 사이즈를 지정할 경우 Prefix값 67을 지정하며, 이 경우 16비트에서는 32비트의 어드레스 사이즈로, 32비트에서는 16비트의 사이즈로 해석되어지게 된다.

작동 코드(Opcode : Operand Code)
- 작동 코드(Opcode)는 마이크로프로세서가 수행해야 할 일들의 종류를 나타내는 것으로 기계어 구성에 있는 다른 요소들은 바로 이 요소를 위한 부가적인 요소들이라 할 수 있다. 그리고 인텔 계열의 작동 코드(Opcode)는 1바이트부터 3바이트까지의 사이즈를 가지며, 단순한 명령어의 종류뿐만 아니라 오퍼랜드의 데이터 크기가 바이트인지, 실행 코드의 비트 사이즈가 16비트 프로그램에서는 16비트, 32비트 프로그램에서는 32비트인지를 나타내는 비트와 데이터의 연산 방향, 그리고 데이터 필드가 확장 부호이어야 하는지 등을 나타내는 비트들과 같은 부가적인 내용을 포함하는 작동 코드도 있다.

  마이크로프로세서에서 가져야 할 명령어는 고급 언어의 명령을 기계어로 표현할 수 있는 명령어가 모두 존재해야 할 것이며, 이러한 점에서 다음과 같은 4가지로 분류할 수 있다.

● 데이터 처리(산술 및 논리 명령어들) : 데이터 처리 명령어는 어떠한 수치 데이터를 계산하기 위하여 제공되어져야 하는 명령어들로서 이들 명령어는 단순히 수뿐만 아니라 비트들에 대한 연산도 가능해야 할 것이다. [ex: add, dec, imul]
● 데이터 저장(기억장치 명령어들) : 이 기억장치 명령어들은 위 명령어에 위하여 수행되는 데이터를 메모리로부터 레지스터로 가져오거나 산술 결과의 값을 레지스터에서 메모리로 전송하는데 사용되는 명령어들이다. [ex: mov, push, pop]
● 제어(비교와 분기 명령어들) : 비교 명령어는 데이터의 값을 비교하여 어떠한 조건과 일치하는지 알아보고자 할 때 사용되어지는 명령어이며, 분기 명령어는 어떠한 조건에 따라 또는 특정 함수의 호출을 위하여 프로그램의 흐름이 바뀌는 명령어들을 뜻한다. [ex: jmp, call]
● 데이터 이동(I/O 명령어들) : I/O 명령어들은 주변장치들에게 데이터를 전송하거나 주변장치를 제어하기 위하여 사용되어지는 명령어들이다. [ex: in, out]

번지 지정 방식과 ModR/M 그리고 SIB
- 번지 지정 방식(Addressing Mode)이란 프로세서가 취급할 정보(Operand)가 위치하는 메모리 번지 도는 레지스터를 지정하는 방법을 말한다. 즉, 이전에 살펴보았던 전형적인 명령어 형식에서 오퍼랜드를 어떻게 지정할 것인지를 나타내는 것으로, 전형적인 명령어 형식에서의 오퍼랜드를 보다 확장하기 위해 사용되어진 방법이다.

주소 지정 방식 산술 표현 
이미디어트(Immediate) Operand를 상수 값으로 사용함 
레지스터(Register)  Operand를 레지스터로 사용함 
변위(Displacement)  지정 주소 = [레지스터 + 변위] 
베이스 레지스터  지정 주소 = [레지스터 + 베이스 레지스터] 
베이스 레지스터 + 변위  지정 주소 = [레지스터 + 베이스 레지스터 +
                 변위] 
스케일 인덱스 + 변위  지정 주소 = [레지스터 + 인덱스 레지스터  * 
                 스케일 값 + 변위] 
베이스 + 인덱스 + 변위  지정 주소 = [레지스터 + 베이스 레지스터 + 
                 인덱스 레지스터 + 변위] 
베이스 + 스케일 + 변위  지정 주소 = [레지스터 + 인덱스 레지스터 *
                 스케일 값 + 베이스 레지스터 + 
                 변위] 
  
  기계어에서는 위와 같은 번지 지정 방식을 구성하기 위하여 작동 코드 뒷부분에 ModR/M, SIB, Displacement, Immediate라는 필드들을 두고 있다.

● ModR/M : 이 필드는 오퍼랜드가 레지스터에 있는지 또는 메모리에 있는지를 나타내는 것으로 Mod, Reg/Opcode, r/m의 세 개의 필드로 구성되어 있다.
● SIB byte : 이 필드는 위에서 언급한 ModR/M 필드에 의하여 간접 어드레스 형태로 주소가 지정되어졌을 때 베이스 어드레스나 스케일 인덱스와 같은 추가적인 어드레스 지정 형식을 구성하고자 할 때 그 정보가 들어가는 필드이며, 이 필드 역시 SS, Index, Base의 세 가지 필드로 구성되어지게 된다.