본문 바로가기

Memo

어셈블리어 상태 레지스터(assembly status register)

이번시간에 설명할것은 어셈블리어 상태 레지스터 입니다.

원래는 가장 많이 쓰이는 아래 올린 Intel x86 FLAGS register 기준으로 설명하려 하였으나

표 출처 : http://en.wikipedia.org/wiki/FLAGS_register

Intel x86 FLAGS register[1]
Bit # Abbreviation Description Category
FLAGS
0 CF Carry flag Status
1 1 Reserved  
2 PF Parity flag Status
3 0 Reserved  
4 AF Adjust flag Status
5 0 Reserved  
6 ZF Zero flag Status
7 SF Sign flag Status
8 TF Trap flag (single step) System
9 IF Interrupt enable flag Control
10 DF Direction flag Control
11 OF Overflow flag Status
12-13 IOPL I/O privilege level (286+ only), always 11 on 8086 and 186 System
14 NT Nested task flag (286+ only), always 1 on 8086 and 186 System
15 0 Reserved, always 1 on 8086 and 186, always 0 on later models  
EFLAGS
16 RF Resume flag (386+ only) System
17 VM Virtual 8086 mode flag (386+ only) System
18 AC Alignment check (486SX+ only) System
19 VIF Virtual interrupt flag (Pentium+) System
20 VIP Virtual interrupt pending (Pentium+) System
21 ID Able to use CPUID instruction (Pentium+) System
22 0 Reserved  
23 0 Reserved  
24 0 Reserved  
25 0 Reserved  
26 0 Reserved  
27 0 Reserved  
28 0 Reserved  
29 0 Reserved  
30 0 Reserved  
31 0 Reserved  
RFLAGS
32-63 0 Reserved  

Text is available under the Creative Commons Attribution-ShareAlike License


종류가 많아 흔히 쓰이는 우리가 플래그 레지스터라 부르는 16비트의 8086을 기준으로 설명하겠습니다.

(EFLAGS부턴 32비트에서 사용되는 플래그 레지스터 RFLAGS부턴 64비트에서 사용되는 플래그 레지스터 들입니다.)

상태 레지스터의 구조는 프로세서의 설계에 따라 플래그의 기능이 약간씩 다를 수 있으며, 일부 아키텍처는 상태 레지스터가 존재하지 않습니다.


실습툴은 Immunity Debugger(이뮤니티 디버거)를 활용했습니다.

windows가 x64환경이라 16비트 8086에 가까운 debug.exe가 없습니다. 따라서 여기선 32비트로 보도록하겠습니다.

Ollydbg(올리디버거)의 후속작입니다.

열은 파일은 정보보호올림피아드 2012에 나온 파일입니다.

리버싱법 때문에 그냥  적당한 PE 파일 아무거나 고르는과정도 중요해졌네요.


왼쪽에 보이는

C

P

A

Z

S

T

D

O

요것들이 이번시간에 포스팅할 플래그들입니다.

보통 저렇게 앞글자만 나타내거나 CF PF AF ZF SF TF DF OF 이런식으로 Flag의 F만 붙여 표기하는 경우가 많습니다.


8086의 플래그들은 크게 상태 플래그(Status flags)와 시스템 플래그(System flags, Control flags)로 나뉩니다.

뒤에 덧붙였듯이 시스템 플래그 전체나 일부(DF, IF, TF 등)를 제어 플래그 라고 부르기도 하는것같습니다.

여기서 설명할 플래그들은 TF를 제외한 모든 플래그가 산술, 논리연산의 결과를 반영하는 상태 플래그에 속합니다.

여기선 풀네임과 설명, 예제 순서로 진행하겠습니다.


1. CF(Carry Flag)

-계산하다가 올림또는 빌림이 발생한경우 1이됩니다.


2. PF(Parity Flag)

-연산결과에서 최하위바이트(8비트)가 1인 비트의 수가 짝수이면 1 홀수일경우 0이됩니다.

이것때문에 삽질많이했습니다.

