본문 바로가기

Programming

x86 Segment Registers

어셈블리 코드를 보다보면 


DS:[EAX], SS:DWORD PTR[EBP-4] 


뭐 대강 이런식으로 DS:, CS:, SS: 이런 부분들을 자주 볼수있다.

 

이것들의 정체는 세그먼트 레지스터로 16비트인데 페이징이 없던시절, 메모리 영역 관리/분리 등을 위해서 사용되었다.

먼저 real mode 상에서는 주소지정이 세그먼트:오프셋 방식으로 이루어진다. 이 말은, CS:0x1234 이런식으로 메모리 주소가 표현된다는 것인데 여기서 세그먼트 레지스터는 16비트이므로 65536 개의 세그먼트 영역이 존재할 수 있다. 한 세그먼트는 16바이트(4비트공간)의 크기를 갖는다. 즉 CS 가 0xAAAA 라면 선형주소에 더해지는 값은 0xAAAA0 이 된다. 뭔가 계산이 깔끔하지않고 더럽지만 아무튼 이렇다.

세그먼트값을 4비트 오른쪽쉬프트하고, 그값에 오프셋을 단순히 더하면 real mode 에서 물리적 메모리주소가 되는것이다.

 

이 옜날 세그먼트 방식이 32비트 시스템으로 업그레이드되고, 페이징이 생기면서 사용되지 않게 되었는데, 옜날 시스템과의 호환성을 위해 세그먼트 레지스터들은 그대로 16비트로 남아있게 되었다.  단지 이제 protected 모드에서 세그먼팅을 하는 방식이 달라진다.

 

protected 모드에서의 어드레싱은 다음과 같이 한다. 먼저 예를들어 CS 에 0xFF12 라는 값이 있다고 치자.

이제는 이 값을 그대로 OFFSET 과 더하는게 아니라 이 값의 2번 비트에따라 GDT, LDT 중 하나를 참조하고 특정 비트영역이 GDT, LDT상의 테이블 offset 이 되고 나머지 비트들은 각종 속성을 나타낸다. GDT, LDT 를 따라가서 거기있는 값이 OFFSET 이 더해진다

 

그 자료구조가 바로 세그먼트 디스크립터이다.

GDT, LDT 라고 불리우는 것의 정체가 이것인데 이것은 OS 가 처음에 로딩될때 물리적 메모리상에 올라온다.

GDT, LDT 의 차이는 아직 잘 모르겠지만 아무튼 CS, SS, DS 같은 세그먼트 레지스터는 이제 GDT, LDT 상의

엔트리를 가리키고, 해당 엔트리의 특정 비트들로 정해진 BASE 값이 OFFSET 과 더해진다.

 

그런데 대부분의 OS 에서 FLAT 메모리 모델을 사용하기 위해서 세그먼팅을 안쓰려고 이 BASE 를 0 으로 놓는다, 그리고 LIMIT 는 FFFFFFFF 로 놓는다. 결국 0에는 무엇을 더해도 그대로이기때문에 이제는

 

세그먼트:오프셋

 

이것이 그냥 오프셋이 되는것이다.

이렇게 얻어진 오프셋이 다시 페이징이 되는것이다.

 

CPU 는 언제나 메모리주소를 reference 할때 세그먼팅을 한다. 그것이 어셈블리상에서


CS:DWORD PTR[EAX] 


이런식으로 명시적으로 CS 를 이용해서 세그먼팅을 하라고 될수도 있고

만약 CS, DS, SS 이런것이 전혀 없이 메모리를 참조하는 어셈블리 코드가 있다면

CPU가 알아서 암묵적으로 해당 상황에 맞는 세그먼트 레지스터를 참조한다.

단지 모든 세그먼트 레지스터가 가리키는 세그먼트 디스크립터 엔트리의 BASE 가 전부 0 이기때문에

이것이 아무 효과가 없을 뿐이다.

 

예를들어 EBP 를 기준으로 무언가 메모리 참조를 하는 어셈블리 명령이 있을때

CPU 는 명시된게 없는경우 자동으로 SS 레지스터를 이용해서 세그먼팅한다.

즉 스택 영역의 참조일것이라 알아서 추정하는것이다.

 

LIMIT 을 FFFFFFFF 로 해놓았기때문에 아무 문제가 없지만

만약 LIMIT 을 작게 해놓는다면 특정 세그먼트 레지스터를 이용해서

메모리를 참조할때 제한을 걸수있다.  BASE 도 마찬가지다.

만약 BASE 100, LIMIT 200 이렇게 놓는다면 해당 세그먼트 레지스터를

거쳐서 메모리 주소를 참조할때는 100번지 ~ 200번지 까지밖에 접근할수가 없다.

 

세그먼트 디스크립터에는 BASE, LIMIT 외에도, 해당 세그먼트의 권한 속성등

여러가지가 비트별로 명시된다. 64비트 시스템에서는 이러한것이 또 달라진다고한다.

 

아무튼 핵심은 세그먼트 레지스터의 쓰임이 real, protected 모드별로 다르고

protected 모드의 경우 GDT, LDT 라는 세그먼트 디스크립터를 참조하는 것이고

현재 페이징을 하더라도 효과가 없는 세그먼팅을 호환성을 위해 같이 하고있는 것이고

어떤 세그먼트 레지스터는 다른 용도로 쓰이기도 한다고 한다.

 

참고로 realmode, protected 모드의 전환은 CR0 레지스터의 1번비트로 결정된다

mov  eax, cr0
inc  ax
mov  cr0, eax
즉 이런식으로 모드변경이 가능하다


Segment Registers

The 6 Segment Registers are:

  • Stack Segment (SS). Pointer to the stack.
  • Code Segment (CS). Pointer to the code.
  • Data Segment (DS). Pointer to the data.
  • Extra Segment (ES). Pointer to extra data ('E' stands for 'Extra').
  • F Segment (FS). Pointer to more extra data ('F' comes after 'E').
  • G Segment (GS). Pointer to still more extra data ('G' comes after 'F').

Most applications on most modern operating systems (like FreeBSD, Linux or Microsoft Windows) use a memory model that points nearly all segment registers to the same place (and uses paging instead), effectively disabling their use. Typically the use of FS or GS is an exception to this rule, instead being used to point at thread-specific data.



FS 세그먼트 레지스터가 윈도우즈의 경우 TEB, PEB 의 주소를 가져오는 용도로
사용되는것 같아 FS 레지스터에 대해서 구글링중 우연히 세그먼트 레지스터에
잘 정리된 문서를 발견. 알고보니 FS, GS 는 단지, ES 에서의 Extra 다음의 알파벳이
F, G 라서 붙은 이름이었다 -_- 아무 약어도 아니다.

fs:[18h] 의 경우 TCB 의 포인터가 담겨있고
fs:[30h] 의 경우 PEB 의 포인터가 담겨있다

PEB 에는 IsDebugged 라는 변수가 있어서 이것이
1/0 인것을 가지고 프로세스가 디버깅되는 중인지 판단이 가능하다.
reversing.kr 의 twist 문제의 경우 pop ss 에 의한 안티디버깅이 아니라
PEB 의 내용을 확인하는 것이었던것 같다.

지금까지 조사한 바에 의하면 pop ss 의 경우 single step 의
경우만 anti debugging 이 가능하기 때문에 아쉽게도 사실 쓸모없는거같다...


보너스로 EFLAGS 레지스터 내용도 잘 정리가 되있다

EFLAGS Register

The EFLAGS is a 32-bit register used as a vector of bits representing Boolean values to store and control the results of operations and the state of the processor.

The names of these bits are:

31302928272625242322212019181716
0000000000IDVIPVIFACVMRF
1514131211109876543210
0NTIOPLOFDFIFTFSFZF0AF0PF1CF

The bits named 0 and 1 are reserved bits and shouldn't be modified.

The different use of these flags are:
0.CF : Carry Flag. Set if the last arithmetic operation carried (addition) or borrowed (subtraction) a bit beyond the size of the register. This is then checked when the operation is followed with an add-with-carry or subtract-with-borrow to deal with values too large for just one register to contain.
2.PF : Parity Flag. Set if the number of set bits in the least significant byte is a multiple of 2.
4.AF : Adjust Flag. Carry of Binary Code Decimal (BCD) numbers arithmetic operations.
6.ZF : Zero Flag. Set if the result of an operation is Zero (0).
7.SF : Sign Flag. Set if the result of an operation is negative.
8.TF : Trap Flag. Set if step by step debugging.
9.IF : Interruption Flag. Set if interrupts are enabled.
10.DF : Direction Flag. Stream direction. If set, string operations will decrement their pointer rather than incrementing it, reading memory backwards.
11.OF : Overflow Flag. Set if signed arithmetic operations result in a value too large for the register to contain.
12-13.IOPL : I/O Privilege Level field (2 bits). I/O Privilege Level of the current process.
14.NT : Nested Task flag. Controls chaining of interrupts. Set if the current process is linked to the next process.
16.RF : Resume Flag. Response to debug exceptions.
17.VM : Virtual-8086 Mode. Set if in 8086 compatibility mode.
18.AC : Alignment Check. Set if alignment checking of memory references is done.
19.VIF : Virtual Interrupt Flag. Virtual image of IF.
20.VIP : Virtual Interrupt Pending flag. Set if an interrupt is pending.
21.ID : Identification Flag. Support for CPUID instruction if can be set.



'Programming' 카테고리의 다른 글

QEMU Detection  (0) 2013.01.28
Anti Debugging with POP SS  (0) 2013.01.28
x86 linux idt hooking  (0) 2013.01.24
gcc inline assembly  (0) 2013.01.24
x86 address/operand 16/32 bit setting  (0) 2013.01.23