다들 최하위 바이트라는 설명을 안해서

아래 예제 1에서부터 1이 홀수개인데 패리티비트가 왜 활성화 되있지? 이러고 있었네요;;ㅎㅎ

32비트 레지스터에서도 마찬가지로 이렇게 생긴 수가 있다 가정했을때

00111100 10101101 00011111 11010111

00010000 00101010 00011111 10010111

앞의 3바이트는 뭐가 되었던 무시하고 뒤에 8비트만 보고 판단합시다.


3. AF(auxiliary Carry Flag, half Carry Flag)

-예전에 많이 쓰인듯한 플래그입니다. 4비트 시절 OF같은게 지금까지 이어져 온건 아닐지...

상위 비트에서 뭘하던 하위 4비트에서 자리올림이나 빌림이발생하는 순간 그냥 1이 됩니다.

-이러한 플래그는 일반적으로 이진화십진법에 사용됩니다.
(Binary Coded Decima, 이진수 4자리를 묶어 십진수 1자리로 사용하는 기수법)


4. ZF(Zero Flag)

-개인적으로 가장 처음으로 알게된 플래그입니다. 연산결과가 0이될경우 1이됩니다.


5. SF(Sign Flag. Negative Flag)

-부호비트가 1인경우 1, 0인경우 0으로 즉 음수인경우 1, 양수인경우 0이됩니다.

최상위 비트의 값과 같다고 보시면 되겠습니다.


6. TF(Trap Flag, Debug Flag)

-디버깅에 사용되는 플래그입니다. 설정된경우 CPU는 명령하나를 실행할때마다 자동적으로 내부 인터럽트 1이 발생합니다.

설정된경우 디버깅시 single-step이 가능합니다.


7. DF(Direction Flag)

-스트링을 처리할때 1일 경우 처리 instruction이 자동으로 감소(높은 주소에서 낮은 주소로)하고, 0일 경우 자동으로 증가(낮은 주소에서 높은주소로)합니다.


8. OF(Overflow Flag)

-계산 결과값이 너무크거나 작아서 지정해준 피연산자(operand)의 데이터 타입을 벗어난경우를 1이 됩니다.

(ex 계산결과가 부호 비트까지 넘어왔을때)


이제 예제 수행해볼 차례네요.

먼저 명령어를 우리가 원하는 명령어로 대체하는 방법을 알려드리겠습니다.

별거없습니다. 그냥 저기 보이는 컬러풀한 명령어들 더블클릭하면 이렇게 수정창이 나오는데요(올리디버거도 동일)

요기에 원하는 명령어 그대로 입력해주시고  엔터하시면됩니다. 편집기 작성하듯이 다음줄로 계속 이어서 쓸수있습니다.

그럼 시작합니다.


예제1

mov eax, 0x0fffffff

mov ebx, 1

add eax, ebx

00001111111111111111111111111111

00000000000000000000000000000001

단순히 더한결과입니다. 

00010000000000000000000000000000

최하위 한바이트에 1이 한개도 없지만 0또한 짝수이므로 패리티 플래그(PF) 활성화

4비트에서 5비트로 넘어온값이 있으므로 보조 캐리 플래그(AF) 역시 활성화입니다.


예제2

mov eax, 0

mov ebx, 1

sub eax, ebx

여기선 뻥뻥 터졌네요.

32비트 범위를 초과한 경우 다음비트가 있다 가정되고 거기서 빌림 비슷한 것이 발생해서 0xFFFFFFFF가 되었다 추정한다면 캐리 플래그(CF)가 발생한것은 이해되었습니다.

다음으로 패리티 플래그인데요

11111111 11111111 11111111 11111111

아시다시피 최하위 한바이트의 1이 8개, 짝수개이므로 활성화되었습니다.

그리고 보조 캐리 플래그(AF)는 하위 4비트를에서 올림이 발생해서 활성화되었습니다.


예제3

mov eax, 7FFFFFFF

mov ecx, 1

add eax, ecx

01111111111111111111111111111111

00000000000000000000000000000001

요거 두개 더한 결과입니다.

10000000000000000000000000000000

아시다시피 최상위 비트는 부호 비트로 사용되고있고 1을 더함으로써 범위가 초과된것을 확인하실수 있습니다.

보조 캐리 플래그(AF)는 위에서 말한 이유로 연산결과가 하위 4비트를 죄다 넘겨 0으로 만들었으므로 활성화,

부호비트에 1이 있으므로 음수가 되서 사인 플래그(SF)와 오버플로우 플래그(OF)가 활성화됩니다.


예제4

mov eax, 0

test eax, eax

add eax, eax

sub eax, eax

결론부터 말하자면 위에 test, add, sub 명령어를 다 수행해도 저 플래그들은 불변합니다.

test eax, eax는 0과 0이 같은지 비교해서 결과가 0이되어서 제로 플래그가 셋팅되는것이고

add는 0에다 0더하기, sub는 0에다 0을 뺀셈입니다.

또한 마지막 한바이트의 1의 수는 여전히 0개이므로 짝수입니다.

00000000 00000000 00000000 00000000

따라서 패리티 플래그(PF)와 제로 플래그(ZF)가 활성화됩니다.


예제 5

mov eax, 0x80000018

mov ebx, 0xe8

sub eax, ebx

10000000000000000000000000011000

00000000000000000000000011101000

이제 익숙해진 분들은 보기도전에 까닭을 짐작하실것 같습니다.

01111111111111111111111100110000

이번엔 음수 -2147483624에서 232를 빼줬습니다.

언뜻보면 음수에서 양수를 빼줬으니 절대값이 더큰 음수가 되겠군. 이지만 Dword(32비트)의 한계에선 그렇지가 않습니다.

-2147483856 보통 생각하는 계산 결과에서 얼마나 어떻게 벗어났는지 확인해봅시다.

[guest@ftz .pa]$ cat overflow.c
main(){
int a=-2147483624;
int b=232;
printf("%d\n",a-b);
}
[guest@ftz .pa]$ ./overflow
2147483440

Qword(64bit)환경이신 분들은 한번 Dword로 전환하시고 사용해보시길바랍니다.

결과적으로 저기 부호비트자리에서 1을 빌려와 계산해버리는겁니다.

32비트 레지스터 eax는 빌려준 32번째 레지스터가 부호비트 일수밖에없을테니 결국 양수가되고 말았습니다.

이것역시 지정해준 데이터 타입을 벗어났으므로 오버플로우 플래그(OF)가 활성화됩니다.


여기까지 읽으셨다면 여러분은 난해한 플래그를 어느정도 정복한것입니다!

아직 이해못한 부분이 있으시면 질문전에 저 디버거를 사용해보세요. (질문은 이메일, 댓글 모두환영합니다 :-)

이렇듯 예제를 직접만들고 실험하다보면 어느새 리버서가 되어있는 자신을 발견할수있을지도 모릅니다.

마지막으로 팁한가지 드리며 마치겠습니다.

이와같이 적당히 많이실험할수있도록 add eax, ecx로 쭉 셋팅해주시고

eax값을 실험하고싶은 자리수

ecx값을 자리 올림용으로 1로 두거나 뺄셈용으로 -1로 두거나

단순히 직관적으로 계산결과에 따른 플래그를 보고싶으시면 0으로 두신후

F8을 눌러서 확인해줍시다.

mov로 직접 넣어주는것보다 옆에서 레지스터값 더블클릭해서 수정해주는게 훨씬 빠릅니다.

그리고 머릿속에 공학계산기 하나쯤 있으신 분들 제외하면 이건 필수아이템입니다.

위에 줄그은 이진수 나오는부분 둘째줄이 32비트 범위입니다. 키보드 필요없이 0과 1을 클릭만 해주시면 됩니다.

Hex로 두면 저렇게 셋팅한 값들이 바로 16진수로 나옵니다. 필요한 수 만들어서 Hexadecimal에 그대로 붙이시면 간단하죵:)